System · Paper and Plate · tokens 1 · 5e9cbec
This site is built from one record, and this page is that record rendered.
Every colour, space and duration on the site comes from one file. This page reads the same file, so what it shows can't disagree with what you're looking at. The checks that keep it honest run on every push and are linked beside each rule.
01 Colour
Two palettes and a fixed plate. Light is the base, dark only redefines, and the plate is dark in both because the work reads at cinema contrast whatever the page does. Every ratio below is computed from the record when the site is built.
| Pair | Light | Dark | Use |
|---|---|---|---|
| ink on paper | 16.13 | 15.34 | text |
| muted on paper | 5.08 | 7.17 | text |
| marker on paper | 5.40 | 6.40 | text |
| blueprint on paper | 5.59 | 7.41 | text |
| muted on field | 4.63 | 6.62 | text |
| ink on field | 14.71 | 14.17 | text |
| rule on paper | 1.30 | 1.31 | decorative, never text |
| plate-ink on plate | 15.87 | 15.87 | text |
| plate-muted on plate | 7.41 | 7.41 | text |
| plate-marker on plate | 6.62 | 6.62 | text |
02 Space and grid
The reading column, the gutter and the gap, as the page uses them.
- content-max
- 72rem
- gutter
- clamp(1rem, 4vw, 3rem)
- grid-gap
- 1.5rem
- measure
- 66ch
03 Motion
The work is the motion and the interface stays still. Names say what a movement is for rather than what it does, because a token named for a mechanism ends up serving two jobs with opposite needs and then neither can be tuned. Two columns: the value, and what it becomes under reduced motion, which is a decision per movement rather than a switch that zeroes everything. A colour change and an opacity fade are not movement. This visit is receiving the defaultreduced column. Each row plays on request only, so this page never moves until you ask it to.
| Token | Default | Reduced | Used by | Why the reduced value | |
|---|---|---|---|---|---|
| --respond | 120ms | 120ms | link and row hover, focus, the press, the hover intent timer | A direct answer to a pointer, so it is spent while somebody waits. Past about 200ms that reads as lag and people click again. A colour change and a hair of scale are not the movement reduce protects against. | |
| --beat | 120ms | 120ms | the gap between the hero sentence and the line under it | A delay rather than a duration. Nothing moves during it, so reduce has nothing to cut, and the offset is what makes two lines read as one arrival instead of two. | |
| --enter-page | 200ms | 200ms | the hero rise on first paint | It stands between a reader and the sentence, so it takes the tightest expressive budget on the site. The travel is 8px, well under any vestibular threshold, so reduce keeps it rather than snapping the text into place. | |
| --enter-overlay | 480ms | 0ms | the still growing from its tile to full size | Several hundred pixels and a scale, so the number that is slow on the hero is brisk here. Nothing is blocked while it runs, which makes it the one place the page performs. Zero under reduce, because a frame scaling across the viewport is the large area movement the setting exists to stop, and the dialog still opens. | |
| --move-mark | 230ms | 0ms | the chapter rail mark sliding under its label | Position change becomes a cut, same end state. | |
| --slip | 230ms | 0ms | the misregistration offset and the fringe tint that rides with it | Hover triggered, but nothing waits on it, because the word is already readable and the ghost is decoration. So it is spent as an expressive movement rather than as a response. Offset and tint share one value because they are one event, not two. | |
| --ease-out | cubic-bezier(0.2, 0, 0, 1) | linear | anything entering or moving | Nothing moves under reduce, so the curve is moot and linear says so. | |
| --ease-in | cubic-bezier(0.4, 0, 1, 1) | linear | anything leaving | As above. | |
| --slip-x | 0.17em | 0.17em | how far a misregistered word slips | A distance, not a duration. The travel is cut under reduce by --slip going to zero, so the offset itself does not change and the fringe still prints. Halved from 0.34em on 2026-09-21: at the old value the ghosts spread far enough that the word underneath stopped being readable on hover, which is the one thing a decorative effect on a title must not do. | |
| --rise-y | 0.5rem | 0.5rem | how far the hero rises on first paint | Same reason. --enter-page carries the same value in both columns because a fade is not the movement reduce protects against, so this distance holds too. | |
| --press | 0.985 | 1 | the scale a tile takes while pressed | Unitless, fed to scale(). It flattens to 1 under reduce, because a press that moves is the one part of this that is movement rather than feedback. | |
| --slip-band | 13rem | 13rem | how far from the cursor a word still lifts | A radius, not a duration. The fringe fades out with distance from the pointer so a title answers where the eye is rather than all at once, and the words themselves never move apart. | |
| --drift | 2000ms | 0ms | the fringe sweeping across a title after a finger lets go of it | Touch has no hover, so on a phone the fringe froze wherever the finger last was once the page scrolled. Letting go hands it to a slow sweep, one end of the line to the other and back, instead. Two seconds a pass, chosen by eye on 2026-09-25 once it ran at its real speed, a wave rather than a crawl. The strobe it showed on the first night was a unit bug, the minified 3.2s read as 3.2 milliseconds, never this value. Zero under reduce, where letting go clears the fringe instead, because an effect that keeps moving on its own is what the setting exists to stop. | |
| --linger | 700ms | 700ms | how long a cursor rests on a title before the fringe starts to sweep | A delay rather than a duration, so nothing moves during it. Long enough that a cursor passing over a title never starts it, short enough that one resting there sees it before looking away. The same under reduce, where --drift is zero and the sweep never starts anyway. | |
| scroll | smooth | auto | anchor jumps | A jump is a jump. |
04 Rules, with their teeth beside them
Each rule names what enforces it. The ones that nothing enforces yet say so, which is more honest than a page of ticks.
Rule is a hairline and never text. It measures about 1.3 to 1 on paper, so an absent chapter is muted with a line through it, never set in rule.
tools/check_tokens.mjsThe interface stays still and the work is the motion. Every movement has a name here and a reduced value that's a choice rather than a zero.
tools/check_tokens.mjsPlate is dark in both themes and carries its own ink, muted and marker, because the light marker on it measures 3.2 to 1 and fails.
data/design/tokens.jsonNothing shifts. Every image and video declares its box before its bytes arrive, and the build fails on any layout shift at all.
site/lighthouserc.jsonNothing is defined for the first time inside a media query or a data attribute block. Light is the base and the dark blocks only redefine.
tools/tokens.mjsA component never writes a literal. It names a token, and a hex, a millisecond or a curve written by hand fails the build.
tools/check_tokens.mjsOne inline script runs before paint, for the visitor's own preferences, and every page is complete without it.
docs/decisions/0013-preferences-before-paint.mdNothing on a branded surface is a flat fill or a stroke. Boxes, rules, icons and display type are lit cells on the lattice, and the smallest tier of text comes off the display entirely because the display cannot do small.
docs/decisions/0018-the-display.md
05 Endpoints
The record this page is rendered from, served as it is on disk.
- tokens
- /system/tokens.json
- design tokens format
- /system/tokens.dtcg.json, the W3C community group shape, with light and dark and default and reduced as modes, which is what a Figma variable import reads
- source
data/design/tokens.jsonat 5e9cbec- renderer
tools/tokens.mjs- guard
tools/check_tokens.mjs- motion lab
- /wire/motion, the four open interactions running for real, each option naming the token it would claim
- wireframe
- /wire, every slot on the site filled or masked and every interaction built or still open. Two of the durations above are documented here and performed nowhere, and that page says which.