Foundations
Typography
Two typefaces. GT Sectra is a broad-nib serif where the pen origin is visible in the letterforms. It represents the complexity of humans. Pentameter is a monospace with calligraphic movement: fixed widths, lively forms. The built × natural tension lives inside this one typeface. Sectra speaks for people, Pentameter speaks for the system.
The rule is simple. If you're reading sentences, it's Sectra. If you're scanning the interface, it's Pentameter.
Each role is rendered here in its actual specs — the same CSS classes that build the site.
Body is 16px at 1.5 line-height. That gives 24px, which becomes the grid cell. The entire layout system grows from this single typographic decision.
Type does not respond to context. Colour shifts with time of day. Typography stays fixed. It's the anchor.
Pentameter is a variable font (weight axis 100–900, 300–600 used). Sectra is static weights: Book (400) and Medium (500). Bold (700) is reserved for case study content only.
Layout
The cell is 24px, derived from body line-height, and constant at every screen width. The page uses as many square cells as fit, capped at 55 (1320px) and centred above that. The scale runs from space-012 (3px) to space-400 (96px). Every step is a multiple or fraction of the cell, including a 1½-cell step.
The grid is not hidden infrastructure. It's discoverable. This is visible construction in practice. In deliberate moments, the system shows its own working: column widths, type measures, spatial values surfaced on screen.
Spacing is measured because it is derived. Every space comes from the cell; the scale itself may be generous.
Components don't pick from the scale directly. Spacing is assigned by role; every role is a step on the scale. Heading spacing is asymmetric by definition: 36px before, 12px after.
The cell is a CSS custom property. Every spacing token is a calc() expression derived from it.
9 roles, read live from the CSS — this list is generated from the role declarations themselves, so a role added or repointed appears here without an edit. Each role aliases a step. space-sections is the total between sections — where a hairline splits the gap, each side takes half.
Stroke
Every visual boundary is a 1px hairline in --border. No decorative strokes, no rounded corners, no variable weights. Structure is communicated through position and colour, not border treatment.
How it is drawn follows what it bounds. A stroke that encloses content is an outline: a border would occupy layout and push everything inside it a pixel off the cell, and an outline occupies nothing, so the inset stays a spacing role. A stroke that marks a single edge stays a border. Adjacent items sharing an edge use the collapsed shadow below.
Focus rings are the exception: 2px solid in --accent with a 2px offset for visibility.
When adjacent elements share an edge, each hairline must come from a single source. Overlapping borders at fractional pixel positions produce a darker, thicker line — the browser composites two 1px strokes that don't align to the same device pixel.
outline: 1px solid var(--border); outline-offset: -1px box-shadow: -1px -1px 0 0 var(--border) background: var(--ground) on every item Colour
The colour creates a connection between Alan and the viewer. Both share the same sky.
One solar day generates the palette. Dawn, sunrise, noon, sunset and dusk position the colour changes, so sunset pink arrives with sunset rather than at a fixed hour. The solar model uses a representative latitude for your timezone and an approximate noon-centred day.
The atmosphere has three related colours: shadow, body and light. Peach light, rose cloud and violet shadow can coexist at sunset. At noon and midnight the family becomes quieter. These are authored interpretations of light, not measured weather.
Ground holds the page. Ink and secondary ink stay readable on ground and surface; accent identifies interaction. The atmospheric colours belong to Bayer fields, never to text or links. One cell holds one palette colour, and adding hues does not add cloud cover.
Ground and ink switch together at sunrise and sunset. Cloud colours continue through twilight, but the reading surface never fades through an intermediate colour behind text of the opposite polarity.
The clock and the day readout share one time control. Your time shows your sky; Alan’s time shows his. Scrubbing either explorer updates the page and the system window together.
The generation function is the primitive layer. Named tokens describe roles, and components use those roles. OKLCH hue and lightness are preserved while excess chroma is reduced to a shared sRGB gamut, so CSS and canvas agree.
Motion
Motion creates the sense that content belongs where it lands. That's why it's here.
Composition, not animation. Content finds its place on the grid as the viewer scrolls. The grid is already there; the content arrives and settles. Long deceleration: fast at the start because it knows where it's going, slow at the end because it's precise about where it lands. Not bouncing, not snapping, not springing. Settling, because it belongs there.
Pace is slow. Content is being told to you, not thrown at you.
One easing curve: cubic-bezier(0.05, 0.7, 0.1, 1.0). Applied to everything. prefers-reduced-motion removes scroll-driven composition. Everything is simply present. The page works perfectly static because the structure holds without motion.
This section demonstrates itself. Trigger the animation and watch the easing in action.
Iconography
All icons come from Phosphor Light — a fill-based set on a 256-unit viewBox. The light weight matches Pentameter 400 optically without runtime calculation.
Icons live in a central registry (src/lib/icons.ts). The <Icon> component reads from the registry and renders via set:html — no inline SVG paths in consuming components. To add an icon: copy the Phosphor Light path data into the registry, pick a name, and it's available everywhere.
One-off icons that appear in a single context (like the cycling map key) use inline SVG directly. The registry holds two populations: the handful the interface reuses, and one mark per design-system entry — the icons on the cards that open each section here. The test for whether a name belongs is whether you can say what it names.