Dex

A blog?

Style guide

Everything on this page renders from the live stylesheet. If a token changes, this page changes with it — it cannot drift from the site the way a screenshot would.

Colour

Eight tokens, defined once in assets/css/tokens.css and redefined under prefers-color-scheme: dark. No other file in the repo contains a colour literal. Toggle your OS between light and dark to see both.

Every value below is asserted by test/tokens.test.mjs: ink tokens clear 4.5:1 against --bg, --ink clears 7:1, --accent clears 4.5:1, and --on-accent clears 4.5:1 against --accent. A palette change that breaks legibility fails the test rather than shipping.

  • --bgpage background
  • --bg-sunkrecessed: code, pre
  • --bg-liftraised: chips
  • --inkbody text
  • --ink-mutedmetadata, summaries
  • --rulehairlines, borders
  • --accentlinks, focus rings
  • --on-accenttext on --accent

Type

Three roles. Body text uses no webfont, so reading never waits on a third party.

Eyebrow — JetBrains Mono, uppercase, tracked

Display — Newsreader

Second level

Third level

Body copy is the system sans stack. It should feel unremarkable, which is the point: the display face and the mono metadata do the work, and the reading column stays out of the way. The measure is capped at 34rem so a line never runs long enough to lose your place on the return sweep.

2026 · 09metadata row3 min

Elements

A link in running text, some inline code, and bold and italic.

A blockquote, for the times a quotation earns its own space.

// A fenced code block.
const tokens = readFileSync('assets/css/tokens.css', 'utf8');
Column Column
Table cell Table cell
Table cell Table cell

Favicon

favicon at 16px favicon at 32px favicon at 64px 16 · 32 · 64

A keyhole, or a chess pawn. The ambiguity is deliberate — escape rooms read the first, games read the second, and neither needs explaining.

assets/favicon.svg is the only file besides tokens.css that contains colour literals, because an external SVG referenced from <link rel="icon"> cannot see the page’s custom properties. test/tokens.test.mjs asserts the duplicated values match the palette in both schemes, so the icon cannot drift.

The glyph fills 78% of the tile height, and that number is load-bearing. An earlier version used 56%. At 16px — the only size most people ever see — that left a head barely 3px across, and the shape collapsed into an unreadable blob. Clearance that looks composed at 64px is wasted resolution at favicon size. A test fails if the scale drops below 1.1.

Some clearance still earns its place: past roughly 90% the glyph looks cramped inside the rounded corners at the large sizes used for bookmarks and PWA icons. 78% is the compromise.

Jelly components

Loaded from jelly-ui.com and enhancement only. Each is shown twice: as it upgrades, and with the upgrade suppressed so you can inspect the fallback without taking the CDN down.

Note that a default <jelly-chip> paints its body on a <canvas> from --jelly-color-background-neutral. Any Jelly token left unbridged in tokens.css silently keeps Jelly’s own blue-grey default, so the bridge list is asserted by test.

Upgraded

hardwareesp32

Fallback, as it renders if package.js never loads

hardwareesp32