Skip to content

Notes4 min read

How this site is built, and how it was made

What on this site is mine, what is machine-made under my direction, and which numbers are dated snapshots.

This page says what on the site is mine, what is machine-made under my direction, what is measured and what is a snapshot.

What it is

A static site. next build with output: "export" produces plain HTML, JavaScript and CSS, and nginx serves the folder. There is no server runtime, no database, no analytics, no cookies and no request to any third party when you view a page. Fonts are self-hosted at build. The source is public on GitLab.

It is served from my own rack. CT114 is a container on the Proxmox host described in the Homelab section, behind the same Traefik gateway as everything else I run. A push to master runs lint, typecheck and tests in CI; if those pass, CI connects to the container and it rebuilds itself from the repository.

The hero

The data centre is three.js through React Three Fiber, WebGL2. It is a lazy chunk that mounts after the browser goes idle, behind a static SVG poster that paints with the first HTML. If WebGL2 is unavailable, if you have asked your OS for reduced motion, or if the renderer throws, the poster simply stays and the page keeps working. A WebGPU build with a compute-shader airflow layer exists behind ?renderer=webgpu; it is opt-in until I have verified it on more GPUs than mine, because the first time it shipped as the default it blanked the hero on a GPU-accelerated Chrome and I found out from the production site.

The racks are a drawing of my homelab, not a live view of it. The labels are real guest names; the boot logs on the screens are abbreviated from the real services. The section below the fold says "static inventory, not a live status page" and means it. The Homelab section below shows live guest state when the host publishes a status file; the racks themselves stay a drawing.

The terminal

xterm.js, fetched only when the section nears the viewport, so it never ships in the first load. Every command's output is generated from the same data file that renders the cards and the resume, and a unit test checks that the terminal cannot drift from the page.

What is measured, what is a snapshot

Numbers on this site fall into two kinds. Counts such as "16 guests" are read from the data file at build and change when I change the file. Figures such as the download count and rating for ShutUp are dated snapshots, written down with the date I checked them, and the Play Store listing they came from is no longer up; the number stands on my own records.

The rule for the data file, written at its top, is that every claim in it must be one I can back in a conversation. Private addresses, ports and anything that would help someone find a machine are kept out, and a test fails the build if one slips in.

How it was made

I build with an AI pair programmer, and most of the current version's code was written by Claude from my direction: I set the goals, reviewed the diffs, ran the site and pushed back. Commits it wrote carry a co-author line, visible in the public history. The decisions, the content and the verification are mine. Every change was linted, typechecked and unit-tested, then rendered in a headless browser at desktop and phone widths before it was committed, and the resume page is printed to PDF on Letter and A4 to check it holds one page. I made the same disclosure in the upstream issue I opened on kwin-canvas.

Motion and accessibility

One easing system, one set of durations, in a single token file that both the animation library and the CSS read; a test fails if they drift apart. Everything moves less if your OS asks it to: the hero becomes a still, the backdrop grid is not mounted, the cursor trail is not drawn. Keyboard users get a skip link to the content, a focus ring on everything, and a terminal that gives focus back on Escape. The 3D room is marked decorative to screen readers; nothing it shows is information you cannot get from the text.

Stack

Next.js 16, React 19, TypeScript, Tailwind 4, three.js and React Three Fiber, framer-motion, xterm.js, Vitest. Geist and Geist Mono. Built with Turbopack, served by nginx, hosted on a Debian container I maintain.