colophon
how this page works
the field
The landing page is one vector field. Each arrow is a particle carried by a velocity made of closed-form terms: a sink at the center, where the name sits, a vortex around it, a slowly turning wind, a drift of traveling curl waves, and a small source at every project, so the flow runs out of the work and into the person. Arrows part around the labels and die as they reach the name.
Two renderers draw it. Where WebGPU is available, a compute shader advects the particles on the GPU and one instanced draw renders them as arrows. Everywhere else, and if WebGPU has not started within 3 s, WebGL2 takes over: the same particle model advected on the CPU, the same instanced draw, made to look identical.
A device tier is picked from WebGPU support, CPU cores, device memory, viewport size and pixel ratio. The tiers high / medium / low space the arrows 24 / 28 / 36 px apart and cap the pixel ratio at 2 / 1.5 / 1. A frame governor drops one tier when the smoothed frame rate stays under 45 fps for 3 s, and never steps back up. Nothing is drawn while the tab is hidden.
the axes
x runs from mechanical engineering (me) to computer engineering (ce), placed by how physical the work is rather than by department: pure software sits at the ce end. y is timescale, the period a system is built around, on a log scale from 100 µs to 1 yr. The axis title swaps y for scope: component → subsystem → system. The x-axis title, bottom left, swaps x for purpose: why a project was built, from a tool relied on day to day to learning, built mainly to learn or stretch. It describes intent, never skill.
telemetry
The field breathes with the homelab it is served from. Once an hour a small Python publisher (standard library only) reads metrics the homelab already collects, from the time-series databases its monitoring keeps, and writes one packet of cluster-wide aggregates: no hostnames, versions or per-machine values. It pushes the packet to the web server; nothing on the page queries the monitoring itself. A metric whose source fails is left out, never invented, and a missed hour stays a gap.
The page fetches the packet on load and every 10 min, then replays the previous day's 24 hourly samples as a 240 s loop: CPU load turns the vortex, memory sets the pull of the sink. The modulation eases out whenever you touch the page, so the field never changes under your hand. A packet older than 2.5 × its interval is stale: the field freezes at the last values rather than fake motion.
what the readouts mean
- served
- how long this page took to arrive, from the browser's own timing (
responseEnd - requestStart) - build
- time since this version of the site was built
- rtt
- the round trip of one request to the homelab that skips every cache; the same request counts the page view
- snapshot
- age of the latest telemetry packet, and so how fresh everything else is
- up
- under the name: time since the machine serving this page booted
- top left
- the homelab's availability over the hours the publisher has watched (up to 30 days); the last day of CPU energy and temperature; the last day of network traffic in and out. Every line is the recorded history, replayed.
- pos, flow
- the cursor in axis units, and the field's strength under it
- fps, arrows
- frame rate and the live particle count
served from the homelab
The site is static HTML, served by the homelab whose numbers it shows. A cache sits in front, so the pages still load when the homelab is down; the telemetry then simply goes stale.
built with
Astro, static output; TypeScript, strict; plain CSS on a handful of tokens. The field page ships its own small modules, raw WGSL and GLSL shaders, and no framework or graphics library. Report figures are instruments: each is drawn once from its data file, as an SVG still at build and as a live canvas in the browser (WebGL for bikeloop's route); without JavaScript or on a low-end device the still is what shows. Behind the text each kind of page moves in its own way (sampling on reports, a scan on the index, standing waves here), with streamlines rendered at build from the field code as the fallback; the link preview and the 404 page's field come from that same code.
One typeface: JetBrains Mono in three weights, self-hosted and subset to the 114 characters the site draws.
privacy
No cookies. Anonymous page-view and performance counts; IPs are hashed daily and never leave the server. One anonymous report is sent when you leave or switch away: renderer, tier, frame rate, served, rtt, the viewport rounded to 100 px, reduced motion, visit length and path.
the log
One panel to the right of the field, behind the arrow at its right edge, is the log: the history as a git log --graph, one column per commit in git order, oldest at the left: linux is the trunk, each project or interest a branch off it, with real merges where work moved between them. Beside its header sits my real GitHub contribution calendar for the last 365 days, daily counts only, fetched when the site is built; it is never drawn from made-up numbers.
keys
With JavaScript on, every page answers to a few keys; Tab works everywhere as usual, and nothing needs a key to be reached. Press ? on any page for its own. The arrow keys work wherever h j k l do.
the field
- h j k l
- move between the projects and the name
- enter
- open its box
- esc
- close the box
- x y
- switch the x or the y axis
- ]
- the log
the log
- h l
- older / newer commit
- j k
- the branch below / above
- h j k l
- held upright: the branch left / older / newer / the branch right
- home end
- newest / oldest (g g, G)
- enter
- check out; again to unselect
- esc
- unselect, then back to the field
- [
- back to the field
the index
- j k
- next / previous project, then contact, colophon
reports and the colophon
- j k
- scroll a line
- ] [
- next / previous section
anywhere
- g f
- the field
- g l
- the log
- g i
- the index
- g c
- the colophon
- ?
- these keys