# 04 · Colour

> Warm paper, one hot accent, and a strict rule about which terracotta you are
> allowed to read. Live swatches and every computed contrast ratio are in the
> [colour explorer](/color/).

---

## 4.1 The idea

Since Labs is **cream, ink and terracotta**. Not dark-mode navy, not electric
blue, not a gradient. The palette is the single fastest signal that this is not
another AI cockpit — protect it harder than anything else in this guide.

The proportions matter as much as the values. **60 / 30 / 10:**

| Share | Role | Colour |
|---|---|---|
| ~60% | The ground | Paper `#F7F5F2` |
| ~30% | Content and structure | Ink, white surfaces, hairlines |
| ~10% | Emphasis | Terracotta `#C4602A` |

If a surface is more than roughly a tenth terracotta, it has stopped meaning
anything. The accent is a scalpel.

---

## 4.2 The palette

### Ink — everything you read

| Token | Hex | Use |
|---|---|---|
| `ink` | `#1A1917` | Headlines, primary text, dark surfaces, the dark-theme ground |
| `ink-body` | `#2C2A28` | Running body copy. Softer than `ink` so long text does not vibrate |
| `ink-muted` | `#6B6762` | Labels, captions, secondary text, inactive nav |
| `on-ink` | `#F5F1EA` | Text on `ink`. **Warm off-white, never `#FFFFFF`** — pure white on a warm dark ground reads clinical and slightly blue |

### Paper — the grounds

| Token | Hex | Use |
|---|---|---|
| `paper` | `#F7F5F2` | The default page background |
| `paper-base` | `#F4F3F1` | Recessed chrome — sidebars, rails, the app frame. One step behind `paper` |

### Surfaces — things on top of paper

| Token | Hex | Use |
|---|---|---|
| `surface` | `#FFFFFF` | Cards, panels, inputs, modals |
| `surface-mist` | `#EFEFEC` | Subtle neutral fill — hover, neutral chips, icon tiles, avatar wells |
| `surface-cream` | `#F0E4DA` | Warm accent fill. The tinted sibling of mist |

### Borders

| Token | Hex | Use |
|---|---|---|
| `border` | `#E5E2DE` | Default hairline — card edges, table rules, dividers |
| `border-strong` | `#D8D5D0` | Emphasised edge — input borders, secondary buttons |

---

## 4.3 Terracotta, and the one rule that matters

This is the most important paragraph in the chapter.

> **`#C4602A` is a fill colour. `#A34A1F` is a text colour.**

`#C4602A` on white measures **4.16:1**. WCAG AA for normal text is **4.5:1**. So
the brand orange, used as a link or a button label, *fails* — by a margin small
enough that nobody notices in review and large enough to matter to a contractor
reading a phone in daylight. The fix is not to abandon the brand colour; it is to
keep a second, darker terracotta for anything with letterforms in it.

| Token | Hex | On white | Use |
|---|---|---|---|
| `accent` | `#C4602A` | 4.16:1 | **Fills only.** The `//` mark, focus rings, chart series, dots, icons, accent tiles |
| `accent-hover` | `#A84E22` | 5.56:1 | Hover for a terracotta fill |
| `accent-ink` | `#A34A1F` | **5.90:1** | **Text and links** on light surfaces, and solid buttons whose label must clear AA |
| `accent-ink-hover` | `#8B3D18` | 8.1:1 | Hover for accent text and links |
| `accent-lift` | `#E07A38` | — (5.86:1 on ink) | Terracotta **on dark surfaces**. `#C4602A` only reaches 4.23:1 there |
| `accent-soft` | `rgba(196,96,42,0.12)` | — | Translucent wash — active nav rows, attention chips |
| `accent-light` | `#F0E4DA` | — | Opaque equivalent of `accent-soft` |

Both `#A34A1F` and `#C4602A` already ship on `sincelabs.com` as `--accent-ink`
and `--accent`. This is not a new colour; it is an existing one being given its
job in writing.

### ⚠️ Conflicting values in circulation

Three different "brand oranges" exist in Since Labs documents. **`#C4602A` is
correct** — it is what both `sincelabs.com` and `sincelabs/app` actually ship.

| Value | Where it appears | Verdict |
|---|---|---|
| **`#C4602A`** | Live site CSS (`--accent`), app `globals.css` | ✅ **Canonical** |
| `#E8501A` | Notion `Agent.md` § 1 | ❌ Stale. Update `Agent.md` |
| `#C4561A` | Notion "Brand Design System Skills" | ❌ An eyeball approximation of the live value |
| `#A95022` | App dashboard charts, hard-coded | ❌ A drift. Close to `accent-ink` — use `#A34A1F` |
| `#FF5C35` | Brand Guidelines v1 § 5 | ❌ An explicit placeholder, marked TO CONFIRM. Now confirmed |

---

## 4.4 Status colours

Six states. Each is a **triple** — a background, a text colour, and a dot.

| State | Background | Text | Dot | Means |
|---|---|---|---|---|
| Success | `#EAF3DE` | `#3B6D11` | `#639922` | Active, approved, live, complete |
| Attention | `#F0E4DA` | `#9A4A1F` | `#C4602A` | Pending, waiting, needs review |
| Warning | `#FFF3E0` | `#8A4A05` | `#B25C00` | Degraded, stale, partial |
| Error | `#FCEBEB` | `#791F1F` | `#E24B4A` | Rejected, failed, disabled, suspended |
| Info | `#EBF0F7` | `#3A4A60` | `#3A4A60` | Internal, draft, informational |
| Neutral | `#EFEFEC` | `#6B6762` | `#6B6762` | Inactive, retired, archived, unknown |

Every pairing above clears AA (5.4:1 to 9.0:1).

**The dot is not decoration.** It is the redundant, non-colour channel. A status
communicated by colour alone fails WCAG 1.4.1 and fails a colour-blind operator
scanning a review queue. Ship the dot, or ship a word.

Attention deliberately shares terracotta. "Waiting on you" is the brand's most
common state, and it should feel like the brand rather than like a warning.

### On dark

Every status has a second triple. Without it the light fills survive into the
dark theme and a chip renders cream-on-cream — unreadable, and the exact bug
that shipped in the scrap-desk mockup before this table existed.

| State | Background | Text | Dot | Text on bg |
|---|---|---|---|---|
| Success | `#26321C` | `#83B456` | `#639922` | 5.6:1 |
| Attention | `#40291E` | `#EF8D5D` | `#C4602A` | 5.6:1 |
| Warning | `#3F2A1B` | `#E89150` | `#B7600B` | 5.5:1 |
| Error | `#422725` | `#F3867F` | `#E24B4A` | 5.5:1 |
| Info | `#292F36` | `#95A7BF` | `#677890` | 5.5:1 |
| Neutral | `#2F2E2C` | `#A9A5A0` | `#7A7670` | 5.5:1 |

> **A dark fill sits *above* the surface, not below it.**
> This is the one thing that is not obvious. On light, a status fill is a pale
> tint *lighter* than the page and darker than nothing. The instinct on dark is
> to make it a deep tint — but a fill darker than the `#232120` card it sits on
> is invisible: it reads as a hole, not a chip. Every fill above is a tint
> *lighter* than the card, separating at 1.18:1 — the same margin the light set
> gets against white (~1.15:1).

`scripts/check-tokens.mjs` enforces both halves: AA for the label on its own
fill, and that the fill is actually distinguishable from `dark.surface`.

---

## 4.5 Dark

The brand **inverts**; it does not switch palettes. Ink becomes the ground,
paper becomes the text, and terracotta lifts so it still clears AA.

| Token | Light | Dark |
|---|---|---|
| Ground | `#F7F5F2` | `#1A1917` |
| Surface | `#FFFFFF` | `#232120` |
| Raised surface | `#EFEFEC` | `#2C2A28` |
| Primary text | `#1A1917` | `#F5F1EA` |
| Muted text | `#6B6762` | `#98938C` |
| Border | `#E5E2DE` | `#38352F` |
| Accent | `#C4602A` | `#E07A38` |
| Accent wash | `rgba(196,96,42,.12)` | `#2E1E0F` |

**Three traps.**

1. Muted text on dark must be `#98938C` (5.8:1). The site currently also defines `#7A7570`, which is **3.9:1** — fine for a 20px label, a failure for a caption.
2. Terracotta must lift to `#E07A38` on dark. `#C4602A` drops to 4.23:1 against ink.
3. **The table above is not the whole palette.** Status and chart colours invert
   too, and they are easy to forget because nothing on a light screen shows you
   they are missing. Their dark values are in [4.4](#44-status-colours) and
   [4.6](#46-data-visualisation).

`accent wash` on dark (`#2E1E0F`) is tuned to sit on the **ground**, not on a
card — it separates from `#1A1917` at 1.10:1 but from `#232120` at only 1.00:1.
For an accent-tinted region *inside* a card, use the attention fill `#40291E`.

`sincelabs.com` ships dark. **`sincelabs/app` does not** — it is light-only, and
that is a deliberate, documented gap, not an oversight. See the [app audit](/audit/).

---

## 4.6 Data visualisation

**Sequential** — one measure across ranked categories. Darkest is largest.

`#A34A1F` → `#C4602A` → `#D27C53` → `#E9A88A` → `#F4D2C3`

**Categorical** — unordered series. Terracotta always leads: it is the subject,
the rest are context.

`#C4602A` terracotta · `#3A4A60` slate · `#639922` moss · `#7A4A5F` wine · `#2C2A28` ink

The categorical set is built to two constraints, both enforced by
`npm run check`:

1. **Every entry clears 3:1 against white**, so a thin line or a small dot is a visible graphical object (WCAG 1.4.11).
2. **Every *pair* differs by at least 1.2:1**, so the set survives greyscale — a printed report, a projector, a colour-blind reader.

That second rule is why the obvious warm choices are not in the set. `#B25C00`
amber sits at 4.75:1 and terracotta at 4.16:1 — a 1.14:1 separation, which is
two indistinguishable oranges the moment colour is removed.

### On dark

The light set cannot carry over. Slot 5 is near-black ink, which is invisible on
an ink ground — and `slate` at `#3A4A60` is a dark navy that all but disappears
on a `#232120` card.

**Sequential** — `#B0562C` → `#C4602A` → `#D27C53` → `#E9A88A` → `#F4D2C3`

**Categorical** — `#C7632D` terracotta · `#6F8199` slate · `#639922` moss · `#9D5E7A` wine · `#A6A4A2` ink

| Alias | Light | Dark |
|---|---|---|
| slate | `#3A4A60` | `#6F8199` |
| moss | `#639922` | `#639922` |
| amber | `#B25C00` | `#C0691B` |
| wine | `#7A4A5F` | `#9D5E7A` |

Slots 4 and 5 are **re-stepped, not merely lifted.** Lifting near-black ink far
enough to clear the dark surface lands it on top of wine at ΔE 7 —
indistinguishable even with full colour vision, before you get to colour
blindness. Re-stepped, the adjacent pair clears ΔE 18. Every dark slot clears
4:1 on `dark.surface`.

Rules:

- **Five series maximum.** Past five, the reader is decoding a legend instead of reading a chart. Group the tail into "Other".
- **Never encode by colour alone.** Label the series directly, or give each a shape or a pattern.
- **Never a dual axis.** Two measures on two y-scales invent a correlation the data does not contain. Two charts, small multiples, or index both to a common base.
- Gridlines are `border` `#E5E2DE` (`#38352F` on dark), never a mid grey.
- Axis labels are `ink-muted` at 11px.
- Area fills are the series colour at **12–15% opacity**, and the stroke is the full-strength colour.

---

## 4.7 Do and don't

**Do**
- ✅ Let paper dominate. Whitespace is the design.
- ✅ Use `accent-ink` for anything with letterforms.
- ✅ Pair every status colour with a dot or a word.
- ✅ Use `on-ink` `#F5F1EA` on dark grounds, not `#FFFFFF`.
- ✅ Check a new surface in **both** themes before calling it done — a light-only review cannot see a missing dark token.

**Don't**
- ❌ Use `accent` `#C4602A` as a text or link colour on light
- ❌ Introduce a colour that is not in this chapter, "just for this one screen"
- ❌ Ship a status chip or chart series without its dark value — the light fill survives into dark and renders cream-on-cream
- ❌ Reach for Tailwind's default palette — `zinc`, `red-700`, `indigo-800` are all off-brand, and all currently in the app
- ❌ Use gradients, except the one sanctioned accent-tile wash
- ❌ Use pure `#000000` or pure `#FFFFFF` as a page ground
- ❌ Communicate state by colour alone
