Internal reference
Design system
Every token the three sites are built from, rendered at the size it ships at. Three brand colours, two ramps, one typeface, one motion curve.
01 — Colour
Three colours, two ramps
The kit names navy, blue and white and nothing else. Everything below is
mixed from those three. The previous business's red
#C63A2E
and secondary blue
#1F4E8C
are gone.
The brand
Navy
#131A2C
Ground on dark, text on light.
Blue
#2268BE
Accent. Links, buttons, markers.
White
#FFFFFF
Ground on light, text on dark.
Blue ramp
-
blue-50
#EEF4FC
-
blue-100
#D8E6F8
-
blue-200
#B0CCF0
-
blue-300
#7FADE5
7.5:1 on navy — links on dark
-
blue-400
#4B8AD5
-
blue-500
#2268BE
BRAND · white on it is 5.5:1
-
blue-600
#1B54A0
-
blue-700
#164381
-
blue-800
#123463
-
blue-900
#0E2749
Ink ramp
Navy-derived rather than grey, so a light surface and a dark surface read as the same brand. ink-400 is the kit's support tone and it is a dark-ground colour only — 2.99:1 on white fails every threshold.
-
ink-50
#F5F7FA
the banded light surface
-
ink-100
#EAEDF2
-
ink-200
#D7DCE3
-
ink-300
#B4BCC8
9.2:1 on navy — body on dark
-
ink-400
#8C96A5
KIT support tone. Dark grounds ONLY — 2.99:1 on white
-
ink-500
#5A6478
6.3:1 on white — secondary
-
ink-600
#39455F
-
ink-700
#26314A
12.0:1 on white — body
-
ink-800
#1B2438
-
ink-900
#131A2C
BRAND navy · 17.3:1 against white
-
ink-950
#0A0E1A
the deep neutral
02 — Contrast
Every pairing the site ships
Rendered on the ground it is actually used on, because a ratio quoted
against the wrong backdrop is worse than none.
npm run check:contrast
recomputes all of these from the tokens and fails the build on a regression.
On white
Heading — navy · 17.3:1
Body — ink-700 · 12.0:1. The default reading colour.
Secondary — ink-500 · 6.3:1.
ink-400 on white — 2.99:1. NEVER. Shown only so the failure is visible.
On navy
Heading — white · 17.3:1
Body — ink-300 · 9.2:1. The default on dark.
Secondary — ink-400 · 5.8:1.
blue-500 on navy — 3.13:1. Large text only. This is the logo's own pairing, which WCAG exempts; body copy is not exempt.
03 — Type
One family, Inter Variable
The brand kit specifies no typeface — the wordmark is outlines, so nothing has to match it. Display character comes from size, weight and optical tracking rather than from a second family. Montserrat was removed: 37KB saved, and a geometric-sans heading over a grotesk body is the single loudest template signal a page can carry.
text-display
44 → 88px · 0.98 · −0.035em · 700
Display — data, AI, software, marketing
text-h1
36 → 64px · 1.05 · −0.03em · 700
Heading 1 — data, AI, software, marketing
text-h2
28 → 48px · 1.1 · −0.025em · 650
Heading 2 — data, AI, software, marketing
text-h3
20 → 26px · 1.25 · −0.015em · 600
Heading 3 — data, AI, software, marketing
text-h4
17 → 19px · 1.35 · 600
Heading 4 — data, AI, software, marketing
text-lead
18 → 22px · 1.55
Lead — data, AI, software, marketing
text-body
17px · 1.7 · 68ch measure
Body — data, AI, software, marketing
text-sm
15px · 1.6
Small — data, AI, software, marketing
text-xs
13px · 1.5 · the floor
Extra small — data, AI, software, marketing
text-eyebrow
12px · 0.16em · 600 · uppercase
Section label
text-figure
36 → 56px · 700 · tabular
2022 · 725318 · 100%
The measure
Body copy is capped at 68ch, which lands
between 62 and 70 characters per line at 17px. The rule below marks the cap.
Buying technical work usually means choosing between two bad shapes: the firm that sells senior and delivers junior, or the one that never had senior at all. The enterprise consultancy sends partners to the pitch and graduates to the project. The offshore shop quotes a rate that only works if nobody senior touches it. Both are answering a staffing question, and neither is answering yours, which is whether the thing should be built.
04 — Space
Eight-point scale
Steps 1 and 2 are the half steps icon gaps and label margins need. Everything from 3 up is a multiple of eight.
- space-1 4px
- space-2 8px
- space-3 12px
- space-4 16px
- space-5 24px
- space-6 32px
- space-7 40px
- space-8 48px
- space-9 64px
- space-10 80px
- space-11 96px
- space-12 120px
- space-13 144px
- space-14 176px
Section rhythm
--section-y
144px
The default band. 72px on a phone.
--section-y-lg
176px
Used once, under the hero.
--section-y-sm
96px
Strips and the closing band.
Read section padding as half a seam. Bands stack, so two adjacent sections contribute 288px between their content. That is affordable because the ground colour alternates — a seam reads as a change of surface rather than as a void.
05 — Elevation and radius
Hairline first, shadow second
A card is defined by its edge. The shadow only appears when it lifts. Every shadow is tinted with navy rather than black, so shade reads as the page's own colour. Dark surfaces get borders and never shadows — a shadow on navy is invisible and still costs a paint.
--shadow-sm
Resting chip, focused input.
--shadow-md
A raised panel.
--shadow-lg
A card under the pointer.
--radius-sm
0.5rem
--radius-md
0.75rem
--radius-lg
1.25rem
--radius-xl
2rem
06 — Motion
One curve, three durations
Restrained and confident, which rules out overshoot. The previous
system's spring curve —
cubic-bezier(0.34, 1.4, 0.64, 1)
— sprang past its end value and back. It was deleted rather than retuned.
--ease
cubic-bezier(0.22, 0.61, 0.36, 1)
Entering, and anything responding to a pointer.
--dur
400ms
The standard. Transform, size, anything spatial.
--dur-slow
700ms
Scroll reveals, which travel further.
--stagger
90ms
Between siblings in a revealed group.
The four cards above are a live stagger — reload to watch them arrive.
Everything on this page respects prefers-reduced-motion,
and the reveal starting state only applies when the document is scripted,
so a crawler sees finished content rather than an empty page.
07 — Components
Buttons, cards, chips, fields
Buttons on light
The primary button was red. It is blue now, and white on blue-500 is 5.53:1 — which is the whole reason the CTA could move off the retired colour without inventing a fourth brand hue.
Buttons on dark
Cards
A linked card
Hairline edge, an accent rule drawn on hover, and the whole surface as the hit area.
Read moreA static card
No lift, because nothing here is clickable.
On a dark section
Border, never a shadow.
Chips
Fields
That email address does not look right.
The invalid state cannot be colour alone now that the palette has no red —
blue on blue would be indistinguishable from focus. It is a thicker,
darker edge plus the message, and aria-invalid
carries it either way.
red-50,
which was itself a colour the brand tokens forbid.
Icons
Inline stroke paths on a 24px grid, 1.5 stroke,
currentColor throughout. Referenced by name
from page frontmatter.
- globe
- users
- shield
- grid
- academic
- book
- plane
- passport
- briefcase
- building
- chat
- home
- tag
- handshake
- alert
- arrow-right
- arrow-up-right
- chevron-down
- check
- menu
- close
- map-pin
- clock
- scale
- chart
- cpu
- code
- server
- megaphone
- file-text
- database
- git
- gauge
- layers
- search
- sparkle
- plug
- lock
- eye
- refresh