Design system

Tokens

The raw CSS custom properties exposed by src/css/global/variables.css. Foundations describes how each token is used; this section is the flat reference.

Every token on this page is a CSS custom property defined once in src/css/global/variables.css. The full :root block is in the Code drawer; the sections below are the flat, copy-ready reference.

Color tokens

Every --color-*, --glass-*, and --gradient-* token from variables.css. Source values are oklch() for perceptual lightness; transparency is expressed in-channel (oklch(… / 0.6)) rather than as a separate alpha token. For deeper context on how the palette composes — brand vs neutral vs surface — see Foundations › Colour.

Brand

--color-blue
oklch(66.623% 0.1177 217.10)
#0aa5c3
--color-blue-light
oklch(77.31% 0.13351 211.702)
#0bcbe6
--color-blue-dark oklch(58.216% 0.1005 212.646)
--color-green
oklch(77.183% 0.1766 131.692)
#8acb46
--color-green-dark oklch(64.020% 0.1502 131.396)
--color-red oklch(62.83% 0.2089 25.21)

--color-red is the error state for the contact form (the data-state="error" message under the submit button). It is intentionally the only non-brand chromatic token — success reads as --color-green and there is no warning or info colour yet.

Neutral ramp

--color-white oklch(96.826% 0.0069 247.896)
--color-grey-50 oklch(20.768% 0.0398 265.755)
--color-grey-100 oklch(23.157% 0.0398 262.510)
--color-grey-200 oklch(27.950% 0.0368 260.031)
--color-grey-300 oklch(37.170% 0.0392 257.287)
--color-grey-400 oklch(71.067% 0.0351 256.788)
--color-grey-500 oklch(55.439% 0.0407 257.417)
--color-grey-600 oklch(86.898% 0.0198 252.894)
--color-grey-700 oklch(92.876% 0.0126 255.508)
--color-grey-800 oklch(96.826% 0.0069 247.896)
--color-grey-900 oklch(98.415% 0.0034 247.858)

Lightness is the first OKLCH channel — in this dark palette, low numbers are deep panels and high numbers are bright text. The opposite of a Tailwind ramp.

Surfaces

--color-body-bg oklch(16.140% 0.0289 266.826)
--color-surface-solid oklch(18.020% 0.0325 266.625)
--color-header-solid-bg oklch(16.140% 0.0289 266.826 / 0.95)

Text

--color-text oklch(86.898% 0.0198 252.894)
--color-text-muted oklch(71.067% 0.0351 256.788)
--color-heading oklch(96.826% 0.0069 247.896)

Glass

--glass-bg oklch(20.768% 0.0398 265.755 / 0.6)
--glass-bg-light oklch(20.768% 0.0398 265.755 / 0.4)
--glass-border oklch(71.067% 0.0351 256.788 / 0.12)
--glass-border-hover oklch(71.067% 0.0351 256.788 / 0.22)
--glass-blur var(--blur-xl)

--glass-blur is an alias of --blur-xl from the blur scale below, so backdrop blur and filter blur stay on the same set of steps.

Blur scale

Four steps reused by every backdrop-filter: blur() and filter: blur() in the system. The chips below have a noisy gradient behind them so the blur step actually shows up.

--blur-sm 0.25rem
--blur-md 0.5rem
--blur-lg 0.75rem
--blur-xl 1rem

Brand tints

Transparent overlays of the brand colours, generated with color-mix(in srgb, var(--color-blue|green) N%, transparent). The numeric suffix is the brand-colour percentage. Use these instead of rgba(10, 165, 195, …) in component CSS so every surface stays on the same tint ramp.

--tint-blue-5 color-mix(in srgb, var(--color-blue) 5%, transparent)
--tint-blue-8 color-mix(in srgb, var(--color-blue) 8%, transparent)
--tint-blue-10 color-mix(in srgb, var(--color-blue) 10%, transparent)
--tint-blue-12 color-mix(in srgb, var(--color-blue) 12%, transparent)
--tint-blue-15 color-mix(in srgb, var(--color-blue) 15%, transparent)
--tint-blue-20 color-mix(in srgb, var(--color-blue) 20%, transparent)
--tint-blue-25 color-mix(in srgb, var(--color-blue) 25%, transparent)
--tint-blue-30 color-mix(in srgb, var(--color-blue) 30%, transparent)
--tint-green-5 color-mix(in srgb, var(--color-green) 5%, transparent)
--tint-green-8 color-mix(in srgb, var(--color-green) 8%, transparent)
--tint-green-10 color-mix(in srgb, var(--color-green) 10%, transparent)
--tint-green-12 color-mix(in srgb, var(--color-green) 12%, transparent)
--tint-green-15 color-mix(in srgb, var(--color-green) 15%, transparent)
--tint-green-20 color-mix(in srgb, var(--color-green) 20%, transparent)
--tint-green-25 color-mix(in srgb, var(--color-green) 25%, transparent)
--tint-green-30 color-mix(in srgb, var(--color-green) 30%, transparent)

Gradients

--gradient-accent linear-gradient(135deg, blue/grey 0-10%, green/grey 90-100%)
--gradient-accent-shimmer linear-gradient(90deg, blue, green, green, blue)

The shimmer gradient pairs with background-size: 200% 100% and an animation: shimmer… keyframe to produce the looping accent. See it live on the animations page.

Grid & shimmer accents

--color-grid-line oklch(71.067% 0.0351 256.788 / 0.08)
--color-grid-line-light oklch(71.067% 0.0351 256.788 / 0.06)
--color-shimmer oklch(100% 0 none / 0.25)

Decorative opacities

Numeric tokens consumed by the hero blob field and other layered backdrop washes. Tune these per surface to dial intensity up or down without changing the base colours.

--hero-blob-blue-1 0.18
--hero-blob-blue-2 0.12
--hero-blob-green-1 0.15
--hero-blob-green-2 0.10
--bg-blue-1 0.10
--bg-blue-2 0.06
--bg-green-1 0.07
--bg-green-2 0.05

Hero signal canvas

Read by src/js/hero-signals.js at runtime to colour the trail dots. Splitting the colour into r/g/b channels lets the canvas pre-compute the alpha lookup without parsing strings every frame. See Brand › Illustration for the recipe.

--signal-trail-r 10
--signal-trail-g 165
--signal-trail-b 195
--signal-trail-multiplier 0.4
--signal-dot oklch(66.623% 0.1177 217.10 / 0.5)

Components

Single-component tokens kept here rather than in the component CSS so they live alongside the rest of the palette.

--benefits-card-bg oklch(20.768% 0.0398 265.755 / 0.6)
--partner-item-bg oklch(26.48% 0.01276 243.42)
--partner-logo-opacity 0.5
--partner-logo-hover-opacity 0.85

Filter tokens (--partner-logo-filter, --partner-logo-hover-filter) drive the partner-logo greyscale-to-colour transition; they have no visual swatch since they describe an image filter, not a colour.

Type tokens

Two family tokens and a 12-step modular size scale, all in variables.css. Sizes are in rem so they respect the user’s root font size; the scale isn’t a strict ratio — spacing between steps tightens at the small end and opens up at the display end so headlines have room to breathe. For the family rationale and weight inventory, see Foundations › Typography.

Families

--font-text Aa Bb 0 1 2
--font-heading Aa Bb 0 1 2

Size scale

--font-size-xs 0.75 rem · Aa
--font-size-sm 0.875 rem · Aa
--font-size-base 1 rem · Aa
--font-size-lg 1.125 rem · Aa
--font-size-xl 1.25 rem · Aa
--font-size-2xl 1.5 rem · Aa
--font-size-3xl 1.875 rem · Aa
--font-size-4xl 2.25 rem · Aa
--font-size-5xl 3 rem · Aa
--font-size-6xl 3.75 rem · Aa
--font-size-7xl 4.5 rem · Aa
--font-size-8xl 5.5 rem · Aa

Spacing tokens

Twelve steps on a 0.25 rem base, with a 7th-step skip from --space-6 to --space-8 — a deliberate gap, since 1.75 rem is a value the layout never needs. Use these for every margin, padding, and gap; never fall back to raw rem or px. For the rhythm rationale see Foundations › Spacing.

--space-1 0.25 rem · 4 px
--space-2 0.5 rem · 8 px
--space-3 0.75 rem · 12 px
--space-4 1 rem · 16 px
--space-5 1.25 rem · 20 px
--space-6 1.5 rem · 24 px
--space-8 2 rem · 32 px
--space-10 2.5 rem · 40 px
--space-12 3 rem · 48 px
--space-16 4 rem · 64 px
--space-20 5 rem · 80 px
--space-24 6 rem · 96 px

Radius tokens

Five named steps and a pill sentinel, all in variables.css. The scale doubles loosely (4 → 8 → 12 → 16 → 24 px) so consumers can pick by feel rather than by arithmetic; --radius-full is a 9999 px sentinel that produces a pill on rectangles and a circle on squares. For the visual specimens at full size and the per-role “where they're used” cross-reference, see Foundations › Radii.

--radius-sm 0.25 rem · 4 px
--radius-md 0.5 rem · 8 px
--radius-lg 0.75 rem · 12 px
--radius-xl 1 rem · 16 px
--radius-2xl 1.5 rem · 24 px
--radius-full 9999 px · pill / circle

Shadow tokens

Four elevation steps plus two branded blue chips. Y-offset and blur grow together on the neutrals so each step reads as “the card is lifted further off the page” rather than “the shadow got bigger”; alpha climbs slightly with elevation (0.30 → 0.45) to keep contrast steady against the dark body. --shadow-glow is a soft brand-blue halo for primary-button hover; --shadow-focus is a tight brand-blue ring for input :focus states. Neither belongs on the neutral elevation ramp. For the visual specimens and per-token consumer list, see Foundations › Shadows.

--shadow-sm 0 1 px 2 px / 30 % alpha
--shadow-md 0 4 px 6 px / 35 % alpha
--shadow-lg 0 10 px 15 px / 40 % alpha
--shadow-xl 0 20 px 25 px / 45 % alpha
--shadow-glow 0 0 24 px brand blue / 35 % alpha
--shadow-focus 0 0 0 3 px brand blue / 18 % alpha

Motion tokens

Three duration tokens on the default ease curve plus one shared emphasis curve and its 600 ms preset. The defaults stay on ease because most state changes (hover swap, dropdown fade, focus ring) want symmetric in-out timing; reach for --easing-emphasis (and its --transition-emphasis shortcut) on slower transforms where decelerating into the resting state is what sells the move — hover lifts on hero shapes, blob drifts, the placement-tabs cross-fade. Bespoke keyframes (gradient shimmer, pulse rings) still define their own timing rather than tweaking a global. For the full motion catalogue see Animations.

Transitions

--transition-fast 150 ms ease
--transition-base 250 ms ease
--transition-slow 400 ms ease
--transition-emphasis 600 ms var(--easing-emphasis)
--easing-emphasis cubic-bezier(0.22, 1, 0.36, 1)

fast for hover colour swaps and underline accents. base for the skip-link reveal, the dropdown panel fade, and most state changes. slow for the staggered fade-in on the contact page and any decorative scroll-triggered entry. emphasis for hero/placement-tabs cross-fades and parallax-style hover lifts where the deceleration is the effect. The demo bars above honour prefers-reduced-motion and freeze when set.

Z-index layers

A four-step stack. Treat the named layer as the contract — never write a raw z-index value in component CSS, even for “just one notch above the header”.

--z-background -1
--z-content 1
--z-header 100
--z-overlay 200
--z-modal 300