Designing a Nostalgic Portfolio: Merging 3D Aesthetics with User Engagement

Aug 27, 2026 982 views

Goodgrowth: Boot Sequences, Spinning Discs, and the Art of the Portfolio

Explore the journey behind Goodgrowth, from console-inspired concepts to 3D aesthetics and intricate technical elements that shape the user experience.

Building a personal portfolio is a blend of creativity and constraint. While the reins are wholly in your hands, the freedom to choose can ironically paralyze you. When I set out to create my site, I committed to an aesthetic vision and a timeline, ensuring that my ambitions didn’t end up as just another forgotten project.

Growing up in the late '90s and early 2000s, the thrill of video game startup screens—particularly from consoles like PlayStation and Dreamcast—profoundly influenced my design choices. My aim was to evoke that nostalgic feeling of powering on a console, where each activation unveiled a new adventure. It’s not just nostalgia; it’s about capturing the essence of simpler, more engaging moments.

The Journey Begins with a Concept

To kick things off, I aimed to push my boundary. Armed with solid HTML, CSS, and JavaScript skills, I ventured into unfamiliar territories of web development. That’s where Claude Code entered the picture, facilitating my creative visions and enhancing my workflow.

The conceptual core involved a looping scroll feature overlaying a globe showcasing my work, creating an engaging way for users to explore projects via swipe or scroll interactions. Additionally, a central rotating disc would hark back to those nostalgic startup animations, enriching the user experience further.

Tech Specifications

  • Three.js: Core framework for 3D integration
  • GSAP: For fluid animations
  • Lenis: To enable smooth scrolling
  • Vite: The chosen build tool
  • Web Audio API: For authentic sound experiences
  • No framework: Pure JavaScript & ES modules
  • Cinema4D: For modeling CD and Floppy Disk assets, converted to GLB for WebGL

Ignition Sequence

Ensuring that the site’s journey felt cohesive—from the preloader to project transitions—was vital. The journey commenced with an "Insert Disc" screen, prioritizing audio to elevate the immersive experience above mere visuals. The disc's rotation was implemented with straightforward CSS, avoiding excessive code while keeping it clean and effective. During this initial loading phase, I opted to swap letters for numbers in the logo to link the preloader with my branding.

One of the most complex parts involved creating thumbnails that would fan out and spiral back before revealing project details. I initially attempted to script a spiral path but soon realized that a spiral could be simplified into an angle increment coupled with a radius reduction, yielding a more organic animation.

// algorithm for dynamically positioning thumbnails
const placePreviews = () => {
    for (let i = 0; i < prevEls.length; i++) {
        const a = ((PREV_ANGLES[i] + orbit.t) * Math.PI) / 180;
        gsap.set(prevEls[i], {
            x: Math.cos(a) * prevState[i].r,
            y: Math.sin(a) * prevState[i].r,
        });
    }
};

// angle speeds up while every radius approaches zero
out.to(orbit, { t: '+=140', duration: 1.3, ease: 'power1.in' }, 0);
prevEls.forEach((el, i) => {
    out.to(prevState[i], { r: 0, duration: 0.55, ease: 'power2.in' }, 0.09 * i);
});

The stagger effect added a sense of trailing motion, where the followers of the thumbnail 'spiral' retain their orbit while the leaders rush towards the center.

The Global Interface

Throughout the globe’s creation, I faced challenges with angles that affected the visual balance. An orthographic view ultimately provided the accuracy I was after, ensuring that the globe's poles didn't distort the design. Using flat drawings of nested ellipses guided the placement for unwavering curves.

Rethinking the project strip, I opted for an approach where it doesn’t simply slide horizontally but instead arcs across the globe's surface. This design decision involved several iterations but resulted in a dynamic single texture that hugged the sphere seamlessly, creating a more engaging navigation experience.

One interesting technical note: invisible elements like the magnetic detents help ensure that the visual presentation keeps varying slightly with every interaction, making certain that the globe never appears static. Despite how subtle, this detail adds depth to the experience.

const TILE_STEP     = (Math.PI * 2) / projects.length;
const MERIDIAN_STEP =  Math.PI / MERIDIAN_COUNT;

// ensuring one tile step correlates with one meridian step
const GLOBE_SPIN =
  (Math.max(1, Math.round(0.6 * TILE_STEP / MERIDIAN_STEP)) * MERIDIAN_STEP) / TILE_STEP;

Early mobile testing revealed delayed transitions when swiping through projects. The root cause was identified: simultaneous gestures conflicted with the drag and release logic. Refining the release mechanism resolved the issue, allowing for a smoother user experience.

const dir = netDX > 0 ? -1 : 1;
// snap based on the starting position of the gesture
targetRot = (Math.round(touchStartRot / step) + dir) * step;

Interactivity at Play

To enrich user engagement, I focused on mouse interactions. Both the globe's rotation and the central CD’s spin were designed to respond to scrolling, while maintaining their inherent movements when idle.

On the project pages, hovering over project icons invokes a visual distortion that resembles a chromatic aberration. This was achieved by blending flow maps and velocity fields to create a stunning liquid-like effect. The choice to allow this distortion to decay instead of resetting provided a smoother experience that felt integrated rather than abrupt.

vec3 prev = texture2D(uPrev, vUv).rgb * 0.94;   // decay makes the trail
prev += vec3(uVel * s, s * length(uVel));       // involves velocity calculations

// red and blue sample at different offsets for a chromatic split
float cr = texture2D(uMap, uv + flow * 0.05).r;
float cb = texture2D(uMap, uv - flow * 0.05).b;

Capturing Retro Vibes

Channeling the nostalgic polygonal aesthetics of the Y2K gaming scene, I adopted a dithered approach in my site's shaders, which helped maintain authenticity. The Bayer dither functions created a cohesive display reminiscent of classic graphics.

The spinning CD at the center was an exercise in detail; I worked extensively to replicate its reflective properties and ensure that the bow-tie pattern reflected accurately based on viewing angle, adding an authentic touch to the experience.

float sweep = dot(normalize(vDiscNormalV), normalize(-vViewPosition));

// the sweep adjusts the cross's pivot point for realistic reflections
float axis = ang - sweep * 1.6;
float bowtie = pow(abs(cos(axis)), 6.0);   // creates the characteristic two-lobe cross

// edges now show hue shifts consistent with spectral fringes along dark layouts
float edge = sin(axis * 2.0);
vec3  fringe = hsv2rgb(vec3(fract(0.30 + edge * 0.22), 0.85, 1.0));

One significant benefit of Three.js is its capacity to easily apply textures to GLBs, minimizing dependencies on external texturing solutions. This efficiency significantly reduced the size of models while preserving the expected visual fidelity, enhancing loading speeds without sacrificing quality.

Smooth Transitions

As I sought to provide a fluid user experience, transitions were key. From landing pages to project views, each transition utilized a five-bar wipe effect. This ensures the project title seamlessly appears as the old view dissolves, preventing distraction from the content itself.

This isn’t your typical fade transition. The five bands that slide to reveal the next content are crafted to act as physical barriers, creating a dynamic effect that maintains user engagement.

// reveal managed through dynamic width adjustments for the bands
inner.style.top = `${-top}dvh`;

// collapsing bands to zero width reveals the content — leading bands create entrance
pwHalves.forEach((pair, i) => {
    tl.to(pair, { width: 0, duration: 0.6, ease: 'power3.inOut' },
        1.8 + Math.abs(i - (pwHalves.length - 1) / 2) * 0.07);
});

The page transitions are among the aspects I take the most pride in. They're not just functional but were crafted to feel alive and interactive, providing feedback during exploration. A notable feature was the scrolling progress fills on project icons, giving users a clear indication of their journey through the site.

Yet, navigating transitions proved more complicated than anticipated. To prevent user frustration, I implemented a mechanism that connects project icons to a fixed position at the top during transitions, ensuring a smooth flow when interacting with the interface.

const IDLE_MS  = 220;    // idle time for trackpad interaction
const MAX_WAIT = 4000;   // max delay to prevent hanging

const settle = () => {
    const now = performance.now();
    if (now - _swallowLastInput < IDLE_MS && now - t0 < MAX_WAIT) {
        requestAnimationFrame(settle);   // waiting for user interaction to cease
        return;
    }
    stopInputSwallow();
    _pinTop = false;
    el.detail.scrollTop = 0;
    transitionLock = false;
};

Soundscapes of Memory

The audio aspect can easily be overlooked, but it’s a defining feature of any console experience. I wanted to ensure this aspect of my site evoked nostalgia. For this, I collaborated with Lane Fujita, who understood the gaming culture as deeply as I do. Not only did he provide stunning audio, but he also included a detailed timing guide to synchronize the sound with visual events.

However, I encountered significant performance challenges while integrating sound. Initially, loading screens experienced lag when audio was active—the culprit? Multiple invocations of HTMLAudioElement.play() flooding the animation frame. After extensive debugging, I discovered the issue didn’t emerge in headless Chromium; using the Safari Web Inspector on an actual device was vital for troubleshooting.

The resolution involved delegating the audio management to the Web Audio API, enabling it to run off the main thread. This significantly minimized processing delays, ensuring a smoother user experience as each audio sequence synced seamlessly with the visual flow.

Final Thoughts on Audio-Visual Integration

Navigating the challenges of integrating audio into web experiences, especially on mobile devices, has proven to be a complex yet rewarding journey. The insights drawn from this process are significant, particularly in how they illuminate the nuances of performance optimization. You'll find that details, like the subtle interplay between audio and visual elements, can make or break user experience. It's not just about getting things to work; it’s about achieving harmony between sound and visuals that elevates your project. Challenges like the silent auto-suspension of the AudioContext in iOS remind us that platform-specific nuances can derail even the best designs. But that’s where creativity thrives. The solution—employing an animated equalizer to toggle sound—represents a clever workaround that does more than address a technical hiccup: it enhances user control and engagement. Such adaptations are essential when the goal is to create an experience that resonates with users, both functionally and aesthetically. That said, the learning curve here is steep. One lesson stands out: don’t get too bogged down in optimizing for problems that you assume might be at play. During development, I fixated on shader compilation as the source of stuttering sound. It turned out that the true culprit was a different factor altogether, a realization that pushed me to rethink my approach to problem-solving. Measurement over assumption should always take precedence. When it comes to visual strategies, storyboarding animations isn’t just a nice-to-have; it's a strategic tool that clarifies your vision. Since many of us come from a background steeped in motion design, employing techniques like After Effects terminology can sharpen our focus on how animations unfold in real time. Tuning those subtleties pays off in creating a more polished end product. If you're venturing into similar projects, remember that ambitious ideas often come with unexpected hurdles. They also lead to invaluable insights. Embrace the challenges and let the process inspire you to push the boundaries of what’s possible. Don't hesitate to reach out for collaboration or conversation—this landscape thrives on shared ideas. A big shoutout goes to the Codrops team and Lane Fujita for their input and sound design expertise. Together, we’re proving that creativity flourishes in the face of obstacles. So here’s to future endeavors; may they be as enlightening as they are enjoyable.
Source: Matt Stone · tympanus.net

Comments

Sign in to comment.
No comments yet. Be the first to comment.

Related Articles

Goodgrowth: Boot Sequences, Spinning Discs, and the Art o...