HallWebDesign
Colophon

How this site is built.

Most portfolios tell you what their clients got. This one also tells you how the portfolio itself is made. If something here is useful for your own project, help yourself.

Stack

Stack.

Framework
Next.js 16 (App Router) on React 19
Language
TypeScript everywhere
Styles
Tailwind CSS v4 with @theme tokens; dark + light via a data-theme attribute flipped by an inline bootstrap script (no flash)
Motion
framer-motion, throttled carefully on mobile
Hosting
Vercel Pro, SSR mode
Data
Vercel KV (Upstash Redis) for the portfolio config + any toggle-without-deploy flags
Forms
Own server endpoint, delivered via Resend
Analytics
Self-hosted Umami (cookieless, no visitor tracking) + Vercel Speed Insights
Auth (admin)
NextAuth v5 with GitHub OAuth
Typography

Typography.

Three typefaces, all self-hosted via next/font:

Geist Sans
Body text, UI. Variable weight. The workhorse.
Bricolage Grotesque
Headings. Variable wdth + wght. Headlines stack a wdth-100 line over a wdth-75 line for the editorial contrast that runs the brand.
Fraunces (italic only)
Editorial accents - phrase-level emphasis, sub-titles, FAQ questions. opsz axis for optical sizing. Italic-only to save ~100KB of font bandwidth.
Geist Mono
Marginalia, page-numbers, hung labels, domains, project indices. Treats numerals as a typographic citizen, not a fallback.
Performance

Performance.

A live PageSpeed Insights run is one click away. Current numbers (median across every public route):

Desktop perf
99 / 100 average (100 on /terms)
Mobile perf
85-89 average, throttled Moto G4 profile
Accessibility
96-100 on every route, WCAG AA contrast
Best practices
100 across the site
SEO
100 across the site
Homepage HTML
~80 KB transferred on first load
Portfolio images
WebP q88 @ 1600w max, 80-150 KB each after a hard audit (PNG originals were 2-3MB)
Regression guard
Lighthouse CI runs on every PR against the Vercel preview, asserts on thresholds
Screenshots

How the portfolio screenshots work.

A Puppeteer script at scripts/screenshots.mjs hits every live client site, waits for animations to settle, dismisses common cookie banners, and writes a PNG at 1280x720 @ 2x. The PNGs get piped through sharp to produce the WebP versions used in production. There's also an ffmpeg path that captures a silent 3-second WebM loop with a soft scroll, which will land when there's time to record them.

Theming

Theme bootstrapping.

The light/dark toggle is backed by CSS custom properties under a [data-theme] attribute on <html>. An inline <script> in the head reads localStorage.theme and sets the attribute before first paint, so light-mode users never see a dark-mode flash.

A11y

Accessibility.

Semantic HTML first, ARIA where it adds value. Every interactive element is keyboard-navigable with a visible focus ring. Motion-heavy sections respect prefers-reduced-motion. Text contrast is WCAG AA in both themes, verified via tokens rather than eyeballing hex values.

Retros

Bits I'd do differently.

No site is a straight line. A few retros from the build:

  • I shipped a WebGL domain-warped gradient shader as the hero background because it looked premium on my desktop. Mobile Lighthouse promptly returned a 41 and 167 seconds of blocking time. I spent a session progressively throttling it before admitting two different heroes for two different viewports was a worse outcome than one editorial hero everywhere.
  • The "Lighthouse 95+" badge in the footer is true on desktop (avg 99) and aspirational on mobile (avg 85). The PSI link is one tap away so anyone can check.
  • The first portfolio data pass had V Clarke Books' headline metric reading "Author-managed via Decap CMS" - technically true, but a tool name is not an outcome. Still on the backlog to replace with a real figure.
  • The first Lighthouse sweep surfaced a 3.5MB PNG hero photo and five portfolio screenshots totalling ~10MB. The second sweep surfaced that I had been compressing them with the wrong settings for a week. Lesson: measure the bytes on the wire, don't trust the build report.
Build 2911e7f