# Canonic Associates Ltd — Design System v2

The visual language of the public website and the CMS console. Every value
below is a live CSS custom property declared in `resources/css/app.css`;
that file is the single source of truth, and this document explains it.

---

## 1. Principles

The redesign adopts an **editorial / industrial** language: oversized display
typography, dashed section labels, a bold amber field against near-black
bands, a warm cream page ground, and a pixel motif borrowed from construction
setting-out marks.

1. **Navy leads, amber signals, onyx grounds.** Deep Ink remains the brand
   voice. Signal Amber is the single call-to-action colour. Onyx supplies the
   dark bands that make both read.
2. **Flat over glossy.** Hairline borders and generous whitespace instead of
   drop shadows and large radii. Shadows appear only on hover and overlays.
3. **Warm neutrals, never grey.** Backgrounds sit on the cream axis so gold
   and amber look intentional rather than applied.
4. **Contrast is a constraint, not a review step.** Every foreground token has
   a documented ratio, and the rule is enforced by an automated test.

---

## 2. Colour palette

### 2.1 Full palette

| Colour | Token | HEX | RGB | CMYK | Role |
| --- | --- | --- | --- | --- | --- |
| Deep Ink | `--ink` | `#0D2F55` | 13, 47, 85 | 85, 45, 0, 67 | Primary brand navy: headings, primary surfaces, logo field |
| Ink Dark | `--ink-dark` | `#071B31` | 7, 27, 49 | 86, 45, 0, 81 | Deepest navy: hero imagery field |
| Ink Light | `--ink-light` | `#164273` | 22, 66, 115 | 81, 43, 0, 55 | Navy hover state |
| Heritage Gold | `--gold` | `#B38A34` | 179, 138, 52 | 0, 23, 71, 30 | Brand gold — FILL ONLY (borders, rules, decoration) |
| Gold Light | `--gold-light` | `#D4A94E` | 212, 169, 78 | 0, 20, 63, 17 | Gold on dark grounds |
| Gold Ink | `--gold-ink` | `#7F611D` | 127, 97, 29 | 0, 24, 77, 50 | Text-safe gold: eyebrows, accents on light |
| Gold Wash | `--gold-bg` | `#FBF8F1` | 251, 248, 241 | 0, 1, 4, 2 | Warm tinted surface |
| Gold Border | `--gold-border` | `#E8D7B0` | 232, 215, 176 | 0, 7, 24, 9 | Warm hairline |
| Signal Amber | `--signal` | `#F0C14B` | 240, 193, 75 | 0, 20, 69, 6 | NEW: hero panel, CTA fill, active markers |
| Signal Deep | `--signal-deep` | `#DCAB2C` | 220, 171, 44 | 0, 22, 80, 14 | NEW: amber hover/pressed |
| Signal Soft | `--signal-soft` | `#FDF3D6` | 253, 243, 214 | 0, 4, 15, 1 | NEW: quiet highlight surface, table hover |
| Onyx | `--onyx` | `#141619` | 20, 22, 25 | 20, 12, 0, 90 | NEW: dark editorial bands, footer, CMS sidebar |
| Onyx Soft | `--onyx-soft` | `#22262B` | 34, 38, 43 | 21, 12, 0, 83 | NEW: raised surface on onyx |
| Onyx Line | `--onyx-line` | `#32373D` | 50, 55, 61 | 18, 10, 0, 76 | NEW: hairline on onyx |
| Canvas Cream | `--canvas` | `#FAF7F0` | 250, 247, 240 | 0, 1, 4, 2 | NEW: page ground |
| Canvas Deep | `--canvas-deep` | `#F2EDE2` | 242, 237, 226 | 0, 2, 7, 5 | NEW: alternating band, hairlines |
| Paper | `--paper` | `#FFFFFF` | 255, 255, 255 | 0, 0, 0, 0 | Card surface |
| Text | `--text` | `#0F172A` | 15, 23, 42 | 64, 45, 0, 84 | Body copy |
| Text Light | `--text-light` | `#334155` | 51, 65, 85 | 40, 24, 0, 67 | Long-form body copy |
| Muted | `--muted` | `#5B6A7D` | 91, 106, 125 | 27, 15, 0, 51 | Secondary copy, labels |
| Border | `--border` | `#E2E8F0` | 226, 232, 240 | 6, 3, 0, 6 | Input hairline |
| Success Deep | `--success-deep` | `#0F7A5A` | 15, 122, 90 | 88, 0, 26, 52 | Status text/fill: approved, published |
| Warning Deep | `--warning-deep` | `#8A5A06` | 138, 90, 6 | 0, 35, 96, 46 | Status text: restricted |
| Danger Deep | `--danger-deep` | `#B91C1C` | 185, 28, 28 | 0, 85, 85, 27 | Status text: destructive, errors |

Legacy tokens `--mist`, `--mist-dark`, `--surface`, `--border-light`,
`--muted-light`, `--success`, `--warning`, `--danger` and their `-bg`
variants remain declared for backward compatibility. `--success`,
`--warning` and `--danger` are **fill-only**; use the `-deep` variants
for any text or icon.

CMYK values are naive uncoated conversions for reference only. Commission a
measured profile before print production.

### 2.2 What changed, and why

| Token | Before | After | Reason |
| --- | --- | --- | --- |
| `--muted` | `#64748B` | `#5B6A7D` | `#64748B` measures 4.49:1 on Canvas Cream — one hundredth under AA. |
| `--gold-ink` | *(did not exist)* | `#7F611D` | Heritage Gold is 3.18:1 on white and fails AA as text. Gold Ink carries the same hue at 5.79:1. |
| `--success-deep` / `--warning-deep` / `--danger-deep` | *(did not exist)* | see table | The base status colours were being used as label text at 2.4–3.8:1 throughout the CMS. |

No brand colour was removed. Deep Ink, Ink Dark, Ink Light, Heritage Gold,
Gold Light, Gold Wash and Gold Border all retain their exact original values.

### 2.3 Verified contrast ratios

| Pairing | Ratio |
| --- | --- |
| --gold-ink on Paper | 5.79:1 |
| --gold-ink on Canvas Cream | 5.41:1 |
| --gold-ink on Canvas Deep | 4.96:1 |
| --muted on Paper | 5.52:1 |
| --muted on Canvas Deep | 4.73:1 |
| --text on Canvas Cream | 16.69:1 |
| --ink on Canvas Cream | 12.62:1 |
| Onyx on Signal Amber | 10.73:1 |
| White on Onyx | 18.13:1 |
| Signal Amber on Onyx | 10.73:1 |
| Gold Light on Onyx | 8.27:1 |
| White on Deep Ink | 13.51:1 |
| White on Success Deep | 5.31:1 |
| Warning Deep on Paper | 5.92:1 |
| Danger Deep on Paper | 6.47:1 |
| #A9A5A0 on Onyx (footer body) | 7.4:1 |
| #9A9591 on Onyx (service index) | 6.11:1 |
| Heritage Gold on Paper (REJECTED for text) | 3.18:1 |

**Rules**
- Normal text (< 24px, or < 18.66px at weight 700) requires **4.5:1**.
- Large text (>= 24px, or >= 18.66px at weight 700+) requires **3:1**.
- Never set text in `--gold`, `--success`, `--warning` or `--danger`.
  Use `--gold-ink` and the `-deep` variants.
- Never set text in `--signal` on a light ground. Signal Amber is a *fill*;
  put `--onyx` on top of it (10.73:1).

---

## 3. Typography

| Role | Token | Family | Size | Tracking |
| --- | --- | --- | --- | --- |
| Display (`.display-title`) | `--font-display` | Space Grotesk 700 | `clamp(2.6rem, 6vw, 4.6rem)` | `-0.035em` |
| Section (`.section-title`) | `--font-display` | Space Grotesk 700 | `clamp(1.9rem, 3.8vw, 3rem)` | `-0.03em` |
| Eyebrow (`.eyebrow`) | `--font-sans` | Plus Jakarta Sans 800 | 0.74rem uppercase | `0.16em` |
| Lead (`.lead`) | `--font-sans` | Plus Jakarta Sans 400 | `clamp(1rem, 1.6vw, 1.15rem)` | — |
| Body | `--font-sans` | Plus Jakarta Sans 400 | 1rem / 1.65 | — |

`.eyebrow` renders dashed rules either side of the label via `::before` /
`::after`, matching the `- - SECTION - -` device in the reference concepts.
It inherits `currentColor`, so it works on light and dark grounds unchanged.

---

## 4. Structure

| Token | Value | Use |
| --- | --- | --- |
| `--radius-sm` | 4px | Inputs, badges, logo mark |
| `--radius` | 6px | Cards, panels |
| `--radius-lg` | 10px | Large surfaces |
| `--radius-full` | 9999px | Buttons, pills, circular controls |
| `--max` | 1280px | `.container` |
| `--shadow-sm/md/lg` | see source | Rest / hover / overlay only |

Section rhythm is `clamp(56px, 8vw, 104px)`. Bands alternate
Canvas Cream -> Canvas Deep (`.section-mist`) -> Onyx (`.section-ink`).

---

## 5. Components

### Buttons
| Class | Fill | Label | Use |
| --- | --- | --- | --- |
| `.btn-primary` | Onyx | White | Default action |
| `.btn-gold` | Signal Amber | Onyx | Single primary CTA per view |
| `.btn-secondary` | Transparent + navy hairline | Deep Ink | Secondary action |
| `.btn-outline-white` | Transparent + white hairline | White | Actions on dark bands |
| `.btn-circle` | Transparent, 46px | Deep Ink | Carousel / arrow controls |

Sizes: `.btn-sm`, default, `.btn-lg`.

### Signature patterns
| Class | Description |
| --- | --- |
| `.hero-panel` + `.hero-aside` | Split hero: amber copy panel against a full-bleed image field |
| `.stat-row` / `.stat-cell` | Statistics divided by vertical hairlines, stacking below 640px |
| `.service-index` / `.service-index-row` | Numbered editorial index on Onyx; rows brighten and indent on hover |
| `.checker` | 3x3 pixel motif; `<Checker />` in `Components/Blocks.jsx`. Decorative, `aria-hidden`, hidden below 768px |
| `.arrow-badge` | Circular arrow on card corners; rotates 45deg and fills amber on hover |
| `.footer-wordmark` | Oversized outlined wordmark across the footer base |
| `.link-arrow` | Inline read-more affordance; arrow slides on parent hover |

### Layout utilities
| Class | Columns | Collapses |
| --- | --- | --- |
| `.split` | 1.4fr / 0.6fr | 1fr below 900px |
| `.split-wide` | 1.3fr / 0.7fr | 1fr below 900px |
| `.split-dash` | 1.2fr / 0.8fr | 1fr below 900px |
| `.split-even` | 1fr / 1fr | 1fr below 600px |
| `.grid-2/3/4` | 2 / 3 / 4 | 2 below 1024px, 1 below 768px |

`.split > *` and `.grid > *` carry `min-width: 0`. Do not remove it —
grid items default to `min-width: auto`, which lets a wide descendant push a
track past the viewport.

**Never hard-code `gridTemplateColumns` inline.** Fixed inline templates do
not respond to breakpoints and were the cause of the contact page overflowing
on mobile. Use the utilities above.

---

## 6. CMS console

The admin shares the same tokens: Onyx sidebar, Canvas Cream body, white data
cards, Signal Amber for the active nav marker, badge counts and table hover.
Status chips use the `-deep` colour family so every label clears AA.

---

## 7. Accessibility

- **Target:** WCAG 2.1 AA — 4.5:1 normal text, 3:1 large text.
- **Enforcement:** `tests/e2e/contrast.spec.ts` walks the rendered DOM of 11
  public pages and 18 CMS pages, composites each element against its real
  backdrop and fails on any shortfall. It runs in every browser project.
- **Layout:** `tests/e2e/responsive.spec.ts` asserts no element overflows the
  viewport and the document never scrolls horizontally.
- Focus rings are 3px `--gold-ink`, switching to Signal Amber on dark grounds.
- All decorative elements (`.checker`, `.footer-wordmark`, hero imagery) are
  `aria-hidden` and convey no information.
- `prefers-reduced-motion` disables all transitions and animation.

Run the audits:

```bash
npx playwright test contrast responsive
```

---

## 8. Browser and viewport support

| Project | Engine | Viewport |
| --- | --- | --- |
| `chromium` | Chrome | 1280x720 |
| `firefox` | Gecko | 1280x720 |
| `webkit` | Safari | 1280x720 |
| `edge` | Chrome (Edge channel) | 1280x720 — runs only where Edge is installed |
| `tablet-safari` | WebKit | iPad 810x1080 |
| `mobile-chrome` | Chrome | Pixel 7 412x915 |

Breakpoints: 1024px (nav -> drawer, 4-up -> 2-up), 900px (splits and hero
collapse), 768px (grids -> single column, motif hidden), 640px (stat rows
stack, header CTA hidden).
# Shared subheaders

All public inner pages use the shared `Subheader` component so spacing, type hierarchy, imagery, responsive behaviour, and accessibility remain consistent (REQ-005, REQ-007, REQ-028). Subheader imagery is managed under **Admin → Global Corporate Settings → Subheader Images**. A route-specific image overrides the default fallback; each meaningful image requires an accompanying description. Detail pages may add resource metadata while retaining the shared frame.
