How this is built
Hand-written HTML, CSS and JavaScript, served straight off GitHub
Pages. No framework, no bundler, no build step between the repository and the page you
are reading. Everything below is measured from the repository itself by
generate_colophon.py rather than typed in, so it cannot go out of date
without a check failing.
What it weighs
- Stylesheet
- —
- Icon fonts
- —
- Text fonts
- —
- JavaScript
- —
- Dependencies
- 0
- Build step on deploy
- None
—
—
—
—
No npm, no lockfile, nothing to audit
Pages serves the repository as written
One stylesheet is authored; the served file is generated by inlining the vendor CSS and then deleting every rule whose selector matches nothing on the site. The icon fonts are subset to the glyphs actually used, which is where most of that saving comes from. Nothing is minified beyond the stylesheet, because the numbers above did not justify the extra moving part.
What is in it
- Pages
- —
- Posts
- —
- Words
- —
- Reading time
- —
- Series
- —
- Topics
- —
- Photographs
- —
- Posts with a live demo
- —
End to end, at 220 words a minute
—
—
The writing
- First post
- —
- Posts a month
- —
- Average post
- —
- Longest
- —
- Shortest
- —
- Commits
- —
—
Averaged across the whole run
words
—
—
—
How it was built, month by month
Every commit to the repository, by month. The site was a single page for years; the writing, the tooling and most of what the rest of this page describes arrived in one long push through 2026.
What it runs on
It uses
- Hand-written HTML, CSS and JavaScript
- GitHub Pages, from the repository's default branch
- Python for the build scripts and the audit
- Playwright to drive the interactive posts in a real browser
- A service worker, so visited pages survive going offline
- GitHub Discussions for comments, Last.fm and Goodreads for the live figures
It deliberately does not
- Use a framework, a bundler, or a package manager
- Ship a single third-party runtime dependency
- Carry advertising, or any tracker beyond one analytics tag
- Require JavaScript to read a post
- Animate anything that a reader has asked it not to
- Publish a figure on this page that was typed rather than measured
The build
There is no build on deploy, but there is one before a commit.
— scripts generate what would otherwise be
maintained by hand, and every one of them has a --check mode that fails
rather than silently repairing.
- minify_css.py
- Inlines the vendor imports, deletes rules nothing matches, points the icon fonts at their subsets
- generate_read_times.py
- Counts each post's words, which also decides its band in the blog filter
- generate_post_facets.py
- Detects which posts carry a live demo or code, for the Format filter
- generate_related_posts.py
- Ranks neighbours by TF-IDF over body text and bakes the cards in
- generate_feed.py
- Builds the RSS feed, full-text for the twenty newest posts
- subset_icon_fonts.py
- Cuts three icon fonts down to the glyphs the site actually uses
- audit_site.py
- Links, metadata, structured data, accessibility basics, feed and sitemap
- generate_colophon.py
- Measures everything on this page
How it is checked
- Checks per push
- —
- Audit baseline
- 0 / 0
- Lines of CSS
- —
- Lines of JavaScript
- —
All must pass before the site deploys
Errors and warnings, across every page
One authored file
—
A push runs the site audit, rebuilds the stylesheet and fails if the committed one differs, and drives every interactive post in a headless browser to check it still converges, still works at phone width, and still announces itself to a screen reader.
Type and colour
Poppins
Interface, navigation, headlines on photographs. Self-hosted, seven weights.
Lora
Section headings and the body of every post. Self-hosted, three weights plus italics.
One accent does one job. Red means a control; the gradient is reserved for the two places the site signs its own name. Everything decorative is neutral, because 492 photographs are colour enough for one website.
Photography is my own, shot on a Canon EOS 60D; the full-size files live in a GitHub release rather than the repository, because Pages has an artifact limit and 492 originals do not fit inside it. Hosting is GitHub Pages on a custom domain. The whole thing is written up, at considerably more length than this, in How This Site Is Built.