Style guide
Archival print. The tokens, what they measure, and where the two modes deliberately disagree.
Warm paper rather than white, ink-brown rather than black, and a single seal-red accent used sparingly enough that it still carries meaning when it appears. Rules provide structure. Section labels name what a region is. Hashes are set as artifacts, because that is what they are.
This page renders in whichever mode your system asks for. Switch your OS appearance and everything below re-measures.
Icons
Phosphor Light, MIT licensed, drawn at a weight that sits with Fraunces and Newsreader rather than against them. Inline SVG, never an icon font — a font maps glyphs onto Private Use Area codepoints, which some screen readers announce as garbage and which vanish entirely for anyone overriding fonts for legibility.
The names are our meanings, not the library’s: manuscript, not
diamond. Changing library is one file, and no call site moves. Every icon in the
app is aria-hidden and sits beside a real text label; an icon that ever appears alone needs a
label of its own.
Palette
--seal and --brand start at the same value and are not the
same thing. --seal is set in type — labels, rules, the current nav item
— so it must clear 4.5:1 against the paper. --brand is the logotype: the
band in the icon, the dot after the wordmark. A logotype is exempt from contrast minimums, so
it is free to be whatever the mark wants. Conflated, they fight — tune the brand and
label text silently loses contrast.
Measured from the stylesheet as this page loads, so it cannot drift from what the site actually uses. The values follow the theme you are reading in — this palette has a light and a dark set, and the ratios below are against the paper of whichever one you have.
Defaults must pass before any setting exists. A preferences pane is for going further than the baseline, never for repairing one. Three tokens originally failed here — supplementary text at 3.38:1, the pending state at 4.18:1, and functional borders at 1.39:1 — and none of it was visible to anyone who could read comfortably.
Two border tokens, on purpose
--rule is ornament: hairlines between panes, under table rows. It sits
below 3:1 deliberately, because WCAG 1.4.11 governs components required to understand
content, not decoration. --edge is functional — buttons, inputs, active
states — and meets 3:1. Collapsing them into one token either breaks compliance or
flattens the design.
Light and dark disagree deliberately
What changes, and why
| Property | Light | Dark |
|---|---|---|
| Body contrast | 15.6:1 | 11.0:1 — one step softer by default |
| Text colour | near-black brown | warm bone, never #fff |
| Accent | saturated seal red | lighter, desaturated — saturated reds vibrate on dark |
| Elevation | borders | lighter surfaces (--paper-deep rises) |
| Grain | reads as paper | reads as sensor noise — candidate for reduction |
Dark mode is an accessibility requirement for some people and an accessibility problem for others. Light sensitivity — migraine, concussion, some autistic sensory profiles — can make a bright field genuinely painful. Astigmatism makes light-on-dark blur, because halation smears strokes that were already smearing.
So neither can be the assumed default, both have to be good, and the real answer is that mode belongs in the settings pane beside the contrast controls rather than behind a single toggle.
The evidence is not symmetric, though. Positive polarity measurably wins for reading — smaller pupil size, better proofreading, and reading-speed penalties reported as high as 26% for light-on-dark. Astigmatism affects 30–50% of adults, and a dark field dilates the pupil in exactly the way that exposes corneal aberration. On OLED phones there is a second mechanism entirely: dark mode encourages lower brightness, and PWM dimming near 240Hz worsens below roughly a quarter brightness.
Hence dark mode here runs one contrast step softer by default. Maximum contrast is the halation condition, so the highest ratio was the wrong place to start.
Scale
Section labels
Small-caps, letterspaced, seal-red, sitting above a region to say what it is. They carry the print language into application chrome and they are the pattern to reuse — a second heading treatment for panes would dilute both.
Motion
One quiet staggered reveal on load, then stillness. Printed things do not fidget, and
a writing surface that animates is a writing surface that interrupts.
prefers-reduced-motion is honoured, and the settings pane will let it be
forced independently of the OS.
Focus
A 2px seal outline with 2px offset on every interactive element. This was entirely absent in the first build — keyboard-only users had no indication of position anywhere in the interface, which no amount of visual polish compensates for. The editing surface is the one exception: there the caret is the indicator.