Token proof sheet

Every ratio below is measured live from getComputedStyle against the shipped src/styles/tokens.css. Nothing is typed in. The same pairings are enforced non-interactively by npm run contrast.

AA text needs 4.5:1 (WCAG 2.2 SC 1.4.3). AA non-text and UI boundaries need 3:1 (SC 1.4.11).

Dark chrome — five surfaces

.s-void the rebate; space behind the globe
--text-primaryheadings, readout numbers
--text-bodyrunning prose
--text-secondarysupporting copy, captions
--text-labeleyebrows, stat keys
--line-controlinteractive boundary
--line-hairdecorative divider
--mark-sunsun marker · sun path · scrubber accent
--mark-sun-lowsun below the horizon
--mark-axisstreet-axis spoke — LEGAL ONLY ON void/sunk/base
--mark-axis-textprose about alignment
.s-sunk inset wells, scrubber track
--text-primaryheadings, readout numbers
--text-bodyrunning prose
--text-secondarysupporting copy, captions
--text-labeleyebrows, stat keys
--line-controlinteractive boundary
--line-hairdecorative divider
--mark-sunsun marker · sun path · scrubber accent
--mark-sun-lowsun below the horizon
--mark-axisstreet-axis spoke — LEGAL ONLY ON void/sunk/base
--mark-axis-textprose about alignment
.s-base page ground
--text-primaryheadings, readout numbers
--text-bodyrunning prose
--text-secondarysupporting copy, captions
--text-labeleyebrows, stat keys
--line-controlinteractive boundary
--line-hairdecorative divider
--mark-sunsun marker · sun path · scrubber accent
--mark-sun-lowsun below the horizon
--mark-axisstreet-axis spoke — LEGAL ONLY ON void/sunk/base
--mark-axis-textprose about alignment
.s-plate a plate sitting in a rebate
--text-primaryheadings, readout numbers
--text-bodyrunning prose
--text-secondarysupporting copy, captions
--text-labeleyebrows, stat keys
--line-controlinteractive boundary
--line-hairdecorative divider
--mark-sunsun marker · sun path · scrubber accent
--mark-sun-lowsun below the horizon
--mark-axisstreet-axis spoke — LEGAL ONLY ON void/sunk/base
--mark-axis-textprose about alignment
.s-raised controls, stat cells, table headers
--text-primaryheadings, readout numbers
--text-bodyrunning prose
--text-secondarysupporting copy, captions
--text-labeleyebrows, stat keys
--line-controlinteractive boundary
--line-hairdecorative divider
--mark-sunsun marker · sun path · scrubber accent
--mark-sun-lowsun below the horizon
--mark-axisstreet-axis spoke — LEGAL ONLY ON void/sunk/base
--mark-axis-textprose about alignment

The paper surface — SPEC §7b

The one light surface on the site: the print preview, a --linen mat inside the black rebate. Not a theme, not a toggle. No reserved mark is legal here — --mark-sun measures 2.34:1 on linen, which is the measurement that produced the dark-only ruling.

.s-paper print preview · certificate · Hahnemühle warm white
--text-primaryheadings, readout numbers
--text-bodyrunning prose
--text-secondarysupporting copy, captions
--text-labeleyebrows, stat keys
--line-controlinteractive boundary
--line-hairdecorative divider
--mark-sunsun marker · sun path · scrubber accent
--mark-sun-lowsun below the horizon
--mark-axisstreet-axis spoke — LEGAL ONLY ON void/sunk/base
--mark-axis-textprose about alignment

Fills — foreground is always --text-primary

--fill-affirm
--fill-water
--fill-deny

Focus ring — tab through these

Two-tone: 1px --focus-inner (void) inside 2px --focus-outer (sage). Deliberately not amber — --sun is reserved, and an amber ring also measurably fails: against mid-grey #515151 it reaches only 2.65:1, below the 3:1 floor. Sage against void is 10.90:1, so over any photograph whichever ring the image swallows, the other survives.

ui-monospace probe — what does it actually resolve to?

SPEC §2 originally asserted ui-monospace resolves to SF Mono on macOS. MDN defines it only as "the default user interface monospace font" and does not say. Rather than assume, this measures the rendered advance width of 100 zeros and compares against known constants read from the font binaries: SF Mono 0.6182, Menlo 0.6021, IBM Plex Mono 0.6000, Inconsolata 0.5000. Since 2026-09-11 the keyword is no longer in the stack at all — this table stays because it is the measurement that explains why. One trap: the default proportional face also sets its digits at 0.5000 em, so a zero-width alone cannot tell Inconsolata from "name not recognised". Each row therefore also measures 100 i against 100 M; they are equal in a monospaced face and never in a proportional one.

measuring…

Mono column width — did the shipped face paint? (A3, amended 2026-09-11)

What you are looking at: this site is a wall of tabular numbers. Until 2026-09-11 it rendered in two different mono faces depending on the browser — SF Mono in Safari on a Mac, IBM Plex Mono everywhere else — and size-adjust: 103.03% on Plex reconciled their widths (0.6182 vs 0.6000). Now the shipped webfont, Inconsolata, leads the stack for every browser, there is no size-adjust, and the only question is whether the webfont painted: the site's mono must measure 0.5000em per character (500/1000, read from the binary) and be monospaced. Menlo, the next real face, is 0.6021 — if you see that, the webfont did not load and every ch measure is 20% wider than designed.

measuring…

In every browser, Safari included: the row marked var(--font-mono) should read 0.5000 and resolve to Inconsolata. There is no longer a Safari-only route — the stack does not start with ui-monospace, so SF Mono is never consulted. The ui-monospace alone row is kept as history: 0.6182 there means the keyword works in this browser (Safari), and a proportional result means it was skipped (everyone else). Neither affects the site any more.

Why Fraunces never renders a number (SPEC §2)

Fraunces has no tnum feature and its digits are proportional — 1024 to 1461 units wide, a 43% spread. Both rows below are counting 0–9 in the same box. The mono row holds still; the Fraunces row breathes. On a scrubbing clock that is visible jitter.

mono · tabular · correct
Fraunces · proportional · forbidden for digits
The Fraunces row has font-variant-numeric: tabular-nums applied. It does nothing — that is the point.

Fraunces axes — SOFT and WONK survived subsetting

Note what is not here: an opsz value. Pinning the optical-size axis swings Fraunces' advance widths by 23%, and our display sizes are fluid, so a pinned opsz would be right at one window width and wrong at every other. font-optical-sizing: auto handles it. The lint fails the build if anyone pins it.

Fifth Avenue at Twenty-Third
SOFT 0 · WONK 0 — refined editorial
Fifth Avenue at Twenty-Third
SOFT 100 · WONK 1 — handmade and strange

The type scale (A4)

What you are looking at: every level in the system, at the size it is right now in this window. Resize the window and the three display levels move; the mono levels do not. That is deliberate — headlines want to fill their measure, but a column of numbers whose width drifts with the window is a column you cannot compare down.

Lightfall
The map is the gallery
Fifth Avenue at Twenty-Third

298.97°
AZ 298.97° · EL +0.75° · SHADOW 76.4×
Running prose sits at the body level. The measure is set in ch, not pixels, so it is defined against whichever face actually painted — Inconsolata for everyone since 2026-09-11, Menlo only if the webfont fails — and the number of characters per line is the same either way.
2026-05-29 · 20:11:04 EDT · Flatiron District
Bearing from north
Sun position computed from the Sunlines core, not reimplemented.

The spacing scale (A5)

What you are looking at: the whole scale, drawn at real size. The rule is the number times 4px — --space-6 is 24px — so you never have to look one up. npm run lint:space fails the build on any hand-typed margin, padding or gap and tells you the nearest token.

Rhythm and gutter — fluid, like the display type

Sections breathe on a large screen and compress on a phone, using the same clamp construction as the display sizes so headings and the space around them scale together instead of drifting apart. Resize the window and watch these move.

Concentric corners — the geometry A6 is built on

What you are looking at: a plate inside a black rebate, which is how every photograph, the globe and the dial will sit (SPEC §2). The rebate has a width and the plate has a corner radius, and those two numbers are not independent: if the outer radius equals the inner one, the black mat visibly pinches at each corner because the two arcs are not parallel. The rule is arithmetic — outer radius = inner radius + rebate width — so the --radius-rebate-* tokens are derived, never typed. This measures the real computed radii and fails if the arithmetic breaks.

rebate 1
thin · chips, inline plates
rebate 2
default · the standard plate
rebate 3
thick · globe, hero photograph
measuring…

Live digit check — every number on this page is mono

What you are looking at: the npm run lint:type rule, running again in the browser against the rendered DOM. The static lint reads built HTML; this walks every text node that actually painted and reads its resolved font. Between them, a number set in Fraunces has nowhere to hide — including one that arrived from a template expression.

checking…