← Portfolio
HAND-BUILT SITE SHIPPED

This site.

The site you are standing in. Built with AI as the junior dev and me as the extremely picky client.

SITE·UPD APR 2026

01 · WHY IT EXISTS

I wanted a home on the internet that felt like me: warm, a little playful, allergic to templates. Building it became its own case study in directing AI tools, when to trust the first draft, when to say no five times in a row, and how taste survives the process.

02 · WHO IT'S FOR

Anyone building a personal site with AI tools who wants proof it does not have to look like everyone else's. Steal the approach, not the palette.

03 · WHAT'S ACTUALLY HERE

No framework, no build step

Every page is a plain .html file. No React, no bundler, no npm install standing between an edit and a deploy. vercel.json sets framework: null and cleanUrls: true, so /portfolio/this-site.html on disk serves at /portfolio/this-site with no rewrite rules to maintain.

Hand-written inline SVG dioramas

The little animated scenes on the homepage and on case-study heroes, like the one above, are hand-authored SVG markup in js/scenes.js, not exported art or a canvas library. Each one is a named entry in a registry keyed by data-scene, so a page just asks for website or dashboard and gets the real, inspectable markup.

Security headers on a static deploy

vercel.json ships Strict-Transport-Security (two-year max-age, preload), X-Content-Type-Options: nosniff, X-Frame-Options: DENY, and a Referrer-Policy of strict-origin-when-cross-origin on every response, plus a one-year immutable cache on /assets and an hour-long cache on /css and /js. None of that needs a server; it is config on a static host.

Motion that actually respects the OS setting

Rather than an animation:none override sprinkled everywhere, one media query in css/site.css clamps every animation and transition's duration to near-zero when prefers-reduced-motion: reduce is set, while leaving each animation's fill-mode end state intact. Anything JS-driven checks the same signal through a shared gbReducedMotion() helper in js/site.js, so the reduced-motion contract is enforced in exactly one place either way.

Fonts that load without a layout jolt

The three typefaces below are self-hosted variable/static .woff2 files, preloaded in the <head> of every page and versioned as .v1. assets so a font update never fights a stale cache. font-display: swap keeps text visible while they arrive.

04 · RUN IT YOURSELF

01

Start with one true sentence about yourself. Mine is in the hero.

02

Pick three fonts and two colors, then refuse to add more. Constraints are the style.

03

Make the AI justify every design choice. Keep the ones that survive.

05 · STACK

HTML/CSS Claude hand-tuned animations stubbornness

TRY IT — DESIGN SYSTEM INSPECTOR

This is the actual palette, type, and motion contract the page you're reading is built from, pulled straight out of css/site.css. Toggle reduced motion below and watch it change for real.

Palette
Ink #171009 · Paper #f3ead9 · Amber #efa85c · Violet #b48fd9 · Sage #93b573

Type
Instrument Serif — headings
Space Grotesk — body text
IBM Plex Mono — labels and mono UI

Motion
Every animation and transition on this site is clamped to near-zero duration when your system's prefers-reduced-motion is set to reduce, via a single media query in css/site.css. It is not opt-in here; it is already respecting your OS setting right now.

Turn on JavaScript to toggle swatches, typefaces, and a live reduced-motion demonstration yourself.