A personal site that refuses to look like a personal site. Every pixel of the interface is generated from CSS and hand-authored sprite data — no images, no icon library, no runtime.
What makes it work
The whole design system is 8-bit primitives. Fourteen CSS utilities do the visual heavy lifting: hard zero-blur shadows, notched corner cuts, a stepped animation curve, and a font stack of one pixel display face plus one readable mono for prose.
Icons are data, not files. 38 sprites live in a TypeScript file as character grids.
Every pixel resolves to a CSS custom property, so switching the CRT phosphor recolours
all 38 icons from one attribute on <html>.
There is no server. output: "export" means every route is prerendered to HTML at
build time. The whole site is a folder of files that could be served from anywhere.
Content is MDX in the repo. Posts and projects are .mdx files with frontmatter.
Reading time, tag counts, adjacent-post navigation and the sitemap are all derived at
build time — there's no database to keep in sync.
Sound is synthesised. WebAudio square waves with hard attack envelopes. No audio files ship, and the whole SFX layer is about 80 lines.
Tech
| Layer | Choice |
| --------- | --------------------------------------------- |
| Framework | Next.js 16 (App Router, static export) |
| Language | TypeScript, strict |
| Styling | Tailwind CSS v4 with custom @utility rules |
| Components| shadcn/ui, restyled square and hard-shadowed |
| Content | MDX compiled at build time |
| Icons | Hand-authored sprite grids → SVG |
Things I got wrong first
- Passing
variantstraight through to shadcn'sButton. The prop name collided with shadcn's ownvariant, and TypeScript caught it. Renaming mine to an explicit tone map fixed it. clip-pathkilled every hard shadow. Notched frames now render the shadow as a second offset layer.- Unquoted YAML dates.
gray-matterparsesdate: 2026-08-14into aDateobject, which is not a string and fails my frontmatter validation. Quote your dates.
Why it's built this way
A personal site should be a toy that happens to be useful. This one loads no analytics, sets no cookies, has no tracking pixel, and costs nothing to host — and the effort went into making it feel like a 1987 arcade cabinet instead of into a cookie banner.
It's also the single best portfolio piece I've ever made, because it's demonstrably the thing I know how to do.