My Portfolio Site
A showcase of my personality and skills.
My website's gone through some major iterations, from clunky, to rough, finally to polished now. Below, I'll highlight the last major design version changing into the current iteration. You can slide between the old design and new (TODO!).
About Page
Not getting into the crazy-long wordiness of my previous site (which must must done the job somewhat, considering the fact hat it helped land me a grad job!), the design itself was so plain and outdated. It didn't scream out "capable developer with an eye for design", which I like to think is what a fair review of Alex McCaughran would say!
Projects Page
Todo...?
Showcase Pages
Todo...?
Terminal
This component tells you a bit about how I like to spend my time, in an interesting way. It mimics a Linux terminal interface by using HTML, CSS, and JavaScript to achieve a dynamic, interactive experience. An array of strings are defined, which describe some of my hobbies, like below:
const stringsArray = ["develop interesting code", "do jiu jitsu", "play guitar", "listen to podcasts" ,"learn new skills",
"go bouldering", "get lost in a good story", "travel to new places",
"solve problems", "drink coffee"];These are then shuffled randomly, so that each page load shows some different ones, giving a different flavour of me very time!
TODO, write how it works later
// Function to handle typing and deletion
function handleTyping() {
if (!isElementInViewport(outputElement)) {
// Pause if not in viewport
animationPaused = true;
// Mark if we were in the middle of deleting
if (isDeleting) {
deletionWasInProgress = true;
}
return;
}
if (animationPaused) {
// Resume the animation if it was paused
animationPaused = false;
if (isDeleting && !deletionWasInProgress) {
// If we weren't in the middle of deleting, pick up the next string
currentCharIndex = 0;
isDeleting = false;
currentIndex = (currentIndex + 1) % stringsArray.length;
}
deletionWasInProgress = false; // Reset flag as we are resuming
}
if (isDeleting) {
// Delete characters
if (outputElement.textContent.length > 0) {
outputElement.textContent = outputElement.textContent.slice(0, -1);
setTimeout(handleTyping, deletionSpeed);
} else {
// When deletion is complete, reset flags and proceed to next string
isDeleting = false;
deletionWasInProgress = false;
currentIndex = (currentIndex + 1) % stringsArray.length;
setTimeout(handleTyping, pauseDuration);
}
} else {
// Type characters
if (currentCharIndex < stringsArray[currentIndex].length) {
outputElement.textContent += stringsArray[currentIndex][currentCharIndex++];
setTimeout(handleTyping, typingSpeed);
} else {
// Start deleting after typing is complete
currentCharIndex = 0;
isDeleting = true;
setTimeout(handleTyping, pauseDuration);
}
}
}handleTyping is configurated from a main function, which co-ordinates things like:
- where to type
- how fast to type characters
- how fast to delete characters
- how long to pause between typing and deleting.
Work Timeline
Each icon on my work timeline is wrapped in a <div>, which contains a date range for each data point. This div has a tooltip child, containing details of each career stage. This tooltip is hidden at first, but an event listener waits for the mouse to enter over these divs, then applies a class to the tooltip which makes it visible. This is fairly simple stuff so far, but the below javascript also accounts for the size of the screen and position of the parent div then positions the tooltip relative to both. This ensures that the tooltip info is always visible.
document.querySelectorAll(".event").forEach((event) => {
const tooltip = event.querySelector(".tooltip");
event.addEventListener("mouseenter", (e) => {
const rect = event.getBoundingClientRect();
const tooltipWidth = tooltip.offsetWidth;
const tooltipHeight = tooltip.offsetHeight;
const tooltipX = rect.left + rect.width / 2 - tooltipWidth / 2;
const tooltipY = rect.top - tooltipHeight - 10;
tooltip.style.left = `${Math.max(0, tooltipX)}px`;
tooltip.style.top = `${Math.max(0, tooltipY)+100}px`;
tooltip.style.opacity = "1";
tooltip.style.visibility = "visible";
tooltip.style.transform = "scale(1)";
});
event.addEventListener("mouseleave", () => {
tooltip.style.opacity = "0";
tooltip.style.visibility = "hidden";
tooltip.style.transform = "scale(0.9)";
});
});