The Editorial-Technical system.
Cool paper, near-black ink, and a single denim-navy accent. Hanken Grotesk for headings, Geist Mono for labels. Small radii, hairline borders, heavy whitespace. Every page on the site composes from the tokens and components documented here.
Foundations / 01
Colour tokens.
The full palette is eight tokens. There are no bright fills, no gradients.
Contrast and rhythm come from ink on paper, with the navy accent reserved
for one mark per view. Every value is a CSS custom property in
:root.
--paper
Page background (near-white cool)
--raised
Cards, inputs
--ink
Headings, body, dark sections
--ink-muted
Body min on paper (AA)
--ink-faint
Decorative only, never body text
--line
Hairline borders, dividers
--line-strong
Emphasised hairline
--accent
One denim-navy accent, used sparingly
Contrast contract
WCAG AA throughout. On paper, body copy never goes lighter than
ink-muted;
ink-faint is decorative only
(rules, dividers, disabled marks). On ink surfaces, text stays at
text-paper or
text-paper/70 and above.
Typography / 02
Two typefaces, one scale.
Hanken Grotesk carries display and body; Geist Mono carries every label,
index, and eyebrow. The Heading primitive
splits semantic level from visual size, so the document outline never breaks
to satisfy a layout.
Heading · display-xl
Display extra-large
Heading · display-lg
Display large
Heading · display-md
Display medium
Heading · h2
Section heading (h2)
Heading · h3
Subsection heading (h3)
Heading · h4
Minor heading (h4)
Text · body sizes
size xl: lead paragraph, comfortable and unhurried.
size lg: secondary lead, muted ink.
size base: standard body copy at ink-muted.
size sm: captions and supporting notes.
size xs: fine print, still AA on paper.
Geist Mono · labels
Foundations / 01
The .mono-label eyebrow:
uppercase, tracked, ink-muted. Numbered to anchor each section.
const tokens = await build();
Inline mono for code, indices, and spec rails.
Primitives / 03
The smallest reusable pieces.
Each primitive enforces accessibility through its API: semantic defaults, required labels, decoupled visual sizing. They render the same on every page.
Button: variants & sizes
primary and
navy are for dark surfaces (shown below);
secondary is the outlined action on ink.
Button: on ink surfaces
Reserved for ink sections: paper fill or a hairline outline, never the paper-surface primary.
Link: inline / arrow / nav
Read the approach . Inline links carry a quiet navy underline.
Badge: mono label chip
A hairline tile in Geist Mono with a small tick: accent on
coral/sunset,
neutral otherwise.
Logo: lockup / monogram / wordmark
A machined monogram plate plus a tight grotesk wordmark. The wordmark carries the accessible name; the monogram is decorative.
Icon: Phosphor set
Decorative (aria-hidden) by default. Pass label
to expose a meaningful icon to assistive tech.
Patterns / 04
Compositions used across pages.
A small set of patterns built from the primitives above: a section header, a metrics row, a code window, and an FAQ. Every page draws from this set.
SectionHeader
A centred section header.
Eyebrow Badge, balanced display heading, and an optional muted lead: the standard opener for a content section.
Metric: a row of measured outcomes
colour tokens drive everything
typefaces: Hanken + Geist Mono
components keyboard-operable
button implementations
Oversized ink figure, sans label, optional before→after. Illustrative values shown. Use only real, defensible numbers in production.
CodeBlock: framed code window
// Field.astro: accessibility wired in by construction.
// label[for], input id, aria-describedby (hint + error),
// aria-invalid and aria-required are all derived, never hand-set.
const hintId = hint ? `${id}-hint` : undefined;
const errorId = error ? `${id}-error` : undefined;
const describedBy =
[hintId, errorId].filter(Boolean).join(' ') || undefined;
// You cannot create a mislabeled input through this API. FAQ: native details/summary, zero JS
Why only one accent colour?
How is accessibility handled?
What carries motion?
Forms / 05
Accessibility wired in by default.
Field auto-generates ids and
connects label, hint, error, aria-describedby, aria-invalid, and
aria-required. You cannot create a mislabeled input through this API.
ChoiceGroup: fieldset + legend
FormAlert: status & alert
Thanks. Your message is on its way.
I’ll reply within one business day, usually sooner.
We couldn’t send your message.
Please check the highlighted fields.
Motion & spacing / 06
Quiet, deliberate motion.
Animation is restrained and reversible. Transform and opacity only; nothing loops loudly, and everything yields to reduced-motion.
Scroll reveal
.scroll-reveal
Blocks fade up 18px on enter over 0.9s. Disabled entirely under prefers-reduced-motion.
Easing: out
cubic-bezier(.22, 1, .36, 1)
--ease-out: entrances, hovers, and
arrow nudges.
Easing: in/out
cubic-bezier(.65, 0, .35, 1)
--ease-inout: looping or symmetric
motion (e.g. the token-propagation demo).
Section padding scale
Section exposes three vertical rhythms:
tight (py-14 / md:py-20),
default (py-20 / md:py-28), and
loose (py-24 / md:py-36).
Container caps line length at
narrow / default / wide, and Grid handles
1-4 responsive columns. Heavy whitespace does the spacing work. No
decorative fills.