@graundtech/fluent2-react-kit 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/components/ui/accordion.d.ts +133 -0
  4. package/dist/components/ui/accordion.js +80 -0
  5. package/dist/components/ui/accordion.js.map +1 -0
  6. package/dist/components/ui/alert.d.ts +106 -0
  7. package/dist/components/ui/alert.js +73 -0
  8. package/dist/components/ui/alert.js.map +1 -0
  9. package/dist/components/ui/avatar.d.ts +42 -0
  10. package/dist/components/ui/avatar.js +55 -0
  11. package/dist/components/ui/avatar.js.map +1 -0
  12. package/dist/components/ui/badge.d.ts +36 -0
  13. package/dist/components/ui/badge.js +81 -0
  14. package/dist/components/ui/badge.js.map +1 -0
  15. package/dist/components/ui/breadcrumb.d.ts +53 -0
  16. package/dist/components/ui/breadcrumb.js +149 -0
  17. package/dist/components/ui/breadcrumb.js.map +1 -0
  18. package/dist/components/ui/button.d.ts +24 -0
  19. package/dist/components/ui/button.js +86 -0
  20. package/dist/components/ui/button.js.map +1 -0
  21. package/dist/components/ui/card.d.ts +51 -0
  22. package/dist/components/ui/card.js +97 -0
  23. package/dist/components/ui/card.js.map +1 -0
  24. package/dist/components/ui/checkbox.d.ts +101 -0
  25. package/dist/components/ui/checkbox.js +57 -0
  26. package/dist/components/ui/checkbox.js.map +1 -0
  27. package/dist/components/ui/combobox.d.ts +212 -0
  28. package/dist/components/ui/combobox.js +236 -0
  29. package/dist/components/ui/combobox.js.map +1 -0
  30. package/dist/components/ui/command.d.ts +187 -0
  31. package/dist/components/ui/command.js +225 -0
  32. package/dist/components/ui/command.js.map +1 -0
  33. package/dist/components/ui/dialog.d.ts +117 -0
  34. package/dist/components/ui/dialog.js +165 -0
  35. package/dist/components/ui/dialog.js.map +1 -0
  36. package/dist/components/ui/dropdown-menu.d.ts +155 -0
  37. package/dist/components/ui/dropdown-menu.js +295 -0
  38. package/dist/components/ui/dropdown-menu.js.map +1 -0
  39. package/dist/components/ui/input.d.ts +44 -0
  40. package/dist/components/ui/input.js +43 -0
  41. package/dist/components/ui/input.js.map +1 -0
  42. package/dist/components/ui/label.d.ts +39 -0
  43. package/dist/components/ui/label.js +30 -0
  44. package/dist/components/ui/label.js.map +1 -0
  45. package/dist/components/ui/link.d.ts +62 -0
  46. package/dist/components/ui/link.js +61 -0
  47. package/dist/components/ui/link.js.map +1 -0
  48. package/dist/components/ui/multi-select.d.ts +115 -0
  49. package/dist/components/ui/multi-select.js +133 -0
  50. package/dist/components/ui/multi-select.js.map +1 -0
  51. package/dist/components/ui/pagination.d.ts +46 -0
  52. package/dist/components/ui/pagination.js +164 -0
  53. package/dist/components/ui/pagination.js.map +1 -0
  54. package/dist/components/ui/popover.d.ts +117 -0
  55. package/dist/components/ui/popover.js +62 -0
  56. package/dist/components/ui/popover.js.map +1 -0
  57. package/dist/components/ui/progress.d.ts +98 -0
  58. package/dist/components/ui/progress.js +67 -0
  59. package/dist/components/ui/progress.js.map +1 -0
  60. package/dist/components/ui/radio-group.d.ts +94 -0
  61. package/dist/components/ui/radio-group.js +65 -0
  62. package/dist/components/ui/radio-group.js.map +1 -0
  63. package/dist/components/ui/select.d.ts +145 -0
  64. package/dist/components/ui/select.js +240 -0
  65. package/dist/components/ui/select.js.map +1 -0
  66. package/dist/components/ui/separator.d.ts +43 -0
  67. package/dist/components/ui/separator.js +27 -0
  68. package/dist/components/ui/separator.js.map +1 -0
  69. package/dist/components/ui/skeleton.d.ts +81 -0
  70. package/dist/components/ui/skeleton.js +24 -0
  71. package/dist/components/ui/skeleton.js.map +1 -0
  72. package/dist/components/ui/spinner.d.ts +56 -0
  73. package/dist/components/ui/spinner.js +83 -0
  74. package/dist/components/ui/spinner.js.map +1 -0
  75. package/dist/components/ui/switch.d.ts +60 -0
  76. package/dist/components/ui/switch.js +63 -0
  77. package/dist/components/ui/switch.js.map +1 -0
  78. package/dist/components/ui/tabs.d.ts +107 -0
  79. package/dist/components/ui/tabs.js +102 -0
  80. package/dist/components/ui/tabs.js.map +1 -0
  81. package/dist/components/ui/textarea.d.ts +28 -0
  82. package/dist/components/ui/textarea.js +36 -0
  83. package/dist/components/ui/textarea.js.map +1 -0
  84. package/dist/components/ui/toast.d.ts +185 -0
  85. package/dist/components/ui/toast.js +181 -0
  86. package/dist/components/ui/toast.js.map +1 -0
  87. package/dist/components/ui/tooltip.d.ts +115 -0
  88. package/dist/components/ui/tooltip.js +72 -0
  89. package/dist/components/ui/tooltip.js.map +1 -0
  90. package/dist/index.d.ts +55 -0
  91. package/dist/index.js +284 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/lib/utils.d.ts +9 -0
  94. package/dist/lib/utils.js +9 -0
  95. package/dist/lib/utils.js.map +1 -0
  96. package/package.json +77 -0
  97. package/src/styles/tokens.css +596 -0
  98. package/src/styles/tokens.test.ts +216 -0
@@ -0,0 +1,596 @@
1
+ /* -------------------------------------------------------------------------- */
2
+ /* @graundtech/fluent2-react-kit — design tokens */
3
+ /* */
4
+ /* Fluent 2-inspired token layer expressed in the shadcn/ui variable */
5
+ /* contract (CSS custom properties + Tailwind v4 utilities). Values are */
6
+ /* derived from publicly documented Fluent 2 design constants; hex is the */
7
+ /* source of truth. See docs/design/tokens-research.md for provenance. */
8
+ /* */
9
+ /* Consumers own `@import "tailwindcss";` — this file must NOT import it. */
10
+ /* Import order in the consumer app: */
11
+ /* @import "tailwindcss"; */
12
+ /* @import "@graundtech/fluent2-react-kit/tokens.css"; */
13
+ /* -------------------------------------------------------------------------- */
14
+
15
+ /* shadcn v4 dark-mode convention: `.dark` on any ancestor enables dark.
16
+ The `:not(.light *)` guard stops `dark:` utilities from firing inside a
17
+ `.light` scope that sits under a `.dark` ancestor — the `.light` re-scope
18
+ class (see the light theme block below) forces light mode locally, so its
19
+ subtree must not keep emitting dark-mode overrides. */
20
+ @custom-variant dark (&:is(.dark *):not(.light *));
21
+
22
+ /* ========================================================================== */
23
+ /* LIGHT theme — the default. All values authored in HEX. */
24
+ /* ========================================================================== */
25
+ /* `:root, .light` — `.light` re-scopes light mode onto any subtree, letting a
26
+ consumer/demo force light inside a `.dark` ancestor. It carries the same
27
+ declarations as `:root` (zero duplication), and because an element's own
28
+ variable declarations beat inherited ones, a `.light` block overrides the
29
+ dark values it inherits. The dark `@custom-variant` above is guarded so
30
+ `dark:` utilities don't fire inside this scope. */
31
+ :root,
32
+ .light {
33
+ /* Native UA chrome (form controls, scrollbars, spellcheck underlines) reads
34
+ `color-scheme` to pick its light/dark rendering — set it at the token
35
+ layer so it tracks the theme without a separate consumer opt-in. */
36
+ color-scheme: light;
37
+
38
+ /* --- radius base (Fluent "large" = 6px; drives the rounded-* scale) ----- */
39
+ --radius: 6px;
40
+
41
+ /* --- shadcn core set (light) ------------------------------------------- */
42
+ --background: #ffffff;
43
+ --foreground: #242424;
44
+ --card: #ffffff;
45
+ --card-foreground: #242424;
46
+ --popover: #ffffff;
47
+ --popover-foreground: #242424;
48
+ --primary: #0f6cbd; /* brand[80] */
49
+ --primary-foreground: #ffffff;
50
+ --secondary: #f5f5f5; /* grey[96] */
51
+ --secondary-foreground: #242424;
52
+ --muted: #f5f5f5; /* grey[96] */
53
+ --muted-foreground: #616161; /* grey[38] */
54
+ --accent: #f0f0f0; /* grey[94] — neutral (brand tint lives on sidebar-accent) */
55
+ --accent-foreground: #242424;
56
+ --destructive: #d13438; /* red primary */
57
+ --destructive-foreground: #ffffff;
58
+ --border: #d1d1d1; /* grey[82] */
59
+ --input: #d1d1d1; /* grey[82] */
60
+ --ring: #0f6cbd; /* brand[80] */
61
+
62
+ /* --- chart palette ------------------------------------------------------ */
63
+ --chart-1: #0f6cbd; /* brand blue */
64
+ --chart-2: #038387; /* teal */
65
+ --chart-3: #107c10; /* green */
66
+ --chart-4: #da3b01; /* darkOrange */
67
+ --chart-5: #5c2e91; /* purple */
68
+
69
+ /* --- sidebar ------------------------------------------------------------ */
70
+ --sidebar: #fafafa; /* grey[98] */
71
+ --sidebar-foreground: #242424;
72
+ --sidebar-primary: #0f6cbd;
73
+ --sidebar-primary-foreground: #ffffff;
74
+ --sidebar-accent: #ebf3fc; /* brand[160] — Fluent "selected nav" tint */
75
+ --sidebar-accent-foreground: #115ea3; /* brand[70] */
76
+ --sidebar-border: #e0e0e0; /* grey[88] */
77
+ --sidebar-ring: #0f6cbd;
78
+
79
+ /* --- brand ramp (full 16 stops; buttons need hover/pressed/selected) ---- */
80
+ --brand-10: #061724;
81
+ --brand-20: #082338;
82
+ --brand-30: #0a2e4a;
83
+ --brand-40: #0c3b5e;
84
+ --brand-50: #0e4775;
85
+ --brand-60: #0f548c;
86
+ --brand-70: #115ea3;
87
+ --brand-80: #0f6cbd;
88
+ --brand-90: #2886de;
89
+ --brand-100: #479ef5;
90
+ --brand-110: #62abf5;
91
+ --brand-120: #77b7f7;
92
+ --brand-130: #96c6fa;
93
+ --brand-140: #b4d6fa;
94
+ --brand-150: #cfe4fa;
95
+ --brand-160: #ebf3fc;
96
+
97
+ /* --- status extensions (shadcn ships only --destructive) ---------------- */
98
+ --success: #107c10;
99
+ --success-foreground: #ffffff;
100
+ --success-subtle: #f1faf1;
101
+ --success-border: #9fd89f;
102
+ --warning: #da3b01; /* Fluent darkOrange (not yellow) */
103
+ --warning-foreground: #ffffff;
104
+ --warning-text: #bc4b09; /* AA-safe warning text on light surfaces (4.74:1 on --warning-subtle) — exact Fluent Figma Message-bar value (validation pass 1 precision fix) */
105
+ --warning-subtle: #fdf6f3;
106
+ --warning-border: #f4bfab;
107
+ --destructive-subtle: #fdf6f6;
108
+ --destructive-border: #f1bbbc;
109
+ --destructive-text: #b10e1c; /* AA-safe destructive text on light surfaces (Fluent red foreground) */
110
+
111
+ /* --- second-tier neutral foreground (Fluent NeutralForeground2) ---------
112
+ One grey step lighter than --foreground (#242424), one darker than
113
+ --muted-foreground (#616161). Fluent uses it for secondary-but-still-
114
+ primary-ish text: menu section headers, breadcrumb trail items, list
115
+ item rest text. Added in the v0.3.0/v0.4.0 Figma validation pass, where
116
+ three independent components resolved to this exact value. */
117
+ --foreground-2: #424242; /* NeutralForeground2.Rest (grey[26]) */
118
+
119
+ /* --- accessible interactive stroke (Fluent NeutralStrokeAccessible ramp) -
120
+ The signature outline for interactive controls: checkbox/radio/switch
121
+ rest borders and the input/select resting bottom edge. Kept as a SEPARATE
122
+ alias from --muted-foreground for stroke semantics, even though the light
123
+ rest value is the same hex. Rest -> Hover -> Pressed darkens (more
124
+ contrast on interaction) to mirror Fluent's Rest/Hover/Pressed states. */
125
+ --stroke-accessible: #616161; /* NeutralStrokeAccessible.Rest (= --muted-foreground) */
126
+ --stroke-accessible-hover: #575757; /* NeutralStrokeAccessible.Hover */
127
+ --stroke-accessible-pressed: #4d4d4d; /* NeutralStrokeAccessible.Pressed */
128
+
129
+ /* --- badge warning (Fluent bright-orange chip) --------------------------
130
+ Fluent's Badge Warning fill is a brighter orange than --warning
131
+ (darkOrange) and pairs with dark static text — the opposite foreground
132
+ polarity from --warning's white text. Both are Fluent Static tokens
133
+ (WarningBackground3 / NeutralForeground1Static): #242424 on #f7630c is
134
+ ~4.98:1, clearing AA. A dedicated pair (not --warning) so Alert's
135
+ darkOrange fill stays put. See docs/design/tokens-research.md §12.9. */
136
+ --warning-badge: #f7630c;
137
+ --warning-badge-foreground: #242424;
138
+
139
+ /* --- elevation / shadows ------------------------------------------------ */
140
+ --shadow-ambient: rgba(0, 0, 0, 0.12);
141
+ --shadow-key: rgba(0, 0, 0, 0.14);
142
+ --shadow-brand-ambient: rgba(15, 108, 189, 0.30);
143
+ --shadow-brand-key: rgba(15, 108, 189, 0.25);
144
+ --shadow-2: 0 0 2px var(--shadow-ambient), 0 1px 2px var(--shadow-key);
145
+ --shadow-4: 0 0 2px var(--shadow-ambient), 0 2px 4px var(--shadow-key);
146
+ --shadow-8: 0 0 2px var(--shadow-ambient), 0 4px 8px var(--shadow-key);
147
+ --shadow-16: 0 0 2px var(--shadow-ambient), 0 8px 16px var(--shadow-key);
148
+ --shadow-28: 0 0 8px var(--shadow-ambient), 0 14px 28px var(--shadow-key);
149
+ --shadow-64: 0 0 8px var(--shadow-ambient), 0 32px 64px var(--shadow-key);
150
+
151
+ /* --- typography: font stacks -------------------------------------------- */
152
+ --font-sans: "Segoe UI", -apple-system, BlinkMacSystemFont, "Roboto",
153
+ "Helvetica Neue", sans-serif;
154
+ --font-mono: "Cascadia Code", Consolas, "Courier New", Courier, monospace;
155
+ --font-numeric: Bahnschrift, "Segoe UI", -apple-system, sans-serif;
156
+
157
+ /* --- typography: weights ------------------------------------------------ */
158
+ --font-weight-regular: 400;
159
+ --font-weight-medium: 500;
160
+ --font-weight-semibold: 600;
161
+ --font-weight-bold: 700;
162
+
163
+ /* --- stroke widths ------------------------------------------------------ */
164
+ --stroke-thin: 1px;
165
+ --stroke-thick: 2px;
166
+ --stroke-thicker: 3px;
167
+ --stroke-thickest: 4px;
168
+
169
+ /* --- focus (Fluent-authentic double-stroke variant) --------------------- */
170
+ --stroke-focus-outer: #000000;
171
+ --stroke-focus-inner: #ffffff;
172
+
173
+ /* --- motion: durations -------------------------------------------------- */
174
+ --duration-ultra-fast: 50ms;
175
+ --duration-faster: 100ms;
176
+ --duration-fast: 150ms;
177
+ --duration-normal: 200ms;
178
+ --duration-gentle: 250ms;
179
+ --duration-slow: 300ms;
180
+ --duration-slower: 400ms;
181
+ --duration-ultra-slow: 500ms;
182
+
183
+ /* --- motion: easing curves ---------------------------------------------- */
184
+ --ease-accelerate-max: cubic-bezier(0.9, 0.1, 1, 0.2);
185
+ --ease-accelerate-mid: cubic-bezier(1, 0, 1, 1);
186
+ --ease-accelerate-min: cubic-bezier(0.8, 0, 0.78, 1);
187
+ --ease-decelerate-max: cubic-bezier(0.1, 0.9, 0.2, 1);
188
+ --ease-decelerate-mid: cubic-bezier(0, 0, 0, 1);
189
+ --ease-decelerate-min: cubic-bezier(0.33, 0, 0.1, 1);
190
+ --ease-max: cubic-bezier(0.8, 0, 0.2, 1);
191
+ --ease-ease: cubic-bezier(0.33, 0, 0.67, 1);
192
+ --ease-linear: cubic-bezier(0, 0, 1, 1);
193
+ }
194
+
195
+ /* ========================================================================== */
196
+ /* DARK theme — re-points aliases at Fluent's published dark slots. */
197
+ /* (Not a hex inversion.) Brand ramp is global and unchanged. */
198
+ /* ========================================================================== */
199
+ .dark {
200
+ /* Match native UA chrome to the dark surface (see :root note). */
201
+ color-scheme: dark;
202
+
203
+ /* --- shadcn core set (dark) — tonal elevation delta --------------------- */
204
+ --background: #242424; /* grey[14] canvas */
205
+ --foreground: #ffffff;
206
+ --card: #292929; /* grey[16] — raised above canvas */
207
+ --card-foreground: #ffffff;
208
+ --popover: #292929;
209
+ --popover-foreground: #ffffff;
210
+ --primary: #115ea3; /* brand[70] — Fluent dark filled-brand */
211
+ --primary-foreground: #ffffff;
212
+ --secondary: #333333; /* grey[20] */
213
+ --secondary-foreground: #ffffff;
214
+ --muted: #1f1f1f; /* grey[12] */
215
+ --muted-foreground: #adadad; /* grey[68] */
216
+ --accent: #333333; /* grey[20] */
217
+ --accent-foreground: #ffffff;
218
+ --destructive: #d13438; /* red primary (kept) */
219
+ --destructive-foreground: #ffffff;
220
+ --border: #525252; /* grey[32] */
221
+ --input: #525252; /* grey[32] */
222
+ --ring: #479ef5; /* brand[100] */
223
+
224
+ /* --- chart palette (lightened for dark surfaces) ------------------------ */
225
+ --chart-1: #479ef5;
226
+ --chart-2: #4bb6b9;
227
+ --chart-3: #54b054;
228
+ --chart-4: #ff8f6b;
229
+ --chart-5: #a97fd6;
230
+
231
+ /* --- sidebar ------------------------------------------------------------ */
232
+ --sidebar: #1f1f1f; /* grey[12] */
233
+ --sidebar-foreground: #ffffff;
234
+ --sidebar-primary: #479ef5;
235
+ --sidebar-primary-foreground: #ffffff;
236
+ --sidebar-accent: #333333; /* grey[20] */
237
+ --sidebar-accent-foreground: #ffffff;
238
+ --sidebar-border: #333333; /* grey[20] */
239
+ --sidebar-ring: #479ef5;
240
+
241
+ /* --- status extensions (dark) ------------------------------------------- */
242
+ --success: #54b054;
243
+ --success-foreground: #0a0a0a;
244
+ --success-subtle: #0c1c0c;
245
+ --success-border: #2f7a2f;
246
+ --warning: #ff8f6b;
247
+ --warning-foreground: #1a0a02;
248
+ --warning-text: #ffb59a;
249
+ --warning-subtle: #22120a;
250
+ --warning-border: #a62d01;
251
+ --destructive-subtle: #3b1212;
252
+ --destructive-border: #9f282b;
253
+ --destructive-text: #ff9a90; /* AA-safe destructive text on dark surfaces (lightened red) */
254
+
255
+ /* --- second-tier neutral foreground (dark) ------------------------------ */
256
+ --foreground-2: #d6d6d6; /* NeutralForeground2 dark (grey[84]) */
257
+
258
+ /* --- accessible interactive stroke (dark) — no dark Figma frame exists for
259
+ these, so hover/pressed are derived: dark neutrals brighten on
260
+ interaction, so each step is lighter than rest (rest = --muted-foreground
261
+ dark). */
262
+ --stroke-accessible: #adadad; /* grey[68] (= --muted-foreground) */
263
+ --stroke-accessible-hover: #b3b3b3; /* [D] derived — one step brighter */
264
+ --stroke-accessible-pressed: #bdbdbd; /* [D] derived — brightest (grey[75]) */
265
+
266
+ /* --- badge warning (dark) — both tokens are Fluent Static, kept identical
267
+ across themes; the chip is self-contained (#242424 on #f7630c ~4.98:1
268
+ clears AA in both themes). */
269
+ --warning-badge: #f7630c;
270
+ --warning-badge-foreground: #242424;
271
+
272
+ /* --- elevation primitives (composite strings inherit via var()) --------- */
273
+ --shadow-ambient: rgba(0, 0, 0, 0.24);
274
+ --shadow-key: rgba(0, 0, 0, 0.28);
275
+
276
+ /* --- focus double-stroke (inverted for dark) ---------------------------- */
277
+ --stroke-focus-outer: #ffffff;
278
+ --stroke-focus-inner: #000000;
279
+ }
280
+
281
+ /* ========================================================================== */
282
+ /* High contrast (a): Windows / forced-colors — hand control to the system. */
283
+ /* ========================================================================== */
284
+ @media (forced-colors: active) {
285
+ :root {
286
+ --ring: Highlight;
287
+ --border: CanvasText;
288
+ --input: CanvasText;
289
+ }
290
+ /* No explicit brand-ramp / status-extension remap is needed here (unlike the
291
+ `.high-contrast` class below, which must remap them by hand). Under
292
+ forced-colors the UA applies `forced-color-adjust: auto` to every element,
293
+ so hex-valued utilities like `bg-brand-160`, `bg-destructive-subtle`, and
294
+ `text-warning-text` are already forced to the system palette automatically.
295
+ The three tokens above are remapped only to steer specific roles to a
296
+ *chosen* system color (Highlight ring, CanvasText border/input) rather than
297
+ the UA default — the brand/status surfaces have no such preference, so the
298
+ automatic override is exactly right and re-declaring them would be noise. */
299
+ /* Focus must stay visible and survive forced-color-adjust. */
300
+ :where(button, a, [role="button"], input, select, textarea):focus-visible {
301
+ outline: 2px solid Highlight;
302
+ outline-offset: 2px;
303
+ }
304
+ /* Preserve brand fill only where meaning depends on it. */
305
+ .btn-primary {
306
+ forced-color-adjust: none;
307
+ background: Highlight;
308
+ color: HighlightText;
309
+ }
310
+ }
311
+
312
+ /* ========================================================================== */
313
+ /* High contrast (b): explicit `.high-contrast` theme class (cross-platform). */
314
+ /* Mirrors Fluent's highContrast theme via CSS system-color keywords. */
315
+ /* ========================================================================== */
316
+ .high-contrast {
317
+ /* This theme paints entirely in CSS system colors (Canvas / CanvasText /
318
+ Highlight …), which resolve against whatever forced-colors theme the OS is
319
+ showing — it is neither inherently light nor dark. Advertise both so native
320
+ chrome resolves to the OS's current scheme. */
321
+ color-scheme: light dark;
322
+
323
+ --background: Canvas;
324
+ --foreground: CanvasText;
325
+ --card: Canvas;
326
+ --card-foreground: CanvasText;
327
+ --popover: Canvas;
328
+ --popover-foreground: CanvasText;
329
+ --primary: Highlight;
330
+ --primary-foreground: HighlightText;
331
+ --secondary: ButtonFace;
332
+ --secondary-foreground: ButtonText;
333
+ --muted: Canvas;
334
+ --muted-foreground: GrayText;
335
+ --foreground-2: CanvasText;
336
+ --accent: Highlight;
337
+ --accent-foreground: HighlightText;
338
+ --destructive: LinkText;
339
+ --destructive-foreground: Canvas;
340
+ --border: CanvasText;
341
+ --input: CanvasText;
342
+ --ring: Highlight;
343
+ --success: CanvasText;
344
+ --warning: CanvasText;
345
+
346
+ /* Status-extension surfaces/borders/text that Alert consumes. The core set
347
+ above covers shadcn's tokens but NOT these, so a destructive/success/
348
+ warning Alert would otherwise keep its raw hex tint in an HC theme.
349
+ Subtle fills collapse to Canvas; borders and the AA-safe *-text tokens go
350
+ to CanvasText — so a status Alert renders as a Canvas panel with a
351
+ CanvasText border and CanvasText icon + title (status still conveyed by
352
+ icon + title text, never color alone — checklist §5). `--success` is
353
+ already CanvasText above, which is what the success icon/title read from,
354
+ so only `--warning-text`/`--destructive-text` need explicit entries. */
355
+ --destructive-subtle: Canvas;
356
+ --success-subtle: Canvas;
357
+ --warning-subtle: Canvas;
358
+ --destructive-border: CanvasText;
359
+ --success-border: CanvasText;
360
+ --warning-border: CanvasText;
361
+ --destructive-text: CanvasText;
362
+ --warning-text: CanvasText;
363
+
364
+ /* Interactive control strokes -> CanvasText so every outline stays visible;
365
+ the badge-warning chip collapses to a Canvas surface with CanvasText ink
366
+ (status conveyed by text, never color alone — checklist §5). */
367
+ --stroke-accessible: CanvasText;
368
+ --stroke-accessible-hover: CanvasText;
369
+ --stroke-accessible-pressed: CanvasText;
370
+ --warning-badge: Canvas;
371
+ --warning-badge-foreground: CanvasText;
372
+
373
+ /* Alert's `info` variant and Avatar's initials fallback tint with the raw
374
+ brand ramp (no dedicated status token), so the specific stops they use as
375
+ surfaces vs. text/border must be remapped here too — otherwise real brand
376
+ blue leaks into an HC theme. Only the consumed stops are redefined:
377
+ surfaces -> Canvas: --brand-160 (Alert-info + Avatar light bg),
378
+ --brand-30 (Alert-info dark bg),
379
+ --brand-40 (Avatar dark bg).
380
+ text + border -> CanvasText: --brand-80 / --brand-100 (Alert-info
381
+ icon+title, light/dark), plus the two DUAL-ROLE
382
+ stops --brand-70 and --brand-140. Each of those is
383
+ both an Avatar initials-text color (light text=70,
384
+ dark text=140) AND an Alert-info border (light
385
+ border=140, dark border=70). Mapping them to
386
+ CanvasText — not Canvas as a naive "border stop"
387
+ reading would suggest — is deliberate: Canvas would
388
+ paint Avatar's initials the same color as their
389
+ fallback background and make them vanish. CanvasText
390
+ keeps the initials legible in both themes and still
391
+ renders the info border as a visible stroke.
392
+ --brand-110 is unused today but remapped defensively
393
+ to keep the consumed-text band internally consistent. */
394
+ --brand-160: Canvas;
395
+ --brand-30: Canvas;
396
+ --brand-40: Canvas;
397
+ --brand-70: CanvasText;
398
+ --brand-80: CanvasText;
399
+ --brand-100: CanvasText;
400
+ --brand-110: CanvasText;
401
+ --brand-140: CanvasText;
402
+ }
403
+
404
+ /* ========================================================================== */
405
+ /* Reduced motion: collapse durations so utilities/vars settle instantly. */
406
+ /* ========================================================================== */
407
+ @media (prefers-reduced-motion: reduce) {
408
+ :root {
409
+ --duration-ultra-fast: 0.01ms;
410
+ --duration-faster: 0.01ms;
411
+ --duration-fast: 0.01ms;
412
+ --duration-normal: 0.01ms;
413
+ --duration-gentle: 0.01ms;
414
+ --duration-slow: 0.01ms;
415
+ --duration-slower: 0.01ms;
416
+ --duration-ultra-slow: 0.01ms;
417
+ }
418
+ }
419
+
420
+ /* ========================================================================== */
421
+ /* Tailwind v4 bridge: re-export semantic vars as utilities. */
422
+ /* `inline` resolves var() at use-site, so `.dark` re-pointing works without */
423
+ /* regenerating utilities. Inline theme vars are NOT re-emitted to :root, so */
424
+ /* the `--x: var(--x)` self-references below are safe (no circular refs). */
425
+ /* ========================================================================== */
426
+ @theme inline {
427
+ /* colors → bg-*, text-*, border-*, ring-* */
428
+ --color-background: var(--background);
429
+ --color-foreground: var(--foreground);
430
+ --color-card: var(--card);
431
+ --color-card-foreground: var(--card-foreground);
432
+ --color-popover: var(--popover);
433
+ --color-popover-foreground: var(--popover-foreground);
434
+ --color-primary: var(--primary);
435
+ --color-primary-foreground: var(--primary-foreground);
436
+ --color-secondary: var(--secondary);
437
+ --color-secondary-foreground: var(--secondary-foreground);
438
+ --color-muted: var(--muted);
439
+ --color-muted-foreground: var(--muted-foreground);
440
+ --color-foreground-2: var(--foreground-2);
441
+ --color-accent: var(--accent);
442
+ --color-accent-foreground: var(--accent-foreground);
443
+ --color-destructive: var(--destructive);
444
+ --color-destructive-foreground: var(--destructive-foreground);
445
+ --color-border: var(--border);
446
+ --color-input: var(--input);
447
+ --color-ring: var(--ring);
448
+
449
+ /* chart */
450
+ --color-chart-1: var(--chart-1);
451
+ --color-chart-2: var(--chart-2);
452
+ --color-chart-3: var(--chart-3);
453
+ --color-chart-4: var(--chart-4);
454
+ --color-chart-5: var(--chart-5);
455
+
456
+ /* sidebar */
457
+ --color-sidebar: var(--sidebar);
458
+ --color-sidebar-foreground: var(--sidebar-foreground);
459
+ --color-sidebar-primary: var(--sidebar-primary);
460
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
461
+ --color-sidebar-accent: var(--sidebar-accent);
462
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
463
+ --color-sidebar-border: var(--sidebar-border);
464
+ --color-sidebar-ring: var(--sidebar-ring);
465
+
466
+ /* status extensions */
467
+ --color-success: var(--success);
468
+ --color-success-foreground: var(--success-foreground);
469
+ --color-success-subtle: var(--success-subtle);
470
+ --color-success-border: var(--success-border);
471
+ --color-warning: var(--warning);
472
+ --color-warning-foreground: var(--warning-foreground);
473
+ --color-warning-text: var(--warning-text);
474
+ --color-warning-subtle: var(--warning-subtle);
475
+ --color-warning-border: var(--warning-border);
476
+ --color-destructive-subtle: var(--destructive-subtle);
477
+ --color-destructive-border: var(--destructive-border);
478
+ --color-destructive-text: var(--destructive-text);
479
+ --color-stroke-accessible: var(--stroke-accessible);
480
+ --color-stroke-accessible-hover: var(--stroke-accessible-hover);
481
+ --color-stroke-accessible-pressed: var(--stroke-accessible-pressed);
482
+ --color-warning-badge: var(--warning-badge);
483
+ --color-warning-badge-foreground: var(--warning-badge-foreground);
484
+
485
+ /* brand ramp → bg-brand-80, text-brand-160, etc. */
486
+ --color-brand-10: var(--brand-10);
487
+ --color-brand-20: var(--brand-20);
488
+ --color-brand-30: var(--brand-30);
489
+ --color-brand-40: var(--brand-40);
490
+ --color-brand-50: var(--brand-50);
491
+ --color-brand-60: var(--brand-60);
492
+ --color-brand-70: var(--brand-70);
493
+ --color-brand-80: var(--brand-80);
494
+ --color-brand-90: var(--brand-90);
495
+ --color-brand-100: var(--brand-100);
496
+ --color-brand-110: var(--brand-110);
497
+ --color-brand-120: var(--brand-120);
498
+ --color-brand-130: var(--brand-130);
499
+ --color-brand-140: var(--brand-140);
500
+ --color-brand-150: var(--brand-150);
501
+ --color-brand-160: var(--brand-160);
502
+
503
+ /* radius → rounded-* (derived from a single --radius base) */
504
+ --radius-sm: calc(var(--radius) - 4px);
505
+ --radius-md: calc(var(--radius) - 2px);
506
+ --radius-lg: var(--radius);
507
+ --radius-xl: calc(var(--radius) + 4px);
508
+ --radius-full: 9999px;
509
+
510
+ /* shadows → shadow-* (composite strings live in :root, adapt via primitives) */
511
+ --shadow-2: var(--shadow-2);
512
+ --shadow-4: var(--shadow-4);
513
+ --shadow-8: var(--shadow-8);
514
+ --shadow-16: var(--shadow-16);
515
+ --shadow-28: var(--shadow-28);
516
+ --shadow-64: var(--shadow-64);
517
+
518
+ /* fonts → font-sans, font-mono, font-numeric */
519
+ --font-sans: var(--font-sans);
520
+ --font-mono: var(--font-mono);
521
+ --font-numeric: var(--font-numeric);
522
+
523
+ /* custom type sizes Tailwind's default scale misses (paired line-heights) */
524
+ --text-caption2: 10px;
525
+ --text-caption2--line-height: 14px;
526
+ --text-title2: 28px;
527
+ --text-title2--line-height: 36px;
528
+ --text-title1: 32px;
529
+ --text-title1--line-height: 40px;
530
+ --text-large-title: 40px;
531
+ --text-large-title--line-height: 52px;
532
+ --text-display: 68px;
533
+ --text-display--line-height: 92px;
534
+
535
+ /* motion durations → duration-* utilities (Tailwind reads --transition-duration-*) */
536
+ --transition-duration-ultra-fast: var(--duration-ultra-fast);
537
+ --transition-duration-faster: var(--duration-faster);
538
+ --transition-duration-fast: var(--duration-fast);
539
+ --transition-duration-normal: var(--duration-normal);
540
+ --transition-duration-gentle: var(--duration-gentle);
541
+ --transition-duration-slow: var(--duration-slow);
542
+ --transition-duration-slower: var(--duration-slower);
543
+ --transition-duration-ultra-slow: var(--duration-ultra-slow);
544
+
545
+ /* motion easings → ease-* utilities */
546
+ --ease-accelerate-max: var(--ease-accelerate-max);
547
+ --ease-accelerate-mid: var(--ease-accelerate-mid);
548
+ --ease-accelerate-min: var(--ease-accelerate-min);
549
+ --ease-decelerate-max: var(--ease-decelerate-max);
550
+ --ease-decelerate-mid: var(--ease-decelerate-mid);
551
+ --ease-decelerate-min: var(--ease-decelerate-min);
552
+ --ease-max: var(--ease-max);
553
+ --ease-ease: var(--ease-ease);
554
+ --ease-linear: var(--ease-linear);
555
+
556
+ /* motion keyframes → animate-* utilities.
557
+ shimmer: Fluent Skeleton's moving-gradient wave — the consuming element
558
+ paints a 200%-wide gradient and the keyframes sweep background-position
559
+ (see skeleton.tsx). progress-indeterminate: Fluent ProgressBar's sliding
560
+ segment — a w-1/3 indicator travels from fully off-track left (-100% of
561
+ its own width) to fully off-track right (300%) inside the root's
562
+ overflow-hidden (see progress.tsx). Components pair these with
563
+ motion-reduce:animate-none themselves (the automatic reduced-motion
564
+ duration collapse only covers the --duration-* transition tokens). */
565
+ --animate-shimmer: shimmer 3s linear infinite;
566
+ --animate-progress-indeterminate: progress-indeterminate 1.8s var(--ease-ease) infinite;
567
+
568
+ @keyframes shimmer {
569
+ from {
570
+ background-position: 200% 0;
571
+ }
572
+ to {
573
+ background-position: -200% 0;
574
+ }
575
+ }
576
+ @keyframes progress-indeterminate {
577
+ from {
578
+ translate: -100% 0;
579
+ }
580
+ to {
581
+ translate: 300% 0;
582
+ }
583
+ }
584
+ }
585
+
586
+ /* ========================================================================== */
587
+ /* Base layer: shadcn v4 defaults (border/outline color + body surface). */
588
+ /* ========================================================================== */
589
+ @layer base {
590
+ * {
591
+ @apply border-border outline-ring/50;
592
+ }
593
+ body {
594
+ @apply bg-background text-foreground;
595
+ }
596
+ }