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
| Token | Swatch | Dark | Light | Role |
|---|---|---|---|---|
--accent | #84ced2 | #84ced2 | Fills, focus outlines, large accent text | |
--accent-strong | #5fb8bd | #2f7a80 | Small accent text & hover fills — AA at any size | |
--accent-contrast | #0c0e10 | #0c0e10 | Text/icons placed on accent fills | |
--glow | rgba(132,206,210,.16) | rgba(132,206,210,.28) | Ambient accent halos |
Surface & text tokens
| Token | Dark | Light | Role |
|---|---|---|---|
--bg | #0c0e10 | #f5f4f0 | Page canvas |
--bg-elev | #14171a | #ffffff | Raised surfaces — cards, bars, wells |
--text | #f2f4f5 | #16181a | Primary text |
--text-dim | #9aa3a8 | #5d6468 | Secondary text, captions, labels |
--line | rgba(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.
| Pairing | Dark | Light | Grade |
|---|---|---|---|
--text on --bg | 17.53 : 1 | 16.17 : 1 | AAA |
--text on --bg-elev | 16.31 : 1 | 17.80 : 1 | AAA |
--text-dim on --bg | 7.53 : 1 | 5.47 : 1 | AAA dark AA light |
--text-dim on --bg-elev | 7.01 : 1 | 6.02 : 1 | AAA dark AA light |
--accent-strong on --bg | 8.36 : 1 | 4.53 : 1 | AA any size |
--accent-contrast on --accent | 10.81 : 1 | 10.81 : 1 | AAA |
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.
| Scope | Dark accent | Light 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-strongfor any accent text below ~24px. - Reserve
--accentfills for one primary action per view. - Let
--linehairlines 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.
- 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 / rule | Value | Role |
|---|---|---|
--pad | clamp(1.25rem, 4vw, 4rem) | Page gutter — every section's horizontal padding |
--nav-h | 76px | Chrome height; anchors offset by it via scroll-padding-top |
| Section rhythm | clamp(4rem, 12vh, 8rem)-class paddings | Vertical space between major sections |
| Reading measure | 55–68ch | Max width for running text |
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 → 1at 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.
| Level | Treatment | Used by |
|---|---|---|
| Flat | --bg, separated by --line | Page sections |
| Raised | --bg-elev + hairline | Cards, bars, wells |
| Floating | Soft shadow, e.g. 0 34px 64px rgba(0,0,0,.6) | Hover lifts, hung frames |
| Overlay | Scrim 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 / band | Value | Role |
|---|---|---|
--ease-out | cubic-bezier(0.22, 1, 0.36, 1) | The signature settle — hovers, lifts, reveals |
| Micro | 0.2–0.3s | Color/border feedback |
| Move | 0.3–0.55s | Transform lifts, toggles |
| Reveal | 0.7–1s, once: true | Scroll-in entrances |
| Scrub | scroll-tied | Pinned sequences (gallery walk, hero parallax) |
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-motionand 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:
| Commitment | Implementation |
|---|---|
| Visible focus | Global :focus-visible — 2px --accent outline, 3px offset; never removed without a stronger replacement |
| Bypass blocks | Skip link on every page with chrome, first in tab order |
| Dialogs | role="dialog" + aria-modal, focus moved in, Tab trapped, Esc closes, focus restored |
| Zoom | No maximum-scale or user-scalable=no anywhere — pinch and page zoom always work |
| Semantics | Landmarks per page, one h1, aria-current on active nav, role="switch" on the theme toggle |
| Motion | prefers-reduced-motion honored by every animation, including canvases and pinned scenes |
| Contrast | Only published pairings ship (see Color) — small accent text uses --accent-strong |
| Pointer independence | Every drag/hover affordance has a click/keyboard equivalent; the custom cursor is decorative and fine-pointer-only |