Design system
Design System
The skeleton shared by every page of the site: grid, layout tokens, vertical rhythm, type hierarchy and light/dark behavior. It doesn't define any section's content — it defines how everything inside breathes. It's the internal criterion the site is built and audited against.
Page grid
A wide container for layout; a narrow reading measure for running text.
Container
Max 1360px, centered. Above xl the margins grow; the content never stretches.
Margins (page-x)
clamp(1.25rem, 5vw, 2.5rem) — 20px on mobile, up to 40px on desktop.
Reading measure
Running text never exceeds 42rem (~91 char.) even when the container is wide. The classic comfortable measure runs 45 to 75 characters, so 91 is above it — a conscious wide-container decision, not an oversight.
Layout tokens
This page's own contribution. They don't exist anywhere else in the system and move to the repository when the site is built.
Fixed, not editable in production
--section-y and --measure have fixed values: clamp(4.5rem, 9vw, 9rem) and 42rem. There's no tuning panel anymore — the rhythm is the same across the whole site.
Defined once
They live in the site's :root alongside the color tokens; each page consumes them. Changing a value here changes it everywhere.
Breakpoints
base0 – 639pxOne column, everything stacked. aside above the content. Type at the clamp minimum.
sm≥ 640pxButtons and metadata in a row; still one content column.
md≥ 768pxThe aside + content two-column grid appears. Margins grow.
lg≥ 1024pxFull 12-column grid; aside can go sticky; 3-card grids.
xl≥ 1280pxContainer caps at 1360px, centered. Margins absorb the extra width.
They match the repo's Tailwind scale. This document also applies fluid type and spacing with clamp(): between breakpoints the size interpolates continuously, with no abrupt jumps.
Vertical rhythm and spacing
A 4px base scale (Tailwind). The air between sections is the primary tool of hierarchy.
Spacing scale
Rhythm between sections
- Between major sections: --section-y — 72px mobile → 144px desktop.
- Section title → content: 40px (2.5rem).
- Between blocks within a section: 24–32px. Optional hairline divider.
- Every section opens with a 1px var(--border) divider full width.
Type hierarchy
Bricolage Grotesque (600) for headlines · Inter for text and UI. Fluid sizes: mobile = clamp minimum, desktop = maximum.
Light and dark
Same skeleton, same surface hierarchy: background → card → border. No heavy shadows — depth comes from hairlines and surface contrast. Light is warm paper; dark is deep blue.
Color rule
- primary is the only action color. secondary / muted / accent stay neutral.
- Links, in two rules: primary for content links; foreground / muted-foreground for navigation chrome (nav, breadcrumb, footer).
- Brand tones decorate or sign the logo — with one exception: brand-purple-accent is a brand tone used as text over inverted backgrounds (AA-large, ≥3:1). Splits and pastels, never as text.
Use the header toggle to see this same page in both modes.
Motion
- Reveal = fade + 12px rise, staggered ~60ms between children.
- Restrained motion: it never competes with the content.
- prefers-reduced-motion: transforms and auto-scroll are disabled. Content always appears, even if the JS fails.
Nav transition — shared across the whole site
- Symbol 48 → 28px and bar 80 → 64px, continuously with scroll.
- The split's color layers extinguish before the symbol drops below 48px — it never passes through a mis-registration state.
- The wordmark fades in opacity without clipping glyphs: the gap only collapses once it's invisible.
- prefers-reduced-motion: a jump between states, no interpolation.
It's the transition you're seeing right now in this page's header.
Accessibility
The list each page of the site closes with. AA as a non-negotiable floor; AAA whenever it's reached without visual cost.
Measured contrast
With this change, every text pair in the system reaches AAA in light and dark — none is left at AA. The one intentional exception is brand-purple-accent, which passes only as large text (≥3:1) over inverted backgrounds, never as body.
Closing checklist
- 01
Measured contrast, with a figure, in both themes. AA is the non-negotiable floor; AAA whenever it's reached without visual cost.
- 02
Visible focus: a 2px ring with var(--ring) and a 2px offset on every interactive element. Never outline:none without a replacement.
- 03
Touch targets of 44×44px minimum, including small controls like the breadcrumb or the theme toggle.
- 04
A single h1 per page and an h2–h4 hierarchy with no skips. Reading order is DOM order.
- 05
Breadcrumb on every internal page, with <nav aria-label>, an ordered list and aria-current="page" on the current level.
- 06
Nothing encoded by color alone: any state or category distinguished by color also carries text or shape.
- 07
prefers-reduced-motion respected in every animation.
- 08
Text alternatives: alt and labels where they inform, aria-hidden on the decorative.
The internal criteria and the measurements. For whoever builds or audits the site.
The public statement: the level the site meets and how to report a problem. For a visitor. It doesn't live here.
Navigable skeleton
A schematic mockup of the home with placeholders — no defined content. It only demonstrates the vertical rhythm and grid shared by Hero, Milestones, How I work and the rest of the sections.
The mobile version is shown. The Desktop / Tablet / Mobile comparison is available on wide screens.