@motir/design-system 0.1.0

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.
Files changed (107) hide show
  1. package/README.md +126 -0
  2. package/dist/appearance.d.ts +47 -0
  3. package/dist/appearance.js +3 -0
  4. package/dist/appearance.js.map +1 -0
  5. package/dist/components/theme/AppearancePickers.d.ts +97 -0
  6. package/dist/components/theme/AppearancePickers.js +542 -0
  7. package/dist/components/theme/AppearancePickers.js.map +1 -0
  8. package/dist/components/theme/HandDrawnFilter.d.ts +23 -0
  9. package/dist/components/theme/HandDrawnFilter.js +41 -0
  10. package/dist/components/theme/HandDrawnFilter.js.map +1 -0
  11. package/dist/components/theme/ImmersiveTilt.d.ts +3 -0
  12. package/dist/components/theme/ImmersiveTilt.js +113 -0
  13. package/dist/components/theme/ImmersiveTilt.js.map +1 -0
  14. package/dist/components/theme/StyleVignette.d.ts +60 -0
  15. package/dist/components/theme/StyleVignette.js +382 -0
  16. package/dist/components/theme/StyleVignette.js.map +1 -0
  17. package/dist/components/ui/Button.d.ts +32 -0
  18. package/dist/components/ui/Button.js +92 -0
  19. package/dist/components/ui/Button.js.map +1 -0
  20. package/dist/components/ui/Card.d.ts +33 -0
  21. package/dist/components/ui/Card.js +64 -0
  22. package/dist/components/ui/Card.js.map +1 -0
  23. package/dist/components/ui/ColorSwatchPicker.d.ts +19 -0
  24. package/dist/components/ui/ColorSwatchPicker.js +71 -0
  25. package/dist/components/ui/ColorSwatchPicker.js.map +1 -0
  26. package/dist/components/ui/Combobox.d.ts +84 -0
  27. package/dist/components/ui/Combobox.js +374 -0
  28. package/dist/components/ui/Combobox.js.map +1 -0
  29. package/dist/components/ui/EmptyState.d.ts +38 -0
  30. package/dist/components/ui/EmptyState.js +73 -0
  31. package/dist/components/ui/EmptyState.js.map +1 -0
  32. package/dist/components/ui/ErrorState.d.ts +43 -0
  33. package/dist/components/ui/ErrorState.js +169 -0
  34. package/dist/components/ui/ErrorState.js.map +1 -0
  35. package/dist/components/ui/FormField.d.ts +31 -0
  36. package/dist/components/ui/FormField.js +43 -0
  37. package/dist/components/ui/FormField.js.map +1 -0
  38. package/dist/components/ui/Input.d.ts +29 -0
  39. package/dist/components/ui/Input.js +105 -0
  40. package/dist/components/ui/Input.js.map +1 -0
  41. package/dist/components/ui/Modal.d.ts +84 -0
  42. package/dist/components/ui/Modal.js +153 -0
  43. package/dist/components/ui/Modal.js.map +1 -0
  44. package/dist/components/ui/MultiSelectPicker.d.ts +73 -0
  45. package/dist/components/ui/MultiSelectPicker.js +358 -0
  46. package/dist/components/ui/MultiSelectPicker.js.map +1 -0
  47. package/dist/components/ui/Pill.d.ts +49 -0
  48. package/dist/components/ui/Pill.js +107 -0
  49. package/dist/components/ui/Pill.js.map +1 -0
  50. package/dist/components/ui/Popover.d.ts +65 -0
  51. package/dist/components/ui/Popover.js +57 -0
  52. package/dist/components/ui/Popover.js.map +1 -0
  53. package/dist/components/ui/SectionLabel.d.ts +28 -0
  54. package/dist/components/ui/SectionLabel.js +27 -0
  55. package/dist/components/ui/SectionLabel.js.map +1 -0
  56. package/dist/components/ui/Segmented.d.ts +30 -0
  57. package/dist/components/ui/Segmented.js +65 -0
  58. package/dist/components/ui/Segmented.js.map +1 -0
  59. package/dist/components/ui/Spinner.d.ts +22 -0
  60. package/dist/components/ui/Spinner.js +39 -0
  61. package/dist/components/ui/Spinner.js.map +1 -0
  62. package/dist/components/ui/Switch.d.ts +34 -0
  63. package/dist/components/ui/Switch.js +50 -0
  64. package/dist/components/ui/Switch.js.map +1 -0
  65. package/dist/components/ui/Textarea.d.ts +19 -0
  66. package/dist/components/ui/Textarea.js +72 -0
  67. package/dist/components/ui/Textarea.js.map +1 -0
  68. package/dist/components/ui/Toast.d.ts +45 -0
  69. package/dist/components/ui/Toast.js +116 -0
  70. package/dist/components/ui/Toast.js.map +1 -0
  71. package/dist/components/ui/Tooltip.d.ts +25 -0
  72. package/dist/components/ui/Tooltip.js +43 -0
  73. package/dist/components/ui/Tooltip.js.map +1 -0
  74. package/dist/contexts/theme-context.d.ts +95 -0
  75. package/dist/contexts/theme-context.js +561 -0
  76. package/dist/contexts/theme-context.js.map +1 -0
  77. package/dist/index.d.ts +41 -0
  78. package/dist/index.js +67 -0
  79. package/dist/specimen/TokensSpecimen.d.ts +13 -0
  80. package/dist/specimen/TokensSpecimen.js +1090 -0
  81. package/dist/specimen/TokensSpecimen.js.map +1 -0
  82. package/dist/theme/appearance-resolution.d.ts +59 -0
  83. package/dist/theme/appearance-resolution.js +379 -0
  84. package/dist/theme/appearance-resolution.js.map +1 -0
  85. package/dist/theme/init-script.d.ts +48 -0
  86. package/dist/theme/init-script.js +391 -0
  87. package/dist/theme/init-script.js.map +1 -0
  88. package/dist/theme/palettes.d.ts +146 -0
  89. package/dist/theme/palettes.js +85 -0
  90. package/dist/theme/palettes.js.map +1 -0
  91. package/dist/theme/styles.d.ts +351 -0
  92. package/dist/theme/styles.js +264 -0
  93. package/dist/theme/styles.js.map +1 -0
  94. package/dist/theme/tilt.d.ts +41 -0
  95. package/dist/theme/tilt.js +18 -0
  96. package/dist/theme/tilt.js.map +1 -0
  97. package/dist/theme/types.d.ts +61 -0
  98. package/dist/theme/types.js +234 -0
  99. package/dist/theme/types.js.map +1 -0
  100. package/dist/theme/typography.d.ts +144 -0
  101. package/dist/theme/typography.js +57 -0
  102. package/dist/theme/typography.js.map +1 -0
  103. package/dist/utils/cn.d.ts +16 -0
  104. package/dist/utils/cn.js +11 -0
  105. package/dist/utils/cn.js.map +1 -0
  106. package/package.json +60 -0
  107. package/theme.css +3924 -0
package/theme.css ADDED
@@ -0,0 +1,3924 @@
1
+ /*
2
+ * Motir design system — two-axis theme architecture.
3
+ *
4
+ * Axis 1 — COLOR (the palette axis — registry in lib/theme/palettes.ts)
5
+ * data-theme="light|dark" — light/dark base
6
+ * data-palette="<palette-id>" — a named palette: a per-palette --el-* token
7
+ * override (Tier 3 below). v1 ships `motir`
8
+ * (the Tier-3 default — no override block, like
9
+ * the warm-editorial base style). More palettes
10
+ * each add a `[data-palette='<id>']` block.
11
+ *
12
+ * Axis 2 — STYLE (the named-aesthetic axis — registry in lib/theme/styles.ts)
13
+ * data-style="<style-id>"
14
+ * a named aesthetic that layers per-style SHAPE/FEEL tokens (radius /
15
+ * shadow / spacing / sizing / motion / type) — and, for richer styles,
16
+ * component-variant overrides — over the Tier-0 base scale. The two
17
+ * existing styles are `warm-editorial` (the Tier-0 default) and
18
+ * `soft-playful` (the Tier-2 block below). More styles (Swiss,
19
+ * Neo-Brutalism, Glassmorphism, …) each add a `[data-style='<id>']` block.
20
+ *
21
+ * The axes are INDEPENDENT: a `[data-style]` TOKEN block overrides ONLY
22
+ * shape/feel tokens, NEVER a colour token (`--color-*` / `--el-*`) — colour
23
+ * is the palette axis's job. Keeping them disjoint is what makes
24
+ * "style × palette" a true product of two free choices.
25
+ *
26
+ * SURFACE-MATERIAL styles (glassmorphism, 7.3.35; later cybercore, aurora,
27
+ * neumorphism …) own a richer surface — translucency, a gradient canvas,
28
+ * frosted backdrop-blur, light borders — that the token block cannot express.
29
+ * They add a palette-DERIVED MATERIAL LAYER: STYLE-SCOPED component rules
30
+ * (`[data-style='id'] [data-surface='…'] { … }`) whose colour comes ONLY from
31
+ * `color-mix()`/`var(--color-*|--el-*)` over the ACTIVE palette — never a raw
32
+ * hue. That keeps the two axes disjoint (a palette swap re-tints the glass; a
33
+ * style swap leaves hues untouched) while letting a style own its material.
34
+ * The token-block rule above is unchanged; the material layer is the addition.
35
+ * Both are enforced by tests/theme/styleRegistry.test.ts.
36
+ *
37
+ * Tier structure (top to bottom):
38
+ * Tier 0 — @theme base tokens (light mode defaults = the warm-editorial style)
39
+ * Tier 1 — [data-theme="dark"] overrides color tokens
40
+ * Tier 2 — [data-style="..."] overrides shape/feel tokens (NOT colour)
41
+ * Tier 3 — :root --el-* element tokens reference Tier 0/1 vars;
42
+ * components reference --el-*, never --color-* directly.
43
+ * A `[data-palette='<id>']` block overrides this --el-* layer to
44
+ * re-skin the whole app (the palette axis) — ONLY colour, never a
45
+ * shape/feel token (that is the data-style axis).
46
+ *
47
+ * Inspiration:
48
+ * - Notion's marketing palette (purple primary, pastel feature tints,
49
+ * ink/charcoal/slate text scale, hairline borders). Sourced via
50
+ * `npx getdesign@latest add notion` — full file in docs/inspiration/.
51
+ * - Figma's shape language (pill buttons via --radius-pill, generous
52
+ * letter-spacing, mono caption/eyebrow). Sourced via `add figma`.
53
+ * - Motir's own typography: Inter (sans body) + Source Serif 4 (serif
54
+ * headlines) + JetBrains Mono (mono) loaded via next/font in
55
+ * app/layout.tsx.
56
+ *
57
+ * Architecture pattern mirrors dooooWeb (see /Users/yuezhu/projects/doooo/
58
+ * dooooWeb/src/styles/ for the canonical reference implementation).
59
+ */
60
+
61
+ @custom-variant dark (&:where([data-theme='dark'], [data-theme='dark'] *));
62
+
63
+ /* ═══════════════════════════════════════════════════════════════════════
64
+ TIER 0 — Base @theme tokens (light mode defaults)
65
+ Tailwind v4 auto-exposes these as utility classes:
66
+ --color-* → bg-*, text-*, border-* utilities
67
+ --radius-* → rounded-* utilities
68
+ --shadow-* → shadow-* utilities
69
+ --spacing-* → m-*, p-* utilities
70
+ --font-* → font-* utilities, text-* utilities
71
+ ═══════════════════════════════════════════════════════════════════════ */
72
+
73
+ @theme {
74
+ /* ── Color: brand & primary ─────────────────────────────────────── */
75
+ /* Notion's signature purple — reserved for the dominant CTA only. */
76
+ --color-primary: #5645d4;
77
+ --color-primary-foreground: #ffffff;
78
+ --color-primary-pressed: #4534b3;
79
+ /* Accent FILL behind white text (filled buttons/badges). In light theme it
80
+ equals --color-primary; the dark theme darkens it so white label text
81
+ clears WCAG AA (the brighter --color-primary stays the accent used AS text
82
+ on the page). See [data-theme='dark']. (MOTIR-757) */
83
+ --color-primary-fill: #5645d4;
84
+
85
+ /* Surfaces */
86
+ --color-background: #ffffff; /* canvas */
87
+ --color-foreground: #1a1a1a; /* ink (primary text) */
88
+ --color-surface: #f6f5f4; /* subtle section backgrounds */
89
+ --color-surface-soft: #fafaf9; /* quieter section divisions */
90
+ --color-canvas: #ecebe7; /* recessed board behind raised cards (planning canvas) */
91
+
92
+ /* Text scale (Notion's warm-charcoal hierarchy) */
93
+ --color-ink: #1a1a1a;
94
+ --color-charcoal: #37352f; /* body emphasis */
95
+ --color-slate: #5d5b54; /* secondary text */
96
+ --color-steel: #787671; /* tertiary / footer */
97
+ --color-stone: #a4a097; /* muted labels */
98
+ --color-muted: #f3f4f6;
99
+ --color-muted-foreground: #787671;
100
+
101
+ /* Borders / hairlines */
102
+ --color-hairline: #e5e3df;
103
+ --color-hairline-soft: #ede9e4;
104
+ --color-hairline-strong: #c8c4be;
105
+ --color-border: #e5e3df;
106
+
107
+ /* Accent secondary (used sparingly — pastel tints for feature cards) */
108
+ --color-accent: #ff64c8; /* brand pink */
109
+ --color-accent-orange: #dd5b00;
110
+ --color-accent-teal: #2a9d99;
111
+ --color-accent-green: #1aae39;
112
+
113
+ /* Card tints (pale, for tinted surface variants) */
114
+ --color-tint-peach: #ffe8d4;
115
+ --color-tint-rose: #fde0ec;
116
+ --color-tint-mint: #d9f3e1;
117
+ --color-tint-lavender: #e6e0f5;
118
+ --color-tint-sky: #dcecfa;
119
+ --color-tint-yellow: #fef7d6;
120
+
121
+ /* Semantic */
122
+ --color-success: #1aae39;
123
+ --color-warning: #dd5b00;
124
+ --color-destructive: #e03131;
125
+ --color-destructive-foreground: #ffffff;
126
+ --color-info: #0075de;
127
+
128
+ /* Link blue (NOT primary purple — distinct role). Tuned to clear WCAG AA
129
+ (4.5:1) on BOTH the white page (#ffffff → 4.94:1) AND the slightly darker
130
+ --color-surface-soft (#fafaf9 → 4.73:1) that hovered rows paint behind it,
131
+ so a link on a hovered soft-surface row stays accessible
132
+ (bug-filters-directory-name-link-hover-aa-contrast — #0075de was 4.37:1
133
+ on the soft surface). */
134
+ --color-link: #0070d2;
135
+ --color-link-pressed: #005bab;
136
+
137
+ /* ── Typography ─────────────────────────────────────────────────── */
138
+ /* CSS variables set by next/font in app/layout.tsx. */
139
+ --font-sans:
140
+ var(--font-sans-source, -apple-system), BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
141
+ --font-serif: var(--font-serif-source, Georgia), serif;
142
+ --font-mono: var(--font-mono-source, ui-monospace), 'SF Mono', Menlo, monospace;
143
+
144
+ /* Type scale — Notion-ish hierarchy.
145
+ Headings (xl and up) default to serif; body defaults to sans. */
146
+ --font-size-xs: 0.75rem; /* 12px — micro labels */
147
+ --font-size-sm: 0.875rem; /* 14px — small body, buttons */
148
+ --font-size-base: 1rem; /* 16px — primary body */
149
+ --font-size-lg: 1.25rem; /* 20px — h4 / subtitle */
150
+ --font-size-xl: 1.5rem; /* 24px — h3 / card titles */
151
+ --font-size-2xl: 2rem; /* 32px — h2 */
152
+ --font-size-3xl: 3rem; /* 48px — h1 */
153
+ --font-size-display: 5rem; /* 80px — hero display */
154
+
155
+ /* ── Shape (the warm-editorial base style) ─────────────────────── */
156
+ /* Notion uses sober rectangles (8px buttons); Figma uses pills (50px).
157
+ The base scale IS the `warm-editorial` style (sober rectangles); the
158
+ `soft-playful` style adopts Figma's pills in its [data-style] block. */
159
+ --radius-xs: 4px;
160
+ --radius-sm: 6px;
161
+ --radius-md: 8px;
162
+ --radius-lg: 12px;
163
+ --radius-xl: 16px;
164
+ --radius-pill: 9999px;
165
+
166
+ /* Semantic shape tokens — components reference these, not --radius-md
167
+ directly. Lets a [data-style] block override these without renaming. */
168
+ --radius-btn: 8px; /* Notion-style sober button */
169
+ --radius-card: 12px;
170
+ --radius-input: 8px;
171
+ --radius-modal: 12px;
172
+ --radius-badge: 9999px;
173
+ --radius-control: 6px; /* small interactive affordances — menu/list rows, icon & close buttons, tooltips, code chips */
174
+ --radius-kbd: 4px; /* keyboard-hint chips */
175
+
176
+ /* ── Elevation ──────────────────────────────────────────────────── */
177
+ --shadow-flat: none;
178
+ --shadow-subtle: 0 1px 2px rgba(15, 15, 15, 0.04);
179
+ --shadow-card: 0 4px 12px rgba(15, 15, 15, 0.08);
180
+ --shadow-elevated: 0 8px 24px rgba(15, 15, 15, 0.12);
181
+ --shadow-modal: 0 16px 48px -8px rgba(15, 15, 15, 0.16);
182
+ --shadow-hero-mockup: 0 24px 48px -8px rgba(15, 15, 15, 0.2);
183
+
184
+ /* ── Spacing ────────────────────────────────────────────────────── */
185
+ --spacing-xxs: 4px;
186
+ --spacing-xs: 8px;
187
+ --spacing-sm: 12px;
188
+ --spacing-md: 16px;
189
+ --spacing-lg: 20px;
190
+ --spacing-xl: 24px;
191
+ --spacing-2xl: 32px;
192
+ --spacing-3xl: 40px;
193
+ --spacing-section: 64px;
194
+ --spacing-section-lg: 96px;
195
+ --spacing-hero: 120px;
196
+
197
+ /* Component-specific spacing */
198
+ --spacing-btn-x: 18px;
199
+ --spacing-btn-y: 10px;
200
+ --spacing-btn-x-sm: 12px;
201
+ --spacing-btn-y-sm: 8px;
202
+ --spacing-input-x: 16px;
203
+ --spacing-input-y: 12px;
204
+ --spacing-card-padding: 24px;
205
+
206
+ /* Control-level spacing — small affordances. Flip with the active style so a
207
+ getdesign swap can redefine control DENSITY, not just radius. */
208
+ --spacing-control-x: 10px; /* menu/list rows, combobox trigger & search, sidebar rows */
209
+ --spacing-control-y: 6px;
210
+ --spacing-icon-btn: 4px; /* square icon / close buttons */
211
+ --spacing-chip-x: 10px; /* badge / pill chips */
212
+ --spacing-chip-y: 2px;
213
+ --spacing-kbd-x: 6px; /* <kbd> keyboard-hint chips */
214
+ --spacing-kbd-y: 2px;
215
+ --spacing-tooltip-x: 8px; /* tooltip & inline code blocks */
216
+ --spacing-tooltip-y: 4px;
217
+
218
+ /* Heights */
219
+ --height-btn-sm: 32px;
220
+ --height-btn-md: 40px;
221
+ --height-btn-lg: 48px;
222
+ --height-input: 44px;
223
+ --height-control: 36px; /* single-line / square small control */
224
+
225
+ /* ── Interaction ────────────────────────────────────────────────── */
226
+ --transition-duration: 150ms;
227
+ --transition-fast: 100ms;
228
+ --transition-slow: 250ms;
229
+ --hover-scale: 1;
230
+ --active-scale: 0.98;
231
+ --focus-ring-width: 2px;
232
+ --focus-ring-offset: 2px;
233
+ --focus-ring-color: var(--color-primary);
234
+ }
235
+
236
+ /* ═══════════════════════════════════════════════════════════════════════
237
+ TIER 1 — Dark mode color overrides
238
+ Activated when [data-theme="dark"] is set on <html> (by the init
239
+ script + ThemeProvider).
240
+ ═══════════════════════════════════════════════════════════════════════ */
241
+
242
+ [data-theme='dark'] {
243
+ color-scheme: dark;
244
+
245
+ --color-background: #0f0f0f;
246
+ --color-foreground: #f3f4f6;
247
+ --color-surface: #1a1a1a;
248
+ --color-surface-soft: #161616;
249
+ --color-canvas: #0e0e0e; /* recessed board behind raised cards (planning canvas) */
250
+
251
+ --color-ink: #f3f4f6;
252
+ --color-charcoal: #e5e5e5;
253
+ --color-slate: #a4a097;
254
+ --color-steel: #787671;
255
+ --color-stone: #5d5b54;
256
+ --color-muted: #1a1a1a;
257
+ --color-muted-foreground: #a4a097;
258
+
259
+ --color-hairline: #2a2a2a;
260
+ --color-hairline-soft: #1f1f1f;
261
+ --color-hairline-strong: #3a3a3a;
262
+ --color-border: #2a2a2a;
263
+
264
+ /* Primary stays close to brand; brightened from the light theme so the accent
265
+ reads as TEXT/icon on the near-black canvas (#7b6ce5 on #0f0f0f ≈ 4.67:1, AA
266
+ text). But white text ON this as a FILL is only 4.1:1 — so filled accent
267
+ buttons use the darker --color-primary-fill below instead (MOTIR-757). */
268
+ --color-primary: #7b6ce5;
269
+ --color-primary-foreground: #ffffff;
270
+ --color-primary-pressed: #5645d4;
271
+ /* Darker accent fill for dark theme: white label text on #6c5cdd is 4.99:1
272
+ (AA), while #6c5cdd still clears 3:1 as a fill against the dark canvas. */
273
+ --color-primary-fill: #6c5cdd;
274
+
275
+ /* Pastel tints dimmed for dark backgrounds */
276
+ --color-tint-peach: #3a2a1a;
277
+ --color-tint-rose: #3a1f2a;
278
+ --color-tint-mint: #1a2f25;
279
+ --color-tint-lavender: #2a253a;
280
+ --color-tint-sky: #1a2a3a;
281
+ --color-tint-yellow: #3a341a;
282
+
283
+ --color-link: #58a6ff;
284
+ --color-link-pressed: #79b8ff;
285
+ }
286
+
287
+ /* Explicit LIGHT re-assertion. Light is the implicit base (the @theme defaults
288
+ above), so <html> needs no `data-theme='light'` — it omits the attribute. This
289
+ block exists so a NESTED subtree can force light back ON even when an ancestor
290
+ (<html>) is dark — the symmetric counterpart to [data-theme='dark'], mirroring
291
+ how [data-palette]/[data-style]/[data-type] already re-skin a subtree. The
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. */
294
+ [data-theme='light'] {
295
+ color-scheme: light;
296
+
297
+ --color-background: #ffffff;
298
+ --color-foreground: #1a1a1a;
299
+ --color-surface: #f6f5f4;
300
+ --color-surface-soft: #fafaf9;
301
+
302
+ --color-ink: #1a1a1a;
303
+ --color-charcoal: #37352f;
304
+ --color-slate: #5d5b54;
305
+ --color-steel: #787671;
306
+ --color-stone: #a4a097;
307
+ --color-muted: #f3f4f6;
308
+ --color-muted-foreground: #787671;
309
+
310
+ --color-hairline: #e5e3df;
311
+ --color-hairline-soft: #ede9e4;
312
+ --color-hairline-strong: #c8c4be;
313
+ --color-border: #e5e3df;
314
+
315
+ --color-primary: #5645d4;
316
+ --color-primary-foreground: #ffffff;
317
+ --color-primary-pressed: #4534b3;
318
+ --color-primary-fill: #5645d4;
319
+
320
+ --color-tint-peach: #ffe8d4;
321
+ --color-tint-rose: #fde0ec;
322
+ --color-tint-mint: #d9f3e1;
323
+ --color-tint-lavender: #e6e0f5;
324
+ --color-tint-sky: #dcecfa;
325
+ --color-tint-yellow: #fef7d6;
326
+
327
+ --color-link: #0070d2;
328
+ --color-link-pressed: #005bab;
329
+ }
330
+
331
+ /* ═══════════════════════════════════════════════════════════════════════
332
+ TIER 2 — Named-style overrides ([data-style], Axis 2)
333
+ `warm-editorial` is the base (the Tier-0 defaults above — Notion-sober
334
+ rectangles), so it needs no override block.
335
+ `soft-playful` adopts Figma's pill buttons + larger radius + softer shadows
336
+ + roomier spacing + a gentler motion feel. Each later style (Swiss,
337
+ Neo-Brutalism, Glassmorphism, …) adds its own `[data-style='<id>']` block
338
+ here, overriding ONLY shape/feel tokens (radius / shadow / spacing / sizing /
339
+ motion / type) — NEVER a colour token (that is the data-palette axis).
340
+ ═══════════════════════════════════════════════════════════════════════ */
341
+
342
+ [data-style='soft-playful'] {
343
+ /* Figma's pill geometry */
344
+ --radius-btn: 9999px; /* pill */
345
+ --radius-input: 24px;
346
+ --radius-card: 24px;
347
+ --radius-modal: 32px;
348
+ --radius-badge: 9999px;
349
+ --radius-control: 12px; /* small affordances round up with the rest */
350
+ --radius-kbd: 8px;
351
+
352
+ /* Softer, more diffused shadows */
353
+ --shadow-card: 0 2px 8px rgba(15, 15, 15, 0.06);
354
+ --shadow-elevated: 0 6px 20px rgba(15, 15, 15, 0.1);
355
+ --shadow-modal: 0 12px 40px rgba(15, 15, 15, 0.15);
356
+
357
+ /* Roomier spacing */
358
+ --spacing-btn-x: 20px;
359
+ --spacing-btn-y: 12px;
360
+ --spacing-input-x: 18px;
361
+ --spacing-input-y: 14px;
362
+ --spacing-card-padding: 28px;
363
+ --spacing-control-x: 14px;
364
+ --spacing-control-y: 8px;
365
+ --spacing-icon-btn: 8px;
366
+ --spacing-chip-x: 14px;
367
+ --spacing-chip-y: 4px;
368
+ --spacing-kbd-x: 8px;
369
+ --spacing-kbd-y: 3px;
370
+ --spacing-tooltip-x: 12px;
371
+ --spacing-tooltip-y: 6px;
372
+ --height-control: 40px;
373
+
374
+ /* Slightly slower transitions for softer feel */
375
+ --transition-duration: 200ms;
376
+ --active-scale: 0.97;
377
+ }
378
+
379
+ /* ─────────────────────────────────────────────────────────────────────────
380
+ Swiss / Minimal-Flat — the International-Typographic style (7.3.33).
381
+ Structural, calm, gridded: hard right angles (sharp 2px corners), every
382
+ shadow removed (truly flat — depth is carried by hairline rules +
383
+ whitespace), a restrained neo-grotesque sans for headlines too (the serif
384
+ is swapped to the sans stack — a type token, not colour), tight/crisp
385
+ control density against generous card whitespace, and minimal motion (fast,
386
+ no press-scale). Overrides ONLY shape/feel tokens — never a colour token
387
+ (that is the data-palette axis; this style wears whatever palette is active).
388
+ The differentiator vs the planned Neo-Brutalism style: Swiss is REFINED and
389
+ calm (hairline borders, no shadow), not harsh (thick borders, hard offset
390
+ shadows). See docs/styles/swiss-minimal-flat.md. ───────────────────────── */
391
+ [data-style='swiss-minimal-flat'] {
392
+ /* Silhouette — hard right angles. Sharp 2px on every surface; --radius-pill
393
+ is left untouched so genuinely-circular affordances (avatars, dots) stay
394
+ round. */
395
+ --radius-xs: 1px;
396
+ --radius-sm: 2px;
397
+ --radius-md: 2px;
398
+ --radius-lg: 2px;
399
+ --radius-xl: 3px;
400
+ --radius-btn: 2px;
401
+ --radius-input: 2px;
402
+ --radius-card: 2px;
403
+ --radius-modal: 2px;
404
+ --radius-badge: 2px; /* rectangular status chips — NOT pills (the Swiss silhouette) */
405
+ --radius-control: 2px;
406
+ --radius-kbd: 2px;
407
+
408
+ /* Elevation — flat. Every surface drops its shadow; structure is drawn with
409
+ hairline borders + whitespace, never lift. */
410
+ --shadow-subtle: none;
411
+ --shadow-card: none;
412
+ --shadow-elevated: none;
413
+ --shadow-modal: none;
414
+ --shadow-hero-mockup: none;
415
+
416
+ /* Typography is the data-type axis now — Swiss ships `defaultTypeId:
417
+ 'motir-sans'` (sans headlines), applied via the [data-type='motir-sans']
418
+ block below. A [data-style] block sets NO --font-* token (disjointness). */
419
+
420
+ /* Density — tight, gridded controls; generous card whitespace. */
421
+ --spacing-btn-x: 16px;
422
+ --spacing-btn-y: 9px;
423
+ --spacing-input-x: 14px;
424
+ --spacing-input-y: 11px;
425
+ --spacing-card-padding: 28px;
426
+ --spacing-control-x: 10px;
427
+ --spacing-control-y: 6px;
428
+ --height-control: 34px;
429
+ --height-input: 42px;
430
+
431
+ /* Motion — minimal and calm. Fast transitions, NO press-scale bounce. */
432
+ --transition-duration: 100ms;
433
+ --transition-fast: 80ms;
434
+ --transition-slow: 160ms;
435
+ --hover-scale: 1;
436
+ --active-scale: 1;
437
+ }
438
+
439
+ /* ─────────────────────────────────────────────────────────────────────────
440
+ Glassmorphism — translucent frosted glass over a vibrant gradient (7.3.35).
441
+ The FIRST surface-MATERIAL style: where Swiss / Soft-Playful only re-shape
442
+ the silhouette, glass re-shapes the SURFACE itself — frosted backdrop-blur
443
+ panels, a soft palette-derived gradient canvas, light 1px borders, layered
444
+ diffuse depth, a rounded silhouette, and gentle motion.
445
+
446
+ It ships in TWO parts (the style contract was extended for this — see
447
+ docs/styles/glassmorphism.md + DESIGN.md):
448
+ 1. This [data-style] TOKEN BLOCK — shape/feel tokens ONLY (radius / shadow /
449
+ motion / density) PLUS palette-AGNOSTIC material SCALARS (blur radius,
450
+ surface/border ALPHAs, wash strength). No hue lives here, so it stays
451
+ disjoint from the palette axis — still enforced by styleRegistry.test.ts.
452
+ 2. A palette-DERIVED MATERIAL LAYER (the [data-surface] rules just below)
453
+ that turns those scalars into frosted translucency + the gradient canvas
454
+ via color-mix()/alpha OVER the active palette tokens, introducing NO new
455
+ hue — so "style x palette" stays orthogonal (swap the palette and the
456
+ glass re-tints; swap the style and the hues are untouched). This is the
457
+ surface-MATERIAL axis Swiss / Soft-Playful never needed.
458
+ ───────────────────────────────────────────────────────────────────────── */
459
+ [data-style='glassmorphism'] {
460
+ /* Silhouette — rounded, soft (not pill). Generous radii read as "glass tile". */
461
+ --radius-xs: 6px;
462
+ --radius-sm: 8px;
463
+ --radius-md: 12px;
464
+ --radius-lg: 16px;
465
+ --radius-xl: 22px;
466
+ --radius-btn: 12px;
467
+ --radius-input: 12px;
468
+ --radius-card: 18px;
469
+ --radius-modal: 22px;
470
+ --radius-badge: 9999px; /* chips stay pills — soft is the glass register */
471
+ --radius-control: 10px;
472
+ --radius-kbd: 6px;
473
+
474
+ /* Elevation — layered, diffuse, soft. Glass FLOATS: bigger, softer, lower-
475
+ opacity shadows than the base, so panels read as hovering sheets. (rgba
476
+ literals are neutral shadow ink, not a palette hue.) */
477
+ --shadow-subtle: 0 1px 2px rgba(15, 15, 15, 0.05);
478
+ --shadow-card: 0 8px 32px -8px rgba(15, 15, 15, 0.18);
479
+ --shadow-elevated: 0 16px 48px -12px rgba(15, 15, 15, 0.24);
480
+ --shadow-modal: 0 28px 70px -16px rgba(15, 15, 15, 0.34);
481
+ --shadow-hero-mockup: 0 32px 80px -16px rgba(15, 15, 15, 0.38);
482
+
483
+ /* Density — comfortable, a touch roomy (glass wants air around its tiles). */
484
+ --spacing-btn-x: 20px;
485
+ --spacing-btn-y: 11px;
486
+ --spacing-card-padding: 26px;
487
+ --spacing-control-x: 12px;
488
+ --spacing-control-y: 7px;
489
+ --height-control: 38px;
490
+
491
+ /* Motion — gentle, smooth (glass slides; never snaps). */
492
+ --transition-duration: 220ms;
493
+ --transition-slow: 320ms;
494
+ --active-scale: 0.98;
495
+
496
+ /* ── Material SCALARS — palette-AGNOSTIC (no hue, so no colour token here).
497
+ Consumed only by the [data-surface] material layer below; they are the
498
+ "glass recipe": how far to blur, and how much of the palette surface to
499
+ keep opaque (the rest lets the blurred gradient show through). ── */
500
+ --glass-blur: 18px; /* frosted-panel backdrop blur */
501
+ --glass-blur-strong: 30px; /* modal / overlay blur — more separation */
502
+ /* Lower opacity than before: real glass is TRANSLUCENT, so the vibrant canvas
503
+ shows THROUGH the panel (the old 64% read as near-solid white = "flat with a
504
+ little blur"). */
505
+ --glass-surface-alpha: 50%; /* opacity of a frosted card / popover */
506
+ --glass-modal-alpha: 56%; /* the modal panel is GLASS too — translucent enough
507
+ that the frosted page shows through it (only a
508
+ touch more opaque than a card, for its body text) */
509
+ --glass-chrome-alpha: 52%; /* sidebar / chrome translucency */
510
+ --glass-input-alpha: 44%; /* form-control fill translucency */
511
+ --glass-border-alpha: 72%; /* the glass RIM — a more visible lit edge */
512
+ --glass-wash: 42%; /* MUCH stronger canvas tint — glass needs vibrant colour
513
+ behind it to refract; a near-white canvas looks flat */
514
+ --glass-saturate: 180%; /* punchier refraction (was a fixed 150%) */
515
+ /* The specular SHEEN + RIM light — white, palette-agnostic (glass catches
516
+ light regardless of hue); used by the material layer, never as a fill. */
517
+ --glass-sheen: rgba(255, 255, 255, 0.22);
518
+ --glass-rim: rgba(255, 255, 255, 0.55);
519
+ }
520
+
521
+ /* ─────────────────────────────────────────────────────────────────────────
522
+ Glassmorphism MATERIAL LAYER — palette-DERIVED surface treatment (7.3.35).
523
+ The style-contract EXTENSION: a style may own its surface MATERIAL in these
524
+ STYLE-SCOPED component rules as long as every colour is DERIVED from the
525
+ active palette via color-mix()/var() (never a raw hue) — so the style axis
526
+ stays disjoint from the palette axis. Surfaces are tagged with the
527
+ `data-surface` hook the shared primitives now emit (Card / Modal / Popover /
528
+ Sidebar / Input). Enforced by the "surface-material layer" test in
529
+ tests/theme/styleRegistry.test.ts. These are descendant-scoped selectors, so
530
+ they are NOT the bare [data-style] token block and never carry a hue token
531
+ there — the disjoint-axis token-block test still passes. ───────────────── */
532
+
533
+ /* The canvas: the palette's page bg + three soft corner washes. The hue sources
534
+ are the `--el-*` accent roles (NOT Tier-0 `--color-*`), so the washes re-tint
535
+ with the active palette — the `data-palette` axis (7.3.48 / MOTIR-1065)
536
+ overrides `--el-*`, never `--color-*`, so reading `--el-*` here is what keeps
537
+ the glass orthogonal to the palette. The gradient is what the frosted panels
538
+ blur — without it, the blur is invisible. */
539
+ [data-style='glassmorphism'] body {
540
+ background-image:
541
+ radial-gradient(
542
+ 85% 80% at 6% -12%,
543
+ color-mix(in srgb, var(--el-accent) var(--glass-wash), transparent),
544
+ transparent 62%
545
+ ),
546
+ radial-gradient(
547
+ 80% 75% at 110% 4%,
548
+ color-mix(in srgb, var(--el-highlight) var(--glass-wash), transparent),
549
+ transparent 60%
550
+ ),
551
+ radial-gradient(
552
+ 95% 90% at 50% 120%,
553
+ color-mix(in srgb, var(--el-link) var(--glass-wash), transparent),
554
+ transparent 62%
555
+ ),
556
+ radial-gradient(
557
+ 60% 60% at 85% 95%,
558
+ color-mix(in srgb, var(--el-accent) var(--glass-wash), transparent),
559
+ transparent 60%
560
+ );
561
+ background-attachment: fixed;
562
+ }
563
+
564
+ /* Frosted container surfaces — cards & popovers float as translucent sheets. */
565
+ [data-style='glassmorphism'] [data-surface='card'],
566
+ [data-style='glassmorphism'] [data-surface='popover'] {
567
+ /* The translucent fill lets the vibrant canvas through; the linear sheen is
568
+ the light raking across the glass (top-left → fading), the inset RIM is the
569
+ lit glass edge, and the soft drop makes the sheet FLOAT. White light is
570
+ palette-agnostic; the colour still comes only from the palette-derived
571
+ fill/border, so the axes stay disjoint. */
572
+ background-color: color-mix(in srgb, var(--el-page-bg) var(--glass-surface-alpha), transparent);
573
+ background-image: linear-gradient(150deg, var(--glass-sheen), transparent 55%);
574
+ border-color: color-mix(in srgb, var(--el-border) var(--glass-border-alpha), transparent);
575
+ box-shadow:
576
+ inset 0 1px 0 0 var(--glass-rim),
577
+ inset 0 0 0 1px rgba(255, 255, 255, 0.06),
578
+ 0 8px 32px -8px rgba(15, 15, 15, 0.18);
579
+ -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
580
+ backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
581
+ }
582
+
583
+ /* Modal panel — a touch more opaque + a stronger blur (body-text AA + lift). */
584
+ [data-style='glassmorphism'] [data-surface='modal'] {
585
+ background-color: color-mix(in srgb, var(--el-page-bg) var(--glass-modal-alpha), transparent);
586
+ background-image: linear-gradient(150deg, var(--glass-sheen), transparent 55%);
587
+ border-color: color-mix(in srgb, var(--el-border) var(--glass-border-alpha), transparent);
588
+ box-shadow:
589
+ inset 0 1px 0 0 var(--glass-rim),
590
+ 0 28px 70px -16px rgba(15, 15, 15, 0.34);
591
+ -webkit-backdrop-filter: blur(var(--glass-blur-strong)) saturate(var(--glass-saturate));
592
+ backdrop-filter: blur(var(--glass-blur-strong)) saturate(var(--glass-saturate));
593
+ }
594
+
595
+ /* The sidebar rail — frosted chrome over the canvas. */
596
+ [data-style='glassmorphism'] [data-surface='sidebar'] {
597
+ background-color: color-mix(in srgb, var(--el-sidebar-bg) var(--glass-chrome-alpha), transparent);
598
+ -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
599
+ backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
600
+ }
601
+
602
+ /* Form controls — a translucent fill so inputs read as glass too. */
603
+ [data-style='glassmorphism'] [data-surface='input'] {
604
+ background-color: color-mix(in srgb, var(--el-page-bg) var(--glass-input-alpha), transparent);
605
+ -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
606
+ backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
607
+ }
608
+
609
+ /* The MODAL / command-palette BACKDROP — the most important glass surface, since
610
+ a modal floats over real page content. Under glass it is NOT a dark scrim: a
611
+ light, palette-derived veil + a page BLUR, so the whole page behind frosts and
612
+ the (also-frosted) modal panel refracts it — the proper glass-modal look.
613
+ Overrides the primitives' `bg-black/40` utility. Palette-derived (no hue). */
614
+ [data-style='glassmorphism'] [data-surface='overlay'] {
615
+ background-color: color-mix(in srgb, var(--el-page-bg) 24%, transparent);
616
+ -webkit-backdrop-filter: blur(8px) saturate(var(--glass-saturate));
617
+ backdrop-filter: blur(8px) saturate(var(--glass-saturate));
618
+ }
619
+
620
+ /* ─────────────────────────────────────────────────────────────────────────
621
+ Neo-Brutalism — the raw, punchy, utilitarian style (7.3.34).
622
+ The LOUD end of the angular family — where Swiss is refined and calm, this is
623
+ harsh on purpose: hard 0px corners on every surface, heavy 2px solid borders
624
+ doing the structural work, and HARD-OFFSET drop shadows with ZERO blur (a
625
+ solid block of shadow, not a soft lift). Tight utilitarian density, raw
626
+ monospace headlines, and snappy mechanical motion (near-instant, no
627
+ press-scale). Overrides ONLY shape/feel tokens — never a colour token (that is
628
+ the data-palette axis; this style wears whatever palette is active, so the
629
+ active palette's AA contrast is preserved by construction). The heavy-border
630
+ axis has no Tier-0 token (components draw a 1px Tailwind `border`), so it is
631
+ carried by the style-scoped component-variant block that FOLLOWS this token
632
+ block. See docs/styles/neo-brutalism.md. ─────────────────────────────────── */
633
+ [data-style='neo-brutalism'] {
634
+ /* Silhouette — zero radius. Hard 0px corners on every surface; --radius-pill
635
+ is left untouched so genuinely-circular affordances (avatars, status dots,
636
+ the spinner) stay round. */
637
+ --radius-xs: 0;
638
+ --radius-sm: 0;
639
+ --radius-md: 0;
640
+ --radius-lg: 0;
641
+ --radius-xl: 0;
642
+ --radius-btn: 0;
643
+ --radius-input: 0;
644
+ --radius-card: 0;
645
+ --radius-modal: 0;
646
+ --radius-badge: 0; /* rectangular (non-pill) status chips — the brutalist block */
647
+ --radius-control: 0;
648
+ --radius-kbd: 0;
649
+
650
+ /* Elevation — HARD-OFFSET shadows, zero blur. A solid offset block of shadow
651
+ (Xpx Ypx 0 0) instead of a soft diffuse lift; the chunky frame is the whole
652
+ point. The shadow colour is a literal near-ink (matching how the base
653
+ shadows already hard-code their rgba) — NOT a palette token, so the colour
654
+ axis stays untouched. */
655
+ --shadow-subtle: 2px 2px 0 0 rgba(15, 15, 15, 1);
656
+ --shadow-card: 4px 4px 0 0 rgba(15, 15, 15, 1);
657
+ --shadow-elevated: 6px 6px 0 0 rgba(15, 15, 15, 1);
658
+ --shadow-modal: 8px 8px 0 0 rgba(15, 15, 15, 1);
659
+ --shadow-hero-mockup: 12px 12px 0 0 rgba(15, 15, 15, 1);
660
+
661
+ /* Typography is the data-type axis now — Neo-Brutalism ships `defaultTypeId:
662
+ 'motir-mono'` (mono headlines), applied via the [data-type='motir-mono']
663
+ block below. A [data-style] block sets NO --font-* token (disjointness). */
664
+
665
+ /* Density — tight, utilitarian. Chunky controls, compact card padding, no
666
+ wasted space. */
667
+ --spacing-btn-x: 16px;
668
+ --spacing-btn-y: 10px;
669
+ --spacing-input-x: 14px;
670
+ --spacing-input-y: 11px;
671
+ --spacing-card-padding: 20px;
672
+ --spacing-control-x: 10px;
673
+ --spacing-control-y: 6px;
674
+ --height-control: 36px;
675
+ --height-input: 44px;
676
+
677
+ /* Motion — snappy, mechanical. Near-instant transitions and NO press-scale;
678
+ the UI reacts, it does not ease. */
679
+ --transition-duration: 60ms;
680
+ --transition-fast: 40ms;
681
+ --transition-slow: 100ms;
682
+ --hover-scale: 1;
683
+ --active-scale: 1;
684
+ }
685
+
686
+ /* Neo-Brutalism — component-variant override: HEAVY borders (the deliverable's
687
+ "component-variant overrides" axis). There is no Tier-0 border-WIDTH token —
688
+ every primitive (Button outline, Card, Input, Modal, Pill, Popover, …) draws
689
+ a 1px Tailwind `.border`. This style-scoped rule thickens that full box border
690
+ to a loud 2px on every framed surface WITHOUT touching a colour token or any
691
+ component file, so the heaviness is real but stays entirely inside the
692
+ neo-brutalism scope (no other style is affected). It is intentionally a
693
+ descendant rule, not a bare token block, so the disjoint-axis test (which
694
+ inspects token blocks) does not treat it as one — and it sets no colour
695
+ anyway, so the two axes stay disjoint regardless. */
696
+ [data-style='neo-brutalism'] .border {
697
+ border-width: 2px;
698
+ }
699
+
700
+ /* ─────────────────────────────────────────────────────────────────────────
701
+ Cybercore / Y2K — the neon-on-dark retro-tech style (7.3.36). Dark-first.
702
+ The identity is GLOW + GRID + MONO: surfaces are lit by an outward halo
703
+ (neon glow elevation) instead of a drop shadow, a faint tech grid textures
704
+ the canvas (see the body rule below), headlines render in mono for a
705
+ terminal read, corners are hard HUD frames, and motion is fast/electric.
706
+ Overrides ONLY shape/feel tokens — never a colour token (that is the
707
+ data-palette axis; the neon hue comes from the active palette / dark theme,
708
+ and this style amplifies it into a halo). The glow + grid are built from
709
+ `currentColor`, so they pick up whatever hue the palette provides and never
710
+ pin one — keeping the style/palette axes disjoint AND preserving the
711
+ palette's AA by construction (no text/background pair is touched). The
712
+ differentiator vs Swiss / Minimal-Flat: Swiss is flat + calm, Cybercore is
713
+ LIT + electric (glow rings, grid texture, mono headlines, snappy press).
714
+ See docs/styles/cybercore-y2k.md. ────────────────────────────────────── */
715
+ [data-style='cybercore-y2k'] {
716
+ /* Silhouette — hard HUD/terminal frames. Sharp small corners on every
717
+ surface; --radius-pill stays untouched so circular affordances (avatars,
718
+ status dots, the spinner) stay round. */
719
+ --radius-xs: 1px;
720
+ --radius-sm: 2px;
721
+ --radius-md: 3px;
722
+ --radius-lg: 4px;
723
+ --radius-xl: 4px;
724
+ --radius-btn: 3px;
725
+ --radius-input: 3px;
726
+ --radius-card: 4px;
727
+ --radius-modal: 4px;
728
+ --radius-badge: 2px; /* rectangular HUD chips — not pills */
729
+ --radius-control: 2px;
730
+ --radius-kbd: 2px;
731
+
732
+ /* Elevation — NEON GLOW, not drop-shadow. Every surface emits an outward
733
+ halo built from currentColor (the active hue), so it reads as lit rather
734
+ than lifted; the 1px ring carries the "stroke" (lit edge) too. No colour
735
+ token is referenced — only currentColor, so the palette stays in charge of
736
+ the hue and AA is untouched. */
737
+ --shadow-subtle: 0 0 0 1px color-mix(in srgb, currentColor 9%, transparent);
738
+ --shadow-card:
739
+ 0 0 0 1px color-mix(in srgb, currentColor 12%, transparent),
740
+ 0 0 14px -3px color-mix(in srgb, currentColor 24%, transparent);
741
+ --shadow-elevated:
742
+ 0 0 0 1px color-mix(in srgb, currentColor 16%, transparent),
743
+ 0 0 22px -4px color-mix(in srgb, currentColor 32%, transparent);
744
+ --shadow-modal:
745
+ 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent),
746
+ 0 0 40px -6px color-mix(in srgb, currentColor 40%, transparent);
747
+ --shadow-hero-mockup:
748
+ 0 0 0 1px color-mix(in srgb, currentColor 22%, transparent),
749
+ 0 0 56px -8px color-mix(in srgb, currentColor 45%, transparent);
750
+
751
+ /* Typography is the data-type axis now — Cybercore ships `defaultTypeId:
752
+ 'motir-mono'` (mono headlines), applied via the [data-type='motir-mono']
753
+ block below. A [data-style] block sets NO --font-* token (disjointness). */
754
+
755
+ /* Density — tight, technical HUD rhythm: compact controls, snug card pad. */
756
+ --spacing-btn-x: 18px;
757
+ --spacing-btn-y: 9px;
758
+ --spacing-input-x: 14px;
759
+ --spacing-input-y: 11px;
760
+ --spacing-card-padding: 20px;
761
+ --spacing-control-x: 10px;
762
+ --spacing-control-y: 6px;
763
+ --height-control: 34px;
764
+ --height-input: 42px;
765
+
766
+ /* Motion — snappy and electric. Very fast transitions, crisp press-scale. */
767
+ --transition-duration: 90ms;
768
+ --transition-fast: 60ms;
769
+ --transition-slow: 140ms;
770
+ --hover-scale: 1;
771
+ --active-scale: 0.97;
772
+ }
773
+
774
+ /* Cybercore surface texture — the tech grid (the "surface treatment" feel
775
+ axis). A component-variant override, NOT a [data-style] token block, so it
776
+ can carry the faint grid lines: built from currentColor at a very low alpha
777
+ so it adapts to the active palette and sits BEHIND content (text never
778
+ renders on it), keeping AA intact. Dark-first — on the dark theme this reads
779
+ as a Tron grid over the near-black canvas. */
780
+ [data-style='cybercore-y2k'] body {
781
+ background-image:
782
+ linear-gradient(color-mix(in srgb, currentColor 6%, transparent) 1px, transparent 1px),
783
+ linear-gradient(90deg, color-mix(in srgb, currentColor 6%, transparent) 1px, transparent 1px);
784
+ background-size: 32px 32px;
785
+ background-position: center top;
786
+ }
787
+
788
+ /* ─────────────────────────────────────────────────────────────────────────
789
+ Aurora — soft, DRIFTING aurora light behind fluid, gently-glowing surfaces
790
+ (7.3.38). The third surface-MATERIAL style (after glassmorphism + cybercore):
791
+ where glass re-shapes the surface into frosted TRANSLUCENCY and cybercore lits
792
+ it with a sharp neon ring, Aurora keeps surfaces OPAQUE and FLUID and moves the
793
+ identity onto (a) a slowly-animated, palette-derived aurora gradient canvas —
794
+ ribbons of light that drift like the northern lights — and (b) a gentle,
795
+ palette-derived COLOUR glow halo on each panel, so surfaces feel lit from
796
+ within rather than lifted by a hard shadow.
797
+
798
+ It ships in TWO parts, the same surface-material contract glassmorphism
799
+ established (see docs/styles/aurora.md + DESIGN.md):
800
+ 1. This [data-style] TOKEN BLOCK — shape/feel tokens ONLY (fluid radii, soft
801
+ NEUTRAL shadows, smooth slow motion, roomy density) PLUS palette-AGNOSTIC
802
+ material SCALARS (wash strengths, surface sheen, the drift duration). No
803
+ hue lives here, so it stays disjoint from the palette axis — still
804
+ enforced by the token-block test in styleRegistry.test.ts.
805
+ 2. A palette-DERIVED MATERIAL LAYER (the rules below) that paints the animated
806
+ aurora canvas + the per-surface colour glow via color-mix() OVER the
807
+ active palette tokens (--el-*), introducing NO new hue — so "style x
808
+ palette" stays orthogonal (swap the palette and the aurora re-tints; swap
809
+ the style and the hues are untouched). Surfaces opt in via the same
810
+ `data-surface` hook the shared primitives emit.
811
+ ───────────────────────────────────────────────────────────────────────── */
812
+ [data-style='aurora'] {
813
+ /* Silhouette — fluid, soft. A touch larger/rounder than glass so panels read
814
+ as organic, flowing forms. --radius-pill stays untouched so circular
815
+ affordances (avatars, status dots, the spinner) stay round. */
816
+ --radius-xs: 7px;
817
+ --radius-sm: 10px;
818
+ --radius-md: 14px;
819
+ --radius-lg: 18px;
820
+ --radius-xl: 24px;
821
+ --radius-btn: 14px;
822
+ --radius-input: 14px;
823
+ --radius-card: 20px;
824
+ --radius-modal: 26px;
825
+ --radius-badge: 9999px; /* chips stay pills — soft is the aurora register */
826
+ --radius-control: 12px;
827
+ --radius-kbd: 7px;
828
+
829
+ /* Elevation — a soft NEUTRAL lift only. The colour glow halo is palette-DERIVED
830
+ and so lives in the material layer below (not here, where no hue may appear);
831
+ these neutral rgba shadows are the diffuse base lift the glow sits on top of.
832
+ (rgba literals are neutral shadow ink — a cool near-black — not a palette
833
+ hue, so the token block stays colour-free.) */
834
+ --shadow-subtle: 0 1px 2px rgba(20, 20, 45, 0.05);
835
+ --shadow-card: 0 8px 30px -12px rgba(20, 20, 45, 0.16);
836
+ --shadow-elevated: 0 16px 44px -14px rgba(20, 20, 45, 0.22);
837
+ --shadow-modal: 0 26px 64px -18px rgba(20, 20, 45, 0.3);
838
+ --shadow-hero-mockup: 0 32px 78px -18px rgba(20, 20, 45, 0.34);
839
+
840
+ /* Density — comfortable, a touch roomy (the light wants room to breathe). */
841
+ --spacing-btn-x: 20px;
842
+ --spacing-btn-y: 11px;
843
+ --spacing-card-padding: 26px;
844
+ --spacing-control-x: 12px;
845
+ --spacing-control-y: 7px;
846
+ --height-control: 38px;
847
+
848
+ /* Motion — smooth and slow. Transitions glide; nothing snaps. */
849
+ --transition-duration: 260ms;
850
+ --transition-slow: 380ms;
851
+ --active-scale: 0.985;
852
+
853
+ /* ── Material SCALARS — palette-AGNOSTIC (no hue, so no colour token here).
854
+ Consumed only by the [data-style='aurora'] material rules below: how strong
855
+ each aurora ribbon tints the canvas, the faint top-sheen on a surface, the
856
+ glow-halo strengths, and how slowly the canvas drifts. ── */
857
+ --aurora-wash: 22%; /* primary ribbon tint over the page canvas */
858
+ --aurora-wash-soft: 15%; /* the cooler secondary ribbon (green/teal band) */
859
+ --aurora-sheen: 7%; /* faint lit-from-above sheen on an opaque surface */
860
+ --aurora-glow: 22%; /* colour-glow halo strength on a card / popover */
861
+ --aurora-glow-strong: 30%; /* the modal's slightly deeper halo */
862
+ --aurora-drift-duration: 28s; /* one slow breath of the drifting canvas */
863
+ }
864
+
865
+ /* ─────────────────────────────────────────────────────────────────────────
866
+ Aurora MATERIAL LAYER — palette-DERIVED surface treatment (7.3.38).
867
+ The same style-contract extension glassmorphism uses: a style may own its
868
+ surface material in STYLE-SCOPED component rules as long as every colour is
869
+ DERIVED from the active palette via color-mix()/var() (never a raw hue) — so
870
+ the style axis stays disjoint from the palette axis. These descendant-scoped
871
+ selectors are NOT the bare token block, so they never trip the colour-free
872
+ token-block test; they ARE read as material rules and must be palette-derived
873
+ (enforced by the "surface-material layer" test in styleRegistry.test.ts).
874
+ ───────────────────────────────────────────────────────────────────────── */
875
+
876
+ /* The canvas: the palette's page bg + four soft aurora ribbons that slowly
877
+ DRIFT. Hues come from the `--el-*` accent roles (NOT Tier-0 `--color-*`), so
878
+ the ribbons re-tint with the active palette — the `data-palette` axis overrides
879
+ `--el-*`, never `--color-*`, which is what keeps the aurora orthogonal to the
880
+ palette. background-size > 100% gives the gradient room to drift; the whole
881
+ field eases back and forth over `--aurora-drift-duration`. */
882
+ [data-style='aurora'] body {
883
+ background-image:
884
+ radial-gradient(
885
+ 58% 75% at 12% 0%,
886
+ color-mix(in srgb, var(--el-accent) var(--aurora-wash), transparent),
887
+ transparent 60%
888
+ ),
889
+ radial-gradient(
890
+ 54% 70% at 92% 8%,
891
+ color-mix(in srgb, var(--el-highlight) var(--aurora-wash), transparent),
892
+ transparent 58%
893
+ ),
894
+ radial-gradient(
895
+ 68% 82% at 62% 104%,
896
+ color-mix(in srgb, var(--el-link) var(--aurora-wash), transparent),
897
+ transparent 62%
898
+ ),
899
+ radial-gradient(
900
+ 48% 64% at 34% 58%,
901
+ color-mix(in srgb, var(--el-info) var(--aurora-wash-soft), transparent),
902
+ transparent 55%
903
+ );
904
+ background-size: 180% 180%;
905
+ background-attachment: fixed;
906
+ animation: aurora-drift var(--aurora-drift-duration) ease-in-out infinite alternate;
907
+ }
908
+
909
+ @keyframes aurora-drift {
910
+ from {
911
+ background-position:
912
+ 0% 0%,
913
+ 100% 0%,
914
+ 50% 100%,
915
+ 0% 60%;
916
+ }
917
+ to {
918
+ background-position:
919
+ 100% 40%,
920
+ 0% 60%,
921
+ 40% 0%,
922
+ 100% 100%;
923
+ }
924
+ }
925
+
926
+ /* Respect a reduced-motion preference — the aurora settles to a still gradient
927
+ (the identity holds; only the drift stops). */
928
+ @media (prefers-reduced-motion: reduce) {
929
+ [data-style='aurora'] body {
930
+ animation: none;
931
+ }
932
+ }
933
+
934
+ /* Fluid container surfaces — OPAQUE (text AA is preserved against the base
935
+ surface the palette already tuned) with a faint lit-from-above aurora sheen and
936
+ a gentle palette-derived COLOUR glow halo, so panels feel lit from within
937
+ rather than lifted by a hard shadow. The sheen sits on background-image over
938
+ the opaque background-color, so it never pulls the surface far enough from its
939
+ base tone to threaten contrast. */
940
+ [data-style='aurora'] [data-surface='card'],
941
+ [data-style='aurora'] [data-surface='popover'] {
942
+ background-color: var(--el-surface);
943
+ background-image: linear-gradient(
944
+ 180deg,
945
+ color-mix(in srgb, var(--el-highlight) var(--aurora-sheen), transparent),
946
+ transparent 42%
947
+ );
948
+ box-shadow:
949
+ var(--shadow-card),
950
+ 0 0 26px -8px color-mix(in srgb, var(--el-accent) var(--aurora-glow), transparent),
951
+ 0 0 40px -14px color-mix(in srgb, var(--el-link) var(--aurora-glow), transparent);
952
+ }
953
+
954
+ /* Modal panel — opaque, with a deeper glow so the dialog separates cleanly from
955
+ the drifting canvas behind it (and body text stays on the solid surface). */
956
+ [data-style='aurora'] [data-surface='modal'] {
957
+ background-color: var(--el-surface);
958
+ background-image: linear-gradient(
959
+ 180deg,
960
+ color-mix(in srgb, var(--el-highlight) var(--aurora-sheen), transparent),
961
+ transparent 38%
962
+ );
963
+ box-shadow:
964
+ var(--shadow-modal),
965
+ 0 0 60px -16px color-mix(in srgb, var(--el-accent) var(--aurora-glow-strong), transparent);
966
+ }
967
+
968
+ /* The sidebar rail — opaque chrome carrying the faint aurora sheen so it reads as
969
+ part of the lit surface family, not a flat slab. */
970
+ [data-style='aurora'] [data-surface='sidebar'] {
971
+ background-color: var(--el-sidebar-bg);
972
+ background-image: linear-gradient(
973
+ 180deg,
974
+ color-mix(in srgb, var(--el-accent) var(--aurora-sheen), transparent),
975
+ transparent 30%
976
+ );
977
+ }
978
+
979
+ /* ─────────────────────────────────────────────────────────────────────────
980
+ 3D / Immersive — the depth + perspective style (7.3.39, DEFERRED follow-up).
981
+ EXPERIMENTAL. The identity is DEPTH + the standard "3D card" interaction:
982
+ deep, multi-layer shadows (specular highlight + contact + ambient + far key
983
+ light) float every surface dramatically off the canvas, and the ImmersiveTilt
984
+ shell engine tips `[data-tilt]` tiles TOWARD THE CURSOR (rotateX/rotateY over a
985
+ perspective — the vanilla-tilt effect; see the @media block below).
986
+ Overrides ONLY shape/feel tokens — never a colour token (that is the
987
+ data-palette axis; this style wears whatever palette is active, so the active
988
+ palette's AA contrast is preserved by construction). The shadow light/ink is
989
+ literal rgba (white specular highlight + near-ink shadow), NOT a palette token,
990
+ so the colour axis stays untouched. The differentiator vs Glassmorphism: glass
991
+ is a translucent MATERIAL over a gradient; 3D is OPAQUE depth — real layered
992
+ light + a cursor-tracking tilt, no blur, no tint. "Performance + accessibility
993
+ heavy" (the card's caveat), so the tilt is gated behind `prefers-reduced-motion`
994
+ (BOTH the engine and the @media block below) — reduced-motion users keep the
995
+ static deep-shadow depth. See docs/styles/3d-immersive.md. ────────────────── */
996
+ [data-style='3d-immersive'] {
997
+ /* Silhouette — generously rounded dimensional tiles. --radius-pill is left
998
+ untouched so genuinely-circular affordances (avatars, status dots, the
999
+ spinner) stay round. */
1000
+ --radius-xs: 6px;
1001
+ --radius-sm: 8px;
1002
+ --radius-md: 12px;
1003
+ --radius-lg: 18px;
1004
+ --radius-xl: 24px;
1005
+ --radius-btn: 14px;
1006
+ --radius-input: 14px;
1007
+ --radius-card: 20px;
1008
+ --radius-modal: 28px;
1009
+ --radius-badge: 9999px; /* soft, dimensional status pills */
1010
+ --radius-control: 12px;
1011
+ --radius-kbd: 8px;
1012
+
1013
+ /* Elevation — THE signature. Every shadow token becomes a DEEP, multi-layer
1014
+ drop: a top specular highlight (inset, a lit edge where light catches the
1015
+ raised tile) + a tight contact shadow + a mid ambient + a wide, soft key
1016
+ light far below, so each tile floats dramatically off the canvas and reads
1017
+ as a physical object — not the timid lift a single shadow gives. Scales from
1018
+ --shadow-subtle up to --shadow-hero-mockup. Pure rgba light/ink (white
1019
+ highlight + near-ink shadow), no palette token — the colour axis is
1020
+ untouched (a white specular highlight reads as "lit" on any palette). */
1021
+ --shadow-subtle:
1022
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.5), 0 1px 2px rgba(15, 15, 15, 0.06),
1023
+ 0 5px 12px -2px rgba(15, 15, 15, 0.13);
1024
+ --shadow-card:
1025
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.6), 0 2px 4px rgba(15, 15, 15, 0.05),
1026
+ 0 12px 24px -6px rgba(15, 15, 15, 0.16), 0 28px 56px -12px rgba(15, 15, 15, 0.3);
1027
+ --shadow-elevated:
1028
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.65), 0 4px 8px rgba(15, 15, 15, 0.06),
1029
+ 0 20px 40px -8px rgba(15, 15, 15, 0.22), 0 44px 80px -16px rgba(15, 15, 15, 0.38);
1030
+ --shadow-modal:
1031
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.7), 0 8px 16px rgba(15, 15, 15, 0.08),
1032
+ 0 32px 64px -12px rgba(15, 15, 15, 0.28), 0 72px 120px -24px rgba(15, 15, 15, 0.46);
1033
+ --shadow-hero-mockup:
1034
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.7), 0 16px 32px rgba(15, 15, 15, 0.1),
1035
+ 0 96px 160px -32px rgba(15, 15, 15, 0.5);
1036
+
1037
+ /* Typography is the data-type axis now — 3D / Immersive ships
1038
+ `defaultTypeId: 'motir'` (the base editorial pairing); the personality is
1039
+ depth + light, not type. A [data-style] block sets NO --font-* token. */
1040
+
1041
+ /* Density — roomy and immersive; depth wants air around each tile. */
1042
+ --spacing-btn-x: 22px;
1043
+ --spacing-btn-y: 12px;
1044
+ --spacing-input-x: 18px;
1045
+ --spacing-input-y: 13px;
1046
+ --spacing-card-padding: 28px;
1047
+ --spacing-control-x: 14px;
1048
+ --spacing-control-y: 8px;
1049
+ --height-control: 40px;
1050
+ --height-input: 46px;
1051
+
1052
+ /* Motion — smooth and floaty. A slower ease for the depth to feel weighty.
1053
+ The signature INTERACTION is the pointer-parallax TILT below (the standard
1054
+ "3D card" effect — cards tip toward the cursor), driven by the
1055
+ ImmersiveTilt shell engine. */
1056
+ --transition-duration: 260ms;
1057
+ --transition-fast: 180ms;
1058
+ --transition-slow: 360ms;
1059
+ --active-scale: 0.98;
1060
+ }
1061
+
1062
+ /* 3D / Immersive — the IMMERSIVE BACKGROUND (depth atmosphere on EVERY page,
1063
+ not just card surfaces) + the resting FLOAT (every tile lifts off that
1064
+ canvas). Both are STATIC depth, so they sit OUTSIDE the reduced-motion gate —
1065
+ a reduced-motion user still gets the full immersive depth, only the tilt is
1066
+ suppressed. The background is palette-DERIVED (color-mix over --el tokens,
1067
+ never a raw hue), so the style/palette axes stay disjoint and the material-
1068
+ rule test passes: a palette swap re-tints the atmosphere, a style swap leaves
1069
+ hues alone. This is what makes the table/list/board pages (no card surfaces of
1070
+ their own) read as immersive too. */
1071
+ [data-style='3d-immersive'] body {
1072
+ background-image:
1073
+ radial-gradient(
1074
+ 130% 85% at 50% -15%,
1075
+ color-mix(in srgb, var(--el-accent) 9%, transparent),
1076
+ transparent 60%
1077
+ ),
1078
+ radial-gradient(
1079
+ 95% 75% at 108% 112%,
1080
+ color-mix(in srgb, var(--el-link) 8%, transparent),
1081
+ transparent 55%
1082
+ ),
1083
+ radial-gradient(
1084
+ 150% 130% at 50% 42%,
1085
+ transparent 52%,
1086
+ color-mix(in srgb, var(--el-text) 8%, transparent)
1087
+ );
1088
+ background-attachment: fixed;
1089
+ }
1090
+
1091
+ /* EVERY PANEL FLOATS — systematic depth across the WHOLE app, not just the
1092
+ handful of surfaces that use the <Card> primitive. A "panel" is any element
1093
+ with the card radius (`rounded-(--radius-card)` → the compiled class
1094
+ `.rounded-\(--radius-card\)`): cards, popovers, callouts, list/table panels,
1095
+ widgets, the hand-rolled `<div className="rounded-(--radius-card) border …">`
1096
+ surfaces every screen builds. They all get the deep resting float shadow so no
1097
+ screen is left flat. This is the same global-utility-targeting approach
1098
+ neo-brutalism uses for `.border`. Static depth (NOT motion-gated). box-shadow
1099
+ is not a colour-painting property, so the material-rule test skips it.
1100
+ Per-surface exceptions (the board column's clip-safe shadow, modals) override
1101
+ below by source order. Modals carry their own deeper shadow via the modal
1102
+ radius (next rule). */
1103
+ [data-style='3d-immersive'] .rounded-\(--radius-card\) {
1104
+ box-shadow: var(--shadow-card);
1105
+ }
1106
+ [data-style='3d-immersive'] .rounded-\(--radius-modal\) {
1107
+ box-shadow: var(--shadow-modal);
1108
+ }
1109
+
1110
+ /* 3D / Immersive — the POINTER-PARALLAX TILT (the standard "3D card" effect,
1111
+ the interactive half of the identity). The ImmersiveTilt shell engine
1112
+ (components/theme/ImmersiveTilt.tsx) — active ONLY when data-style is this
1113
+ style and the user has NOT requested reduced motion — reads the cursor
1114
+ position over a `[data-tilt]` tile and writes per-tile `--tilt-rx` / `--tilt-ry`
1115
+ rotation vars, so the tile tips toward the pointer (rotateX/rotateY over a
1116
+ perspective) and lifts; on leave the engine zeroes the vars and the tile eases
1117
+ back flat. The whole block is GATED behind `prefers-reduced-motion:
1118
+ no-preference` (the card flags this style accessibility-heavy) — a
1119
+ reduced-motion user gets NO transform at all and keeps the static deep-shadow
1120
+ depth. It sets NO colour (transform + box-shadow only; box-shadow draws the
1121
+ existing near-ink token), so the style/palette axes stay disjoint and
1122
+ styleRegistry.test.ts's material-rule check (which inspects only
1123
+ colour-painting rules) skips it. Tiles opt in with the `data-tilt` hook
1124
+ (Card, BoardCard). */
1125
+ @media (prefers-reduced-motion: no-preference) {
1126
+ /* The transition lives on the resting tile so BOTH the tilt-in and the
1127
+ settle-back-flat animate. Crucially, NO `transform` is set at rest — only
1128
+ the actively-hovered tile (data-tilt-active) gets one, so a resting card
1129
+ never establishes a transform stacking context that could reposition a
1130
+ non-portaled fixed/absolute descendant. On leave the engine drops the
1131
+ attribute and the transform eases back to none over 400ms. */
1132
+ [data-style='3d-immersive'] [data-tilt] {
1133
+ transition:
1134
+ transform 400ms cubic-bezier(0.22, 1, 0.36, 1),
1135
+ box-shadow 260ms ease-out;
1136
+ }
1137
+ /* While the pointer is over the tile the engine sets data-tilt-active='1':
1138
+ tip it toward the cursor (the per-tile --tilt-rx/ry the engine writes),
1139
+ drop the transform transition so it tracks the cursor in real time, deepen
1140
+ the shadow so the lifted tile casts further, and — the proper-3D bit —
1141
+ establish a 3D context (`preserve-3d`) so the card's slots can sit on
1142
+ separate translateZ planes and PARALLAX (a flat-plane tilt without this is
1143
+ the "halfway / flat-3D mix" tell). `position: relative` anchors the glare. */
1144
+ [data-style='3d-immersive'] [data-tilt][data-tilt-active='1'] {
1145
+ transform: perspective(var(--tilt-perspective, 900px)) rotateX(var(--tilt-rx, 0deg))
1146
+ rotateY(var(--tilt-ry, 0deg));
1147
+ transform-style: preserve-3d;
1148
+ transition:
1149
+ transform 80ms linear,
1150
+ box-shadow 260ms ease-out;
1151
+ box-shadow: var(--shadow-elevated);
1152
+ }
1153
+
1154
+ /* DEPTH PLANES — the card's slots lift onto different translateZ planes so
1155
+ they parallax above the card face as it tilts (the title/header pops most,
1156
+ the footer sits nearer the face; the body stays ON the face so arbitrary
1157
+ content never renders skewed). Only while actively tilting, so a resting
1158
+ card is perfectly flat. This is the standard layered-parallax technique
1159
+ (vanilla-tilt / Atropos): perspective + preserve-3d + per-layer translateZ. */
1160
+ [data-style='3d-immersive'] [data-tilt][data-tilt-active='1'] [data-tilt-layer='front'] {
1161
+ transform: translateZ(42px);
1162
+ transition: transform 80ms linear;
1163
+ }
1164
+ [data-style='3d-immersive'] [data-tilt][data-tilt-active='1'] [data-tilt-layer='back'] {
1165
+ transform: translateZ(14px);
1166
+ transition: transform 80ms linear;
1167
+ }
1168
+
1169
+ /* GLARE — a cursor-tracked specular sweep across the face (the light catching
1170
+ a tilted surface). Palette-DERIVED (color-mix over --el-page-bg, never a raw
1171
+ hue → passes the material-rule test; a palette swap re-tints the sheen). It
1172
+ rides a high translateZ so it reads as light on the front glass, fades in
1173
+ only while active, and never eats pointer events. */
1174
+ [data-style='3d-immersive'] [data-tilt]::after {
1175
+ content: '';
1176
+ position: absolute;
1177
+ inset: 0;
1178
+ border-radius: inherit;
1179
+ background: radial-gradient(
1180
+ circle at var(--tilt-glare-x, 50%) var(--tilt-glare-y, 50%),
1181
+ color-mix(in srgb, var(--el-page-bg) 65%, transparent),
1182
+ transparent 55%
1183
+ );
1184
+ opacity: 0;
1185
+ transition: opacity 200ms ease-out;
1186
+ pointer-events: none;
1187
+ }
1188
+ [data-style='3d-immersive'] [data-tilt][data-tilt-active='1']::after {
1189
+ opacity: 0.45;
1190
+ /* Just above the card FACE but BELOW the popped header/footer planes, so it
1191
+ lights the surface without washing the floating title. */
1192
+ transform: translateZ(2px);
1193
+ }
1194
+ }
1195
+
1196
+ /* `position: relative` anchors the glare ::after to each tile. Static (outside
1197
+ the motion gate) so the anchor is stable; the glare itself only shows while
1198
+ tilting (above). */
1199
+ [data-style='3d-immersive'] [data-tilt] {
1200
+ position: relative;
1201
+ }
1202
+
1203
+ /* ── 3D / Immersive — BOARD COLUMNS ─────────────────────────────────────────
1204
+ A board column is a TALL panel inside an `overflow-x-auto` scroll row, which
1205
+ forces overflow-y to clip — so the wide --shadow-card gets cut off at the
1206
+ bottom and adjacent opaque columns occlude each other's side shadows, making
1207
+ the board read as one backing slab. Instead each column gets a TIGHTER,
1208
+ clip/occlusion-safe float shadow (small horizontal bleed, modest drop), and
1209
+ the scroll row widens its gap + adds bottom room so the shadow shows. Result:
1210
+ each column floats as its own distinct card. */
1211
+ [data-style='3d-immersive'] [data-board-col-panel] {
1212
+ box-shadow:
1213
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.55),
1214
+ 0 1px 3px rgba(15, 15, 15, 0.08),
1215
+ 0 8px 18px -8px rgba(15, 15, 15, 0.22);
1216
+ /* The column is `h-[calc(100dvh-12rem)]` — sized to fill the board area. In
1217
+ the `overflow-x-auto` row that exactly-filling height means the column's
1218
+ rounded BOTTOM corner + its drop shadow sit at the overflow-clip boundary
1219
+ and get sheared flat (the reported "sharp bottom corner"). Trim the height
1220
+ so the rounded corner + shadow clear the clip and the column reads as a
1221
+ fully-rounded floating card. Also clip the column's OWN children to its
1222
+ radius so a flush inner surface can't square a corner — the board cards pop
1223
+ on hover but the DragOverlay clone is portaled, so nothing 3D is lost. */
1224
+ height: calc(100dvh - 14.5rem);
1225
+ overflow: hidden;
1226
+ }
1227
+ [data-style='3d-immersive'] [data-board-scroll] {
1228
+ gap: 1.5rem;
1229
+ padding-bottom: 2.25rem;
1230
+ padding-top: 0.25rem;
1231
+ }
1232
+
1233
+ /* ── 3D / Immersive — PHYSICAL BUTTONS ──────────────────────────────────────
1234
+ In a 3D design a button is not flat: it has visible THICKNESS (a solid base
1235
+ edge) and presses DOWN onto it on click (translateY + the base compresses) —
1236
+ the standard 3D/skeuomorphic button. This must cover EVERY button, not just
1237
+ the <Button> primitive: the ~35 hand-rolled `rounded-(--radius-btn)` <button>s
1238
+ (the /issues toolbar triggers — Filter / Advanced / Saved / the Tree-List
1239
+ switcher — links-styled-as-buttons, etc.) were flat. So the GLOBAL rule on the
1240
+ button-radius class is the baseline (a neutral `--el-border-strong` edge, which
1241
+ suits the outline/toolbar buttons); FILLED variants override it with a
1242
+ colour-matched edge (primary → `--el-accent-pressed`, danger → a darkened
1243
+ `--el-danger` — palette-derived, so the axes stay disjoint); ghost stays flat
1244
+ (the quiet button). box-shadow is not a colour-painting property, so the
1245
+ material-rule test skips these. The press overrides the base `active:scale`
1246
+ with a more physical translateY. */
1247
+ /* `button.rounded-(--radius-input)` catches DROPDOWN-TRIGGER buttons styled as an
1248
+ input (the board switcher, combobox / picker triggers) so they're physical
1249
+ keys too — scoped to `button` so a real <input>/<textarea> text field (same
1250
+ radius) is NEVER raised. */
1251
+ [data-style='3d-immersive'] .rounded-\(--radius-btn\),
1252
+ [data-style='3d-immersive'] button.rounded-\(--radius-input\) {
1253
+ box-shadow:
1254
+ 0 3px 0 0 var(--el-border-strong),
1255
+ 0 5px 10px -3px rgba(15, 15, 15, 0.18);
1256
+ transition:
1257
+ transform 90ms ease-out,
1258
+ box-shadow 90ms ease-out,
1259
+ background-color var(--transition-duration);
1260
+ }
1261
+ [data-style='3d-immersive'] [data-variant='primary'] {
1262
+ box-shadow:
1263
+ 0 4px 0 0 var(--el-accent-pressed),
1264
+ 0 7px 12px -3px rgba(15, 15, 15, 0.32);
1265
+ }
1266
+ [data-style='3d-immersive'] [data-variant='danger'] {
1267
+ box-shadow:
1268
+ 0 4px 0 0 color-mix(in srgb, var(--el-danger), black 32%),
1269
+ 0 7px 12px -3px rgba(15, 15, 15, 0.32);
1270
+ }
1271
+ /* Ghost — the deliberately-flat quiet button; no thickness. */
1272
+ [data-style='3d-immersive'] [data-variant='ghost'] {
1273
+ box-shadow: none;
1274
+ }
1275
+ /* Press DOWN onto the base — the button drops by its thickness and the base
1276
+ edge compresses, like a real key. Overrides the base `active:scale`. */
1277
+ [data-style='3d-immersive'] .rounded-\(--radius-btn\):active:not(:disabled),
1278
+ [data-style='3d-immersive'] button.rounded-\(--radius-input\):active:not(:disabled) {
1279
+ transform: translateY(3px);
1280
+ box-shadow:
1281
+ 0 1px 0 0 var(--el-border-strong),
1282
+ 0 2px 4px rgba(15, 15, 15, 0.14);
1283
+ }
1284
+ [data-style='3d-immersive'] [data-variant='primary']:active:not(:disabled) {
1285
+ box-shadow:
1286
+ 0 1px 0 0 var(--el-accent-pressed),
1287
+ 0 2px 5px rgba(15, 15, 15, 0.22);
1288
+ }
1289
+ [data-style='3d-immersive'] [data-variant='danger']:active:not(:disabled) {
1290
+ box-shadow:
1291
+ 0 1px 0 0 color-mix(in srgb, var(--el-danger), black 32%),
1292
+ 0 2px 5px rgba(15, 15, 15, 0.22);
1293
+ }
1294
+ [data-style='3d-immersive'] [data-variant='ghost']:active:not(:disabled) {
1295
+ box-shadow: none;
1296
+ }
1297
+
1298
+ /* ── 3D / Immersive — DROPDOWNS / POPOVERS / MENUS unfold in 3D ──────────────
1299
+ A flat fade-in is the "still not 3D" tell on overlays. The standard 3D
1300
+ dropdown UNFOLDS: it swings open from its top edge over a perspective
1301
+ (transform-origin top + rotateX, folded → flat). Every floating overlay in the
1302
+ app shares `shadow-(--shadow-elevated)` (popovers, comboboxes, multi-selects,
1303
+ menus, tooltips, toasts), so one global rule on the compiled
1304
+ `.shadow-\(--shadow-elevated\)` class gives them all the 3D open — and restores
1305
+ the deeper elevated shadow (the global card-float rule above would otherwise
1306
+ flatten them to --shadow-card). gated by reduced-motion. */
1307
+ @keyframes immersive-menu-open {
1308
+ from {
1309
+ opacity: 0;
1310
+ transform: perspective(900px) rotateX(-26deg) translateY(-6px);
1311
+ }
1312
+ to {
1313
+ opacity: 1;
1314
+ transform: none;
1315
+ }
1316
+ }
1317
+ /* Restore the deeper ELEVATED shadow on every overlay (the global card-float
1318
+ rule above would otherwise flatten any `shadow-(--shadow-elevated)` surface to
1319
+ --shadow-card). Global + static so menus, tooltips, drag-overlays all keep the
1320
+ right depth. */
1321
+ [data-style='3d-immersive'] .shadow-\(--shadow-elevated\) {
1322
+ box-shadow: var(--shadow-elevated);
1323
+ }
1324
+ @media (prefers-reduced-motion: no-preference) {
1325
+ /* The 3D UNFOLD is scoped to actual MENU surfaces (the Popover primitive +
1326
+ the `data-menu-surface` hook on Combobox / MultiSelect / BoardSwitcher
1327
+ menus) — NOT every elevated surface, so a drag-overlay or toast doesn't
1328
+ unexpectedly fold open. */
1329
+ [data-style='3d-immersive'] [data-surface='popover'],
1330
+ [data-style='3d-immersive'] [data-menu-surface] {
1331
+ transform-origin: top center;
1332
+ animation: immersive-menu-open 200ms cubic-bezier(0.22, 1, 0.36, 1);
1333
+ }
1334
+ /* A Radix popover that flips ABOVE its trigger folds from the bottom edge. */
1335
+ [data-style='3d-immersive'] [data-surface='popover'][data-side='top'] {
1336
+ transform-origin: bottom center;
1337
+ }
1338
+ }
1339
+
1340
+ /* ── Hand-Drawn / Indie (data-style="hand-drawn-indie") — Subtask 7.3.41 ─────
1341
+ DEFERRED follow-up. A sketchy, friendly, zine-like aesthetic. TWO moves make
1342
+ it read as genuinely hand-drawn, not just "softly rounded":
1343
+ (1) WONKY corners — every framed surface gets a strongly asymmetric
1344
+ multi-value radius (one corner near-sharp, the opposite generously round,
1345
+ no two alike) so a card/button/input reads as a DRAWN box; and
1346
+ (2) ROUGH edges — a `feTurbulence`+`feDisplacementMap` filter (`#hd-rough`,
1347
+ defined once by <HandDrawnFilter/> in the root layout) warps the OUTLINE
1348
+ into an ink-like wobble. Asymmetric radius alone only bends the corners;
1349
+ the displacement is what makes the LINES wavy (the Excalidraw / rough.js
1350
+ look). It is applied to a CONTENT-SAFE `::after` pseudo-border so only the
1351
+ outline is displaced — the text stays crisp.
1352
+ Plus a soft hand-placed offset shadow, a roomy rhythm, and a springy
1353
+ hover/press bounce. Colour stays the palette axis: the token block sets no
1354
+ `--color-` / `--el-` colour token (AA preserved), and the rough-border rule
1355
+ takes its ink from the palette (var(--el-border-strong)). Out-of-the-box type
1356
+ is the characterful Editorial (Fraunces) pairing via `defaultTypeId`. See
1357
+ docs/styles/hand-drawn-indie.md. */
1358
+ [data-style='hand-drawn-indie'] {
1359
+ /* Silhouette — WONKY hand-drawn corners. The slash splits horizontal /
1360
+ vertical corner radii, and the four values per side differ widely, so each
1361
+ corner of a box is a different curve (the "drawn rectangle" tell). Generic
1362
+ scale stays soft + single-valued; the SEMANTIC surfaces carry the wonk. */
1363
+ --radius-xs: 6px;
1364
+ --radius-sm: 9px;
1365
+ --radius-md: 13px;
1366
+ --radius-lg: 18px;
1367
+ --radius-xl: 24px;
1368
+ --radius-btn: 14px 40px 12px 36px / 36px 14px 38px 16px;
1369
+ --radius-input: 16px 48px 14px 42px / 42px 16px 46px 18px;
1370
+ --radius-card: 18px 58px 22px 48px / 48px 20px 54px 24px;
1371
+ --radius-modal: 22px 66px 20px 56px / 56px 22px 60px 26px;
1372
+ --radius-badge: 9px 26px 11px 22px / 22px 11px 24px 9px;
1373
+ --radius-control: 10px 26px 12px 22px / 22px 12px 24px 10px;
1374
+ --radius-kbd: 6px 18px 8px 15px / 15px 8px 16px 6px;
1375
+
1376
+ /* Elevation — soft, hand-placed offset shadows (a doodled drop, nudged
1377
+ down-right); friendly + low, never a hard brutalist block. Warm ink. */
1378
+ --shadow-subtle: 1px 2px 4px -1px rgba(38, 30, 22, 0.1);
1379
+ --shadow-card: 2px 4px 9px -3px rgba(38, 30, 22, 0.14);
1380
+ --shadow-elevated: 3px 6px 16px -4px rgba(38, 30, 22, 0.17);
1381
+ --shadow-modal: 5px 9px 30px -8px rgba(38, 30, 22, 0.22);
1382
+ --shadow-hero-mockup: 6px 12px 40px -10px rgba(38, 30, 22, 0.22);
1383
+
1384
+ /* Density — roomy + relaxed; the sketch wants margins around it. */
1385
+ --spacing-btn-x: 20px;
1386
+ --spacing-btn-y: 12px;
1387
+ --spacing-input-x: 16px;
1388
+ --spacing-input-y: 12px;
1389
+ --spacing-card-padding: 26px;
1390
+ --spacing-control-x: 12px;
1391
+ --spacing-control-y: 8px;
1392
+ --height-control: 38px;
1393
+ --height-input: 44px;
1394
+
1395
+ /* Motion — springy + playful: a bouncy hover lift and a deeper press squish. */
1396
+ --transition-duration: 200ms;
1397
+ --transition-fast: 130ms;
1398
+ --transition-slow: 320ms;
1399
+ --hover-scale: 1.02;
1400
+ --active-scale: 0.97;
1401
+ }
1402
+
1403
+ /* The ROUGH, hand-inked edge. A wavy ink line is drawn on a `::after` overlay
1404
+ warped by the `#hd-rough` turbulence filter — the move that makes lines look
1405
+ DRAWN, not machine-straight. The overlay is `pointer-events: none` and only it
1406
+ is filtered, so the surface's own text/content stays CRISP (filtering the
1407
+ element itself would smear the text). The real border is KEPT (2px) as the
1408
+ structural fallback. The ink is palette-DERIVED (var(--el-border-strong)), so
1409
+ the colour axis stays disjoint and it adapts to dark mode.
1410
+
1411
+ The overlay needs a positioned host. We add `position: relative` ONLY to
1412
+ STATIC elements: a `fixed`/`absolute` element (a centered modal / command
1413
+ palette — both `.fixed .border`) or a `sticky` header is ALREADY a containing
1414
+ block, so overriding its position would break it (overriding `.fixed` is what
1415
+ knocked the modals off-centre). Those positioned elements still get the `::after`
1416
+ overlay (they're already a containing block), just not the position override.
1417
+
1418
+ Two line kinds are roughened so the frame AND the inner grid match (no straight
1419
+ dividers inside a wavy card): the full-box `.border`, and the single-side
1420
+ dividers `.border-b` / `.border-t` (table rows, section rules) — the latter
1421
+ scoped INSIDE `[data-surface]` panels to bound the count to real surfaces (and
1422
+ their rows are virtualised, so only ~viewport-many overlays ever render). */
1423
+ [data-style='hand-drawn-indie'] .border {
1424
+ border-width: 2px;
1425
+ }
1426
+ [data-style='hand-drawn-indie'] .border:not(.fixed):not(.absolute):not(.sticky) {
1427
+ position: relative;
1428
+ }
1429
+ [data-style='hand-drawn-indie'] .border::after {
1430
+ content: '';
1431
+ position: absolute;
1432
+ inset: -1px;
1433
+ pointer-events: none;
1434
+ border: 2px solid var(--el-border-strong);
1435
+ border-radius: inherit;
1436
+ filter: url(#hd-rough);
1437
+ }
1438
+
1439
+ /* Internal grid dividers — make the inner rules as rough as the frame. Scoped
1440
+ inside framed surfaces; `:not(.border)` so a full-box element isn't handled
1441
+ twice; `:not(:last-child)` mirrors the common `last:border-b-0` strip so the
1442
+ final row gets no phantom line. */
1443
+ [data-style='hand-drawn-indie']
1444
+ [data-surface]
1445
+ :is(.border-b, .border-t):not(.border):not(.fixed):not(.absolute):not(.sticky) {
1446
+ position: relative;
1447
+ }
1448
+ [data-style='hand-drawn-indie'] [data-surface] .border-b:not(.border)::after {
1449
+ content: '';
1450
+ position: absolute;
1451
+ left: 0;
1452
+ right: 0;
1453
+ bottom: -1px;
1454
+ height: 0;
1455
+ pointer-events: none;
1456
+ border-bottom: 2px solid var(--el-border-strong);
1457
+ filter: url(#hd-rough);
1458
+ }
1459
+ [data-style='hand-drawn-indie'] [data-surface] .border-t:not(.border)::after {
1460
+ content: '';
1461
+ position: absolute;
1462
+ left: 0;
1463
+ right: 0;
1464
+ top: -1px;
1465
+ height: 0;
1466
+ pointer-events: none;
1467
+ border-top: 2px solid var(--el-border-strong);
1468
+ filter: url(#hd-rough);
1469
+ }
1470
+ /* Suppress the overlay only where the REAL divider is removed: the last row of a
1471
+ fully-rendered list strips its border via `last:border-b-0`. (We can't use
1472
+ `:not(:last-child)` on the draw rule — a STICKY table header sits alone in its
1473
+ own `role="rowgroup"`, so it is itself a `:last-child` and would be wrongly
1474
+ skipped, which left the work-item table header's divider straight.) Same
1475
+ specificity as the draw rule, placed after, so it wins on the real last row. */
1476
+ [data-style='hand-drawn-indie'] [data-surface] .last\:border-b-0:last-child::after {
1477
+ content: none;
1478
+ }
1479
+
1480
+ /* The APP SHELL FRAME — the sidebar rail's right edge + the top bar's bottom edge
1481
+ (MOTIR-1315). These use DIRECTIONAL borders (border-r / border-b) the full-box
1482
+ `.border` rule doesn't match, and the rail/header sit OUTSIDE the [data-surface]
1483
+ panels the divider rules scope to — so the shell frame stayed machine-straight
1484
+ while every card looked hand-drawn. Draw the rough ink directly on the
1485
+ data-surface host: the rail (data-surface="sidebar") gets a wavy RIGHT edge, the
1486
+ header (data-surface="header") a wavy BOTTOM edge. Palette-DERIVED ink
1487
+ (var(--el-border-strong)), so the colour axis stays disjoint. Add `position`
1488
+ ONLY to a STATIC host (the rail) — never the STICKY header, which is already a
1489
+ containing block (overriding it would break the sticky), mirroring the `.border`
1490
+ rule's guard. */
1491
+ [data-style='hand-drawn-indie']
1492
+ :is([data-surface='sidebar'], [data-surface='header']):not(.fixed):not(.absolute):not(.sticky) {
1493
+ position: relative;
1494
+ }
1495
+ [data-style='hand-drawn-indie'] [data-surface='sidebar']::after {
1496
+ content: '';
1497
+ position: absolute;
1498
+ top: 0;
1499
+ bottom: 0;
1500
+ right: -1px;
1501
+ width: 0;
1502
+ pointer-events: none;
1503
+ border-right: 2px solid var(--el-border-strong);
1504
+ filter: url(#hd-rough);
1505
+ }
1506
+ [data-style='hand-drawn-indie'] [data-surface='header']::after {
1507
+ content: '';
1508
+ position: absolute;
1509
+ left: 0;
1510
+ right: 0;
1511
+ bottom: -1px;
1512
+ height: 0;
1513
+ pointer-events: none;
1514
+ border-bottom: 2px solid var(--el-border-strong);
1515
+ filter: url(#hd-rough);
1516
+ }
1517
+
1518
+ /* ─────────────────────────────────────────────────────────────────────────
1519
+ Neumorphism — the soft-extruded "soft UI" style (7.3.42, DEFERRED follow-up).
1520
+ EXPERIMENTAL. The identity is a SINGLE MONOCHROME FIELD out of which every
1521
+ surface is moulded by a DUAL soft shadow — a light highlight (top-left) paired
1522
+ with a palette-derived dark shadow (bottom-right) — so a card swells OUT of the
1523
+ canvas (raised) and a form field sinks INTO it (an inset well). No translucency,
1524
+ no gradient, no glow: just continuous opaque material + paired soft shadows.
1525
+ Ships in two parts, like the other surface-material styles (glass / aurora):
1526
+ 1. This [data-style] TOKEN BLOCK — shape/feel tokens ONLY (soft radii, soft
1527
+ NEUTRAL shadows, gentle motion, roomy density) PLUS palette-AGNOSTIC
1528
+ material SCALARS (the extrusion distance + blur, the white highlight, and
1529
+ the dark-shadow strength %). No hue lives here, so it stays disjoint from
1530
+ the palette axis — enforced by the token-block test in styleRegistry.test.ts.
1531
+ 2. A palette-DERIVED MATERIAL LAYER (the rules below) that paints the
1532
+ monochrome field + the dual raised/inset shadows. The dark shadow is
1533
+ color-mix() over the active palette's `--el-text` ink (NO new hue); the
1534
+ light highlight is palette-AGNOSTIC white (like glass's --glass-rim), so a
1535
+ palette swap re-tones the moulded surface and a style swap leaves hues be.
1536
+ ── Accessibility (the card's "accessibility-hard, gate carefully" caveat) ──
1537
+ Soft-UI's classic failure is low contrast — borderless same-colour surfaces and
1538
+ text that washes out. Gated by construction here: surfaces stay OPAQUE at the
1539
+ palette's own AA-tuned `--el-surface` (text contrast preserved, like aurora), a
1540
+ faint hairline border is KEPT (not removed) so structure never relies on shadow
1541
+ alone, and a `prefers-contrast: more` / `forced-colors: active` fallback below
1542
+ restores a solid border and flattens the shadow for users who need it.
1543
+ See docs/styles/neumorphism.md. ──────────────────────────────────────────── */
1544
+ [data-style='neumorphism'] {
1545
+ /* Silhouette — soft, generously rounded pebbles; the rounding lets the paired
1546
+ shadow wrap each surface. --radius-pill / circular affordances untouched. */
1547
+ --radius-xs: 6px;
1548
+ --radius-sm: 8px;
1549
+ --radius-md: 12px;
1550
+ --radius-lg: 16px;
1551
+ --radius-xl: 20px;
1552
+ --radius-btn: 12px;
1553
+ --radius-input: 12px;
1554
+ --radius-card: 18px;
1555
+ --radius-modal: 22px;
1556
+ --radius-badge: 9999px; /* chips stay pills — soft is the neumorphic register */
1557
+ --radius-control: 10px;
1558
+ --radius-kbd: 6px;
1559
+
1560
+ /* Elevation — soft, diffuse NEUTRAL base lift for any non-data-surface elevated
1561
+ element (tooltips, bare dropdowns). The dual raised/inset MATERIAL shadow is
1562
+ palette-derived and lives in the material layer below (not here, where no hue
1563
+ may appear). rgba literals are neutral shadow ink, not a palette hue. */
1564
+ --shadow-subtle: 0 1px 2px rgba(20, 20, 30, 0.05);
1565
+ --shadow-card: 0 6px 18px -10px rgba(20, 20, 30, 0.14);
1566
+ --shadow-elevated: 0 12px 30px -14px rgba(20, 20, 30, 0.18);
1567
+ --shadow-modal: 0 22px 50px -18px rgba(20, 20, 30, 0.24);
1568
+ --shadow-hero-mockup: 0 26px 60px -18px rgba(20, 20, 30, 0.28);
1569
+
1570
+ /* Density — comfortable, roomy; the soft shadows need air to read as extruded. */
1571
+ --spacing-btn-x: 20px;
1572
+ --spacing-btn-y: 11px;
1573
+ --spacing-card-padding: 26px;
1574
+ --spacing-control-x: 12px;
1575
+ --spacing-control-y: 7px;
1576
+ --height-control: 38px;
1577
+
1578
+ /* Motion — gentle; a soft press-scale, nothing snaps. */
1579
+ --transition-duration: 200ms;
1580
+ --transition-slow: 300ms;
1581
+ --active-scale: 0.98;
1582
+
1583
+ /* ── Material SCALARS — palette-AGNOSTIC (no hue, so no colour token here).
1584
+ Consumed only by the [data-style='neumorphism'] material rules below: how far
1585
+ the paired shadow is offset, how soft it blurs, and the two shadow inks (the
1586
+ top-left highlight + the bottom-right dark). Both inks are NEUTRAL rgba (white
1587
+ light + near-black shade) — like glass's --glass-rim / aurora's --shadow-*:
1588
+ they are shadow ink, not a palette hue, so the token block stays colour-free.
1589
+ A neumorphic mould needs a CONSTANT light/dark pairing (highlight always
1590
+ lighter, shadow always darker than the surface) — deriving the dark ink from
1591
+ a palette text token would INVERT it in dark mode (the ink goes light), so the
1592
+ ink stays neutral and the [data-theme='dark'] block below re-balances it. ── */
1593
+ --neu-distance: 6px; /* offset of the paired shadow on a card / panel */
1594
+ --neu-blur: 16px; /* how soft the paired shadow blurs (the "swell") */
1595
+ --neu-distance-sm: 3px; /* tighter offset for an inset input well */
1596
+ --neu-blur-sm: 8px; /* tighter blur for the inset well */
1597
+ --neu-light: rgba(255, 255, 255, 0.6); /* top-left raised HIGHLIGHT (light ink) */
1598
+ --neu-shadow: rgba(20, 20, 30, 0.16); /* bottom-right dark shadow (shade ink) */
1599
+ }
1600
+
1601
+ /* Dark theme re-balances the two inks: on a dark surface a 0.6 white halo is far
1602
+ too loud and a 0.16 near-black shade barely shows, so the highlight drops to a
1603
+ faint lighten and the shade deepens to a real black — keeping the constant
1604
+ light-above / dark-below mould readable in both themes. Scoped to BOTH axis
1605
+ attributes on <html> (NOT a bare [data-style] token block, so the disjoint-axis
1606
+ test does not read it); sets only neutral rgba scalars, no hue. */
1607
+ [data-style='neumorphism'][data-theme='dark'] {
1608
+ --neu-light: rgba(255, 255, 255, 0.05);
1609
+ --neu-shadow: rgba(0, 0, 0, 0.44);
1610
+ }
1611
+
1612
+ /* ─────────────────────────────────────────────────────────────────────────
1613
+ Neumorphism MATERIAL LAYER — palette-DERIVED surface treatment (7.3.42).
1614
+ The same style-contract extension glass / aurora use: a style may own its
1615
+ surface material in STYLE-SCOPED component rules as long as every colour is
1616
+ DERIVED from the active palette via color-mix()/var() (the dark shadow + the
1617
+ monochrome field) — never a raw hue (the white highlight is palette-agnostic
1618
+ light, not a hue). These descendant-scoped selectors are NOT the bare token
1619
+ block, so they never trip the colour-free token-block test; they ARE read as
1620
+ material rules and are palette-derived (enforced by the "surface-material
1621
+ layer" test in styleRegistry.test.ts). ──────────────────────────────────── */
1622
+
1623
+ /* The monochrome field: the page canvas adopts the palette's opaque surface so
1624
+ panels look MOULDED OUT OF the same material rather than placed on top. Both
1625
+ the canvas and the surfaces below share `var(--el-surface)`, which is the whole
1626
+ neumorphic illusion. Palette-derived (no hue of its own). */
1627
+ [data-style='neumorphism'] body {
1628
+ background-color: var(--el-surface);
1629
+ }
1630
+
1631
+ /* Raised container surfaces — cards & popovers swell OUT of the field on a paired
1632
+ soft shadow: a light highlight top-left + a dark shadow bottom-right. Both inks
1633
+ are neutral (palette-agnostic) shadow ink; the SURFACE + border are
1634
+ palette-derived, so the mould re-tones per palette and the style stays disjoint
1635
+ from hue. The faint hairline is kept as the accessibility fallback so structure
1636
+ doesn't depend on the shadow alone. Opaque `--el-surface` keeps text on the
1637
+ palette's AA-tuned surface. */
1638
+ [data-style='neumorphism'] [data-surface='card'],
1639
+ [data-style='neumorphism'] [data-surface='popover'] {
1640
+ background-color: var(--el-surface);
1641
+ border-color: color-mix(in srgb, var(--el-border) 45%, transparent);
1642
+ box-shadow:
1643
+ var(--neu-distance) var(--neu-distance) var(--neu-blur) var(--neu-shadow),
1644
+ calc(-1 * var(--neu-distance)) calc(-1 * var(--neu-distance)) var(--neu-blur) var(--neu-light);
1645
+ }
1646
+
1647
+ /* Modal panel — the same raised mould, a touch deeper so the dialog separates
1648
+ from the field behind it (body text stays on the opaque surface). */
1649
+ [data-style='neumorphism'] [data-surface='modal'] {
1650
+ background-color: var(--el-surface);
1651
+ border-color: color-mix(in srgb, var(--el-border) 45%, transparent);
1652
+ box-shadow:
1653
+ calc(var(--neu-distance) + 2px) calc(var(--neu-distance) + 2px) calc(var(--neu-blur) + 8px)
1654
+ var(--neu-shadow),
1655
+ calc(-1 * var(--neu-distance)) calc(-1 * var(--neu-distance)) calc(var(--neu-blur) + 4px)
1656
+ var(--neu-light);
1657
+ }
1658
+
1659
+ /* The sidebar rail — moulded from the same field as the chrome, gently raised so
1660
+ it reads as part of the one continuous surface. */
1661
+ [data-style='neumorphism'] [data-surface='sidebar'] {
1662
+ background-color: var(--el-surface);
1663
+ box-shadow:
1664
+ var(--neu-distance) 0 var(--neu-blur) var(--neu-shadow),
1665
+ calc(-1 * var(--neu-distance)) 0 var(--neu-blur) var(--neu-light);
1666
+ }
1667
+
1668
+ /* Form controls — the inverse mould: a CONCAVE INSET well pressed INTO the field
1669
+ (the soft-UI input look), the paired shadow flipped to `inset`. */
1670
+ [data-style='neumorphism'] [data-surface='input'] {
1671
+ background-color: var(--el-surface);
1672
+ border-color: color-mix(in srgb, var(--el-border) 45%, transparent);
1673
+ box-shadow:
1674
+ inset var(--neu-distance-sm) var(--neu-distance-sm) var(--neu-blur-sm) var(--neu-shadow),
1675
+ inset calc(-1 * var(--neu-distance-sm)) calc(-1 * var(--neu-distance-sm)) var(--neu-blur-sm)
1676
+ var(--neu-light);
1677
+ }
1678
+
1679
+ /* Accessibility fallback — when a user asks for more contrast (or forced colours),
1680
+ structure must NOT rest on the soft shadow. Restore a solid hairline on every
1681
+ moulded surface and flatten the paired shadow to a single readable lift.
1682
+ Palette-derived (var(--el-border)); no hue. */
1683
+ @media (prefers-contrast: more) {
1684
+ [data-style='neumorphism'] [data-surface='card'],
1685
+ [data-style='neumorphism'] [data-surface='popover'],
1686
+ [data-style='neumorphism'] [data-surface='modal'],
1687
+ [data-style='neumorphism'] [data-surface='input'],
1688
+ [data-style='neumorphism'] [data-surface='sidebar'] {
1689
+ border-color: var(--el-border-strong);
1690
+ box-shadow: var(--shadow-card);
1691
+ }
1692
+ }
1693
+ @media (forced-colors: active) {
1694
+ [data-style='neumorphism'] [data-surface='card'],
1695
+ [data-style='neumorphism'] [data-surface='popover'],
1696
+ [data-style='neumorphism'] [data-surface='modal'],
1697
+ [data-style='neumorphism'] [data-surface='input'],
1698
+ [data-style='neumorphism'] [data-surface='sidebar'] {
1699
+ border-color: var(--el-border-strong);
1700
+ box-shadow: none;
1701
+ }
1702
+ }
1703
+
1704
+ /* ─────────────────────────────────────────────────────────────────────────
1705
+ Retrofuturism — chrome + gradient sweeps + retro display (7.3.40, DEFERRED
1706
+ follow-up). The 70s/80s sci-fi / Outrun look: a synthwave HORIZON gradient
1707
+ sweeps the canvas, and every surface wears a CHROME BEVEL (a bright top
1708
+ highlight fading to a dark base) plus a diagonal reflective sheen, so panels
1709
+ read as machined chrome rather than flat cards. The retro display read is the
1710
+ `grotesk` type pairing (the style's defaultTypeId — wide geometric headlines),
1711
+ NOT a `--font-*` override here (type is the independent data-type axis, 7.3.53).
1712
+ Overrides ONLY shape/feel tokens — never a colour token (the data-palette
1713
+ axis; the style wears whatever palette is active, so AA contrast is preserved
1714
+ by construction). The metallic bevel/sheen lives in the MATERIAL LAYER below,
1715
+ palette-DERIVED: every colour comes from color-mix()/var(--el-*) over the
1716
+ active palette, or from the achromatic white/black KEYWORDS that make the
1717
+ chrome read as lightness — never a raw hex hue (so the style stays disjoint
1718
+ from the palette; enforced by tests/theme/styleRegistry.test.ts). The
1719
+ differentiator vs Cybercore/Y2K: cybercore GLOWS neon on dark behind a tech
1720
+ grid; retrofuturism is REFLECTIVE chrome over a sunset horizon. See
1721
+ docs/styles/retrofuturism.md. ──────────────────────────────────────────── */
1722
+ [data-style='retrofuturism'] {
1723
+ /* Silhouette — streamlined chrome capsules. --radius-pill stays untouched so
1724
+ genuinely-circular affordances (avatars, status dots, the spinner) stay
1725
+ round. Buttons + chips go FULL pill (chrome lozenges); panels stay softly
1726
+ rounded. */
1727
+ --radius-xs: 6px;
1728
+ --radius-sm: 9px;
1729
+ --radius-md: 12px;
1730
+ --radius-lg: 16px;
1731
+ --radius-xl: 22px;
1732
+ --radius-btn: 9999px; /* chrome-capsule buttons */
1733
+ --radius-input: 12px;
1734
+ --radius-card: 16px;
1735
+ --radius-modal: 22px;
1736
+ --radius-badge: 9999px; /* status chips stay pills — the streamlined register */
1737
+ --radius-control: 10px;
1738
+ --radius-kbd: 6px;
1739
+
1740
+ /* Elevation — a soft NEUTRAL lift only. The chrome bevel highlight + the
1741
+ palette-derived colour glow are painted in the material layer below (no hue
1742
+ may appear here); these neutral rgba shadows are the diffuse base the chrome
1743
+ sits on. (rgba literals are neutral shadow ink — a cool near-black — not a
1744
+ palette hue, so the token block stays colour-free.) */
1745
+ --shadow-subtle: 0 1px 2px rgba(10, 12, 30, 0.06);
1746
+ --shadow-card: 0 10px 26px -12px rgba(10, 12, 30, 0.22);
1747
+ --shadow-elevated: 0 18px 40px -14px rgba(10, 12, 30, 0.28);
1748
+ --shadow-modal: 0 28px 60px -18px rgba(10, 12, 30, 0.34);
1749
+ --shadow-hero-mockup: 0 34px 74px -18px rgba(10, 12, 30, 0.4);
1750
+
1751
+ /* Density — confident and roomy; the chrome wants room to catch the light. */
1752
+ --spacing-btn-x: 22px;
1753
+ --spacing-btn-y: 11px;
1754
+ --spacing-card-padding: 24px;
1755
+ --spacing-control-x: 12px;
1756
+ --spacing-control-y: 7px;
1757
+ --height-control: 38px;
1758
+
1759
+ /* Motion — smooth and gliding; transitions ease, with a confident press. */
1760
+ --transition-duration: 200ms;
1761
+ --transition-slow: 320ms;
1762
+ --active-scale: 0.97;
1763
+
1764
+ /* ── Material SCALARS — palette-AGNOSTIC (no hue, so no colour token here).
1765
+ Consumed only by the [data-style='retrofuturism'] material rules below: the
1766
+ synthwave SKY / HORIZON / GROUND band strengths + the neon GRID line alpha
1767
+ and cell size (the Outrun floor), the chrome SPECULAR streak + bevel
1768
+ highlight/shade (mixed from the achromatic white/black keywords), and the
1769
+ palette-derived glow. Bold by design — this style is dark-FIRST (the neon
1770
+ horizon + grid read strongest on a dark palette), so the strengths run high.
1771
+ ── */
1772
+ --retro-sky: 24%; /* the upper-sky wash (cool, from --el-link) */
1773
+ --retro-horizon: 72%; /* the bright sun/horizon GLOW band — the signature */
1774
+ --retro-ground: 26%; /* the warm ground wash rising from the bottom */
1775
+ --retro-grid: 52%; /* the neon perspective-grid line alpha */
1776
+ --retro-grid-size: 60px; /* the grid cell size before the perspective tilt */
1777
+ --retro-spec: 44%; /* the bright diagonal SPECULAR streak across chrome */
1778
+ --retro-bevel-light: 44%; /* the bright top metallic bevel highlight (white mix) */
1779
+ --retro-bevel-shade: 28%; /* the dark base of the bevel (black mix) */
1780
+ --retro-sheen: 18%; /* the quieter chrome edge tint (rail / input) */
1781
+ --retro-glow: 32%; /* palette-derived colour glow on a card / popover */
1782
+ --retro-glow-strong: 44%; /* the modal's / primary button's deeper glow */
1783
+ }
1784
+
1785
+ /* ─────────────────────────────────────────────────────────────────────────
1786
+ Retrofuturism MATERIAL LAYER — palette-DERIVED chrome + horizon (7.3.40).
1787
+ The same style-contract extension glassmorphism / aurora use: a style may own
1788
+ its surface material in STYLE-SCOPED component rules as long as every colour
1789
+ is DERIVED from the active palette via color-mix()/var() (or the achromatic
1790
+ white/black keywords for pure metallic lightness) — never a raw hex hue — so
1791
+ the style axis stays disjoint from the palette axis. These descendant-scoped
1792
+ selectors are NOT the bare token block, so they never trip the colour-free
1793
+ token-block test; they ARE read as material rules and must be palette-derived
1794
+ (enforced by the "surface-material layer" test in styleRegistry.test.ts).
1795
+ ───────────────────────────────────────────────────────────────────────── */
1796
+
1797
+ /* The canvas: the palette's page bg + a SYNTHWAVE SKY — a cool upper-sky wash, a
1798
+ bright sun/HORIZON GLOW band ~62% down, and a warm ground wash rising from the
1799
+ bottom. Every hue comes from the `--el-*` accent roles (NOT Tier-0
1800
+ `--color-*`), so the sky re-tints with the active palette — the `data-palette`
1801
+ axis overrides `--el-*`, never `--color-*`, which keeps the horizon orthogonal
1802
+ to the palette. Painted as the body's own background (always behind content),
1803
+ the proven 3D/Immersive atmosphere approach. */
1804
+ [data-style='retrofuturism'] body {
1805
+ background-image:
1806
+ linear-gradient(
1807
+ 180deg,
1808
+ transparent 54%,
1809
+ color-mix(in srgb, var(--el-highlight) var(--retro-horizon), transparent) 60.5%,
1810
+ color-mix(in srgb, var(--el-accent) var(--retro-horizon), transparent) 62.5%,
1811
+ transparent 69%
1812
+ ),
1813
+ linear-gradient(
1814
+ 180deg,
1815
+ color-mix(in srgb, var(--el-link) var(--retro-sky), transparent),
1816
+ transparent 46%
1817
+ ),
1818
+ linear-gradient(
1819
+ 0deg,
1820
+ color-mix(in srgb, var(--el-accent) var(--retro-ground), transparent),
1821
+ transparent 42%
1822
+ );
1823
+ background-size: 100% 100%;
1824
+ background-attachment: fixed;
1825
+ }
1826
+
1827
+ /* The Outrun GRID FLOOR — a receding neon grid below the horizon. A separate
1828
+ fixed layer (`body::after`) so it can be perspective-tilted and masked to fade
1829
+ into the horizon; `z-index: -1` keeps it BEHIND all app content (above only the
1830
+ body's own sky background), so it textures the floor without ever covering the
1831
+ UI. Lines are `color-mix()` over `--el-*` accents (palette-derived, no hue of
1832
+ their own); the mask uses the achromatic `black` keyword (no hex). The grid is
1833
+ STATIC depth — like 3D/Immersive's atmosphere it carries the identity, so it is
1834
+ not motion-gated (there is no animation to suppress). */
1835
+ [data-style='retrofuturism'] body::after {
1836
+ content: '';
1837
+ position: fixed;
1838
+ left: -30%;
1839
+ right: -30%;
1840
+ bottom: 0;
1841
+ height: 42vh;
1842
+ background-image:
1843
+ repeating-linear-gradient(
1844
+ 0deg,
1845
+ color-mix(in srgb, var(--el-highlight) var(--retro-grid), transparent) 0 2px,
1846
+ transparent 2px var(--retro-grid-size)
1847
+ ),
1848
+ repeating-linear-gradient(
1849
+ 90deg,
1850
+ color-mix(in srgb, var(--el-accent) var(--retro-grid), transparent) 0 2px,
1851
+ transparent 2px var(--retro-grid-size)
1852
+ );
1853
+ background-size: var(--retro-grid-size) var(--retro-grid-size);
1854
+ transform: perspective(300px) rotateX(75deg);
1855
+ transform-origin: bottom center;
1856
+ -webkit-mask-image: linear-gradient(to top, black 2%, transparent 80%);
1857
+ mask-image: linear-gradient(to top, black 2%, transparent 80%);
1858
+ pointer-events: none;
1859
+ z-index: -1;
1860
+ }
1861
+
1862
+ /* Chrome container surfaces — OPAQUE (text AA is preserved against the base
1863
+ surface the palette already tuned) carrying a diagonal SPECULAR streak (a
1864
+ bright `white` light-bar swept across the panel, the reflective-metal read) AND
1865
+ a vertical CHROME BEVEL (bright `white` top → dark `black` base — both
1866
+ achromatic lightness, no hue), over a soft lift + a palette-derived colour
1867
+ glow. The streak/bevel ride background-image over the opaque background-color,
1868
+ so they never pull a surface far from its base tone; the inset top/bottom lines
1869
+ are the lit chrome rim. Applied to EVERY panel via the global card-radius
1870
+ utility (the 3D/Immersive coverage approach), not just the <Card> primitive. */
1871
+ [data-style='retrofuturism'] [data-surface='card'],
1872
+ [data-style='retrofuturism'] [data-surface='popover'],
1873
+ [data-style='retrofuturism'] .rounded-\(--radius-card\) {
1874
+ background-color: var(--el-surface);
1875
+ background-image:
1876
+ linear-gradient(
1877
+ 158deg,
1878
+ transparent 36%,
1879
+ color-mix(in srgb, white var(--retro-spec), transparent) 49%,
1880
+ transparent 62%
1881
+ ),
1882
+ linear-gradient(
1883
+ 180deg,
1884
+ color-mix(in srgb, white var(--retro-bevel-light), transparent) 0%,
1885
+ transparent 16%,
1886
+ transparent 78%,
1887
+ color-mix(in srgb, black var(--retro-bevel-shade), transparent) 100%
1888
+ );
1889
+ box-shadow:
1890
+ var(--shadow-card),
1891
+ inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
1892
+ inset 0 -1px 0 color-mix(in srgb, black 24%, transparent),
1893
+ 0 0 28px -8px color-mix(in srgb, var(--el-accent) var(--retro-glow), transparent);
1894
+ }
1895
+
1896
+ /* Modal panel — the same chrome, with a deeper palette glow so the dialog
1897
+ separates cleanly from the grid behind it (and body text stays on the solid
1898
+ surface). Overrides the card-radius rule above for the modal radius. */
1899
+ [data-style='retrofuturism'] [data-surface='modal'] {
1900
+ background-color: var(--el-surface);
1901
+ background-image:
1902
+ linear-gradient(
1903
+ 158deg,
1904
+ transparent 34%,
1905
+ color-mix(in srgb, white var(--retro-spec), transparent) 49%,
1906
+ transparent 64%
1907
+ ),
1908
+ linear-gradient(
1909
+ 180deg,
1910
+ color-mix(in srgb, white var(--retro-bevel-light), transparent) 0%,
1911
+ transparent 14%,
1912
+ transparent 80%,
1913
+ color-mix(in srgb, black var(--retro-bevel-shade), transparent) 100%
1914
+ );
1915
+ box-shadow:
1916
+ var(--shadow-modal),
1917
+ inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
1918
+ 0 0 56px -14px color-mix(in srgb, var(--el-highlight) var(--retro-glow-strong), transparent);
1919
+ }
1920
+
1921
+ /* The sidebar rail — opaque chrome with a quieter specular + bevel so it reads as
1922
+ part of the polished-metal family, not a flat slab. */
1923
+ [data-style='retrofuturism'] [data-surface='sidebar'] {
1924
+ background-color: var(--el-sidebar-bg);
1925
+ background-image:
1926
+ linear-gradient(
1927
+ 158deg,
1928
+ transparent 40%,
1929
+ color-mix(in srgb, white var(--retro-sheen), transparent) 50%,
1930
+ transparent 60%
1931
+ ),
1932
+ linear-gradient(
1933
+ 180deg,
1934
+ color-mix(in srgb, white var(--retro-sheen), transparent),
1935
+ transparent 24%,
1936
+ color-mix(in srgb, black var(--retro-bevel-shade), transparent) 100%
1937
+ );
1938
+ }
1939
+
1940
+ /* Text inputs (a TYPING field) — a RECESSED chrome well: the bevel inverts (a
1941
+ dark lip at the top) + an inset shadow, so the field reads as stamped INTO the
1942
+ metal. Clickable controls (dropdown / select / picker triggers + buttons) get
1943
+ the RAISED chrome below instead — matching the CTA — so a typing field stays
1944
+ recessed while everything you click reads as a raised chrome control. */
1945
+ [data-style='retrofuturism'] [data-surface='input'] {
1946
+ background-color: var(--el-page-bg);
1947
+ background-image: linear-gradient(
1948
+ 180deg,
1949
+ color-mix(in srgb, black var(--retro-sheen), transparent) 0%,
1950
+ transparent 34%
1951
+ );
1952
+ box-shadow: inset 0 1px 3px color-mix(in srgb, black var(--retro-glow), transparent);
1953
+ }
1954
+
1955
+ /* ── EVERY clickable control reads as RETRO CHROME (7.3.40) ──────────────────
1956
+ The whole point of the style: not just the `Button` primitive, but every
1957
+ button-SHAPED control gets a machined-chrome treatment, so nothing sits flat.
1958
+ Buttons render in THREE shapes across the app, so keying on `data-variant`
1959
+ alone (only the `Button` primitive sets it) misses most of them — these rules
1960
+ instead key on the UTILITY CLASSES the design system emits
1961
+ (`.rounded-(--radius-btn)` + the fill / `.border` class), which are
1962
+ tag-agnostic and catch all of:
1963
+ • the `Button` primitive (`<button>` with `data-variant`),
1964
+ • `<a>` / `<Link>` styled via `buttonVariants()` (NO `data-variant` — e.g.
1965
+ the "View all issues" / empty-state links),
1966
+ • hand-rolled `<button>` / `<a>` with `rounded-(--radius-btn) border` (the
1967
+ /issues filter bar: Filter / Advanced / Saved / the Tree·List switcher),
1968
+ • input-styled dropdown / select / picker triggers
1969
+ (`button.rounded-(--radius-input)`),
1970
+ • the SEGMENTED control (`components/ui/Segmented.tsx` — the board "Group by"
1971
+ switch & friends: a `[role='group']` track + `aria-pressed` segments).
1972
+ Two families: a FILLED CTA (accent / danger) keeps its hue under a metallic
1973
+ sweep; everything else is NEUTRAL GUNMETAL steel. All palette-derived
1974
+ (color-mix / var(--el-*)); the light/shade are the achromatic white/black
1975
+ keywords (no hex). The gunmetal base is `--el-surface` DARKENED — on a light
1976
+ palette the bare surface ≈ the page, so a white-highlight bevel over it is
1977
+ invisible (flat white); a mid-grey gives the highlight AND the shade contrast,
1978
+ so the metal reads on light AND dark. ───────────────────────────────────── */
1979
+
1980
+ /* FILLED CTAs — accent (primary) keeps its hue under a diagonal specular glint +
1981
+ a vertical metallic sweep + a palette glow. Keyed on the fill class so it
1982
+ covers the `Button` primitive AND `buttonVariants()` anchors alike. */
1983
+ [data-style='retrofuturism'] .rounded-\(--radius-btn\).bg-\(--el-accent\) {
1984
+ background-image:
1985
+ linear-gradient(
1986
+ 158deg,
1987
+ transparent 38%,
1988
+ color-mix(in srgb, white var(--retro-spec), transparent) 50%,
1989
+ transparent 62%
1990
+ ),
1991
+ linear-gradient(
1992
+ 180deg,
1993
+ color-mix(in srgb, white var(--retro-spec), var(--el-accent)) 0%,
1994
+ var(--el-accent) 46%,
1995
+ color-mix(in srgb, black var(--retro-bevel-shade), var(--el-accent)) 100%
1996
+ );
1997
+ box-shadow:
1998
+ var(--shadow-subtle),
1999
+ inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
2000
+ 0 0 22px -6px color-mix(in srgb, var(--el-accent) var(--retro-glow-strong), transparent);
2001
+ }
2002
+
2003
+ /* Danger CTAs — the same machined sweep, in the danger hue (so a destructive
2004
+ button reads as red chrome, not a flat red, matching the accent CTA). */
2005
+ [data-style='retrofuturism'] .rounded-\(--radius-btn\).bg-\(--el-danger\) {
2006
+ background-image:
2007
+ linear-gradient(
2008
+ 158deg,
2009
+ transparent 38%,
2010
+ color-mix(in srgb, white var(--retro-spec), transparent) 50%,
2011
+ transparent 62%
2012
+ ),
2013
+ linear-gradient(
2014
+ 180deg,
2015
+ color-mix(in srgb, white var(--retro-spec), var(--el-danger)) 0%,
2016
+ var(--el-danger) 46%,
2017
+ color-mix(in srgb, black var(--retro-bevel-shade), var(--el-danger)) 100%
2018
+ );
2019
+ box-shadow:
2020
+ var(--shadow-subtle),
2021
+ inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
2022
+ 0 0 22px -6px color-mix(in srgb, var(--el-danger) var(--retro-glow-strong), transparent);
2023
+ }
2024
+
2025
+ /* NEUTRAL STEEL chrome — every OTHER clickable control: bordered buttons + links
2026
+ (secondary `Button`, secondary anchors, the hand-rolled filter-bar buttons, the
2027
+ "View all" links) and the dropdown / select / picker triggers.
2028
+ `:not([role='group'])` excludes the segmented TRACK (its own rule below); the
2029
+ FILLED CTAs are excluded automatically (they carry a fill class, not `.border`).
2030
+ The gunmetal base is set here because secondary controls ship `bg-transparent`. */
2031
+ [data-style='retrofuturism'] .rounded-\(--radius-btn\).border:not([role='group']),
2032
+ [data-style='retrofuturism'] button.rounded-\(--radius-input\) {
2033
+ background-color: color-mix(in srgb, black 24%, var(--el-surface));
2034
+ background-image:
2035
+ linear-gradient(
2036
+ 158deg,
2037
+ transparent 36%,
2038
+ color-mix(in srgb, white var(--retro-spec), transparent) 50%,
2039
+ transparent 64%
2040
+ ),
2041
+ linear-gradient(
2042
+ 180deg,
2043
+ color-mix(in srgb, white var(--retro-bevel-light), transparent) 0%,
2044
+ transparent 42%,
2045
+ transparent 60%,
2046
+ color-mix(in srgb, black var(--retro-bevel-shade), transparent) 100%
2047
+ );
2048
+ box-shadow:
2049
+ var(--shadow-subtle),
2050
+ inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
2051
+ inset 0 -1px 0 color-mix(in srgb, black 24%, transparent),
2052
+ 0 0 18px -8px color-mix(in srgb, var(--el-accent) var(--retro-glow), transparent);
2053
+ }
2054
+
2055
+ /* SEGMENTED control (the board "Group by" switch + every other `Segmented` use):
2056
+ the classic metal segmented control — a RECESSED gunmetal TRACK (the
2057
+ `[role='group']` wrapper) with the ACTIVE segment (`aria-pressed='true'`) raised
2058
+ out of it as a chrome chip. Inactive segments stay transparent on the track. */
2059
+ [data-style='retrofuturism'] [role='group'].rounded-\(--radius-btn\) {
2060
+ background-color: color-mix(in srgb, black 15%, var(--el-surface));
2061
+ box-shadow: inset 0 1px 3px color-mix(in srgb, black var(--retro-glow), transparent);
2062
+ }
2063
+ [data-style='retrofuturism'] [role='group'] [aria-pressed='true'] {
2064
+ background-color: color-mix(in srgb, black 24%, var(--el-surface));
2065
+ background-image:
2066
+ linear-gradient(
2067
+ 158deg,
2068
+ transparent 36%,
2069
+ color-mix(in srgb, white var(--retro-spec), transparent) 50%,
2070
+ transparent 64%
2071
+ ),
2072
+ linear-gradient(
2073
+ 180deg,
2074
+ color-mix(in srgb, white var(--retro-bevel-light), transparent) 0%,
2075
+ transparent 42%,
2076
+ transparent 60%,
2077
+ color-mix(in srgb, black var(--retro-bevel-shade), transparent) 100%
2078
+ );
2079
+ box-shadow:
2080
+ var(--shadow-subtle),
2081
+ inset 0 1px 0 color-mix(in srgb, white 60%, transparent),
2082
+ inset 0 -1px 0 color-mix(in srgb, black 24%, transparent);
2083
+ }
2084
+
2085
+ /* Hover — brighten the steel a touch (the controls' own `hover:bg-*` utilities
2086
+ are out-specified by the rules above, so restore a clear hover signal). */
2087
+ [data-style='retrofuturism']
2088
+ .rounded-\(--radius-btn\).border:not([role='group']):hover:not(:disabled),
2089
+ [data-style='retrofuturism'] button.rounded-\(--radius-input\):hover:not(:disabled),
2090
+ [data-style='retrofuturism'] [role='group'] [aria-pressed='false']:hover:not(:disabled) {
2091
+ background-color: color-mix(in srgb, black 14%, var(--el-surface));
2092
+ }
2093
+
2094
+ /* ─────────────────────────────────────────────────────────────────────────
2095
+ Style-preview vignette material canvas (7.3.37 / MOTIR-1050).
2096
+ The StyleVignette preview specimen carries its OWN axis attribute on a
2097
+ nested wrapper (`.style-vignette[data-style='…']`), so it cannot lean on the
2098
+ `body`-scoped material canvas above — a glassmorphism / cybercore vignette
2099
+ would otherwise float its frosted/glow surfaces over a flat fill. These rules
2100
+ paint the same canvas onto the vignette's `.sv-canvas` layer, scoped to a
2101
+ DIRECT child of the marked wrapper so an ancestor `<html data-style>` can't
2102
+ bleed the wrong canvas into a differently-scoped vignette. Palette-DERIVED
2103
+ (color-mix / currentColor over the active palette, never a raw hue) and
2104
+ descendant-scoped — so styleRegistry.test.ts reads them as material rules,
2105
+ not bare token blocks. */
2106
+ .style-vignette[data-style='glassmorphism'] > .sv-canvas {
2107
+ background-image:
2108
+ radial-gradient(
2109
+ 62% 70% at 12% -5%,
2110
+ color-mix(in srgb, var(--el-accent) var(--glass-wash), transparent),
2111
+ transparent 70%
2112
+ ),
2113
+ radial-gradient(
2114
+ 58% 65% at 105% 6%,
2115
+ color-mix(in srgb, var(--el-highlight) var(--glass-wash), transparent),
2116
+ transparent 68%
2117
+ ),
2118
+ radial-gradient(
2119
+ 72% 78% at 50% 112%,
2120
+ color-mix(in srgb, var(--el-link) var(--glass-wash), transparent),
2121
+ transparent 72%
2122
+ );
2123
+ }
2124
+ .style-vignette[data-style='cybercore-y2k'] > .sv-canvas {
2125
+ background-color: var(--el-page-bg);
2126
+ background-image:
2127
+ linear-gradient(color-mix(in srgb, currentColor 6%, transparent) 1px, transparent 1px),
2128
+ linear-gradient(90deg, color-mix(in srgb, currentColor 6%, transparent) 1px, transparent 1px);
2129
+ background-size: 26px 26px;
2130
+ }
2131
+ /* Aurora's drifting ribbons, painted statically onto the preview swatch (the
2132
+ gallery shows many vignettes at once, so the swatch holds a still frame of the
2133
+ canvas — the live drift is reserved for the full `body`). Palette-derived. */
2134
+ .style-vignette[data-style='aurora'] > .sv-canvas {
2135
+ background-color: var(--el-page-bg);
2136
+ background-image:
2137
+ radial-gradient(
2138
+ 58% 75% at 12% 0%,
2139
+ color-mix(in srgb, var(--el-accent) var(--aurora-wash), transparent),
2140
+ transparent 60%
2141
+ ),
2142
+ radial-gradient(
2143
+ 54% 70% at 92% 8%,
2144
+ color-mix(in srgb, var(--el-highlight) var(--aurora-wash), transparent),
2145
+ transparent 58%
2146
+ ),
2147
+ radial-gradient(
2148
+ 68% 82% at 62% 104%,
2149
+ color-mix(in srgb, var(--el-link) var(--aurora-wash), transparent),
2150
+ transparent 62%
2151
+ ),
2152
+ radial-gradient(
2153
+ 48% 64% at 34% 58%,
2154
+ color-mix(in srgb, var(--el-info) var(--aurora-wash-soft), transparent),
2155
+ transparent 55%
2156
+ );
2157
+ }
2158
+
2159
+ /* Retrofuturism's synthwave sky + horizon glow + a hint of the neon grid,
2160
+ painted statically onto the preview swatch (the gallery shows many vignettes
2161
+ at once, so the swatch holds a still frame — the live grid floor is reserved
2162
+ for the full `body`). Palette-derived. */
2163
+ .style-vignette[data-style='retrofuturism'] > .sv-canvas {
2164
+ background-color: var(--el-page-bg);
2165
+ background-image:
2166
+ repeating-linear-gradient(
2167
+ 90deg,
2168
+ color-mix(in srgb, var(--el-accent) var(--retro-grid), transparent) 0 1px,
2169
+ transparent 1px 18px
2170
+ ),
2171
+ linear-gradient(
2172
+ 180deg,
2173
+ transparent 56%,
2174
+ color-mix(in srgb, var(--el-highlight) var(--retro-horizon), transparent) 64%,
2175
+ transparent 72%
2176
+ ),
2177
+ linear-gradient(
2178
+ 180deg,
2179
+ color-mix(in srgb, var(--el-link) var(--retro-sky), transparent),
2180
+ transparent 50%
2181
+ ),
2182
+ linear-gradient(
2183
+ 0deg,
2184
+ color-mix(in srgb, var(--el-accent) var(--retro-ground), transparent),
2185
+ transparent 46%
2186
+ );
2187
+ background-size:
2188
+ 18px 100%,
2189
+ 100% 100%,
2190
+ 100% 100%,
2191
+ 100% 100%;
2192
+ background-position: center bottom;
2193
+ }
2194
+
2195
+ /* ═══════════════════════════════════════════════════════════════════════
2196
+ TIER 3 — Element tokens (--el-*)
2197
+ Components reference these, NEVER --color-* directly. This is the
2198
+ layer future palettes (data-palette="...") will override to swap the
2199
+ look without touching component code.
2200
+
2201
+ The COMPLETE semantic element-token layer (finding #54). Every colour a
2202
+ component renders flows through one of these — never a Tier-0 --color-*
2203
+ nor an auto-generated --color-* utility (text-foreground / bg-surface /
2204
+ text-muted-foreground), which bypass this swap layer. When a surface needs
2205
+ a colour not exposed here, ADD the --el-* token (→ --color-*) and consume
2206
+ that (the per-component growth pattern, notes.html mistake #20). All tokens
2207
+ reference Tier-0 vars, so the Tier-1 dark block re-skins them automatically;
2208
+ only concrete hexes (the sidebar hover lift) carry their own dark value.
2209
+ ═══════════════════════════════════════════════════════════════════════ */
2210
+
2211
+ /* `[data-appearance-scope]` re-emits this whole Tier-3 layer onto a NESTED
2212
+ element too — not just :root. `--el-*` reference `--color-*` and var() resolves
2213
+ at the DECLARING element, so a subtree that overrides `--color-*` (via a nested
2214
+ data-theme/-palette/-style) needs the `--el-*` re-declared locally to pick those
2215
+ overrides up. Put `data-appearance-scope` on an element alongside its data-*
2216
+ axes to theme that subtree independently of <html> (the onboarding design step,
2217
+ MOTIR-1040, uses it to preview the user's PROJECT design without touching Motir).
2218
+ Keep this selector list in sync if a new `--el-*` token is added below. */
2219
+ :root,
2220
+ [data-appearance-scope] {
2221
+ /* ── Page ──────────────────────────────────────────────────────── */
2222
+ --el-page-bg: var(--color-background);
2223
+ --el-page-text: var(--color-foreground);
2224
+ --el-page-text-muted: var(--color-muted-foreground);
2225
+
2226
+ /* ── Text scale (the warm-charcoal hierarchy, named by ROLE) ────── */
2227
+ --el-text: var(--color-foreground); /* primary ink */
2228
+ --el-text-strong: var(--color-charcoal); /* emphasis / AA-safe text on tints */
2229
+ --el-text-secondary: var(--color-slate); /* secondary copy */
2230
+ --el-text-muted: var(--color-muted-foreground); /* muted / captions */
2231
+ --el-text-tertiary: var(--color-steel); /* tertiary / footers */
2232
+ --el-text-faint: var(--color-stone); /* faint labels */
2233
+ --el-text-inverted: var(--color-background); /* text on an ink / accent fill */
2234
+
2235
+ /* ── Accent (active CTA identity) + brand highlight ─────────────── */
2236
+ /* --el-accent is the FILL behind --el-accent-text (white): filled buttons,
2237
+ badges, toggles. --el-accent-on-surface is the accent used AS text/icon on
2238
+ 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. */
2240
+ --el-accent: var(--color-primary-fill);
2241
+ --el-accent-text: var(--color-primary-foreground);
2242
+ --el-accent-on-surface: var(--color-primary);
2243
+ --el-accent-pressed: var(--color-primary-pressed);
2244
+ --el-highlight: var(--color-accent); /* brand pink — decorative accents */
2245
+
2246
+ /* ── Surfaces ──────────────────────────────────────────────────── */
2247
+ --el-surface: var(--color-surface);
2248
+ --el-surface-soft: var(--color-surface-soft);
2249
+ --el-canvas: var(--color-canvas); /* recessed board behind raised cards (planning canvas) */
2250
+ --el-surface-text: var(--color-foreground);
2251
+ --el-muted: var(--color-muted); /* faint fill — chips, code, inverted text */
2252
+
2253
+ /* ── Borders ───────────────────────────────────────────────────── */
2254
+ --el-border: var(--color-border); /* default hairline */
2255
+ --el-border-soft: var(--color-hairline-soft);
2256
+ --el-border-strong: var(--color-hairline-strong);
2257
+
2258
+ /* ── Links ─────────────────────────────────────────────────────── */
2259
+ --el-link: var(--color-link);
2260
+ --el-link-pressed: var(--color-link-pressed);
2261
+
2262
+ /* ── Semantic status ───────────────────────────────────────────── */
2263
+ --el-danger: var(--color-destructive);
2264
+ --el-danger-text: var(--color-destructive-foreground);
2265
+ --el-success: var(--color-success);
2266
+ --el-warning: var(--color-warning);
2267
+ --el-info: var(--color-info);
2268
+
2269
+ /* ── Pastel tints (Pill tones, feature surfaces) ───────────────── */
2270
+ --el-tint-peach: var(--color-tint-peach);
2271
+ --el-tint-rose: var(--color-tint-rose);
2272
+ --el-tint-mint: var(--color-tint-mint);
2273
+ --el-tint-lavender: var(--color-tint-lavender);
2274
+ --el-tint-sky: var(--color-tint-sky);
2275
+ --el-tint-yellow: var(--color-tint-yellow);
2276
+
2277
+ /* ── Status hues (MOTIR-1273 · 1266.2 — un-collapse the workflow statuses) ──
2278
+ Each workflow status renders its dot/icon in its OWN hue, so in_review /
2279
+ blocked / cancelled differentiate from the three lifecycle CATEGORIES the
2280
+ filter dots used to collapse them onto. Maps to a Tier-0 --color-* (the
2281
+ gold-standard --el-chart-* / --el-type-* pattern), so every palette re-skins
2282
+ them automatically through its --color-* overrides — no per-palette block
2283
+ needed (the card's "+ 10 palette blocks" is satisfied by that indirection;
2284
+ verified slate ≠ stone and info/warning/destructive/success/primary are all
2285
+ palette-defined across all 10). The status DOT uses the hue at full strength;
2286
+ a status CHIP bg = color-mix(var(--el-status-X) 14%, var(--el-surface)) with
2287
+ --el-text-strong (design/design-system/design-notes.md §A). Keys grounded in
2288
+ lib/workflows/defaultWorkflow.ts. */
2289
+ --el-status-todo: var(--color-stone); /* neutral grey — not started */
2290
+ --el-status-in-progress: var(--color-info); /* blue — active */
2291
+ --el-status-in-review: var(--color-primary); /* brand — differentiate from in-progress */
2292
+ --el-status-done: var(--color-success); /* green — resolved */
2293
+ --el-status-blocked: var(--color-warning); /* amber — can't proceed (gap fixed) */
2294
+ --el-status-cancelled: var(--color-steel); /* terminal grey — not red (cancel ≠ error) */
2295
+
2296
+ /* ── Priority hues (MOTIR-1273 · 1266.2 — a 5-step diverging ramp) ──
2297
+ Un-collapses lib/issues/priorityMeta.ts, where `medium` AND `lowest` were
2298
+ both neutral grey (the exact "can't differentiate" complaint). red → orange
2299
+ → mid-slate → blue → faint-stone: two DISTINCT greys for medium / lowest,
2300
+ kept legible by the redundant ArrowUp / Minus / ArrowDown icon (AA, finding
2301
+ #35). The Pill `priority` tone tints these over --el-surface
2302
+ (design/design-system/design-notes.md §B). */
2303
+ --el-priority-highest: var(--color-destructive); /* red */
2304
+ --el-priority-high: var(--color-warning); /* orange */
2305
+ --el-priority-medium: var(--color-slate); /* mid slate (un-collapsed) */
2306
+ --el-priority-low: var(--color-info); /* blue */
2307
+ --el-priority-lowest: var(--color-stone); /* faint stone (un-collapsed) */
2308
+
2309
+ /* ── Semantic SURFACES (MOTIR-1273 · 1266.2 — banner / callout fills) ──
2310
+ Names the banner/callout fills components reached for as raw --el-tint-*
2311
+ (the FormField error box, the public danger / warning banners). Each = a
2312
+ tint base + a strong-ink text token, AA-safe (charcoal-on-tint clears
2313
+ ~10:1 both themes, finding #35). notice-info / callout / success are
2314
+ defined AHEAD of their dedicated surfaces (the spec's "NEW (no impl)"
2315
+ pattern; design/design-system/design-notes.md §C). */
2316
+ --el-danger-surface: var(--color-tint-rose);
2317
+ --el-danger-surface-text: var(--color-charcoal);
2318
+ --el-warning-surface: var(--color-tint-peach);
2319
+ --el-warning-text: var(--color-charcoal); /* ink on the warning surface */
2320
+ --el-success-surface: var(--color-tint-mint);
2321
+ --el-notice-info-bg: var(--color-tint-sky);
2322
+ --el-notice-info-border: var(--color-info);
2323
+ --el-callout-bg: var(--color-tint-lavender);
2324
+ --el-callout-text: var(--color-charcoal);
2325
+
2326
+ /* ── Identity hues (MOTIR-1274 · 1266.3 — un-collapse the shared --el-tint-* pool) ──
2327
+ Roles / org-roles / privacy / labels / avatars each got a DEDICATED token so a
2328
+ palette can tune them apart (today they all pulled from the same six --el-tint-*,
2329
+ so a label, a role and an avatar could not diverge). Every token DEFAULTS to
2330
+ today's exact tint → zero visual change in the motir base; each maps to a Tier-0
2331
+ --color-* so palettes + the dark block re-skin them for free (no per-palette
2332
+ block — the gold-standard --el-chart-* / --el-type-* indirection). This block
2333
+ also DECOUPLES the --el-type-* MISUSE: NotificationRow event badges, the
2334
+ ProjectAvatar mono tile and the OrgUsageClient model dots were borrowing
2335
+ work-item-KIND hues for non-type meaning; they get their own --el-notif-* /
2336
+ --el-avatar-fallback / --el-model-* tokens at the SAME hues (still zero change).
2337
+ design/design-system/design-notes.md §D. */
2338
+ /* Roles / org-roles / privacy — Pill memberRole/orgRole/tone (today hardcoded in
2339
+ Pill.tsx CVA). admin→lavender, member→sky, viewer→mint; owner→lavender; private→
2340
+ lavender, public→sky (public defined AHEAD — no rendered "public" badge yet). */
2341
+ --el-role-admin: var(--color-tint-lavender);
2342
+ --el-role-member: var(--color-tint-sky);
2343
+ --el-role-viewer: var(--color-tint-mint);
2344
+ --el-org-role-owner: var(--color-tint-lavender);
2345
+ --el-org-role-admin: var(--color-tint-sky);
2346
+ --el-org-role-member: var(--color-tint-mint);
2347
+ --el-privacy-private: var(--color-tint-lavender);
2348
+ --el-privacy-public: var(--color-tint-sky);
2349
+ /* Label ramp — lib/labels/labelTint.ts hashes the name → 1..6 in the fixed order
2350
+ peach,rose,mint,lavender,sky,yellow (the seed-loader family; matches shipped). */
2351
+ --el-label-1: var(--color-tint-peach);
2352
+ --el-label-2: var(--color-tint-rose);
2353
+ --el-label-3: var(--color-tint-mint);
2354
+ --el-label-4: var(--color-tint-lavender);
2355
+ --el-label-5: var(--color-tint-sky);
2356
+ --el-label-6: var(--color-tint-yellow);
2357
+ /* Avatar ramp — KEEP the named keys (peach…yellow): lib/projects/avatar.ts persists
2358
+ project.avatarColor ∈ these strings, so numbering them would break stored rows
2359
+ (spec §7.1, rung-2 migration safety). The fallback fixes --el-type-* misuse #2 —
2360
+ the mono initials tile keeps its blue but stops borrowing --el-type-task. */
2361
+ --el-avatar-peach: var(--color-tint-peach);
2362
+ --el-avatar-rose: var(--color-tint-rose);
2363
+ --el-avatar-mint: var(--color-tint-mint);
2364
+ --el-avatar-lavender: var(--color-tint-lavender);
2365
+ --el-avatar-sky: var(--color-tint-sky);
2366
+ --el-avatar-yellow: var(--color-tint-yellow);
2367
+ --el-avatar-fallback: var(--color-info); /* mono tile — was --el-type-task (= --color-info) */
2368
+ /* Notification event badges (decouple --el-type-* misuse #2; same hues → zero change).
2369
+ mentioned keeps the accent fill it already used — --el-accent resolves to
2370
+ --color-primary-fill (purple), NOT --color-accent (pink); the spec's base column
2371
+ read --color-accent but its note said "alias for consistency", so per §2
2372
+ zero-visual-change this maps to the SHIPPED value. */
2373
+ --el-notif-mentioned: var(--color-primary-fill); /* the accent fill (matches prior --el-accent) */
2374
+ --el-notif-commented: var(--color-info); /* was --el-type-task */
2375
+ --el-notif-assigned: var(--color-accent-green); /* was --el-type-story */
2376
+ --el-notif-transitioned: var(--color-accent-teal); /* was --el-type-subtask */
2377
+ /* AI model dots (promote to a named family; deepseek was borrowing --el-type-subtask).
2378
+ opus keeps the accent fill, same --color-primary-fill reasoning as mentioned. */
2379
+ --el-model-opus: var(--color-primary-fill); /* the accent fill (matches prior --el-accent) */
2380
+ --el-model-sonnet: var(--color-info);
2381
+ --el-model-haiku: var(--color-success);
2382
+ --el-model-deepseek: var(--color-accent-teal); /* was --el-type-subtask */
2383
+
2384
+ /* ── Icon + text roles (MOTIR-1275 · 1266.4 — split the --el-text-muted /
2385
+ --el-text triple-duty) ──
2386
+ Icons and overlines/subtitles were borrowing the body-copy text tokens
2387
+ (--el-text-muted for every icon + caption, --el-text for the tooltip fill),
2388
+ so a palette could not tune an icon apart from prose. These give each role
2389
+ its own token mapped to the SAME Tier-0 neutral it resolves to today, so
2390
+ every palette + the dark block re-skin them for free (the gold-standard
2391
+ --el-chart-* / --el-type-* indirection — no per-palette block). icon-btn /
2392
+ icon-heading are defined AHEAD of an explicit consumer (the spec's
2393
+ "NEW (no impl)" pattern; buttons inherit currentColor today).
2394
+ design/design-system/design-notes.md §E. */
2395
+ --el-icon-muted: var(--color-muted-foreground); /* inactive nav / chevron / close */
2396
+ --el-icon-active: var(--color-primary); /* active nav icon (was --el-accent-on-surface) */
2397
+ --el-icon-btn: var(--color-foreground); /* explicit button-icon ink (usually currentColor) */
2398
+ --el-icon-heading: var(--color-charcoal); /* an icon beside a heading */
2399
+ --el-icon-field: var(--color-muted-foreground); /* search / chevron / calendar in inputs */
2400
+ --el-text-eyebrow: var(--color-muted-foreground); /* uppercase-mono overline */
2401
+ --el-text-subtitle: var(--color-slate); /* lead paragraph (Modal / EmptyState desc) */
2402
+ --el-text-helper: var(--color-muted-foreground); /* form hint (was --el-text-muted) */
2403
+ --el-text-identifier: var(--color-slate); /* monospace MOTIR-123 keys (was --el-text-secondary) */
2404
+
2405
+ /* ── Component-surface primitives (MOTIR-1275 · 1266.4) ──
2406
+ Names the control surfaces that were overloading --el-surface (option
2407
+ highlight / chip fill / switch knob), --el-border(-strong) (control vs
2408
+ panel edge) and --el-text (tooltip fill). Each defaults to the exact
2409
+ Tier-0 --color-* the component renders today → zero visual change in the
2410
+ motir base; a palette can now tune each apart. Two deliberate
2411
+ spec-vs-shipped reconciliations (rung-2), documented in §F of the notes:
2412
+ (1) --el-count-bg maps to --color-muted, NOT the spec's tentative
2413
+ --color-surface — board count badges render ON an --el-surface column, so
2414
+ surface would erase them; muted is the shipped fill (and identical to
2415
+ surface in dark). (2) --el-overlay-scrim is the lone concrete-value token
2416
+ (no --color-* base), so it carries an explicit dark companion below; light
2417
+ == today's bg-black/40, dark DEEPENS to ~65% for a legible scrim.
2418
+ design/design-system/design-notes.md §F. */
2419
+ --el-tooltip-bg: var(--color-foreground); /* inverted tooltip fill (was --el-text) */
2420
+ --el-tooltip-text: var(--color-background); /* text on the tooltip (was --el-text-inverted) */
2421
+ --el-switch-on: var(--color-primary-fill); /* checked track (was --el-accent) */
2422
+ --el-switch-knob: var(--color-surface); /* the thumb (was --el-surface) */
2423
+ --el-option-active-bg: var(--color-muted); /* highlighted listbox option (was --el-surface) */
2424
+ --el-overlay-scrim: #00000066; /* modal/dialog scrim — concrete (light); dark companion below */
2425
+ --el-chip-bg: var(--color-surface); /* neutral chip fill (tinted chips keep their tint) */
2426
+ --el-chip-border: var(--color-border); /* neutral chip edge */
2427
+ --el-card: var(--color-background); /* untinted card surface — fixes the bg-card orphan */
2428
+ --el-input-border: var(--color-hairline-strong); /* input outline (was --el-border-strong) */
2429
+ --el-button-border: var(--color-hairline-strong); /* secondary-button outline */
2430
+ --el-count-bg: var(--color-muted); /* count-badge fill (shipped value; see note above) */
2431
+ --el-count-text: var(--color-slate); /* count-badge ink (was --el-text-secondary) */
2432
+
2433
+ /* ── Issue-type hues (work-item kind identity — finding #54) ────── */
2434
+ /* The colour the UI was missing: each issue type renders its icon in its
2435
+ own hue, matching ISSUE_TYPE_META.colorToken. */
2436
+ --el-type-epic: var(--color-accent); /* pink */
2437
+ --el-type-story: var(--color-accent-green);
2438
+ --el-type-task: var(--color-info);
2439
+ --el-type-bug: var(--color-destructive);
2440
+ --el-type-subtask: var(--color-accent-teal);
2441
+
2442
+ /* ── Work-item TYPE hues (the work-NATURE palette, Story 2.7 · 2.7.4) ────
2443
+ Parallel to the per-KIND hues above: the ten fixed `WorkItemType` members
2444
+ each render their icon/chip in their own hue (matches
2445
+ lib/issues/workItemTypeMeta.ts). NO new Tier-0 colour needed — the seven
2446
+ "produces an artifact" types take the seven saturated hues; the three
2447
+ "meta / admin" types (decision, manual, chore) take the three greys
2448
+ (desaturation is semantically apt). Each maps to a Tier-0 var, so the
2449
+ [data-theme="dark"] block re-skins them automatically. The chip puts the
2450
+ hue in a color-mix tint BACKGROUND with --el-text-strong text (AA,
2451
+ finding #35); never a page-level tint. */
2452
+ --el-type-code: var(--color-info); /* blue — implementation */
2453
+ --el-type-design: var(--color-accent); /* pink — UI/UX design */
2454
+ --el-type-test: var(--color-success); /* green — automated tests */
2455
+ --el-type-content: var(--color-accent-teal); /* teal — copy / docs / translate */
2456
+ --el-type-research: var(--color-primary); /* purple — spike / investigation */
2457
+ --el-type-review: var(--color-accent-orange); /* orange — QA / review */
2458
+ --el-type-deploy: var(--color-destructive); /* red — infra / ops / release */
2459
+ --el-type-decision: var(--color-charcoal); /* ink — decision record */
2460
+ --el-type-manual: var(--color-stone); /* stone — human provisioning */
2461
+ --el-type-chore: var(--color-slate); /* slate — housekeeping */
2462
+
2463
+ /* ── Chart series (Story 4.6 · the reusable viz Story 6.3 reuses) ──
2464
+ Every chart colour routes through one of these, NEVER a raw --color-*
2465
+ in chart code (the per-component growth pattern). They map to Tier-0
2466
+ vars, so the Tier-1 dark block re-skins them automatically. The pair
2467
+ committed/completed is distinguished by TEXT label too, never colour
2468
+ alone (finding #35). */
2469
+ --el-chart-guideline: var(--color-stone); /* burndown ideal line (dashed) */
2470
+ --el-chart-actual: var(--color-primary); /* burndown actual remaining */
2471
+ --el-chart-scope: var(--color-warning); /* scope-change marker */
2472
+ --el-chart-committed: var(--color-info); /* velocity committed bar */
2473
+ --el-chart-completed: var(--color-success); /* velocity completed bar */
2474
+ --el-chart-average: var(--color-charcoal); /* velocity average (dashed ref) */
2475
+ --el-chart-plot: var(--color-surface-soft); /* plot background fill */
2476
+ --el-chart-grid: var(--color-border); /* gridlines */
2477
+ --el-chart-axis: var(--color-steel); /* axis line + tick labels */
2478
+ /* Story 6.3 dashboards (6.3.4) — the categorical donut ramp + the
2479
+ created/resolved series + the deficit/surplus difference fills. Same
2480
+ growth pattern: map to existing Tier-0 vars, so the dark block re-skins
2481
+ them automatically. The donut cycles cat-1..7; "None"/unset is always
2482
+ cat-none (neutral grey); created/resolved read by line + legend label,
2483
+ never colour alone (finding #35). */
2484
+ --el-chart-cat-1: var(--color-primary); /* distribution donut segment 1 */
2485
+ --el-chart-cat-2: var(--color-info); /* segment 2 */
2486
+ --el-chart-cat-3: var(--color-success); /* segment 3 */
2487
+ --el-chart-cat-4: var(--color-warning); /* segment 4 */
2488
+ --el-chart-cat-5: var(--color-accent-teal); /* segment 5 */
2489
+ --el-chart-cat-6: var(--color-accent); /* segment 6 */
2490
+ --el-chart-cat-7: var(--color-charcoal); /* segment 7 (ramp length) */
2491
+ --el-chart-cat-none: var(--color-stone); /* the "None" / unset group */
2492
+ --el-chart-created: var(--color-info); /* created-vs-resolved: created line */
2493
+ --el-chart-resolved: var(--color-success); /* created-vs-resolved: resolved line */
2494
+ --el-chart-deficit: var(--color-destructive); /* fill where created > resolved */
2495
+ --el-chart-surplus: var(--color-success); /* fill where resolved > created */
2496
+ --el-chart-age: var(--color-warning); /* average-age bar (8.8.13 — aging backlog) */
2497
+ --el-chart-resolution: var(--color-info); /* resolution-time bar (8.8.13 — throughput) */
2498
+ /* Sprint CYCLE GRAPH series (Story 8.14 — Linear scope-vs-completed). A
2499
+ collision-free --el-chart-cycle-* family: the bare --el-chart-completed /
2500
+ -started / -target names are taken (velocity / old burndown), so reusing
2501
+ them would silently re-skin those charts. The --el-chart-plot/grid/axis
2502
+ scaffold is reused unchanged. */
2503
+ --el-chart-cycle-scope: var(--color-stone); /* live total scope (gray) */
2504
+ --el-chart-cycle-started: var(--color-warning); /* started / in progress (amber) */
2505
+ --el-chart-cycle-completed: var(--color-info); /* completed (solid blue) */
2506
+ --el-chart-cycle-target: var(--color-info); /* ideal remaining (blue, dotted) */
2507
+
2508
+ /* ── Public projects (Story 6.12 · public read-only view) ──────────
2509
+ New --el-* roles 6.12.4/6.12.6/6.12.7 add here, each mapped to an
2510
+ existing Tier-0 value (the per-component token-growth pattern). The
2511
+ [data-theme="dark"] block re-skins them automatically because every
2512
+ mapping is a --color-* var the Tier-1 block already flips (no concrete
2513
+ hex). Per design/public-projects/design-notes.md "Colour roles". */
2514
+ --el-public-banner-bg: var(--color-tint-sky); /* "you're viewing a public project" framing */
2515
+ --el-public-banner-text: var(--color-charcoal); /* AA ~10:1 on the sky tint */
2516
+ --el-vote-bg: var(--color-tint-lavender); /* upvote control resting fill (6.12.7) */
2517
+ --el-vote-active-bg: var(--color-primary); /* voted: accent fill */
2518
+ --el-vote-active-text: var(--color-primary-foreground);
2519
+ --el-roadmap-submitted: var(--color-tint-peach);
2520
+ --el-roadmap-planned: var(--color-tint-lavender);
2521
+ --el-roadmap-progress: var(--color-tint-sky);
2522
+ --el-roadmap-done: var(--color-tint-mint);
2523
+ /* ── Onboarding / canvas (Story 1266.6 · MOTIR-1277) ───────────────
2524
+ Per-element hues for the onboarding canvas: revision-diff kind chips
2525
+ (RevisionDiff), the discovery chat bubbles (DiscoveryChatRail), the
2526
+ planning-canvas edges (PlanningCanvas), and the station tier tile
2527
+ (StationNode). Each maps to the exact Tier-0 --color-* it renders
2528
+ today (zero visual change vs the borrowed --el-tint-* / --el-accent /
2529
+ --el-border), so a palette re-skins them via its --color-* override
2530
+ and [data-theme="dark"] flips them — no per-palette / dark entry.
2531
+ Per design/design-system/design-notes.md §H. NB: the card's "wire
2532
+ --el-roadmap-* into StationNode" is corrected there — roadmap stays
2533
+ scoped to the PUBLIC roadmap; the canvas gets its own
2534
+ --el-station-tier-* family (a different semantic concept). */
2535
+ --el-diff-added: var(--color-tint-mint);
2536
+ --el-diff-removed: var(--color-tint-rose);
2537
+ --el-diff-moved: var(--color-tint-sky);
2538
+ --el-chat-bubble-user: var(--color-primary-fill); /* text stays --el-accent-text */
2539
+ --el-chat-bubble-ai: var(--color-surface-soft); /* text stays --el-text */
2540
+ --el-canvas-edge-pending: var(--color-border); /* dashed, not-yet-committed edge */
2541
+ --el-canvas-edge-committed: var(--color-hairline-strong); /* solid, settled edge */
2542
+ --el-station-tier-discovery: var(--color-tint-sky);
2543
+ --el-station-tier-vision: var(--color-tint-lavender);
2544
+ --el-station-tier-feasibility: var(--color-tint-mint);
2545
+ --el-station-tier-validation: var(--color-tint-peach);
2546
+ /* the Overview hero's soft corner washes (decorative tints only; all text
2547
+ sits on --el-page-bg, never on the wash, so AA holds — finding #35) */
2548
+ --el-hero-wash-a: var(--color-tint-lavender);
2549
+ --el-hero-wash-b: var(--color-tint-sky);
2550
+ /* "Building in public" (Story 6.17.2/6.17.4) — the reframed `public` access
2551
+ option glyph + the "Live"/status badge. Tier-0 flips (--color-tint-lavender
2552
+ and --color-charcoal both re-skin under [data-theme="dark"]) re-skin these
2553
+ automatically, so they live in Tier 3 once with no dark-block entry. */
2554
+ --el-build-bg: var(--color-tint-lavender); /* badge / reframed-option tint */
2555
+ --el-build-text: var(--color-charcoal); /* AA ~10:1 on the lavender tint, both themes */
2556
+ --el-build-glyph: var(--color-primary); /* the megaphone accent glyph */
2557
+
2558
+ /* ── Interaction / agile surfaces (MOTIR-1276 · 1266.5) ──────────────
2559
+ Un-collapses the dnd / selection / overdue / accent meanings that all
2560
+ rode --el-tint-lavender / --el-accent / --el-text, so a palette can tune a
2561
+ drop target apart from a selected row apart from an overdue date. Each
2562
+ maps to a --color-* → zero base-palette change AND every palette + dark
2563
+ re-skins for free (§1; the chart/type ramp pattern). The previously
2564
+ undifferentiated meanings (selection vs drop; overdue vs a future date)
2565
+ now read distinctly; the rest default to today's exact value.
2566
+ design/design-system/design-notes.md §G. */
2567
+ --el-selection-bg: var(--color-tint-sky); /* selected row/card — sky, NOT the drop lavender */
2568
+ --el-droptarget-bg: var(--color-tint-lavender); /* dnd drop-zone fill (shipped) */
2569
+ --el-board-column-accent: var(--color-primary); /* drop ring / column border */
2570
+ --el-overdue: var(--color-destructive); /* past-due date */
2571
+ --el-due-soon: var(--color-warning); /* due today / within 3 days */
2572
+ --el-sprint-accent: var(--color-tint-lavender); /* sprint-header emphasis (shipped) */
2573
+ --el-epic-accent: var(--color-accent); /* pink epic identity (aliases --el-type-epic) */
2574
+ --el-archived-pill-bg: var(--color-muted); /* inactive "Archived" badge fill */
2575
+ --el-archived-pill-text: var(--color-slate); /* AA-safe ink on the muted fill */
2576
+ --el-auth-wash: var(--color-tint-sky); /* sign-in background wash (text on the card) */
2577
+ --el-tabnav-track: var(--color-surface); /* tab-bar track (Segmented / PublicTabNav) */
2578
+ --el-tabnav-active: var(--color-primary); /* active-tab glyph (== --el-accent-on-surface) */
2579
+ --el-card-icon-bg: var(--color-muted); /* hub/settings card icon tile */
2580
+ --el-card-icon-fg: var(--color-primary); /* the icon inside that tile */
2581
+
2582
+ /* ── Code/mono (used by /tokens reference route) ───────────────── */
2583
+ --el-code-bg: var(--color-muted);
2584
+ --el-code-text: var(--color-charcoal);
2585
+
2586
+ /* ── App shell — sidebar (Subtask 1.5.2). The shell mockup paints the
2587
+ sidebar rail on $--surface and the content area on $--background; the
2588
+ active nav row inverts that (a $--background row against the $--surface
2589
+ rail) so the current page reads as inset into the page canvas. ──── */
2590
+ --el-sidebar-bg: var(--color-surface); /* the rail — surface, distinct from the page canvas */
2591
+ --el-sidebar-border: var(--color-hairline); /* the 1px seam between rail and content */
2592
+ --el-sidebar-item-bg-hover: #eeede9; /* subtle hover lift — one shade darker than surface (light) */
2593
+ --el-sidebar-item-bg-active: var(
2594
+ --color-background
2595
+ ); /* active row = canvas inset into the rail */
2596
+ }
2597
+
2598
+ /* Dark-mode lift for the sidebar hover token. The other three --el-sidebar-*
2599
+ tokens resolve through --color-* vars the Tier-1 dark block already flips;
2600
+ only the hover shade is a concrete hex, so it carries its own dark value —
2601
+ one shade *lighter* than the dark surface, mirroring the light recipe's
2602
+ one-shade-darker. */
2603
+ [data-theme='dark'] {
2604
+ --el-sidebar-item-bg-hover: #222222;
2605
+ /* The modal scrim deepens in dark (MOTIR-1275 · 1266.4): on a near-black
2606
+ canvas a 40% scrim barely separates the dialog, so dark uses ~65% — the
2607
+ other concrete-hex --el-* alongside the sidebar hover. Light value (40%,
2608
+ == the prior bg-black/40) lives in the :root block above. */
2609
+ --el-overlay-scrim: #000000a6;
2610
+ }
2611
+
2612
+ /* ═══════════════════════════════════════════════════════════════════════
2613
+ AXIS 1 (COLOUR) — data-palette overrides (Subtask 7.3.48)
2614
+ Registry: lib/theme/palettes.ts. A named palette re-skins the app by
2615
+ OVERRIDING the Tier-3 --el-* layer above — and ONLY colour (--el-* /
2616
+ --color-*), NEVER a shape/feel token (radius / spacing / shadow / sizing /
2617
+ motion / type). That is the data-style axis's job; keeping the two disjoint
2618
+ is what makes "style × palette" a product of two independent choices.
2619
+
2620
+ v1 ships exactly one palette — `motir` (Yue, 2026-06-17: "use Motir's colour
2621
+ palette for the v1"). It IS the Tier-3 base, so — exactly like the
2622
+ `warm-editorial` base style — it needs NO override block: setting
2623
+ data-palette="motir" simply leaves the base --el-* tokens in force. Each
2624
+ later "Palette: …" subtask ADDS its `[data-palette='<id>']` block here,
2625
+ overriding --el-* (light + dark via [data-palette='<id>'][data-theme='dark']
2626
+ where the palette diverges from the base flip), and authors its
2627
+ docs/palettes/<id>.md — mirroring how the per-style subtasks add a
2628
+ [data-style] block on the other axis.
2629
+ ═══════════════════════════════════════════════════════════════════════ */
2630
+
2631
+ /* `motir` needs no block — it is the Tier-3 base above. Each later palette
2632
+ ADDS its [data-palette='<id>'] override here (light + dark companion). */
2633
+
2634
+ /* ── Palette: Cobalt (data-palette="cobalt") — Subtask 7.3.49 ───────────────
2635
+ A cool, calm, trustworthy INSTITUTIONAL-BLUE re-skin: slate-cool surfaces, a
2636
+ confident cobalt primary/accent, a cool cyan highlight, cooled pastel tints.
2637
+ Mood anchored in getdesign.md's Coinbase (clean institutional blue) / IBM
2638
+ (structured blue) analysis; the actual light/dark ramps + UI-state steps are
2639
+ built from Radix Colors (Blue / Indigo / Slate / Red / Jade / Amber) — the
2640
+ accessibility-first 12-step scales designed for UI states.
2641
+
2642
+ This block re-skins by overriding the Tier-0 --color-* SOURCE the Tier-3
2643
+ --el-* layer references (exactly how the [data-theme='dark'] block works), so
2644
+ every --el-* token — surfaces, ink, accent, links, semantic, tints, and
2645
+ work-item type hues — re-skins coherently with no per-token churn. It sets
2646
+ ONLY colour tokens (--color-* / --el-*), NEVER a shape/feel token (radius /
2647
+ spacing / shadow / sizing / motion / type) — that is the independent
2648
+ data-style axis (paletteRegistry.test.ts enforces the disjointness).
2649
+
2650
+ Accessibility: every text-on-surface, white-on-fill, link, and chip-tint
2651
+ pairing clears WCAG AA (≥4.5; ≥3.0 for icon/UI hues) in BOTH light and dark —
2652
+ verified numerically + by rendered specimen, never eyeballed. See
2653
+ docs/palettes/cobalt.md for the colour-role reference. */
2654
+ [data-palette='cobalt'] {
2655
+ /* brand & primary — confident cobalt (Radix Indigo family) */
2656
+ --color-primary: #3a57cf; /* cobalt AS text/icon on a surface (AA on white/surface) */
2657
+ --color-primary-foreground: #ffffff;
2658
+ --color-primary-pressed: #2b44ad;
2659
+ --color-primary-fill: #3650c2; /* fill behind white labels (white-on AA) */
2660
+
2661
+ /* surfaces — white canvas over cool slate sections */
2662
+ --color-background: #ffffff;
2663
+ --color-foreground: #0f1b2e; /* cool navy ink */
2664
+ --color-surface: #eef2f8; /* cool slate section background */
2665
+ --color-surface-soft: #f7f9fc; /* quieter cool division */
2666
+
2667
+ /* text scale — cool slate hierarchy */
2668
+ --color-ink: #0f1b2e;
2669
+ --color-charcoal: #1e2c40; /* body emphasis / AA-safe text on tints */
2670
+ --color-slate: #46556b; /* secondary text */
2671
+ --color-steel: #586a82; /* tertiary / footer */
2672
+ --color-stone: #8493a6; /* faint labels */
2673
+ --color-muted: #eef2f8;
2674
+ --color-muted-foreground: #5b6a7e; /* captions — AA on white + surfaces */
2675
+
2676
+ /* borders — cool slate hairlines */
2677
+ --color-hairline: #d8e0ea;
2678
+ --color-hairline-soft: #e6ebf2;
2679
+ --color-hairline-strong: #bcc7d6;
2680
+ --color-border: #d8e0ea;
2681
+
2682
+ /* accent secondary — a cool cyan highlight (replaces the warm brand pink) */
2683
+ --color-accent: #0e93a8; /* cool cyan — decorative / epic & design type hue */
2684
+ --color-accent-orange: #c2410c;
2685
+ --color-accent-teal: #0e8f86;
2686
+ --color-accent-green: #18874c; /* cooled green */
2687
+
2688
+ /* card tints — cooled pastel washes (6 distinct feature hues) */
2689
+ --color-tint-peach: #fde7d4;
2690
+ --color-tint-rose: #fbe0ea;
2691
+ --color-tint-mint: #d6f0e2;
2692
+ --color-tint-lavender: #e4e8fb; /* cool indigo tint */
2693
+ --color-tint-sky: #dbeafb; /* cool blue tint — the most-used wash */
2694
+ --color-tint-yellow: #fbf2d2;
2695
+
2696
+ /* semantic */
2697
+ --color-success: #18874c;
2698
+ --color-warning: #c2410c;
2699
+ --color-destructive: #d3322f; /* white-on AA */
2700
+ --color-destructive-foreground: #ffffff;
2701
+ --color-info: #0d63b8;
2702
+
2703
+ /* links — a cleaner sky-blue, distinct from the cobalt primary (AA on the
2704
+ soft surface a hovered row paints, too) */
2705
+ --color-link: #0d63b8;
2706
+ --color-link-pressed: #0a4f95;
2707
+
2708
+ /* sidebar hover is a concrete hex in Tier 3 (not a --color-* ref), so the
2709
+ palette overrides it directly — one shade darker than the cool surface. */
2710
+ --el-sidebar-item-bg-hover: #e4e9f1;
2711
+ }
2712
+
2713
+ /* Cobalt — dark companion. A cool navy canvas (not the base near-black);
2714
+ brightened cobalt/link hues so they read AS text on the dark surface, and a
2715
+ darker accent fill so white labels still clear AA. */
2716
+ [data-palette='cobalt'][data-theme='dark'] {
2717
+ --color-background: #0b111d; /* deep cool navy canvas */
2718
+ --color-foreground: #e7edf6;
2719
+ --color-surface: #131d2c;
2720
+ --color-surface-soft: #0f1825;
2721
+
2722
+ --color-ink: #e7edf6;
2723
+ --color-charcoal: #d3deec;
2724
+ --color-slate: #a0b1c6;
2725
+ --color-steel: #7e90a6;
2726
+ --color-stone: #5f6f84;
2727
+ --color-muted: #131d2c;
2728
+ --color-muted-foreground: #94a4ba;
2729
+
2730
+ --color-hairline: #243245;
2731
+ --color-hairline-soft: #1a2433;
2732
+ --color-hairline-strong: #324258;
2733
+ --color-border: #243245;
2734
+
2735
+ /* cobalt — brightened as text on navy; fill darkened for white-label AA */
2736
+ --color-primary: #7e98f2;
2737
+ --color-primary-foreground: #ffffff;
2738
+ --color-primary-pressed: #3a51b8;
2739
+ --color-primary-fill: #4a63cf;
2740
+
2741
+ /* cooled tints, dimmed for the navy canvas */
2742
+ --color-tint-peach: #2f2317;
2743
+ --color-tint-rose: #301a25;
2744
+ --color-tint-mint: #15281f;
2745
+ --color-tint-lavender: #1c2238;
2746
+ --color-tint-sky: #13243a;
2747
+ --color-tint-yellow: #2b2814;
2748
+
2749
+ --color-accent: #2ec3d6; /* bright cool cyan highlight on dark */
2750
+ --color-accent-orange: #f08a4b;
2751
+ --color-accent-teal: #2db6a8;
2752
+ --color-accent-green: #34c578;
2753
+
2754
+ --color-success: #34c578;
2755
+ --color-warning: #f08a4b;
2756
+ --color-destructive: #d83847; /* white-on AA on the navy canvas */
2757
+ --color-destructive-foreground: #ffffff;
2758
+ --color-info: #6fb3ff;
2759
+
2760
+ --color-link: #6fb3ff;
2761
+ --color-link-pressed: #93c5ff;
2762
+
2763
+ /* sidebar hover — one shade lighter than the dark cool surface */
2764
+ --el-sidebar-item-bg-hover: #1c2840;
2765
+ }
2766
+
2767
+ /* ── Palette: Graphite (data-palette="graphite") — Subtask 7.3.51 ───────────
2768
+ A stark, editorial, COOL-NEUTRAL MONOCHROME re-skin — the cool foil to
2769
+ Motir's warmth: true greyscale surfaces + ink on a clean Radix Slate scale,
2770
+ a single restrained accent. The accent is expressed two ways, exactly as
2771
+ Vercel / Linear do it: the primary CTA is a high-contrast INK fill (near-black
2772
+ on light, near-white on dark — the monochrome statement), while the one
2773
+ chromatic accent — a restrained cool blue — carries links, active/selected
2774
+ states, focus, and the decorative highlight. Surfaces, ink, and borders are
2775
+ pure cool greyscale; semantic (danger/success/warning/info) and work-item
2776
+ type hues stay chromatic-but-cooled so status & kind remain legible (the
2777
+ finding-#54 guard — never collapse the UI to grey + one colour).
2778
+
2779
+ Mood + accent hue anchored in getdesign.md's Vercel (black-and-white
2780
+ precision) / Linear (ultra-minimal) analysis; the actual light/dark ramps +
2781
+ UI-state steps are built from Radix Colors (Slate / Blue / Red / Grass /
2782
+ Amber / Teal) — the accessibility-first 12-step scales designed for UI
2783
+ states. Slate is the cool neutral closest to the brand anchor.
2784
+
2785
+ This block re-skins by overriding the Tier-0 --color-* SOURCE the Tier-3
2786
+ --el-* layer references (exactly how the [data-theme='dark'] block works), so
2787
+ every --el-* token — surfaces, ink, accent, links, semantic, tints, and
2788
+ work-item type hues — re-skins coherently with no per-token churn. It sets
2789
+ ONLY colour tokens (--color-* / --el-*), NEVER a shape/feel token (radius /
2790
+ spacing / shadow / sizing / motion / type) — that is the independent
2791
+ data-style axis (paletteRegistry.test.ts enforces the disjointness).
2792
+
2793
+ Accessibility: every text-on-surface, white-on-fill, link, and chip-tint
2794
+ pairing clears WCAG AA (≥4.5; ≥3.0 for icon/UI hues) in BOTH light and dark —
2795
+ verified numerically + by rendered specimen, never eyeballed. See
2796
+ docs/palettes/graphite.md for the colour-role reference. */
2797
+ [data-palette='graphite'] {
2798
+ /* brand & primary — the high-contrast INK CTA + the single cool-blue accent.
2799
+ --color-primary is the accent AS text/icon/focus on a surface (the cool
2800
+ blue, AA on white & the slate surface); --color-primary-fill is the CTA
2801
+ FILL behind white labels — near-black ink, the monochrome statement. */
2802
+ --color-primary: #155bc4; /* cool-blue accent — links / active / focus / icon */
2803
+ --color-primary-foreground: #ffffff; /* white labels on the ink fill */
2804
+ --color-primary-pressed: #2b2f35; /* pressed ink CTA (one step off the fill) */
2805
+ --color-primary-fill: #1a1d21; /* near-black graphite CTA fill (white-on AA) */
2806
+
2807
+ /* surfaces — pure white canvas over cool slate sections (Radix Slate) */
2808
+ --color-background: #ffffff; /* stark white canvas */
2809
+ --color-foreground: #16191d; /* cool near-black ink */
2810
+ --color-surface: #eef0f3; /* cool slate section background */
2811
+ --color-surface-soft: #f8f9fa; /* quieter cool division */
2812
+
2813
+ /* text scale — cool slate hierarchy (Radix Slate 11→9 below body) */
2814
+ --color-ink: #16191d;
2815
+ --color-charcoal: #20242a; /* body emphasis / AA-safe text on tints */
2816
+ --color-slate: #565c64; /* secondary text — AA on white + surfaces */
2817
+ --color-steel: #6b7079; /* tertiary / footer */
2818
+ --color-stone: #8a8f98; /* faint labels (decorative, sub-AA — as the base) */
2819
+ --color-muted: #eef0f3;
2820
+ --color-muted-foreground: #5d636b; /* captions — AA on white + surfaces */
2821
+
2822
+ /* borders — cool slate hairlines */
2823
+ --color-hairline: #e2e4e9;
2824
+ --color-hairline-soft: #ebedf0;
2825
+ --color-hairline-strong: #c8ccd3;
2826
+ --color-border: #e2e4e9;
2827
+
2828
+ /* accent secondary — the cool-blue highlight replaces the warm brand pink */
2829
+ --color-accent: #2563c9; /* cool-blue decorative highlight / epic & design hue */
2830
+ --color-accent-orange: #c2410c; /* review hue / warning — cooled */
2831
+ --color-accent-teal: #0e8f86; /* subtask / content hue */
2832
+ --color-accent-green: #18804a; /* story hue / success — cooled */
2833
+
2834
+ /* card tints — cooled pastel washes (6 distinct feature hues; charcoal text
2835
+ on them clears AA in both themes) */
2836
+ --color-tint-peach: #f7e6d6;
2837
+ --color-tint-rose: #f6dfe8;
2838
+ --color-tint-mint: #d9efe1;
2839
+ --color-tint-lavender: #e4e6f3; /* cool indigo wash */
2840
+ --color-tint-sky: #dde9f6; /* cool blue wash — the accent tint */
2841
+ --color-tint-yellow: #f6f1d6;
2842
+
2843
+ /* semantic */
2844
+ --color-success: #18804a;
2845
+ --color-warning: #c2410c;
2846
+ --color-destructive: #c92a2a; /* white-on AA */
2847
+ --color-destructive-foreground: #ffffff;
2848
+ --color-info: #155bc4; /* shares the accent blue (coherent in mono) */
2849
+
2850
+ /* links — the accent blue (AA on white AND the soft surface a hovered row
2851
+ paints behind it) */
2852
+ --color-link: #155bc4;
2853
+ --color-link-pressed: #114a9e;
2854
+
2855
+ /* sidebar hover is a concrete hex in Tier 3 (not a --color-* ref), so the
2856
+ palette overrides it directly — one shade darker than the cool surface. */
2857
+ --el-sidebar-item-bg-hover: #e6e8ec;
2858
+ }
2859
+
2860
+ /* Graphite — dark companion. A cool near-black slate canvas; the CTA fill
2861
+ inverts to near-WHITE ink (Vercel's dark white-button), the accent blue
2862
+ brightens so it reads AS text on the dark canvas, and semantic/type hues
2863
+ brighten to clear AA on near-black. */
2864
+ [data-palette='graphite'][data-theme='dark'] {
2865
+ --color-background: #0c0d0f; /* stark cool near-black canvas */
2866
+ --color-foreground: #edeef0;
2867
+ --color-surface: #18191c; /* cool slate section */
2868
+ --color-surface-soft: #141517; /* quieter cool division */
2869
+
2870
+ --color-ink: #edeef0;
2871
+ --color-charcoal: #d7d9dd; /* emphasis / AA-safe on tints */
2872
+ --color-slate: #a9adb5; /* secondary — AA on the dark canvas */
2873
+ --color-steel: #888d96; /* tertiary / footer */
2874
+ --color-stone: #696e77; /* faint labels */
2875
+ --color-muted: #18191c;
2876
+ --color-muted-foreground: #a0a4ac; /* captions — AA on dark */
2877
+
2878
+ --color-hairline: #282a2e;
2879
+ --color-hairline-soft: #1d1f22;
2880
+ --color-hairline-strong: #3a3d42;
2881
+ --color-border: #282a2e;
2882
+
2883
+ /* accent — blue brightened AS text on near-black; CTA fill inverts to
2884
+ near-white ink with near-black labels (the dark monochrome statement) */
2885
+ --color-primary: #7db1ff; /* bright cool-blue accent — links / active / focus */
2886
+ --color-primary-foreground: #0c0d0f; /* near-black labels on the white fill */
2887
+ --color-primary-pressed: #d3d5d9; /* pressed white-ink CTA */
2888
+ --color-primary-fill: #edeef0; /* near-white ink CTA fill (black-on AA) */
2889
+
2890
+ /* cooled tints, dimmed for the near-black canvas */
2891
+ --color-tint-peach: #2e2418;
2892
+ --color-tint-rose: #2f1c26;
2893
+ --color-tint-mint: #15271e;
2894
+ --color-tint-lavender: #1f2236;
2895
+ --color-tint-sky: #15233a;
2896
+ --color-tint-yellow: #2a2716;
2897
+
2898
+ --color-accent: #5b9dff; /* bright cool-blue highlight on dark */
2899
+ --color-accent-orange: #f08a4b;
2900
+ --color-accent-teal: #2db6a8;
2901
+ --color-accent-green: #34b86e;
2902
+
2903
+ --color-success: #34b86e;
2904
+ --color-warning: #f08a4b;
2905
+ --color-destructive: #d83847; /* white-on AA on the dark canvas */
2906
+ --color-destructive-foreground: #ffffff;
2907
+ --color-info: #7db1ff;
2908
+
2909
+ --color-link: #7db1ff;
2910
+ --color-link-pressed: #a3c8ff;
2911
+
2912
+ /* sidebar hover — one shade lighter than the dark cool surface */
2913
+ --el-sidebar-item-bg-hover: #202226;
2914
+ }
2915
+
2916
+ /* ── Palette: Evergreen (data-palette="evergreen") — Subtask 7.3.50 ──────────
2917
+ A fresh, technical emerald scheme — Supabase (dark emerald) / Spotify
2918
+ (vibrant green) / MongoDB (spring green), built on Radix green/jade ramps.
2919
+ See docs/palettes/evergreen.md.
2920
+
2921
+ Like every palette block, this re-skins by overriding the Tier-0 --color-*
2922
+ layer (COLOUR only — never a shape/feel token, which is the data-style axis):
2923
+ because the Tier-3 --el-* element tokens reference --color-*, this cascades a
2924
+ COORDINATED re-skin through every surface, ink step, accent/CTA, link,
2925
+ semantic, tint, work-item-type hue, and chart colour at once — no per-`--el-*`
2926
+ enumeration, no drift from the element layer. The work-item type / chart hues
2927
+ stay a distinguishable rainbow (a `code` chip must still read distinct from a
2928
+ `test` chip); only the palette's MOOD shifts emerald. Every text-on-surface,
2929
+ CTA, link, and chip pairing passes WCAG AA in light AND dark (verified by
2930
+ render specimen; see the doc).
2931
+
2932
+ Concrete-hex --el-* tokens that do NOT resolve through --color-*
2933
+ (--el-sidebar-item-bg-hover) are re-skinned explicitly below. */
2934
+ [data-palette='evergreen'] {
2935
+ /* Brand / primary — emerald CTA (white label text clears AA on the fill) */
2936
+ --color-primary: #0c7a52;
2937
+ --color-primary-foreground: #ffffff;
2938
+ --color-primary-pressed: #095f40;
2939
+ --color-primary-fill: #0c7a52;
2940
+
2941
+ /* Surfaces — lightly cooled (green-leaning) neutrals over a white canvas */
2942
+ --color-background: #ffffff;
2943
+ --color-foreground: #14201b;
2944
+ --color-surface: #eef3f0;
2945
+ --color-surface-soft: #f6faf8;
2946
+
2947
+ /* Text scale — cooled forest-charcoal ink hierarchy */
2948
+ --color-ink: #14201b;
2949
+ --color-charcoal: #1d2c25;
2950
+ --color-slate: #42534a;
2951
+ --color-steel: #5e6f66;
2952
+ --color-stone: #8f9f96;
2953
+ --color-muted: #eef2f0;
2954
+ --color-muted-foreground: #5e6f66;
2955
+
2956
+ /* Borders / hairlines — cool green-grey */
2957
+ --color-hairline: #d9e2dc;
2958
+ --color-hairline-soft: #e6ece9;
2959
+ --color-hairline-strong: #c2d0c8;
2960
+ --color-border: #d9e2dc;
2961
+
2962
+ /* Secondary accents — a harmonized-but-distinct rainbow for type/chart hues.
2963
+ Violet replaces the house pink as the complementary accent (epic/design),
2964
+ so those chips stay distinct from the emerald greens. */
2965
+ --color-accent: #8257e6; /* violet — epic / design / highlight */
2966
+ --color-accent-orange: #c2410c; /* review */
2967
+ --color-accent-teal: #0d8a86; /* subtask / content */
2968
+ --color-accent-green: #157f3a; /* story — fresh grass green */
2969
+
2970
+ /* Card tints — cool/green-leaning washes (lavender shifts toward the violet
2971
+ accent; carry charcoal text at AA) */
2972
+ --color-tint-peach: #ffe9d6;
2973
+ --color-tint-rose: #f6dced;
2974
+ --color-tint-mint: #d5f1e1;
2975
+ --color-tint-lavender: #e7e0fa;
2976
+ --color-tint-sky: #d8ecf6;
2977
+ --color-tint-yellow: #fbf3d2;
2978
+
2979
+ /* Semantic — green-leaning success; the rest kept distinct for legibility */
2980
+ --color-success: #127c3e; /* grassier than the emerald primary, so they read apart */
2981
+ --color-warning: #b45309;
2982
+ --color-destructive: #d63d3d;
2983
+ --color-destructive-foreground: #ffffff;
2984
+ --color-info: #1366c4; /* a clear blue — keeps task/code apart from teal subtask/content */
2985
+
2986
+ /* Links — blue affordance (distinct from the emerald accent), AA on both the
2987
+ page and the soft hovered-row surface */
2988
+ --color-link: #0a6ebd;
2989
+ --color-link-pressed: #08537f;
2990
+
2991
+ /* Concrete-hex --el token (does not resolve through --color-*) — cool hover
2992
+ lift one shade darker than the cooled light surface */
2993
+ --el-sidebar-item-bg-hover: #e4ebe7;
2994
+ }
2995
+
2996
+ /* Evergreen — dark companion. Bright emerald on a deep-forest canvas
2997
+ (Supabase-style: the bright accent FILL carries DARK label text). Mirrors the
2998
+ Tier-1 dark flip for the cooled neutrals. */
2999
+ [data-palette='evergreen'][data-theme='dark'] {
3000
+ --color-primary: #3ddc97;
3001
+ --color-primary-foreground: #06140d; /* dark ink on the bright emerald fill */
3002
+ --color-primary-pressed: #2bbd7f;
3003
+ --color-primary-fill: #3ddc97;
3004
+
3005
+ --color-background: #0c130f;
3006
+ --color-foreground: #e8efe9;
3007
+ --color-surface: #141d18;
3008
+ --color-surface-soft: #101713;
3009
+
3010
+ --color-ink: #e8efe9;
3011
+ --color-charcoal: #d6e0d9;
3012
+ --color-slate: #9aaaa1;
3013
+ --color-steel: #6f8077;
3014
+ --color-stone: #5a6b62;
3015
+ --color-muted: #141d18;
3016
+ --color-muted-foreground: #9aaaa1;
3017
+
3018
+ --color-hairline: #25322b;
3019
+ --color-hairline-soft: #1b251f;
3020
+ --color-hairline-strong: #35453c;
3021
+ --color-border: #25322b;
3022
+
3023
+ --color-accent: #a78bfa; /* violet */
3024
+ --color-accent-orange: #fb923c;
3025
+ --color-accent-teal: #2dd4bf;
3026
+ --color-accent-green: #34d27a;
3027
+
3028
+ /* Pastel tints dimmed for the dark canvas */
3029
+ --color-tint-peach: #2f2114;
3030
+ --color-tint-rose: #2e1822;
3031
+ --color-tint-mint: #11271c;
3032
+ --color-tint-lavender: #211b33;
3033
+ --color-tint-sky: #122430;
3034
+ --color-tint-yellow: #2b2712;
3035
+
3036
+ --color-success: #34d27a;
3037
+ --color-warning: #fb923c;
3038
+ --color-destructive: #f87171;
3039
+ --color-destructive-foreground: #06140d; /* dark ink on the bright red */
3040
+ --color-info: #5aa8f5;
3041
+
3042
+ --color-link: #56b6f5;
3043
+ --color-link-pressed: #7cc7f8;
3044
+
3045
+ /* hover lift one shade *lighter* than the dark surface (mirrors base recipe) */
3046
+ --el-sidebar-item-bg-hover: #1e2a23;
3047
+ }
3048
+
3049
+ /* ═══════════════════════════════════════════════════════════════════════
3050
+ AXIS 3 (TYPE) — data-type overrides (Subtask 7.3.53)
3051
+ Registry: lib/theme/typography.ts. A named type pairing re-points the
3052
+ `--font-*` ROLE tokens (sans / serif / mono) — and ONLY font tokens, NEVER a
3053
+ colour (--el / --color) or shape (--radius / --spacing / --shadow) token. The
3054
+ shared --font-size scale stays common (sizes are layout, not brand).
3055
+
3056
+ v1 ships three pairings built from the THREE already-loaded next/font faces
3057
+ (Inter · Source Serif 4 · JetBrains Mono) — ZERO new payload:
3058
+ - `motir` — the BASE (editorial serif headlines); it IS the Tier-0
3059
+ --font-* defaults, so — like the `motir` palette — it needs
3060
+ NO override block.
3061
+ - `motir-sans` — all-sans: the serif headline face re-points to the sans
3062
+ stack (reproduces the old Swiss `--font-serif` override).
3063
+ - `motir-mono` — mono headlines: re-points to the mono stack (reproduces
3064
+ the old Neo-Brutalism / Cybercore override).
3065
+ A STYLE declares a `defaultTypeId` (lib/theme/styles.ts) — the pairing applied
3066
+ when the user hasn't pinned a `data-type` — so the out-of-the-box look is
3067
+ UNCHANGED (swiss → motir-sans, neo/cybercore → motir-mono). Each later
3068
+ "Type: …" subtask (7.3.54+) ADDS its `[data-type='<id>']` block + faces here:
3069
+ - `grotesk` (7.3.54) — Space Grotesk on the headline role over Inter.
3070
+ - `mono-technical` (7.3.56) — IBM Plex Mono on the headline + meta/code
3071
+ roles over the Inter body; loads one new face (in layout.tsx).
3072
+ ═══════════════════════════════════════════════════════════════════════ */
3073
+
3074
+ [data-type='motir-sans'] {
3075
+ /* Sans headlines — re-point the editorial serif role at the sans stack. */
3076
+ --font-serif:
3077
+ var(--font-sans-source, -apple-system), BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
3078
+ }
3079
+
3080
+ [data-type='motir-mono'] {
3081
+ /* Mono throughout — re-point BOTH the headline (serif) role AND the body/UI
3082
+ (sans) role at the mono stack, so the WHOLE app (nav, header, buttons,
3083
+ content) re-types to a code-native monospace voice, not just headlines (Yue,
3084
+ 2026-06-19: the type axis must re-type the whole UI, chrome included). Meta
3085
+ already is mono. ONLY --font-* tokens (the disjointness guard). */
3086
+ --font-serif: var(--font-mono-source, ui-monospace), 'SF Mono', Menlo, monospace;
3087
+ --font-sans: var(--font-mono-source, ui-monospace), 'SF Mono', Menlo, monospace;
3088
+ }
3089
+
3090
+ /* ── Palette: Spectrum (data-palette="spectrum") — Subtask 7.3.52 ───────────
3091
+ A vibrant, playful, energetic MULTI-ACCENT re-skin: crisp cool-white
3092
+ surfaces, a bright violet primary, a candy-magenta highlight, and a
3093
+ saturated, well-separated rainbow tint/accent set — friendly, never garish.
3094
+ Visibly distinct from Motir (warm cream → crisp cool surfaces; muted pastels
3095
+ → candy-bright tints), not just an accent-hue swap. Mood anchored in
3096
+ getdesign.md's Figma (vibrant multi-colour) / Airtable (colourful, friendly)
3097
+ analysis; the actual light/dark ramps + UI-state steps are built from Radix
3098
+ Colors (Violet / Iris / Pink / Blue / Jade / Amber / Red) — the
3099
+ accessibility-first 12-step scales designed for UI states.
3100
+
3101
+ This block re-skins by overriding the Tier-0 --color-* SOURCE the Tier-3
3102
+ --el-* layer references (exactly how the [data-theme='dark'] block works), so
3103
+ every --el-* token — surfaces, ink, accent, links, semantic, tints, and
3104
+ work-item type hues — re-skins coherently with no per-token churn. It sets
3105
+ ONLY colour tokens (--color-* / --el-*), NEVER a shape/feel token (radius /
3106
+ spacing / shadow / sizing / motion / type) — that is the independent
3107
+ data-style axis (paletteRegistry.test.ts enforces the disjointness).
3108
+
3109
+ Accessibility: every text-on-surface, white-on-fill, link, and chip-tint
3110
+ pairing clears WCAG AA (≥4.5 text; ≥3.0 icon/UI hue) in BOTH light and dark —
3111
+ verified numerically + by rendered specimen, never eyeballed. See
3112
+ docs/palettes/spectrum.md for the colour-role reference. */
3113
+ [data-palette='spectrum'] {
3114
+ /* brand & primary — bright violet (Radix Violet/Iris family) */
3115
+ --color-primary: #6440d6; /* violet AS text/icon on a surface (AA ~6.5:1 on white) */
3116
+ --color-primary-foreground: #ffffff;
3117
+ --color-primary-pressed: #5331b8;
3118
+ --color-primary-fill: #5a37c9; /* fill behind white labels (white-on AA ~7.4:1) */
3119
+
3120
+ /* surfaces — crisp cool white over faintly violet-tinted sections */
3121
+ --color-background: #ffffff;
3122
+ --color-foreground: #1a1626; /* cool near-black ink with a faint violet cast */
3123
+ --color-surface: #f4f2fb; /* cool violet-tinted section background */
3124
+ --color-surface-soft: #faf9fe; /* quieter cool division */
3125
+
3126
+ /* text scale — cool violet-neutral hierarchy */
3127
+ --color-ink: #1a1626;
3128
+ --color-charcoal: #2a2440; /* body emphasis / AA-safe text on tints */
3129
+ --color-slate: #524d66; /* secondary text */
3130
+ --color-steel: #6b6680; /* tertiary / footer */
3131
+ --color-stone: #938da6; /* faint labels */
3132
+ --color-muted: #f4f2fb;
3133
+ --color-muted-foreground: #5e5975; /* captions — AA ~6.6:1 on white + surfaces */
3134
+
3135
+ /* borders — cool violet hairlines */
3136
+ --color-hairline: #e6e2f3;
3137
+ --color-hairline-soft: #efecf8;
3138
+ --color-hairline-strong: #cfc8e6;
3139
+ --color-border: #e6e2f3;
3140
+
3141
+ /* accent secondary — a candy magenta-pink highlight (the playful brand spark;
3142
+ also the epic & design type hue) + a vivid, well-separated accent trio */
3143
+ --color-accent: #e23d97; /* vivid magenta-pink — decorative / epic & design hue */
3144
+ --color-accent-orange: #ea580c;
3145
+ --color-accent-teal: #0d9488;
3146
+ --color-accent-green: #16a34a;
3147
+
3148
+ /* card tints — candy-bright washes (6 distinct, saturated feature hues; each
3149
+ light enough that --el-text-strong charcoal clears AA ≥10:1 on it) */
3150
+ --color-tint-peach: #ffe1c2;
3151
+ --color-tint-rose: #ffd9e8;
3152
+ --color-tint-mint: #c7f5d9;
3153
+ --color-tint-lavender: #e6ddff;
3154
+ --color-tint-sky: #d2e7ff;
3155
+ --color-tint-yellow: #fff0b8;
3156
+
3157
+ /* semantic */
3158
+ --color-success: #16a34a;
3159
+ --color-warning: #ea580c;
3160
+ --color-destructive: #d92e2b; /* white-on AA */
3161
+ --color-destructive-foreground: #ffffff;
3162
+ --color-info: #2563eb;
3163
+
3164
+ /* links — a clean vivid blue, distinct from the violet primary (AA on the
3165
+ soft surface a hovered row paints, too) */
3166
+ --color-link: #2563eb;
3167
+ --color-link-pressed: #1d4ed8;
3168
+
3169
+ /* sidebar hover is a concrete hex in Tier 3 (not a --color-* ref), so the
3170
+ palette overrides it directly — one shade darker than the cool surface. */
3171
+ --el-sidebar-item-bg-hover: #ece8f8;
3172
+ }
3173
+
3174
+ /* Spectrum — dark companion. A deep cool-violet canvas (not the base
3175
+ near-black); brightened violet/pink/link hues so they read AS text on the
3176
+ dark surface, and a darker accent fill so white labels still clear AA. */
3177
+ [data-palette='spectrum'][data-theme='dark'] {
3178
+ --color-background: #14111f; /* deep cool-violet canvas */
3179
+ --color-foreground: #ece9f6;
3180
+ --color-surface: #1d1830;
3181
+ --color-surface-soft: #181426;
3182
+
3183
+ --color-ink: #ece9f6;
3184
+ --color-charcoal: #ddd8ee;
3185
+ --color-slate: #aaa3c2;
3186
+ --color-steel: #8a82a3;
3187
+ --color-stone: #6b6485;
3188
+ --color-muted: #1d1830;
3189
+ --color-muted-foreground: #a39bbd;
3190
+
3191
+ --color-hairline: #2b2440;
3192
+ --color-hairline-soft: #211b33;
3193
+ --color-hairline-strong: #3a3257;
3194
+ --color-border: #2b2440;
3195
+
3196
+ /* violet — brightened as text on the violet canvas; fill darkened for
3197
+ white-label AA */
3198
+ --color-primary: #a78bfa;
3199
+ --color-primary-foreground: #ffffff;
3200
+ --color-primary-pressed: #5331b8;
3201
+ --color-primary-fill: #6d4fd6;
3202
+
3203
+ /* candy tints, dimmed for the dark canvas (light --el-text-strong on them) */
3204
+ --color-tint-peach: #34261a;
3205
+ --color-tint-rose: #351c28;
3206
+ --color-tint-mint: #16301f;
3207
+ --color-tint-lavender: #251f3c;
3208
+ --color-tint-sky: #16263f;
3209
+ --color-tint-yellow: #322c16;
3210
+
3211
+ --color-accent: #ff5fb0; /* bright candy magenta-pink on dark */
3212
+ --color-accent-orange: #fb8b4c;
3213
+ --color-accent-teal: #2dc4b8;
3214
+ --color-accent-green: #36c977;
3215
+
3216
+ --color-success: #36c977;
3217
+ --color-warning: #fb8b4c;
3218
+ --color-destructive: #f0555f; /* reads AS text on the dark canvas (AA ~5.5:1) */
3219
+ --color-destructive-foreground: #ffffff;
3220
+ --color-info: #6fa8ff;
3221
+
3222
+ --color-link: #8fb8ff;
3223
+ --color-link-pressed: #b3d0ff;
3224
+
3225
+ /* sidebar hover — one shade lighter than the dark cool surface */
3226
+ --el-sidebar-item-bg-hover: #261f3d;
3227
+ }
3228
+
3229
+ /* ── Palette: Amber (data-palette="amber") — Subtask 8.8.34 (MOTIR-1252) ─────
3230
+ BINANCE's whole palette: the bold trading-floor GOLD over the exchange's
3231
+ cool graphite near-blacks. Transcribed from Binance's documented system —
3232
+ the warm gold primary IS what makes this a "warm" palette; the neutrals are
3233
+ Binance's own cool greys (the whole-palette directive: a warm primary + the
3234
+ brand's real neutrals). Brand tokens: Binance Gold #F0B90B (primary) /
3235
+ brighter #FCD535 (on-dark accent), trading-floor blacks #0B0E11 / #181A20 /
3236
+ "Shark" #1E2329 / #2B3139, greys #EAECEF / #B7BDC6 / #848E9C / #5E6673 /
3237
+ #474D57, buy-green #0ECB81, sell-red #F6465D. (Binance ships no documented
3238
+ light theme, so the light surfaces/greys are derived from its cool grey
3239
+ family + the gold; the dark theme is the authentic trading floor.)
3240
+
3241
+ ⚠️ THE YELLOW TRAP: the gold FILL cannot carry white text at AA, so the
3242
+ accent-text (--color-primary-foreground) is Binance's near-black #181A20,
3243
+ not #fff (dark-on-gold ≥9:1) — Binance's own black-on-yellow treatment.
3244
+
3245
+ Re-skins by overriding the Tier-0 --color-* SOURCE (as [data-theme='dark']
3246
+ does), so every --el-* token re-skins coherently. ONLY colour tokens
3247
+ (--color-* / --el-*), NEVER a shape/feel token (the independent data-style
3248
+ axis; paletteRegistry.test.ts enforces the disjointness). Every --el-* role
3249
+ clears WCAG AA (≥4.5 text; ≥3.0 icon/UI) in BOTH themes, verified
3250
+ numerically. See docs/palettes/amber.md. */
3251
+ [data-palette='amber'] {
3252
+ /* brand & primary — Binance Gold. The FILL carries DARK ink labels (yellow
3253
+ trap); --color-primary is a darkened gold used AS text/icon on a surface. */
3254
+ --color-primary: #8a6a00; /* darkened gold as text/icon (AA ~5:1 on white) */
3255
+ --color-primary-foreground: #181a20; /* Binance near-black on the gold fill (AA ~9.6:1) */
3256
+ --color-primary-pressed: #d4a30a;
3257
+ --color-primary-fill: #f0b90b; /* Binance Gold — the signature; dark-on AA */
3258
+
3259
+ /* surfaces — white canvas over cool graphite sections (derived light) */
3260
+ --color-background: #ffffff;
3261
+ --color-foreground: #181a20; /* Binance near-black ink */
3262
+ --color-surface: #f4f5f7; /* cool graphite section background */
3263
+ --color-surface-soft: #fafbfc; /* quieter cool division */
3264
+
3265
+ /* text scale — Binance cool-grey hierarchy */
3266
+ --color-ink: #181a20;
3267
+ --color-charcoal: #1e2329; /* Binance "Shark" — emphasis / AA-safe on tints */
3268
+ --color-slate: #4a5159; /* secondary text */
3269
+ --color-steel: #5e6673; /* tertiary / footer */
3270
+ --color-stone: #848e9c; /* Binance signature grey — faint labels */
3271
+ --color-muted: #f4f5f7;
3272
+ --color-muted-foreground: #5e6673; /* captions — AA on white + surfaces */
3273
+
3274
+ /* borders — cool graphite hairlines */
3275
+ --color-hairline: #e6e8eb;
3276
+ --color-hairline-soft: #eef0f2;
3277
+ --color-hairline-strong: #c9ced4;
3278
+ --color-border: #e6e8eb;
3279
+
3280
+ /* accent secondary — a deep amber-gold highlight (epic & design hue),
3281
+ distinct from the bright primary gold */
3282
+ --color-accent: #97751a;
3283
+ --color-accent-orange: #c2410c;
3284
+ --color-accent-teal: #0e8f86;
3285
+ --color-accent-green: #0a8f57; /* darkened Binance buy-green for light AA */
3286
+
3287
+ /* card tints — gold-leaning warm washes + cool feature hues */
3288
+ --color-tint-peach: #fbe7c2;
3289
+ --color-tint-rose: #f7dde2;
3290
+ --color-tint-mint: #d6ecdf;
3291
+ --color-tint-lavender: #e2e4ee;
3292
+ --color-tint-sky: #dbe7f5;
3293
+ --color-tint-yellow: #fbeec0; /* gold wash — the most-used here */
3294
+
3295
+ /* semantic — Binance buy-green / sell-red, darkened for white-on / light AA */
3296
+ --color-success: #0a8f57;
3297
+ --color-warning: #b45309;
3298
+ --color-destructive: #d4283f; /* Binance sell-red, darkened (white-on AA) */
3299
+ --color-destructive-foreground: #ffffff;
3300
+ --color-info: #1366c4;
3301
+
3302
+ /* links — a clear blue, distinct from the gold primary */
3303
+ --color-link: #1366c4;
3304
+ --color-link-pressed: #0f4f9c;
3305
+
3306
+ /* sidebar hover — one shade darker than the cool surface */
3307
+ --el-sidebar-item-bg-hover: #eceef1;
3308
+ }
3309
+
3310
+ /* Amber — dark companion: the AUTHENTIC Binance trading floor. Cool near-black
3311
+ canvas (#0B0E11) + "Shark" cards (#1E2329); the gold brightens to #FCD535 and
3312
+ the CTA fill carries dark labels; semantics are Binance's real buy-green
3313
+ #0ECB81 / sell-red #F6465D. */
3314
+ [data-palette='amber'][data-theme='dark'] {
3315
+ --color-background: #0b0e11; /* Binance deepest trading-floor black */
3316
+ --color-foreground: #eaecef;
3317
+ --color-surface: #1e2329; /* Binance "Shark" card surface */
3318
+ --color-surface-soft: #181a20; /* Binance app background — quieter division */
3319
+
3320
+ --color-ink: #eaecef;
3321
+ --color-charcoal: #d1d6dd;
3322
+ --color-slate: #b7bdc6;
3323
+ --color-steel: #848e9c;
3324
+ --color-stone: #5e6673;
3325
+ --color-muted: #1e2329;
3326
+ --color-muted-foreground: #8a94a0;
3327
+
3328
+ --color-hairline: #2b3139;
3329
+ --color-hairline-soft: #1e2329;
3330
+ --color-hairline-strong: #474d57;
3331
+ --color-border: #2b3139;
3332
+
3333
+ /* gold brightened to Binance #FCD535 on the dark floor; fill carries dark
3334
+ labels (AA ~13.5:1) */
3335
+ --color-primary: #fcd535;
3336
+ --color-primary-foreground: #0b0e11;
3337
+ --color-primary-pressed: #d4a30a;
3338
+ --color-primary-fill: #fcd535;
3339
+
3340
+ /* tints dimmed for the trading-floor black */
3341
+ --color-tint-peach: #2f2814;
3342
+ --color-tint-rose: #2f1a22;
3343
+ --color-tint-mint: #102a1e;
3344
+ --color-tint-lavender: #1c2030;
3345
+ --color-tint-sky: #122436;
3346
+ --color-tint-yellow: #2e2810;
3347
+
3348
+ --color-accent: #f0b90b; /* Binance Gold highlight on dark */
3349
+ --color-accent-orange: #fb923c;
3350
+ --color-accent-teal: #2dd4bf;
3351
+ --color-accent-green: #0ecb81; /* Binance buy-green */
3352
+
3353
+ --color-success: #0ecb81; /* Binance buy-green */
3354
+ --color-warning: #fbab4c;
3355
+ --color-destructive: #f6465d; /* Binance sell-red */
3356
+ --color-destructive-foreground: #0b0e11; /* dark ink on the bright sell-red */
3357
+ --color-info: #6fb3ff;
3358
+
3359
+ --color-link: #6fb3ff;
3360
+ --color-link-pressed: #a3ccff;
3361
+
3362
+ /* sidebar hover — one shade lighter than the dark "Shark" surface */
3363
+ --el-sidebar-item-bg-hover: #262d35;
3364
+ }
3365
+
3366
+ /* ── Palette: Sienna (data-palette="sienna") — Subtask 8.8.34 (MOTIR-1252) ───
3367
+ MISTRAL AI's whole palette: the flame-orange over Mistral's Mediterranean
3368
+ warm-cream surfaces. Transcribed from Mistral's documented system
3369
+ (getdesign.md/mistral.ai): Mistral Orange #fa520f (primary) / Flame #fb6424,
3370
+ the Block gradient (yellow→amber→flame→orange), the Sunshine family
3371
+ (#ff8a00/#ffa110/#ffb83e/#ffd06a), Block Gold #ffe295, Warm-Ivory cream
3372
+ surfaces (#fffaeb → #fff0c2), Mistral-Black #1f1f1f ink. Mistral ships NO
3373
+ semantic colours, so success/warning/danger/info are added warm-consistent.
3374
+
3375
+ ⚠️ THE YELLOW TRAP (as Amber): the bright flame FILL cannot carry white at
3376
+ AA, so the accent-text is a DARK warm ink in BOTH themes — Mistral's own
3377
+ dark-on-orange treatment (dark-on-flame ~5.3:1 light / ~6.8:1 dark).
3378
+
3379
+ Re-skins by overriding the Tier-0 --color-* SOURCE; ONLY colour tokens, never
3380
+ a shape/feel token (paletteRegistry.test.ts enforces it). Every --el-* role
3381
+ clears WCAG AA in BOTH themes, verified numerically. See
3382
+ docs/palettes/sienna.md. */
3383
+ [data-palette='sienna'] {
3384
+ /* brand & primary — Mistral's flame. The FILL is Mistral Orange #fa520f with
3385
+ DARK ink labels; --color-primary is a darkened flame used AS text/icon. */
3386
+ --color-primary: #c4400a; /* darkened Mistral flame as text/icon (AA ~4.9:1 on the cream) */
3387
+ --color-primary-foreground: #2a1205; /* DARK warm ink on the flame fill (AA ~5.3:1) */
3388
+ --color-primary-pressed: #cc3a05; /* Mistral primary-deep */
3389
+ --color-primary-fill: #fa520f; /* Mistral Orange — the signature flame; dark-on AA */
3390
+
3391
+ /* surfaces — Mistral's Mediterranean warm creams (never pure white) */
3392
+ --color-background: #fffaeb; /* Mistral "Warm Ivory" page background */
3393
+ --color-foreground: #1f1f1f; /* Mistral Black ink */
3394
+ --color-surface: #fbf1d3; /* warm cream section background (Mistral cream ramp) */
3395
+ --color-surface-soft: #fdf6e0; /* quieter warm cream division */
3396
+
3397
+ /* text scale — Mistral warm-charcoal hierarchy */
3398
+ --color-ink: #1f1f1f;
3399
+ --color-charcoal: #2c2c2c; /* emphasis / AA-safe text on tints */
3400
+ --color-slate: #4a4a4a; /* secondary text */
3401
+ --color-steel: #6a6a6a; /* tertiary / footer */
3402
+ --color-stone: #8a8a8a; /* faint labels */
3403
+ --color-muted: #fbf1d3;
3404
+ --color-muted-foreground: #5f5f5f; /* captions — AA on the cream + surfaces */
3405
+
3406
+ /* borders — warm beige hairlines (Mistral beige-deep family) */
3407
+ --color-hairline: #ece0c4;
3408
+ --color-hairline-soft: #f3ebd6;
3409
+ --color-hairline-strong: #ddcca6;
3410
+ --color-border: #ece0c4;
3411
+
3412
+ /* accent secondary — a darkened Mistral "Sunshine" amber (epic & design hue) */
3413
+ --color-accent: #b5660a;
3414
+ --color-accent-orange: #c2410c;
3415
+ --color-accent-teal: #0e8f86;
3416
+ --color-accent-green: #18874c;
3417
+
3418
+ /* card tints — Mistral's golden washes (yellow = Block Gold #ffe295) + warm
3419
+ feature hues; charcoal text clears AA ≥11:1 on each */
3420
+ --color-tint-peach: #ffe0c6;
3421
+ --color-tint-rose: #fbdfd2;
3422
+ --color-tint-mint: #e3ead0;
3423
+ --color-tint-lavender: #ece6d6;
3424
+ --color-tint-sky: #e6ebda;
3425
+ --color-tint-yellow: #ffe295; /* Mistral Block Gold */
3426
+
3427
+ /* semantic — Mistral documents none; added warm-consistent + AA-safe */
3428
+ --color-success: #18804a;
3429
+ --color-warning: #b45309;
3430
+ --color-destructive: #c92a2a;
3431
+ --color-destructive-foreground: #ffffff;
3432
+ --color-info: #1366c4;
3433
+
3434
+ /* links — Mistral uses the brand orange for links; a darkened flame (AA) */
3435
+ --color-link: #c4400a;
3436
+ --color-link-pressed: #a8350a;
3437
+
3438
+ /* sidebar hover — one shade deeper than the cream surface */
3439
+ --el-sidebar-item-bg-hover: #f5e9c4;
3440
+ }
3441
+
3442
+ /* Sienna — dark companion. Mistral is light-dominant (few dark tokens: Mistral
3443
+ Black #1f1f1f); a warm near-black canvas built from it, the flame brightened
3444
+ to read as text, the fill a bright flame with dark-ink labels. */
3445
+ [data-palette='sienna'][data-theme='dark'] {
3446
+ --color-background: #15120d; /* warm near-black canvas (from Mistral Black) */
3447
+ --color-foreground: #f5efe2;
3448
+ --color-surface: #1f1d17;
3449
+ --color-surface-soft: #1a1712;
3450
+
3451
+ --color-ink: #f5efe2;
3452
+ --color-charcoal: #e6ddcb;
3453
+ --color-slate: #b3a892;
3454
+ --color-steel: #8a8069;
3455
+ --color-stone: #6b6250;
3456
+ --color-muted: #1f1d17;
3457
+ --color-muted-foreground: #a89d86;
3458
+
3459
+ --color-hairline: #322c1d;
3460
+ --color-hairline-soft: #241f15;
3461
+ --color-hairline-strong: #4a4129;
3462
+ --color-border: #322c1d;
3463
+
3464
+ /* flame brightened as text; fill a bright flame with DARK ink labels (~6.8:1) */
3465
+ --color-primary: #ff8a4c;
3466
+ --color-primary-foreground: #2a1205;
3467
+ --color-primary-pressed: #d2530a;
3468
+ --color-primary-fill: #ff7a38;
3469
+
3470
+ --color-tint-peach: #33240f;
3471
+ --color-tint-rose: #321c16;
3472
+ --color-tint-mint: #23271a;
3473
+ --color-tint-lavender: #272318;
3474
+ --color-tint-sky: #22251a;
3475
+ --color-tint-yellow: #332b12;
3476
+
3477
+ --color-accent: #ffa110; /* Mistral Sunshine-700 amber on dark */
3478
+ --color-accent-orange: #fb923c;
3479
+ --color-accent-teal: #2dd4bf;
3480
+ --color-accent-green: #34d27a;
3481
+
3482
+ --color-success: #34d27a;
3483
+ --color-warning: #fbab4c;
3484
+ --color-destructive: #e0586a;
3485
+ --color-destructive-foreground: #15120d; /* dark ink on the bright danger fill */
3486
+ --color-info: #6fb3ff;
3487
+
3488
+ --color-link: #ff9a5c; /* warm orange link on dark (Mistral) */
3489
+ --color-link-pressed: #ffb37e;
3490
+
3491
+ /* sidebar hover — one shade lighter than the warm-dark surface */
3492
+ --el-sidebar-item-bg-hover: #292619;
3493
+ }
3494
+
3495
+ /* ── Palette: Garnet (data-palette="garnet") — Subtask 8.8.34 (MOTIR-1252) ───
3496
+ PINTEREST's whole palette: the Pushpin RED over Pinterest's neutral greys.
3497
+ Transcribed from Pinterest's documented brand + Gestalt design system: Pushpin
3498
+ red #E60023 (primary, deepened to #c30021 for white-on AA) / darker #BD081C,
3499
+ Cod-Gray #111111 ink, greys #767676 / #EFEFEF, Gestalt "Skycicle" blue for
3500
+ links/info. The warm red primary makes this a "warm" palette; Pinterest's
3501
+ neutrals are true-neutral greys (the whole-palette directive). The danger hue
3502
+ is kept a distinct orange-red so it never blurs into the red brand.
3503
+
3504
+ Re-skins by overriding the Tier-0 --color-* SOURCE; ONLY colour tokens, never
3505
+ a shape/feel token (paletteRegistry.test.ts enforces it). The deep-red FILL
3506
+ carries WHITE labels at AA in both themes. Every --el-* role clears WCAG AA in
3507
+ BOTH themes, verified numerically. See docs/palettes/garnet.md. */
3508
+ [data-palette='garnet'] {
3509
+ /* brand & primary — Pinterest Pushpin red, deepened for white-on AA */
3510
+ --color-primary: #c30021; /* Pushpin red as text/icon on a surface (AA ~6.3:1 on white) */
3511
+ --color-primary-foreground: #ffffff;
3512
+ --color-primary-pressed: #9e0419; /* darker (Pinterest #BD081C family) */
3513
+ --color-primary-fill: #c30021; /* deep Pushpin red — white-on AA ~6.3:1 */
3514
+
3515
+ /* surfaces — white canvas over Pinterest neutral greys */
3516
+ --color-background: #ffffff;
3517
+ --color-foreground: #111111; /* Pinterest "Cod Gray" ink */
3518
+ --color-surface: #efefef; /* Pinterest light-gray section background */
3519
+ --color-surface-soft: #f7f7f7; /* quieter neutral division */
3520
+
3521
+ /* text scale — Pinterest neutral-grey hierarchy */
3522
+ --color-ink: #111111;
3523
+ --color-charcoal: #1a1a1a; /* emphasis / AA-safe text on tints */
3524
+ --color-slate: #444444; /* secondary text */
3525
+ --color-steel: #5f5f5f; /* tertiary / footer */
3526
+ --color-stone: #767676; /* Pinterest grey — faint labels */
3527
+ --color-muted: #efefef;
3528
+ --color-muted-foreground: #5f5f5f; /* captions — AA on white + surfaces */
3529
+
3530
+ /* borders — neutral hairlines */
3531
+ --color-hairline: #e0e0e0;
3532
+ --color-hairline-soft: #ececec;
3533
+ --color-hairline-strong: #cdcdcd;
3534
+ --color-border: #e0e0e0;
3535
+
3536
+ /* accent secondary — a warm magenta-plum highlight (epic & design hue),
3537
+ distinct from both the Pushpin red primary and the orange-red danger */
3538
+ --color-accent: #9c2d7f;
3539
+ --color-accent-orange: #c2410c;
3540
+ --color-accent-teal: #0e8f86;
3541
+ --color-accent-green: #0a7d4f;
3542
+
3543
+ /* card tints — a Pinterest-red rose wash + clean feature hues */
3544
+ --color-tint-peach: #ffe2d4;
3545
+ --color-tint-rose: #ffe0e4; /* Pushpin-red rose wash */
3546
+ --color-tint-mint: #d9efe1;
3547
+ --color-tint-lavender: #ece0f2;
3548
+ --color-tint-sky: #d8e8fb;
3549
+ --color-tint-yellow: #faedc8;
3550
+
3551
+ /* semantic — danger held distinct (orange-red) from the red brand */
3552
+ --color-success: #0a7d4f;
3553
+ --color-warning: #b45309;
3554
+ --color-destructive: #e0300f;
3555
+ --color-destructive-foreground: #ffffff;
3556
+ --color-info: #0a66c2; /* Gestalt "Skycicle" blue family */
3557
+
3558
+ /* links — Gestalt Skycicle blue, distinct from the red primary */
3559
+ --color-link: #0a66c2;
3560
+ --color-link-pressed: #084e95;
3561
+
3562
+ /* sidebar hover — one shade darker than the neutral surface */
3563
+ --el-sidebar-item-bg-hover: #e6e6e6;
3564
+ }
3565
+
3566
+ /* Garnet — dark companion. Pinterest's Cod-Gray near-black canvas; the red
3567
+ brightens to read as text, the CTA fill stays the deep Pushpin red carrying
3568
+ WHITE labels, semantics brighten to clear AA. */
3569
+ [data-palette='garnet'][data-theme='dark'] {
3570
+ --color-background: #0e0e0e; /* Cod-Gray near-black canvas */
3571
+ --color-foreground: #efefef;
3572
+ --color-surface: #1a1a1a;
3573
+ --color-surface-soft: #161616;
3574
+
3575
+ --color-ink: #efefef;
3576
+ --color-charcoal: #e0e0e0;
3577
+ --color-slate: #b5b5b5;
3578
+ --color-steel: #8c8c8c;
3579
+ --color-stone: #6e6e6e;
3580
+ --color-muted: #1a1a1a;
3581
+ --color-muted-foreground: #9a9a9a;
3582
+
3583
+ --color-hairline: #2e2e2e;
3584
+ --color-hairline-soft: #1f1f1f;
3585
+ --color-hairline-strong: #444444;
3586
+ --color-border: #2e2e2e;
3587
+
3588
+ /* red brightened as text on the dark canvas; fill stays deep Pushpin red with
3589
+ WHITE labels (AA ~6.3:1) */
3590
+ --color-primary: #ff5a76;
3591
+ --color-primary-foreground: #ffffff;
3592
+ --color-primary-pressed: #9e0419;
3593
+ --color-primary-fill: #c30021;
3594
+
3595
+ --color-tint-peach: #33231a;
3596
+ --color-tint-rose: #33161a;
3597
+ --color-tint-mint: #15281e;
3598
+ --color-tint-lavender: #271d33;
3599
+ --color-tint-sky: #14233a;
3600
+ --color-tint-yellow: #2e2613;
3601
+
3602
+ --color-accent: #e35bb8; /* bright magenta-plum highlight on dark */
3603
+ --color-accent-orange: #fb923c;
3604
+ --color-accent-teal: #2dd4bf;
3605
+ --color-accent-green: #34d27a;
3606
+
3607
+ --color-success: #2ec27e;
3608
+ --color-warning: #fbab4c;
3609
+ --color-destructive: #ff6b6b;
3610
+ --color-destructive-foreground: #0e0e0e; /* dark ink on the bright danger fill */
3611
+ --color-info: #5aa0ff;
3612
+
3613
+ --color-link: #5aa0ff;
3614
+ --color-link-pressed: #8cc0ff;
3615
+
3616
+ /* sidebar hover — one shade lighter than the dark surface */
3617
+ --el-sidebar-item-bg-hover: #232323;
3618
+ }
3619
+
3620
+ /* ── Palette: Citrine (data-palette="citrine") — Subtask 8.8.34 (MOTIR-1252) ──
3621
+ MIRO's whole palette, transcribed verbatim from its Mirotone design system
3622
+ (npm mirotone@5.3.2). EVERY value here is a documented Mirotone primitive
3623
+ step or Miro brand token — nothing is invented or hand-darkened; where a role
3624
+ needs a darker shade for AA, a documented deeper RAMP STEP is used (e.g.
3625
+ yellow-700 #746019 for on-surface gold text). Miro is a 5-hue system
3626
+ (yellow / blue / green / red / gray): the warm Sunglow YELLOW (#ffd02f =
3627
+ yellow-500, the brand/logo colour) is the primary; the action-BLUE #3859ff
3628
+ (blue-500) is the accent; ink is Stratos #050038; semantics are green-500
3629
+ #1c8f00 / red-500 #d8182c. The warm yellow primary is what makes this a "warm"
3630
+ palette; the neutrals are Mirotone's own cool gray ramp.
3631
+
3632
+ ⚠️ THE YELLOW TRAP: the Sunglow FILL cannot carry white at AA, so the
3633
+ accent-text (--color-primary-foreground) is Miro's Stratos ink #050038, not
3634
+ #fff (dark-on-Sunglow ~13.5:1) — Miro's own dark-on-yellow treatment.
3635
+
3636
+ Re-skins by overriding the Tier-0 --color-* SOURCE; ONLY colour tokens, never
3637
+ a shape/feel token (paletteRegistry.test.ts enforces it). Every --el-* role
3638
+ clears WCAG AA in BOTH themes, verified numerically. See
3639
+ docs/palettes/citrine.md. */
3640
+ [data-palette='citrine'] {
3641
+ /* brand & primary — Miro Sunglow yellow (yellow-500). FILL carries DARK
3642
+ Stratos-ink labels (yellow trap); --color-primary is a documented dark
3643
+ yellow step (yellow-700) used AS text/icon on a surface. */
3644
+ --color-primary: #746019; /* Mirotone yellow-700 — gold text/icon (AA ~6.1:1 on white) */
3645
+ --color-primary-foreground: #050038; /* Miro Stratos ink on the Sunglow fill (AA ~13.5:1) */
3646
+ --color-primary-pressed: #d7b029; /* Mirotone yellow-550 */
3647
+ --color-primary-fill: #ffd02f; /* Miro Sunglow — yellow-500, the brand/logo colour */
3648
+
3649
+ /* surfaces — white over Mirotone gray-50/100 (documented) */
3650
+ --color-background: #ffffff;
3651
+ --color-foreground: #050038; /* Miro Stratos navy ink */
3652
+ --color-surface: #f3f4f6; /* Mirotone gray-100 section background */
3653
+ --color-surface-soft: #f6f7f9; /* Mirotone gray-50 — quieter division */
3654
+
3655
+ /* text scale — Mirotone gray ramp */
3656
+ --color-ink: #050038;
3657
+ --color-charcoal: #1a1b1e; /* gray-900 — emphasis / AA-safe on tints */
3658
+ --color-slate: #4a4e5e; /* gray-600 — secondary text */
3659
+ --color-steel: #656b81; /* gray-500 — tertiary / footer */
3660
+ --color-stone: #7f8497; /* gray-450 — faint labels */
3661
+ --color-muted: #f3f4f6;
3662
+ --color-muted-foreground: #575c6f; /* gray-550 — captions, AA on white + surfaces */
3663
+
3664
+ /* borders — Mirotone gray hairlines */
3665
+ --color-hairline: #e0e1e6; /* gray-200 */
3666
+ --color-hairline-soft: #ebecf0; /* gray-150 */
3667
+ --color-hairline-strong: #c1c3cd; /* gray-300 */
3668
+ --color-border: #e0e1e6;
3669
+
3670
+ /* accent secondary — Miro's action-BLUE (blue-500) as the epic/design hue;
3671
+ the deep-blue + dark-gold below cover the other type hues with documented
3672
+ Mirotone steps (Miro has no orange/teal primitive) */
3673
+ --color-accent: #3859ff; /* Mirotone blue-500 — Miro action blue */
3674
+ --color-accent-orange: #91771e; /* yellow-650 (Miro has no orange ramp) */
3675
+ --color-accent-teal: #243797; /* blue-650 (Miro has no teal ramp) */
3676
+ --color-accent-green: #1c8f00; /* green-500 */
3677
+
3678
+ /* card tints — Mirotone light ramp steps (charcoal text clears AA ≥13:1) */
3679
+ --color-tint-peach: #ffefb9; /* yellow-250 */
3680
+ --color-tint-rose: #fbe6e8; /* red-150 */
3681
+ --color-tint-mint: #dff1da; /* green-150 */
3682
+ --color-tint-lavender: #d9dffc; /* blue-200 */
3683
+ --color-tint-sky: #f2f4fc; /* blue-100 */
3684
+ --color-tint-yellow: #fff7d9; /* yellow-150 */
3685
+
3686
+ /* semantic — Mirotone green/red/blue ramps (Miro maps these by convention) */
3687
+ --color-success: #1c8f00; /* green-500 */
3688
+ --color-warning: #91771e; /* yellow-650 (AA-safe documented gold) */
3689
+ --color-destructive: #d8182c; /* red-500 */
3690
+ --color-destructive-foreground: #ffffff;
3691
+ --color-info: #3859ff; /* blue-500 */
3692
+
3693
+ /* links — Miro action blue (blue-500 → pressed blue-550) */
3694
+ --color-link: #3859ff;
3695
+ --color-link-pressed: #314cd9;
3696
+
3697
+ /* sidebar hover — Mirotone gray-150, one step off the surface */
3698
+ --el-sidebar-item-bg-hover: #ebecf0;
3699
+ }
3700
+
3701
+ /* Citrine — dark companion. A Mirotone gray-ramp near-black canvas (black
3702
+ #090909 / gray-900 #1a1b1e); the Sunglow yellow brightens to read as text and
3703
+ the fill keeps dark Stratos labels; blue/green/red brighten one ramp step to
3704
+ clear AA on the dark canvas. All values are documented Mirotone steps. */
3705
+ [data-palette='citrine'][data-theme='dark'] {
3706
+ --color-background: #090909; /* Miro black */
3707
+ --color-foreground: #f6f7f9; /* gray-50 */
3708
+ --color-surface: #1a1b1e; /* gray-900 */
3709
+ --color-surface-soft: #18191c; /* gray-950 */
3710
+
3711
+ --color-ink: #f6f7f9;
3712
+ --color-charcoal: #ebecf0; /* gray-150 */
3713
+ --color-slate: #c1c3cd; /* gray-300 */
3714
+ --color-steel: #979cab; /* gray-400 */
3715
+ --color-stone: #656b81; /* gray-500 */
3716
+ --color-muted: #1a1b1e;
3717
+ --color-muted-foreground: #adb1bd; /* gray-350 */
3718
+
3719
+ --color-hairline: #2b2e35; /* gray-750 */
3720
+ --color-hairline-soft: #24262c; /* gray-800 */
3721
+ --color-hairline-strong: #3f424f; /* gray-650 */
3722
+ --color-border: #2b2e35;
3723
+
3724
+ /* Sunglow reads as text on black; fill stays Sunglow with dark Stratos labels */
3725
+ --color-primary: #ffd02f; /* yellow-500 */
3726
+ --color-primary-foreground: #050038; /* Stratos ink on the Sunglow fill */
3727
+ --color-primary-pressed: #d7b029; /* yellow-550 */
3728
+ --color-primary-fill: #ffd02f;
3729
+
3730
+ /* tints — Mirotone deep ramp steps for the dark feature washes */
3731
+ --color-tint-peach: #342c0f; /* yellow-850 */
3732
+ --color-tint-rose: #2f1719; /* red-900 */
3733
+ --color-tint-mint: #11260c; /* green-900 */
3734
+ --color-tint-lavender: #12193e; /* blue-850 */
3735
+ --color-tint-sky: #101633; /* blue-900 */
3736
+ --color-tint-yellow: #28220d; /* yellow-900 */
3737
+
3738
+ --color-accent: #5b76fe; /* blue-450 — brighter Miro blue on dark */
3739
+ --color-accent-orange: #b39223; /* yellow-600 */
3740
+ --color-accent-teal: #7a90fe; /* blue-400 */
3741
+ --color-accent-green: #65b452; /* green-400 */
3742
+
3743
+ --color-success: #65b452; /* green-400 */
3744
+ --color-warning: #ffd850; /* yellow-450 */
3745
+ --color-destructive: #e56673; /* red-400 */
3746
+ --color-destructive-foreground: #090909; /* dark ink on the bright danger */
3747
+ --color-info: #7a90fe; /* blue-400 */
3748
+
3749
+ --color-link: #7a90fe;
3750
+ --color-link-pressed: #97a8fe; /* blue-350 */
3751
+
3752
+ /* sidebar hover — Mirotone gray-800, one step lighter than the surface */
3753
+ --el-sidebar-item-bg-hover: #24262c;
3754
+ }
3755
+
3756
+ /* ── Palette: Candy (data-palette="candy") — Subtask 8.8.34 (MOTIR-1252) ──────
3757
+ A sweet, playful PASTEL-CANDY re-skin led by a LIGHT bubblegum PINK. There is
3758
+ no single brand for a "candy" concept, so — to avoid inventing colours — EVERY value
3759
+ is a documented **Radix Colors** step (@radix-ui/colors): Pink is the primary
3760
+ ramp, Mauve the matched warm neutral, and Sky / Jade / Violet / Amber / Crimson
3761
+ supply the candy rainbow of tints + type hues. Glossier Pink #F5DADF is the
3762
+ pastel-pink MOOD reference; the actual values are Radix.
3763
+
3764
+ ⚠️ THE LIGHT-PINK TRAP: a light pink FILL cannot carry white text, so the CTA
3765
+ is a light pink (Radix Pink-6 #efbfdd) with DARK berry-plum labels (Pink-12
3766
+ #651249, ~7.6:1) — the same dark-on-light pattern Amber/Citrine use for gold.
3767
+ --color-primary (pink AS text/icon on a surface) is the AA "text" step
3768
+ Pink-11 #c2298a.
3769
+
3770
+ Re-skins by overriding the Tier-0 --color-* SOURCE; ONLY colour tokens, never a
3771
+ shape/feel token (paletteRegistry.test.ts enforces it). Every --el-* role
3772
+ clears WCAG AA in BOTH themes, verified numerically. See
3773
+ docs/palettes/candy.md. */
3774
+ [data-palette='candy'] {
3775
+ /* brand & primary — a LIGHT bubblegum pink (Radix Pink). FILL is a light pink
3776
+ with DARK berry labels; --color-primary is the Pink-11 text step. */
3777
+ --color-primary: #c2298a; /* Radix Pink-11 — pink text/icon (AA ~5.2:1 on white) */
3778
+ --color-primary-foreground: #651249; /* Radix Pink-12 berry ink on the light-pink fill (~7.6:1) */
3779
+ --color-primary-pressed: #e7acd0; /* Radix Pink-7 */
3780
+ --color-primary-fill: #efbfdd; /* Radix Pink-6 — the light bubblegum pink */
3781
+
3782
+ /* surfaces — candy-paper pinks (Radix Pink-1/2/3) */
3783
+ --color-background: #fffcfe; /* Radix Pink-1 — barely-pink candy white */
3784
+ --color-foreground: #211f26; /* Radix Mauve-12 ink */
3785
+ --color-surface: #fee9f5; /* Radix Pink-3 — candy-pink section */
3786
+ --color-surface-soft: #fef7fb; /* Radix Pink-2 — quieter candy wash */
3787
+
3788
+ /* text scale — Radix Mauve neutral + a Pink-12 berry emphasis */
3789
+ --color-ink: #211f26;
3790
+ --color-charcoal: #651249; /* Pink-12 berry — emphasis / AA-safe text on tints */
3791
+ --color-slate: #65636d; /* Mauve-11 — secondary text */
3792
+ --color-steel: #65636d; /* Mauve-11 — tertiary (Radix Mauve has one AA text grey) */
3793
+ --color-stone: #8e8c99; /* Mauve-9 — faint labels (decorative) */
3794
+ --color-muted: #fee9f5;
3795
+ --color-muted-foreground: #65636d; /* Mauve-11 — captions, AA on white + surfaces */
3796
+
3797
+ /* borders — soft candy-pink hairlines (Radix Pink-5/4/6) */
3798
+ --color-hairline: #f6cee7; /* Pink-5 */
3799
+ --color-hairline-soft: #fbdcef; /* Pink-4 */
3800
+ --color-hairline-strong: #efbfdd; /* Pink-6 */
3801
+ --color-border: #f6cee7;
3802
+
3803
+ /* accent secondary — a candy berry-plum highlight (Radix Plum-9), distinct
3804
+ from the light-pink primary; the candy rainbow covers the type hues */
3805
+ --color-accent: #ab4aba; /* Radix Plum-9 — berry highlight / epic & design hue */
3806
+ --color-accent-orange: #ab6400; /* Radix Amber-11 — candy honey */
3807
+ --color-accent-teal: #208368; /* Radix Jade-11 — candy mint */
3808
+ --color-accent-green: #2a7e3b; /* Radix Grass-11 — candy green */
3809
+
3810
+ /* card tints — Radix step-4 pastels: the candy rainbow (charcoal text AA ≥9:1) */
3811
+ --color-tint-peach: #fbdcef; /* Pink-4 */
3812
+ --color-tint-rose: #fedce7; /* Crimson-4 */
3813
+ --color-tint-mint: #d6f1e3; /* Jade-4 */
3814
+ --color-tint-lavender: #ebe4ff; /* Violet-4 */
3815
+ --color-tint-sky: #d1f0fa; /* Sky-4 */
3816
+ --color-tint-yellow: #ffee9c; /* Amber-4 */
3817
+
3818
+ /* semantic — Radix candy "text" steps (AA-safe) */
3819
+ --color-success: #2a7e3b; /* Grass-11 */
3820
+ --color-warning: #ab6400; /* Amber-11 */
3821
+ --color-destructive: #cb1d63; /* Crimson-11 (white-on AA) */
3822
+ --color-destructive-foreground: #ffffff;
3823
+ --color-info: #00749e; /* Sky-11 */
3824
+
3825
+ /* links — Radix Sky-11 candy blue, distinct from the pink primary */
3826
+ --color-link: #00749e;
3827
+ --color-link-pressed: #1d3e56; /* Sky-12 */
3828
+
3829
+ /* sidebar hover — one shade deeper than the candy-pink surface (Pink-4) */
3830
+ --el-sidebar-item-bg-hover: #fbdcef;
3831
+ }
3832
+
3833
+ /* Candy — dark companion. A pink-tinted near-black canvas (Radix Pink dark
3834
+ 1/2/3); the bubblegum pink brightens to read as text, the fill is a bright
3835
+ candy pink with dark labels, and the rainbow tints become deep candy washes.
3836
+ All values are documented Radix dark steps. */
3837
+ [data-palette='candy'][data-theme='dark'] {
3838
+ --color-background: #191117; /* Radix Pink dark-1 — pink-tinted black */
3839
+ --color-foreground: #eeeef0; /* Mauve dark-12 */
3840
+ --color-surface: #37172f; /* Pink dark-3 — candy-plum section */
3841
+ --color-surface-soft: #21121d; /* Pink dark-2 — quieter division */
3842
+
3843
+ --color-ink: #eeeef0;
3844
+ --color-charcoal: #fdd1ea; /* Pink dark-12 — pink emphasis / AA on dark tints */
3845
+ --color-slate: #b5b2bc; /* Mauve dark-11 — secondary */
3846
+ --color-steel: #b5b2bc; /* Mauve dark-11 — tertiary */
3847
+ --color-stone: #6f6d78; /* Mauve dark-9 — faint */
3848
+ --color-muted: #37172f;
3849
+ --color-muted-foreground: #b5b2bc;
3850
+
3851
+ --color-hairline: #692955; /* Pink dark-6 */
3852
+ --color-hairline-soft: #591c47; /* Pink dark-5 */
3853
+ --color-hairline-strong: #833869; /* Pink dark-7 */
3854
+ --color-border: #692955;
3855
+
3856
+ /* bubblegum pink brightened as text; fill a bright candy pink with dark labels */
3857
+ --color-primary: #ff8dcc; /* Pink dark-11 */
3858
+ --color-primary-foreground: #21121d; /* Pink dark-2 ink on the bright pink fill */
3859
+ --color-primary-pressed: #de51a8; /* Pink dark-10 */
3860
+ --color-primary-fill: #ff8dcc;
3861
+
3862
+ /* deep candy washes (Radix dark step-4) */
3863
+ --color-tint-peach: #4b143d; /* Pink dark-4 */
3864
+ --color-tint-rose: #4d122f; /* Crimson dark-4 */
3865
+ --color-tint-mint: #0b3b2c; /* Jade dark-4 */
3866
+ --color-tint-lavender: #33255b; /* Violet dark-4 */
3867
+ --color-tint-sky: #113555; /* Sky dark-4 */
3868
+ --color-tint-yellow: #3f2700; /* Amber dark-4 */
3869
+
3870
+ --color-accent: #e796f3; /* Plum dark-11 — bright berry */
3871
+ --color-accent-orange: #ffca16; /* Amber dark-11 */
3872
+ --color-accent-teal: #1fd8a4; /* Jade dark-11 */
3873
+ --color-accent-green: #71d083; /* Grass dark-11 */
3874
+
3875
+ --color-success: #71d083; /* Grass dark-11 */
3876
+ --color-warning: #ffca16; /* Amber dark-11 */
3877
+ --color-destructive: #ff92ad; /* Crimson dark-11 */
3878
+ --color-destructive-foreground: #191114; /* dark ink on the bright danger */
3879
+ --color-info: #75c7f0; /* Sky dark-11 */
3880
+
3881
+ --color-link: #75c7f0;
3882
+ --color-link-pressed: #c2f3ff; /* Sky dark-12 */
3883
+
3884
+ /* sidebar hover — one shade lighter than the candy-plum surface (Pink dark-4) */
3885
+ --el-sidebar-item-bg-hover: #4b143d;
3886
+ }
3887
+
3888
+ [data-type='grotesk'] {
3889
+ /* Grotesk (7.3.54) — geometric / neo-grotesque headlines AND body/UI. Re-point
3890
+ BOTH the headline (serif) role AND the body/UI (sans) role at Space Grotesk
3891
+ (loaded in app/layout.tsx as --font-grotesk-source), so the WHOLE app (nav,
3892
+ header, buttons, content) re-types — Space Grotesk reads cleanly as a UI sans
3893
+ (Yue, 2026-06-19: the type axis must re-type the whole UI, chrome included).
3894
+ Meta stays JetBrains Mono (--font-mono). ONLY font tokens here (the
3895
+ disjointness guard): no colour (--el / --color) or shape token. */
3896
+ --font-serif: var(--font-grotesk-source), ui-sans-serif, system-ui, sans-serif;
3897
+ --font-sans: var(--font-grotesk-source), ui-sans-serif, system-ui, sans-serif;
3898
+ }
3899
+
3900
+ [data-type='editorial'] {
3901
+ /* Editorial (7.3.55) — a characterful display serif over the humanist sans
3902
+ body. Adds one new face: Fraunces, loaded in app/layout.tsx as
3903
+ --font-editorial-source. Re-point the headline (serif) role at it; body
3904
+ (Inter, --font-sans) and meta (JetBrains, --font-mono) keep the base roles,
3905
+ so the change is a real, visible FACE swap — magazine, considered — not a
3906
+ size tweak. ONLY --font-* role tokens here (the disjointness guard): no
3907
+ colour (--el/--color) or shape (--radius/--spacing/--shadow) token. */
3908
+ --font-serif: var(--font-editorial-source), 'Fraunces', Georgia, serif;
3909
+ }
3910
+
3911
+ [data-type='mono-technical'] {
3912
+ /* Mono-Technical (7.3.56) — a precise, developer-grade pairing. IBM Plex Mono
3913
+ dresses the headline role (--font-serif), the meta/code role (--font-mono)
3914
+ AND the body/UI role (--font-sans), so the WHOLE app (nav, header, buttons,
3915
+ content) re-types to one technical mono voice (Yue, 2026-06-19: the type axis
3916
+ must re-type the whole UI, chrome included). Sharing the technical mono
3917
+ everywhere is what distinguishes this from motir-mono (which uses the
3918
+ JetBrains mono stack). Only --font-* role tokens are touched — colour
3919
+ (--el/--color) and shape (--radius/--spacing) are the other two, independent
3920
+ axes (the disjointness guard). */
3921
+ --font-serif: var(--font-mono-technical-source, ui-monospace), 'SF Mono', Menlo, monospace;
3922
+ --font-mono: var(--font-mono-technical-source, ui-monospace), 'SF Mono', Menlo, monospace;
3923
+ --font-sans: var(--font-mono-technical-source, ui-monospace), 'SF Mono', Menlo, monospace;
3924
+ }