@motir/design-system 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/theme.css CHANGED
@@ -290,7 +290,24 @@
290
290
  (<html>) is dark — the symmetric counterpart to [data-theme='dark'], mirroring
291
291
  how [data-palette]/[data-style]/[data-type] already re-skin a subtree. The
292
292
  onboarding design step (MOTIR-1040) uses it to preview the user's project in
293
- light regardless of Motir's own theme. Values mirror the @theme light base. */
293
+ light regardless of Motir's own theme. Values mirror the @theme light base.
294
+
295
+ ⚠️ On a NESTED element this block is only HALF the mechanism (MOTIR-2077).
296
+ `--el-*` are declared `--el-x: var(--color-y)` and var() substitutes where the
297
+ property is DECLARED, so re-asserting Tier-0 here cannot retroactively change
298
+ the `--el-*` a component reads — those resolved at :root and merely inherit
299
+ down. Pair this with `data-appearance-scope` on the same element (below), which
300
+ re-emits the Tier-3 layer locally so its var()s see these overrides. That
301
+ attribute is axis-agnostic — a nested [data-palette]/[data-style] needs it too
302
+ — which is why light has no duplicated `--el-*` companion block the way dark
303
+ does; dark's exists only for its two concrete-hex tokens, not to scope a
304
+ subtree.
305
+
306
+ ⚠️ KEEP AT PARITY with [data-theme='dark'] above. A Tier-0 token dark declares
307
+ and this block omits keeps its DARK value in a forced-light subtree no matter
308
+ what scopes it: with nothing local to shadow it, the inherited value stands.
309
+ That was MOTIR-2077's defect (`--color-canvas`, added below).
310
+ tests/theme/forcedThemeSubtree.test.ts asserts the parity both ways. */
294
311
  [data-theme='light'] {
295
312
  color-scheme: light;
296
313
 
@@ -298,6 +315,7 @@
298
315
  --color-foreground: #1a1a1a;
299
316
  --color-surface: #f6f5f4;
300
317
  --color-surface-soft: #fafaf9;
318
+ --color-canvas: #ecebe7; /* recessed board behind raised cards (planning canvas) */
301
319
 
302
320
  --color-ink: #1a1a1a;
303
321
  --color-charcoal: #37352f;
@@ -1213,15 +1231,22 @@
1213
1231
  inset 0 1px 0 0 rgba(255, 255, 255, 0.55),
1214
1232
  0 1px 3px rgba(15, 15, 15, 0.08),
1215
1233
  0 8px 18px -8px rgba(15, 15, 15, 0.22);
1216
- /* The column is `h-[calc(100dvh-12rem)]` — sized to fill the board area. In
1217
- the `overflow-x-auto` row that exactly-filling height means the column's
1218
- rounded BOTTOM corner + its drop shadow sit at the overflow-clip boundary
1219
- and get sheared flat (the reported "sharp bottom corner"). Trim the height
1220
- so the rounded corner + shadow clear the clip and the column reads as a
1221
- fully-rounded floating card. Also clip the column's OWN children to its
1222
- radius so a flush inner surface can't square a corner the board cards pop
1223
- on hover but the DragOverlay clone is portaled, so nothing 3D is lost. */
1224
- height: calc(100dvh - 14.5rem);
1234
+ /* The column is `h-[calc(100dvh - 10.5rem - var(--shell-bottom-clearance))]` —
1235
+ sized to fill the board area. In the `overflow-x-auto` row that
1236
+ exactly-filling height means the column's rounded BOTTOM corner + its drop
1237
+ shadow sit at the overflow-clip boundary and get sheared flat (the reported
1238
+ "sharp bottom corner"). Trim the height by a further 2.5rem so the rounded
1239
+ corner + shadow clear the clip and the column reads as a fully-rounded
1240
+ floating card. Also clip the column's OWN children to its radius so a flush
1241
+ inner surface can't square a corner the board cards pop on hover but the
1242
+ DragOverlay clone is portaled, so nothing 3D is lost.
1243
+
1244
+ ⚠️ This tracks BoardColumn's height and must keep spending the same
1245
+ `--shell-bottom-clearance` the shell sets (MOTIR-2763), not a flat number:
1246
+ a stylesheet cannot read the `showPlanWithAi` prop, so the custom property
1247
+ is the only way this stays conditional on the orb actually mounting. The
1248
+ 13rem below is 10.5rem of chrome + this style's own 2.5rem trim. */
1249
+ height: calc(100dvh - 13rem - var(--shell-bottom-clearance, 1.5rem));
1225
1250
  overflow: hidden;
1226
1251
  }
1227
1252
  [data-style='3d-immersive'] [data-board-scroll] {
@@ -1230,26 +1255,84 @@
1230
1255
  padding-top: 0.25rem;
1231
1256
  }
1232
1257
 
1233
- /* ── 3D / Immersive — PHYSICAL BUTTONS ──────────────────────────────────────
1258
+ /* ── 3D / Immersive — CONTROL DEPTH: every control sits on a named plane ────
1234
1259
  In a 3D design a button is not flat: it has visible THICKNESS (a solid base
1235
1260
  edge) and presses DOWN onto it on click (translateY + the base compresses) —
1236
- the standard 3D/skeuomorphic button. This must cover EVERY button, not just
1237
- the <Button> primitive: the ~35 hand-rolled `rounded-(--radius-btn)` <button>s
1238
- (the /issues toolbar triggers — Filter / Advanced / Saved / the Tree-List
1239
- switcher links-styled-as-buttons, etc.) were flat. So the GLOBAL rule on the
1240
- button-radius class is the baseline (a neutral `--el-border-strong` edge, which
1241
- suits the outline/toolbar buttons); FILLED variants override it with a
1242
- colour-matched edge (primary `--el-accent-pressed`, danger a darkened
1243
- `--el-danger` palette-derived, so the axes stay disjoint); ghost stays flat
1244
- (the quiet button). box-shadow is not a colour-painting property, so the
1245
- material-rule test skips these. The press overrides the base `active:scale`
1246
- with a more physical translateY. */
1247
- /* `button.rounded-(--radius-input)` catches DROPDOWN-TRIGGER buttons styled as an
1248
- input (the board switcher, combobox / picker triggers) so they're physical
1249
- keys too scoped to `button` so a real <input>/<textarea> text field (same
1250
- radius) is NEVER raised. */
1251
- [data-style='3d-immersive'] .rounded-\(--radius-btn\),
1252
- [data-style='3d-immersive'] button.rounded-\(--radius-input\) {
1261
+ the standard 3D/skeuomorphic button. §4's plane ladder in
1262
+ docs/styles/3d-immersive.md is the spec; this block is its implementation.
1263
+
1264
+ ⚠️ THE ROLE DECIDES THE PLANE, AND A RADIUS UTILITY DOES NOT CARRY THE ROLE
1265
+ (MOTIR-3522). This rule used to match `.rounded-(--radius-btn)` alone, on the
1266
+ reasoning that the button radius identifies a button. It does not: the app
1267
+ emits SEVEN radius utilities on interactive tags, and each of the other six
1268
+ mixes families that want opposite treatments
1269
+
1270
+ `--radius-badge` is worn by 3 hero action buttons (Plan with AI, Re-plan,
1271
+ the build-in-public link) AND by 9 filter/tag chips and segmented tabs,
1272
+ which §4 says stay flat pills;
1273
+ `--radius-control` is worn by ~63 square icon buttons (real keys) AND by
1274
+ ~51 menu / option / full-width list rows AND by inline text
1275
+ affordances that only ever wanted a focus ring;
1276
+ `rounded-full` is worn by the Plan-with-AI orb (a key) AND by two Switch
1277
+ tracks, a colour swatch, an avatar and a tag remove-×.
1278
+
1279
+ So widening the old rule by radius would have raised switches, swatches,
1280
+ avatars and chips. Depth is keyed on the ROLE instead, which reaches the
1281
+ element two ways:
1282
+
1283
+ 1. `data-depth` — the EXPLICIT hook, and the one to reach for. `key` puts a
1284
+ control on the raised plane, `flat` takes it off. Emitted by the
1285
+ primitives that know their own role and set by hand on the handful of
1286
+ hand-rolled controls the default below gets wrong.
1287
+ 2. a RADIUS DEFAULT for the un-annotated majority, chosen so the default is
1288
+ SAFE: the button radii and the square icon-button shape are keys;
1289
+ everything else interactive is QUIET (flat at rest, but it still
1290
+ presses). A control that should be raised and is not looks understated;
1291
+ one raised that should not be looks broken.
1292
+
1293
+ `tests/theme/immersive-control-depth.test.ts` enumerates the radius utilities
1294
+ the codebase actually emits on interactive tags and fails when one has no
1295
+ classification here — the promise is bound to class names, so something has
1296
+ to break when a new control class appears.
1297
+
1298
+ The GLOBAL baseline edge is a neutral `--el-border-strong` (it suits the
1299
+ outline/toolbar buttons); FILLED variants override it with a colour-matched
1300
+ edge (primary → `--el-accent-pressed`, danger → a darkened `--el-danger` —
1301
+ palette-derived, so the axes stay disjoint); ghost stays flat (the quiet
1302
+ button). box-shadow is not a colour-painting property, so the material-rule
1303
+ test skips these. The press overrides the base `active:scale` with a more
1304
+ physical translateY.
1305
+
1306
+ ⚠️ SPECIFICITY: the key set is wrapped in `:where()` so it contributes
1307
+ NOTHING beyond the `[data-style]` attribute (0,1,0). That is deliberate — the
1308
+ `[data-variant]` overrides below are (0,2,0) and the flat overrides after
1309
+ them are (0,2,0), so both still win. Adding a plain `:is()` here silently
1310
+ inverts that. */
1311
+
1312
+ /* THE KEY SET — controls that carry real thickness.
1313
+ `button.rounded-(--radius-input)` catches DROPDOWN-TRIGGER buttons styled as
1314
+ an input (the board switcher, combobox / picker triggers) — scoped to
1315
+ `button` so a real <input>/<textarea> text field (same radius) is NEVER
1316
+ raised; it is RECESSED instead, below.
1317
+ The `.justify-center` limb is the square icon-button shape: a centred control
1318
+ on the control radius is a key (the 3-dots trigger, the fullscreen toggle,
1319
+ every close ×). A row is never centred — it is `w-full` and `text-left`. */
1320
+ [data-style='3d-immersive']
1321
+ :where(
1322
+ .rounded-\(--radius-btn\),
1323
+ button.rounded-\(--radius-input\),
1324
+ :is(button, a).rounded-\(--radius-control\).justify-center,
1325
+ [data-depth='key']
1326
+ ):not(
1327
+ :where(
1328
+ [role='switch'],
1329
+ [role='radio'],
1330
+ [role='checkbox'],
1331
+ [role='menuitem'],
1332
+ [role='option'],
1333
+ [data-depth='flat']
1334
+ )
1335
+ ) {
1253
1336
  box-shadow:
1254
1337
  0 3px 0 0 var(--el-border-strong),
1255
1338
  0 5px 10px -3px rgba(15, 15, 15, 0.18);
@@ -1272,10 +1355,37 @@
1272
1355
  [data-style='3d-immersive'] [data-variant='ghost'] {
1273
1356
  box-shadow: none;
1274
1357
  }
1275
- /* Press DOWN onto the base — the button drops by its thickness and the base
1276
- edge compresses, like a real key. Overrides the base `active:scale`. */
1277
- [data-style='3d-immersive'] .rounded-\(--radius-btn\):active:not(:disabled),
1278
- [data-style='3d-immersive'] button.rounded-\(--radius-input\):active:not(:disabled) {
1358
+
1359
+ /* THE FLAT SET — rows, chips and switches, which §4 keeps on their parent's
1360
+ plane. These come AFTER the key set and carry (0,2,0), so they win over its
1361
+ (0,1,0) wherever the two overlap (a centred control inside an open menu, a
1362
+ `data-depth='flat'` icon button). A menu row raised inside an already-floating
1363
+ popover reads as debris, not depth — the overlay is the thing that floats. */
1364
+ [data-style='3d-immersive']
1365
+ :is(
1366
+ [role='menuitem'],
1367
+ [role='option'],
1368
+ [role='menuitemcheckbox'],
1369
+ [role='menuitemradio'],
1370
+ [role='switch'],
1371
+ [data-depth='flat']
1372
+ ),
1373
+ [data-style='3d-immersive']
1374
+ :is([data-surface='popover'], [data-menu-surface])
1375
+ :is(button, a).rounded-\(--radius-control\),
1376
+ [data-style='3d-immersive'] :is(button, a).w-full.rounded-\(--radius-control\) {
1377
+ box-shadow: none;
1378
+ }
1379
+
1380
+ /* THE PRESS — the raised set drops by its thickness and the base edge
1381
+ compresses, like a real key. Overrides the base `active:scale`. */
1382
+ [data-style='3d-immersive']
1383
+ :where(
1384
+ .rounded-\(--radius-btn\),
1385
+ button.rounded-\(--radius-input\),
1386
+ :is(button, a).rounded-\(--radius-control\).justify-center,
1387
+ [data-depth='key']
1388
+ ):active:not(:disabled) {
1279
1389
  transform: translateY(3px);
1280
1390
  box-shadow:
1281
1391
  0 1px 0 0 var(--el-border-strong),
@@ -1295,6 +1405,67 @@
1295
1405
  box-shadow: none;
1296
1406
  }
1297
1407
 
1408
+ /* THE QUIET PRESS — a control with no resting thickness still has to answer the
1409
+ pointer, or the style reads as flat wherever it is not a key: menu rows, list
1410
+ rows, nav links, inline affordances, the ghost button. One pixel of travel is
1411
+ the whole treatment; it is what separates "quiet" from "inert". */
1412
+ [data-style='3d-immersive']
1413
+ :where(:is(button, a).rounded-\(--radius-control\), [data-variant='ghost']):active:not(
1414
+ :disabled
1415
+ ) {
1416
+ transform: translateY(1px);
1417
+ }
1418
+
1419
+ /* THE RECESSED SET — text fields are pressed INTO the surface, not raised off
1420
+ it. §4's "on the face" left them with a 1px border and no depth at all on a
1421
+ page where every panel carries a four-layer float, which is the other half of
1422
+ what MOTIR-3522 reported (the roadmap's search box). A raised text field is
1423
+ wrong — you type into a well — so this is the inverse of the key treatment,
1424
+ not a copy of it. `[data-surface='input']` is the Input primitive's shell;
1425
+ the bare element selectors catch the hand-rolled fields. Checkbox and radio
1426
+ are excluded: they are chips, not wells. */
1427
+ [data-style='3d-immersive']
1428
+ :where(
1429
+ [data-surface='input'],
1430
+ input:not([type='checkbox'], [type='radio'], [type='range'], [type='color']),
1431
+ textarea,
1432
+ select
1433
+ ) {
1434
+ box-shadow:
1435
+ inset 0 2px 6px -2px rgba(15, 15, 15, 0.26),
1436
+ inset 0 1px 2px 0 rgba(15, 15, 15, 0.05);
1437
+ }
1438
+
1439
+ /* Hero controls paint their own shadow in an INLINE style, which beats every
1440
+ rule above — an inline declaration is not something a style layer can reach.
1441
+ So the two of them read their shadow from a variable instead, with the base
1442
+ look as the `var()` fallback, and this is where the key's base edge is added
1443
+ to it. Palette-derived throughout; the colour axis stays disjoint.
1444
+ Consumed by components/planning/PlanWithAILauncher.tsx (--plan-hero-shadow)
1445
+ and components/planning/PlanWithAIFab.tsx (--plan-orb-shadow).
1446
+
1447
+ ⚠️ ON `body`, NOT ON THE BARE `[data-style]` TOKEN BLOCK. Both values READ
1448
+ `--el-*`, and `tests/theme/styleRegistry.test.ts` holds a bare token block to
1449
+ the colour/style axis split — a `[data-style='…'] { … }` block may set only
1450
+ shape/feel tokens, never anything naming a palette token, and
1451
+ `tests/theme/shapeSwapLint.test.ts` additionally requires such a block to
1452
+ override every shape role. A descendant rule is neither, which is where the
1453
+ immersive background (which reads `--el-*` for the same reason) already
1454
+ lives. Custom properties inherit, so every control below `body` sees these. */
1455
+ [data-style='3d-immersive'] body {
1456
+ --plan-hero-shadow:
1457
+ 0 4px 0 0 var(--el-accent-pressed),
1458
+ inset 0 1px 0 color-mix(in srgb, var(--el-accent-text) 38%, transparent),
1459
+ inset 0 0 0 1px color-mix(in srgb, var(--el-accent-text) 18%, transparent),
1460
+ 0 9px 18px -5px color-mix(in srgb, var(--el-accent) 75%, transparent),
1461
+ 0 0 22px -3px color-mix(in srgb, var(--el-highlight) 50%, transparent);
1462
+ --plan-orb-shadow:
1463
+ 0 5px 0 0 var(--el-accent-pressed),
1464
+ inset 0 1px 0 color-mix(in srgb, var(--el-accent-text) 40%, transparent),
1465
+ 0 12px 24px -6px color-mix(in srgb, var(--el-accent) 80%, transparent),
1466
+ 0 0 28px -2px color-mix(in srgb, var(--el-highlight) 55%, transparent);
1467
+ }
1468
+
1298
1469
  /* ── 3D / Immersive — DROPDOWNS / POPOVERS / MENUS unfold in 3D ──────────────
1299
1470
  A flat fade-in is the "still not 3D" tell on overlays. The standard 3D
1300
1471
  dropdown UNFOLDS: it swings open from its top edge over a perspective
@@ -2226,17 +2397,31 @@
2226
2397
  /* ── Text scale (the warm-charcoal hierarchy, named by ROLE) ────── */
2227
2398
  --el-text: var(--color-foreground); /* primary ink */
2228
2399
  --el-text-strong: var(--color-charcoal); /* emphasis / AA-safe text on tints */
2229
- --el-text-secondary: var(--color-slate); /* secondary copy */
2400
+ --el-text-secondary: var(--color-slate); /* secondary copy — AA on EVERY surface */
2401
+ /* ⚠️ AA-SAFE ONLY ON THE WHITE PAGE/CARD, and by 0.04 (4.54:1). On
2402
+ --el-surface it is 4.17, on --el-muted 4.12, on --el-surface-soft 4.34 —
2403
+ all under AA. A muted caption belongs inside a card, never on a panel.
2404
+ (MOTIR-2455; light theme is the binding one — every ink clears AA on dark.) */
2230
2405
  --el-text-muted: var(--color-muted-foreground); /* muted / captions */
2231
2406
  --el-text-tertiary: var(--color-steel); /* tertiary / footers */
2232
- --el-text-faint: var(--color-stone); /* faint labels */
2407
+ /* ⚠️ NEVER CARRIES TEXT WCAG MEASURES — 2.37–2.82:1 on every surface in BOTH
2408
+ themes, so there is no context in which it passes. It is for DECORATIVE
2409
+ glyphs (aria-hidden, or a role="img" whose label carries the meaning) and
2410
+ for DISABLED / inactive text, which WCAG 1.4.3 exempts. Active
2411
+ informational text takes --el-text-secondary. (MOTIR-2455) */
2412
+ --el-text-faint: var(--color-stone); /* faint labels — decoration + disabled ONLY */
2233
2413
  --el-text-inverted: var(--color-background); /* text on an ink / accent fill */
2234
2414
 
2235
2415
  /* ── Accent (active CTA identity) + brand highlight ─────────────── */
2236
2416
  /* --el-accent is the FILL behind --el-accent-text (white): filled buttons,
2237
2417
  badges, toggles. --el-accent-on-surface is the accent used AS text/icon on
2238
2418
  a page surface. They diverge in dark theme (fill darkens for white-text AA;
2239
- text stays brand-bright for AA on the near-black canvas) — MOTIR-757. */
2419
+ text stays brand-bright for AA on the near-black canvas) — MOTIR-757.
2420
+ ⚠️ --el-accent-on-surface is OVERRIDDEN in the [data-theme='dark'] block
2421
+ further down: brand-bright-on-the-canvas is not the same claim as AA on the
2422
+ tinted surfaces that sit above it, and the ink was 4.24-4.41:1 on all three
2423
+ for as long as the dark theme has shipped (MOTIR-3745). The light
2424
+ declaration below is unchanged and is what the Tier-0-alias guards read. */
2240
2425
  --el-accent: var(--color-primary-fill);
2241
2426
  --el-accent-text: var(--color-primary-foreground);
2242
2427
  --el-accent-on-surface: var(--color-primary);
@@ -2260,8 +2445,32 @@
2260
2445
  --el-link-pressed: var(--color-link-pressed);
2261
2446
 
2262
2447
  /* ── Semantic status ───────────────────────────────────────────── */
2448
+ /* THREE danger tokens, and the middle one is the trap (MOTIR-3663).
2449
+ --el-danger is the FILL. --el-danger-text is the ink FOR that fill, so it
2450
+ is legal ONLY on an element that also carries bg-(--el-danger) — the
2451
+ Button danger variant, and nothing else. Painted on a page instead it is
2452
+ 1.00:1 in the light theme of ALL TEN palettes (the ink and the page are
2453
+ the same white) and 1.00:1 in six of the ten dark ones; in the other four
2454
+ it renders near-white, indistinguishable from --el-text, so the DANGER
2455
+ SIGNAL is lost rather than the text. Both halves are the same defect, and
2456
+ `tests/theme/inkContrastLint.test.ts`'s danger arm now holds the rule at
2457
+ zero over the same four roots.
2458
+ --el-danger-on-surface is the ink for danger text ON a page: the palette's
2459
+ own danger hue pulled 30% toward that theme's body ink, which is the
2460
+ smallest move that clears AA everywhere. Raw --el-danger does NOT — it is
2461
+ 4.25:1 (base), 4.11:1 (cobalt) and 4.24:1 (graphite) on the DARK page, and
2462
+ under 4.5 on most tints in both themes.
2463
+ DERIVED rather than listed, deliberately: both inputs are tokens, so it
2464
+ re-skins with data-palette and a new palette is covered on the day it is
2465
+ added instead of the day somebody remembers to extend a table. Measured
2466
+ across all 20 palette x theme combinations against page/card, --el-surface,
2467
+ --el-surface-soft, --el-muted, --el-tint-rose and --el-danger-surface, the
2468
+ worst pair is 4.77:1 and it stays perceptually red (deltaE 6.8-14.6 from
2469
+ the base hue, 23.8-39.2 from the body ink). The table is asserted, not
2470
+ asserted-once: inkContrastLint recomputes it from this file. */
2263
2471
  --el-danger: var(--color-destructive);
2264
2472
  --el-danger-text: var(--color-destructive-foreground);
2473
+ --el-danger-on-surface: color-mix(in srgb, var(--el-danger) 70%, var(--el-text));
2265
2474
  --el-success: var(--color-success);
2266
2475
  --el-warning: var(--color-warning);
2267
2476
  --el-info: var(--color-info);
@@ -2288,6 +2497,27 @@
2288
2497
  lib/workflows/defaultWorkflow.ts. */
2289
2498
  --el-status-todo: var(--color-stone); /* neutral grey — not started */
2290
2499
  --el-status-in-progress: var(--color-info); /* blue — active */
2500
+ --el-status-planning: var(--color-accent-teal); /* teal — being RE-PLANNED, not being worked */
2501
+ /* BUILT, waiting on checks (MOTIR-3003) — a DESATURATED In Review, and the
2502
+ mix is the meaning: this card is what In Review will be once CI says so, and
2503
+ has not earned the brand hue yet. Both inputs are palette-tuned status
2504
+ tokens, so it needs no per-palette block and it TRACKS a palette that moves
2505
+ `in_review` — measured at ΔE2000 >= 10 against every other status in all ten
2506
+ palettes x both themes (tests/theme/statusHueSeparation.test.ts).
2507
+ ⚠️ It is NOT --color-accent, which `design/boards/implemented-column.mock.html`
2508
+ originally specified: that reads clean in the base palette and collides in
2509
+ five of the ten (amber/candy/citrine/graphite/sienna, ΔE as low as 0.0). The
2510
+ asset carries the correction and the measurement. */
2511
+ /* pink — BUILT, waiting on checks (MOTIR-3003). The one saturated Tier-0 hue
2512
+ no other STATUS spends; sharing a hue across FAMILIES is this file's own
2513
+ convention (--el-type-epic is also --color-accent). ΔE 33.2 / 26.2 from its
2514
+ nearest neighbour in this palette.
2515
+ ⚠️ FIVE palettes re-map --color-accent onto something a status already uses,
2516
+ so each carries its own override below — the same per-palette follow-up
2517
+ --el-status-planning needed in citrine and candy (MOTIR-2425). Measured by
2518
+ tests/theme/statusHueSeparation.test.ts, which now folds a color-mix rather
2519
+ than throwing on one. */
2520
+ --el-status-implemented: var(--color-accent);
2291
2521
  --el-status-in-review: var(--color-primary); /* brand — differentiate from in-progress */
2292
2522
  --el-status-done: var(--color-success); /* green — resolved */
2293
2523
  --el-status-blocked: var(--color-warning); /* amber — can't proceed (gap fixed) */
@@ -2341,6 +2571,9 @@
2341
2571
  --el-role-admin: var(--color-tint-lavender);
2342
2572
  --el-role-member: var(--color-tint-sky);
2343
2573
  --el-role-viewer: var(--color-tint-mint);
2574
+ /* A project's OWN role (MOTIR-2485) — peach, the next free slot: the members
2575
+ surface had already spent lavender / sky / mint on the three built-ins. */
2576
+ --el-role-custom: var(--color-tint-peach);
2344
2577
  --el-org-role-owner: var(--color-tint-lavender);
2345
2578
  --el-org-role-admin: var(--color-tint-sky);
2346
2579
  --el-org-role-member: var(--color-tint-mint);
@@ -2374,6 +2607,13 @@
2374
2607
  --el-notif-commented: var(--color-info); /* was --el-type-task */
2375
2608
  --el-notif-assigned: var(--color-accent-green); /* was --el-type-story */
2376
2609
  --el-notif-transitioned: var(--color-accent-teal); /* was --el-type-subtask */
2610
+ /* The EXECUTOR badge on a Home row's assignee avatar (Story MOTIR-2649 ·
2611
+ Subtask MOTIR-2653, per design/home/design-notes.md §"The agent state").
2612
+ Same mapping form as --el-notif-mentioned above and for the same reason:
2613
+ it resolves to the accent fill today, but owning its own slot means a
2614
+ palette can move agent badges without dragging the CTA with them. The
2615
+ third pillar becoming visible is worth a token of its own. */
2616
+ --el-executor-agent: var(--color-primary-fill);
2377
2617
  /* AI model dots (promote to a named family; deepseek was borrowing --el-type-subtask).
2378
2618
  opus keeps the accent fill, same --color-primary-fill reasoning as mentioned. */
2379
2619
  --el-model-opus: var(--color-primary-fill); /* the accent fill (matches prior --el-accent) */
@@ -2427,6 +2667,33 @@
2427
2667
  --el-card: var(--color-background); /* untinted card surface — fixes the bg-card orphan */
2428
2668
  --el-input-border: var(--color-hairline-strong); /* input outline (was --el-border-strong) */
2429
2669
  --el-button-border: var(--color-hairline-strong); /* secondary-button outline */
2670
+
2671
+ /* ── Non-editable field states (MOTIR-2495) ──
2672
+ A field that cannot be typed into used to be drawn with `opacity-50` on the
2673
+ whole wrapper, which composites its CHILDREN — value, placeholder, and the
2674
+ `addonStart`/`addonEnd` affixes — against the page and roughly halves
2675
+ whatever contrast the palette guaranteed them. Contrast is a property of a
2676
+ PAIR (theme.css's own colour rule); opacity quietly makes it a property of
2677
+ a triple, so no ink choice can be checked and none can be fixed: the org
2678
+ URL's affix was already `--el-text-secondary`, the darkest caption ink, and
2679
+ still measured below AA. These four tokens express the two states as
2680
+ COLOUR instead, so every ink over them is AA-checkable and palette-swappable.
2681
+ Measured on the base palette, light (the binding theme; dark clears by more):
2682
+ disabled value/affix --color-slate on --color-muted = 6.18:1 ✓
2683
+ read-only value --color-foreground on --color-surface = 15.98:1 ✓
2684
+ read-only affix --color-slate on --color-surface = 6.24:1 ✓
2685
+ The DISABLED border is deliberately softer than the enabled one — the state
2686
+ reads through fill + ink + edge together, so it survives a surface-material
2687
+ style (glassmorphism repaints [data-surface='input'], and its rule outranks
2688
+ a utility class) that erases the fill alone. */
2689
+ --el-input-disabled-bg: var(--color-muted); /* disabled field fill (replaces opacity-50) */
2690
+ --el-input-disabled-border: var(--color-hairline); /* disabled field edge — softer than enabled */
2691
+ --el-input-disabled-text: var(
2692
+ --color-slate
2693
+ ); /* disabled value/placeholder/affix ink — AA on the fill above */
2694
+ --el-input-readonly-bg: var(
2695
+ --color-surface
2696
+ ); /* read-only field fill — not editable, but focusable + full ink */
2430
2697
  --el-count-bg: var(--color-muted); /* count-badge fill (shipped value; see note above) */
2431
2698
  --el-count-text: var(--color-slate); /* count-badge ink (was --el-text-secondary) */
2432
2699
 
@@ -2460,6 +2727,21 @@
2460
2727
  --el-type-manual: var(--color-stone); /* stone — human provisioning */
2461
2728
  --el-type-chore: var(--color-slate); /* slate — housekeeping */
2462
2729
 
2730
+ /* The four members ADR Amendment 1 (MOTIR-2629) admitted, per MOTIR-2631's
2731
+ design (design/work-items/design-notes.md, "Q2"). The block above spent
2732
+ ALL SEVEN mutually distinct saturated hues and all three greys at ten
2733
+ members, and there is no eighth — inventing one is forbidden. So each
2734
+ newcomer POINTS AT the Tier-0 hue of the member the amendment named as its
2735
+ NEAREST NEIGHBOUR, and the GLYPH carries the difference (the chip renders
2736
+ its label too, so hue was never the sole carrier — finding #35).
2737
+ Each keeps its OWN token so a future palette can split them without
2738
+ touching a component, and so WORK_ITEM_TYPE_META's per-member `hueVar`
2739
+ shape holds. Hue names the FAMILY; the glyph names the member. */
2740
+ --el-type-copy: var(--color-accent-teal); /* teal — with content (Author) */
2741
+ --el-type-translate: var(--color-accent-teal); /* teal — with content (Author) */
2742
+ --el-type-verification: var(--color-accent-orange); /* orange — with review (Investigate) */
2743
+ --el-type-legal: var(--color-charcoal); /* ink — with decision (Govern) */
2744
+
2463
2745
  /* ── Chart series (Story 4.6 · the reusable viz Story 6.3 reuses) ──
2464
2746
  Every chart colour routes through one of these, NEVER a raw --color-*
2465
2747
  in chart code (the per-component growth pattern). They map to Tier-0
@@ -2537,8 +2819,14 @@
2537
2819
  --el-diff-moved: var(--color-tint-sky);
2538
2820
  --el-chat-bubble-user: var(--color-primary-fill); /* text stays --el-accent-text */
2539
2821
  --el-chat-bubble-ai: var(--color-surface-soft); /* text stays --el-text */
2540
- --el-canvas-edge-pending: var(--color-border); /* dashed, not-yet-committed edge */
2541
- --el-canvas-edge-committed: var(--color-hairline-strong); /* solid, settled edge */
2822
+ /* Canvas dependency edges ride the RECESSED --el-canvas, not a raised surface, so
2823
+ they use INK tokens (--color-slate / -charcoal), NOT the hairline-on-a-card
2824
+ --color-border / -hairline-strong which are near-invisible there (~1.0-1.9:1 vs
2825
+ the canvas in every palette — MOTIR-1564). Ink clears WCAG 1.4.11 (≥3:1) on the
2826
+ canvas across all palettes/themes while still re-tinting per palette; solid vs
2827
+ dashed + the arrowhead marker carry the committed/pending distinction. */
2828
+ --el-canvas-edge-pending: var(--color-slate); /* dashed, not-yet-committed edge (quiet ink) */
2829
+ --el-canvas-edge-committed: var(--color-charcoal); /* solid, settled edge (emphasis ink) */
2542
2830
  --el-station-tier-discovery: var(--color-tint-sky);
2543
2831
  --el-station-tier-vision: var(--color-tint-lavender);
2544
2832
  --el-station-tier-feasibility: var(--color-tint-mint);
@@ -2602,6 +2890,53 @@
2602
2890
  one-shade-darker. */
2603
2891
  [data-theme='dark'] {
2604
2892
  --el-sidebar-item-bg-hover: #222222;
2893
+ /* THE DARK ACCENT INK (MOTIR-3745). Light keeps the plain
2894
+ var(--color-primary) alias declared in the Tier-3 block above, byte for
2895
+ byte — this is a DARK-ONLY lift, and deliberately so: the light accent
2896
+ already clears AA on every surface (6.29:1 on --el-surface-soft in the
2897
+ base palette), so there is nothing there to buy and a re-tint would cost
2898
+ the brand hue for no contrast.
2899
+
2900
+ What it fixes: the dark --color-primary was chosen for AA on the near-black
2901
+ CANVAS (#0f0f0f, 4.67:1 — see the Tier-1 comment at --color-primary, and
2902
+ MOTIR-757) and was never measured against anything else. Every tinted
2903
+ surface in dark sits ABOVE the canvas, so the ink arrives there with less
2904
+ contrast, not more: 4.24:1 on --el-surface / --el-muted (#1a1a1a) and
2905
+ 4.41:1 on --el-surface-soft (#161616), across all sixteen --el-* aliases of
2906
+ those three fills. 13.5px semibold is not WCAG large text, so 1.4.3 asks
2907
+ 4.5:1 and ExploreTopBar's current-page nav item missed it by 0.09 on
2908
+ /explore and /docs — both public, both reachable signed out.
2909
+
2910
+ Why the mix and not a hex: DERIVED from two tokens, exactly like
2911
+ --el-danger-on-surface above, so it re-skins with data-palette and an
2912
+ eleventh palette is covered the day it lands rather than the day somebody
2913
+ remembers a table.
2914
+
2915
+ Why 82%: it is sized against a WIDER set than the guard measures. On the
2916
+ surface vocabulary the accent arm asserts (the page white and the three
2917
+ greys, every alias) the worst dark pair at 82% is 5.53:1. The binding
2918
+ constraint is --el-tint-lavender — the accent family's own tint, which the
2919
+ arm does not measure but which the tree paints accent text on in ten files:
2920
+ 84% is the largest primary share that clears it (4.57:1) and 82% is one
2921
+ 2% step further, for headroom (4.68:1). MOTIR-3774 carries the LIGHT half
2922
+ of that pair, which no dark override can reach. Pulling further still would
2923
+ eventually cost the accent its signal rather than buy contrast, so the
2924
+ distance from --el-text is asserted too (22.6-54.5 deltaE, floor 10).
2925
+
2926
+ Why HERE and not on the Tier-3 alias: --el-accent-on-surface's light
2927
+ declaration is a load-bearing Tier-0 ALIAS. tests/theme/
2928
+ iconTextSurfaceTokens + interactionAgileTokens pin --el-icon-active and
2929
+ --el-tabnav-active as zero-change aliases OF it, and brand-tile-contrast
2930
+ resolves it to a --color-* by name. A dark-only override leaves all three
2931
+ reading the base declaration they were written about, and leaves
2932
+ --color-primary's eleven other Tier-3 consumers (plus --focus-ring-color)
2933
+ where they are — including --el-vote-active-bg, which is a FILL and would
2934
+ have LOST contrast against its white ink. Move the ink, not the fill
2935
+ (MOTIR-3664).
2936
+
2937
+ The table is asserted, not asserted-once: inkContrastLint's accent arm
2938
+ recomputes it from this file over every palette x theme. */
2939
+ --el-accent-on-surface: color-mix(in srgb, var(--color-primary) 82%, var(--el-text));
2605
2940
  /* The modal scrim deepens in dark (MOTIR-1275 · 1266.4): on a near-black
2606
2941
  canvas a 40% scrim barely separates the dialog, so dark uses ~65% — the
2607
2942
  other concrete-hex --el-* alongside the sidebar hover. Light value (40%,
@@ -2609,6 +2944,83 @@
2609
2944
  --el-overlay-scrim: #000000a6;
2610
2945
  }
2611
2946
 
2947
+ /* ═══════════════════════════════════════════════════════════════════════
2948
+ RECIPE KNOBS — the SCALARS a component's own colour recipe reads
2949
+ (MOTIR-3207)
2950
+
2951
+ Not part of the Tier-3 layer above and deliberately not named `--el-*`:
2952
+ that layer is a COLOUR contract (every member aliases a Tier-0 --color-*
2953
+ and is asserted palette-dependent by tests/theme/paletteTokenCoverage).
2954
+ A knob here is a NUMBER inside a color-mix() — one value for every
2955
+ palette and both themes — so it fails both halves of that contract while
2956
+ being exactly the thing that has to be expressed once.
2957
+
2958
+ Declared on a plain `:root` rule rather than in @theme so the property is
2959
+ emitted unconditionally: its only consumer is a `var()` inside a JS style
2960
+ object, which Tailwind's source scan has no reason to keep a theme
2961
+ variable alive for.
2962
+ ═══════════════════════════════════════════════════════════════════════ */
2963
+ :root {
2964
+ /* THE ORB's lit-sphere highlight — how much --el-accent-text is mixed into
2965
+ --el-accent at the first stop of `PlanWithAIFab`'s radial gradient, which
2966
+ is the gradient's LIGHTEST point and therefore where the white glyph over
2967
+ it is closest to disappearing (design/ai-chat/design-notes.md § B).
2968
+
2969
+ 26%, not the 32% that shipped, and ONE number rather than a per-theme
2970
+ pair. At 32% the glyph misses WCAG 1.4.11's 3:1 for a non-text graphic in
2971
+ FOUR of the twenty palette x theme contexts — default dark 2.78:1, cobalt
2972
+ dark 2.86:1, spectrum dark 3.00:1 (2.999, i.e. under the bar) and,
2973
+ contrary to the finding that filed this, evergreen LIGHT at 2.94:1. So
2974
+ "light has headroom" is a property of the DEFAULT PALETTE, not of light:
2975
+ a `[data-theme='dark']` override of this stop would have left evergreen's
2976
+ light orb failing. At 26% the worst context is 3.09:1 and all twenty
2977
+ clear the bar.
2978
+
2979
+ Mixing the glyph's own colour INTO its backdrop is monotonic — a larger
2980
+ number is always less contrast — so this is a CEILING, and lowering it
2981
+ further is always safe for legibility and only ever costs the lit look.
2982
+ tests/theme/orb-glyph-contrast.test.ts measures every context from these
2983
+ tokens and fails if a future palette or a raised knob crosses 3:1. */
2984
+ --orb-lit-mix: 26%;
2985
+ }
2986
+
2987
+ /* ═══════════════════════════════════════════════════════════════════════
2988
+ MOTION FLOOR — the reduced-motion guard the PACKAGE owns (MOTIR-3844)
2989
+
2990
+ Not an axis and not a tier: a floor every consumer of this stylesheet gets
2991
+ whichever palette, style or type it applies. The style axis already gates
2992
+ its OWN motion (the `aurora` canvas drift and the `3d-immersive` tilt /
2993
+ menu unfold, all `prefers-reduced-motion`-gated in their Tier-2 blocks
2994
+ above), because that motion is decorative and belongs to the style that
2995
+ introduced it. `animate-spin` belongs to no style: `Spinner` paints it on
2996
+ ordinary interactive surfaces, and `<Button loading>` substitutes a Spinner
2997
+ for the left icon — so it renders under every axis combination, and nothing
2998
+ guarded it. Tailwind v4 defines `--animate-spin: spin 1s linear infinite`
2999
+ with no media query anywhere near it, and neither consumer's `globals.css`
3000
+ carried an override.
3001
+
3002
+ STOP THE ROTATION, KEEP THE GLYPH. A spinner is not decoration a reader can
3003
+ do without — it is the only signal that a control is working — so this sets
3004
+ `animation` and nothing else: the ring keeps its size, its border and its
3005
+ `role="status"`, and a reader who asked for less motion still sees that
3006
+ something is in flight. (`motir-marketing`'s own `.motir-spin` reaches the
3007
+ same shape by hand, and pairs it with a label change — with the animation
3008
+ stilled, the word is the whole of the state. A consumer whose only motion
3009
+ cue is the rotation should do the same.)
3010
+
3011
+ WHY IT WINS THE CASCADE. `.animate-spin` is emitted by Tailwind inside
3012
+ `@layer utilities`; this rule is UNLAYERED, and an unlayered declaration
3013
+ outranks a layered one whatever their specificity. That is the whole of the
3014
+ mechanism — it does not depend on source order or on this file's position in
3015
+ the `@import` list. Measured, not reasoned: tests/theme/reducedMotionSpinner
3016
+ reads it back off the COMPILED stylesheet for both consumer shapes.
3017
+ ═══════════════════════════════════════════════════════════════════════ */
3018
+ @media (prefers-reduced-motion: reduce) {
3019
+ .animate-spin {
3020
+ animation: none;
3021
+ }
3022
+ }
3023
+
2612
3024
  /* ═══════════════════════════════════════════════════════════════════════
2613
3025
  AXIS 1 (COLOUR) — data-palette overrides (Subtask 7.3.48)
2614
3026
  Registry: lib/theme/palettes.ts. A named palette re-skins the app by
@@ -2663,6 +3075,7 @@
2663
3075
  --color-foreground: #0f1b2e; /* cool navy ink */
2664
3076
  --color-surface: #eef2f8; /* cool slate section background */
2665
3077
  --color-surface-soft: #f7f9fc; /* quieter cool division */
3078
+ --color-canvas: #e3e9f2; /* recessed planning board — a cool-slate step under --color-surface */
2666
3079
 
2667
3080
  /* text scale — cool slate hierarchy */
2668
3081
  --color-ink: #0f1b2e;
@@ -2708,6 +3121,26 @@
2708
3121
  /* sidebar hover is a concrete hex in Tier 3 (not a --color-* ref), so the
2709
3122
  palette overrides it directly — one shade darker than the cool surface. */
2710
3123
  --el-sidebar-item-bg-hover: #e4e9f1;
3124
+
3125
+ /* Status ramp — in_review vs in_progress (MOTIR-2075, the MOTIR-2073 class).
3126
+ `--el-status-in-progress` rides --color-info (Radix Blue 11 #0d63b8) and
3127
+ `--el-status-in-review` rides --color-primary (the Indigo accent #3a57cf);
3128
+ Cobalt is an institutional-BLUE palette, so its two blues sit ΔE2000 8.4
3129
+ apart — different hexes, one dot. The ramp takes its own deeper step of the
3130
+ SAME indigo instead of a new hue: Radix Indigo 12. Separation from
3131
+ in_progress ΔE 20.9; contrast 11.8:1 surface / 13.2:1 card. */
3132
+ --el-status-in-review: #1f2d5c; /* Radix Indigo 12 — deep accent step */
3133
+
3134
+ /* Priority ramp — highest vs high (MOTIR-2085, the MOTIR-2075 class one
3135
+ family over). `--el-priority-highest` rides --color-destructive (#d3322f)
3136
+ and `--el-priority-high` rides --color-warning (#c2410c); Cobalt's warning
3137
+ is a RED-leaning burnt orange, so the two land ΔE2000 9.9 apart — under the
3138
+ ΔE 10 bar a coloured priority GLYPH needs. The semantic identity is kept
3139
+ (danger stays danger, warning stays warning); the PRIORITY ramp takes its
3140
+ own step of a hue Cobalt already owns — Radix Amber 11, one of the palette's
3141
+ documented Radix sources. Separation from highest ΔE 24.3; contrast 4.6:1
3142
+ card / 4.1:1 surface, past the 3:1 icon/UI bar. */
3143
+ --el-priority-high: #ab6400; /* Radix Amber 11 — the ramp's own amber step */
2711
3144
  }
2712
3145
 
2713
3146
  /* Cobalt — dark companion. A cool navy canvas (not the base near-black);
@@ -2718,6 +3151,7 @@
2718
3151
  --color-foreground: #e7edf6;
2719
3152
  --color-surface: #131d2c;
2720
3153
  --color-surface-soft: #0f1825;
3154
+ --color-canvas: #070c16; /* recessed planning board — a step under the navy page */
2721
3155
 
2722
3156
  --color-ink: #e7edf6;
2723
3157
  --color-charcoal: #d3deec;
@@ -2762,6 +3196,18 @@
2762
3196
 
2763
3197
  /* sidebar hover — one shade lighter than the dark cool surface */
2764
3198
  --el-sidebar-item-bg-hover: #1c2840;
3199
+
3200
+ /* Dark keeps the indigo primary — the two blues are already ΔE 10.4 apart
3201
+ here. Re-asserted (not omitted) because the light block above would
3202
+ otherwise inherit onto this canvas: the palette block and
3203
+ [data-theme='dark'] tie on specificity and the palette block wins by
3204
+ source order. Pairing every status override is the rule, not the value. */
3205
+ --el-status-in-review: var(--color-primary);
3206
+
3207
+ /* Dark keeps the brightened warning orange — highest/high are already ΔE 25.8
3208
+ apart here. Re-asserted for the same pairing reason as the status override
3209
+ above: an unpaired light override leaks onto this canvas (MOTIR-2085). */
3210
+ --el-priority-high: var(--color-warning);
2765
3211
  }
2766
3212
 
2767
3213
  /* ── Palette: Graphite (data-palette="graphite") — Subtask 7.3.51 ───────────
@@ -2809,6 +3255,7 @@
2809
3255
  --color-foreground: #16191d; /* cool near-black ink */
2810
3256
  --color-surface: #eef0f3; /* cool slate section background */
2811
3257
  --color-surface-soft: #f8f9fa; /* quieter cool division */
3258
+ --color-canvas: #e6e8ed; /* recessed planning board — a cool-slate step under --color-surface */
2812
3259
 
2813
3260
  /* text scale — cool slate hierarchy (Radix Slate 11→9 below body) */
2814
3261
  --color-ink: #16191d;
@@ -2855,6 +3302,42 @@
2855
3302
  /* sidebar hover is a concrete hex in Tier 3 (not a --color-* ref), so the
2856
3303
  palette overrides it directly — one shade darker than the cool surface. */
2857
3304
  --el-sidebar-item-bg-hover: #e6e8ec;
3305
+
3306
+ /* Status ramp — the one place the mono identity COLLIDES with the Tier-0
3307
+ indirection (MOTIR-2073). `--el-status-in-progress` rides --color-info and
3308
+ `--el-status-in-review` rides --color-primary; every other palette keeps
3309
+ those two sources apart, but Graphite deliberately unifies them (info =
3310
+ the accent, above), which re-collapsed in_review onto in_progress — the
3311
+ exact differentiation MOTIR-1273 delivered. The fix keeps BOTH deliberate
3312
+ choices by giving the status ramp its own SECOND STEP of the same accent
3313
+ ramp rather than a second hue: Radix Blue 12, one step deeper than the
3314
+ accent (Blue 11-ish #155bc4). Still one chromatic accent, two depths —
3315
+ the same light/dark inversion the ink CTA already uses. Separation from
3316
+ in-progress: ΔE2000 16.9, contrast 2.0:1 — well clear of this palette's
3317
+ tightest deliberate status pair (todo/cancelled, ΔE 11.8). */
3318
+ --el-status-in-review: #113264; /* Radix Blue 12 — deep accent step */
3319
+
3320
+ /* Priority ramp — highest vs high (MOTIR-2094, the MOTIR-2085 shape in a
3321
+ second palette). `--el-priority-highest` rides --color-destructive
3322
+ (#c92a2a) and `--el-priority-high` rides --color-warning (#c2410c);
3323
+ Graphite's warning is a RED-leaning burnt orange sitting beside its danger
3324
+ red, so the two land ΔE2000 10.02 apart. That PASSES the ΔE 10 bar a
3325
+ coloured priority GLYPH needs — by 0.02, which is rounding, not margin: the
3326
+ two marks are at the perceptual minimum, and any future nudge to either
3327
+ semantic tips the pair under the floor. Nothing about this is the monochrome
3328
+ identity (the chrome is greyscale; semantic hues stay chromatic on purpose),
3329
+ so it is a collision to fix, not a choice to document.
3330
+ The fix is Cobalt's: neither semantic moves — danger stays danger, warning
3331
+ stays warning — and the PRIORITY ramp takes its own step of a hue Graphite
3332
+ already owns, Radix Amber 11, one of this palette's documented Radix
3333
+ sources. Separation from highest ΔE 24.7 (nearest other step, medium, ΔE
3334
+ 32.2); contrast 4.6:1 card / 4.0:1 surface, past the 3:1 icon/UI bar. */
3335
+ --el-priority-high: #ab6400; /* Radix Amber 11 — the ramp's own amber step */
3336
+ /* implemented (MOTIR-3003) — Graphite's --color-accent is its blue accent, a
3337
+ step from the same family as --el-status-in-progress. The ink step is free
3338
+ here and reads as "not yet coloured in", which is what the state is:
3339
+ ΔE 15.9 light / 11.8 dark from the nearest status. */
3340
+ --el-status-implemented: var(--color-charcoal);
2858
3341
  }
2859
3342
 
2860
3343
  /* Graphite — dark companion. A cool near-black slate canvas; the CTA fill
@@ -2866,6 +3349,7 @@
2866
3349
  --color-foreground: #edeef0;
2867
3350
  --color-surface: #18191c; /* cool slate section */
2868
3351
  --color-surface-soft: #141517; /* quieter cool division */
3352
+ --color-canvas: #08090b; /* recessed planning board — a step under the near-black page */
2869
3353
 
2870
3354
  --color-ink: #edeef0;
2871
3355
  --color-charcoal: #d7d9dd; /* emphasis / AA-safe on tints */
@@ -2911,6 +3395,26 @@
2911
3395
 
2912
3396
  /* sidebar hover — one shade lighter than the dark cool surface */
2913
3397
  --el-sidebar-item-bg-hover: #202226;
3398
+
3399
+ /* Status ramp — the dark companion of the light block's in_review step
3400
+ (MOTIR-2073). Same idea, inverted like the CTA fill: on near-black the
3401
+ "deeper" step is the PALER one, so in_review takes Radix Blue 12 dark —
3402
+ one step lighter than the dark accent (Blue 11-ish #7db1ff). Separation
3403
+ from in-progress: ΔE2000 15.3, contrast 1.67:1; on the dark canvas
3404
+ 14.9:1. Every palette block that sets a var must set it in BOTH themes —
3405
+ a light-only override keeps its LIGHT value on the dark canvas (the
3406
+ palette block outranks the base [data-theme='dark'] block). */
3407
+ --el-status-in-review: #c2e6ff; /* Radix Blue 12 dark — pale accent step */
3408
+
3409
+ /* Dark keeps the brightened warning orange — highest/high are already ΔE 25.8
3410
+ apart here, so only light needed the amber step. Re-asserted (not omitted)
3411
+ for the same pairing reason as the status override above: an unpaired light
3412
+ override leaks onto this canvas, where it was never measured (MOTIR-2094). */
3413
+ --el-priority-high: var(--color-warning);
3414
+ /* implemented — the dark half of the light override above; declared in BOTH
3415
+ themes so the light value cannot cascade into dark (the cascade trap this
3416
+ suite checks for). */
3417
+ --el-status-implemented: var(--color-charcoal);
2914
3418
  }
2915
3419
 
2916
3420
  /* ── Palette: Evergreen (data-palette="evergreen") — Subtask 7.3.50 ──────────
@@ -2932,17 +3436,23 @@
2932
3436
  Concrete-hex --el-* tokens that do NOT resolve through --color-*
2933
3437
  (--el-sidebar-item-bg-hover) are re-skinned explicitly below. */
2934
3438
  [data-palette='evergreen'] {
2935
- /* Brand / primary — emerald CTA (white label text clears AA on the fill) */
2936
- --color-primary: #0c7a52;
3439
+ /* Brand / primary — emerald CTA (white label text clears AA on the fill).
3440
+ --color-primary is the emerald used AS text/icon; --color-primary-fill is
3441
+ the CTA fill. They were the same hex until MOTIR-3774 deepened the INK by
3442
+ 1.5% lightness (same hue 158.2, same 82.1% saturation) so it clears AA on
3443
+ the palette's own pastel tints; the FILL is untouched, which is what the
3444
+ picker swatch and every white-on-emerald label read. */
3445
+ --color-primary: #0b714c; /* deepened emerald as text/icon (6.03:1 on white) */
2937
3446
  --color-primary-foreground: #ffffff;
2938
3447
  --color-primary-pressed: #095f40;
2939
- --color-primary-fill: #0c7a52;
3448
+ --color-primary-fill: #0c7a52; /* emerald CTA fill — the signature; white-on AA */
2940
3449
 
2941
3450
  /* Surfaces — lightly cooled (green-leaning) neutrals over a white canvas */
2942
3451
  --color-background: #ffffff;
2943
3452
  --color-foreground: #14201b;
2944
3453
  --color-surface: #eef3f0;
2945
3454
  --color-surface-soft: #f6faf8;
3455
+ --color-canvas: #e4ebe7; /* recessed planning board — a green-grey step under --color-surface */
2946
3456
 
2947
3457
  /* Text scale — cooled forest-charcoal ink hierarchy */
2948
3458
  --color-ink: #14201b;
@@ -2991,6 +3501,19 @@
2991
3501
  /* Concrete-hex --el token (does not resolve through --color-*) — cool hover
2992
3502
  lift one shade darker than the cooled light surface */
2993
3503
  --el-sidebar-item-bg-hover: #e4ebe7;
3504
+
3505
+ /* Status ramp — in_review vs done (MOTIR-2075). `--el-status-in-review` rides
3506
+ --color-primary (emerald #0c7a52) and `--el-status-done` rides
3507
+ --color-success (grass #127c3e): two greens ΔE2000 5.5 apart, and this
3508
+ palette's own comment claimed they "read apart". They do not. The ramp takes
3509
+ its own deeper step of the palette's JADE — Radix Jade 12 — so review reads
3510
+ as a deep jade against the grass done. Separation ΔE 19.2; contrast 10.9:1
3511
+ surface / 12.2:1 card. */
3512
+ --el-status-in-review: #1d3b31; /* Radix Jade 12 — deep jade step */
3513
+
3514
+ /* cancelled is unchanged in light (ΔE 17.9 from todo); re-asserted to pair
3515
+ the dark override below — see the cascade note in the dark block. */
3516
+ --el-status-cancelled: var(--color-steel);
2994
3517
  }
2995
3518
 
2996
3519
  /* Evergreen — dark companion. Bright emerald on a deep-forest canvas
@@ -3006,6 +3529,7 @@
3006
3529
  --color-foreground: #e8efe9;
3007
3530
  --color-surface: #141d18;
3008
3531
  --color-surface-soft: #101713;
3532
+ --color-canvas: #080d0a; /* recessed planning board — a step under the deep-forest page */
3009
3533
 
3010
3534
  --color-ink: #e8efe9;
3011
3535
  --color-charcoal: #d6e0d9;
@@ -3044,6 +3568,18 @@
3044
3568
 
3045
3569
  /* hover lift one shade *lighter* than the dark surface (mirrors base recipe) */
3046
3570
  --el-sidebar-item-bg-hover: #1e2a23;
3571
+
3572
+ /* Status ramp (MOTIR-2075) — two collisions in this theme.
3573
+ in_review vs done: emerald #3ddc97 against grass #34d27a, ΔE2000 4.7. Same
3574
+ fix as light, inverted: Radix Jade 12's DARK step, pale rather than deep —
3575
+ the light/dark inversion Graphite's accent step already uses. ΔE 17.2;
3576
+ contrast 13.3:1 surface / 14.5:1 card.
3577
+ todo vs cancelled: --color-stone #5a6b62 against --color-steel #6f8077,
3578
+ ΔE 8.3 — this palette's neutral ramp is compressed against the base's
3579
+ (which keeps the pair ~12 apart). cancelled takes the palette's own next
3580
+ step up, --color-slate. ΔE 22.4; contrast 7.1:1 / 7.7:1. */
3581
+ --el-status-in-review: #adf0d4; /* Radix Jade 12 dark — pale jade step */
3582
+ --el-status-cancelled: #9aaaa1; /* Evergreen's own slate — the next neutral step */
3047
3583
  }
3048
3584
 
3049
3585
  /* ═══════════════════════════════════════════════════════════════════════
@@ -3122,6 +3658,7 @@
3122
3658
  --color-foreground: #1a1626; /* cool near-black ink with a faint violet cast */
3123
3659
  --color-surface: #f4f2fb; /* cool violet-tinted section background */
3124
3660
  --color-surface-soft: #faf9fe; /* quieter cool division */
3661
+ --color-canvas: #ece8f7; /* recessed planning board — a violet-tinted step under --color-surface */
3125
3662
 
3126
3663
  /* text scale — cool violet-neutral hierarchy */
3127
3664
  --color-ink: #1a1626;
@@ -3169,6 +3706,20 @@
3169
3706
  /* sidebar hover is a concrete hex in Tier 3 (not a --color-* ref), so the
3170
3707
  palette overrides it directly — one shade darker than the cool surface. */
3171
3708
  --el-sidebar-item-bg-hover: #ece8f8;
3709
+
3710
+ /* Priority ramp — highest vs high, the third instance of the Cobalt
3711
+ (MOTIR-2085) / Graphite (MOTIR-2094) collision, found once the floor was
3712
+ measured on the CHIP rather than the source (MOTIR-2107).
3713
+ `--el-priority-highest` rides --color-destructive (#d92e2b) and
3714
+ `--el-priority-high` rides --color-warning (#ea580c); Spectrum's warning is
3715
+ a red-leaning orange, so the two source hues land ΔE2000 14.5 apart — over
3716
+ the glyph bar, while the 14% chip `Pill` paints rendered only ΔE 4.2, under
3717
+ cobalt's own pre-fix 3.8 in feel. Neither semantic moves; the PRIORITY ramp
3718
+ takes its own step of a hue Spectrum already owns — Radix Amber 11, one of
3719
+ the palette's documented Radix sources. Source separation from highest
3720
+ ΔE 24.3, rendered chip ΔE 8.6; contrast 4.6:1 card / 4.2:1 surface, past
3721
+ the 3:1 icon/UI bar. */
3722
+ --el-priority-high: #ab6400; /* Radix Amber 11 — the ramp's own amber step */
3172
3723
  }
3173
3724
 
3174
3725
  /* Spectrum — dark companion. A deep cool-violet canvas (not the base
@@ -3179,6 +3730,7 @@
3179
3730
  --color-foreground: #ece9f6;
3180
3731
  --color-surface: #1d1830;
3181
3732
  --color-surface-soft: #181426;
3733
+ --color-canvas: #0f0c18; /* recessed planning board — a step under the cool-violet page */
3182
3734
 
3183
3735
  --color-ink: #ece9f6;
3184
3736
  --color-charcoal: #ddd8ee;
@@ -3216,7 +3768,19 @@
3216
3768
  --color-success: #36c977;
3217
3769
  --color-warning: #fb8b4c;
3218
3770
  --color-destructive: #f0555f; /* reads AS text on the dark canvas (AA ~5.5:1) */
3219
- --color-destructive-foreground: #ffffff;
3771
+ /* The FILL ink, not a page ink — this is what `Button`'s danger variant paints
3772
+ ON `--color-destructive` above. Dark lightened the red to #f0555f so it would
3773
+ read AS text on the violet canvas, and left the ink at #ffffff: two
3774
+ individually reasonable decisions that are wrong together, at 3.40:1 — the
3775
+ only one of ten palettes x two themes under AA (MOTIR-3664). A lighter red
3776
+ needs darker ink, which is what amber / candy / citrine / evergreen / garnet
3777
+ / sienna already do in their own dark blocks, each with the palette's own
3778
+ near-black. Spectrum's is the deep cool-violet canvas above: white -> #14111f
3779
+ takes the pairing 3.40 -> 5.46:1, mid-range among those six (5.14-8.81).
3780
+ `--color-destructive` itself does NOT move — it is also the border/glyph hue
3781
+ (5.03-5.68:1 on every dark surface here) and the source of
3782
+ --el-priority-highest / --el-type-bug / --el-type-deploy / --el-overdue. */
3783
+ --color-destructive-foreground: #14111f;
3220
3784
  --color-info: #6fa8ff;
3221
3785
 
3222
3786
  --color-link: #8fb8ff;
@@ -3224,6 +3788,15 @@
3224
3788
 
3225
3789
  /* sidebar hover — one shade lighter than the dark cool surface */
3226
3790
  --el-sidebar-item-bg-hover: #261f3d;
3791
+
3792
+ /* Priority ramp — the dark half of the light block's amber step (MOTIR-2107).
3793
+ UNLIKE cobalt and graphite (whose dark hues were already far enough apart to
3794
+ re-assert the source here), Spectrum's dark destructive #f0555f and warning
3795
+ #fb8b4c render a chip only ΔE 4.8 apart, so dark carries its own amber too:
3796
+ the brighter #fbab4c the other dark palettes already use, which reads as an
3797
+ icon on the violet canvas. Source separation from highest ΔE 33.9, rendered
3798
+ chip ΔE 8.1; contrast 9.8:1 card / 9.0:1 surface. */
3799
+ --el-priority-high: #fbab4c; /* on-dark amber — the ramp's own step */
3227
3800
  }
3228
3801
 
3229
3802
  /* ── Palette: Amber (data-palette="amber") — Subtask 8.8.34 (MOTIR-1252) ─────
@@ -3251,7 +3824,7 @@
3251
3824
  [data-palette='amber'] {
3252
3825
  /* brand & primary — Binance Gold. The FILL carries DARK ink labels (yellow
3253
3826
  trap); --color-primary is a darkened gold used AS text/icon on a surface. */
3254
- --color-primary: #8a6a00; /* darkened gold as text/icon (AA ~5:1 on white) */
3827
+ --color-primary: #7d6000; /* darkened gold as text/icon (5.92:1 on white — MOTIR-3774) */
3255
3828
  --color-primary-foreground: #181a20; /* Binance near-black on the gold fill (AA ~9.6:1) */
3256
3829
  --color-primary-pressed: #d4a30a;
3257
3830
  --color-primary-fill: #f0b90b; /* Binance Gold — the signature; dark-on AA */
@@ -3261,6 +3834,7 @@
3261
3834
  --color-foreground: #181a20; /* Binance near-black ink */
3262
3835
  --color-surface: #f4f5f7; /* cool graphite section background */
3263
3836
  --color-surface-soft: #fafbfc; /* quieter cool division */
3837
+ --color-canvas: #eaecf0; /* recessed planning board — a graphite step under --color-surface */
3264
3838
 
3265
3839
  /* text scale — Binance cool-grey hierarchy */
3266
3840
  --color-ink: #181a20;
@@ -3285,7 +3859,10 @@
3285
3859
  --color-accent-green: #0a8f57; /* darkened Binance buy-green for light AA */
3286
3860
 
3287
3861
  /* card tints — gold-leaning warm washes + cool feature hues */
3288
- --color-tint-peach: #fbe7c2;
3862
+ /* peach leans ORANGE, not gold: gold-leaning every warm tint had collapsed
3863
+ peach onto the Block-gold yellow at ΔE2000 4.3 light / 1.7 dark — the two
3864
+ most-used washes reading as one chip (MOTIR-2085). */
3865
+ --color-tint-peach: #fbdfc0;
3289
3866
  --color-tint-rose: #f7dde2;
3290
3867
  --color-tint-mint: #d6ecdf;
3291
3868
  --color-tint-lavender: #e2e4ee;
@@ -3305,6 +3882,11 @@
3305
3882
 
3306
3883
  /* sidebar hover — one shade darker than the cool surface */
3307
3884
  --el-sidebar-item-bg-hover: #eceef1;
3885
+ /* implemented (MOTIR-3003) — Amber's --color-accent sits beside its brand
3886
+ amber, i.e. next to --el-status-in-review. Its red is unspent by any status
3887
+ in this palette (blocked rides the amber), so it takes it: ΔE 21.2 light /
3888
+ 33.7 dark from the nearest status. */
3889
+ --el-status-implemented: var(--color-destructive);
3308
3890
  }
3309
3891
 
3310
3892
  /* Amber — dark companion: the AUTHENTIC Binance trading floor. Cool near-black
@@ -3316,6 +3898,7 @@
3316
3898
  --color-foreground: #eaecef;
3317
3899
  --color-surface: #1e2329; /* Binance "Shark" card surface */
3318
3900
  --color-surface-soft: #181a20; /* Binance app background — quieter division */
3901
+ --color-canvas: #07090c; /* recessed planning board — a step under the trading-floor black */
3319
3902
 
3320
3903
  --color-ink: #eaecef;
3321
3904
  --color-charcoal: #d1d6dd;
@@ -3338,7 +3921,8 @@
3338
3921
  --color-primary-fill: #fcd535;
3339
3922
 
3340
3923
  /* tints dimmed for the trading-floor black */
3341
- --color-tint-peach: #2f2814;
3924
+ /* the dark twin of the light orange wash — see the light block's note. */
3925
+ --color-tint-peach: #331f10;
3342
3926
  --color-tint-rose: #2f1a22;
3343
3927
  --color-tint-mint: #102a1e;
3344
3928
  --color-tint-lavender: #1c2030;
@@ -3361,6 +3945,9 @@
3361
3945
 
3362
3946
  /* sidebar hover — one shade lighter than the dark "Shark" surface */
3363
3947
  --el-sidebar-item-bg-hover: #262d35;
3948
+ /* implemented — the dark half of the light override above; declared in BOTH
3949
+ themes so the light value cannot cascade into dark. */
3950
+ --el-status-implemented: var(--color-destructive);
3364
3951
  }
3365
3952
 
3366
3953
  /* ── Palette: Sienna (data-palette="sienna") — Subtask 8.8.34 (MOTIR-1252) ───
@@ -3383,7 +3970,7 @@
3383
3970
  [data-palette='sienna'] {
3384
3971
  /* brand & primary — Mistral's flame. The FILL is Mistral Orange #fa520f with
3385
3972
  DARK ink labels; --color-primary is a darkened flame used AS text/icon. */
3386
- --color-primary: #c4400a; /* darkened Mistral flame as text/icon (AA ~4.9:1 on the cream) */
3973
+ --color-primary: #b53b09; /* darkened Mistral flame as text/icon (5.60:1 on the cream — MOTIR-3774) */
3387
3974
  --color-primary-foreground: #2a1205; /* DARK warm ink on the flame fill (AA ~5.3:1) */
3388
3975
  --color-primary-pressed: #cc3a05; /* Mistral primary-deep */
3389
3976
  --color-primary-fill: #fa520f; /* Mistral Orange — the signature flame; dark-on AA */
@@ -3393,6 +3980,7 @@
3393
3980
  --color-foreground: #1f1f1f; /* Mistral Black ink */
3394
3981
  --color-surface: #fbf1d3; /* warm cream section background (Mistral cream ramp) */
3395
3982
  --color-surface-soft: #fdf6e0; /* quieter warm cream division */
3983
+ --color-canvas: #f5e9cb; /* recessed planning board — a warm-cream step under --color-surface */
3396
3984
 
3397
3985
  /* text scale — Mistral warm-charcoal hierarchy */
3398
3986
  --color-ink: #1f1f1f;
@@ -3421,7 +4009,11 @@
3421
4009
  --color-tint-rose: #fbdfd2;
3422
4010
  --color-tint-mint: #e3ead0;
3423
4011
  --color-tint-lavender: #ece6d6;
3424
- --color-tint-sky: #e6ebda;
4012
+ /* sky is a muted wash of the palette's OWN teal accent (#0e8f86), not another
4013
+ olive: warming every cool tint had collapsed sky onto mint at ΔE2000 3.4
4014
+ light / 1.3 dark — one colour doing two jobs in a six-chip ramp (MOTIR-2085).
4015
+ Still Mediterranean, just the sea rather than the hillside. */
4016
+ --color-tint-sky: #d9e8e4;
3425
4017
  --color-tint-yellow: #ffe295; /* Mistral Block Gold */
3426
4018
 
3427
4019
  /* semantic — Mistral documents none; added warm-consistent + AA-safe */
@@ -3437,6 +4029,20 @@
3437
4029
 
3438
4030
  /* sidebar hover — one shade deeper than the cream surface */
3439
4031
  --el-sidebar-item-bg-hover: #f5e9c4;
4032
+
4033
+ /* Status ramp — blocked vs in_review (MOTIR-2075). Mistral ships NO semantic
4034
+ colours, so this palette's warning was added warm-consistent (#b45309) —
4035
+ which lands it next to the darkened Mistral flame --color-primary #c4400a:
4036
+ ΔE2000 7.1, amber against flame. Rather than move the brand flame, blocked
4037
+ takes the palette's own amber accent step, which reads as a warning without
4038
+ being the primary. ΔE 14.2; contrast 3.8:1 surface / 4.1:1 card. */
4039
+ --el-status-blocked: var(--color-accent);
4040
+
4041
+ /* implemented (MOTIR-3003) — Sienna re-maps --el-status-blocked onto
4042
+ --color-accent (directly above), so the base pink would collide with it
4043
+ here. The ink step is free: ΔE 21.1 light / 22.6 dark from the nearest
4044
+ status. */
4045
+ --el-status-implemented: var(--color-charcoal);
3440
4046
  }
3441
4047
 
3442
4048
  /* Sienna — dark companion. Mistral is light-dominant (few dark tokens: Mistral
@@ -3447,6 +4053,7 @@
3447
4053
  --color-foreground: #f5efe2;
3448
4054
  --color-surface: #1f1d17;
3449
4055
  --color-surface-soft: #1a1712;
4056
+ --color-canvas: #100d08; /* recessed planning board — a step under the warm near-black page */
3450
4057
 
3451
4058
  --color-ink: #f5efe2;
3452
4059
  --color-charcoal: #e6ddcb;
@@ -3471,7 +4078,8 @@
3471
4078
  --color-tint-rose: #321c16;
3472
4079
  --color-tint-mint: #23271a;
3473
4080
  --color-tint-lavender: #272318;
3474
- --color-tint-sky: #22251a;
4081
+ /* the dark twin of the light teal wash — see the light block's note. */
4082
+ --color-tint-sky: #1b2622;
3475
4083
  --color-tint-yellow: #332b12;
3476
4084
 
3477
4085
  --color-accent: #ffa110; /* Mistral Sunshine-700 amber on dark */
@@ -3490,6 +4098,14 @@
3490
4098
 
3491
4099
  /* sidebar hover — one shade lighter than the warm-dark surface */
3492
4100
  --el-sidebar-item-bg-hover: #292619;
4101
+
4102
+ /* Dark's amber and flame are already ΔE 11.9 apart. Re-asserted to pair the
4103
+ light override above — see the cascade note in cobalt/dark. */
4104
+ --el-status-blocked: var(--color-warning);
4105
+ /* implemented — the dark half of the light override above; declared in BOTH
4106
+ themes so the light value cannot cascade into dark (this palette's
4107
+ --el-status-blocked note above is the same pairing). */
4108
+ --el-status-implemented: var(--color-charcoal);
3493
4109
  }
3494
4110
 
3495
4111
  /* ── Palette: Garnet (data-palette="garnet") — Subtask 8.8.34 (MOTIR-1252) ───
@@ -3517,6 +4133,7 @@
3517
4133
  --color-foreground: #111111; /* Pinterest "Cod Gray" ink */
3518
4134
  --color-surface: #efefef; /* Pinterest light-gray section background */
3519
4135
  --color-surface-soft: #f7f7f7; /* quieter neutral division */
4136
+ --color-canvas: #e7e7e7; /* recessed planning board — a neutral step under --color-surface */
3520
4137
 
3521
4138
  /* text scale — Pinterest neutral-grey hierarchy */
3522
4139
  --color-ink: #111111;
@@ -3561,6 +4178,14 @@
3561
4178
 
3562
4179
  /* sidebar hover — one shade darker than the neutral surface */
3563
4180
  --el-sidebar-item-bg-hover: #e6e6e6;
4181
+
4182
+ /* Status ramp — todo vs cancelled (MOTIR-2075). Pinterest's neutrals are
4183
+ true-neutral greys and its documented ramp puts --color-stone #767676 and
4184
+ --color-steel #5f5f5f close together: ΔE2000 8.8, where the base keeps the
4185
+ pair ~12 apart. cancelled drops to Pinterest's next documented grey,
4186
+ #444444 (the palette's own --color-slate). ΔE 18.1; contrast 8.5:1 surface
4187
+ / 9.7:1 card. */
4188
+ --el-status-cancelled: #444444; /* Pinterest grey — one documented step deeper */
3564
4189
  }
3565
4190
 
3566
4191
  /* Garnet — dark companion. Pinterest's Cod-Gray near-black canvas; the red
@@ -3571,6 +4196,7 @@
3571
4196
  --color-foreground: #efefef;
3572
4197
  --color-surface: #1a1a1a;
3573
4198
  --color-surface-soft: #161616;
4199
+ --color-canvas: #090909; /* recessed planning board — a step under the Cod-Gray page */
3574
4200
 
3575
4201
  --color-ink: #efefef;
3576
4202
  --color-charcoal: #e0e0e0;
@@ -3615,6 +4241,10 @@
3615
4241
 
3616
4242
  /* sidebar hover — one shade lighter than the dark surface */
3617
4243
  --el-sidebar-item-bg-hover: #232323;
4244
+
4245
+ /* Dark's greys are already ΔE 11.6 apart. Re-asserted to pair the light
4246
+ override above — see the cascade note in cobalt/dark. */
4247
+ --el-status-cancelled: var(--color-steel);
3618
4248
  }
3619
4249
 
3620
4250
  /* ── Palette: Citrine (data-palette="citrine") — Subtask 8.8.34 (MOTIR-1252) ──
@@ -3651,6 +4281,7 @@
3651
4281
  --color-foreground: #050038; /* Miro Stratos navy ink */
3652
4282
  --color-surface: #f3f4f6; /* Mirotone gray-100 section background */
3653
4283
  --color-surface-soft: #f6f7f9; /* Mirotone gray-50 — quieter division */
4284
+ --color-canvas: #ebecf0; /* recessed planning board — Mirotone gray-150, a step under gray-100 */
3654
4285
 
3655
4286
  /* text scale — Mirotone gray ramp */
3656
4287
  --color-ink: #050038;
@@ -3696,6 +4327,37 @@
3696
4327
 
3697
4328
  /* sidebar hover — Mirotone gray-150, one step off the surface */
3698
4329
  --el-sidebar-item-bg-hover: #ebecf0;
4330
+
4331
+ /* Status ramp (MOTIR-2075) — two collisions, both from Mirotone's ramps.
4332
+ blocked vs in_review: --color-warning is yellow-650 #91771e and
4333
+ --color-primary is yellow-700 #746019 — ADJACENT steps of one gold ramp,
4334
+ ΔE2000 9.7. Miro is a 5-hue system with NO orange ramp (see the block
4335
+ header), and the golds that clear AA on a light surface are exactly those
4336
+ two, so the gold cannot separate itself. The status ramp therefore takes
4337
+ Miro's OTHER brand hue at its own depth — the action blue, blue-650 — which
4338
+ leaves blocked on the warning gold where its semantics belong. Note
4339
+ in_progress rides blue-500 #3859ff: same family, two depths, ΔE 17.0 apart,
4340
+ the Graphite idiom. Contrast 9.2:1 surface / 10.2:1 card.
4341
+ todo vs cancelled: gray-450 #7f8497 vs gray-500 #656b81, ΔE 10.0 — one step
4342
+ apart on Mirotone's gray ramp. cancelled drops to gray-600. ΔE 20.6;
4343
+ contrast 7.5:1 / 8.3:1. */
4344
+ --el-status-in-review: #243797; /* Mirotone blue-650 — deep action blue */
4345
+ /* Status ramp — planning vs the two blues (MOTIR-2427, the MOTIR-2073 class).
4346
+ Citrine is a deliberate 5-hue Miro palette and it re-skins
4347
+ `--color-accent-teal` to Mirotone BLUE, so the base teal source lands on
4348
+ top of in_progress here. Mirotone red-500 is the only remaining documented
4349
+ family: `Planning` means the card was found WRONG and is being re-planned,
4350
+ which is the one status where an attention hue is honest — and `cancelled`
4351
+ stays deliberately grey, so red is not overloaded. */
4352
+ --el-status-planning: #d8182c; /* Mirotone red-500 — the attention family */
4353
+ --el-status-cancelled: #4a4e5e; /* Mirotone gray-600 — one step deeper */
4354
+ /* implemented (MOTIR-3003) — Citrine spends --color-accent on a hue its
4355
+ --el-status-in-progress is already in the family of, and its red is
4356
+ --el-status-planning. The ink step is what is left: ΔE 17.8 light / 10.0
4357
+ dark. ⚠️ The dark figure sits EXACTLY on the ΔE 10 floor, against
4358
+ in_review — a palette change that moves either one needs re-measuring
4359
+ rather than nudging. */
4360
+ --el-status-implemented: var(--color-charcoal);
3699
4361
  }
3700
4362
 
3701
4363
  /* Citrine — dark companion. A Mirotone gray-ramp near-black canvas (black
@@ -3707,6 +4369,7 @@
3707
4369
  --color-foreground: #f6f7f9; /* gray-50 */
3708
4370
  --color-surface: #1a1b1e; /* gray-900 */
3709
4371
  --color-surface-soft: #18191c; /* gray-950 */
4372
+ --color-canvas: #040404; /* recessed planning board — a step under Miro black */
3710
4373
 
3711
4374
  --color-ink: #f6f7f9;
3712
4375
  --color-charcoal: #ebecf0; /* gray-150 */
@@ -3751,6 +4414,26 @@
3751
4414
 
3752
4415
  /* sidebar hover — Mirotone gray-800, one step lighter than the surface */
3753
4416
  --el-sidebar-item-bg-hover: #24262c;
4417
+
4418
+ /* Status ramp (MOTIR-2075) — the worst collision in the whole set:
4419
+ blocked #ffd850 against in_review #ffd02f is ΔE2000 3.0. Two Sunglow golds
4420
+ one hex apart; no one can tell them apart on a 10px dot. Same fix as light,
4421
+ inverted — the action blue at its PALE step, Mirotone blue-200, rather than
4422
+ the deep blue-650 (which sits at 1.7:1 on this near-black surface and would
4423
+ fail the 3:1 icon bar). Separation from in_progress (blue-400 #7a90fe)
4424
+ ΔE 18.4; contrast 13.1:1 surface / 15.1:1 card.
4425
+ cancelled is unchanged here (ΔE 17.8 from todo) and re-asserted only to
4426
+ pair the light block's override — see the cascade note in cobalt/dark. */
4427
+ --el-status-in-review: #d9dffc; /* Mirotone blue-200 — pale action blue */
4428
+ /* The dark twin of the planning override above — declared in BOTH themes even
4429
+ though it is the same family, because an unpaired light override loses to
4430
+ the later palette block (the cascade trap). Brightened one step, as this
4431
+ palette does with every semantic on dark. */
4432
+ --el-status-planning: #ff6b78; /* Mirotone red-400 — brightened for dark */
4433
+ --el-status-cancelled: var(--color-steel);
4434
+ /* implemented — the dark half of the light override above; declared in BOTH
4435
+ themes so the light value cannot cascade into dark. */
4436
+ --el-status-implemented: var(--color-charcoal);
3754
4437
  }
3755
4438
 
3756
4439
  /* ── Palette: Candy (data-palette="candy") — Subtask 8.8.34 (MOTIR-1252) ──────
@@ -3774,7 +4457,7 @@
3774
4457
  [data-palette='candy'] {
3775
4458
  /* brand & primary — a LIGHT bubblegum pink (Radix Pink). FILL is a light pink
3776
4459
  with DARK berry labels; --color-primary is the Pink-11 text step. */
3777
- --color-primary: #c2298a; /* Radix Pink-11 pink text/icon (AA ~5.2:1 on white) */
4460
+ --color-primary: #b72682; /* Radix Pink-11 88% toward Pink-12 (5.81:1 on white — MOTIR-3774) */
3778
4461
  --color-primary-foreground: #651249; /* Radix Pink-12 berry ink on the light-pink fill (~7.6:1) */
3779
4462
  --color-primary-pressed: #e7acd0; /* Radix Pink-7 */
3780
4463
  --color-primary-fill: #efbfdd; /* Radix Pink-6 — the light bubblegum pink */
@@ -3784,6 +4467,7 @@
3784
4467
  --color-foreground: #211f26; /* Radix Mauve-12 ink */
3785
4468
  --color-surface: #fee9f5; /* Radix Pink-3 — candy-pink section */
3786
4469
  --color-surface-soft: #fef7fb; /* Radix Pink-2 — quieter candy wash */
4470
+ --color-canvas: #fbdcef; /* recessed planning board — Radix Pink-4, a step under the Pink-3 surface */
3787
4471
 
3788
4472
  /* text scale — Radix Mauve neutral + a Pink-12 berry emphasis */
3789
4473
  --color-ink: #211f26;
@@ -3822,12 +4506,21 @@
3822
4506
  --color-destructive-foreground: #ffffff;
3823
4507
  --color-info: #00749e; /* Sky-11 */
3824
4508
 
4509
+ /* Status ramp — planning, the LIGHT twin of the dark override below (the
4510
+ cascade trap: an unpaired light override loses to the later palette block).
4511
+ Same documented Plum family, at the light scale step. */
4512
+ --el-status-planning: #ab4aba; /* Plum-9 — the documented berry accent */
4513
+
3825
4514
  /* links — Radix Sky-11 candy blue, distinct from the pink primary */
3826
4515
  --color-link: #00749e;
3827
4516
  --color-link-pressed: #1d3e56; /* Sky-12 */
3828
4517
 
3829
4518
  /* sidebar hover — one shade deeper than the candy-pink surface (Pink-4) */
3830
4519
  --el-sidebar-item-bg-hover: #fbdcef;
4520
+ /* implemented (MOTIR-3003) — Candy's --color-accent is the berry family its
4521
+ --el-status-planning already occupies (ΔE 0.0 with the base value). The ink
4522
+ step is free: ΔE 20.0 light / 16.2 dark from the nearest status. */
4523
+ --el-status-implemented: var(--color-charcoal);
3831
4524
  }
3832
4525
 
3833
4526
  /* Candy — dark companion. A pink-tinted near-black canvas (Radix Pink dark
@@ -3839,6 +4532,7 @@
3839
4532
  --color-foreground: #eeeef0; /* Mauve dark-12 */
3840
4533
  --color-surface: #37172f; /* Pink dark-3 — candy-plum section */
3841
4534
  --color-surface-soft: #21121d; /* Pink dark-2 — quieter division */
4535
+ --color-canvas: #120c11; /* recessed planning board — a step under the Pink dark-1 page */
3842
4536
 
3843
4537
  --color-ink: #eeeef0;
3844
4538
  --color-charcoal: #fdd1ea; /* Pink dark-12 — pink emphasis / AA on dark tints */
@@ -3878,11 +4572,22 @@
3878
4572
  --color-destructive-foreground: #191114; /* dark ink on the bright danger */
3879
4573
  --color-info: #75c7f0; /* Sky dark-11 */
3880
4574
 
4575
+ /* Status ramp — planning vs done (MOTIR-2427, the MOTIR-2073 class). Candy's
4576
+ `--color-accent-teal` is Jade dark-11 (#1fd8a4) and its `--color-success` is
4577
+ Grass dark-11 (#71d083): two bright dark-mode greens ΔE2000 9.2 apart, which
4578
+ is one dot at a glance. The ramp takes the palette's own documented BERRY
4579
+ instead of a fourth green — Plum, which candy.md names as its accent family
4580
+ and which nothing else in the status ramp uses. */
4581
+ --el-status-planning: #ab4aba; /* Plum-9 — the documented berry accent */
4582
+
3881
4583
  --color-link: #75c7f0;
3882
4584
  --color-link-pressed: #c2f3ff; /* Sky dark-12 */
3883
4585
 
3884
4586
  /* sidebar hover — one shade lighter than the candy-plum surface (Pink dark-4) */
3885
4587
  --el-sidebar-item-bg-hover: #4b143d;
4588
+ /* implemented — the dark half of the light override above; declared in BOTH
4589
+ themes so the light value cannot cascade into dark. */
4590
+ --el-status-implemented: var(--color-charcoal);
3886
4591
  }
3887
4592
 
3888
4593
  [data-type='grotesk'] {