@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/README.md +42 -10
- package/dist/components/theme/StyleVignette.d.ts +28 -7
- package/dist/components/theme/StyleVignette.js +25 -4
- package/dist/components/theme/StyleVignette.js.map +1 -1
- package/dist/components/ui/Checkbox.d.ts +84 -0
- package/dist/components/ui/Checkbox.js +70 -0
- package/dist/components/ui/Checkbox.js.map +1 -0
- package/dist/components/ui/Combobox.d.ts +7 -1
- package/dist/components/ui/Combobox.js +21 -3
- package/dist/components/ui/Combobox.js.map +1 -1
- package/dist/components/ui/ErrorState.js +1 -1
- package/dist/components/ui/ErrorState.js.map +1 -1
- package/dist/components/ui/Input.d.ts +33 -0
- package/dist/components/ui/Input.js +17 -6
- package/dist/components/ui/Input.js.map +1 -1
- package/dist/components/ui/MultiSelectPicker.js +1 -1
- package/dist/components/ui/MultiSelectPicker.js.map +1 -1
- package/dist/components/ui/Pill.d.ts +7 -4
- package/dist/components/ui/Pill.js +21 -1
- package/dist/components/ui/Pill.js.map +1 -1
- package/dist/components/ui/Segmented.js +22 -3
- package/dist/components/ui/Segmented.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/specimen/TokensSpecimen.js +42 -10
- package/dist/specimen/TokensSpecimen.js.map +1 -1
- package/package.json +7 -2
- package/theme.css +927 -48
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-
|
|
1217
|
-
the `overflow-x-auto` row that
|
|
1218
|
-
rounded BOTTOM corner + its drop
|
|
1219
|
-
and get sheared flat (the reported
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
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 —
|
|
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.
|
|
1237
|
-
the
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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
|
-
|
|
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
|
|
2541
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: #
|
|
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
|
-
|
|
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
|
-
|
|
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: #
|
|
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
|
-
|
|
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
|
-
|
|
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: #
|
|
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'] {
|