Skip to content

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.

Page title · --fs-title

The White Reset

Band heading · --fs-lead

A row of counted facts

Record title · --fs-heading

Depth is a hairline

Card sub-head · --fs-sub

Bound to state, never to brand

In-card title · --fs-item

One pointer, and it is ink

Type tokens

Type tokens tokens
TokenValueNotes
--sansvar(--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.
--serifvar(--font-newsreader, 'Newsreader'), var(--cjk-serif), Georgia, serif
--monovar(--font-plex-mono, 'IBM Plex Mono'), var(--cjk-sans), ui-monospace, SFMono-Regular, Menlo, monospace
--cjk-serifvar(--font-noto-serif-sc, 'Noto Serif SC'), 'Source Han Serif SC', 'Songti SC', STSong, SimSun
--cjk-sansvar(--font-noto-sans-sc, 'Noto Sans SC'), 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei'
--lh-cjk1.8
--ls-cjk0
--fs-titleclamp(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-leadclamp(1.875rem, 3vw, 2.625rem)page h1 40 → 54px
--fs-headingclamp(1.5rem, 2.1vw, 1.8125rem)band heading 30 → 42px
--fs-subclamp(1.3125rem, 1.7vw, 1.5625rem)record title 24 → 29px
--fs-itemclamp(1.125rem, 1.35vw, 1.25rem)card sub-head 21 → 25px