Contour · Design System v1.0.0 ← Back to drewbmeyer.com

Contour / Foundations

Foundations

The rules everything else is built on: a compact token set with published contrast math, a two-family type system, fluid spacing, one easing curve, and a written accessibility floor.

Color#

Contour is dark-first with a full light mode. The palette is deliberately small: two neutral surfaces, two text colors, a hairline, and a single teal accent pulled from the topographic hero. Restraint is what lets the expressive parts — galleries, case-study bands — own their color without the chrome competing.

Accent tokens

TokenSwatchDarkLightRole
--accent#84ced2#84ced2Fills, focus outlines, large accent text
--accent-strong #5fb8bd#2f7a80Small accent text & hover fills — AA at any size
--accent-contrast#0c0e10#0c0e10Text/icons placed on accent fills
--glowrgba(132,206,210,.16)rgba(132,206,210,.28)Ambient accent halos

Surface & text tokens

TokenDarkLightRole
--bg #0c0e10 #f5f4f0Page canvas
--bg-elev #14171a #ffffffRaised surfaces — cards, bars, wells
--text #f2f4f5 #16181aPrimary text
--text-dim #9aa3a8 #5d6468Secondary text, captions, labels
--linergba(242,244,245,.12)rgba(22,24,26,.12)Hairline borders & dividers

Published contrast

Every text pairing the system permits, with its computed WCAG ratio. If a pairing isn't in this table, don't ship it.

PairingDarkLightGrade
--text on --bg17.53 : 116.17 : 1AAA
--text on --bg-elev16.31 : 117.80 : 1AAA
--text-dim on --bg7.53 : 15.47 : 1AAA dark AA light
--text-dim on --bg-elev7.01 : 16.02 : 1AAA dark AA light
--accent-strong on --bg8.36 : 14.53 : 1AA any size
--accent-contrast on --accent10.81 : 110.81 : 1AAA

Case-study accents

Each gated case study re-themes the accent tokens under a [data-case] scope to meet the client's brand — the mechanism the specimen theme-switches on these pages exploit. Light-mode values are darkened per palette until small text passes AA.

ScopeDark accentLight accent (AA)
[data-case="bicycle"] #b7e84a #567400
[data-case="eleanor"] #f5c518 #1950cc
[data-case="league"] #9b86ff #4226b8
[data-case="hca"] #ff7547 #c4471c
[data-case="idg"] #25c39a #0a7d5f

✓ Do

  • Use --accent-strong for any accent text below ~24px.
  • Reserve --accent fills for one primary action per view.
  • Let --line hairlines do the separating; save shadows for overlays.

✕ Don't

  • Don't set body copy in the accent — it's an emphasis color, not a reading color.
  • Don't invent new grays; there are exactly two text colors.
  • Don't ship a pairing that isn't in the published-contrast table.

Typography#

Two families, clear jobs: Gabarito (--font-display) carries every heading, label and UI string; Inter (--font-body) carries running text. All display sizes are fluid clamp() expressions — there are no breakpoint font jumps anywhere in the system.

Type scale — live
Display Gabarito 700 · clamp(3.4–8.5rem) · lh 0.9
Section title Gabarito 600 · clamp(1.7–3.4rem)
Lead paragraph for section intros Gabarito 500 · clamp(1.4–2.2rem) · lh 1.3
Body — Inter at 1rem with a 1.7 line height, measured to 55–68 characters for comfortable reading. Inter 400 · 1rem · lh 1.7
Label / small caps Gabarito 600 · 0.72–0.8rem · tracking 0.12–0.34em
290% Numerals — Gabarito 700 · clamp(2.6–3.9rem)
  • Negative tracking scales with size: −0.015em to −0.04em on display sizes, never on body or labels.
  • Small caps always track wide: uppercase labels get 0.12–0.34em letter-spacing and usually --text-dim.
  • The homepage hero is the one exception: it cycles through nine loaded display faces as a signature interaction — a licensed stunt, not a pattern.

Spacing & layout#

Spacing is fluid-first: a single page gutter token plus clamp() rhythm per section, rather than a fixed step scale. Vertical rhythm between sections breathes with the viewport.

Token / ruleValueRole
--padclamp(1.25rem, 4vw, 4rem)Page gutter — every section's horizontal padding
--nav-h76pxChrome height; anchors offset by it via scroll-padding-top
Section rhythmclamp(4rem, 12vh, 8rem)-class paddingsVertical space between major sections
Reading measure55–68chMax width for running text
--pad across viewports

20px floor → 4vw fluid → 64px ceiling

Layout patterns

  • Content sections — single column, gutter --pad, headings hung from a section-head block.
  • Two-column grids — e.g. the About grid (1fr 1.3fr) collapsing to one column below tablet.
  • Masonry walls — CSS columns, 3 → 2 → 1 at 900px/560px, used by photo galleries.
  • Full-bleed stages — 100vh pinned corridors (gallery walk) and fixed canvases (Lab) that own their own internal spacing.

Radius & elevation#

Corners follow a five-stop scale. Elevation is expressed with hairlines first, shadows second: flat surfaces separated by --line, with soft, large shadows reserved for things that truly float — hover lifts, dialogs, hung artwork.

Radius scale
8 · inputs
10–12 · small
14–16 · cards
18–24 · large
999 · pill
50% · round
LevelTreatmentUsed by
Flat--bg, separated by --linePage sections
Raised--bg-elev + hairlineCards, bars, wells
FloatingSoft shadow, e.g. 0 34px 64px rgba(0,0,0,.6)Hover lifts, hung frames
OverlayScrim rgba(8,9,11,.93) + backdrop-filter: blur(8–14px)Lightboxes, modals, sticky bars

Motion#

One signature curve, three duration bands, two properties. Motion is built on GSAP + ScrollTrigger with Lenis smoothing page scroll; micro-interactions are plain CSS transitions.

Token / bandValueRole
--ease-outcubic-bezier(0.22, 1, 0.36, 1)The signature settle — hovers, lifts, reveals
Micro0.2–0.3sColor/border feedback
Move0.3–0.55sTransform lifts, toggles
Reveal0.7–1s, once: trueScroll-in entrances
Scrubscroll-tiedPinned sequences (gallery walk, hero parallax)
Feel the curve — hover each puck
0.3s · ease
0.55s · --ease-out
1s · --ease-out

Rules

  • Transform and opacity only. Nothing that triggers layout ever animates.
  • Entrances run once. Scroll reveals use once: true — content never re-hides behind the reader.
  • Reduced motion is a real mode. Every animated surface checks prefers-reduced-motion and renders a calm, complete state — Lenis off, reveals instant, canvases static or removed.
var prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!prefersReduced) {
  /* tween, pin, scrub… */
} else {
  /* set the finished state immediately */
}

Accessibility#

The floor is WCAG 2.2 AA, verified — not aspired to. Every page of the site and of this documentation is scanned with axe-core in both themes; keyboard flows are tested by hand. These are the standing commitments:

CommitmentImplementation
Visible focusGlobal :focus-visible — 2px --accent outline, 3px offset; never removed without a stronger replacement
Bypass blocksSkip link on every page with chrome, first in tab order
Dialogsrole="dialog" + aria-modal, focus moved in, Tab trapped, Esc closes, focus restored
ZoomNo maximum-scale or user-scalable=no anywhere — pinch and page zoom always work
SemanticsLandmarks per page, one h1, aria-current on active nav, role="switch" on the theme toggle
Motionprefers-reduced-motion honored by every animation, including canvases and pinned scenes
ContrastOnly published pairings ship (see Color) — small accent text uses --accent-strong
Pointer independenceEvery drag/hover affordance has a click/keyboard equivalent; the custom cursor is decorative and fine-pointer-only
How it's checked. axe-core 4.10 (WCAG 2.0/2.1/2.2 A + AA rulesets) across all pages in dark and light, plus manual keyboard passes over every dialog, gallery and the Lab canvas. Findings land as fixes, not tickets.
© 2026 Drew B Meyer Contour v1.0.0 Built from css/style.css — the docs render the production system live