I've built a lot of websites. Client brands, product dashboards, landing pages that live for a season and get replaced. But building something for yourself is a different kind of challenge entirely. There's no brief, no deadline, no one to sign off. Just you and your taste, arguing.
This site is the result of that argument. Not a portfolio in the traditional sense, more of a living workspace. A place where I could push on the details that usually get cut for time, and explore the gap between how something works and how it feels.
This is a walkthrough of how it was built, the decisions that shaped it, and the interactive techniques underneath.
The Starting Point
The stack is intentionally simple. Next.js 14 with the App Router, React 18, TypeScript, and plain CSS with custom properties. No Tailwind, no component library, no design system imported from somewhere else.
That last part was deliberate. Tailwind is great for speed but it flattens aesthetic decisions into utility classes. When you're building something personal, the CSS is the design. Every property, every value is a choice. I wanted to feel each one.
The monospace font, ABC Monument Grotesk Mono, sets the tone. It signals precision without being cold. It makes the whole site feel like a workspace, a terminal, a place where things are being made rather than just displayed.
Underneath everything is a canvas layer running an ASCII shader, a subtle noise texture that gives the background depth. It's almost invisible, which is the point. The best details are the ones you feel before you notice.
Spring Physics Over Easing Curves
Traditional CSS animations use easing curves: ease-in-out, cubic-bezier, predefined mathematical functions that describe how a value changes over a fixed duration. They work. But they never quite feel alive.
Spring physics are different. Instead of defining a duration, you define physical properties: stiffness (how snappy the spring is) and damping (how quickly it settles). The animation emerges from the physics, not from a timeline.
This is how the exploration list hover works on the homepage. When you move your cursor between rows, a floating highlight follows, but it doesn't teleport. It chases your cursor with a spring, overshooting slightly before settling. The motion is organic. It feels like the UI is responding to you, not just executing a script.
Try it below. Drag the sliders to change the spring properties and hover over the list to feel the difference:
High stiffness with low damping gives a snappy, bouncy feel. Low stiffness with high damping creates a lazy, heavy motion. The defaults on the site (stiffness: 400, damping: 35) sit in a sweet spot that feels responsive without being hyperactive.
Under the hood, this uses useMotionValue and useSpring from Motion (the library formerly known as Framer Motion). The highlight position is a motion value that gets spring-smoothed before being applied to the element's y transform. No CSS transitions involved.
Theming as Material
Most dark modes are an afterthought. Invert the text, darken the background, call it done. But when theming is treated as a material, as a fundamental property of the design surface, it changes everything.
The site uses a data-theme attribute on the root <html> element. Every color references a CSS custom property: --text, --muted, --bg, --accent. When the theme flips, every color updates simultaneously through a single attribute change.
The toggle itself is more than a state swap. It fires a haptic pulse (on supported devices), plays a subtle click sound, and the icon transitions with a scale animation. These micro-interactions make the toggle feel physical, like flipping a switch in the real world.
Here's a scoped playground. This toggle only affects the card below, not the page:
Toggle to see how the design
language shifts between modes.
Notice how the accent color shifts too. Light mode uses #86DBEF (a cool blue) while dark mode uses #CE7FFF (a soft purple). These aren't arbitrary. They were chosen to feel native to their respective backgrounds, to glow without clashing.
The key insight: theming isn't about supporting two modes. It's about designing two complete visual identities that share the same structure but express different moods.
Depth and Dimension
Flat design solved real problems. But it also created a new one: everything feels the same depth. Buttons, cards, backgrounds, all occupying the same z-plane. The eye has nothing to anchor to.
The passport card on the homepage breaks this flatness. It responds to your mouse position (or device tilt on mobile) with 3D rotation driven by spring physics. Move your cursor over it and it tilts toward you, creating a tangible sense of depth.
The technique layers several effects. CSS rotateX and rotateY transforms are derived from mouse position and smoothed through springs. A reactive inner shadow shifts with the tilt angle, reinforcing the illusion that this is a physical surface catching light from different directions.
But the most interesting layer is the WebGL foil shader. It simulates the iridescent holographic strips you find on real passports and credit cards. The shader generates an equirectangular environment map, then uses Fresnel reflections, thin-film iridescence, and metallic flake particles to create that rainbow-shift effect you see when tilting a foil sticker under light. The mouse position perturbs the surface normal, so moving your cursor sweeps different rainbow colors across the surface.
Move your mouse over the card below. The foil overlay responds to your cursor in real time:
The spring config (stiffness: 150, damping: 20) is deliberately softer than the list highlight. The card should feel weighty, like picking up an actual card and tilting it in your hand. Too snappy and it feels digital. Too slow and it feels broken.
The shader adapts to the page theme too. In light mode it uses overlay blend mode at 60% opacity for a subtle effect. In dark mode it switches to screen blending at 50%, making the iridescence glow against the dark surface. On mobile, the device gyroscope takes over from the mouse, so the card responds to how you physically hold your phone.
The Small Things
The biggest impact often comes from the smallest decisions. A few that shaped this site:
Haptics. The theme toggle, the passport card tap, all fire subtle vibration patterns using the web-haptics library. On iOS Safari, these feel surprisingly physical. They turn interactions from visual events into tangible ones.
The mascot. A small pixel sprite in the footer that cycles through idle, hover, and sleep states. It blinks randomly when awake, breathes when asleep, and dances when you hover. It does nothing functional. But it makes the site feel inhabited, like someone lives here.
Hover-to-play video. Work cards play their video on hover and pause on leave. No controls, no loading spinners. The content just comes alive when you look at it.
Selection highlight. Custom text selection colors that match the theme. A tiny detail that 99% of visitors will never consciously notice, but it subtly reinforces that every pixel was considered.
Smooth scroll. Lenis handles scroll smoothing across the entire page, giving movement a consistent, polished feel rather than the default browser jank.
Getting Lost (The Fun Way)
Most 404 pages are dead ends. A line of text, a link back home, and nothing else. But a dead end is also an opportunity. If someone is already lost, why not make the journey back memorable?
The 404 page on this site is a connect-the-dots puzzle. Twenty-four numbered dots outline a cat, and you draw freehand between them to trace the shape.
The interaction is continuous, not point-and-click. You press and drag from dot to dot, drawing a real stroke. Lift your finger and you can pick back up where you left off. A purring sound loops while you draw and pauses when you stop, and when the final dot snaps into place, a meow plays before the site takes you home.
Under the hood, it's an SVG with pointer event handlers that track freehand movement and snap when the cursor enters each dot's radius. The path is drawn directly to the DOM via refs for performance, no re-renders per mousemove. The dots are theme-responsive: blue in light mode, purple-pink in dark mode, cyan in retro.
It's a small thing. Most people will never see it. But for the few who stumble into a broken link, it turns a moment of frustration into a moment of play. And that's the whole philosophy of this site condensed into one page.
The gap between good and great isn't in the features. It's in the texture.
Building this site was a reminder that the best digital experiences borrow from the physical world, not literally, but emotionally. Weight, resistance, response, warmth. These are things we understand instinctively, and when software echoes them, even subtly, it stops feeling like a tool and starts feeling like a place.
If you're a designer who codes or a developer who designs, I'd encourage you to build your own bridge. Not because the world needs another personal site, but because the process of building one, where you control every decision and every pixel, teaches you things that no client project ever will.
The source is the site. Every technique mentioned here is running right now on the page around you.