Blog
4 min read
Colophon: how this site is built
This page is a plain file on disk. So is every other page here. There is no application process behind the site and no database, which means there is very little that can break and nothing to keep warm.
The generator
sitegen is a small Python package in the repository. It reads Markdown files with TOML headers out of content/, structured data — the CV, the resource list — out of data/, and writes a finished tree into public/. It uses only the standard library: tomllib for configuration, and a deliberately small Markdown subset written by hand rather than pulled in as a dependency.
That last choice is worth defending. A personal site needs headings, lists, links, emphasis, code, quotes, and tables. That is a closed set, it fits in two hundred lines, and in exchange the build runs anywhere Python 3.11 does — including on the server, with nothing installed.
An instrument holding paper
The design has one rule, and everything follows from it: the chrome is always darker than the content. The header, the section tags, the scroll axis and the status strip are the instrument — near-black, monospace, amber. Everything you actually read sits on a light sheet with an amber edge, loaded into that frame.
The rule also settles the accents. Amber belongs to the chrome and ultramarine to the document, and they never trade places; two accents that each own a territory stay legible, while two that wander stop meaning anything. Buttons are keys — a hairline border with a two-pixel hard offset that collapses when you press them. Sections are numbered in the nav because there are six of them in a fixed order, and a number gives the eye something to hold in a row of monospace.
Dark mode keeps the rule rather than the material: the sheet stops being paper and becomes a lit screen, still lighter than the chrome around it. The distinction between frame and document survives; the literal paper does not, and it is the distinction that was doing the work.
The portrait is a graphite drawing, and the palette of the sheet is taken from it. It blends into its backing by darken, so the paper of the drawing disappears and only the strokes remain.
Two languages
Both are first-class: /en/ and /ru/ are generated from mirrored content trees, every page declares its counterpart with hreflang, and the root address hands you off according to your Accept-Language header. Where a translation does not exist yet, the language switch takes you to that section's index rather than dumping you on the home page.
An earlier version set the masthead in both languages at once — the active one solid, the other in outline. It was the most interesting thing on the page and it had to go: on the English page a Russian outline reads as two languages colliding, not as a device.
The one script
There is a single JavaScript file, about a hundred lines, and it decides nothing. It reports position: which section you are in, how far down you are, and it builds the tick axis on the right. Without it every page works — the axis simply never appears, and the status strip keeps whatever the server put there. That is why the axis is built in the script rather than in the markup: an element that means nothing without JavaScript has no business being in the HTML.
The axis only appears from three sections up. On a single article it would be decoration pretending to be an instrument.
Serving it
Caddy answers on 443, serves public/ as files, and gets its certificate from Let's Encrypt without a cron job. The content security policy allows scripts from this origin and nothing else — no third-party host of any kind. The news aggregator still runs on the same machine, now under /news.
Fonts
Three faces, self-hosted: Unbounded for display, Golos Text for body, JetBrains Mono for labels and data. Self-hosted rather than linked, because half this site is in Russian and fonts.gstatic.com is not reliably reachable from Russia. The page makes no third-party requests at all.