Skip to content

Warm Meridian — Implementation Spec

Updated: 31/03/2026

Date: 6 March 2026 (original), 31 March 2026 (contrast audit — border/input darkened) Status: All phases complete — migration finished, zero raw Tailwind colours remain Source: docs/design/warm-meridian-philosophy.md, docs/design/warm-meridian-identity.pdf


All five phases of the Warm Meridian design system migration are complete. The codebase is fully semantic-token-driven with zero raw Tailwind colour classes in components, no HSL fallbacks, and automatic light/dark mode via CSS custom properties.

PhaseScopeStatus
1Token swap (globals.css)Complete
2Font swap (Instrument Sans)Complete
3Status component migrationComplete
4Domain colour recalibrationComplete
5Cleanup (HSL fallbacks, dead deps)Complete

Primitive tokens (Stone 50-900)
|
v
Semantic tokens (:root for light, .dark for dark)
|
v
@theme inline (registers as Tailwind v4 utilities)
|
v
Components use `text-freshness-fresh`, `bg-bid-active-bg`, etc.

All tokens are defined in app/globals.css. The @theme inline block registers semantic tokens as first-class Tailwind utilities, enabling IDE autocomplete and avoiding arbitrary value syntax.

  • Primary: Instrument Sans (loaded via next/font/google in app/layout.tsx)
  • CSS variable: --font-sans: 'Instrument Sans', system-ui, -apple-system, sans-serif
  • Accessibility override: Dyslexia-friendly mode swaps to Atkinson Hyperlegible via a11y.css

Warm stone neutrals replace the original cool slate (hue 250). All values use OKLCH colour space.

TokenValuePurpose
--stone-50oklch(0.98 0.008 48)Lightest background
--stone-100oklch(0.95 0.010 48)Secondary/muted surfaces
--stone-200oklch(0.90 0.012 48)Borders, input borders
--stone-300oklch(0.82 0.014 48)Subtle dividers
--stone-400oklch(0.65 0.012 48)Disabled state
--stone-500oklch(0.55 0.014 48)Muted text
--stone-600oklch(0.45 0.016 48)Secondary text
--stone-700oklch(0.40 0.016 48)Strong secondary text
--stone-800oklch(0.25 0.016 48)Primary text (light mode)
--stone-900oklch(0.18 0.014 48)Page background (dark mode)

TokenValueNotes
--backgroundoklch(0.98 0.008 48)Stone 50
--foregroundoklch(0.25 0.016 48)Stone 800
--cardoklch(0.99 0.005 48)Slightly warmer than bg
--card-foregroundoklch(0.25 0.016 48)Stone 800
--popoveroklch(0.99 0.005 48)Matches card
--popover-foregroundoklch(0.25 0.016 48)Stone 800
--primaryoklch(0.65 0.16 55)Amber 400 — signal colour
--primary-foregroundoklch(0.99 0.003 48)White/near-white
--secondaryoklch(0.95 0.010 48)Stone 100
--secondary-foregroundoklch(0.25 0.016 48)Stone 800
--mutedoklch(0.95 0.010 48)Stone 100
--muted-foregroundoklch(0.55 0.014 48)Stone 500
--accentoklch(0.95 0.010 48)Stone 100
--accent-foregroundoklch(0.25 0.016 48)Stone 800
--destructiveoklch(0.55 0.20 20)Rose 400
--destructive-foregroundoklch(0.99 0.003 48)White
--borderoklch(0.86 0.012 48)Between Stone 200 (0.90) and Stone 300 (0.82) — darkened for contrast
--inputoklch(0.86 0.012 48)Between Stone 200 (0.90) and Stone 300 (0.82) — darkened for contrast
--ringoklch(0.65 0.16 55)Amber 400 (matches primary)
--successoklch(0.55 0.15 160)Teal 400
TokenValueNotes
--backgroundoklch(0.18 0.014 48)Stone 900
--foregroundoklch(0.95 0.010 48)Stone 100
--cardoklch(0.22 0.014 48)Elevated surface
--primaryoklch(0.72 0.14 55)Lighter amber for dark bg
--primary-foregroundoklch(0.18 0.014 48)Stone 900
--secondaryoklch(0.25 0.016 48)Stone 800
--mutedoklch(0.25 0.016 48)Stone 800
--muted-foregroundoklch(0.65 0.012 48)Stone 400
--borderoklch(0.30 0.014 48)Lighter than bg
--ringoklch(0.72 0.14 55)Lighter amber
--successoklch(0.45 0.12 160)Teal (reduced lightness)

All status tokens are defined in both :root (light) and .dark blocks, and registered in @theme inline as Tailwind utilities.

TokenLightDarkUse
--color-freshness-freshoklch(0.55 0.15 160)oklch(0.70 0.13 160)Text/icon
--color-freshness-fresh-bgoklch(0.93 0.04 160)oklch(0.25 0.06 160)Badge bg
--color-freshness-agingoklch(0.60 0.14 70)oklch(0.72 0.12 70)Text/icon
--color-freshness-aging-bgoklch(0.97 0.02 70)oklch(0.25 0.04 70)Badge bg
--color-freshness-staleoklch(0.55 0.20 20)oklch(0.70 0.17 20)Text/icon
--color-freshness-stale-bgoklch(0.95 0.03 20)oklch(0.25 0.05 20)Badge bg
--color-freshness-expiredoklch(0.50 0.20 20)oklch(0.65 0.17 20)Text/icon
--color-freshness-expired-bgoklch(0.95 0.03 20)oklch(0.25 0.05 20)Badge bg
TokenLightDarkUse
--color-confidence-strongoklch(0.55 0.15 160)oklch(0.70 0.13 160)Teal
--color-confidence-strong-bgoklch(0.93 0.04 160)oklch(0.25 0.06 160)
--color-confidence-strong-borderoklch(0.80 0.06 160)oklch(0.35 0.08 160)
--color-confidence-partialoklch(0.60 0.14 70)oklch(0.72 0.12 70)Sand
--color-confidence-partial-bgoklch(0.97 0.02 70)oklch(0.25 0.04 70)
--color-confidence-partial-borderoklch(0.82 0.06 70)oklch(0.35 0.06 70)
--color-confidence-needs-smeoklch(0.55 0.12 240)oklch(0.70 0.10 240)Blue
--color-confidence-needs-sme-bgoklch(0.95 0.02 240)oklch(0.25 0.04 240)
--color-confidence-needs-sme-borderoklch(0.80 0.06 240)oklch(0.35 0.06 240)
--color-confidence-noneoklch(0.55 0.014 48)oklch(0.65 0.012 48)Stone (neutral)
--color-confidence-none-bgoklch(0.95 0.010 48)oklch(0.25 0.016 48)
--color-confidence-none-borderoklch(0.82 0.014 48)oklch(0.35 0.014 48)

10 states, each with text, bg, border, and (where applicable) dot tokens.

StateHueLight textLight bg
Draft48 (stone)oklch(0.55 0.014 48)oklch(0.95 0.010 48)
Discovery240 (blue)oklch(0.55 0.12 240)oklch(0.95 0.02 240)
Active70 (sand)oklch(0.60 0.14 70)oklch(0.97 0.02 70)
In Review290 (purple)oklch(0.55 0.12 290)oklch(0.95 0.02 290)
Export Ready195 (cyan)oklch(0.55 0.15 195)oklch(0.93 0.04 195)
Submitted160 (teal)oklch(0.50 0.15 160)oklch(0.93 0.04 160)
Won160 (teal)oklch(0.50 0.15 160)oklch(0.93 0.04 160)
Lost20 (rose)oklch(0.55 0.20 20)oklch(0.95 0.03 20)
Withdrawn48 (stone)oklch(0.55 0.014 48)oklch(0.95 0.010 48)
Overdue20 (rose)oklch(0.50 0.20 20)oklch(0.95 0.03 20)

Full token names follow the pattern: --color-bid-{state}, --color-bid-{state}-bg, --color-bid-{state}-border, --color-bid-{state}-dot.

StateHueLight textLight bg
Pending70 (sand)oklch(0.60 0.14 70)oklch(0.97 0.02 70)
Approved160 (teal)oklch(0.55 0.15 160)oklch(0.93 0.04 160)
Changes45 (amber)oklch(0.60 0.16 45)oklch(0.97 0.02 45)
Reverted20 (rose)oklch(0.55 0.20 20)oklch(0.95 0.03 20)
Draft48 (stone)oklch(0.55 0.014 48)oklch(0.95 0.010 48)

Full token names: --color-governance-{state}, -bg, -border.

TokenLightUse
--color-phase-analysingoklch(0.55 0.12 240)Blue
--color-phase-draftingoklch(0.55 0.15 290)Purple
--color-phase-qualityoklch(0.60 0.14 70)Sand
--color-phase-savingoklch(0.55 0.12 240)Blue
--color-phase-doneoklch(0.55 0.15 160)Teal
--color-phase-done-bgoklch(0.93 0.04 160)
--color-phase-done-borderoklch(0.80 0.06 160)
--color-phase-active-bgoklch(0.95 0.02 240)Active phase container
--color-phase-active-borderoklch(0.80 0.06 240)
StateHueToken prefix
Unreviewed240 (blue)--color-template-unreviewed
Confirmed160 (teal)--color-template-confirmed
Rejected48 (stone)--color-template-rejected
Manual70 (sand)--color-template-manual
Unmapped20 (rose)--color-template-unmapped

Each has -bg variant for background.

TokenLightUse
--color-quality-goodoklch(0.55 0.15 160)Good score (teal)
--color-quality-good-bgoklch(0.93 0.04 160)
--color-quality-moderateoklch(0.60 0.14 70)Moderate score (sand)
--color-quality-moderate-bgoklch(0.97 0.02 70)
--color-quality-severity-warningoklch(0.60 0.14 70)Warning indicator
--color-quality-severity-infooklch(0.55 0.12 240)Info indicator
TokenLightUse
--color-tag-rareoklch(0.60 0.014 48)Rare tag (stone)
--color-tag-rare-bgoklch(0.95 0.010 48)
--color-tag-coreoklch(0.55 0.15 160)Core tag (teal)
--color-tag-core-bgoklch(0.93 0.04 160)
TokenLightUse
--color-verifiedoklch(0.55 0.15 160)Teal
--color-verified-bgoklch(0.93 0.04 160)

10 entity types, each with -bg and -text tokens.

Entity TypeHueLight bgLight text
Organisation240oklch(0.93 0.04 240)oklch(0.35 0.12 240)
Certification160oklch(0.93 0.04 160)oklch(0.35 0.12 160)
Regulation20oklch(0.93 0.04 20)oklch(0.35 0.15 20)
Framework300oklch(0.93 0.04 300)oklch(0.35 0.12 300)
Capability70oklch(0.93 0.04 70)oklch(0.40 0.12 70)
Person350oklch(0.93 0.04 350)oklch(0.38 0.12 350)
Technology195oklch(0.93 0.04 195)oklch(0.35 0.12 195)
Project275oklch(0.93 0.04 275)oklch(0.35 0.12 275)
Sector55oklch(0.93 0.04 55)oklch(0.40 0.12 55)
Product170oklch(0.93 0.04 170)oklch(0.35 0.12 170)

Token names: --color-entity-{type}-bg, --color-entity-{type}-text.

TokenLightUse
--color-draft-badge-bgoklch(0.93 0.04 70)Draft content badge
--color-draft-badge-textoklch(0.50 0.14 70)
--color-draft-badge-borderoklch(0.82 0.06 70)
--color-action-publishoklch(0.55 0.14 55)Publish button
--color-action-publish-hoveroklch(0.50 0.15 55)

TokenLightDarkUse
--color-progressoklch(0.65 0.16 55)oklch(0.72 0.14 55)Progress indicator (amber)
--color-successoklch(0.55 0.15 160)oklch(0.45 0.12 160)General success
--color-progress-baroklch(0.65 0.12 70)oklch(0.55 0.10 70)Progress fill (warm sand)
--color-progress-midoklch(0.58 0.10 65)oklch(0.45 0.08 65)Mid-progress transition
--color-progress-completeoklch(0.55 0.15 160)oklch(0.45 0.12 160)Complete (teal)
--color-review-skipoklch(0.7 0.08 15)oklch(0.35 0.08 15)Skip swipe direction
--color-review-readoklch(0.55 0.15 160)oklch(0.35 0.12 160)Read swipe direction
--color-review-staroklch(0.7 0.15 85)oklch(0.4 0.12 85)Star rating
TokenLightDarkUse
--color-highlight-bgoklch(0.96 0.03 55)oklch(0.22 0.03 55)Highlighted card bg
--color-highlight-borderoklch(0.85 0.06 55)oklch(0.3 0.05 55)
--color-highlight-markoklch(0.925 0.12 55)oklch(0.35 0.08 55)Search highlight mark
--color-staroklch(0.795 0.184 86)oklch(0.855 0.15 86)Star icon
--color-priority-highoklch(0.637 0.237 25)oklch(0.704 0.191 22)High priority
--color-priority-mediumoklch(0.769 0.188 70)oklch(0.82 0.15 70)Medium priority
--color-priority-lowoklch(0.723 0.191 261)oklch(0.78 0.15 261)Low priority
--color-status-successoklch(0.65 0.2 160)oklch(0.72 0.17 160)
--color-status-warningoklch(0.75 0.18 70)oklch(0.82 0.15 70)
--color-status-erroroklch(0.63 0.24 25)oklch(0.7 0.19 22)
--color-status-infooklch(0.65 0.16 55)oklch(0.72 0.14 55)
TokenLightDark
--shadow-review-bar0 -2px 10px oklch(0.18 0.014 48 / 0.08)0 -2px 10px oklch(0.10 0.014 48 / 0.15)
TokenDefaultPurpose
--a11y-letter-spacingnormalAdjusted in high-contrast/dyslexia modes
--a11y-word-spacingnormal
--a11y-line-height1.5
--a11y-font-weight400
--a11y-paragraph-spacing1em
--a11y-max-widthnoneConstrains line length for readability
--a11y-font-size-scale1Multiplier for large text mode

7 domains, each with -bg, -text, and -surface tokens for light and dark.

DomainHueLight bgLight textNotes
Security220oklch(0.93 0.04 220)oklch(0.35 0.12 220)Blue — universally security
Compliance275oklch(0.93 0.04 275)oklch(0.35 0.12 275)Purple
Implementation155oklch(0.93 0.04 155)oklch(0.35 0.12 155)Green
Support85oklch(0.93 0.04 85)oklch(0.4 0.12 85)Yellow-green (shifted from 45 to avoid amber overlap)
Corporate200oklch(0.93 0.02 200)oklch(0.4 0.08 200)Neutral blue-grey
Product145oklch(0.93 0.04 145)oklch(0.35 0.12 145)Teal-green
Methodology340oklch(0.93 0.04 340)oklch(0.38 0.12 340)Pink/magenta

Surface variants (for home page cards) use lightness 0.96 and chroma 0.02 (light) / lightness 0.2 and chroma 0.03 (dark).


TokenLightDark
--chart-1oklch(0.65 0.16 55)oklch(0.72 0.14 55)
--chart-2oklch(0.55 0.20 20)oklch(0.65 0.15 20)
--chart-3oklch(0.55 0.15 160)oklch(0.55 0.12 160)
--chart-4oklch(0.60 0.14 70)oklch(0.65 0.12 70)
--chart-5oklch(0.55 0.12 275)oklch(0.65 0.12 275)

12 preset colours harmonised with the Warm Meridian palette, grouped into 3 rows:

WarmOrganicNeutral
Amber (#d4880f)Teal (#2b9e82)Slate (#7a756c)
Copper (#c27840)Sage (#5a8a6c)Charcoal (#5c5750)
Terracotta (#c4604a)Ocean (#3b82b6)Stone (#a8a49c)
Rose (#c25670)Indigo (#5b6abf)
Plum (#8b5cc0)

Located in components/workspace-colour-picker.tsx. Custom hex input also supported.


Defined in the @theme inline block:

TokenValue
--radius0.5rem
--radius-smcalc(var(--radius) - 4px)
--radius-mdcalc(var(--radius) - 2px)
--radius-lgvar(--radius)
--radius-xlcalc(var(--radius) + 4px)
--spacing-10.25rem (4px grid)
--spacing-20.5rem
--spacing-30.75rem
--spacing-41rem
--spacing-61.5rem
--spacing-82rem
--spacing-123rem
--spacing-164rem

Current token lightness values (OKLCH L channel):

TokenLightnessPurpose
white1.0Header, search inputs, elevated interactive elements
--card / --popover1.0Cards, popovers, active toggle states
--accent / --muted0.95Hover states, wells, subtle backgrounds
--background0.94Page background
--border / --input0.78Borders, input outlines
  • Minimum 0.10 OKLCH lightness delta between adjacent surfaces (e.g. border on background, card on page)
  • Use --border token for all component edges (inputs, selects, cards, toggle groups)
  • Never use opacity modifiers on borders that reduce visibility (e.g. border-border/60)

All page-level search inputs use bg-white shadow-sm to lift them clearly above the 0.94 page background. This applies to browse, guides, Q&A library, entities, and tags search bars. Search inputs inside overlays (sheets, popovers, drawers) do not need this treatment as they sit on elevated surfaces already.

The site header uses bg-white (not bg-background) for clear separation from the page background (0.94). It includes a warm shadow (shadow-[0_1px_3px_oklch(0.18_0.014_48/0.06)]) and border-b border-border. Active nav tabs use font-semibold text-foreground; inactive tabs use text-muted-foreground.

Tailwind v4 removed the default border-color: var(--border) preflight. Bare border now uses currentColor. The global rule in globals.css (*, ::after, ::before { border-color: var(--border) }) restores the expected behaviour. Settings cards intentionally rely on currentColor for dark borders, so this rule must remain but should not be extended to override per-component border colours.

SelectTrigger uses bg-card (not bg-transparent) for a warm near-white fill against page backgrounds. The dark:bg-input/30 override still applies for dark mode.

Segmented toggles (e.g. coverage Cards/Heatmap) use a well pattern: container has bg-muted/50, active button has bg-card shadow-sm (raised from the well), inactive button has hover:bg-card/50.


Typography plugin colours inherit from semantic tokens for automatic dark mode:

.prose {
--tw-prose-body: var(--foreground);
--tw-prose-headings: var(--foreground);
--tw-prose-links: var(--primary);
--tw-prose-bold: var(--foreground);
--tw-prose-counters: var(--muted-foreground);
--tw-prose-bullets: var(--muted-foreground);
--tw-prose-hr: var(--border);
--tw-prose-quotes: var(--foreground);
--tw-prose-quote-borders: var(--border);
--tw-prose-captions: var(--muted-foreground);
--tw-prose-code: var(--foreground);
--tw-prose-pre-code: var(--foreground);
--tw-prose-pre-bg: var(--muted);
--tw-prose-th-borders: var(--border);
--tw-prose-td-borders: var(--border);
}

All migrations complete. Verification results:

  • Zero raw Tailwind colour classes (text-green-*, bg-amber-*, etc.) in component files
  • Zero HSL fallbacks in component files
  • tailwindcss-animate dependency removed
  • All 23 shadcn/ui components fully semantic (no changes needed)
  • components/domain-badge.tsx — uses var(--domain-{name}-bg/text/surface)
  • components/verification-badge.tsx — uses var(--color-status-success)
  • Workspace colour picker — 12 hex presets recalibrated for warm palette
  • Review action bar shadow — tokenised as --shadow-review-bar
  • Font — Instrument Sans via next/font/google
CategoryTokens (light + dark)
Primitive (Stone)10
Core semantic (shadcn)~30 per mode
Freshness8 per mode
Confidence12 per mode
Bid status~40 per mode
Governance15 per mode
Streaming phases9 per mode
Template field review10 per mode
Quality score6 per mode
Tag frequency4 per mode
Entity type badges20 per mode
Domain colours21 per mode
Gap summary banner7 per mode
Draft/publish actions5 per mode
Verified2 per mode
Progress/review8 per mode
Highlight/status/priority~12 per mode
Accessibility7 (mode-independent)
Total~230 light + ~200 dark

Implementation spec produced: 6 March 2026 Token reference updated: 23 March 2026 — all phases complete, 0 raw colours remaining