Design System
The values the site is built from, rendered from the same stylesheet the site uses. Nothing here is a mockup, so nothing here can be out of date.
Two pages. This one holds the values, the other holds what is built from them, because one page carrying both is a scroll, not a reference. Each section shows its tokens as they render, with the one rule that governs them. Read the first column of a row as the name to type, in the form it is declared in. Reasons sit beside each rule in the stylesheet itself.
Type Scale
Ten sizes. A token is named for its desktop size, and everything from 24px up scales down to a mobile floor between 360px and 1280px, so headings shrink smoothly. Labels and body copy stay fixed because they already sit at their floor. The count is how many rules use each token, and the ratio is the step up from the size above it.
12px is the floor, and there is no token below it. Every step is 1.12x or wider except the first: 12px is chrome (the header bar and the footer) and 13px is a label inside content, a spatial step, not a typographic one.
--text-1212px · 12 rules--text-1313px · 37 rules · 1.08x--text-1616px · 41 rules · 1.23x--text-1818px · 19 rules · 1.12x--text-2424px · 15 rules · 1.33x--text-3232px · 3 rules · 1.33x--text-4040px · 1 rule · 1.25x--text-4848px · 2 rules · 1.20x--text-5656px · 4 rules · 1.17x--text-7272px · 1 rule · 1.29xUppercase Labels
Two families, and the family is the tracking. Size has nothing to do with it. A section label a reader scans to find their place gets 0.14em and is always 13px. Every other uppercase label gets 0.08em, at 13px inside content and 12px across the chrome. Three rules sit outside both, listed below, because a family of two only means anything if the exceptions are named.
The three exceptions, each with its reason written beside it in the stylesheet. The swatch hex renders a project's own brand color as a number in tabular figures, and tracking would undo the alignment that buys. The wordmark's initials drop to 0.04em, because at the wordmark's own 0.08em the hyphen in S A-P opens into a gap and the pair stops reading as one. A brand specimen sets its characters at 0.02em, which is type being shown, not type doing a job.
Tracking is the one family on this page with nothing to type. The four tracked values above are literals wherever they are set, so a new label copies a number out of a neighboring rule rather than naming a family, and a third family could arrive without anyone deciding on one.
The crumb and the project preview label are not in the chrome row above. Both are destinations a reader clicks rather than labels naming something else, so neither takes tracking or uppercase.
Some of the rows above are chrome, shown here on paper because that is where this page is. Their contrast in this row differs from production, where they sit on the dark bar.
Color
Two tiers. The scale below holds sixteen neutral steps and says nothing about what any of them is for. The roles under it say what a color is for and point at a step. Nothing else in the stylesheet names a step directly, and that rule is what makes the second tier worth having.
A step is named for its oklch lightness times a thousand, so 650 is 0.650 and the distance between two steps is the difference between their names. The site is neutral by design; a project's own brand color arrives per entry from its content and never enters the scale.
The Neutral Scale--neutral-000Ink on paper, and only when the page is printed--neutral-090Below the page's own darkest ink, and only ever a device bezel--neutral-150The text on a call to action, and nothing else--neutral-220The dark page, and the primary text on paper--neutral-260Chrome, and a panel standing on the dark ground--neutral-310The default line on the dark ground, the overlay link fill, and text on hover on paper--neutral-340The overlay link fill on hover--neutral-470Secondary text on paper, and an outline on hover on the dark ground--neutral-650Secondary text on the dark ground, and an outline on hover on paper--neutral-860The line on paper--neutral-900Text on hover on the dark ground--neutral-920The call to action fill--neutral-940Raised fills on paper, the band a diagram panel stands in--neutral-955The panel a diagram sits in--neutral-1000The sheet a page is printed on, and only then--neutral-970The paper ground, primary text on the dark ground, and the pill on hoverEight roles that .paper rebinds. Every component that reads them follows onto the light ground without being told, and that is the whole mechanism. Both grounds have exactly one muted text level. There was a dimmer step below it; it was measured at 1.46:1 where it was used, which fails WCAG AA, and lifting it far enough to pass would have put it a hundredth away from muted. So there is no third level, and that is the finding rather than the compromise.
--groundThe surface a component stands on. Dark 220, paper 970.--raisedA panel standing on the ground, one step above it. Dark 260, paper 940.--frame-bgThe panel a diagram sits in: a framed story row, an annotated plate, a numbered sequence. Paper 955, and no value on the dark ground, because all three live in a case study body and a body is always paper.--lineEvery border a component draws. Dark 310, paper 900.--on-groundPrimary text and the focus ring. Dark 970, paper 220.--on-ground-mutedSecondary text. Dark 650, paper 470.--on-ground-hoverWhere muted text goes when you point at it, a step short of the top. Dark 900, paper 310.--line-hoverWhere a resting hairline goes on hover, doubled rather than run to white. Dark 470, paper 650.These do not rebind, and that is the point. Chrome is the frame around the page, not something standing on it. The call to action is the one thing a reader should recognize in the same color wherever it appears, so a filled pill is the same pill on both grounds.
--chromeChrome that stands on the page: the menu overlay, the entry dropdown, the footer, the floating scroll-up, and a bar control under the pointer. Always 260. Every one of those has an edge of its own, which is the condition for using it.--chipThe overlay link fill. Always 310.--chip-hoverThat fill on hover. Always 340.--seamThe header bar's own ground. Always 220, which is the page's value, so the bar seams into the page instead of sitting a step above it. It stayed a fixed token rather than becoming --ground because a paper band must never be able to turn the bar white.--ctaThe filled pill. Always 920.--cta-hoverThat pill on hover. Always 970.--on-ctaText on the pill. Always 150.--bezel-inkThe ink of a phone bezel in a step sequence. Always 090, below the dark page's own darkest ink, because a device is the same object on either ground.The two grounds do not mirror each other. Secondary text is 0.65 on a 0.22 ground, a lightness gap of 0.43. On paper it is 0.47 on 0.985, a gap of 0.515. The paper version is the more legible of the two, so muted text looks quieter on the dark page than it does in a case study.
Spacing and Rhythm
One base unit, 8px. Anything that moves the page down is a multiple of it. Line heights are snapped to the same grid, so a paragraph in one column lands level with an image in the next.
--space-18px--space-216px--space-324px--space-432px--space-648px--space-864px--space-1296px--space-5--space-7--lh-body32px--lh-title48px--lh-caption24px--measure30em, about 67 characters at any sizetext-wrap: balance does that job for display type by another route.--measure-max40em, about 90 characters--measure.--cover-ratio16:9Breakpoints
Seven widths, and not one of them is a device. Each is the measurement where something stops working: a column narrower than the label above it, a grid about to strand one item, a tile under the size its artwork needs. The width is written where that crossing was measured, rounded away from it so the next change to the type scale does not land back on the edge.
They are not a scale and they do not nest. A rule takes the one width its own problem happens at, which is why two of these carry a single block and one carries ten. Two more exist as the opening half of a band, min-width: 601px and min-width: 769px, and are counted with the width they close against.
min-width: 1280px1 blockmax-width: 1100px1 blockmax-width: 1024px2 blocksmax-width: 900px2 blocksmax-width: 768px10 blocksmax-width: 600px5 blocksmax-width: 480px2 blocksA grid of equal peers never leaves exactly one item on the last row.
Three go three across or one down, never two and a widow. A row of five takes three, so the last row holds two together rather than one alone. An index that wraps is exempt: its count is set by a filter, and no fixed rule could be right for every one.
Nothing here reads prefers-color-scheme. The body is a fixed light band inside dark chrome and there is no second theme to switch to, so a width is the only thing any of these rules asks about.
Corner Radius
Four values and a rule for which is which. Anything that does not fit one of the four is a question about the component, not a reason for a fifth value.
--radius-sm3px--radius-md6px--radius-lg10px--radius-pill999pxIcons
One box and one weight. Every icon is a 24 unit viewBox drawn at 16px, which puts a stroke of 2 on screen at 1.33px: heavier than the 1px keyline beside it, lighter than the 700 weight label beside it. Nothing here is an icon font and nothing is a raster.
Box16 × 16Stroke2, set in CSSColorcurrentColorThe direction an arrow points is the thing it does, so the set is small and each one means one thing.
Backarrow leftForwardarrow rightTo toparrow upExternalarrow outDisclosechevron downEvery arrow is drawn, and none is a character. A text arrow sets in the weight of the type around it, so it can never match a drawn icon beside it, and on a site carrying this many icons the two always end up side by side. No exception for an arrow inside a sentence.
Diagrams
Six drawn figures on this site: an annotated screenshot, a numbered sequence, a flow of boxes, a plain panel, the Flield sitemap and the HomeBase operations map. They are markup and not exported plates, because a 22px label baked into a 2048px image arrives at about 4px on a phone, and a diagram is all label. Six of anything drifts, so they share one set of parts.
Panelthe figure itself--frame-bg, --radius-lg, --space-4 of padding, and no border. A diagram owns its panel, so its section never carries {framed}: two panels a few grays apart is one container too many.Cella node inside it--ground, a 1px --line keyline and --radius-md. Never --raised, which is the trap below.Core cellthe one everything points at--on-ground-muted and its text inverted to --ground, keylined in its own fill. 6.5:1, so every line on it clears the floor. One per figure or it stops meaning anything.Type13, 16 and 18.story__group sets. A micro label is 13. A note or a line of body is 16 at --lh-caption, muted. Nothing else.Numbersmarker or ordinalDepth is a fill, one even step lighter at every nesting. A keyline is definition; the fill is what carries the level.
- A case study section,
--neutral-940 - A diagram panel standing on it,
--frame-bgat 955 - A cell inside the panel,
--groundat 970
Fifteen steps between each, which is close enough to read as one level and far enough to read at all. Three surfaces inside the top 3% of the scale leave a panel with no presence against the page it stands on.
--raised is the trap in that ladder. The name reads as a surface that comes forward, and on the dark ground it is one. Inside .paper it resolves to --neutral-940, the section ground itself and a step darker than the panel holding it, so a cell painted with it recedes. A chip inside a cell may take it on purpose, since a chip sits back into its card where a card lifts off its panel.
All six hold to one short list, and a build that breaks it fails instead of shipping. Three type sizes, two weights, one keyline at 1px --line, and no cell painted --raised.
Motion
Five durations, each a token, so a new rule picks a name and not a number. A tint, a thing moving, a panel opening, a section arriving. Every name says what moves and leaves the milliseconds to the token. Under a reduced-motion preference all four collapse to effectively instant.
Two easings carry them. --ease is for a control answering a pointer, where the movement is short and a curve would read as lag. --ease-out is for a thing arriving and settling, which spends most of its distance early and lands slowly.
Two more tokens hold a gap, not a duration. One is a time, pacing one thing against the next inside a single arrival: --step-reveal at 60ms for the parts of a revealing block, and for the headline, the blurb and the actions on the homepage. The other is not a time at all. --settle-span answers scroll position, so it is a share of a card's travel into view, and a millisecond would mean nothing there.
--dur-tint150ms · 37 uses--dur-move180ms · 16 uses--dur-panel220ms · 25 uses--dur-reveal420ms · 18 uses--dur-draw1100ms · 1 useA sixth kind of movement takes no duration at all, because it answers scroll position and not a clock.
- The bar turning solid
- The hero text drifting against the covers rising through it
- The covers settling into place, every one below the fold
- The rule beside a section number drawing itself across
Each follows a finger exactly and reverses on the way back up. A browser with no scroll timeline gets the finished state and nothing else.
Covers arrive level. Same distance, same curve, same moment, so a row rises into its place as a row. --settle-span at 60% is the share of a card's travel into view that the settle uses, which puts the movement in the part of the travel a reader is watching and leaves the last 40% at rest.
The homepage's top row is the exception, and the reason is arithmetic rather than taste. A scroll-driven settle has to start somewhere other than its end, so a card already on screen at load would be offset at rest: measured, 11.8px and 24px low, showing 38% and 35.3% of themselves against the 40.6% the layout gives them. Scrolling carries that row off the screen rather than into it, so the offset would be the only state of it most readers saw. The top row runs the same distance and the same curve on a timer instead, once, finished in 250ms of its 420.
Which covers those are is a question about the fold and not about the markup. Two of them from 769 up, where the grid is two across; one below that, where the second cover measures 0% visible at load and settles against scroll like every other cover on the site.
Distance is --settle-from, 8% of a card's own height rather than a pixel count, which is what lets one value serve both grids at every width: 36.6px on the homepage at 1440, 27.2px on the three-up index, 24.1px on a phone, and no media query anywhere.
A fixed distance would need a breakpoint, and no breakpoint fits, because these covers do not get smaller as the screen does. At 768 the homepage goes one across and its cover becomes the largest on the site at 720 by 491, which is the same width where the work index drops to a 196px tile.
Two earlier versions sequenced the row, first with different travel per column and then with equal travel and offset starts. Both drew a diagonal, and a diagonal claims the covers have an order. They do not. The second version was the better built of the two and still left one cover 11px below its neighbor for a whole scroll, which reads as a grid out of alignment rather than as depth.
Scroll-driven rules write translate or scale, never transform itself. That is a rule, not a preference.
Such an animation is filled in both directions, so it holds the property it writes for the life of the page, and an animation outranks a normal declaration in the cascade. Written to transform, one of these closes the property to every hover on the same element. Independent transform properties compose, so the scroll keeps one and the states keep the other.
Typeface
--font-sans