@motir/design-system 0.1.0 → 0.1.2

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,41 @@
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).
2514
+ ⚠️ FIVE palettes re-map --color-accent onto something a status already uses,
2515
+ so each carries its own override below — the same per-palette follow-up
2516
+ --el-status-planning needed in citrine and candy (MOTIR-2425). Measured by
2517
+ tests/theme/statusHueSeparation.test.ts, which now folds a color-mix rather
2518
+ than throwing on one.
2519
+ ⚠️ STEPPED TOWARD THE INK (MOTIR-3954). The bare --color-accent was 2.66:1 on
2520
+ --el-card and 2.44:1 on --el-surface in the BASE palette's LIGHT theme —
2521
+ under 1.4.11's 3:1 for a graphical object, and the one palette x theme in
2522
+ which this token missed the bar. A saturated pink at Radix-9 lightness has
2523
+ nowhere to go on white, so the recipe takes one step of the palette's own
2524
+ emphasis ink instead of a new hue: #cd58a2, 3.81 / 3.50 / 3.81 on
2525
+ card / surface / page-bg. --color-charcoal flips with the theme (#37352f
2526
+ light, #e5e5e5 dark), so the same expression DARKENS on white and LIGHTENS
2527
+ on near-black — dark goes 7.22 -> 8.42:1. Both inputs stay palette-tuned, so
2528
+ the four palettes that ride this base value (cobalt, evergreen, spectrum,
2529
+ garnet) re-skin it as before and each GAINS contrast; ΔE2000 to the nearest
2530
+ sibling stays >= 13.7 in all ten palettes x both themes (27.1 / 26.8 here).
2531
+ Why the base and not a [data-palette='motir'] block: <html> carries no
2532
+ data-appearance-scope, so a root palette block would be a second cascade
2533
+ nobody renders — the base layer IS how the browser reaches these values. */
2534
+ --el-status-implemented: color-mix(in srgb, var(--color-accent) 75%, var(--color-charcoal));
2291
2535
  --el-status-in-review: var(--color-primary); /* brand — differentiate from in-progress */
2292
2536
  --el-status-done: var(--color-success); /* green — resolved */
2293
2537
  --el-status-blocked: var(--color-warning); /* amber — can't proceed (gap fixed) */
@@ -2341,6 +2585,9 @@
2341
2585
  --el-role-admin: var(--color-tint-lavender);
2342
2586
  --el-role-member: var(--color-tint-sky);
2343
2587
  --el-role-viewer: var(--color-tint-mint);
2588
+ /* A project's OWN role (MOTIR-2485) — peach, the next free slot: the members
2589
+ surface had already spent lavender / sky / mint on the three built-ins. */
2590
+ --el-role-custom: var(--color-tint-peach);
2344
2591
  --el-org-role-owner: var(--color-tint-lavender);
2345
2592
  --el-org-role-admin: var(--color-tint-sky);
2346
2593
  --el-org-role-member: var(--color-tint-mint);
@@ -2374,6 +2621,13 @@
2374
2621
  --el-notif-commented: var(--color-info); /* was --el-type-task */
2375
2622
  --el-notif-assigned: var(--color-accent-green); /* was --el-type-story */
2376
2623
  --el-notif-transitioned: var(--color-accent-teal); /* was --el-type-subtask */
2624
+ /* The EXECUTOR badge on a Home row's assignee avatar (Story MOTIR-2649 ·
2625
+ Subtask MOTIR-2653, per design/home/design-notes.md §"The agent state").
2626
+ Same mapping form as --el-notif-mentioned above and for the same reason:
2627
+ it resolves to the accent fill today, but owning its own slot means a
2628
+ palette can move agent badges without dragging the CTA with them. The
2629
+ third pillar becoming visible is worth a token of its own. */
2630
+ --el-executor-agent: var(--color-primary-fill);
2377
2631
  /* AI model dots (promote to a named family; deepseek was borrowing --el-type-subtask).
2378
2632
  opus keeps the accent fill, same --color-primary-fill reasoning as mentioned. */
2379
2633
  --el-model-opus: var(--color-primary-fill); /* the accent fill (matches prior --el-accent) */
@@ -2427,6 +2681,33 @@
2427
2681
  --el-card: var(--color-background); /* untinted card surface — fixes the bg-card orphan */
2428
2682
  --el-input-border: var(--color-hairline-strong); /* input outline (was --el-border-strong) */
2429
2683
  --el-button-border: var(--color-hairline-strong); /* secondary-button outline */
2684
+
2685
+ /* ── Non-editable field states (MOTIR-2495) ──
2686
+ A field that cannot be typed into used to be drawn with `opacity-50` on the
2687
+ whole wrapper, which composites its CHILDREN — value, placeholder, and the
2688
+ `addonStart`/`addonEnd` affixes — against the page and roughly halves
2689
+ whatever contrast the palette guaranteed them. Contrast is a property of a
2690
+ PAIR (theme.css's own colour rule); opacity quietly makes it a property of
2691
+ a triple, so no ink choice can be checked and none can be fixed: the org
2692
+ URL's affix was already `--el-text-secondary`, the darkest caption ink, and
2693
+ still measured below AA. These four tokens express the two states as
2694
+ COLOUR instead, so every ink over them is AA-checkable and palette-swappable.
2695
+ Measured on the base palette, light (the binding theme; dark clears by more):
2696
+ disabled value/affix --color-slate on --color-muted = 6.18:1 ✓
2697
+ read-only value --color-foreground on --color-surface = 15.98:1 ✓
2698
+ read-only affix --color-slate on --color-surface = 6.24:1 ✓
2699
+ The DISABLED border is deliberately softer than the enabled one — the state
2700
+ reads through fill + ink + edge together, so it survives a surface-material
2701
+ style (glassmorphism repaints [data-surface='input'], and its rule outranks
2702
+ a utility class) that erases the fill alone. */
2703
+ --el-input-disabled-bg: var(--color-muted); /* disabled field fill (replaces opacity-50) */
2704
+ --el-input-disabled-border: var(--color-hairline); /* disabled field edge — softer than enabled */
2705
+ --el-input-disabled-text: var(
2706
+ --color-slate
2707
+ ); /* disabled value/placeholder/affix ink — AA on the fill above */
2708
+ --el-input-readonly-bg: var(
2709
+ --color-surface
2710
+ ); /* read-only field fill — not editable, but focusable + full ink */
2430
2711
  --el-count-bg: var(--color-muted); /* count-badge fill (shipped value; see note above) */
2431
2712
  --el-count-text: var(--color-slate); /* count-badge ink (was --el-text-secondary) */
2432
2713
 
@@ -2460,6 +2741,21 @@
2460
2741
  --el-type-manual: var(--color-stone); /* stone — human provisioning */
2461
2742
  --el-type-chore: var(--color-slate); /* slate — housekeeping */
2462
2743
 
2744
+ /* The four members ADR Amendment 1 (MOTIR-2629) admitted, per MOTIR-2631's
2745
+ design (design/work-items/design-notes.md, "Q2"). The block above spent
2746
+ ALL SEVEN mutually distinct saturated hues and all three greys at ten
2747
+ members, and there is no eighth — inventing one is forbidden. So each
2748
+ newcomer POINTS AT the Tier-0 hue of the member the amendment named as its
2749
+ NEAREST NEIGHBOUR, and the GLYPH carries the difference (the chip renders
2750
+ its label too, so hue was never the sole carrier — finding #35).
2751
+ Each keeps its OWN token so a future palette can split them without
2752
+ touching a component, and so WORK_ITEM_TYPE_META's per-member `hueVar`
2753
+ shape holds. Hue names the FAMILY; the glyph names the member. */
2754
+ --el-type-copy: var(--color-accent-teal); /* teal — with content (Author) */
2755
+ --el-type-translate: var(--color-accent-teal); /* teal — with content (Author) */
2756
+ --el-type-verification: var(--color-accent-orange); /* orange — with review (Investigate) */
2757
+ --el-type-legal: var(--color-charcoal); /* ink — with decision (Govern) */
2758
+
2463
2759
  /* ── Chart series (Story 4.6 · the reusable viz Story 6.3 reuses) ──
2464
2760
  Every chart colour routes through one of these, NEVER a raw --color-*
2465
2761
  in chart code (the per-component growth pattern). They map to Tier-0
@@ -2537,8 +2833,14 @@
2537
2833
  --el-diff-moved: var(--color-tint-sky);
2538
2834
  --el-chat-bubble-user: var(--color-primary-fill); /* text stays --el-accent-text */
2539
2835
  --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 */
2836
+ /* Canvas dependency edges ride the RECESSED --el-canvas, not a raised surface, so
2837
+ they use INK tokens (--color-slate / -charcoal), NOT the hairline-on-a-card
2838
+ --color-border / -hairline-strong which are near-invisible there (~1.0-1.9:1 vs
2839
+ the canvas in every palette — MOTIR-1564). Ink clears WCAG 1.4.11 (≥3:1) on the
2840
+ canvas across all palettes/themes while still re-tinting per palette; solid vs
2841
+ dashed + the arrowhead marker carry the committed/pending distinction. */
2842
+ --el-canvas-edge-pending: var(--color-slate); /* dashed, not-yet-committed edge (quiet ink) */
2843
+ --el-canvas-edge-committed: var(--color-charcoal); /* solid, settled edge (emphasis ink) */
2542
2844
  --el-station-tier-discovery: var(--color-tint-sky);
2543
2845
  --el-station-tier-vision: var(--color-tint-lavender);
2544
2846
  --el-station-tier-feasibility: var(--color-tint-mint);
@@ -2602,6 +2904,53 @@
2602
2904
  one-shade-darker. */
2603
2905
  [data-theme='dark'] {
2604
2906
  --el-sidebar-item-bg-hover: #222222;
2907
+ /* THE DARK ACCENT INK (MOTIR-3745). Light keeps the plain
2908
+ var(--color-primary) alias declared in the Tier-3 block above, byte for
2909
+ byte — this is a DARK-ONLY lift, and deliberately so: the light accent
2910
+ already clears AA on every surface (6.29:1 on --el-surface-soft in the
2911
+ base palette), so there is nothing there to buy and a re-tint would cost
2912
+ the brand hue for no contrast.
2913
+
2914
+ What it fixes: the dark --color-primary was chosen for AA on the near-black
2915
+ CANVAS (#0f0f0f, 4.67:1 — see the Tier-1 comment at --color-primary, and
2916
+ MOTIR-757) and was never measured against anything else. Every tinted
2917
+ surface in dark sits ABOVE the canvas, so the ink arrives there with less
2918
+ contrast, not more: 4.24:1 on --el-surface / --el-muted (#1a1a1a) and
2919
+ 4.41:1 on --el-surface-soft (#161616), across all sixteen --el-* aliases of
2920
+ those three fills. 13.5px semibold is not WCAG large text, so 1.4.3 asks
2921
+ 4.5:1 and ExploreTopBar's current-page nav item missed it by 0.09 on
2922
+ /explore and /docs — both public, both reachable signed out.
2923
+
2924
+ Why the mix and not a hex: DERIVED from two tokens, exactly like
2925
+ --el-danger-on-surface above, so it re-skins with data-palette and an
2926
+ eleventh palette is covered the day it lands rather than the day somebody
2927
+ remembers a table.
2928
+
2929
+ Why 82%: it is sized against a WIDER set than the guard measures. On the
2930
+ surface vocabulary the accent arm asserts (the page white and the three
2931
+ greys, every alias) the worst dark pair at 82% is 5.53:1. The binding
2932
+ constraint is --el-tint-lavender — the accent family's own tint, which the
2933
+ arm does not measure but which the tree paints accent text on in ten files:
2934
+ 84% is the largest primary share that clears it (4.57:1) and 82% is one
2935
+ 2% step further, for headroom (4.68:1). MOTIR-3774 carries the LIGHT half
2936
+ of that pair, which no dark override can reach. Pulling further still would
2937
+ eventually cost the accent its signal rather than buy contrast, so the
2938
+ distance from --el-text is asserted too (22.6-54.5 deltaE, floor 10).
2939
+
2940
+ Why HERE and not on the Tier-3 alias: --el-accent-on-surface's light
2941
+ declaration is a load-bearing Tier-0 ALIAS. tests/theme/
2942
+ iconTextSurfaceTokens + interactionAgileTokens pin --el-icon-active and
2943
+ --el-tabnav-active as zero-change aliases OF it, and brand-tile-contrast
2944
+ resolves it to a --color-* by name. A dark-only override leaves all three
2945
+ reading the base declaration they were written about, and leaves
2946
+ --color-primary's eleven other Tier-3 consumers (plus --focus-ring-color)
2947
+ where they are — including --el-vote-active-bg, which is a FILL and would
2948
+ have LOST contrast against its white ink. Move the ink, not the fill
2949
+ (MOTIR-3664).
2950
+
2951
+ The table is asserted, not asserted-once: inkContrastLint's accent arm
2952
+ recomputes it from this file over every palette x theme. */
2953
+ --el-accent-on-surface: color-mix(in srgb, var(--color-primary) 82%, var(--el-text));
2605
2954
  /* The modal scrim deepens in dark (MOTIR-1275 · 1266.4): on a near-black
2606
2955
  canvas a 40% scrim barely separates the dialog, so dark uses ~65% — the
2607
2956
  other concrete-hex --el-* alongside the sidebar hover. Light value (40%,
@@ -2609,6 +2958,83 @@
2609
2958
  --el-overlay-scrim: #000000a6;
2610
2959
  }
2611
2960
 
2961
+ /* ═══════════════════════════════════════════════════════════════════════
2962
+ RECIPE KNOBS — the SCALARS a component's own colour recipe reads
2963
+ (MOTIR-3207)
2964
+
2965
+ Not part of the Tier-3 layer above and deliberately not named `--el-*`:
2966
+ that layer is a COLOUR contract (every member aliases a Tier-0 --color-*
2967
+ and is asserted palette-dependent by tests/theme/paletteTokenCoverage).
2968
+ A knob here is a NUMBER inside a color-mix() — one value for every
2969
+ palette and both themes — so it fails both halves of that contract while
2970
+ being exactly the thing that has to be expressed once.
2971
+
2972
+ Declared on a plain `:root` rule rather than in @theme so the property is
2973
+ emitted unconditionally: its only consumer is a `var()` inside a JS style
2974
+ object, which Tailwind's source scan has no reason to keep a theme
2975
+ variable alive for.
2976
+ ═══════════════════════════════════════════════════════════════════════ */
2977
+ :root {
2978
+ /* THE ORB's lit-sphere highlight — how much --el-accent-text is mixed into
2979
+ --el-accent at the first stop of `PlanWithAIFab`'s radial gradient, which
2980
+ is the gradient's LIGHTEST point and therefore where the white glyph over
2981
+ it is closest to disappearing (design/ai-chat/design-notes.md § B).
2982
+
2983
+ 26%, not the 32% that shipped, and ONE number rather than a per-theme
2984
+ pair. At 32% the glyph misses WCAG 1.4.11's 3:1 for a non-text graphic in
2985
+ FOUR of the twenty palette x theme contexts — default dark 2.78:1, cobalt
2986
+ dark 2.86:1, spectrum dark 3.00:1 (2.999, i.e. under the bar) and,
2987
+ contrary to the finding that filed this, evergreen LIGHT at 2.94:1. So
2988
+ "light has headroom" is a property of the DEFAULT PALETTE, not of light:
2989
+ a `[data-theme='dark']` override of this stop would have left evergreen's
2990
+ light orb failing. At 26% the worst context is 3.09:1 and all twenty
2991
+ clear the bar.
2992
+
2993
+ Mixing the glyph's own colour INTO its backdrop is monotonic — a larger
2994
+ number is always less contrast — so this is a CEILING, and lowering it
2995
+ further is always safe for legibility and only ever costs the lit look.
2996
+ tests/theme/orb-glyph-contrast.test.ts measures every context from these
2997
+ tokens and fails if a future palette or a raised knob crosses 3:1. */
2998
+ --orb-lit-mix: 26%;
2999
+ }
3000
+
3001
+ /* ═══════════════════════════════════════════════════════════════════════
3002
+ MOTION FLOOR — the reduced-motion guard the PACKAGE owns (MOTIR-3844)
3003
+
3004
+ Not an axis and not a tier: a floor every consumer of this stylesheet gets
3005
+ whichever palette, style or type it applies. The style axis already gates
3006
+ its OWN motion (the `aurora` canvas drift and the `3d-immersive` tilt /
3007
+ menu unfold, all `prefers-reduced-motion`-gated in their Tier-2 blocks
3008
+ above), because that motion is decorative and belongs to the style that
3009
+ introduced it. `animate-spin` belongs to no style: `Spinner` paints it on
3010
+ ordinary interactive surfaces, and `<Button loading>` substitutes a Spinner
3011
+ for the left icon — so it renders under every axis combination, and nothing
3012
+ guarded it. Tailwind v4 defines `--animate-spin: spin 1s linear infinite`
3013
+ with no media query anywhere near it, and neither consumer's `globals.css`
3014
+ carried an override.
3015
+
3016
+ STOP THE ROTATION, KEEP THE GLYPH. A spinner is not decoration a reader can
3017
+ do without — it is the only signal that a control is working — so this sets
3018
+ `animation` and nothing else: the ring keeps its size, its border and its
3019
+ `role="status"`, and a reader who asked for less motion still sees that
3020
+ something is in flight. (`motir-marketing`'s own `.motir-spin` reaches the
3021
+ same shape by hand, and pairs it with a label change — with the animation
3022
+ stilled, the word is the whole of the state. A consumer whose only motion
3023
+ cue is the rotation should do the same.)
3024
+
3025
+ WHY IT WINS THE CASCADE. `.animate-spin` is emitted by Tailwind inside
3026
+ `@layer utilities`; this rule is UNLAYERED, and an unlayered declaration
3027
+ outranks a layered one whatever their specificity. That is the whole of the
3028
+ mechanism — it does not depend on source order or on this file's position in
3029
+ the `@import` list. Measured, not reasoned: tests/theme/reducedMotionSpinner
3030
+ reads it back off the COMPILED stylesheet for both consumer shapes.
3031
+ ═══════════════════════════════════════════════════════════════════════ */
3032
+ @media (prefers-reduced-motion: reduce) {
3033
+ .animate-spin {
3034
+ animation: none;
3035
+ }
3036
+ }
3037
+
2612
3038
  /* ═══════════════════════════════════════════════════════════════════════
2613
3039
  AXIS 1 (COLOUR) — data-palette overrides (Subtask 7.3.48)
2614
3040
  Registry: lib/theme/palettes.ts. A named palette re-skins the app by
@@ -2631,6 +3057,125 @@
2631
3057
  /* `motir` needs no block — it is the Tier-3 base above. Each later palette
2632
3058
  ADDS its [data-palette='<id>'] override here (light + dark companion). */
2633
3059
 
3060
+ /* ── The `motir` BASE palette, re-asserted for a SCOPED subtree (MOTIR-3933) ──
3061
+ `motir` is the Tier-0 default, so it historically had NO block: the base
3062
+ values simply were the palette. That is correct for `<html>` and wrong for a
3063
+ NESTED scope, which inherits its ancestor's palette and has nothing to
3064
+ override it with — a `motir` preview under a `graphite` document rendered as
3065
+ graphite. Every other palette re-asserts, so the base must too.
3066
+
3067
+ ⚠️ SCOPED BY `[data-appearance-scope]`, DELIBERATELY. `<html>` never carries
3068
+ that attribute, so this block matches only a nested preview and the ROOT
3069
+ cascade is byte-identical to before. It also keeps the block from reading as
3070
+ a palette OVERRIDE: several `tests/theme/*` guards DERIVE "which palettes
3071
+ override this token" from the stylesheet, and a bare `[data-palette='motir']`
3072
+ block silently enrolled the BASE palette in bars written for overrides. It is
3073
+ not an override — it is a scope reset — and the selector now says so.
3074
+
3075
+ These values are COPIES, and the duplication is guarded rather than trusted:
3076
+ `tests/theme/scopedPreviewIsolation.test.ts` asserts each declaration here
3077
+ equals the value the base layer declares for that token, so a change to
3078
+ `@theme` that is not mirrored here fails rather than drifts. The token SET is
3079
+ the union of what any `[data-palette]` block overrides — a new palette that
3080
+ overrides a token absent here fails the same test.
3081
+ ⚠️ The path above was `test/paletteScopeIsolation.test.tsx` until MOTIR-3954,
3082
+ which is a file that has never existed, and the VALUE assertion it promised
3083
+ had never been written either — only the token-SET one had. Both are true
3084
+ now. A citation to a guard is worth exactly as much as the guard. */
3085
+ [data-appearance-scope][data-palette='motir'] {
3086
+ --color-accent: #ff64c8;
3087
+ --color-accent-green: #1aae39;
3088
+ --color-accent-orange: #dd5b00;
3089
+ --color-accent-teal: #2a9d99;
3090
+ --color-background: #ffffff;
3091
+ --color-border: #e5e3df;
3092
+ --color-canvas: #ecebe7;
3093
+ --color-charcoal: #37352f;
3094
+ --color-destructive: #e03131;
3095
+ --color-destructive-foreground: #ffffff;
3096
+ --color-foreground: #1a1a1a;
3097
+ --color-hairline: #e5e3df;
3098
+ --color-hairline-soft: #ede9e4;
3099
+ --color-hairline-strong: #c8c4be;
3100
+ --color-info: #0075de;
3101
+ --color-ink: #1a1a1a;
3102
+ --color-link: #0070d2;
3103
+ --color-link-pressed: #005bab;
3104
+ --color-muted: #f3f4f6;
3105
+ --color-muted-foreground: #787671;
3106
+ --color-primary: #5645d4;
3107
+ --color-primary-fill: #5645d4;
3108
+ --color-primary-foreground: #ffffff;
3109
+ --color-primary-pressed: #4534b3;
3110
+ --color-slate: #5d5b54;
3111
+ --color-steel: #787671;
3112
+ --color-stone: #a4a097;
3113
+ --color-success: #1aae39;
3114
+ --color-surface: #f6f5f4;
3115
+ --color-surface-soft: #fafaf9;
3116
+ --color-tint-lavender: #e6e0f5;
3117
+ --color-tint-mint: #d9f3e1;
3118
+ --color-tint-peach: #ffe8d4;
3119
+ --color-tint-rose: #fde0ec;
3120
+ --color-tint-sky: #dcecfa;
3121
+ --color-tint-yellow: #fef7d6;
3122
+ --color-warning: #dd5b00;
3123
+ --el-priority-high: var(--color-warning);
3124
+ --el-sidebar-item-bg-hover: #eeede9;
3125
+ --el-status-blocked: var(--color-warning);
3126
+ --el-status-cancelled: var(--color-steel);
3127
+ --el-status-implemented: color-mix(in srgb, var(--color-accent) 75%, var(--color-charcoal));
3128
+ --el-status-in-review: var(--color-primary);
3129
+ --el-status-planning: var(--color-accent-teal);
3130
+ }
3131
+
3132
+ /* The base palette's DARK arm — only the tokens the global dark block
3133
+ overrides; the rest keep the light block's values above. Two selectors for
3134
+ the same reason every other palette now has two: the compound one for
3135
+ `<html>`, the descendant one for a nested scope. */
3136
+ [data-theme='dark'] [data-appearance-scope][data-palette='motir']:not([data-theme]),
3137
+ [data-appearance-scope][data-palette='motir'][data-theme='dark'] {
3138
+ --color-background: #0f0f0f;
3139
+ --color-border: #2a2a2a;
3140
+ --color-canvas: #0e0e0e;
3141
+ --color-charcoal: #e5e5e5;
3142
+ --color-foreground: #f3f4f6;
3143
+ --color-hairline: #2a2a2a;
3144
+ --color-hairline-soft: #1f1f1f;
3145
+ --color-hairline-strong: #3a3a3a;
3146
+ --color-ink: #f3f4f6;
3147
+ --color-link: #58a6ff;
3148
+ --color-link-pressed: #79b8ff;
3149
+ --color-muted: #1a1a1a;
3150
+ --color-muted-foreground: #a4a097;
3151
+ --color-primary: #7b6ce5;
3152
+ --color-primary-fill: #6c5cdd;
3153
+ --color-primary-foreground: #ffffff;
3154
+ --color-primary-pressed: #5645d4;
3155
+ --color-slate: #a4a097;
3156
+ --color-steel: #787671;
3157
+ --color-stone: #5d5b54;
3158
+ --color-surface: #1a1a1a;
3159
+ --color-surface-soft: #161616;
3160
+ --color-tint-lavender: #2a253a;
3161
+ --color-tint-mint: #1a2f25;
3162
+ --color-tint-peach: #3a2a1a;
3163
+ --color-tint-rose: #3a1f2a;
3164
+ --color-tint-sky: #1a2a3a;
3165
+ --color-tint-yellow: #3a341a;
3166
+ --el-sidebar-item-bg-hover: #222222;
3167
+ }
3168
+
3169
+ /* The `motir` BASE type pairing, re-asserted (MOTIR-3933) — same reason as
3170
+ the base palette above. The type axis carries no descendant rules, so these
3171
+ three role tokens are the whole of it. */
3172
+ [data-appearance-scope][data-type='motir'] {
3173
+ --font-mono: var(--font-mono-source, ui-monospace), 'SF Mono', Menlo, monospace;
3174
+ --font-sans:
3175
+ var(--font-sans-source, -apple-system), BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
3176
+ --font-serif: var(--font-serif-source, Georgia), serif;
3177
+ }
3178
+
2634
3179
  /* ── Palette: Cobalt (data-palette="cobalt") — Subtask 7.3.49 ───────────────
2635
3180
  A cool, calm, trustworthy INSTITUTIONAL-BLUE re-skin: slate-cool surfaces, a
2636
3181
  confident cobalt primary/accent, a cool cyan highlight, cooled pastel tints.
@@ -2663,6 +3208,7 @@
2663
3208
  --color-foreground: #0f1b2e; /* cool navy ink */
2664
3209
  --color-surface: #eef2f8; /* cool slate section background */
2665
3210
  --color-surface-soft: #f7f9fc; /* quieter cool division */
3211
+ --color-canvas: #e3e9f2; /* recessed planning board — a cool-slate step under --color-surface */
2666
3212
 
2667
3213
  /* text scale — cool slate hierarchy */
2668
3214
  --color-ink: #0f1b2e;
@@ -2708,16 +3254,38 @@
2708
3254
  /* sidebar hover is a concrete hex in Tier 3 (not a --color-* ref), so the
2709
3255
  palette overrides it directly — one shade darker than the cool surface. */
2710
3256
  --el-sidebar-item-bg-hover: #e4e9f1;
3257
+
3258
+ /* Status ramp — in_review vs in_progress (MOTIR-2075, the MOTIR-2073 class).
3259
+ `--el-status-in-progress` rides --color-info (Radix Blue 11 #0d63b8) and
3260
+ `--el-status-in-review` rides --color-primary (the Indigo accent #3a57cf);
3261
+ Cobalt is an institutional-BLUE palette, so its two blues sit ΔE2000 8.4
3262
+ apart — different hexes, one dot. The ramp takes its own deeper step of the
3263
+ SAME indigo instead of a new hue: Radix Indigo 12. Separation from
3264
+ in_progress ΔE 20.9; contrast 11.8:1 surface / 13.2:1 card. */
3265
+ --el-status-in-review: #1f2d5c; /* Radix Indigo 12 — deep accent step */
3266
+
3267
+ /* Priority ramp — highest vs high (MOTIR-2085, the MOTIR-2075 class one
3268
+ family over). `--el-priority-highest` rides --color-destructive (#d3322f)
3269
+ and `--el-priority-high` rides --color-warning (#c2410c); Cobalt's warning
3270
+ is a RED-leaning burnt orange, so the two land ΔE2000 9.9 apart — under the
3271
+ ΔE 10 bar a coloured priority GLYPH needs. The semantic identity is kept
3272
+ (danger stays danger, warning stays warning); the PRIORITY ramp takes its
3273
+ own step of a hue Cobalt already owns — Radix Amber 11, one of the palette's
3274
+ documented Radix sources. Separation from highest ΔE 24.3; contrast 4.6:1
3275
+ card / 4.1:1 surface, past the 3:1 icon/UI bar. */
3276
+ --el-priority-high: #ab6400; /* Radix Amber 11 — the ramp's own amber step */
2711
3277
  }
2712
3278
 
2713
3279
  /* Cobalt — dark companion. A cool navy canvas (not the base near-black);
2714
3280
  brightened cobalt/link hues so they read AS text on the dark surface, and a
2715
3281
  darker accent fill so white labels still clear AA. */
3282
+ [data-theme='dark'] [data-palette='cobalt']:not([data-theme]),
2716
3283
  [data-palette='cobalt'][data-theme='dark'] {
2717
3284
  --color-background: #0b111d; /* deep cool navy canvas */
2718
3285
  --color-foreground: #e7edf6;
2719
3286
  --color-surface: #131d2c;
2720
3287
  --color-surface-soft: #0f1825;
3288
+ --color-canvas: #070c16; /* recessed planning board — a step under the navy page */
2721
3289
 
2722
3290
  --color-ink: #e7edf6;
2723
3291
  --color-charcoal: #d3deec;
@@ -2762,6 +3330,18 @@
2762
3330
 
2763
3331
  /* sidebar hover — one shade lighter than the dark cool surface */
2764
3332
  --el-sidebar-item-bg-hover: #1c2840;
3333
+
3334
+ /* Dark keeps the indigo primary — the two blues are already ΔE 10.4 apart
3335
+ here. Re-asserted (not omitted) because the light block above would
3336
+ otherwise inherit onto this canvas: the palette block and
3337
+ [data-theme='dark'] tie on specificity and the palette block wins by
3338
+ source order. Pairing every status override is the rule, not the value. */
3339
+ --el-status-in-review: var(--color-primary);
3340
+
3341
+ /* Dark keeps the brightened warning orange — highest/high are already ΔE 25.8
3342
+ apart here. Re-asserted for the same pairing reason as the status override
3343
+ above: an unpaired light override leaks onto this canvas (MOTIR-2085). */
3344
+ --el-priority-high: var(--color-warning);
2765
3345
  }
2766
3346
 
2767
3347
  /* ── Palette: Graphite (data-palette="graphite") — Subtask 7.3.51 ───────────
@@ -2809,6 +3389,7 @@
2809
3389
  --color-foreground: #16191d; /* cool near-black ink */
2810
3390
  --color-surface: #eef0f3; /* cool slate section background */
2811
3391
  --color-surface-soft: #f8f9fa; /* quieter cool division */
3392
+ --color-canvas: #e6e8ed; /* recessed planning board — a cool-slate step under --color-surface */
2812
3393
 
2813
3394
  /* text scale — cool slate hierarchy (Radix Slate 11→9 below body) */
2814
3395
  --color-ink: #16191d;
@@ -2855,17 +3436,55 @@
2855
3436
  /* sidebar hover is a concrete hex in Tier 3 (not a --color-* ref), so the
2856
3437
  palette overrides it directly — one shade darker than the cool surface. */
2857
3438
  --el-sidebar-item-bg-hover: #e6e8ec;
3439
+
3440
+ /* Status ramp — the one place the mono identity COLLIDES with the Tier-0
3441
+ indirection (MOTIR-2073). `--el-status-in-progress` rides --color-info and
3442
+ `--el-status-in-review` rides --color-primary; every other palette keeps
3443
+ those two sources apart, but Graphite deliberately unifies them (info =
3444
+ the accent, above), which re-collapsed in_review onto in_progress — the
3445
+ exact differentiation MOTIR-1273 delivered. The fix keeps BOTH deliberate
3446
+ choices by giving the status ramp its own SECOND STEP of the same accent
3447
+ ramp rather than a second hue: Radix Blue 12, one step deeper than the
3448
+ accent (Blue 11-ish #155bc4). Still one chromatic accent, two depths —
3449
+ the same light/dark inversion the ink CTA already uses. Separation from
3450
+ in-progress: ΔE2000 16.9, contrast 2.0:1 — well clear of this palette's
3451
+ tightest deliberate status pair (todo/cancelled, ΔE 11.8). */
3452
+ --el-status-in-review: #113264; /* Radix Blue 12 — deep accent step */
3453
+
3454
+ /* Priority ramp — highest vs high (MOTIR-2094, the MOTIR-2085 shape in a
3455
+ second palette). `--el-priority-highest` rides --color-destructive
3456
+ (#c92a2a) and `--el-priority-high` rides --color-warning (#c2410c);
3457
+ Graphite's warning is a RED-leaning burnt orange sitting beside its danger
3458
+ red, so the two land ΔE2000 10.02 apart. That PASSES the ΔE 10 bar a
3459
+ coloured priority GLYPH needs — by 0.02, which is rounding, not margin: the
3460
+ two marks are at the perceptual minimum, and any future nudge to either
3461
+ semantic tips the pair under the floor. Nothing about this is the monochrome
3462
+ identity (the chrome is greyscale; semantic hues stay chromatic on purpose),
3463
+ so it is a collision to fix, not a choice to document.
3464
+ The fix is Cobalt's: neither semantic moves — danger stays danger, warning
3465
+ stays warning — and the PRIORITY ramp takes its own step of a hue Graphite
3466
+ already owns, Radix Amber 11, one of this palette's documented Radix
3467
+ sources. Separation from highest ΔE 24.7 (nearest other step, medium, ΔE
3468
+ 32.2); contrast 4.6:1 card / 4.0:1 surface, past the 3:1 icon/UI bar. */
3469
+ --el-priority-high: #ab6400; /* Radix Amber 11 — the ramp's own amber step */
3470
+ /* implemented (MOTIR-3003) — Graphite's --color-accent is its blue accent, a
3471
+ step from the same family as --el-status-in-progress. The ink step is free
3472
+ here and reads as "not yet coloured in", which is what the state is:
3473
+ ΔE 15.9 light / 11.8 dark from the nearest status. */
3474
+ --el-status-implemented: var(--color-charcoal);
2858
3475
  }
2859
3476
 
2860
3477
  /* Graphite — dark companion. A cool near-black slate canvas; the CTA fill
2861
3478
  inverts to near-WHITE ink (Vercel's dark white-button), the accent blue
2862
3479
  brightens so it reads AS text on the dark canvas, and semantic/type hues
2863
3480
  brighten to clear AA on near-black. */
3481
+ [data-theme='dark'] [data-palette='graphite']:not([data-theme]),
2864
3482
  [data-palette='graphite'][data-theme='dark'] {
2865
3483
  --color-background: #0c0d0f; /* stark cool near-black canvas */
2866
3484
  --color-foreground: #edeef0;
2867
3485
  --color-surface: #18191c; /* cool slate section */
2868
3486
  --color-surface-soft: #141517; /* quieter cool division */
3487
+ --color-canvas: #08090b; /* recessed planning board — a step under the near-black page */
2869
3488
 
2870
3489
  --color-ink: #edeef0;
2871
3490
  --color-charcoal: #d7d9dd; /* emphasis / AA-safe on tints */
@@ -2911,6 +3530,26 @@
2911
3530
 
2912
3531
  /* sidebar hover — one shade lighter than the dark cool surface */
2913
3532
  --el-sidebar-item-bg-hover: #202226;
3533
+
3534
+ /* Status ramp — the dark companion of the light block's in_review step
3535
+ (MOTIR-2073). Same idea, inverted like the CTA fill: on near-black the
3536
+ "deeper" step is the PALER one, so in_review takes Radix Blue 12 dark —
3537
+ one step lighter than the dark accent (Blue 11-ish #7db1ff). Separation
3538
+ from in-progress: ΔE2000 15.3, contrast 1.67:1; on the dark canvas
3539
+ 14.9:1. Every palette block that sets a var must set it in BOTH themes —
3540
+ a light-only override keeps its LIGHT value on the dark canvas (the
3541
+ palette block outranks the base [data-theme='dark'] block). */
3542
+ --el-status-in-review: #c2e6ff; /* Radix Blue 12 dark — pale accent step */
3543
+
3544
+ /* Dark keeps the brightened warning orange — highest/high are already ΔE 25.8
3545
+ apart here, so only light needed the amber step. Re-asserted (not omitted)
3546
+ for the same pairing reason as the status override above: an unpaired light
3547
+ override leaks onto this canvas, where it was never measured (MOTIR-2094). */
3548
+ --el-priority-high: var(--color-warning);
3549
+ /* implemented — the dark half of the light override above; declared in BOTH
3550
+ themes so the light value cannot cascade into dark (the cascade trap this
3551
+ suite checks for). */
3552
+ --el-status-implemented: var(--color-charcoal);
2914
3553
  }
2915
3554
 
2916
3555
  /* ── Palette: Evergreen (data-palette="evergreen") — Subtask 7.3.50 ──────────
@@ -2932,17 +3571,23 @@
2932
3571
  Concrete-hex --el-* tokens that do NOT resolve through --color-*
2933
3572
  (--el-sidebar-item-bg-hover) are re-skinned explicitly below. */
2934
3573
  [data-palette='evergreen'] {
2935
- /* Brand / primary — emerald CTA (white label text clears AA on the fill) */
2936
- --color-primary: #0c7a52;
3574
+ /* Brand / primary — emerald CTA (white label text clears AA on the fill).
3575
+ --color-primary is the emerald used AS text/icon; --color-primary-fill is
3576
+ the CTA fill. They were the same hex until MOTIR-3774 deepened the INK by
3577
+ 1.5% lightness (same hue 158.2, same 82.1% saturation) so it clears AA on
3578
+ the palette's own pastel tints; the FILL is untouched, which is what the
3579
+ picker swatch and every white-on-emerald label read. */
3580
+ --color-primary: #0b714c; /* deepened emerald as text/icon (6.03:1 on white) */
2937
3581
  --color-primary-foreground: #ffffff;
2938
3582
  --color-primary-pressed: #095f40;
2939
- --color-primary-fill: #0c7a52;
3583
+ --color-primary-fill: #0c7a52; /* emerald CTA fill — the signature; white-on AA */
2940
3584
 
2941
3585
  /* Surfaces — lightly cooled (green-leaning) neutrals over a white canvas */
2942
3586
  --color-background: #ffffff;
2943
3587
  --color-foreground: #14201b;
2944
3588
  --color-surface: #eef3f0;
2945
3589
  --color-surface-soft: #f6faf8;
3590
+ --color-canvas: #e4ebe7; /* recessed planning board — a green-grey step under --color-surface */
2946
3591
 
2947
3592
  /* Text scale — cooled forest-charcoal ink hierarchy */
2948
3593
  --color-ink: #14201b;
@@ -2991,11 +3636,25 @@
2991
3636
  /* Concrete-hex --el token (does not resolve through --color-*) — cool hover
2992
3637
  lift one shade darker than the cooled light surface */
2993
3638
  --el-sidebar-item-bg-hover: #e4ebe7;
3639
+
3640
+ /* Status ramp — in_review vs done (MOTIR-2075). `--el-status-in-review` rides
3641
+ --color-primary (emerald #0c7a52) and `--el-status-done` rides
3642
+ --color-success (grass #127c3e): two greens ΔE2000 5.5 apart, and this
3643
+ palette's own comment claimed they "read apart". They do not. The ramp takes
3644
+ its own deeper step of the palette's JADE — Radix Jade 12 — so review reads
3645
+ as a deep jade against the grass done. Separation ΔE 19.2; contrast 10.9:1
3646
+ surface / 12.2:1 card. */
3647
+ --el-status-in-review: #1d3b31; /* Radix Jade 12 — deep jade step */
3648
+
3649
+ /* cancelled is unchanged in light (ΔE 17.9 from todo); re-asserted to pair
3650
+ the dark override below — see the cascade note in the dark block. */
3651
+ --el-status-cancelled: var(--color-steel);
2994
3652
  }
2995
3653
 
2996
3654
  /* Evergreen — dark companion. Bright emerald on a deep-forest canvas
2997
3655
  (Supabase-style: the bright accent FILL carries DARK label text). Mirrors the
2998
3656
  Tier-1 dark flip for the cooled neutrals. */
3657
+ [data-theme='dark'] [data-palette='evergreen']:not([data-theme]),
2999
3658
  [data-palette='evergreen'][data-theme='dark'] {
3000
3659
  --color-primary: #3ddc97;
3001
3660
  --color-primary-foreground: #06140d; /* dark ink on the bright emerald fill */
@@ -3006,6 +3665,7 @@
3006
3665
  --color-foreground: #e8efe9;
3007
3666
  --color-surface: #141d18;
3008
3667
  --color-surface-soft: #101713;
3668
+ --color-canvas: #080d0a; /* recessed planning board — a step under the deep-forest page */
3009
3669
 
3010
3670
  --color-ink: #e8efe9;
3011
3671
  --color-charcoal: #d6e0d9;
@@ -3044,6 +3704,18 @@
3044
3704
 
3045
3705
  /* hover lift one shade *lighter* than the dark surface (mirrors base recipe) */
3046
3706
  --el-sidebar-item-bg-hover: #1e2a23;
3707
+
3708
+ /* Status ramp (MOTIR-2075) — two collisions in this theme.
3709
+ in_review vs done: emerald #3ddc97 against grass #34d27a, ΔE2000 4.7. Same
3710
+ fix as light, inverted: Radix Jade 12's DARK step, pale rather than deep —
3711
+ the light/dark inversion Graphite's accent step already uses. ΔE 17.2;
3712
+ contrast 13.3:1 surface / 14.5:1 card.
3713
+ todo vs cancelled: --color-stone #5a6b62 against --color-steel #6f8077,
3714
+ ΔE 8.3 — this palette's neutral ramp is compressed against the base's
3715
+ (which keeps the pair ~12 apart). cancelled takes the palette's own next
3716
+ step up, --color-slate. ΔE 22.4; contrast 7.1:1 / 7.7:1. */
3717
+ --el-status-in-review: #adf0d4; /* Radix Jade 12 dark — pale jade step */
3718
+ --el-status-cancelled: #9aaaa1; /* Evergreen's own slate — the next neutral step */
3047
3719
  }
3048
3720
 
3049
3721
  /* ═══════════════════════════════════════════════════════════════════════
@@ -3075,6 +3747,15 @@
3075
3747
  /* Sans headlines — re-point the editorial serif role at the sans stack. */
3076
3748
  --font-serif:
3077
3749
  var(--font-sans-source, -apple-system), BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
3750
+ /* Re-asserted at the BASE value, not omitted (MOTIR-3933). An omitted role
3751
+ inherits the ANCESTOR's, which is invisible on `<html>` and wrong in a
3752
+ nested scope: this pairing under a mono ancestor rendered mono. Unlike the
3753
+ colour axis there is no `[data-appearance-scope]` layer to fall back
3754
+ through — `--font-*` are role tokens, not `--el-*` — so every
3755
+ `[data-type]` block must be TOTAL over the three roles. */
3756
+ --font-sans:
3757
+ var(--font-sans-source, -apple-system), BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
3758
+ --font-mono: var(--font-mono-source, ui-monospace), 'SF Mono', Menlo, monospace;
3078
3759
  }
3079
3760
 
3080
3761
  [data-type='motir-mono'] {
@@ -3085,6 +3766,13 @@
3085
3766
  already is mono. ONLY --font-* tokens (the disjointness guard). */
3086
3767
  --font-serif: var(--font-mono-source, ui-monospace), 'SF Mono', Menlo, monospace;
3087
3768
  --font-sans: var(--font-mono-source, ui-monospace), 'SF Mono', Menlo, monospace;
3769
+ /* Re-asserted at the BASE value, not omitted (MOTIR-3933). An omitted role
3770
+ inherits the ANCESTOR's, which is invisible on `<html>` and wrong in a
3771
+ nested scope: this pairing under a mono ancestor rendered mono. Unlike the
3772
+ colour axis there is no `[data-appearance-scope]` layer to fall back
3773
+ through — `--font-*` are role tokens, not `--el-*` — so every
3774
+ `[data-type]` block must be TOTAL over the three roles. */
3775
+ --font-mono: var(--font-mono-source, ui-monospace), 'SF Mono', Menlo, monospace;
3088
3776
  }
3089
3777
 
3090
3778
  /* ── Palette: Spectrum (data-palette="spectrum") — Subtask 7.3.52 ───────────
@@ -3122,6 +3810,7 @@
3122
3810
  --color-foreground: #1a1626; /* cool near-black ink with a faint violet cast */
3123
3811
  --color-surface: #f4f2fb; /* cool violet-tinted section background */
3124
3812
  --color-surface-soft: #faf9fe; /* quieter cool division */
3813
+ --color-canvas: #ece8f7; /* recessed planning board — a violet-tinted step under --color-surface */
3125
3814
 
3126
3815
  /* text scale — cool violet-neutral hierarchy */
3127
3816
  --color-ink: #1a1626;
@@ -3169,16 +3858,32 @@
3169
3858
  /* sidebar hover is a concrete hex in Tier 3 (not a --color-* ref), so the
3170
3859
  palette overrides it directly — one shade darker than the cool surface. */
3171
3860
  --el-sidebar-item-bg-hover: #ece8f8;
3861
+
3862
+ /* Priority ramp — highest vs high, the third instance of the Cobalt
3863
+ (MOTIR-2085) / Graphite (MOTIR-2094) collision, found once the floor was
3864
+ measured on the CHIP rather than the source (MOTIR-2107).
3865
+ `--el-priority-highest` rides --color-destructive (#d92e2b) and
3866
+ `--el-priority-high` rides --color-warning (#ea580c); Spectrum's warning is
3867
+ a red-leaning orange, so the two source hues land ΔE2000 14.5 apart — over
3868
+ the glyph bar, while the 14% chip `Pill` paints rendered only ΔE 4.2, under
3869
+ cobalt's own pre-fix 3.8 in feel. Neither semantic moves; the PRIORITY ramp
3870
+ takes its own step of a hue Spectrum already owns — Radix Amber 11, one of
3871
+ the palette's documented Radix sources. Source separation from highest
3872
+ ΔE 24.3, rendered chip ΔE 8.6; contrast 4.6:1 card / 4.2:1 surface, past
3873
+ the 3:1 icon/UI bar. */
3874
+ --el-priority-high: #ab6400; /* Radix Amber 11 — the ramp's own amber step */
3172
3875
  }
3173
3876
 
3174
3877
  /* Spectrum — dark companion. A deep cool-violet canvas (not the base
3175
3878
  near-black); brightened violet/pink/link hues so they read AS text on the
3176
3879
  dark surface, and a darker accent fill so white labels still clear AA. */
3880
+ [data-theme='dark'] [data-palette='spectrum']:not([data-theme]),
3177
3881
  [data-palette='spectrum'][data-theme='dark'] {
3178
3882
  --color-background: #14111f; /* deep cool-violet canvas */
3179
3883
  --color-foreground: #ece9f6;
3180
3884
  --color-surface: #1d1830;
3181
3885
  --color-surface-soft: #181426;
3886
+ --color-canvas: #0f0c18; /* recessed planning board — a step under the cool-violet page */
3182
3887
 
3183
3888
  --color-ink: #ece9f6;
3184
3889
  --color-charcoal: #ddd8ee;
@@ -3216,7 +3921,19 @@
3216
3921
  --color-success: #36c977;
3217
3922
  --color-warning: #fb8b4c;
3218
3923
  --color-destructive: #f0555f; /* reads AS text on the dark canvas (AA ~5.5:1) */
3219
- --color-destructive-foreground: #ffffff;
3924
+ /* The FILL ink, not a page ink — this is what `Button`'s danger variant paints
3925
+ ON `--color-destructive` above. Dark lightened the red to #f0555f so it would
3926
+ read AS text on the violet canvas, and left the ink at #ffffff: two
3927
+ individually reasonable decisions that are wrong together, at 3.40:1 — the
3928
+ only one of ten palettes x two themes under AA (MOTIR-3664). A lighter red
3929
+ needs darker ink, which is what amber / candy / citrine / evergreen / garnet
3930
+ / sienna already do in their own dark blocks, each with the palette's own
3931
+ near-black. Spectrum's is the deep cool-violet canvas above: white -> #14111f
3932
+ takes the pairing 3.40 -> 5.46:1, mid-range among those six (5.14-8.81).
3933
+ `--color-destructive` itself does NOT move — it is also the border/glyph hue
3934
+ (5.03-5.68:1 on every dark surface here) and the source of
3935
+ --el-priority-highest / --el-type-bug / --el-type-deploy / --el-overdue. */
3936
+ --color-destructive-foreground: #14111f;
3220
3937
  --color-info: #6fa8ff;
3221
3938
 
3222
3939
  --color-link: #8fb8ff;
@@ -3224,6 +3941,15 @@
3224
3941
 
3225
3942
  /* sidebar hover — one shade lighter than the dark cool surface */
3226
3943
  --el-sidebar-item-bg-hover: #261f3d;
3944
+
3945
+ /* Priority ramp — the dark half of the light block's amber step (MOTIR-2107).
3946
+ UNLIKE cobalt and graphite (whose dark hues were already far enough apart to
3947
+ re-assert the source here), Spectrum's dark destructive #f0555f and warning
3948
+ #fb8b4c render a chip only ΔE 4.8 apart, so dark carries its own amber too:
3949
+ the brighter #fbab4c the other dark palettes already use, which reads as an
3950
+ icon on the violet canvas. Source separation from highest ΔE 33.9, rendered
3951
+ chip ΔE 8.1; contrast 9.8:1 card / 9.0:1 surface. */
3952
+ --el-priority-high: #fbab4c; /* on-dark amber — the ramp's own step */
3227
3953
  }
3228
3954
 
3229
3955
  /* ── Palette: Amber (data-palette="amber") — Subtask 8.8.34 (MOTIR-1252) ─────
@@ -3251,7 +3977,7 @@
3251
3977
  [data-palette='amber'] {
3252
3978
  /* brand & primary — Binance Gold. The FILL carries DARK ink labels (yellow
3253
3979
  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) */
3980
+ --color-primary: #7d6000; /* darkened gold as text/icon (5.92:1 on white — MOTIR-3774) */
3255
3981
  --color-primary-foreground: #181a20; /* Binance near-black on the gold fill (AA ~9.6:1) */
3256
3982
  --color-primary-pressed: #d4a30a;
3257
3983
  --color-primary-fill: #f0b90b; /* Binance Gold — the signature; dark-on AA */
@@ -3261,6 +3987,7 @@
3261
3987
  --color-foreground: #181a20; /* Binance near-black ink */
3262
3988
  --color-surface: #f4f5f7; /* cool graphite section background */
3263
3989
  --color-surface-soft: #fafbfc; /* quieter cool division */
3990
+ --color-canvas: #eaecf0; /* recessed planning board — a graphite step under --color-surface */
3264
3991
 
3265
3992
  /* text scale — Binance cool-grey hierarchy */
3266
3993
  --color-ink: #181a20;
@@ -3285,7 +4012,10 @@
3285
4012
  --color-accent-green: #0a8f57; /* darkened Binance buy-green for light AA */
3286
4013
 
3287
4014
  /* card tints — gold-leaning warm washes + cool feature hues */
3288
- --color-tint-peach: #fbe7c2;
4015
+ /* peach leans ORANGE, not gold: gold-leaning every warm tint had collapsed
4016
+ peach onto the Block-gold yellow at ΔE2000 4.3 light / 1.7 dark — the two
4017
+ most-used washes reading as one chip (MOTIR-2085). */
4018
+ --color-tint-peach: #fbdfc0;
3289
4019
  --color-tint-rose: #f7dde2;
3290
4020
  --color-tint-mint: #d6ecdf;
3291
4021
  --color-tint-lavender: #e2e4ee;
@@ -3305,17 +4035,24 @@
3305
4035
 
3306
4036
  /* sidebar hover — one shade darker than the cool surface */
3307
4037
  --el-sidebar-item-bg-hover: #eceef1;
4038
+ /* implemented (MOTIR-3003) — Amber's --color-accent sits beside its brand
4039
+ amber, i.e. next to --el-status-in-review. Its red is unspent by any status
4040
+ in this palette (blocked rides the amber), so it takes it: ΔE 21.2 light /
4041
+ 33.7 dark from the nearest status. */
4042
+ --el-status-implemented: var(--color-destructive);
3308
4043
  }
3309
4044
 
3310
4045
  /* Amber — dark companion: the AUTHENTIC Binance trading floor. Cool near-black
3311
4046
  canvas (#0B0E11) + "Shark" cards (#1E2329); the gold brightens to #FCD535 and
3312
4047
  the CTA fill carries dark labels; semantics are Binance's real buy-green
3313
4048
  #0ECB81 / sell-red #F6465D. */
4049
+ [data-theme='dark'] [data-palette='amber']:not([data-theme]),
3314
4050
  [data-palette='amber'][data-theme='dark'] {
3315
4051
  --color-background: #0b0e11; /* Binance deepest trading-floor black */
3316
4052
  --color-foreground: #eaecef;
3317
4053
  --color-surface: #1e2329; /* Binance "Shark" card surface */
3318
4054
  --color-surface-soft: #181a20; /* Binance app background — quieter division */
4055
+ --color-canvas: #07090c; /* recessed planning board — a step under the trading-floor black */
3319
4056
 
3320
4057
  --color-ink: #eaecef;
3321
4058
  --color-charcoal: #d1d6dd;
@@ -3338,7 +4075,8 @@
3338
4075
  --color-primary-fill: #fcd535;
3339
4076
 
3340
4077
  /* tints dimmed for the trading-floor black */
3341
- --color-tint-peach: #2f2814;
4078
+ /* the dark twin of the light orange wash — see the light block's note. */
4079
+ --color-tint-peach: #331f10;
3342
4080
  --color-tint-rose: #2f1a22;
3343
4081
  --color-tint-mint: #102a1e;
3344
4082
  --color-tint-lavender: #1c2030;
@@ -3361,6 +4099,9 @@
3361
4099
 
3362
4100
  /* sidebar hover — one shade lighter than the dark "Shark" surface */
3363
4101
  --el-sidebar-item-bg-hover: #262d35;
4102
+ /* implemented — the dark half of the light override above; declared in BOTH
4103
+ themes so the light value cannot cascade into dark. */
4104
+ --el-status-implemented: var(--color-destructive);
3364
4105
  }
3365
4106
 
3366
4107
  /* ── Palette: Sienna (data-palette="sienna") — Subtask 8.8.34 (MOTIR-1252) ───
@@ -3383,7 +4124,7 @@
3383
4124
  [data-palette='sienna'] {
3384
4125
  /* brand & primary — Mistral's flame. The FILL is Mistral Orange #fa520f with
3385
4126
  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) */
4127
+ --color-primary: #b53b09; /* darkened Mistral flame as text/icon (5.60:1 on the cream — MOTIR-3774) */
3387
4128
  --color-primary-foreground: #2a1205; /* DARK warm ink on the flame fill (AA ~5.3:1) */
3388
4129
  --color-primary-pressed: #cc3a05; /* Mistral primary-deep */
3389
4130
  --color-primary-fill: #fa520f; /* Mistral Orange — the signature flame; dark-on AA */
@@ -3393,6 +4134,7 @@
3393
4134
  --color-foreground: #1f1f1f; /* Mistral Black ink */
3394
4135
  --color-surface: #fbf1d3; /* warm cream section background (Mistral cream ramp) */
3395
4136
  --color-surface-soft: #fdf6e0; /* quieter warm cream division */
4137
+ --color-canvas: #f5e9cb; /* recessed planning board — a warm-cream step under --color-surface */
3396
4138
 
3397
4139
  /* text scale — Mistral warm-charcoal hierarchy */
3398
4140
  --color-ink: #1f1f1f;
@@ -3421,7 +4163,11 @@
3421
4163
  --color-tint-rose: #fbdfd2;
3422
4164
  --color-tint-mint: #e3ead0;
3423
4165
  --color-tint-lavender: #ece6d6;
3424
- --color-tint-sky: #e6ebda;
4166
+ /* sky is a muted wash of the palette's OWN teal accent (#0e8f86), not another
4167
+ olive: warming every cool tint had collapsed sky onto mint at ΔE2000 3.4
4168
+ light / 1.3 dark — one colour doing two jobs in a six-chip ramp (MOTIR-2085).
4169
+ Still Mediterranean, just the sea rather than the hillside. */
4170
+ --color-tint-sky: #d9e8e4;
3425
4171
  --color-tint-yellow: #ffe295; /* Mistral Block Gold */
3426
4172
 
3427
4173
  /* semantic — Mistral documents none; added warm-consistent + AA-safe */
@@ -3437,16 +4183,32 @@
3437
4183
 
3438
4184
  /* sidebar hover — one shade deeper than the cream surface */
3439
4185
  --el-sidebar-item-bg-hover: #f5e9c4;
4186
+
4187
+ /* Status ramp — blocked vs in_review (MOTIR-2075). Mistral ships NO semantic
4188
+ colours, so this palette's warning was added warm-consistent (#b45309) —
4189
+ which lands it next to the darkened Mistral flame --color-primary #c4400a:
4190
+ ΔE2000 7.1, amber against flame. Rather than move the brand flame, blocked
4191
+ takes the palette's own amber accent step, which reads as a warning without
4192
+ being the primary. ΔE 14.2; contrast 3.8:1 surface / 4.1:1 card. */
4193
+ --el-status-blocked: var(--color-accent);
4194
+
4195
+ /* implemented (MOTIR-3003) — Sienna re-maps --el-status-blocked onto
4196
+ --color-accent (directly above), so the base pink would collide with it
4197
+ here. The ink step is free: ΔE 21.1 light / 22.6 dark from the nearest
4198
+ status. */
4199
+ --el-status-implemented: var(--color-charcoal);
3440
4200
  }
3441
4201
 
3442
4202
  /* Sienna — dark companion. Mistral is light-dominant (few dark tokens: Mistral
3443
4203
  Black #1f1f1f); a warm near-black canvas built from it, the flame brightened
3444
4204
  to read as text, the fill a bright flame with dark-ink labels. */
4205
+ [data-theme='dark'] [data-palette='sienna']:not([data-theme]),
3445
4206
  [data-palette='sienna'][data-theme='dark'] {
3446
4207
  --color-background: #15120d; /* warm near-black canvas (from Mistral Black) */
3447
4208
  --color-foreground: #f5efe2;
3448
4209
  --color-surface: #1f1d17;
3449
4210
  --color-surface-soft: #1a1712;
4211
+ --color-canvas: #100d08; /* recessed planning board — a step under the warm near-black page */
3450
4212
 
3451
4213
  --color-ink: #f5efe2;
3452
4214
  --color-charcoal: #e6ddcb;
@@ -3471,7 +4233,8 @@
3471
4233
  --color-tint-rose: #321c16;
3472
4234
  --color-tint-mint: #23271a;
3473
4235
  --color-tint-lavender: #272318;
3474
- --color-tint-sky: #22251a;
4236
+ /* the dark twin of the light teal wash — see the light block's note. */
4237
+ --color-tint-sky: #1b2622;
3475
4238
  --color-tint-yellow: #332b12;
3476
4239
 
3477
4240
  --color-accent: #ffa110; /* Mistral Sunshine-700 amber on dark */
@@ -3490,6 +4253,14 @@
3490
4253
 
3491
4254
  /* sidebar hover — one shade lighter than the warm-dark surface */
3492
4255
  --el-sidebar-item-bg-hover: #292619;
4256
+
4257
+ /* Dark's amber and flame are already ΔE 11.9 apart. Re-asserted to pair the
4258
+ light override above — see the cascade note in cobalt/dark. */
4259
+ --el-status-blocked: var(--color-warning);
4260
+ /* implemented — the dark half of the light override above; declared in BOTH
4261
+ themes so the light value cannot cascade into dark (this palette's
4262
+ --el-status-blocked note above is the same pairing). */
4263
+ --el-status-implemented: var(--color-charcoal);
3493
4264
  }
3494
4265
 
3495
4266
  /* ── Palette: Garnet (data-palette="garnet") — Subtask 8.8.34 (MOTIR-1252) ───
@@ -3517,6 +4288,7 @@
3517
4288
  --color-foreground: #111111; /* Pinterest "Cod Gray" ink */
3518
4289
  --color-surface: #efefef; /* Pinterest light-gray section background */
3519
4290
  --color-surface-soft: #f7f7f7; /* quieter neutral division */
4291
+ --color-canvas: #e7e7e7; /* recessed planning board — a neutral step under --color-surface */
3520
4292
 
3521
4293
  /* text scale — Pinterest neutral-grey hierarchy */
3522
4294
  --color-ink: #111111;
@@ -3561,16 +4333,26 @@
3561
4333
 
3562
4334
  /* sidebar hover — one shade darker than the neutral surface */
3563
4335
  --el-sidebar-item-bg-hover: #e6e6e6;
4336
+
4337
+ /* Status ramp — todo vs cancelled (MOTIR-2075). Pinterest's neutrals are
4338
+ true-neutral greys and its documented ramp puts --color-stone #767676 and
4339
+ --color-steel #5f5f5f close together: ΔE2000 8.8, where the base keeps the
4340
+ pair ~12 apart. cancelled drops to Pinterest's next documented grey,
4341
+ #444444 (the palette's own --color-slate). ΔE 18.1; contrast 8.5:1 surface
4342
+ / 9.7:1 card. */
4343
+ --el-status-cancelled: #444444; /* Pinterest grey — one documented step deeper */
3564
4344
  }
3565
4345
 
3566
4346
  /* Garnet — dark companion. Pinterest's Cod-Gray near-black canvas; the red
3567
4347
  brightens to read as text, the CTA fill stays the deep Pushpin red carrying
3568
4348
  WHITE labels, semantics brighten to clear AA. */
4349
+ [data-theme='dark'] [data-palette='garnet']:not([data-theme]),
3569
4350
  [data-palette='garnet'][data-theme='dark'] {
3570
4351
  --color-background: #0e0e0e; /* Cod-Gray near-black canvas */
3571
4352
  --color-foreground: #efefef;
3572
4353
  --color-surface: #1a1a1a;
3573
4354
  --color-surface-soft: #161616;
4355
+ --color-canvas: #090909; /* recessed planning board — a step under the Cod-Gray page */
3574
4356
 
3575
4357
  --color-ink: #efefef;
3576
4358
  --color-charcoal: #e0e0e0;
@@ -3615,6 +4397,10 @@
3615
4397
 
3616
4398
  /* sidebar hover — one shade lighter than the dark surface */
3617
4399
  --el-sidebar-item-bg-hover: #232323;
4400
+
4401
+ /* Dark's greys are already ΔE 11.6 apart. Re-asserted to pair the light
4402
+ override above — see the cascade note in cobalt/dark. */
4403
+ --el-status-cancelled: var(--color-steel);
3618
4404
  }
3619
4405
 
3620
4406
  /* ── Palette: Citrine (data-palette="citrine") — Subtask 8.8.34 (MOTIR-1252) ──
@@ -3651,6 +4437,7 @@
3651
4437
  --color-foreground: #050038; /* Miro Stratos navy ink */
3652
4438
  --color-surface: #f3f4f6; /* Mirotone gray-100 section background */
3653
4439
  --color-surface-soft: #f6f7f9; /* Mirotone gray-50 — quieter division */
4440
+ --color-canvas: #ebecf0; /* recessed planning board — Mirotone gray-150, a step under gray-100 */
3654
4441
 
3655
4442
  /* text scale — Mirotone gray ramp */
3656
4443
  --color-ink: #050038;
@@ -3696,17 +4483,50 @@
3696
4483
 
3697
4484
  /* sidebar hover — Mirotone gray-150, one step off the surface */
3698
4485
  --el-sidebar-item-bg-hover: #ebecf0;
4486
+
4487
+ /* Status ramp (MOTIR-2075) — two collisions, both from Mirotone's ramps.
4488
+ blocked vs in_review: --color-warning is yellow-650 #91771e and
4489
+ --color-primary is yellow-700 #746019 — ADJACENT steps of one gold ramp,
4490
+ ΔE2000 9.7. Miro is a 5-hue system with NO orange ramp (see the block
4491
+ header), and the golds that clear AA on a light surface are exactly those
4492
+ two, so the gold cannot separate itself. The status ramp therefore takes
4493
+ Miro's OTHER brand hue at its own depth — the action blue, blue-650 — which
4494
+ leaves blocked on the warning gold where its semantics belong. Note
4495
+ in_progress rides blue-500 #3859ff: same family, two depths, ΔE 17.0 apart,
4496
+ the Graphite idiom. Contrast 9.2:1 surface / 10.2:1 card.
4497
+ todo vs cancelled: gray-450 #7f8497 vs gray-500 #656b81, ΔE 10.0 — one step
4498
+ apart on Mirotone's gray ramp. cancelled drops to gray-600. ΔE 20.6;
4499
+ contrast 7.5:1 / 8.3:1. */
4500
+ --el-status-in-review: #243797; /* Mirotone blue-650 — deep action blue */
4501
+ /* Status ramp — planning vs the two blues (MOTIR-2427, the MOTIR-2073 class).
4502
+ Citrine is a deliberate 5-hue Miro palette and it re-skins
4503
+ `--color-accent-teal` to Mirotone BLUE, so the base teal source lands on
4504
+ top of in_progress here. Mirotone red-500 is the only remaining documented
4505
+ family: `Planning` means the card was found WRONG and is being re-planned,
4506
+ which is the one status where an attention hue is honest — and `cancelled`
4507
+ stays deliberately grey, so red is not overloaded. */
4508
+ --el-status-planning: #d8182c; /* Mirotone red-500 — the attention family */
4509
+ --el-status-cancelled: #4a4e5e; /* Mirotone gray-600 — one step deeper */
4510
+ /* implemented (MOTIR-3003) — Citrine spends --color-accent on a hue its
4511
+ --el-status-in-progress is already in the family of, and its red is
4512
+ --el-status-planning. The ink step is what is left: ΔE 17.8 light / 10.0
4513
+ dark. ⚠️ The dark figure sits EXACTLY on the ΔE 10 floor, against
4514
+ in_review — a palette change that moves either one needs re-measuring
4515
+ rather than nudging. */
4516
+ --el-status-implemented: var(--color-charcoal);
3699
4517
  }
3700
4518
 
3701
4519
  /* Citrine — dark companion. A Mirotone gray-ramp near-black canvas (black
3702
4520
  #090909 / gray-900 #1a1b1e); the Sunglow yellow brightens to read as text and
3703
4521
  the fill keeps dark Stratos labels; blue/green/red brighten one ramp step to
3704
4522
  clear AA on the dark canvas. All values are documented Mirotone steps. */
4523
+ [data-theme='dark'] [data-palette='citrine']:not([data-theme]),
3705
4524
  [data-palette='citrine'][data-theme='dark'] {
3706
4525
  --color-background: #090909; /* Miro black */
3707
4526
  --color-foreground: #f6f7f9; /* gray-50 */
3708
4527
  --color-surface: #1a1b1e; /* gray-900 */
3709
4528
  --color-surface-soft: #18191c; /* gray-950 */
4529
+ --color-canvas: #040404; /* recessed planning board — a step under Miro black */
3710
4530
 
3711
4531
  --color-ink: #f6f7f9;
3712
4532
  --color-charcoal: #ebecf0; /* gray-150 */
@@ -3751,6 +4571,26 @@
3751
4571
 
3752
4572
  /* sidebar hover — Mirotone gray-800, one step lighter than the surface */
3753
4573
  --el-sidebar-item-bg-hover: #24262c;
4574
+
4575
+ /* Status ramp (MOTIR-2075) — the worst collision in the whole set:
4576
+ blocked #ffd850 against in_review #ffd02f is ΔE2000 3.0. Two Sunglow golds
4577
+ one hex apart; no one can tell them apart on a 10px dot. Same fix as light,
4578
+ inverted — the action blue at its PALE step, Mirotone blue-200, rather than
4579
+ the deep blue-650 (which sits at 1.7:1 on this near-black surface and would
4580
+ fail the 3:1 icon bar). Separation from in_progress (blue-400 #7a90fe)
4581
+ ΔE 18.4; contrast 13.1:1 surface / 15.1:1 card.
4582
+ cancelled is unchanged here (ΔE 17.8 from todo) and re-asserted only to
4583
+ pair the light block's override — see the cascade note in cobalt/dark. */
4584
+ --el-status-in-review: #d9dffc; /* Mirotone blue-200 — pale action blue */
4585
+ /* The dark twin of the planning override above — declared in BOTH themes even
4586
+ though it is the same family, because an unpaired light override loses to
4587
+ the later palette block (the cascade trap). Brightened one step, as this
4588
+ palette does with every semantic on dark. */
4589
+ --el-status-planning: #ff6b78; /* Mirotone red-400 — brightened for dark */
4590
+ --el-status-cancelled: var(--color-steel);
4591
+ /* implemented — the dark half of the light override above; declared in BOTH
4592
+ themes so the light value cannot cascade into dark. */
4593
+ --el-status-implemented: var(--color-charcoal);
3754
4594
  }
3755
4595
 
3756
4596
  /* ── Palette: Candy (data-palette="candy") — Subtask 8.8.34 (MOTIR-1252) ──────
@@ -3774,7 +4614,7 @@
3774
4614
  [data-palette='candy'] {
3775
4615
  /* brand & primary — a LIGHT bubblegum pink (Radix Pink). FILL is a light pink
3776
4616
  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) */
4617
+ --color-primary: #b72682; /* Radix Pink-11 88% toward Pink-12 (5.81:1 on white — MOTIR-3774) */
3778
4618
  --color-primary-foreground: #651249; /* Radix Pink-12 berry ink on the light-pink fill (~7.6:1) */
3779
4619
  --color-primary-pressed: #e7acd0; /* Radix Pink-7 */
3780
4620
  --color-primary-fill: #efbfdd; /* Radix Pink-6 — the light bubblegum pink */
@@ -3784,6 +4624,7 @@
3784
4624
  --color-foreground: #211f26; /* Radix Mauve-12 ink */
3785
4625
  --color-surface: #fee9f5; /* Radix Pink-3 — candy-pink section */
3786
4626
  --color-surface-soft: #fef7fb; /* Radix Pink-2 — quieter candy wash */
4627
+ --color-canvas: #fbdcef; /* recessed planning board — Radix Pink-4, a step under the Pink-3 surface */
3787
4628
 
3788
4629
  /* text scale — Radix Mauve neutral + a Pink-12 berry emphasis */
3789
4630
  --color-ink: #211f26;
@@ -3822,23 +4663,34 @@
3822
4663
  --color-destructive-foreground: #ffffff;
3823
4664
  --color-info: #00749e; /* Sky-11 */
3824
4665
 
4666
+ /* Status ramp — planning, the LIGHT twin of the dark override below (the
4667
+ cascade trap: an unpaired light override loses to the later palette block).
4668
+ Same documented Plum family, at the light scale step. */
4669
+ --el-status-planning: #ab4aba; /* Plum-9 — the documented berry accent */
4670
+
3825
4671
  /* links — Radix Sky-11 candy blue, distinct from the pink primary */
3826
4672
  --color-link: #00749e;
3827
4673
  --color-link-pressed: #1d3e56; /* Sky-12 */
3828
4674
 
3829
4675
  /* sidebar hover — one shade deeper than the candy-pink surface (Pink-4) */
3830
4676
  --el-sidebar-item-bg-hover: #fbdcef;
4677
+ /* implemented (MOTIR-3003) — Candy's --color-accent is the berry family its
4678
+ --el-status-planning already occupies (ΔE 0.0 with the base value). The ink
4679
+ step is free: ΔE 20.0 light / 16.2 dark from the nearest status. */
4680
+ --el-status-implemented: var(--color-charcoal);
3831
4681
  }
3832
4682
 
3833
4683
  /* Candy — dark companion. A pink-tinted near-black canvas (Radix Pink dark
3834
4684
  1/2/3); the bubblegum pink brightens to read as text, the fill is a bright
3835
4685
  candy pink with dark labels, and the rainbow tints become deep candy washes.
3836
4686
  All values are documented Radix dark steps. */
4687
+ [data-theme='dark'] [data-palette='candy']:not([data-theme]),
3837
4688
  [data-palette='candy'][data-theme='dark'] {
3838
4689
  --color-background: #191117; /* Radix Pink dark-1 — pink-tinted black */
3839
4690
  --color-foreground: #eeeef0; /* Mauve dark-12 */
3840
4691
  --color-surface: #37172f; /* Pink dark-3 — candy-plum section */
3841
4692
  --color-surface-soft: #21121d; /* Pink dark-2 — quieter division */
4693
+ --color-canvas: #120c11; /* recessed planning board — a step under the Pink dark-1 page */
3842
4694
 
3843
4695
  --color-ink: #eeeef0;
3844
4696
  --color-charcoal: #fdd1ea; /* Pink dark-12 — pink emphasis / AA on dark tints */
@@ -3878,11 +4730,22 @@
3878
4730
  --color-destructive-foreground: #191114; /* dark ink on the bright danger */
3879
4731
  --color-info: #75c7f0; /* Sky dark-11 */
3880
4732
 
4733
+ /* Status ramp — planning vs done (MOTIR-2427, the MOTIR-2073 class). Candy's
4734
+ `--color-accent-teal` is Jade dark-11 (#1fd8a4) and its `--color-success` is
4735
+ Grass dark-11 (#71d083): two bright dark-mode greens ΔE2000 9.2 apart, which
4736
+ is one dot at a glance. The ramp takes the palette's own documented BERRY
4737
+ instead of a fourth green — Plum, which candy.md names as its accent family
4738
+ and which nothing else in the status ramp uses. */
4739
+ --el-status-planning: #ab4aba; /* Plum-9 — the documented berry accent */
4740
+
3881
4741
  --color-link: #75c7f0;
3882
4742
  --color-link-pressed: #c2f3ff; /* Sky dark-12 */
3883
4743
 
3884
4744
  /* sidebar hover — one shade lighter than the candy-plum surface (Pink dark-4) */
3885
4745
  --el-sidebar-item-bg-hover: #4b143d;
4746
+ /* implemented — the dark half of the light override above; declared in BOTH
4747
+ themes so the light value cannot cascade into dark. */
4748
+ --el-status-implemented: var(--color-charcoal);
3886
4749
  }
3887
4750
 
3888
4751
  [data-type='grotesk'] {
@@ -3895,6 +4758,13 @@
3895
4758
  disjointness guard): no colour (--el / --color) or shape token. */
3896
4759
  --font-serif: var(--font-grotesk-source), ui-sans-serif, system-ui, sans-serif;
3897
4760
  --font-sans: var(--font-grotesk-source), ui-sans-serif, system-ui, sans-serif;
4761
+ /* Re-asserted at the BASE value, not omitted (MOTIR-3933). An omitted role
4762
+ inherits the ANCESTOR's, which is invisible on `<html>` and wrong in a
4763
+ nested scope: this pairing under a mono ancestor rendered mono. Unlike the
4764
+ colour axis there is no `[data-appearance-scope]` layer to fall back
4765
+ through — `--font-*` are role tokens, not `--el-*` — so every
4766
+ `[data-type]` block must be TOTAL over the three roles. */
4767
+ --font-mono: var(--font-mono-source, ui-monospace), 'SF Mono', Menlo, monospace;
3898
4768
  }
3899
4769
 
3900
4770
  [data-type='editorial'] {
@@ -3906,6 +4776,15 @@
3906
4776
  size tweak. ONLY --font-* role tokens here (the disjointness guard): no
3907
4777
  colour (--el/--color) or shape (--radius/--spacing/--shadow) token. */
3908
4778
  --font-serif: var(--font-editorial-source), 'Fraunces', Georgia, serif;
4779
+ /* Re-asserted at the BASE value, not omitted (MOTIR-3933). An omitted role
4780
+ inherits the ANCESTOR's, which is invisible on `<html>` and wrong in a
4781
+ nested scope: this pairing under a mono ancestor rendered mono. Unlike the
4782
+ colour axis there is no `[data-appearance-scope]` layer to fall back
4783
+ through — `--font-*` are role tokens, not `--el-*` — so every
4784
+ `[data-type]` block must be TOTAL over the three roles. */
4785
+ --font-sans:
4786
+ var(--font-sans-source, -apple-system), BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4787
+ --font-mono: var(--font-mono-source, ui-monospace), 'SF Mono', Menlo, monospace;
3909
4788
  }
3910
4789
 
3911
4790
  [data-type='mono-technical'] {