Foundations
Typography
Three faces, one heading ladder, and nothing above the page title.
Hanken Grotesk for interface, Newsreader for headings and editorial voice, IBM Plex Mono for labels and data. Each stack names a CJK fallback, so a Chinese page renders in a matched face rather than in whatever the platform substitutes.
The heading scale is a single ladder every surface climbs, fluid between a phone and the 1288px page. Nothing sits above --fs-title: a page has exactly one thing larger than its own records. Steps sit close together deliberately — the ladder separates records of the same kind, not a heading from its own sub-heading, so two headings that nest must skip a step.
The White Reset
A row of counted facts
Depth is a hairline
Bound to state, never to brand
One pointer, and it is ink
Type tokens
| Token | Value | Notes | |
|---|---|---|---|
--sans | var(--font-hanken, 'Hanken Grotesk'), var(--cjk-sans), 'Helvetica Neue', Arial, sans-serif | ─── Type ─────────────────────────────────────────────────────────────── Three faces and their CJK companions. EVERY `var()` here carries a fallback, and that is load-bearing rather than tidy. A `var(--font-hanken)` with no fallback, in a host that does not define `--font-hanken`, is invalid at computed-value time — and IACVT does not skip the one term, it discards the WHOLE declaration. So the entire stack collapsed to nothing and every surface rendered in the platform's system font. It worked inside the site, where next/font defines those names, and failed everywhere else: for a package whose whole purpose is to render the same on both sides, that is the worst possible bug. With the fallback, a host that loads the faces itself (next/font, a CDN) sets `--font-*` and wins; a host that does not falls through to the literal family name, which is exactly the name `fonts.css` declares its vendored @font-face rules under. The CJK stacks deliberately end WITHOUT a generic family. They are spliced into the middle of the three stacks above, and a generic in the middle terminates a font stack — everything listed after it is unreachable. | |
--serif | var(--font-newsreader, 'Newsreader'), var(--cjk-serif), Georgia, serif | — | |
--mono | var(--font-plex-mono, 'IBM Plex Mono'), var(--cjk-sans), ui-monospace, SFMono-Regular, Menlo,
monospace | — | |
--cjk-serif | var(--font-noto-serif-sc, 'Noto Serif SC'), 'Source Han Serif SC', 'Songti SC', STSong,
SimSun | — | |
--cjk-sans | var(--font-noto-sans-sc, 'Noto Sans SC'), 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei' | — | |
--lh-cjk | 1.8 | — | |
--ls-cjk | 0 | — | |
--fs-title | clamp(2.5rem, 4.6vw, 3.375rem) | Heading scale — the single ladder every surface climbs. Five steps, each fluid between a phone and the 1288px page, and nothing sits above --fs-title: a page has exactly one thing larger than its own records. Steps sit close together by design — the ladder separates records of the same kind, not a heading from its own sub-heading. Two headings that nest must skip a step: --fs-lead over --fs-sub reads as a hierarchy (1.33), --fs-lead over --fs-heading does not (1.14). | |
--fs-lead | clamp(1.875rem, 3vw, 2.625rem) | page h1 40 → 54px | |
--fs-heading | clamp(1.5rem, 2.1vw, 1.8125rem) | band heading 30 → 42px | |
--fs-sub | clamp(1.3125rem, 1.7vw, 1.5625rem) | record title 24 → 29px | |
--fs-item | clamp(1.125rem, 1.35vw, 1.25rem) | card sub-head 21 → 25px |