# Colour: agent spec

Twin of `/colour`. Source wins: `app/utils/motive-tokens.ts`, `app/utils/site-theme.ts`, `app/composables/useSiteTheme.ts`.

## Roles

| Role | Colours | Use for |
|---|---|---|
| Primary theme | Neutrals (Light, Dark) and one accent, orange (`brandAccent`, now `brandOrange`) | Motive Unknown: website, pitches, social posts, advertising. Light and Dark are also the themes of this guide |
| Black and white only | B&W | Work where colour is not possible, for example a one-colour print |
| Secondary | The 19 palettes on `/colour` | One project (release, campaign, tour). Choose the palette that agrees with the artist or the collaborator |

## Page

- `::primary-theme` (`PrimaryTheme.vue`) shows `PrimaryDemo.vue` in the site theme. Light or Dark: the key-figure card is the Accent with `onAccent` type. The vertical cards are ground with ink. The last word of the title is `typeOnLight` or `typeOnDark`. Colour theme: the key-figure card is ink with ground type.
- `::neutral-chips` (`NeutralChips.vue`) shows the seven tokens of the site theme in use. Each chip has a name and a hex value (`text-caption`).
- `::brand-ramp` (`BrandRamp.vue`) shows the four accent steps in the same chip style.
- `useRenderedTheme()` renders Light on the server and the site mode after mount, so hydration matches.

## Neutrals

The tokens come from `siteTheme()`. See Site theme.

| Token | Light | Dark | Floor on ground and grey |
|---|---|---|---|
| Ground `--ui-bg` | `#FFFFFF` | `#000000` | – |
| Grey `--grey` | `#F5F5F5` | `#121212` | – |
| Ink `--ui-primary` | `#080808` | `#F5F5F5` | – |
| Text `--ui-text` | `#3C3C3C` | `#BEBEBE` | 10:1 |
| Toned `--ui-text-toned` | `#535353` | `#9F9F9F` | 7:1 |
| Muted `--ui-text-muted` | `#707070` | `#7D7D7D` | 4.5:1 |
| Dimmed `--ui-text-dimmed` | `#8E8E8E` | `#616161` | 3:1 |

## Accents

`brandAccent` in `motive-tokens.ts` is the source. It points at `brandOrange`, hue 36. `brandPurple` (hue 296) stays in the file as the alternative. Change `brandAccent` to change the accent.

| Step | OKLCH | Hex | Use | Measured |
|---|---|---|---|---|
| Tint | `0.95 0.0128 36` | `#F7ECE9` | Pale panel under ink | Ink 17.3:1 |
| Soft | `0.78 0.0983 36` | `#EFA18C` | Panel under ink | Ink 9.7:1 |
| Accent | `0.6629 0.2272 35.97` | `#FF4800` | Fill under ink. Orange type on Dark | Ink 5.9:1. On black 6.2:1. On white 3.4:1 |
| Deep | `0.26 0.0719 36` | `#401408` | Dark panel under white type | White 15.9:1 |

- The Accent is the Orange palette primary. It has the highest chroma of the hue, at L 0.66.
- Use the Accent for one item in each view: a key figure, the primary action or a highlight.
- Do not put white type on the Accent (3.4:1).
- Orange type on Light: H3 or larger, and graphics, only. Orange type on Dark: any size.
- Do not add a brand colour. The icon of a vertical identifies the vertical (`/agent/icons.md`). Do not give a vertical a colour.

## Palettes

`palettes` in `motive-tokens.ts` keeps each value as `oklch(L C H)`. The hex values below are `toHex()` output. Use them only for decks and print specifications.

| Palette | Primary | Secondary | Ratio | Use for |
|---|---|---|---|---|
| B&W | `#F5F5F5` | `#080808` | 18.4:1 | Where colour is not possible |
| Lime | `#D5FF00` | `#6C6C6C` | 4.5:1 | Loud work |
| Purple | `#A789EE` | `#141C04` | 6.2:1 | Pop and club, fan-facing |
| Yellow | `#F1ED0E` | `#384228` | 8.5:1 | Loud work |
| Pink | `#FF00F6` | `#400036` | 5.2:1 | Pop and club, fan-facing |
| Blue | `#2370FF` | `#00022E` | 4.6:1 | Electronic, tech, data-led documents |
| Orange | `#FF4800` | `#230400` | 5.7:1 | Loud work |
| Amber | `#FFA51F` | `#101F3C` | 8.3:1 | Loud work |
| Cerise | `#FF5C9E` | `#0A2E1C` | 5.1:1 | Pop and club, fan-facing |
| Plum | `#D98FD4` | `#332A0B` | 6.0:1 | Pop and club, fan-facing |
| Rose | `#FFB3C7` | `#3A0A1B` | 10.1:1 | Pop and club, fan-facing |
| Olive | `#A6A855` | `#33102A` | 6.7:1 | Slower, earthier work |
| Green | `#2AE84F` | `#06240F` | 10.1:1 | Growth and performance stories |
| Forest | `#3E9E6B` | `#2B0E12` | 5.4:1 | Slower, earthier work |
| Mint | `#8FEFBD` | `#2F1438` | 11.9:1 | Growth and performance stories |
| Teal | `#00E0C6` | `#04302E` | 8.5:1 | Electronic, tech, data-led documents |
| Sky | `#4CC6F2` | `#3A1608` | 8.2:1 | Electronic, tech, data-led documents |
| Sand | `#E9DCC3` | `#33291B` | 10.5:1 | Quiet work |
| Ice | `#CFE6FF` | `#11243D` | 12.2:1 | Quiet work |
| Slate | `#C3CBD4` | `#1C2229` | 9.8:1 | Quiet work |

Fixed neutrals outside the system: `brandWhite` `#FFFFFF`, `brandBlack` `#000000`.

Blue’s primary and the Lime and Purple secondaries differ from the `.pen` file. `motive-tokens.ts` is authoritative.

Distinctness metric between two primaries: `hypot(ΔL, ΔC, Δh × min(C₁,C₂) × 2 / 100)`. Five existing pairs are below the 0.09 floor: Lime/Yellow 0.059, B&W/Ice 0.069, B&W/Sand 0.080, Sand/Slate 0.081, Ice/Slate 0.082. These pairs stay. Do not use them as a precedent.

Hue gap between primary and secondary: Lime, Purple, Amber, Cerise, Plum, Olive, Forest, Mint and Sky are ≥ 120°. B&W, Yellow, Pink, Blue, Orange, Rose, Green, Teal, Sand, Ice and Slate are < 120°. These pairs stay. Do not use them as a precedent.

## Layouts

`resolveLayout({ palette, layout })`:

| Layout | Surface | Ink |
|---|---|---|
| `Deep` | secondary | primary |
| `Bright` | primary | secondary |

`brand/surface` and `brand/ink` in these specs mean the `resolveLayout()` result for the active palette and layout. On the site they are `--ui-bg` and `--ui-primary`.

## Site theme

`siteTheme({ ground, ink, layout })` writes every colour token the site reads.

| Mode | Ground (`--ui-bg`) | Ink (`--ui-primary`) | Set by |
|---|---|---|---|
| Light | `#FFFFFF` | B&W secondary, `#080808` | Switch, or the B&W Bright chip |
| Dark | `#000000` | B&W primary, `#F5F5F5` | Switch, or the B&W Deep chip |
| Colour | `resolveLayout().surface` | `resolveLayout().ink` | Switch (last pick, else Lime Bright), or any other chip |

- Tokens written: `--ui-bg`, `--ui-primary`, `--ui-text-highlighted`, `--ui-text-inverted`, `--ui-bg-inverted`, `--ui-border-inverted`, `--grey`, `--ui-text`, `--ui-text-toned`, `--ui-text-muted`, `--ui-text-dimmed`.
- `--grey` feeds `--ui-bg-muted`, `--ui-bg-elevated`, `--ui-bg-accented`, `--ui-border`, `--ui-border-muted`, `--ui-border-accented` (`main.css`).
- `--grey` is the ground moved toward the ink. It keeps the hue and the saturation of the ground (chroma as a share of the sRGB maximum at that lightness and hue). `exactContrast` sets its contrast against the ground to the B&W value. Bright: 1.09:1 (`oklch(0.97 0 0)`, `#F5F5F5`, on white). Deep: 1.12:1 (`oklch(0.18 0 0)`, `#121212`, on black).
- Text tones: each tone is the ink moved toward the ground in OKLCH lightness, with hue and chroma held. Each tone moves as far as it can and still clears its floor on the ground and on `--grey` (`contrastRatio`). Floors: `--ui-text` 10, `--ui-text-toned` 7, `--ui-text-muted` 4.5, `--ui-text-dimmed` 3. Captions are in the ink (`text-highlighted`). A tone that cannot clear its floor keeps the ink.
- `--ui-text-highlighted`, `--ui-bg-inverted` and `--ui-border-inverted` are the ink. `--ui-text-inverted` is the ground.

| Token | Light | Dark | Ice Bright | Yellow Deep |
|---|---|---|---|---|
| `--grey` | `oklch(0.9700 0 0)` `#F5F5F5` | `oklch(0.1800 0 0)` `#121212` | `#BFDEFF` | `#3E492D` |
| `--ui-text` | `oklch(0.3581 0 0)` `#3C3C3C` | `oklch(0.8000 0 0)` `#BEBEBE` | `#1A2D47` | ink, `#F1ED0E` |
| `--ui-text-toned` | `oklch(0.4440 0 0)` `#535353` | `oklch(0.7009 0 0)` `#9F9F9F` | `#314561` | `#E7E300` |
| `--ui-text-muted` | `oklch(0.5468 0 0)` `#707070` | `oklch(0.5881 0 0)` `#7D7D7D` | `#4C627E` | `#BCB700` |
| `--ui-text-dimmed` | `oklch(0.6483 0 0)` `#8E8E8E` | `oklch(0.4909 0 0)` `#616161` | `#677D9C` | `#9A9400` |

- Light and Dark tokens are in a `<style>` in the head (`app.vue`): `:root{…}` and `:root.dark{…}`. Outside `.report-*` scopes, `main.css` sets no colour token except the six that read `--grey`.
- A colour theme’s ink on its `--grey` is lower than the pair’s ratio. Lime (4.04 to 4.16:1) and Blue (4.12 to 4.25:1) are below 4.5:1 on `--grey`, in both layouts.
- `PalettePair` (`::palette-pair{name}`) chips call `useSiteTheme().pick()`. The primary chip gives Bright, the secondary chip gives Deep. A cross marks the chip in use.
- `ThemeSwitch` (Light / Dark / Colour) calls `setMode('light' | 'dark' | 'colour')`. From `nav` (1124px) it is in the foot bar. Below `nav` it is at the foot of the menu.
- A colour theme persists in `localStorage`: `mu-colour-theme` (`{palette, layout, vars}`), removed on Light or Dark, and `mu-colour-last` (`{palette, layout}`), kept. An inline head script in `nuxt.config.ts` reads `mu-colour-theme` and sets its tokens as inline styles on `<html>` before paint.
- A colour theme sets `data-theme="{palette}-{layout}"` on `<html>`, for example `Ice-Bright`.
- A Deep colour theme sets colour mode to dark, so `dark:` variants follow.
- `::motif-square` paints with `var(--ui-bg)` and `var(--ui-primary)`, `::piano-roll` with `var(--grey)` and `var(--ui-primary)`. They have no palette or layout props.

## Craft: `better-colors`

- Author colour as `oklch(L C H)`. Convert only with `parseColor`, `toRgb`, `toHex`, `luminance`, `contrastRatio`, `exactContrast` in `motive-tokens.ts`.
- Hex appears only on `/colour` tiles (`toHex()`) and in `brandWhite`/`brandBlack`.
- Components and content reference semantic tokens (`--ui-primary`, `--report-ink`, `brand/surface`), never primitives. Exception: a swatch whose purpose is to show a value.
- Use a token only in its role. If a role has no token, add a token.
- Hues within 15° are the same colour. The only semantic hues are `success`, `info`, `warning`, `error`. `/report` does not use them. `report-theme.ts` `label` resolves to black or white.
- One filled control per view marks the primary action. Its peers stay neutral (`outline`, `ghost`).
- Measure contrast against the colour below the element. Resolve opacity and any band or image below it.
- When a pair fails, report the pair, the measured value and the threshold. Do not repaint a brand colour.
- Gradients: use `in oklab` by default. Use `in oklch` only when a two-hue gradient goes grey in the middle. `patternGradient` sets no keyword.
- A colour outside sRGB declares the sRGB value first, then overrides it in `@media (color-gamut: p3)`. No palette value is outside sRGB.

## Rules

1. Dress one project in one secondary palette and one layout. Never change palette per piece or rotate for variety.
2. Dress Motive Unknown's own work in the primary theme. Use B&W without the accent only where colour is not possible.
3. Never mix two palettes in one composition. Never use Deep for some elements and Bright for others on one surface.
4. Never hardcode a palette hex outside `motive-tokens.ts`. Resolve through `getPalette`/`resolveLayout`, or a prop that does (`LogoTile` `palette`/`layout`).
5. In site components, use `--ui-bg` and `--ui-primary` so the colour follows the site theme.
6. Contrast floor: WCAG 2, 4.5:1 for normal text, 3:1 for large text (≥24px) and UI components. Preferred: APCA Lc 90 for body text, Lc 60 for large text.
7. Every palette pair clears 4.5:1 between its two colours. Never measure a pair against white.
8. To fix a failing pair, change OKLCH lightness only, hold hue and chroma, and re-measure. Flag the change to a human.
9. Use `brandWhite`/`brandBlack` only where a colour must stay neutral under every palette. They do not replace B&W’s primary or secondary.
10. A new palette’s primary and secondary are ≥ 120° apart in OKLCH hue. A darker copy of the primary’s hue is not a second colour.
11. A new palette’s primary is ≥ 0.09 from every existing primary on the distinctness metric.

## Related

- `/agent/pattern.md`: generator colour inputs.
- `/agent/logo.md`: mark and wordmark ink.
- `/agent/layout.md#rounding`: controls versus content containers.
- `/agent/typography.md`: type on these colours. Never fix contrast by changing the type.
