Behind the site

How this site is built

This site doubles as a work sample. Here's how it's put together.

Fast by default

  • Every page is static HTML. There's no framework: a few small Node scripts assemble the pages from shared partials at build time, the CSS is inlined into each page so nothing blocks the first render, and all of it is minified.
  • The only JavaScript is one small module (under 1.5 KB) for the mobile menu and the scroll reveals. Every page works without it.
  • Fonts are self-hosted, trimmed to the Latin character set, and preloaded. The fallback fonts are metric-matched, so when the real fonts arrive nothing on the page moves.
  • Photos are served as WebP with JPEG fallbacks, in several sizes.

On Lighthouse's mobile test, which simulates a mid-range phone on a slow connection, pages score 96 to 100 for performance (it varies a little run to run) with zero layout shift, and 100 for accessibility, best practices, and SEO. Each page is 87 to 133 KB in total, fonts and photos included.

No tracking

I removed Google Analytics and every other third-party script, including an embedded scheduling widget that pulled in 4.6 MB from 23 other domains. The reasoning is recorded as a decision record in the repo. The only measurement left is Cloudflare's cookieless visit count.

Accessible

A skip link and full keyboard support, text contrast that meets WCAG AA in both the light and dark themes (the site follows your system setting), reduced-motion support, and content that renders with JavaScript turned off. Every page passes axe with no violations.

Maintained like production code

  • Hosted on Cloudflare Pages. Cache headers, security headers, and redirects live in the repo as config files, not in a dashboard.
  • GitHub Actions build every pull request, check formatting, run unit tests, and validate every page: metadata, canonical URLs, internal links, and one check that rejects em dashes.
  • The sitemap regenerates itself when pages change, and an hourly job checks that every page is up.
  • Decisions a future reader might question are written down as short decision records.

The resume is built from data

My resume used to be a Google Docs export. Now each job, bullet, and skill is a JSON record in the repo, rendered into the resume page, and the PDF is that page printed by headless Chromium. I matched the Google Docs version line for line, so the only thing that changed is where it comes from.

  • Every PDF is checked before I see it: exactly two pages, fonts embedded in a format resume parsers can read, and extracted text that matches the page word for word, in order. The order check caught a first draft that listed each job's tech stack ahead of its bullets.
  • The PDF is built on my machine and committed only after I review it. CI fails if the resume data changes without a rebuilt PDF, and download links carry a hash of the file, so nobody gets a stale cached copy.
  • The link preview image is rendered from the resume page too, blurred on purpose so a cached preview never shows details that have since changed.

Built with AI, reviewed by me

I build and maintain this site with AI coding agents, mostly Claude Code, plus GitHub Copilot. They follow instructions that live in the repo, including a voice guide so the copy sounds like me. Every change goes through a pull request that I review and merge myself.