Specimen · ouroboros-design

Design System

Every value on this page is a live token. Nothing here is a screenshot or a hand-copied hex. Switch the theme and the page restates itself, because it is built out of the thing it documents.

11.251.5631.9532.441
ratio 1.25

Type

--fs-1 … --fs-11

Eleven steps, ascending. Steps 1 to 6 are the text range and hold fixed rem values. Steps 7 to 11 are the display range and are fluid: each one is a clamp() with a viewport term, so a headline shrinks on a phone without a media query.

--fs-61.6rem · 25.6px
text range, top

Institutional betrayal

--fs-51.4rem · 22.4px

Institutional betrayal

--fs-41.2rem · 19.2px

Institutional betrayal

--fs-31rem · 16px
body copy

Institutional betrayal

--fs-20.9rem · 14.4px

Institutional betrayal

--fs-10.8rem · 12.8px
floor

Institutional betrayal

--fs-9clamp(2rem, 4vw, 3rem)

A record that holds

--fs-11clamp(3.5rem, 10vw, 8rem)
display, top

Ouroboros

The text range is not the modular scale. --fs-1 through --fs-6 run 0.8, 0.9, 1, 1.2, 1.4, 1.6rem. That is a hand-tuned ramp, not 1.25 compounded, and pretending otherwise would be a nicer story than a true one.

The ratio steps live separately as --f-1 through --f-5, unitless, for anything that genuinely needs to compute off the scale. The masthead spine above is one such thing. Its tile widths are the ratios, so the spine measures itself.

Leading

--lh-1 … --lh-4

Leading runs backwards against size. Small text needs air between lines to stay trackable. A 64px headline at 1.75 reads as two unrelated lines instead of one sentence, so display type takes --lh-1 and body copy takes --lh-3.

--lh-41.85
long-form prose

Survivors of institutional betrayal rarely arrive needing to be convinced that something went wrong. They arrive needing a record.

--lh-31.75
body default

They arrive needing a record that holds up later, when the people who caused the harm are the ones writing the summary.

--lh-21.35
labels, meta

A record that holds up later.

--lh-11
display only

A record that holds

Spacing

--space-1 … --space-13

Thirteen steps, derived from an audit of the 719 spacing literals that preceded them rather than from a generic 4px grid. The ten values the system already leaned on are all present at their exact former size, which is why most of the migration moved nothing at all.

--space-10.25rem · 4px

--space-20.375rem · 6px

--space-30.5rem · 8px

--space-40.75rem · 12px

--space-51rem · 16px

--space-61.25rem · 20px

--space-71.5rem · 24px

--space-82rem · 32px

--space-92.5rem · 40px

--space-103rem · 48px

--space-114rem · 64px

--space-126rem · 96px

--space-138rem · 128px

Two values sit outside the ramp on purpose. --space-card is 1.4rem and --space-band-lg is 3.5rem. Both are component metrics, and snapping either one would have moved real chrome to satisfy a number.

Sixteen more call sites were left as raw literals. Nine are sub-pixel chip pads at 0.05 to 0.15rem, where the nearest step is a 67% change. Seven are outliers between 4.5 and 10rem. The migration rule was no more than 0.25rem and no more than 25% of movement, and those sixteen failed it. Leaving them visible is more honest than resizing them quietly.

Surface

--surface-1 … --surface-5

Ascending means further forward, in both themes. On a dark ground a drop shadow has almost nothing left to darken, so a raised panel reads as raised because it is brighter than what it sits on. The light ramp keeps the same order rather than inverting it, so hover-to-brighter still reads as lift and not as sink.

--surface-1page floor
#0c101a · #E7EAF0
--surface-2cards
#1a1d26 · #EFF1F5
--surface-3hover, wells
#22262f · #F5F7FA
--surface-4callouts
#252831 · #FCFDFE
--surface-5ink only
#2b2f39 · #FFFFFF

Ink

--ink-1 … --ink-4

Four steps, ascending by emphasis. Brightness accelerates as it climbs: the gap from meta to body is small, and the gap from body to display is large. Faint text sits far from its neighbours so it never competes; display text crowds toward the extreme so it always wins.

--ink-1meta, captions
#999999 · #535971
--ink-2secondary
#B0AAA0 · #3D4353
--ink-3body copy
#E8E4DC · #34394A
--ink-4headings
#FFFFFF · #1C2029

Contrast

WCAG 2.1, every pairing

Measured, not assumed. Every ink and every accent against every surface, computed with the sRGB relative-luminance formula. The same numbers run as a build gate: npm run check:contrast reads the compiled CSS and fails the build if any token drops below its declared floor.

Dark theme. AA requires 4.5:1 for body text and 3:1 for large text and non-text. The worst pairing in the system is 4.26:1, and it is the reason --surface-5 is ink only. In light theme the worst pairing is --mark at 4.93:1 on --surface-1.
on → surface-1surface-2 surface-3surface-4 surface-5
ink-16.675.915.325.174.70
ink-28.247.306.576.385.80
ink-314.9913.2711.9511.6110.56
ink-419.0116.8315.1514.7213.39
mark7.396.545.895.725.20
sage6.435.695.124.984.53
teal6.385.655.084.944.49 ✗
amethyst6.065.364.834.694.27 ✗
steel6.065.364.834.694.26 ✗

The one place the system runs out of room. --steel is the binding token. Holding it at 4.5:1 caps the surface luminance at 0.02432, and --surface-4 already sits at 0.02133. That leaves about one perceptual step, and --surface-5 spends it.

So --surface-5 carries ink and never accent text. The alternative was lightening steel, amethyst and teal, which is a brand change rather than a CSS change. The restriction is written into the token source, the contrast script and the README, so the next person to reach for it finds the reason before they find the bug.

Accent

categorical, not a ramp

Five accents, each bound to a meaning. Gold is the house colour and the only accent allowed to decorate. The other four carry category: announcement type, dashboard audience, service family. Call-site counts are in the captions, and the distribution is the point.

--markhouse gold · 211 uses
#b39f7b · #7A6028
--amethyst12 uses
#A284CA · #6B4F94
--sage10 uses
#6DA187 · #38624C
--teal9 uses
#5DA19C · #295F5A
--steel5 uses
#7F94A6 · #4A5A68

--accent-1 through --accent-5 exist, and you almost certainly want the semantic name. They are aliases, added so the numbered vocabulary is complete. They are not a lightness ramp and the numbers imply an interchangeability this palette does not have.

Reach for --accent-N only when you genuinely mean the nth swatch in the palette, which in practice means a page like this one. Everywhere else, name the meaning.

Elevation

--elev-0 … --elev-5

Shadow carries elevation and lightness carries it back. --shadow is itself a theme token, so every level re-resolves per palette: a near-black at low alpha on dark, a cool grey at 18% on light. Pair each level with the matching surface step and the two channels agree.

--elev-0
none
--elev-1
0 1px 3px
--elev-2
0 2px 8px
--elev-3
0 2px 12px
--elev-4
0 4px 16px
--elev-5
0 24px 60px

Radius

px, not rem

A corner radius is a fixed optical detail, not rhythm, so it does not scale with the root font the way spacing does. The low end is spaced one pixel at a time because 2px, 3px and 4px account for 54 of the 65 radius call sites in the system.

--radius-1
2px
--radius-2
3px
--radius-3
4px
--radius-4
8px
--radius-5
20px
--radius-round
50%

Line

--line-1 … --line-4

Four hairlines, ascending by assertiveness. They are opaque steps rather than white at low alpha, so a border keeps the same value whatever surface it lands on. In dark theme --line-1 is the same value as --surface-3, which is what makes it read as a seam rather than a frame.

--line-1row separators
#22262f · #E1E4EC
--line-2cell borders
#2b3038 · #D4D8E2
--line-3default border
#30343e · #C7CBD6
--line-4assertive frame
#3d424e · #B3B8C6

Measure

--measure: 68ch

Set in ch rather than rem, so the column tracks the font actually rendering rather than a guess about it. Sixty-eight characters is roughly the span the eye tracks before it starts losing the beginning of the next line.

This column is exactly one measure wide. Drag the window narrower and it shrinks with you. Drag it wider and it refuses to follow. A text column that grows to fill a 27-inch display is not generous, it is unreadable, and no amount of leading rescues it. That matters more here than on most sites: a good share of the people reading this are doing it at 200% zoom, on a phone, or under stress, and often all three.

Not legal or mental-health advice · Disclaimer · Privacy · Accessibility