FILE /blueprint
SECTION
SCROLL
The Durability Curve London · 2026.05
LIVE BST v0.1 68 TOKENS 6 ATOMS ACCENT VIEWPORT PX
File /blueprint
Rev 2026.05

Blueprint.

Every component on this site descends from one file of tokens. Move a token; the brand moves with it. This page is the receipt.

Est. 03.2026 · London 51.5074°N · 0.1278°W

Section 01 / 06

Foundations

Sixty-eight values. One file. One cascade.

Every pixel on the site is downstream of these. Colour, type, space, motion. Change one in tokens.css and the cascade tells the truth: instantly, here, in production, and in every component below.

Palette

· · 68 tokens

Type

Fraunces · Inter · Mono

Contrast

··

Ink on background · live

Spacing

Modular · base 16

Radius

· · base

Motion

200ms · standard ease

Contrast

Signal strength on every text-on-surface pair the site reads against.

Aa

Body text on background

Aa

Body prose

Aa

Labels, secondary

Aa

Button text on accent

Aa

Accent text on bg

Aa

Critical badge text

Colour

Fourteen tokens. Three surfaces of ink. Three of rule. One accent that does most of the editorial work.

Typography

Three families across nine ranks. Fraunces speaks for the brand, Newsreader carries the prose, Inter handles the controls.

Display

Hero title

H1

Section openers

H2

Sub-section heads

H3

Card titles

H4

Inline emphasis

Body large

Lede paragraphs

Body

Running text

Caption

Captions, meta

Micro

Labels, stamps

Spacing

Thirteen stops on a 4px base. Geometric. Predictable. The page is built out of these gaps.

STOP 01 --space-1
STOP 02 --space-2
STOP 03 --space-3
STOP 04 --space-4
STOP 05 --space-5
STOP 06 --space-6
STOP 07 --space-8
STOP 08 --space-10
STOP 09 --space-12
STOP 10 --space-16
STOP 11 --space-20
STOP 12 --space-24
STOP 13 --space-32

Radii

Six ranks. Never custom. Pills only where the shape itself is a pill.

RNK 01

Small

4px --radius-sm

Inline tags · code chips

RNK 02

Base

6px --radius-base

Buttons · inputs · mini pills

RNK 03

Medium

10px --radius-md

Cards · panels · default surfaces

RNK 04

Large

14px --radius-lg

Featured cards · modal surfaces

RNK 05

X-Large

20px --radius-xl

Sheet · drawer · hero surfaces

RNK 06

Pill

999px --radius-pill

Capsule chips · toggles · avatars

Shadows

Four ranks. Used sparingly. A shadow on every card is no shadow at all.

Small

--shadow-sm

Rest state · subtle lift

Base

--shadow-base

Hover · focus · default elevation

Large

--shadow-lg

Modal · popover · drop layer

Glow

--shadow-glow

Accent emphasis · success state

Motion

Four durations. Fast for feedback, slow for transitions, slower for page-level change. Respects prefers-reduced-motion at every step.

CH 01

Fast

--duration-fast

Micro feedback. The fastest visible response.

120 ms
CH 02

Base

--duration-base

Hover, focus, default transitions.

180 ms
CH 03

Slow

--duration-slow

Section transitions. The reader has time to notice.

280 ms
CH 04

Slower

--duration-slower

Page-level change. Use sparingly.

500 ms

Principles

Five rules the page refuses to break. Read these before extending anything.

  1. 01

    One primary per page

    cta.singular

    The primary names the one decision the page is asking the reader for. Two primaries split the eye and halve the conversion.

  2. 02

    Tokens before components

    tokens.css → cascade

    Every visible value descends from a token. If you find yourself writing a literal hex or rem in a component, the system has a gap. Fix the token, not the component.

  3. 03

    Accessibility is not a layer

    a11y.contract

    Focus rings, semantic tags, contrast minimums, reduced-motion fallbacks all ship with the component, not after it. Stripping them means re-implementing them, badly.

  4. 04

    Every claim has a contract

    claim.signed

    A Badge that says Live must point at a measured behaviour. A Heading that says H1 must be the page's first heading. A button that says Subscribe must take a payment.

  5. 05

    Architecture outlives content

    law-iii.active

    The scaffold persists. The content turns over. Spend the time on the scaffold, on this file, and the brand earns interest on every page that descends from it.

Section 02 / 06

Atoms

Six components. No exceptions.

Every page on the site is built from these. If a piece of UI on this site can't be built from them, the spec is wrong. The atom isn't. Each one ships with anatomy, props, do / don't, and the accessibility contract it refuses to break.

Button

Primary · Secondary · Ghost

Badge

Recommended

7 variants

Inventory

06Live

Anatomy · Props · A11y

Button

Where the page asks the reader to commit. Three variants. Primary for the page's one main decision, secondary for adjacent moves, ghost for inline affordance. If you find yourself reaching for a fourth, it isn't a Button you need.

Change variant, size, label, and disabled state. The code panel regenerates as you do.

Controls

Code

Anatomy

Container border 1.5px · radius var(--radius-base)
Label font-sans · weight 600 · gap var(--space-2)
Background var(--c-accent), hover --c-accent-soft
Padding sm 3·5 · base 4·6 · lg 5·8
Focus ring 3px var(--c-accent-tint) on :focus-visible
Hover lift translateY(-1px) · var(--duration-fast)

States

default
:hover
:focus-visible
:active
[disabled]

Variants

primary · base
secondary · base
ghost · base
primary · sm
primary · lg
primary · disabled

Props

Prop Type Default Description
variant 'primary' | 'secondary' | 'ghost' 'primary' Visual emphasis. Primary commits the page; secondary supports it; ghost gives an inline link button affordance.
size 'sm' | 'base' | 'lg' 'base' Use lg for hero CTAs only.
href string When set, renders as <a> instead of <button>. Use for navigation, never for actions.
target string Pass "_blank" for external links; set rel="noopener" with it.
rel string Required when target="_blank" for security.
type 'button' | 'submit' | 'reset' 'button' Ignored when href is set.
disabled boolean false Sets aria-disabled and blocks pointer events.
fullWidth boolean false Stretches to container width. Use only in tight columns or forms.
children slot The label. Verb-first, under five words.

Do · Don't

Do

One primary per page. The primary names the decision the page is asking for. If two compete, the reader makes neither.

Don't

Two primaries on the same screen. The eye splits, the conversion halves, and the page stops being about anything.

Do

Verb-first label, under five words. Start Operator. View free tracker. Read the case.

Don't

Sentence-shaped CTAs. Click here to learn more about subscribing is not a button label, it's a meeting.

Accessibility

What this guarantees, and what you must not strip when composing.

Keyboard

  • Tab focuses; Enter or Space activates.
  • Disabled buttons are skipped by the focus order but still announced to screen readers via aria-disabled.

Screen reader

  • Renders as semantic <button> by default; <a> when href is set. Never override that.
  • The label slot must contain plain text. Icon-only buttons require aria-label.

Focus

  • 3px --c-accent-tint ring on :focus-visible, visible against every surface in the token system.
  • Focus survives theme switching; we do not remove the outline to look cleaner.

Contrast

  • Primary: --c-bg on --c-accent, AA verified live on the Foundations grid.
  • Secondary: --c-ink on --c-bg at AAA in both themes.

Motion

  • Hover translates 1px up. We honour prefers-reduced-motion by collapsing the lift to zero.

Badge

A status without a sentence. Every Badge is a claim the rest of the page has to back up. Use one. Use it precisely. Critical binds; warn watches; success confirms. The rest are categorisation chrome.

Pick a variant for the semantic role (status, category, ticker), change the size, change the label.

Recommended

Controls

Code

Anatomy

Recommended
Surface fill var(--c-accent) on accent; transparent on outline
Label font-sans · weight 700 · letter-spacing widest
Casing uppercase by default; opt out for tickers
Padding sm 1·1.8 · base 1.4·2.8 (rem ×0.25)
Radius var(--radius-sm) — never pill, never square
Line height 1 — chip is the bounding box

Variants

Recommendedaccent · primary call-out
Criticalcritical · binding
Constrainedwarn · watch list
Confirmingsuccess · confirmed
Outlineoutline · secondary
Subtlesubtle · context
NVDAdefault · ticker
Newaccent · sm

Props

Prop Type Default Description
variant 'default' | 'accent' | 'critical' | 'warn' | 'success' | 'outline' | 'subtle' 'default' Semantic role. Pick by meaning, not by colour you want.
size 'sm' | 'base' 'base' Use sm when the Badge sits inline with body copy. base for standalone chips.
uppercase boolean true Opt out for proper nouns and tickers (NVDA, HIMS) where capitalisation already encodes meaning.
children slot One or two words. If it needs three, it isn't a Badge.

Do · Don't

Do
Binding

One Badge per surface. The Badge promises the eye that this is what to read first.

Don't
NewHotTrendLive

A row of Badges across one card. The eye gives up; nothing is first; the surface goes flat.

Do
Binding constraint

Pick by semantic meaning. Critical is a binding constraint. Success is something we have measured and confirmed.

Don't
New post

Pick by colour. "I want a red Badge here" is not a reason to use critical. The visual register collapses the moment the semantics do.

Accessibility

What this guarantees, and what you must not strip when composing.

Keyboard

  • Badges are not focusable; they are decorative or informational, not interactive.
  • If a Badge needs to be clickable, it isn't a Badge. Use a Button or a link with appropriate styling.

Screen reader

  • Renders as a plain <span>. Screen readers announce the label as inline text.
  • For status that isn't obvious from prose, add role="status" on the parent or wrap in a live region.

Focus

  • No focus state by default. Badges cannot be tab targets.

Contrast

  • accent, critical, warn, success: AA against the chip surface (verified in token system).
  • outline and subtle: AAA on body backgrounds in both themes.
  • default: AAA muted-on-soft pair, designed for tickers and tertiary labels.

Motion

  • Static. Badges do not animate; movement here would compete with primary CTAs.

Kicker

The line that tells the reader what section they're in without making them read the heading. Small. Uppercase. Always above a Heading, never alone. If the heading carries the section context on its own, the Kicker is editorial decoration. Cut it.

Switch tone between muted and accent. Edit the label.

Section context

Controls

Code

Anatomy

03 — Section

Casing uppercase, no exceptions
Type font-sans · weight 700
Tracking var(--tracking-widest) — 0.18em
Tone muted (default) · accent for sections that bind
Line height 1 — sits flush above the heading
Spacing below var(--space-4) margin — fixed by component

In situ

Foundations

The tokens the brand stands on

The Kicker tells you where you are. The Heading tells you what we are about to say. The pair earns the section break.

Props

Prop Type Default Description
tone 'muted' | 'accent' 'muted' Use accent for sections that bind a decision (purchase, conversion, hero CTAs). Use muted everywhere else, and that is most places.
children slot One short label. Numbered prefix (01 —) is editorial, not structural. Drop it if a section has no siblings.

Do · Don't

Do

03 — Section

A clean signpost

Pair every Kicker with a Heading directly beneath it. The two are one structural unit.

Don't

Floating context

A standalone Kicker floating in space. Without a Heading underneath, it reads as a typographic mistake.

Do

Foundations

One Kicker per section. The Kicker is a signpost, and you only get one signpost per turn.

Don't

Header

Subheader

Note

A Kicker on every sub-block. The uppercase tracking starts to feel like a SaaS dashboard, and the editorial register collapses.

Accessibility

What this guarantees, and what you must not strip when composing.

Keyboard

  • Not focusable. Kickers are static labels, never interactive.

Screen reader

  • Renders as <p>. Screen readers announce it as prose preceding the Heading.
  • Do not use a Kicker as the section's accessible heading; that is the Heading's job. Use both.

Focus

  • Inherits no focus state.

Contrast

  • muted: --c-muted on body surface. AAA in light, AA-large in dark.
  • accent: --c-accent on body surface. AA verified across both themes.

Motion

  • Static. Kickers should not move; they anchor the section visually.

Check

The smallest possible yes on the site. One glyph, no copy, paired with a line of text that earns it. If a Check needs explanation, you're using the wrong atom. Use a Badge, or rewrite the line.

Pick a tone. Swap the glyph if a list needs ★ emphasis or a bullet.

Controls

Code

Anatomy

Glyph ✓ default · ★ emphasis · • bullet
Bounding box 1rem × 1rem · flex-shrink 0
Weight font-weight 700 — earns its presence
Tone accent · success · muted
Alignment baseline with the line beside it
A11y aria-hidden — decorative only

Variants

accent · ✓
success · ✓
muted · ✓
accent · ★
muted · •

In situ

  • Live token editor with five presets and full keyboard control.
  • Light and dark themes derived from the same primitives, AA verified.
  • Anatomy, props, and an accessibility contract for every atom.

Props

Prop Type Default Description
tone 'accent' | 'success' | 'muted' 'accent' Use accent for default confirmations, success for items you have personally verified, muted for tertiary lists where the glyph should not lead the eye.
symbol string '✓' Swap to for emphasis lines or when a list should read as bullets rather than affirmations.

Do · Don't

Do
Free 30-day refund

Pair every Check with a line of text that names the affirmation in plain language.

Don't

A row of standalone Checks without text beside them. Reads as a sticker book, not a claim.

Do
Live token editor Audited AA contrast

Mix tones with intent. One success line in a list of accent lines tells the reader which item you have proven.

Don't
One Two Three

Random tone variation. If every line is a different colour, none of them mean anything.

Accessibility

What this guarantees, and what you must not strip when composing.

Keyboard

  • Not focusable. Check is decorative.

Screen reader

  • aria-hidden="true" on every Check. The list line beside it carries the meaning.
  • If a list of features must be announced as a list, wrap items in <ul>. Not Check on its own.

Focus

  • No focus state. Check is never a tab target.

Contrast

  • accent and success: visible against every surface in the token system.
  • muted: meets AA on body surfaces. Never use for status that must be read.

Motion

  • Static.

Heading

Every level of the page hierarchy in one component. Semantic level (h1h6) is decoupled from visual size, because screen readers care about the first and designers care about the second. Use both. Lie about neither.

Switch the visual size, font family, and text-wrap balance. Semantic level stays h1 in this demo; change it in code for real use.

Which bottleneck is binding right now

Controls

Code

Visual scale

Display heading

H1 heading

H2 heading

H3 heading

H4 heading italic

Anatomy

Which bottleneck is binding

Semantic level h1–h6, set on the tag — the contract with screen readers
Visual size display · h1 · h2 · h3 · h4 — independent of level
Font display (Fraunces) by default · serif (Newsreader) opt-in
Tracking var(--tracking-tighter) on display + h1; tight on the rest
Line height var(--leading-tight) — 1.15 across the scale
Balance text-wrap: balance by default — kills orphans

Props

Prop Type Default Description
level 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' 'h2' Semantic level. Determines the tag. Set by the document structure, not by visual preference.
size 'display' | 'h1' | 'h2' | 'h3' | 'h4' Visual size override. Defaults to match level. Use to render a visually smaller h1 or a visually larger h3 when the layout demands.
font 'display' | 'serif' 'display' display is Fraunces (the brand voice). serif is Newsreader, used when the heading wants to read more like an essay than a poster.
balance boolean true Applies text-wrap: balance. Kills the one-word last line that always looks broken in display type.
children slot The heading text. No trailing punctuation in display sizes; periods make sense in body, not in posters.

Do · Don't

Do

The Durability Curve

Subsection that follows the H1

Set level by the document hierarchy. One h1 per page. Then style with size if you need to break visual rank.

Don't

First H1

Second H1

Third H1

Pick level="h1" because it looks bigger. Three h1s on a page is a screen-reader failure, regardless of how it renders.

Do

Which bottleneck is binding right now

Leave balance on. text-wrap: balance is the cheapest typography upgrade we ship.

Don't

Which bottleneck is binding right now

Hard-wrap with <br>. The site is responsive; your line break isn't.

Accessibility

What this guarantees, and what you must not strip when composing.

Keyboard

  • Headings build the page outline screen reader users navigate with. Tab doesn't apply, but H, 16, and rotor navigation in VoiceOver and NVDA do.

Screen reader

  • Tag matches level exactly. There is no path through the API that produces a mismatch.
  • One h1 per page. Subsequent h2h6 nest under it. We do not skip levels.

Focus

  • Inherits none. Headings are not focusable unless explicitly given tabindex.

Contrast

  • --c-ink on every body surface in both themes. AAA across the board.

Motion

  • Static. We do not animate headings on entry; the size carries the weight already.

ThemeToggle

Light or dark. Persisted. No flash. Boring on purpose. This is one of the most-clicked controls on the site and the contract is: it must not surprise anyone, ever. The interesting behaviour lives in the inline script that runs before the first paint, not in the button.

Click. The whole page switches. Reload; your choice persists.

Anatomy

Surface transparent, 1px border in --c-rule
Shape 2.2rem square, var(--radius-pill) — circle by side effect
Glyph sun in dark, moon in light · swap is CSS-only
Hover border + glyph → var(--c-accent)
Persistence localStorage 'tdc-theme'
First paint init script in <head> reads storage before render

States

default
:hover
:focus-visible

Props

Prop Type Default Description
(none) behaviour-driven ThemeToggle takes no props. The button reads its aria-label and the inline script handles persistence, OS-preference fallback, and the icon swap. There is nothing to configure; that is the point.

Behaviour contract

  • 1OS preference on first visit. If localStorage.tdc-theme is unset, the theme inherits from prefers-color-scheme. The user's system gets to set the default.
  • 2Persistence across sessions. Click sets data-theme on <html> and writes to localStorage. Survives reload, survives navigation.
  • 3No flash of wrong theme. The init script runs in BaseHead, before the first paint. The page never renders the wrong theme for even a single frame.

Do · Don't

Do

One ThemeToggle in the header, full stop. The control belongs where users expect it on every site they have ever used.

Don't

A second toggle in the footer "for convenience". Two toggles, two sources of truth, twice the bugs.

Do
<head> <script is:inline> /* read storage, set data-theme, before paint */ </script> </head>

Trust the inline script. The flash-free contract depends on the init running synchronously in <head>; do not move it.

Don't
document.addEventListener( 'DOMContentLoaded', () => { /* too late */ } );

Wrap the init in an event listener. DOMContentLoaded fires after first paint. You will ship a flash, and it will not be subtle.

Accessibility

What this guarantees, and what you must not strip when composing.

Keyboard

  • Tab focuses; Enter or Space toggles.
  • The toggle lives in the header tab order. Every page has the same shortcut to switch themes.

Screen reader

  • aria-label="Toggle dark mode" on the button. The current state is implicit in the rendered icon, which is aria-hidden.
  • Future: consider aria-pressed to expose the binary state more directly to assistive tech.

Focus

  • 3px --c-accent-tint ring on :focus-visible. Identical to Button; keyboard users get one focus vocabulary across the site.

Contrast

  • Border --c-rule on body surface, visible in both themes.
  • Hover state shifts to --c-accent, which is AA against both surface tokens.

Motion

  • Border and colour transitions only. We do not animate the theme switch itself; the cross-page flip would create motion sickness on long pages.

Iconography

Stroke-based 24×24 SVGs. 1.5px stroke at base size. currentColor everywhere so they inherit ink and theme correctly. Click any to copy.

Section 03 of 06 · Status Building · 1 of 6 specified

Molecules

Atoms in concert.

A molecule is a fixed composition. Each one declares its rhythm — the gap between its atoms — once, and every instance on the site inherits it. The Hero, the page-frame stamps, the terminal bar: each becomes a molecule with a spec, not a one-off layout.

HeroBlock

Title + lede + scroll cue. One rhythm, used wherever a section opens with a major statement.

SPECIMEN

Capability accumulates.

Atoms compose into molecules. Molecules compose into organisms. The rhythm is fixed once, then everywhere inherits it.

Continue
RHYTHM
01Title → Lede
var(--space-5)20px
02Lede → Scroll
var(--space-8)32px
03Title leading
0.86tight
04Lede measure
44remmax

Every HeroBlock on the site reads from these tokens. Change --space-5 in Theme Studio and the title → lede gap moves everywhere this molecule is used.

LabelValueStamp

Key + value (+ optional sub) in mono caps. Used as every corner stamp on the page — page-frame corners, hero corners, anywhere a fixed label needs to read as instrument output.

SPECIMEN
File /blueprint
Est. 03.2026 · London 51.5074°N · 0.1278°W
SECTION /molecules
RHYTHM
01Key → Value · stacked
2pxtight
02Key → Value · inline
var(--space-2)8px
03Value → Sub
2pxtight
04Tracking
0.14emmono caps

All 7 corner stamps on this page render through LabelValueStamp. Edit the key or value via Tinker Mode and the change persists per stamp; edit the spec values here and every stamp moves together.

Queue

The remaining molecules. Each is a known composition pulled from the site; each will land here with the same SPECIMEN + RHYTHM treatment.

  • M03MetricRowLabel · value · unit, dot-separated. The terminal bar pattern.
  • M04CardSurfaceHeading + lede + footer chips. The reference grid + research cells.
  • M05TokenRailMeta column + visual rail + tip readout. Spacing, Motion.
  • M06ChromaticTitleThe six-layer title used in Hero and ChapterBreak.

Section 04 of 06 · Status Queued · After molecules

Organisms

Composed surfaces.

Composed sections built from molecules. Building next: Hero, TierGrid, LayerGrid, MigrationFeed, FAQList, FounderNote, Header, Footer.

Organisms ship after molecules. Each will be a single Astro component. Import it once, use it on any page.

Section 05 of 06 · Status Live · BlogTemplate

Templates

Page-level scaffolds.

Page-level scaffolds, thin by design: data plus a template. Live: BlogTemplate, the real article asset, built from every atom above. Building next: TrackerTemplate, IndexTemplate.

Section 06 of 06 · Status Live · 8 lineages

Reference

What this stands on.

The design knowledge this blueprint inherits from. Pulled from people who have already solved it, and named here so the lineage is visible.

Atomic Design

Brad Frost — Atomic Design (2016)

The five-tier taxonomy (atoms → molecules → organisms → templates → pages). The blueprint's structural backbone.

Component playgrounds

Storybook · Histoire · Ladle

Args panel, live controls, code snippet generation. We've built the same pattern in vanilla Astro to avoid framework lock-in.

Token systems

Style Dictionary · Tokens Studio · Open Props

Tokens-as-source-of-truth, with mathematical scale derivation. Type and spacing here scale via ratio formulas, not hand-typed values.

Theme builders

Material Theme Builder · Carbon Theme Builder · Adobe Spectrum

Live preset palettes, accessibility-checked colour pairs, export pipeline. The Theme Studio's preset bar comes from here.

Accessibility

WCAG 2.2 · Radix UI

Live contrast ratio checking with AA/AAA badges. Focus-visible rings on every interactive element. Keyboard navigation by default.

Visual register

Vercel Geist · Linear · Stripe Press · Stratechery

Editorial restraint. Confident typography. Sharp focus states. No decoration that doesn't earn its place.

Component libraries

shadcn/ui · Radix Themes · Geist UI

Copy-paste primitives over framework lock-in. Astro components live in the repo, owned by us — not in a node_modules package we can't change.

Pattern documentation

Shopify Polaris · GitHub Primer · Atlassian Design

Do's & don'ts, anatomy diagrams, behavioural notes per component. Pattern shipping next.

LIVE BST v0.1 68 TOKENS 6 ATOMS ACCENT VIEWPORT PX