Building This Website: From Blank Canvas to Production
How I rebuilt my portfolio from a Hugo template into a hand-crafted Astro site with animated SVG diagrams, a custom OKLCH design system, and React islands.
What started as “I should probably have a website” turned into something much bigger. I’d been using a Hugo template for years and fighting it every time I wanted to change anything. At some point I decided to just start fresh and build exactly what I wanted.
This is a look at how it came together — the stack, the design choices, and what I learned along the way.
Astro 5 static generation · React 19 islands for interactivity · Tailwind CSS v4 with OKLCH colors · Framer Motion animations · MDX content · Vercel hosting · Built with Windsurf (Cascade) as my AI development environment.
The Starting Point
I had a Hugo site. It worked, technically. But every small change meant digging through someone else’s Go template logic and wondering why things broke. I wanted something where I had full control — the animations, the colors, the way content flows.
The goals were straightforward:
- Full creative control — every interaction and visual detail, my call
- Fast by default — static HTML, no framework tax on page load
- Shows how I think — not just a CV with a dark background
- Easy to maintain — write in Markdown, push to deploy
Architecture
The idea is simple enough: generate static HTML at build time, only load JavaScript where you actually need it.
Astro’s island architecture is what makes this practical. Pages arrive as plain HTML — no framework runtime, no hydration waterfall. React only shows up for the parts that genuinely need it: the portfolio diagrams, blog tag filtering, the mobile menu.
Everything else is zero-JS static HTML. It’s a good trade-off.
Tech Stack
Content collections with Zod schemas, MDX rendering, automatic sitemap and RSS generation, file-based routing, and ClientRouter for smooth page transitions.
Animated SVG diagrams, portfolio hub, blog tag filtering, and mobile navigation. Each hydrated on demand via client:visible to keep the initial bundle small.
The new @theme directive with OKLCH colors, custom animation easings, elevation tokens, and a typography scale. All defined as CSS custom properties.
Strict mode everywhere. Every prop, every utility, every component is typed. Biome handles linting and formatting with zero config files to maintain.
Design System
I didn’t want the typical dark-mode-with-green-terminal-accents look. I landed on what I call “Slate + Electric Blue” — cool neutral backgrounds with one high-chroma accent. Confident without being loud.
Color Palette
The entire palette uses the OKLCH color space. Unlike HSL, it’s perceptually uniform. A lightness of 0.65 looks equally bright at any hue, which means contrast ratios are predictable and the blue accent never clashes with the lavender secondary.
Typography
The Portfolio: Interactive Expertise Diagrams
The portfolio is probably my favorite part. Instead of listing projects as cards, I built animated SVG diagrams for each expertise area — rendered as React islands with Framer Motion.
Each diagram animates in on scroll with flowing particles on subtle loops. Just enough motion to feel alive without being distracting. Getting the balance right took more iteration than anything else on the site.
See them in action on the portfolio page.
Performance
What keeps it fast:
- Static HTML by default — pages render before any JS executes
client:visibleon below-fold islands — JS only loads when you scroll to it- Font preconnect hints — avoids render-blocking font loads
- Cookieless analytics via Vercel, injected at the edge with zero bundle cost
ClientRouterfor smooth page transitions after first load
Lessons Learned
Defining the color palette, typography, spacing scale, and animation tokens first meant every component built afterwards was automatically consistent. This was the single highest-leverage decision of the entire project.
Coming from React SPAs, opting in to JavaScript per-component felt counterintuitive. But for a content-heavy site, the performance difference is night and day. Most pages ship zero JS.
Supporting light and dark themes doubles the design work. For a portfolio that leans into data-visualization aesthetics, dark mode is the natural choice. Committing to one theme and doing it properly was the right call.
I built this using Windsurf with Cascade as my primary development environment. It’s remarkably effective for scaffolding components and refactoring across files. But the visual refinements (spacing, timing, hierarchy) always came down to manual iteration.
Astro · React · Tailwind · TypeScript · Framer Motion · Vercel