I started this site the way most people do: a clean template, a light theme, a hero that said the right things and felt like nothing. It worked. It was also completely forgettable — the kind of site you close before you finish scrolling.
So I threw it out and rebuilt it as something closer to a field notebook: warm paper, ink, a single teal-pine accent, and a handful of interactions I actually cared about. These are the notes on why, and how.
The constraint that shaped everything: no animation libraries
The fastest way to make a portfolio feel generic is to reach for a motion
library and sprinkle whileInView on everything. I wanted the opposite — motion
that felt authored, not applied. So I set a rule: every "fancy" thing on this
site is hand-rolled with the platform.
- Scroll-driven reveals use CSS scroll-timeline and
IntersectionObserver, not a library. - The per-glyph heading animation is a small component that splits text into
spans and staggers a CSS
char-reveal. framer-motionisn't in the bundle. Neither isthree.js.
That constraint did more for the final feel than any single feature. When motion is expensive to add, you only add it where it earns its place.
The globe is a <canvas>, not a 3D engine
The travel page has a slowly rotating dotted Earth with little hearts on the
cities I've lived in and arcs tracing the moves between them. The obvious way to
build that is three.js and a globe library. That's also ~500KB of dependency
for something that is, visually, a field of dots.
Instead it's a 2D <canvas>: a baked grid of land coordinates, projected to a
sphere and rotated each frame, with great-circle (slerp) arcs between cities.
Drag to spin it. It's a few hundred lines and it ships nothing extra.
A résumé you type at
The hero is an interactive terminal. Type get-experience, get-projects,
get-skills — it answers from a single source of truth (lib/resume.ts), with
tab-completion and command history. It's a small wink at what I do for a living,
and it turned the most boring part of any portfolio (the CV) into the part people
actually play with.
Shipping it: Vercel + Cloudflare, ~20 minutes
The stack underneath is deliberately boring so the surface can be interesting:
Next.js 16 on the App Router, Tailwind v4 configured entirely in CSS,
next-themes for a dark-by-default toggle. Content lives in a couple of typed
data files, so updating the site is editing an array, not fighting a CMS.
Deploy was a GitHub import into Vercel's free tier, a custom domain registered at
cost on Cloudflare, and one CNAME record with the proxy turned off so Vercel
issues the certificate. That's it. No pipeline to babysit — every push to main
ships.
What I'd tell past me
Templates get you a site. They don't get you a voice. The parts of this build that took the longest — the globe, the terminal, the restraint around motion — are the only parts anyone remembers. If you're building your own corner of the internet, spend your time on the one or two things that could only be yours, and keep everything else boring.
More field notes to come.