basalt

The opinionated, javascript-optional Zola theme that powers this site and timsiggins.com. Two sites, two personas, one engine. About 35kb per page, no build step in the consumer repo.

The theme behind this site and behind timsiggins.com. Monospace-accented or editorial depending on the persona, keyboard-friendly, light and dark, small enough to serve from a potato. No framework in the consumer repo. JavaScript off? Everything still works except the command palette and the comment embeds.

Source lives at codeberg.org/ttsigg/basalt. The README has install notes; the announcement post covers what shipped in v1.

What you get

  • btop-inspired panes on the homepage and /now, with a live uptime clock, a reading list, and a tinkering-with card driven by a single data/now.json.
  • Command palette on /. Searches titles and body text via Zola's built-in index, ships at about 3kb, lazy-loads on first keypress. Ctrl+K works too.
  • Light and dark themes with no flash of wrong theme, ever. Press t to toggle.
  • Eleven shortcodes with full no-JS fallbacks: alert, collapsible, em, toc, image (with float + lightbox), carousel, asciinema, katex, mermaid (markdown fence), project, pullquote, epigraph.
  • Asciinema embeds with a titlebar that matches the site. Player loads only on pages that use the shortcode.
  • KaTeX for math. Loaded only on pages that contain math.
  • Mastodon and Bluesky comments, opt-in per post, lazy-loaded on scroll-into-view.
  • Responsive images via Zola's resize_image() with multi-width WebP srcset out of the box.
  • MPA View Transitions for smooth navigation in browsers that support them, gated on prefers-reduced-motion.
  • Persona token system. Two sites running on this theme look completely different — they swap fonts, colours, ornaments, and editorial chrome via a [extra.persona.*] table.
  • Under 40kb per page on almost every route, fonts included.
basalt homepage on auxdev.net, desktop, light theme
auxdev's persona — terminal green, mono accents.
basalt homepage on auxdev.net, desktop, dark theme
The same homepage in dark mode.

Two personas, one engine

The part that took six rewrites is the persona system. Both consumer sites import basalt's SCSS aggregator once, then layer their own token table:

Tokenauxdev (this site)timsiggins.com
--font-textInterPublic Sans
--font-displaySpace GroteskNewsreader
--ornament▍ ▍ ▍
--anchor-glyph§
Accentterminal greeneditorial indigo
Pullquote chromemono italic, centeredserif italic, large

Same Tera templates, same Vite bundle, two wildly different identities. Adding a third consumer site is a config.toml away.

A blog post, light and dark

basalt blog post, desktop, light theme
Blog post, desktop, light theme.
basalt code block rendering, dark theme
Syntax highlighting via giallo, class-based, paired light and dark themes.

Mobile

Same content, narrower viewport. Sidebar collapses to a top bar; panes stack; the command palette still works on phones with a hardware keyboard.

A terminal recording, because why not

The asciinema shortcode renders a scaled player with a themed titlebar. The player and runtime are lazy-loaded only on pages that have a cast.

asciinema // demo session

Math, lazy-loaded

Inline KaTeX: . Display mode:

KaTeX is loaded only on pages whose content contains a math element. Pages without math never pay the cost.

Comments, two flavours

Opt-in per post via front-matter:

+++
comments = "bluesky"
bluesky = "https://bsky.app/profile/handle/post/id"
+++

Both renderers lazy-load the platform's public API when the reader scrolls to the bottom. No cookies, no third-party script until then, no consent banner. Live demos: Mastodon, Bluesky.

Tech

  • Zola 0.22.1 — static generator and built-in Sass.
  • Tera — templates, because Zola ships Tera.
  • Sass with CSS custom properties for theming.
  • Vite + Rollup — JS bundle. Output ships as a committed artifact in the theme repo, so consumer sites don't need npm.
  • Playwright — cross-site visual-consistency suite.

Using it

Clone the repo, drop your content into content/, point config.toml at yourself, run just setup once to fetch Zola, then just serve. Production builds go to public/ via just build. The auxdev repo's DEPLOY notes walk through Cloudflare Workers from scratch.

If you hit something busted, file a Codeberg issue. I read them.