# 02 — Design system Authority: `AGENTS.md` §3 D7 — *keep the palette and the infinity mark; modernize the execution.* The look is not up for redesign. What follows is the system that preserves it while fixing what the old build got wrong. --- ## What carries over unchanged - The **palette**: cream, ink, maroon, gold. - The **infinity mark** — SML Company Ltd.'s actual logo, and the metaphor holds: a dispute is a loop, and the work is redrawing the loop into a line. - The **type pairing**: Instrument Serif for display, Geist for text, Geist Mono for eyebrows and labels. - The **editorial register** — generous whitespace, restrained colour, serif display type at large sizes. ## What changes | Was | Is | Why | |---|---|---| | Google Fonts at runtime | Self-hosted, subset, preloaded | Removes a render-blocking third-party round trip from a page that collects legal inquiries | | Fixed px type sizes | Fluid `clamp()` scale | One scale from 360 px to 1600 px with no breakpoint jumps | | Ad hoc spacing values | 8 px base scale | Consistent vertical rhythm; no magic numbers | | Gold used as a text colour on cream | Gold restricted to decorative and on-dark | **It fails WCAG AA at 2.10:1.** Measured, not assumed | | Scroll-reveal on every element, always on | Reveal on major sections only, gated behind `prefers-reduced-motion` | Motion that reads as confident rather than decorative; accessible by default | | 2.2 MB single file, ~1 MB of base64 logos | Optimized SVG mark, AVIF/WebP photography | The mark is geometry; it should be vector, not a 470 KB PNG | | React 18 dev build + Babel Standalone in the browser | Static HTML, near-zero JS | The reason the site is invisible to crawlers | --- ## Colour Tokens live in `src/styles/tokens.css`. Never write a raw hex value in a component. | Token | Value | Use | |---|---|---| | `--cream` | `#faf7f2` | Page background | | `--cream-2` | `#f3ede0` | Alternating section background | | `--cream-3` | `#ebe3d1` | Cards and insets on cream | | `--ink` | `#1a1614` | Body text; dark section backgrounds | | `--ink-soft` | `#3a322c` | Secondary text | | `--muted` | `#6e6359` | Metadata, captions — **on cream only** | | `--maroon` | `#5a1a1c` | Primary action, accents, dark panels | | `--maroon-d` | `#3d1112` | Hover on maroon | | `--maroon-l` | `#7a2a2c` | Links on cream | | `--gold` | `#c9a876` | Rules, dividers, on-dark accent — **never text on cream** | | `--gold-d` | `#a88858` | Large decorative text on cream only | | `--gold-l` | `#e2c89a` | Text on ink or maroon | ### Contrast — measured, 2026-08-25 Against `--cream` `#faf7f2`: | Colour | Ratio | AA body (4.5) | AA large (3.0) | |---|---|---|---| | `--ink` `#1a1614` | **16.81** | pass | pass | | `--ink-soft` `#3a322c` | **11.75** | pass | pass | | `--maroon` `#5a1a1c` | **12.29** | pass | pass | | `--maroon-l` `#7a2a2c` | **8.95** | pass | pass | | `--muted` `#6e6359` | **5.47** | pass | pass | | `--gold-d` `#a88858` | **3.11** | **FAIL** | pass | | `--gold` `#c9a876` | **2.10** | **FAIL** | **FAIL** | Against `--maroon` `#5a1a1c`: cream 12.29 · gold-l 8.11 · gold 5.84 — all pass. Against `--ink` `#1a1614`: cream 16.81 · gold-l 11.09 · gold 8.00 — all pass. `--muted` on ink measures **3.07 and fails**; use `--gold-l` or cream at reduced opacity for secondary text on dark. **Hard rules.** 1. `--gold` is never a text colour on cream. Rules, borders, dividers, icon strokes, and on-dark text only. 2. `--gold-d` on cream only at 24 px+ / 19 px bold, and only for decorative display text — never for anything a reader must parse. 3. Body text on cream is `--ink` or `--ink-soft`. Metadata may use `--muted`. 4. Secondary text on dark is `--gold-l` or `--cream` at ≥ 70% opacity. 5. **No dark mode.** A single committed light identity is the right call for a legal practice, and halving the surface area halves the ways contrast breaks. --- ## Typography **Faces.** Instrument Serif (display) · Geist 300/400/500/600 (text) · Geist Mono 400/500 (eyebrows, labels, data). Self-host all three. Subset to Latin + Latin Extended-A. `font-display: swap`. Preload only the two faces used above the fold — Instrument Serif regular and Geist 400. **Scale.** Fluid, `clamp()`, `1.25` ratio at the small end widening to `1.333` at the display end. Tokens `--text-xs` through `--text-6xl` in `tokens.css`. **Rules.** - Display type (`--font-serif`) at `--text-4xl` and above only. It has almost no hinting at small sizes and looks weak below 32 px. - Display line-height `0.95`–`1.05`; letter-spacing `-0.02em`. - Body line-height `1.6`. Measure capped at `68ch` — the old site ran full-bleed paragraphs at 1400 px, which is unreadable. - Eyebrows: mono, 11–12 px, `0.18em` tracking, uppercase, always paired with a real heading. An eyebrow is not a heading and never carries the ``. - Italic display (`.it`) is the one flourish the design allows. One italic phrase per headline, at most. - Never skip a heading level. `

` once per page. --- ## Spacing and layout 8 px base: `--space-1` `4px` · `-2` `8` · `-3` `12` · `-4` `16` · `-5` `24` · `-6` `32` · `-7` `48` · `-8` `64` · `-9` `96` · `-10` `128` · `-11` `160`. Content width `1280px`; prose measure `68ch`; wide media `1440px`. Gutters: `24px` mobile, `48px` desktop. Section rhythm: `--space-9` (96px) mobile, `--space-11` (160px) desktop. Grid: 12 columns desktop, 6 tablet, 4 mobile, `--space-5` gutter. --- ## Motion The old build animated nearly everything on scroll. The replacement is deliberate and quiet. - **Reveal on section entry only** — not on every child element. Sub-element stagger is limited to card grids, and capped at six children. - Duration `600ms`, easing `cubic-bezier(.2,.7,.2,1)`. Transform and opacity only — never layout properties. - Implement with `IntersectionObserver` in one tiny inline script, or `animation-timeline: view()` where supported. Not a framework, not a library. - **Content is visible without JavaScript.** The reveal is an enhancement layered on top of already-rendered HTML. If the observer never runs, the page reads normally. The old build had this exactly backwards. - Hover transitions `250ms`. ```css @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } ``` Nothing animates infinitely. Nothing autoplays. Nothing moves on page load except the reveal of the hero. --- ## Components | Component | Notes | |---|---| | `InfinityMark` | Inline SVG, `currentColor`, `aria-hidden` when decorative. Never a PNG | | `SiteHeader` | Sticky, condenses on scroll. Practice dropdown as CSS-only `
` | | `SiteFooter` | Three-column sitemap, contact block, designations, entity line | | `Eyebrow` | Mono label with optional maroon dot | | `SectionHeading` | Eyebrow + display heading + optional lede, one measure | | `Button` | Variants `primary` (maroon) · `ghost` (outlined) · `gold` (ink bg, gold-l text). Renders `` or `