@gradeui/ui 2.0.0 → 3.0.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.
@@ -0,0 +1,2278 @@
1
+ @import "tailwindcss";
2
+ @import "@gradeui/core/tokens.css"; /* primitive ramps + semantic aliases + type scale (layers 1-2) */
3
+
4
+ /* v4 NATIVE THEME — the v3 config bridge (tailwind.config.ts →
5
+ tailwind-preset.ts) is retired (THEME-MIGRATION.md Phase A). Everything
6
+ the preset defined lives here as native @theme declarations.
7
+
8
+ Source scanning: v4's automatic detection covers everything inside this
9
+ package (components/, lib/, styles/ — INCLUDING the .md sidecars, whose
10
+ class mentions the old @config build was already picking up via
11
+ auto-detection; the v3 `content` globs were additive, not exclusive).
12
+ Only out-of-package sources need explicit @source directives. */
13
+
14
+ /* Playground scaffolds — siloed dev-only set rendered by Studio's
15
+ Playground tab. Scanning them here means arbitrary Tailwind values
16
+ (`h-[600px]`, `md:grid-cols-[minmax(0,440px)_1fr]`, etc.) get compiled
17
+ into dist/styles.css — the bundle Fast Frame loads in its iframe.
18
+ Without this, arbitrary classes would be present in the JSX but
19
+ generate no CSS rules. (Path is relative to this file.) */
20
+ @source "../../studio/src/playbook/layouts/scaffolds-playground/*.{jsx,tsx}";
21
+
22
+ /* v3 `darkMode: "class"` — same selector the @config compat layer
23
+ generated, so dark: variants are unchanged. */
24
+ @custom-variant dark (&:is(.dark *));
25
+
26
+ @plugin "tailwindcss-animate";
27
+
28
+ /* The theme bridge. `inline` because every value here references a runtime
29
+ CSS variable (the GradeThemeProvider contract) — utilities must carry
30
+ the var() reference itself, not a second-hop --color-* theme var.
31
+ `reference` so none of these are re-emitted into :root (the runtime
32
+ vars are authored in @gradeui/core/tokens.css and the :root blocks
33
+ below; emitting e.g. `--shadow-lift: var(--shadow-lift)` would be
34
+ circular). Net effect: byte-compatible with the old @config output.
35
+
36
+ Fonts are deliberately ABSENT: the runtime vars are literally named
37
+ --font-sans / --font-mono (same as v4's theme namespace), so preflight's
38
+ `--default-font-family: var(--font-sans)` already lands on them and the
39
+ font-sans/font-mono utilities resolve correctly with no declaration —
40
+ declaring them here would be the one truly circular case. */
41
+ /* -- Type scale extension — `2xs` (11px) is the dense tool-panel step
42
+ below `xs`, used by the Studio inspector and other Figma-density
43
+ surfaces. Declared in a PLAIN @theme block (not inline/reference) so
44
+ the utility references var(--text-2xs) — that's what lets the theme
45
+ generator re-pitch the whole named ladder at runtime (Phase B2): a
46
+ modular scale writes --text-2xs…--text-7xl to :root and every text-*
47
+ utility follows. -- */
48
+ @theme {
49
+ --text-2xs: 0.6875rem;
50
+ --text-2xs--line-height: 1rem;
51
+ }
52
+
53
+ @theme inline reference {
54
+ /* -- Brand color ramps — values live in @gradeui/core as --gds-* vars. -- */
55
+ --color-rds-green: var(--gds-green);
56
+ --color-rds-green-50: var(--gds-green-50);
57
+ --color-rds-green-100: var(--gds-green-100);
58
+ --color-rds-green-200: var(--gds-green-200);
59
+ --color-rds-green-300: var(--gds-green-300);
60
+ --color-rds-green-400: var(--gds-green-400);
61
+ --color-rds-green-500: var(--gds-green-500);
62
+ --color-rds-green-600: var(--gds-green-600);
63
+ --color-rds-green-700: var(--gds-green-700);
64
+ --color-rds-green-800: var(--gds-green-800);
65
+ --color-rds-green-900: var(--gds-green-900);
66
+ --color-rds-green-950: var(--gds-green-950);
67
+ --color-rds-yellow: var(--gds-yellow);
68
+ --color-rds-yellow-50: var(--gds-yellow-50);
69
+ --color-rds-yellow-100: var(--gds-yellow-100);
70
+ --color-rds-yellow-200: var(--gds-yellow-200);
71
+ --color-rds-yellow-300: var(--gds-yellow-300);
72
+ --color-rds-yellow-400: var(--gds-yellow-400);
73
+ --color-rds-yellow-500: var(--gds-yellow-500);
74
+ --color-rds-yellow-600: var(--gds-yellow-600);
75
+ --color-rds-yellow-700: var(--gds-yellow-700);
76
+ --color-rds-yellow-800: var(--gds-yellow-800);
77
+ --color-rds-yellow-900: var(--gds-yellow-900);
78
+ --color-rds-orange: var(--gds-orange);
79
+ --color-rds-orange-50: var(--gds-orange-50);
80
+ --color-rds-orange-100: var(--gds-orange-100);
81
+ --color-rds-orange-200: var(--gds-orange-200);
82
+ --color-rds-orange-300: var(--gds-orange-300);
83
+ --color-rds-orange-400: var(--gds-orange-400);
84
+ --color-rds-orange-500: var(--gds-orange-500);
85
+ --color-rds-orange-600: var(--gds-orange-600);
86
+ --color-rds-orange-700: var(--gds-orange-700);
87
+ --color-rds-orange-800: var(--gds-orange-800);
88
+ --color-rds-orange-900: var(--gds-orange-900);
89
+ --color-rds-red: var(--gds-red);
90
+ --color-rds-red-50: var(--gds-red-50);
91
+ --color-rds-red-100: var(--gds-red-100);
92
+ --color-rds-red-200: var(--gds-red-200);
93
+ --color-rds-red-300: var(--gds-red-300);
94
+ --color-rds-red-400: var(--gds-red-400);
95
+ --color-rds-red-500: var(--gds-red-500);
96
+ --color-rds-red-600: var(--gds-red-600);
97
+ --color-rds-red-700: var(--gds-red-700);
98
+ --color-rds-red-800: var(--gds-red-800);
99
+ --color-rds-red-900: var(--gds-red-900);
100
+ --color-rds-teal: var(--gds-teal);
101
+ --color-rds-teal-50: var(--gds-teal-50);
102
+ --color-rds-teal-100: var(--gds-teal-100);
103
+ --color-rds-teal-200: var(--gds-teal-200);
104
+ --color-rds-teal-300: var(--gds-teal-300);
105
+ --color-rds-teal-400: var(--gds-teal-400);
106
+ --color-rds-teal-500: var(--gds-teal-500);
107
+ --color-rds-teal-600: var(--gds-teal-600);
108
+ --color-rds-teal-700: var(--gds-teal-700);
109
+ --color-rds-teal-800: var(--gds-teal-800);
110
+ --color-rds-teal-900: var(--gds-teal-900);
111
+ --color-rds-teal-950: var(--gds-teal-950);
112
+ --color-rds-navy: var(--gds-navy);
113
+ --color-rds-navy-50: var(--gds-navy-50);
114
+ --color-rds-navy-100: var(--gds-navy-100);
115
+ --color-rds-navy-200: var(--gds-navy-200);
116
+ --color-rds-navy-300: var(--gds-navy-300);
117
+ --color-rds-navy-400: var(--gds-navy-400);
118
+ --color-rds-navy-500: var(--gds-navy-500);
119
+ --color-rds-navy-600: var(--gds-navy-600);
120
+ --color-rds-navy-700: var(--gds-navy-700);
121
+ --color-rds-navy-800: var(--gds-navy-800);
122
+ --color-rds-navy-900: var(--gds-navy-900);
123
+ --color-rds-blue: var(--gds-blue);
124
+ --color-rds-blue-50: var(--gds-blue-50);
125
+ --color-rds-blue-100: var(--gds-blue-100);
126
+ --color-rds-blue-200: var(--gds-blue-200);
127
+ --color-rds-blue-300: var(--gds-blue-300);
128
+ --color-rds-blue-400: var(--gds-blue-400);
129
+ --color-rds-blue-500: var(--gds-blue-500);
130
+ --color-rds-blue-600: var(--gds-blue-600);
131
+ --color-rds-blue-700: var(--gds-blue-700);
132
+ --color-rds-blue-800: var(--gds-blue-800);
133
+ --color-rds-blue-900: var(--gds-blue-900);
134
+ --color-rds-gray-50: var(--gds-gray-50);
135
+ --color-rds-gray-100: var(--gds-gray-100);
136
+ --color-rds-gray-200: var(--gds-gray-200);
137
+ --color-rds-gray-300: var(--gds-gray-300);
138
+ --color-rds-gray-400: var(--gds-gray-400);
139
+ --color-rds-gray-500: var(--gds-gray-500);
140
+ --color-rds-gray-600: var(--gds-gray-600);
141
+ --color-rds-gray-700: var(--gds-gray-700);
142
+ --color-rds-gray-800: var(--gds-gray-800);
143
+ --color-rds-gray-900: var(--gds-gray-900);
144
+ --color-rds-gray-950: var(--gds-gray-950);
145
+ --color-rds-black: var(--gds-black);
146
+ --color-rds-white: var(--gds-white);
147
+
148
+ /* -- Semantic roles — runtime vars hold bare "L C H" OKLCH triplets,
149
+ wrapped with oklch() at use. Opacity shortcuts (bg-primary/50) come
150
+ out as color-mix() under v4 — visually identical to the old
151
+ <alpha-value> substitution. -- */
152
+ --color-border: oklch(var(--border));
153
+ --color-input: oklch(var(--input));
154
+ --color-ring: oklch(var(--ring));
155
+ --color-background: oklch(var(--background));
156
+ --color-foreground: oklch(var(--foreground));
157
+ --color-primary: oklch(var(--primary));
158
+ --color-primary-foreground: oklch(var(--primary-foreground));
159
+ --color-secondary: oklch(var(--secondary));
160
+ --color-secondary-foreground: oklch(var(--secondary-foreground));
161
+ --color-destructive: oklch(var(--destructive));
162
+ --color-destructive-foreground: oklch(var(--destructive-foreground));
163
+ /* Alert surface pair — paler tinted bg + deeper on-surface text. */
164
+ --color-destructive-soft: oklch(var(--destructive-soft));
165
+ --color-destructive-deep: oklch(var(--destructive-deep));
166
+ --color-muted: oklch(var(--muted));
167
+ --color-muted-foreground: oklch(var(--muted-foreground));
168
+ --color-accent: oklch(var(--accent));
169
+ --color-accent-foreground: oklch(var(--accent-foreground));
170
+ --color-popover: oklch(var(--popover));
171
+ --color-popover-foreground: oklch(var(--popover-foreground));
172
+ --color-card: oklch(var(--card));
173
+ --color-card-foreground: oklch(var(--card-foreground));
174
+ /* Status colours — each exposes `-soft` / `-deep` siblings, mapping to
175
+ the tinted alert surface + readable text tokens generated by the
176
+ theme pipeline (lib/themes/oklch.ts#deriveAlertPair). */
177
+ --color-success: oklch(var(--success));
178
+ --color-success-soft: oklch(var(--success-soft));
179
+ --color-success-deep: oklch(var(--success-deep));
180
+ --color-warning: oklch(var(--warning));
181
+ --color-warning-soft: oklch(var(--warning-soft));
182
+ --color-warning-deep: oklch(var(--warning-deep));
183
+ --color-info: oklch(var(--info));
184
+ --color-info-soft: oklch(var(--info-soft));
185
+ --color-info-deep: oklch(var(--info-deep));
186
+ --color-highlight: oklch(var(--highlight));
187
+ --color-highlight-soft: oklch(var(--highlight-soft));
188
+ --color-highlight-deep: oklch(var(--highlight-deep));
189
+ /* Selection pair — solid fill, on-fill text, ambient glow halo. */
190
+ --color-selected: oklch(var(--selected));
191
+ --color-selected-foreground: oklch(var(--selected-foreground));
192
+ --color-selected-glow: oklch(var(--selected-glow));
193
+ /* Chart series palette — derived from theme hues by the generator. */
194
+ --color-chart-1: oklch(var(--chart-1));
195
+ --color-chart-2: oklch(var(--chart-2));
196
+ --color-chart-3: oklch(var(--chart-3));
197
+ --color-chart-4: oklch(var(--chart-4));
198
+ --color-chart-5: oklch(var(--chart-5));
199
+
200
+ /* -- Role ramp families (THEME-MIGRATION.md B4) — every semantic alias
201
+ is a whole ramp: status displays many ways (soft 100 bg, solid 600
202
+ fill, 800 text). The generator writes --gds-<role>-<step> OKLCH
203
+ triplets to :root (applyThemeToRoot); each step falls back to the
204
+ flat role triplet so e.g. bg-success-600 degrades to bg-success when
205
+ no generated theme is active. `neutral` is deliberately ABSENT from
206
+ the utility layer — --color-neutral-* would shadow Tailwind's
207
+ default neutral palette; its family ships as runtime vars only. -- */
208
+ --color-primary-50: oklch(var(--gds-primary-50, var(--primary)));
209
+ --color-primary-100: oklch(var(--gds-primary-100, var(--primary)));
210
+ --color-primary-200: oklch(var(--gds-primary-200, var(--primary)));
211
+ --color-primary-300: oklch(var(--gds-primary-300, var(--primary)));
212
+ --color-primary-400: oklch(var(--gds-primary-400, var(--primary)));
213
+ --color-primary-500: oklch(var(--gds-primary-500, var(--primary)));
214
+ --color-primary-600: oklch(var(--gds-primary-600, var(--primary)));
215
+ --color-primary-700: oklch(var(--gds-primary-700, var(--primary)));
216
+ --color-primary-800: oklch(var(--gds-primary-800, var(--primary)));
217
+ --color-primary-900: oklch(var(--gds-primary-900, var(--primary)));
218
+ --color-primary-950: oklch(var(--gds-primary-950, var(--primary)));
219
+ --color-accent-50: oklch(var(--gds-accent-50, var(--accent)));
220
+ --color-accent-100: oklch(var(--gds-accent-100, var(--accent)));
221
+ --color-accent-200: oklch(var(--gds-accent-200, var(--accent)));
222
+ --color-accent-300: oklch(var(--gds-accent-300, var(--accent)));
223
+ --color-accent-400: oklch(var(--gds-accent-400, var(--accent)));
224
+ --color-accent-500: oklch(var(--gds-accent-500, var(--accent)));
225
+ --color-accent-600: oklch(var(--gds-accent-600, var(--accent)));
226
+ --color-accent-700: oklch(var(--gds-accent-700, var(--accent)));
227
+ --color-accent-800: oklch(var(--gds-accent-800, var(--accent)));
228
+ --color-accent-900: oklch(var(--gds-accent-900, var(--accent)));
229
+ --color-accent-950: oklch(var(--gds-accent-950, var(--accent)));
230
+ --color-success-50: oklch(var(--gds-success-50, var(--success)));
231
+ --color-success-100: oklch(var(--gds-success-100, var(--success)));
232
+ --color-success-200: oklch(var(--gds-success-200, var(--success)));
233
+ --color-success-300: oklch(var(--gds-success-300, var(--success)));
234
+ --color-success-400: oklch(var(--gds-success-400, var(--success)));
235
+ --color-success-500: oklch(var(--gds-success-500, var(--success)));
236
+ --color-success-600: oklch(var(--gds-success-600, var(--success)));
237
+ --color-success-700: oklch(var(--gds-success-700, var(--success)));
238
+ --color-success-800: oklch(var(--gds-success-800, var(--success)));
239
+ --color-success-900: oklch(var(--gds-success-900, var(--success)));
240
+ --color-success-950: oklch(var(--gds-success-950, var(--success)));
241
+ --color-warning-50: oklch(var(--gds-warning-50, var(--warning)));
242
+ --color-warning-100: oklch(var(--gds-warning-100, var(--warning)));
243
+ --color-warning-200: oklch(var(--gds-warning-200, var(--warning)));
244
+ --color-warning-300: oklch(var(--gds-warning-300, var(--warning)));
245
+ --color-warning-400: oklch(var(--gds-warning-400, var(--warning)));
246
+ --color-warning-500: oklch(var(--gds-warning-500, var(--warning)));
247
+ --color-warning-600: oklch(var(--gds-warning-600, var(--warning)));
248
+ --color-warning-700: oklch(var(--gds-warning-700, var(--warning)));
249
+ --color-warning-800: oklch(var(--gds-warning-800, var(--warning)));
250
+ --color-warning-900: oklch(var(--gds-warning-900, var(--warning)));
251
+ --color-warning-950: oklch(var(--gds-warning-950, var(--warning)));
252
+ --color-info-50: oklch(var(--gds-info-50, var(--info)));
253
+ --color-info-100: oklch(var(--gds-info-100, var(--info)));
254
+ --color-info-200: oklch(var(--gds-info-200, var(--info)));
255
+ --color-info-300: oklch(var(--gds-info-300, var(--info)));
256
+ --color-info-400: oklch(var(--gds-info-400, var(--info)));
257
+ --color-info-500: oklch(var(--gds-info-500, var(--info)));
258
+ --color-info-600: oklch(var(--gds-info-600, var(--info)));
259
+ --color-info-700: oklch(var(--gds-info-700, var(--info)));
260
+ --color-info-800: oklch(var(--gds-info-800, var(--info)));
261
+ --color-info-900: oklch(var(--gds-info-900, var(--info)));
262
+ --color-info-950: oklch(var(--gds-info-950, var(--info)));
263
+ --color-highlight-50: oklch(var(--gds-highlight-50, var(--highlight)));
264
+ --color-highlight-100: oklch(var(--gds-highlight-100, var(--highlight)));
265
+ --color-highlight-200: oklch(var(--gds-highlight-200, var(--highlight)));
266
+ --color-highlight-300: oklch(var(--gds-highlight-300, var(--highlight)));
267
+ --color-highlight-400: oklch(var(--gds-highlight-400, var(--highlight)));
268
+ --color-highlight-500: oklch(var(--gds-highlight-500, var(--highlight)));
269
+ --color-highlight-600: oklch(var(--gds-highlight-600, var(--highlight)));
270
+ --color-highlight-700: oklch(var(--gds-highlight-700, var(--highlight)));
271
+ --color-highlight-800: oklch(var(--gds-highlight-800, var(--highlight)));
272
+ --color-highlight-900: oklch(var(--gds-highlight-900, var(--highlight)));
273
+ --color-highlight-950: oklch(var(--gds-highlight-950, var(--highlight)));
274
+ --color-destructive-50: oklch(var(--gds-destructive-50, var(--destructive)));
275
+ --color-destructive-100: oklch(var(--gds-destructive-100, var(--destructive)));
276
+ --color-destructive-200: oklch(var(--gds-destructive-200, var(--destructive)));
277
+ --color-destructive-300: oklch(var(--gds-destructive-300, var(--destructive)));
278
+ --color-destructive-400: oklch(var(--gds-destructive-400, var(--destructive)));
279
+ --color-destructive-500: oklch(var(--gds-destructive-500, var(--destructive)));
280
+ --color-destructive-600: oklch(var(--gds-destructive-600, var(--destructive)));
281
+ --color-destructive-700: oklch(var(--gds-destructive-700, var(--destructive)));
282
+ --color-destructive-800: oklch(var(--gds-destructive-800, var(--destructive)));
283
+ --color-destructive-900: oklch(var(--gds-destructive-900, var(--destructive)));
284
+ --color-destructive-950: oklch(var(--gds-destructive-950, var(--destructive)));
285
+
286
+ /* Surface backgrounds — `bg-surface-glass` etc. work alongside the
287
+ `.gds-surface-*` classes (those add backdrop-filter). */
288
+ --color-surface-solid: var(--surface-solid);
289
+ --color-surface-translucent: var(--surface-translucent);
290
+ --color-surface-glass: var(--surface-glass);
291
+ --color-surface-glass-strong: var(--surface-glass-strong);
292
+
293
+ /* -- Radius — pinned to the theme's --radius base. -- */
294
+ --radius-lg: var(--radius);
295
+ --radius-md: calc(var(--radius) - 2px);
296
+ --radius-sm: calc(var(--radius) - 4px);
297
+
298
+ /* -- Presence — elevation tokens exposed both as semantic
299
+ `shadow-elevation-N` utilities AND as overrides of Tailwind's default
300
+ `shadow-sm/md/lg/xl/2xl` scale so existing call sites inherit the new
301
+ system without code changes. See PRESENCE.md.
302
+ (`shadow-none` stays v4's static `0 0 #0000` — that's what the old
303
+ build shipped too; the preset's `none: var(--elevation-0)` never won.) */
304
+ --shadow: var(--elevation-2);
305
+ --shadow-sm: var(--elevation-1);
306
+ --shadow-md: var(--elevation-4);
307
+ --shadow-lg: var(--elevation-5);
308
+ --shadow-xl: var(--elevation-5);
309
+ --shadow-2xl: var(--elevation-5);
310
+ --shadow-inner: var(--shadow-pressed-bevel);
311
+ /* Explicit elevation levels (preferred for new code) */
312
+ --shadow-elevation-0: var(--elevation-0);
313
+ --shadow-elevation-1: var(--elevation-1);
314
+ --shadow-elevation-2: var(--elevation-2);
315
+ --shadow-elevation-3: var(--elevation-3);
316
+ --shadow-elevation-4: var(--elevation-4);
317
+ --shadow-elevation-5: var(--elevation-5);
318
+ /* State variants for raised/tactile surfaces */
319
+ --shadow-raised: var(--elevation-3);
320
+ --shadow-hot: var(--elevation-hot);
321
+ --shadow-pressed: var(--elevation-pressed);
322
+ /* Single-layer atoms (when you need bevel-only / lift-only) */
323
+ --shadow-bevel-hi: var(--shadow-bevel-hi);
324
+ --shadow-bevel-lo: var(--shadow-bevel-lo);
325
+ --shadow-contact: var(--shadow-contact);
326
+ --shadow-lift: var(--shadow-lift);
327
+ --shadow-lift-deep: var(--shadow-lift-deep);
328
+ --shadow-heat-inner: var(--shadow-heat-inner);
329
+ --shadow-heat-outer: var(--shadow-heat-outer);
330
+
331
+ /* -- Backdrop blur (glass surfaces) — v4 shares the --blur-* namespace
332
+ between blur-* and backdrop-blur-*; only the backdrop forms are part
333
+ of the public API. -- */
334
+ --blur-glass: var(--surface-blur-glass);
335
+ --blur-glass-strong: var(--surface-blur-strong);
336
+ --blur-subtle: var(--surface-blur-subtle);
337
+
338
+ /* -- Motion -- */
339
+ --animate-accordion-down: accordion-down 0.2s ease-out;
340
+ --animate-accordion-up: accordion-up 0.2s ease-out;
341
+ --animate-fade-in: fade-in 0.2s ease-out;
342
+ --animate-fade-out: fade-out 0.2s ease-in;
343
+ --animate-scale-in: scale-in 0.15s ease-out;
344
+ --animate-slide-in-from-top: slide-in-from-top 0.2s ease-out;
345
+ --animate-slide-in-from-bottom: slide-in-from-bottom 0.2s ease-out;
346
+ --animate-slide-in-from-left: slide-in-from-left 0.2s ease-out;
347
+ --animate-slide-in-from-right: slide-in-from-right 0.2s ease-out;
348
+ --animate-energy-pulse: energy-pulse 2s ease-in-out infinite;
349
+ --animate-shimmer: shimmer 2s linear infinite;
350
+
351
+ @keyframes accordion-down {
352
+ from {
353
+ height: 0;
354
+ }
355
+ to {
356
+ height: var(--radix-accordion-content-height);
357
+ }
358
+ }
359
+ @keyframes accordion-up {
360
+ from {
361
+ height: var(--radix-accordion-content-height);
362
+ }
363
+ to {
364
+ height: 0;
365
+ }
366
+ }
367
+ @keyframes fade-in {
368
+ from {
369
+ opacity: 0;
370
+ }
371
+ to {
372
+ opacity: 1;
373
+ }
374
+ }
375
+ @keyframes fade-out {
376
+ from {
377
+ opacity: 1;
378
+ }
379
+ to {
380
+ opacity: 0;
381
+ }
382
+ }
383
+ @keyframes scale-in {
384
+ from {
385
+ opacity: 0;
386
+ transform: scale(0.95);
387
+ }
388
+ to {
389
+ opacity: 1;
390
+ transform: scale(1);
391
+ }
392
+ }
393
+ @keyframes slide-in-from-top {
394
+ from {
395
+ opacity: 0;
396
+ transform: translateY(-10px);
397
+ }
398
+ to {
399
+ opacity: 1;
400
+ transform: translateY(0);
401
+ }
402
+ }
403
+ @keyframes slide-in-from-bottom {
404
+ from {
405
+ opacity: 0;
406
+ transform: translateY(10px);
407
+ }
408
+ to {
409
+ opacity: 1;
410
+ transform: translateY(0);
411
+ }
412
+ }
413
+ @keyframes slide-in-from-left {
414
+ from {
415
+ opacity: 0;
416
+ transform: translateX(-10px);
417
+ }
418
+ to {
419
+ opacity: 1;
420
+ transform: translateX(0);
421
+ }
422
+ }
423
+ @keyframes slide-in-from-right {
424
+ from {
425
+ opacity: 0;
426
+ transform: translateX(10px);
427
+ }
428
+ to {
429
+ opacity: 1;
430
+ transform: translateX(0);
431
+ }
432
+ }
433
+ @keyframes energy-pulse {
434
+ 0%,
435
+ 100% {
436
+ opacity: 1;
437
+ box-shadow: 0 0 0 0 oklch(var(--accent) / 0.4);
438
+ }
439
+ 50% {
440
+ opacity: 0.8;
441
+ box-shadow: 0 0 0 8px oklch(var(--accent) / 0);
442
+ }
443
+ }
444
+ @keyframes shimmer {
445
+ 0% {
446
+ background-position: 200% 0;
447
+ }
448
+ 100% {
449
+ background-position: -200% 0;
450
+ }
451
+ }
452
+ }
453
+
454
+ /* Elevation/presence shadow utilities are typed at runtime (Studio output,
455
+ generated screens), so the scanner never sees them in source. v4's
456
+ `@source inline(...)` force-emits them — this replaces the JS `safelist`
457
+ that v4 removed. */
458
+ @source inline("shadow-{elevation-0,elevation-1,elevation-2,elevation-3,elevation-4,elevation-5,raised,hot,pressed,bevel-hi,bevel-lo,contact,lift,lift-deep,heat-inner,heat-outer}");
459
+
460
+ /* The Studio inspector's WHOLE token vocabulary — every class the
461
+ TokenField controls can mint at runtime. None of these literals appear
462
+ in scanned source (they're assembled from registry scales while
463
+ editing), so without this block they'd produce no CSS in Fast Frame —
464
+ the "blend modes don't do anything" bug, one family at a time. Finite
465
+ sets, so safelisting is cheap and correct. */
466
+ @source inline("mix-blend-{normal,multiply,screen,overlay,darken,lighten,color-dodge,color-burn,difference,exclusion,hue,saturation,color,luminosity}");
467
+ @source inline("opacity-{0,5,10,20,30,40,50,60,70,80,90,100}");
468
+ @source inline("text-{2xs,xs,sm,base,lg,xl,2xl,3xl,4xl,5xl}");
469
+ @source inline("font-{thin,extralight,light,normal,medium,semibold,bold,extrabold,black}");
470
+ @source inline("rounded{,-none,-sm,-md,-lg,-xl,-2xl,-3xl,-full}");
471
+ @source inline("rounded-{tl,tr,br,bl,t,r,b,l}{,-none,-sm,-md,-lg,-xl,-2xl,-3xl,-full}");
472
+ @source inline("{p,m}{,t,r,b,l,x,y}-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,14,16,20,24}");
473
+ @source inline("gap-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,8,10,12}");
474
+ @source inline("grid-cols-{1,2,3,4,5,6,8,12}");
475
+ @source inline("shadow{,-none,-sm,-md,-lg,-xl,-2xl,-inner}");
476
+ @source inline("bg-{background,card,muted,secondary,accent,primary,destructive,transparent}");
477
+ /* Border family — the inspector's BorderGroup mints every one of these at
478
+ runtime (side-aware widths, styles, colours, and ring-based inside/outside
479
+ positions). Without the force-emit, anything the scanner hasn't seen in
480
+ source (border-4, border-t-8, ring-inset…) silently renders as no CSS in
481
+ Fast Frame — the ">2px borders don't work" bug. */
482
+ @source inline("border{,-t,-r,-b,-l,-x,-y}{,-0,-2,-4,-8}");
483
+ @source inline("border-{solid,dashed,dotted,double}");
484
+ @source inline("border{,-t,-r,-b,-l}-{border,foreground,primary,muted-foreground,destructive,ring}");
485
+ @source inline("ring{,-0,-1,-2,-4,-8}");
486
+ @source inline("ring-inset");
487
+ @source inline("ring-{border,foreground,primary,muted-foreground,destructive,ring}");
488
+ /* Typography family — the inspector's TypographyGroup mints these at
489
+ runtime (line-height, letter-spacing, text-align). Same force-emit
490
+ rationale as the border/blend families above. */
491
+ @source inline("leading-{none,tight,snug,normal,relaxed,loose}");
492
+ @source inline("tracking-{tighter,tight,normal,wide,wider,widest}");
493
+ @source inline("text-{left,center,right,justify}");
494
+ /* Display sizes + responsive variants — generated heroes lean on
495
+ `text-6xl`/`md:text-8xl`-style ladders that never appear in scanned
496
+ source, so the responsive variants silently no-oped in Fast Frame.
497
+ sm/md/lg cover BOTH model output and everything the inspector's
498
+ BreakpointOverridesEditor can mint (size, weight, line-height,
499
+ tracking, align — keep in sync with EDITABLE_BREAKPOINTS). */
500
+ @source inline("{,sm:,md:,lg:}text-{6xl,7xl,8xl,9xl}");
501
+ @source inline("{sm:,md:,lg:}text-{2xs,xs,sm,base,lg,xl,2xl,3xl,4xl,5xl}");
502
+ @source inline("{sm:,md:,lg:}font-{thin,extralight,light,normal,medium,semibold,bold,extrabold,black}");
503
+ @source inline("{sm:,md:,lg:}leading-{none,tight,snug,normal,relaxed,loose}");
504
+ @source inline("{sm:,md:,lg:}tracking-{tighter,tight,normal,wide,wider,widest}");
505
+ @source inline("{sm:,md:,lg:}text-{left,center,right,justify}");
506
+ /* Gradient-text + gradient-stop recipe — `bg-clip-text text-transparent
507
+ bg-gradient-to-b from-foreground to-foreground/75` is the canonical
508
+ generated-hero headline. All of it is runtime-typed (model output),
509
+ none of it appears in scanned source. Stops cover the semantic tokens
510
+ at full strength plus a sensible opacity ladder. */
511
+ @source inline("bg-clip-text");
512
+ @source inline("text-transparent");
513
+ @source inline("bg-gradient-to-{t,tr,r,br,b,bl,l,tl}");
514
+ @source inline("{from,via,to}-{background,foreground,card,card-foreground,muted,muted-foreground,primary,primary-foreground,secondary,accent,destructive,border,transparent,white,black}");
515
+ @source inline("{from,via,to}-{background,foreground,muted,primary,secondary,accent,white,black}/{10,20,25,30,40,50,60,70,75,80,90}");
516
+ /* Animate-in vocabulary (tailwindcss-animate enter/exit modifiers) +
517
+ easing — generated screens compose `animate-in fade-in
518
+ slide-in-from-top`-style entrances at runtime, so the scanner never
519
+ sees the pieces in source. Until the v4 native-@theme migration these
520
+ leaked into the build by accident (the scanner picked them out of
521
+ tailwind-preset.ts comment/value text); now they're force-emitted
522
+ deliberately. */
523
+ @source inline("fade-{in,out}");
524
+ @source inline("slide-in-from-{top,bottom,left,right}");
525
+ @source inline("ease-{in,out,in-out}");
526
+ /* Role ramp family vocabulary (THEME-MIGRATION.md B4) — bg/text/border
527
+ at every step for every themed role. Runtime-typed (model output +
528
+ future inspector token picker), so force-emit like the other
529
+ families. The --color-<role>-<step> @theme entries below define the
530
+ values; the generator's --gds-<role>-<step> vars make them live. */
531
+ @source inline("bg-{primary,accent,success,warning,info,highlight,destructive}-{50,100,200,300,400,500,600,700,800,900,950}");
532
+ @source inline("text-{primary,accent,success,warning,info,highlight,destructive}-{50,100,200,300,400,500,600,700,800,900,950}");
533
+ @source inline("border-{primary,accent,success,warning,info,highlight,destructive}-{50,100,200,300,400,500,600,700,800,900,950}");
534
+ /* Parity holdovers from the same accidental scan — shipped in every
535
+ previous dist/styles.css, kept so no runtime-typed class loses its
536
+ rules. TODO(follow-up): widen bg-surface-glass to the full
537
+ bg-surface-{solid,translucent,glass,glass-strong} family as a
538
+ deliberate (additive) API decision, and decide whether the two
539
+ playground measurements below should live in a scaffold instead. */
540
+ @source inline("bg-surface-glass");
541
+ @source inline("h-[600px]");
542
+ @source inline("md:grid-cols-[minmax(0,440px)_1fr]");
543
+
544
+ /* ============================================
545
+ GRADE DESIGN SYSTEM - COMPONENT-LAYER TOKENS
546
+ ============================================ */
547
+
548
+ :root {
549
+ /* Brand color ramps, neutral grays, semantic aliases (--gds-success …),
550
+ spacing scale, and border radii moved to @gradeui/core — see the
551
+ `@import "@gradeui/core/tokens.css"` at the top of this file and
552
+ packages/core/styles/tokens.css. Component tokens stay below. */
553
+ /* ============================================
554
+ ELEVATION SYSTEM
555
+ ============================================
556
+ A single vocabulary for every box-shadow in the DS. Three layers:
557
+
558
+ 1. ATOMIC SUB-TOKENS — `-y` (y-offset), `-blur`, `-spread`, `-alpha`.
559
+ These are the bare numbers a theme tweaks to tune "depth" globally
560
+ without touching component CSS. e.g. lowering `--shadow-lift-alpha`
561
+ across the board gives the whole system a "lighter" feel.
562
+
563
+ 2. COMPOSED LAYER TOKENS — full box-shadow expressions for one layer
564
+ each (`--shadow-bevel-hi`, `--shadow-contact`, `--shadow-lift`,
565
+ etc.). Components compose layers into a complete shadow stack
566
+ without having to know the atomic numbers.
567
+
568
+ 3. ELEVATION PRESETS — `--elevation-0` through `--elevation-5` plus
569
+ state variants (`--elevation-hot`, `--elevation-pressed`). The
570
+ default contract: components read these, not the layers, unless
571
+ they're doing something custom (Button raised, custom cards).
572
+
573
+ Light source convention: light comes from above. Bevel-hi sits at
574
+ the top edge, bevel-lo at the bottom, drops fall downward (positive
575
+ y). Flipping this in a future "inverse light" theme = changing the
576
+ atomic y-offsets to negative; no component CSS changes.
577
+
578
+ Color convention:
579
+ bevel-hi — white (the highlight)
580
+ bevel-lo — black (the undercut)
581
+ contact — black (tight close drop)
582
+ lift — black (diffuse ambient drop)
583
+ heat-inner — var(--btn-glow) (tonal hover bloom, inset from bottom)
584
+ heat-outer — var(--btn-glow) (tonal hover bloom, outer drop)
585
+ -------------------------------------------- */
586
+
587
+ /* -- Layer 1: bevel-hi (top inset highlight) -- */
588
+ --shadow-bevel-hi-y: 1px;
589
+ --shadow-bevel-hi-alpha: 0.12;
590
+ --shadow-bevel-hi: inset 0 var(--shadow-bevel-hi-y) 0 oklch(1 0 0 / var(--shadow-bevel-hi-alpha));
591
+
592
+ /* -- Layer 2: bevel-lo (bottom inset shadow) -- */
593
+ --shadow-bevel-lo-y: 1px;
594
+ --shadow-bevel-lo-alpha: 0.30;
595
+ --shadow-bevel-lo: inset 0 calc(-1 * var(--shadow-bevel-lo-y)) 0 oklch(0 0 0 / var(--shadow-bevel-lo-alpha));
596
+
597
+ /* -- Layer 3: contact (tight outer drop, the "sitting on a surface" feel) -- */
598
+ --shadow-contact-y: 1px;
599
+ --shadow-contact-blur: 2px;
600
+ --shadow-contact-alpha: 0.10;
601
+ --shadow-contact: 0 var(--shadow-contact-y) var(--shadow-contact-blur) oklch(0 0 0 / var(--shadow-contact-alpha));
602
+
603
+ /* -- Layer 4: lift (diffuse outer drop, the "floating" feel) -- */
604
+ --shadow-lift-y: 4px;
605
+ --shadow-lift-blur: 12px;
606
+ --shadow-lift-alpha: 0.18;
607
+ --shadow-lift: 0 var(--shadow-lift-y) var(--shadow-lift-blur) oklch(0 0 0 / var(--shadow-lift-alpha));
608
+
609
+ /* -- Layer 5: lift-deep (deeper diffuse drop for dialogs/sheets) -- */
610
+ --shadow-lift-deep-y: 16px;
611
+ --shadow-lift-deep-blur: 40px;
612
+ --shadow-lift-deep-alpha: 0.28;
613
+ --shadow-lift-deep: 0 var(--shadow-lift-deep-y) var(--shadow-lift-deep-blur) oklch(0 0 0 / var(--shadow-lift-deep-alpha));
614
+
615
+ /* -- Layer 6: heat-inner (tonal hover bloom, bleeds in from bottom) -- */
616
+ --shadow-heat-inner-y: 10px;
617
+ --shadow-heat-inner-blur: 24px;
618
+ --shadow-heat-inner-spread: 4px;
619
+ --shadow-heat-inner-alpha: 0.55;
620
+ --shadow-heat-inner: inset 0 calc(-1 * var(--shadow-heat-inner-y)) var(--shadow-heat-inner-blur) calc(-1 * var(--shadow-heat-inner-spread)) oklch(var(--btn-glow, var(--selected-glow)) / var(--shadow-heat-inner-alpha));
621
+
622
+ /* -- Layer 7: heat-outer (tonal hover bloom, escapes outward) -- */
623
+ --shadow-heat-outer-y: 6px;
624
+ --shadow-heat-outer-blur: 18px;
625
+ --shadow-heat-outer-alpha: 0.30;
626
+ --shadow-heat-outer: 0 var(--shadow-heat-outer-y) var(--shadow-heat-outer-blur) oklch(var(--btn-glow, var(--selected-glow)) / var(--shadow-heat-outer-alpha));
627
+
628
+ /* -- Layer 8: pressed (inset shadow simulating a key push) -- */
629
+ --shadow-pressed-y: 2px;
630
+ --shadow-pressed-blur: 4px;
631
+ --shadow-pressed-alpha: 0.45;
632
+ --shadow-pressed-bevel: inset 0 var(--shadow-pressed-y) var(--shadow-pressed-blur) oklch(0 0 0 / var(--shadow-pressed-alpha));
633
+
634
+ /* -- Composed elevation presets --
635
+ 0 = flat, 1 = minimal contact, 2 = interactive surface (bevel + contact),
636
+ 3 = raised key (bevel + contact + lift), 4 = popover/dropdown,
637
+ 5 = dialog/sheet/modal. Components reach for these by default. */
638
+ --elevation-0: 0 0 0 0 transparent;
639
+ --elevation-1: var(--shadow-contact);
640
+ --elevation-2: var(--shadow-bevel-hi), var(--shadow-bevel-lo), var(--shadow-contact);
641
+ --elevation-3: var(--shadow-bevel-hi), var(--shadow-bevel-lo), var(--shadow-contact), var(--shadow-lift);
642
+ --elevation-4: var(--shadow-contact), var(--shadow-lift);
643
+ --elevation-5: var(--shadow-contact), var(--shadow-lift-deep);
644
+
645
+ /* State variants for interactive surfaces (Button raised, Toggle, Tab) */
646
+ --elevation-hot: var(--shadow-bevel-hi), var(--shadow-bevel-lo), var(--shadow-heat-inner), var(--shadow-contact), var(--shadow-heat-outer), var(--shadow-lift);
647
+ --elevation-pressed: var(--shadow-pressed-bevel), inset 0 -1px 0 oklch(1 0 0 / 0.04), var(--shadow-contact);
648
+
649
+ /* ----------------------------------------
650
+ Legacy shadow tokens — repointed onto the elevation system so call
651
+ sites that read `--gds-shadow-*` directly inherit the new look
652
+ without a code change. Kept here as a compatibility shim; new code
653
+ should prefer the elevation tokens above.
654
+ ---------------------------------------- */
655
+ --gds-shadow-sm: var(--elevation-1);
656
+ --gds-shadow-md: var(--elevation-4);
657
+ --gds-shadow-lg: var(--elevation-5);
658
+ --gds-shadow-xl: var(--elevation-5);
659
+ --gds-shadow-2xl: var(--elevation-5);
660
+ --gds-shadow-inner: var(--shadow-pressed-bevel);
661
+
662
+ /* ============================================
663
+ SURFACE SYSTEM
664
+ ============================================
665
+ What the surface is *made of*. Independent from elevation (how
666
+ high it sits) and from aura (what it's radiating). A glass card
667
+ can sit at elevation-4 with a calm aura, or at elevation-2 with
668
+ no aura — these axes don't entangle.
669
+
670
+ Atomic alpha + blur sub-tokens make the look tunable per theme
671
+ (frosted dark vs. light glass want different opacity floors).
672
+ The composed `--surface-*` tokens read from `--card` so they
673
+ stay theme-aware — switching themes changes the glass tint.
674
+ -------------------------------------------- */
675
+
676
+ /* -- Atomic alphas -- */
677
+ --surface-alpha-solid: 1;
678
+ --surface-alpha-translucent: 0.82;
679
+ --surface-alpha-glass: 0.58;
680
+ --surface-alpha-glass-strong: 0.42;
681
+
682
+ /* -- Atomic blurs -- */
683
+ --surface-blur-subtle: 6px;
684
+ --surface-blur-glass: 14px;
685
+ --surface-blur-strong: 24px;
686
+
687
+ /* -- Atomic inner-edge highlight (the "wet" line on glass surfaces) -- */
688
+ --surface-edge-alpha: 0.10;
689
+ --surface-edge: inset 0 1px 0 oklch(1 0 0 / var(--surface-edge-alpha));
690
+
691
+ /* -- Composed surface backgrounds — theme-aware via --card -- */
692
+ --surface-solid: oklch(var(--card) / var(--surface-alpha-solid));
693
+ --surface-translucent: oklch(var(--card) / var(--surface-alpha-translucent));
694
+ --surface-glass: oklch(var(--card) / var(--surface-alpha-glass));
695
+ --surface-glass-strong: oklch(var(--card) / var(--surface-alpha-glass-strong));
696
+
697
+ /* ============================================
698
+ AURA SYSTEM
699
+ ============================================
700
+ What's actively *radiating* from an element. State signals — AI
701
+ attention, generation-in-progress, selection focus. Three
702
+ composable styles: ring (pulsing outer shadow), gradient
703
+ (rotating conic border), shimmer (diagonal sweep). They stack —
704
+ `.gds-aura-ring.gds-aura-shimmer` runs both passes.
705
+
706
+ Per-style timing & easing vars are overridable at the consumer
707
+ level, so a heavyweight button can slow its pulse without
708
+ rewriting keyframes:
709
+
710
+ <Button className="gds-aura-ring"
711
+ style={{ '--aura-pulse-duration': '3.2s' }} />
712
+
713
+ Color defaults to --selected-glow (blue, themed). Override
714
+ per-instance via `--aura-color` for danger / success / brand
715
+ attention.
716
+ -------------------------------------------- */
717
+
718
+ /* -- Color -- */
719
+ --aura-color: var(--selected-glow);
720
+
721
+ /* -- Ring (pulsing outer halo via animated box-shadow) -- */
722
+ --aura-ring-spread: 2px;
723
+ --aura-ring-blur: 14px;
724
+ --aura-ring-alpha-min: 0.00;
725
+ --aura-ring-alpha-max: 0.65;
726
+ --aura-pulse-duration: 2400ms;
727
+ --aura-pulse-ease: cubic-bezier(0.4, 0, 0.2, 1);
728
+
729
+ /* -- Gradient (rotating conic-gradient border) -- */
730
+ --aura-gradient-thickness: 1.5px;
731
+ --aura-gradient-duration: 6000ms;
732
+ --aura-gradient-ease: linear;
733
+
734
+ /* -- Shimmer (diagonal sweep) -- */
735
+ --aura-shimmer-width: 28%;
736
+ --aura-shimmer-alpha: 0.18;
737
+ --aura-shimmer-duration: 2000ms;
738
+ --aura-shimmer-ease: cubic-bezier(0.4, 0, 0.2, 1);
739
+ --aura-shimmer-delay-between: 1200ms;
740
+
741
+ /* Font stacks (--font-sans / --font-mono) and the type scale
742
+ (--text-display … --text-overline-*) moved to @gradeui/core/tokens.css. */
743
+ /* ----------------------------------------
744
+ Animation
745
+ ---------------------------------------- */
746
+ --gds-transition-fast: 150ms;
747
+ --gds-transition-base: 200ms;
748
+ --gds-transition-slow: 300ms;
749
+ --gds-transition-slower: 500ms;
750
+
751
+ --gds-ease-in: cubic-bezier(0.4, 0, 1, 1);
752
+ --gds-ease-out: cubic-bezier(0, 0, 0.2, 1);
753
+ --gds-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
754
+
755
+ /* ----------------------------------------
756
+ Semantic Tokens (OKLCH) — STUDIO DEFAULTS.
757
+ Values are bare "L C H" triplets, wrapped at call-time with
758
+ oklch(var(--x)) via the @theme inline reference block above.
759
+ These defaults mirror the current Studio look (warm-cream
760
+ neutrals at hue 85, near-black primary, Inter/Geist type) and
761
+ are SYNCED FROM apps/docs/app/globals.css — the canonical copy —
762
+ by the A7.1 pass (2026-06-11; the previous values here were the
763
+ pre-redesign terracotta theme, which leaked into any surface that
764
+ loaded both stylesheets — the MCP panel "pink progress bar" bug).
765
+ If the docs palette changes, re-sync this block. The
766
+ GradeThemeProvider overrides all of it at runtime.
767
+ ---------------------------------------- */
768
+ --background: 0.985 0.0018 85; /* neutral 50 */
769
+ --foreground: 0.170 0.0048 85; /* neutral 950 */
770
+ --card: 1 0 0; /* pure white */
771
+ --card-foreground: 0.170 0.0048 85;
772
+ --popover: 1 0 0;
773
+ --popover-foreground: 0.170 0.0048 85;
774
+ --primary: 0.245 0.0096 85; /* near-black — Studio buttons */
775
+ --primary-foreground: 0.985 0.0018 85;
776
+ --secondary: 0.955 0.0048 85; /* neutral 100 */
777
+ --secondary-foreground: 0.415 0.0180 85; /* neutral 700 */
778
+ --muted: 0.955 0.0048 85;
779
+ --muted-foreground: 0.610 0.0204 85; /* neutral 500 */
780
+ --accent: 0.610 0.0306 85; /* warm cream accent */
781
+ --accent-foreground: 0.985 0.0027 85;
782
+ --destructive: 0.560 0.220 27;
783
+ --destructive-foreground: 0.990 0.005 27;
784
+ --border: 0.895 0.0090 85; /* neutral 200 */
785
+ --input: 0.895 0.0090 85;
786
+ --ring: 0.415 0.0180 85;
787
+ --radius: 0.250rem; /* Studio's compact radius base */
788
+
789
+ /* Typography defaults — Inter body, Geist display (Studio). */
790
+ --font-sans: var(--font-inter), system-ui, -apple-system, sans-serif;
791
+ --font-display: var(--font-geist), system-ui, -apple-system, sans-serif;
792
+
793
+ /* Fixed semantic colors (not hue-derived, for accessibility consistency) */
794
+ --success: 0.610 0.180 145;
795
+ --warning: 0.720 0.180 60;
796
+ --info: 0.580 0.200 240;
797
+ --highlight: 0.860 0.180 95;
798
+
799
+ /* Selection token pair — solid fill behind a selected option (radio
800
+ row, segmented control) and the ambient halo around it on hover /
801
+ focus. Blue by default; theme-overridable. Distinct from --primary
802
+ so a brand can keep its primary cta colour while selection stays
803
+ consistent, and distinct from --highlight (amber status). */
804
+ --selected: 0.55 0.22 258;
805
+ --selected-foreground: 0.985 0.0018 85;
806
+ --selected-glow: 0.66 0.20 258;
807
+
808
+ /* Accent glow — tonal halo for raised/tactile chrome. Defaults to
809
+ --primary so raised buttons read as branded by default, not as
810
+ selected. Themes can retune independently (e.g. lift lightness for
811
+ softer halos) by setting --accent-glow explicitly. Per-button
812
+ overrides still flow through `style={{ "--btn-glow": ... }}` for
813
+ "traffic light" semantics (warning iterate, success ship, etc.). */
814
+ --accent-glow: var(--primary);
815
+
816
+ /* Media placeholder pair — used by MediaSurface when it has no children
817
+ (image-not-yet-loaded slots, scaffolds without real photos, layouts
818
+ waiting on the image-generation pipeline). Defined here as a token
819
+ pair so themes can override per-brand and consumers can roll their
820
+ own placeholder UI with `background: var(--gds-media-placeholder-bg)`.
821
+ Defaults to the secondary surface so it reads visibly distinct from
822
+ `bg-card` and `bg-background` while staying neutral (not brand-loud).
823
+ Resolves dynamically against the active mode because var(--secondary)
824
+ itself is mode-scoped — no `.dark` duplicate needed.
825
+
826
+ Both bg and fg are SOLID (no alpha) — earlier `secondary-foreground / 0.4`
827
+ produced a "half-cooked" look where the icon and caption faded into the
828
+ surface. Solid muted-foreground reads as "intentional placeholder
829
+ colour", not "loading half-way". */
830
+ --gds-media-placeholder-bg: oklch(var(--muted));
831
+ --gds-media-placeholder-fg: oklch(var(--muted-foreground));
832
+
833
+ /* Canvas fill — the standard backdrop behind a screen when it doesn't fill
834
+ its frame: the letterbox bars in an embed/share, and the stage a
835
+ `<ScreenAnimator>` reveals when it flies in or pulls below 1× zoom. One
836
+ token so every "canvas" surface (embed, share, animator) matches and a
837
+ theme/brand can restyle it in one place. Mode-aware: a soft neutral in
838
+ light mode and a deep near-black in dark (see `.dark` below) — a neutral
839
+ cinematic backdrop that flatters a screen sitting on top of it in either
840
+ mode. Set it to `transparent` to let the host page show through. */
841
+ --gds-canvas-fill: #e8e8ec;
842
+
843
+ /* Sidebar knobs — the width pair drives the collapsed/expanded animation
844
+ on `<Sidebar>` (compound nav primitive). Header height + section
845
+ padding + per-section gap are exposed so consumers can retune density
846
+ without prop drilling. */
847
+ --gds-sidebar-width: 16rem;
848
+ --gds-sidebar-collapsed-width: 4rem;
849
+ --gds-sidebar-header-height: 3.25rem;
850
+ --gds-sidebar-content-py: 0.5rem;
851
+ --gds-sidebar-section-px: 0.5rem;
852
+ --gds-sidebar-section-gap: 0.125rem;
853
+
854
+ /* Carousel knobs — every visual dimension of <Carousel> is driven from
855
+ this stanza so consumers can re-skin the slideshow (dot shape, arrow
856
+ chrome, transition feel) without prop-drilling. The component reads
857
+ these as `var(--gds-carousel-* , <fallback>)` so missing values are
858
+ safe. */
859
+ --gds-carousel-gap: 0;
860
+ --gds-carousel-radius: var(--gds-media-radius, 0.5rem);
861
+ --gds-carousel-fade-ms: 200ms;
862
+ --gds-carousel-slide-basis: 100%;
863
+ /* Dots */
864
+ --gds-carousel-dot-size: 0.5rem;
865
+ --gds-carousel-dot-active-width: 1.25rem;
866
+ --gds-carousel-dot-color: oklch(var(--muted-foreground) / 0.4);
867
+ --gds-carousel-dot-active-color: oklch(var(--primary));
868
+ --gds-carousel-dots-gap: 0.75rem; /* between viewport and below-dots */
869
+ --gds-carousel-dots-spacing: 0.5rem; /* between adjacent dots */
870
+ --gds-carousel-dots-inset: 0.75rem; /* overlay position from bottom */
871
+ /* Arrows */
872
+ --gds-carousel-arrow-size: 2.25rem;
873
+ --gds-carousel-arrow-inset: 0.75rem;
874
+ --gds-carousel-arrow-bg: oklch(var(--background) / 0.85);
875
+ --gds-carousel-arrow-hover-bg: oklch(var(--background));
876
+ --gds-carousel-arrow-fg: oklch(var(--foreground));
877
+ --gds-carousel-arrow-backdrop: blur(6px);
878
+ --gds-carousel-arrow-shadow: 0 2px 8px oklch(0 0 0 / 0.12);
879
+
880
+ /* Alert surface pairs — paler tinted surface + readable deep text for each
881
+ status colour. Pre-hydration fallbacks; the JS theme pipeline overwrites
882
+ these via lib/themes/oklch.ts#deriveAlertPair. Kept here so the Alert
883
+ component has the right tokens before the theme provider mounts. */
884
+ --destructive-soft: 0.965 0.045 27;
885
+ --destructive-deep: 0.380 0.220 27;
886
+ --success-soft: 0.965 0.040 145;
887
+ --success-deep: 0.380 0.180 145;
888
+ --warning-soft: 0.965 0.040 60;
889
+ --warning-deep: 0.380 0.180 60;
890
+ --info-soft: 0.965 0.044 240;
891
+ --info-deep: 0.380 0.200 240;
892
+ --highlight-soft: 0.965 0.040 95;
893
+ --highlight-deep: 0.380 0.180 95;
894
+
895
+ /* Code surface tokens — used by `<Code>` (marketing/docs code blocks).
896
+ Token roles map to prism's tag types; the component reads them as
897
+ `color: var(--gds-code-<role>)` so the palette inverts with the
898
+ theme without us swapping prism themes at runtime. Light values
899
+ are tuned against a near-paper surface; the .dark block below
900
+ mirrors them for dark mode.
901
+
902
+ diff-added / diff-removed pairs are intentionally desaturated
903
+ surfaces with strong text — same pattern as the destructive-soft /
904
+ -deep alert pairs. Line-highlight is a quieter accent (no green/
905
+ red implication, just "look here"). */
906
+ --gds-code-bg: oklch(0.985 0.0018 85);
907
+ --gds-code-fg: oklch(0.220 0.012 250);
908
+ --gds-code-comment: oklch(0.620 0.030 250);
909
+ --gds-code-punctuation: oklch(0.420 0.020 250);
910
+ --gds-code-property: oklch(0.550 0.180 25);
911
+ --gds-code-number: oklch(0.560 0.180 35);
912
+ --gds-code-string: oklch(0.500 0.140 145);
913
+ --gds-code-operator: oklch(0.480 0.140 280);
914
+ --gds-code-keyword: oklch(0.490 0.220 285);
915
+ --gds-code-function: oklch(0.500 0.200 250);
916
+ --gds-code-variable: oklch(0.530 0.180 25);
917
+ --gds-code-tag: oklch(0.520 0.200 15);
918
+ --gds-code-attr-name: oklch(0.510 0.150 60);
919
+ --gds-code-attr-value: oklch(0.500 0.140 145);
920
+ --gds-code-line-highlight-bg: oklch(var(--selected-glow) / 0.10);
921
+ --gds-code-line-highlight-marker: oklch(var(--selected-glow) / 0.65);
922
+ --gds-code-diff-added-bg: oklch(var(--success) / 0.12);
923
+ --gds-code-diff-added-fg: oklch(0.300 0.140 145);
924
+ --gds-code-diff-removed-bg: oklch(var(--destructive) / 0.10);
925
+ --gds-code-diff-removed-fg: oklch(0.360 0.180 27);
926
+ }
927
+
928
+ .dark {
929
+ /* ----------------------------------------
930
+ Semantic Tokens — Studio dark defaults (synced from
931
+ apps/docs/app/globals.css, A7.1 — see the :root note).
932
+ ---------------------------------------- */
933
+ --background: 0.170 0.0048 85;
934
+ --foreground: 0.985 0.0018 85;
935
+ --card: 0.245 0.0096 85; /* neutral 900 */
936
+ --card-foreground: 0.985 0.0018 85;
937
+ --popover: 0.245 0.0096 85;
938
+ --popover-foreground: 0.985 0.0018 85;
939
+ --primary: 0.955 0.0048 85; /* primary 400 */
940
+ --primary-foreground: 0.170 0.0048 85;
941
+ --secondary: 0.325 0.0144 85; /* neutral 800 */
942
+ --secondary-foreground: 0.895 0.0090 85;
943
+ --muted: 0.325 0.0144 85;
944
+ --muted-foreground: 0.720 0.0168 85;
945
+ --accent: 0.720 0.0252 85;
946
+ --accent-foreground: 0.170 0.0072 85;
947
+ --destructive: 0.680 0.220 27;
948
+ --destructive-foreground: 0.990 0.005 27;
949
+ --border: 0.325 0.0144 85;
950
+ --input: 0.325 0.0144 85;
951
+ --ring: 0.820 0.0132 85;
952
+
953
+ --success: 0.720 0.180 145;
954
+ --warning: 0.800 0.180 60;
955
+ --info: 0.700 0.200 240;
956
+ --highlight: 0.880 0.180 95;
957
+
958
+ /* Selection pair — dark mode. Solid fill stays vivid; halo lifts in
959
+ lightness so it can glow against the dark canvas. */
960
+ --selected: 0.62 0.22 258;
961
+ --selected-foreground: 0.985 0.0018 85;
962
+ --selected-glow: 0.74 0.20 258;
963
+
964
+ /* Accent glow — dark mode. Same theme-driven default (primary), but
965
+ the dark variants of --primary already carry the appropriate lift
966
+ for a glow against the dark canvas. */
967
+ --accent-glow: var(--primary);
968
+
969
+ /* Canvas fill — dark mode. Deep near-black cinematic backdrop (the light
970
+ default in :root is a soft neutral). One token, mode-scoped: letterbox
971
+ bars in embed/share/preview and the ScreenAnimator stage all match. */
972
+ --gds-canvas-fill: #0b0b0e;
973
+
974
+ /* Alert surface pairs — dark-mode variant: tint sits just above the dark
975
+ background without going gray; deep text stays bright and readable. */
976
+ --destructive-soft: 0.220 0.075 27;
977
+ --destructive-deep: 0.820 0.198 27;
978
+ --success-soft: 0.220 0.075 145;
979
+ --success-deep: 0.820 0.162 145;
980
+ --warning-soft: 0.220 0.075 60;
981
+ --warning-deep: 0.820 0.162 60;
982
+ --info-soft: 0.220 0.075 240;
983
+ --info-deep: 0.820 0.180 240;
984
+ --highlight-soft: 0.220 0.075 95;
985
+ --highlight-deep: 0.820 0.162 95;
986
+
987
+ /* -- Elevation: bump alphas in dark mode. Shadows fade against
988
+ dark surfaces; without this the lift looks anemic. -- */
989
+ --shadow-bevel-hi-alpha: 0.14;
990
+ --shadow-bevel-lo-alpha: 0.40;
991
+ --shadow-contact-alpha: 0.45;
992
+ --shadow-lift-alpha: 0.50;
993
+ --shadow-lift-deep-alpha: 0.60;
994
+
995
+ /* -- Surface: glass needs higher opacity in dark mode or the card
996
+ loses its boundary against the canvas. -- */
997
+ --surface-alpha-translucent: 0.74;
998
+ --surface-alpha-glass: 0.48;
999
+ --surface-alpha-glass-strong: 0.32;
1000
+ --surface-edge-alpha: 0.06;
1001
+
1002
+ /* Code surface tokens — dark mode mirror. Background sits a touch
1003
+ above the page bg so the code block reads as a distinct surface
1004
+ even on a dark hero. Tokens lift in lightness to stay readable. */
1005
+ --gds-code-bg: oklch(0.205 0.010 250);
1006
+ --gds-code-fg: oklch(0.920 0.012 250);
1007
+ --gds-code-comment: oklch(0.580 0.024 250);
1008
+ --gds-code-punctuation: oklch(0.760 0.018 250);
1009
+ --gds-code-property: oklch(0.780 0.150 25);
1010
+ --gds-code-number: oklch(0.790 0.150 60);
1011
+ --gds-code-string: oklch(0.780 0.140 145);
1012
+ --gds-code-operator: oklch(0.770 0.130 280);
1013
+ --gds-code-keyword: oklch(0.760 0.180 285);
1014
+ --gds-code-function: oklch(0.770 0.150 250);
1015
+ --gds-code-variable: oklch(0.790 0.160 25);
1016
+ --gds-code-tag: oklch(0.780 0.180 15);
1017
+ --gds-code-attr-name: oklch(0.800 0.140 60);
1018
+ --gds-code-attr-value: oklch(0.780 0.140 145);
1019
+ --gds-code-line-highlight-bg: oklch(var(--selected-glow) / 0.16);
1020
+ --gds-code-line-highlight-marker: oklch(var(--selected-glow) / 0.85);
1021
+ --gds-code-diff-added-bg: oklch(var(--success) / 0.18);
1022
+ --gds-code-diff-added-fg: oklch(0.870 0.160 145);
1023
+ --gds-code-diff-removed-bg: oklch(var(--destructive) / 0.18);
1024
+ --gds-code-diff-removed-fg: oklch(0.870 0.180 27);
1025
+ }
1026
+
1027
+ /* ============================================
1028
+ ENERGY THEME — scoped overrides.
1029
+ Engaged when data-grade-theme="energy" is set on :root (either by the
1030
+ pre-hydration inline script or GradeThemeProvider). Mirrors the Energy
1031
+ built-in (hues all at 175, teal primary, Geist sans).
1032
+ ============================================ */
1033
+ :root[data-grade-theme="energy"] {
1034
+ --background: 0.985 0.0012 175; /* neutral 50 */
1035
+ --foreground: 0.170 0.0032 175; /* neutral 950 */
1036
+ --card: 1 0 0;
1037
+ --card-foreground: 0.170 0.0032 175;
1038
+ --popover: 1 0 0;
1039
+ --popover-foreground: 0.170 0.0032 175;
1040
+ --primary: 0.610 0.170 175; /* primary 500 — teal */
1041
+ --primary-foreground: 0.985 0.015 175;
1042
+ --secondary: 0.955 0.0032 175; /* neutral 100 */
1043
+ --secondary-foreground: 0.415 0.012 175; /* neutral 700 */
1044
+ --muted: 0.955 0.0032 175;
1045
+ --muted-foreground: 0.610 0.0136 175;
1046
+ --accent: 0.610 0.170 175;
1047
+ --accent-foreground: 0.985 0.015 175;
1048
+ --border: 0.895 0.006 175;
1049
+ --input: 0.895 0.006 175;
1050
+ --ring: 0.610 0.170 175;
1051
+ --radius: 0.5rem;
1052
+
1053
+ --font-sans: var(--font-geist), system-ui, sans-serif;
1054
+ --font-display: var(--font-geist), system-ui, sans-serif;
1055
+ }
1056
+
1057
+ .dark[data-grade-theme="energy"] {
1058
+ --background: 0.170 0.0032 175;
1059
+ --foreground: 0.985 0.0012 175;
1060
+ --card: 0.245 0.0064 175;
1061
+ --card-foreground: 0.985 0.0012 175;
1062
+ --popover: 0.245 0.0064 175;
1063
+ --popover-foreground: 0.985 0.0012 175;
1064
+ --primary: 0.720 0.140 175;
1065
+ --primary-foreground: 0.170 0.040 175;
1066
+ --secondary: 0.325 0.0096 175;
1067
+ --secondary-foreground: 0.895 0.006 175;
1068
+ --muted: 0.325 0.0096 175;
1069
+ --muted-foreground: 0.720 0.0112 175;
1070
+ --accent: 0.720 0.140 175;
1071
+ --accent-foreground: 0.170 0.040 175;
1072
+ --border: 0.325 0.0096 175;
1073
+ --input: 0.325 0.0096 175;
1074
+ --ring: 0.720 0.140 175;
1075
+ }
1076
+
1077
+ @layer base {
1078
+ * {
1079
+ @apply border-border;
1080
+ }
1081
+
1082
+ /* Smooth theme transitions */
1083
+ html {
1084
+ transition-property: background-color, border-color;
1085
+ transition-duration: 200ms;
1086
+ transition-timing-function: ease-in-out;
1087
+ }
1088
+
1089
+ body {
1090
+ @apply bg-background text-foreground;
1091
+ font-feature-settings: "rlig" 1, "calt" 1;
1092
+ font-size: var(--text-body);
1093
+ line-height: var(--text-body-line);
1094
+ }
1095
+
1096
+ /* Theme-driven heading font.
1097
+ Naked <h1>–<h4> elements pick up the active theme's display font
1098
+ (falls back to sans if the theme doesn't set one). Utility classes
1099
+ like .text-h1 still win if they set their own font, since classes
1100
+ outrank tag selectors. */
1101
+ h1, h2, h3, h4 {
1102
+ font-family: var(--font-display, var(--font-sans));
1103
+ font-weight: var(--font-heading-weight, 600);
1104
+ letter-spacing: var(--font-heading-tracking, -0.01em);
1105
+ }
1106
+
1107
+ /* Link styles */
1108
+ a:not([class]) {
1109
+ @apply text-primary underline-offset-4 hover:underline;
1110
+ }
1111
+ }
1112
+
1113
+ /* ============================================
1114
+ APP SHELL GRID
1115
+ Five slots — Header, Nav, Aside, Main, Footer — placed via CSS-grid
1116
+ template areas keyed off [data-nav] on the shell root. Slot order in
1117
+ JSX doesn't matter; each slot has a fixed grid-area class assignment.
1118
+ ============================================ */
1119
+
1120
+ .gds-app-shell-header { grid-area: header; }
1121
+ .gds-app-shell-nav { grid-area: nav; }
1122
+ .gds-app-shell-aside { grid-area: aside; }
1123
+ .gds-app-shell-main { grid-area: main; }
1124
+ .gds-app-shell-footer { grid-area: footer; }
1125
+
1126
+ /* nav="none" — Header / Main / Footer stacked vertically. */
1127
+ .gds-app-shell[data-nav="none"] {
1128
+ grid-template-areas: "header" "main" "footer";
1129
+ grid-template-rows: auto 1fr auto;
1130
+ }
1131
+
1132
+ /* nav="top" — Header (site chrome), then in-app top nav, then Main. */
1133
+ .gds-app-shell[data-nav="top"] {
1134
+ grid-template-areas: "header" "nav" "main" "footer";
1135
+ grid-template-rows: auto auto 1fr auto;
1136
+ }
1137
+
1138
+ /* nav="side" — Header full width, Nav rail + Main below, Footer full width. */
1139
+ .gds-app-shell[data-nav="side"] {
1140
+ grid-template-areas:
1141
+ "header header"
1142
+ "nav main"
1143
+ "footer footer";
1144
+ grid-template-rows: auto 1fr auto;
1145
+ grid-template-columns: auto 1fr;
1146
+ }
1147
+
1148
+ /* nav="three-pane" — Header full width, Nav rail + fixed Aside + flex Main,
1149
+ Footer full width. Aside width is set by --gds-app-shell-aside (default
1150
+ 320px) so callers can override per-screen via inline style or a parent
1151
+ class without forking the component. */
1152
+ .gds-app-shell[data-nav="three-pane"] {
1153
+ grid-template-areas:
1154
+ "header header header"
1155
+ "nav aside main"
1156
+ "footer footer footer";
1157
+ grid-template-rows: auto 1fr auto;
1158
+ grid-template-columns: auto var(--gds-app-shell-aside, 320px) 1fr;
1159
+ }
1160
+
1161
+ /* ============================================
1162
+ THEME-DRIVEN COMPONENT SHAPES
1163
+ Keyed off data attributes that GradeThemeProvider sets on <html>.
1164
+ Each rule is an additive opt-in — themes that don't set a style get
1165
+ the component's default look.
1166
+ ============================================ */
1167
+
1168
+ /* Card styles */
1169
+ [data-card-style="outlined"] .gds-card {
1170
+ box-shadow: none;
1171
+ }
1172
+
1173
+ [data-card-style="flat"] .gds-card {
1174
+ box-shadow: none;
1175
+ border-color: transparent;
1176
+ background-color: oklch(var(--muted));
1177
+ }
1178
+
1179
+ [data-card-style="elevated"] .gds-card {
1180
+ box-shadow: var(--gds-shadow-lg);
1181
+ border-color: transparent;
1182
+ }
1183
+
1184
+ [data-card-style="glass"] .gds-card {
1185
+ background-color: oklch(var(--card) / 0.6);
1186
+ backdrop-filter: blur(12px);
1187
+ -webkit-backdrop-filter: blur(12px);
1188
+ border-color: oklch(var(--border) / 0.5);
1189
+ box-shadow: var(--gds-shadow-md);
1190
+ }
1191
+
1192
+ /* Button shapes — themes can set buttonShape: "pill" | "square" | "sharp".
1193
+ The Button component's radius comes from --radius (shadcn), so we override
1194
+ border-radius directly on the gds-button class when a shape is set. */
1195
+ [data-button-shape="pill"] .gds-button {
1196
+ border-radius: 9999px;
1197
+ }
1198
+
1199
+ [data-button-shape="square"] .gds-button,
1200
+ [data-button-shape="sharp"] .gds-button {
1201
+ border-radius: 0;
1202
+ }
1203
+
1204
+ /* ============================================
1205
+ BUTTON — RAISED VARIANT
1206
+ ============================================
1207
+ Tactile "physical key" treatment composed from elevation tokens.
1208
+ Tone is driven by `--btn-glow` (defaults to --selected-glow); the
1209
+ heat-inner / heat-outer layers in the elevation system already read
1210
+ from --btn-glow so per-button overrides flow through automatically.
1211
+
1212
+ Tabs and Toggle pick this up too via the same data-state / aria
1213
+ selectors below — TabsTrigger emits `data-state="active"`, Toggle
1214
+ emits `data-state="on"`, both listed. */
1215
+ .gds-button-raised {
1216
+ /* Cascade: per-element style override > --accent-glow (theme accent,
1217
+ defaults to --primary) > --selected-glow (system selection blue).
1218
+ Was hardcoded to --selected-glow before May 2026 — the user
1219
+ flagged "should pull from theme accent, not selection blue", which
1220
+ this fallback chain solves without breaking existing per-button
1221
+ style overrides like `style={{ "--btn-glow": "var(--warning)" }}`. */
1222
+ --btn-glow: var(--accent-glow, var(--selected-glow));
1223
+
1224
+ background-color: oklch(var(--secondary));
1225
+ color: oklch(var(--secondary-foreground));
1226
+
1227
+ box-shadow: var(--elevation-3);
1228
+
1229
+ transition:
1230
+ box-shadow var(--gds-transition-base) var(--gds-ease-out),
1231
+ transform var(--gds-transition-fast) var(--gds-ease-out),
1232
+ background-color var(--gds-transition-base) var(--gds-ease-out);
1233
+ }
1234
+
1235
+ .gds-button-raised:hover {
1236
+ box-shadow: var(--elevation-hot);
1237
+ }
1238
+
1239
+ .gds-button-raised:active {
1240
+ transform: translateY(1px);
1241
+ box-shadow: var(--elevation-pressed);
1242
+ }
1243
+
1244
+ .gds-button-raised[data-state="on"],
1245
+ .gds-button-raised[data-state="active"],
1246
+ .gds-button-raised[aria-pressed="true"],
1247
+ .gds-button-raised[data-selected="true"] {
1248
+ background-color: oklch(var(--selected) / 0.18);
1249
+ color: oklch(var(--foreground));
1250
+ box-shadow:
1251
+ var(--shadow-bevel-hi),
1252
+ inset 0 0 0 1px oklch(var(--selected) / 0.55),
1253
+ var(--shadow-heat-inner),
1254
+ 0 0 0 1px oklch(var(--selected) / 0.20),
1255
+ var(--shadow-heat-outer),
1256
+ var(--shadow-lift);
1257
+ }
1258
+
1259
+ .gds-button-raised:focus-visible {
1260
+ outline: none;
1261
+ box-shadow:
1262
+ var(--shadow-bevel-hi),
1263
+ var(--shadow-bevel-lo),
1264
+ 0 0 0 2px oklch(var(--background)),
1265
+ 0 0 0 4px oklch(var(--btn-glow) / 0.70),
1266
+ var(--shadow-lift);
1267
+ }
1268
+
1269
+ /* ============================================
1270
+ CODE CURSOR — blinking caret for `<Code>` typewriter / terminal demos
1271
+ ============================================
1272
+ Rendered as a 1ch-wide block at the tail of the active line. Uses
1273
+ currentColor so it inherits the surrounding token colour (which is
1274
+ `--gds-code-fg` by default but can be set per-line by the consumer).
1275
+ The 1.05s cycle is the canonical iOS/macOS caret blink rate. */
1276
+ @keyframes gds-code-caret-blink {
1277
+ 0%, 49% { opacity: 1; }
1278
+ 50%, 100% { opacity: 0; }
1279
+ }
1280
+ .gds-code-cursor {
1281
+ display: inline-block;
1282
+ width: 0.55em;
1283
+ height: 1.05em;
1284
+ vertical-align: text-bottom;
1285
+ margin-left: 0.05em;
1286
+ background-color: currentColor;
1287
+ border-radius: 1px;
1288
+ animation: gds-code-caret-blink 1.05s steps(1, end) infinite;
1289
+ }
1290
+ @media (prefers-reduced-motion: reduce) {
1291
+ .gds-code-cursor { animation: none; opacity: 1; }
1292
+ }
1293
+
1294
+ /* ============================================
1295
+ DEMO CURSOR — generic blinking caret used by lib/demo/ consumers
1296
+ ============================================
1297
+ The shared caret used wherever a gradeui component scripts typing
1298
+ (Code's terminal lines, Composer's inline cursor, future surfaces).
1299
+ Parameterised via CSS variables so each host can dial dimensions
1300
+ and colour without forking the class.
1301
+
1302
+ --gds-demo-cursor-color defaults to currentColor
1303
+ --gds-demo-cursor-width defaults to 0.55em
1304
+ --gds-demo-cursor-height defaults to 1.05em
1305
+ --gds-demo-cursor-radius defaults to 1px
1306
+
1307
+ Two variants — `inline` (default; thin caret for text) and `block`
1308
+ (square cell for terminal demos). The block variant swaps width to
1309
+ match height. */
1310
+ .gds-demo-cursor {
1311
+ display: inline-block;
1312
+ width: var(--gds-demo-cursor-width, 0.55em);
1313
+ height: var(--gds-demo-cursor-height, 1.05em);
1314
+ vertical-align: text-bottom;
1315
+ margin-left: 0.05em;
1316
+ background-color: var(--gds-demo-cursor-color, currentColor);
1317
+ border-radius: var(--gds-demo-cursor-radius, 1px);
1318
+ animation: gds-code-caret-blink 1.05s steps(1, end) infinite;
1319
+ }
1320
+ .gds-demo-cursor[data-gds-variant="block"] {
1321
+ width: var(--gds-demo-cursor-height, 1.05em);
1322
+ border-radius: 0;
1323
+ }
1324
+ @media (prefers-reduced-motion: reduce) {
1325
+ .gds-demo-cursor { animation: none; opacity: 1; }
1326
+ }
1327
+
1328
+ /* ============================================
1329
+ COMPOSER — token palette for <Composer>
1330
+ ============================================
1331
+ The generic text composition surface (chat / comments / post body).
1332
+ Tokens here drive the editor card, toolbar, mention pills, and
1333
+ attachment chips. Theme via consumer CSS to retune any surface
1334
+ without forking the component.
1335
+
1336
+ Two themes shipped inline: light + dark via .dark scoping. */
1337
+ :root {
1338
+ --gds-composer-bg: var(--background);
1339
+ --gds-composer-fg: var(--foreground);
1340
+ --gds-composer-muted-fg: var(--muted-foreground);
1341
+ --gds-composer-border: var(--border);
1342
+ --gds-composer-action-fg: var(--muted-foreground);
1343
+ --gds-composer-toolbar-fg: var(--muted-foreground);
1344
+ --gds-composer-toolbar-hover-bg: var(--muted);
1345
+ --gds-composer-toolbar-active-bg: var(--accent);
1346
+ --gds-composer-toolbar-active-fg: var(--accent-foreground);
1347
+ --gds-composer-chip-remove-bg: oklch(0.15 0 0);
1348
+ --gds-composer-chip-remove-fg: #fff;
1349
+ --gds-composer-mention-bg: oklch(var(--primary) / 0.12);
1350
+ --gds-composer-mention-fg: oklch(var(--primary));
1351
+ --gds-composer-mention-slash-bg: oklch(0.6 0.12 280 / 0.12);
1352
+ --gds-composer-mention-slash-fg: oklch(0.6 0.12 280);
1353
+ }
1354
+ .dark {
1355
+ --gds-composer-chip-remove-bg: #fff;
1356
+ --gds-composer-chip-remove-fg: oklch(0.15 0 0);
1357
+ }
1358
+
1359
+ /* Mention pill — applied via Lexical theme.beautifulMentions.
1360
+ Renders as an inline rounded chip with a subtle background and
1361
+ the trigger char + value. */
1362
+ .gds-composer-mention {
1363
+ display: inline-block;
1364
+ padding: 0 0.4em;
1365
+ margin: 0 0.05em;
1366
+ border-radius: 0.4em;
1367
+ background: var(--gds-composer-mention-bg);
1368
+ color: var(--gds-composer-mention-fg);
1369
+ font-weight: 500;
1370
+ cursor: default;
1371
+ }
1372
+ .gds-composer-mention-slash {
1373
+ background: var(--gds-composer-mention-slash-bg);
1374
+ color: var(--gds-composer-mention-slash-fg);
1375
+ }
1376
+
1377
+ /* Pullquote — styled variant of blockquote. The Lexical node
1378
+ carries a __pullquote sentinel; this rule applies whenever the
1379
+ blockquote DOM is tagged with data-gds-pullquote (set via a
1380
+ future custom node) OR carries the .gds-composer-pullquote class. */
1381
+ .gds-composer-pullquote,
1382
+ [data-gds-pullquote="true"] {
1383
+ border-left: 3px solid var(--primary);
1384
+ padding-left: 1rem;
1385
+ font-size: 1.125rem;
1386
+ font-style: normal;
1387
+ font-weight: 500;
1388
+ }
1389
+
1390
+ /* ============================================
1391
+ SURFACE CLASSES
1392
+ ============================================
1393
+ Apply one to any container. Composes with elevation classes —
1394
+ `<div class="gds-surface-glass shadow-elevation-4">` is a glass
1395
+ popover sitting at popover elevation, no conflict.
1396
+
1397
+ --card is theme-aware, so each theme automatically gets its own
1398
+ "frosted X" tint without per-theme overrides. */
1399
+ .gds-surface-solid {
1400
+ background-color: var(--surface-solid);
1401
+ }
1402
+
1403
+ .gds-surface-translucent {
1404
+ background-color: var(--surface-translucent);
1405
+ }
1406
+
1407
+ .gds-surface-glass {
1408
+ background-color: var(--surface-glass);
1409
+ backdrop-filter: blur(var(--surface-blur-glass));
1410
+ -webkit-backdrop-filter: blur(var(--surface-blur-glass));
1411
+ border: 1px solid oklch(var(--border) / 0.5);
1412
+ box-shadow: var(--surface-edge);
1413
+ }
1414
+
1415
+ .gds-surface-glass-strong {
1416
+ background-color: var(--surface-glass-strong);
1417
+ backdrop-filter: blur(var(--surface-blur-strong));
1418
+ -webkit-backdrop-filter: blur(var(--surface-blur-strong));
1419
+ border: 1px solid oklch(var(--border) / 0.4);
1420
+ box-shadow: var(--surface-edge);
1421
+ }
1422
+
1423
+ /* ============================================
1424
+ AURA — RING (pulsing outer halo)
1425
+ ============================================
1426
+ Apply `.gds-aura-ring` to any element. Animation duration & easing
1427
+ are CSS vars (`--aura-pulse-duration`, `--aura-pulse-ease`) — set
1428
+ them at the consumer level to retune without rewriting keyframes.
1429
+ Tone defaults to --aura-color (= --selected-glow); override per
1430
+ element for danger / success attention. */
1431
+ @keyframes gds-aura-pulse {
1432
+ 0%, 100% {
1433
+ box-shadow:
1434
+ 0 0 0 0 oklch(var(--aura-color) / var(--aura-ring-alpha-min)),
1435
+ 0 0 0 0 oklch(var(--aura-color) / 0);
1436
+ }
1437
+ 50% {
1438
+ box-shadow:
1439
+ 0 0 0 var(--aura-ring-spread) oklch(var(--aura-color) / var(--aura-ring-alpha-max)),
1440
+ 0 0 var(--aura-ring-blur) 0 oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.6));
1441
+ }
1442
+ }
1443
+
1444
+ .gds-aura-ring {
1445
+ animation: gds-aura-pulse var(--aura-pulse-duration) var(--aura-pulse-ease) infinite;
1446
+ }
1447
+
1448
+ /* ============================================
1449
+ AURA — GRADIENT (rotating conic border)
1450
+ ============================================
1451
+ Uses a ::before pseudo with a conic-gradient masked to just the
1452
+ border ring (via the standard mask-composite trick). The angle is
1453
+ animated via @property so the gradient rotates rather than redrawing.
1454
+ Requires position:relative on the host element; class adds it. */
1455
+ @property --aura-gradient-angle {
1456
+ syntax: "<angle>";
1457
+ initial-value: 0deg;
1458
+ inherits: false;
1459
+ }
1460
+
1461
+ @keyframes gds-aura-gradient-spin {
1462
+ to { --aura-gradient-angle: 360deg; }
1463
+ }
1464
+
1465
+ .gds-aura-gradient {
1466
+ position: relative;
1467
+ isolation: isolate;
1468
+ }
1469
+
1470
+ .gds-aura-gradient::before {
1471
+ content: "";
1472
+ position: absolute;
1473
+ inset: 0;
1474
+ border-radius: inherit;
1475
+ padding: var(--aura-gradient-thickness);
1476
+ background: conic-gradient(
1477
+ from var(--aura-gradient-angle),
1478
+ oklch(var(--aura-color) / 0) 0deg,
1479
+ oklch(var(--aura-color) / 0.9) 90deg,
1480
+ oklch(var(--aura-color) / 0) 180deg,
1481
+ oklch(var(--aura-color) / 0.9) 270deg,
1482
+ oklch(var(--aura-color) / 0) 360deg
1483
+ );
1484
+ /* mask trick: paint only the border ring, not the body */
1485
+ -webkit-mask:
1486
+ linear-gradient(#000 0 0) content-box,
1487
+ linear-gradient(#000 0 0);
1488
+ -webkit-mask-composite: xor;
1489
+ mask:
1490
+ linear-gradient(#000 0 0) content-box,
1491
+ linear-gradient(#000 0 0);
1492
+ mask-composite: exclude;
1493
+ pointer-events: none;
1494
+ animation: gds-aura-gradient-spin var(--aura-gradient-duration) var(--aura-gradient-ease) infinite;
1495
+ }
1496
+
1497
+ /* ============================================
1498
+ AURA — SHIMMER (diagonal sweep)
1499
+ ============================================
1500
+ Highlight sweeps across the element periodically. Lives on a
1501
+ ::after pseudo with `overflow: hidden` applied to the host so the
1502
+ sweep clips to the element's shape. Uses background-position rather
1503
+ than transform so it composites cheaply with the gradient + ring
1504
+ layers. */
1505
+ @keyframes gds-aura-shimmer-sweep {
1506
+ 0% { background-position: -150% 0; opacity: 0; }
1507
+ 8% { opacity: var(--aura-shimmer-alpha); }
1508
+ 60% { background-position: 250% 0; opacity: var(--aura-shimmer-alpha); }
1509
+ 62% { opacity: 0; }
1510
+ 100% { background-position: 250% 0; opacity: 0; }
1511
+ }
1512
+
1513
+ .gds-aura-shimmer {
1514
+ position: relative;
1515
+ overflow: hidden;
1516
+ isolation: isolate;
1517
+ }
1518
+
1519
+ .gds-aura-shimmer::after {
1520
+ content: "";
1521
+ position: absolute;
1522
+ inset: 0;
1523
+ border-radius: inherit;
1524
+ background-image: linear-gradient(
1525
+ 105deg,
1526
+ transparent 30%,
1527
+ oklch(var(--aura-color) / 1) 50%,
1528
+ transparent 70%
1529
+ );
1530
+ background-size: var(--aura-shimmer-width) 200%;
1531
+ background-repeat: no-repeat;
1532
+ pointer-events: none;
1533
+ animation: gds-aura-shimmer-sweep
1534
+ calc(var(--aura-shimmer-duration) + var(--aura-shimmer-delay-between))
1535
+ var(--aura-shimmer-ease) infinite;
1536
+ }
1537
+
1538
+ /* Reduce-motion: aura animations are decorative. Honor the user's
1539
+ pref by holding at the "peak" frame instead of animating. */
1540
+ @media (prefers-reduced-motion: reduce) {
1541
+ .gds-aura-ring,
1542
+ .gds-aura-gradient::before,
1543
+ .gds-aura-shimmer::after {
1544
+ animation: none;
1545
+ }
1546
+ .gds-aura-ring {
1547
+ box-shadow:
1548
+ 0 0 0 var(--aura-ring-spread) oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.6)),
1549
+ 0 0 var(--aura-ring-blur) 0 oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.4));
1550
+ }
1551
+ }
1552
+
1553
+ /* ============================================
1554
+ MOTION TOGGLE — data-motion="off" on <html>
1555
+ ============================================
1556
+ The manual motion switch (lib/motion). Mirrors the OS reduced-motion
1557
+ behaviour but driven by an attribute, so it can be flipped from a toolbar
1558
+ and posted into Studio's Fast Frame / embed iframes (grade:set-motion).
1559
+ Reduce-only: this never forces motion ON, it only suppresses it.
1560
+
1561
+ Broad reset first — neutralises animation/transition on arbitrary
1562
+ (generated-screen) content, the same shape the canonical
1563
+ prefers-reduced-motion reset uses. */
1564
+ [data-motion="off"] *,
1565
+ [data-motion="off"] *::before,
1566
+ [data-motion="off"] *::after {
1567
+ animation-duration: 0.01ms !important;
1568
+ animation-iteration-count: 1 !important;
1569
+ transition-duration: 0.01ms !important;
1570
+ scroll-behavior: auto !important;
1571
+ }
1572
+
1573
+ /* Aura keeps its bespoke static fallback (the broad reset can't supply the
1574
+ resting box-shadow). */
1575
+ [data-motion="off"] .gds-aura-ring {
1576
+ box-shadow:
1577
+ 0 0 0 var(--aura-ring-spread) oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.6)),
1578
+ 0 0 var(--aura-ring-blur) 0 oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.4));
1579
+ }
1580
+
1581
+ /* ============================================
1582
+ STREAM-IN — data-gds-streaming on <html>
1583
+ ============================================
1584
+ Live-draw treatment for streaming renders: while the attribute is
1585
+ stamped on <html>, every element ENTERING the DOM fades + rises in.
1586
+
1587
+ CURRENTLY DORMANT — the Fast Frame sandbox no longer stamps the
1588
+ attribute. Speculative drafts moved to double-buffered full
1589
+ remounts (every compile re-inserts EVERY element, so this rule made
1590
+ the whole frame pulse on each draft tick rather than easing in just
1591
+ the new content). Kept because the treatment is right for a future
1592
+ diff-aware renderer that only inserts the appended elements.
1593
+
1594
+ While stamped, every element ENTERING the DOM fades + rises in.
1595
+ Implemented with @starting-style + transition rather than a
1596
+ keyframe animation on purpose:
1597
+ - `@starting-style` only fires on first render of an element, so
1598
+ React-preserved nodes don't re-animate on every draft compile —
1599
+ only the newly streamed-in elements move.
1600
+ - It composes via `transition`, leaving each component's own
1601
+ `animation` (pulse, spin, aura) untouched. The universal
1602
+ transition does temporarily override component transitions while
1603
+ streaming, which is acceptable: the screen is read-only while it
1604
+ draws.
1605
+ Tunable via the --gds-stream-in-* tokens. Respects both the OS
1606
+ reduced-motion preference and the manual data-motion="off" toggle
1607
+ (the broad reset above zeroes transition-duration). */
1608
+ :root {
1609
+ --gds-stream-in-duration: 240ms;
1610
+ --gds-stream-in-ease: cubic-bezier(0.22, 1, 0.36, 1);
1611
+ --gds-stream-in-rise: 6px;
1612
+ }
1613
+
1614
+ html[data-gds-streaming] body * {
1615
+ transition:
1616
+ opacity var(--gds-stream-in-duration) var(--gds-stream-in-ease),
1617
+ translate var(--gds-stream-in-duration) var(--gds-stream-in-ease);
1618
+ }
1619
+
1620
+ html[data-gds-streaming] body * {
1621
+ @starting-style {
1622
+ opacity: 0;
1623
+ translate: 0 var(--gds-stream-in-rise);
1624
+ }
1625
+ }
1626
+
1627
+ @media (prefers-reduced-motion: reduce) {
1628
+ html[data-gds-streaming] body * {
1629
+ transition: none;
1630
+ }
1631
+ }
1632
+
1633
+ /* ============================================
1634
+ EDIT FLASH — data-gds-flash on changed nodes
1635
+ ============================================
1636
+ "Show me what the AI touched": when an edit turn (or any sealed
1637
+ re-render) commits, the Fast Frame sandbox diffs the old and new
1638
+ frames by data-gds-source-id and stamps `data-gds-flash` on the
1639
+ innermost changed nodes. One short pulse — outline ring + a faint
1640
+ primary wash — then the attribute is removed on animationend so the
1641
+ next edit re-triggers cleanly. Tunable via the --gds-edit-flash-*
1642
+ tokens. Honours reduced motion + the manual data-motion="off" toggle
1643
+ (the broad animation reset above zeroes it). */
1644
+ :root {
1645
+ --gds-edit-flash-duration: 900ms;
1646
+ --gds-edit-flash-color: var(--primary);
1647
+ }
1648
+
1649
+ [data-gds-flash] {
1650
+ animation: gds-edit-flash var(--gds-edit-flash-duration) ease-out;
1651
+ }
1652
+
1653
+ @keyframes gds-edit-flash {
1654
+ 0% {
1655
+ outline: 2px solid oklch(var(--gds-edit-flash-color) / 0.9);
1656
+ outline-offset: 2px;
1657
+ background-color: oklch(var(--gds-edit-flash-color) / 0.1);
1658
+ }
1659
+ 60% {
1660
+ outline: 2px solid oklch(var(--gds-edit-flash-color) / 0.45);
1661
+ outline-offset: 2px;
1662
+ background-color: oklch(var(--gds-edit-flash-color) / 0.04);
1663
+ }
1664
+ 100% {
1665
+ outline: 2px solid transparent;
1666
+ outline-offset: 2px;
1667
+ background-color: transparent;
1668
+ }
1669
+ }
1670
+
1671
+ @media (prefers-reduced-motion: reduce) {
1672
+ [data-gds-flash] {
1673
+ animation: none;
1674
+ }
1675
+ }
1676
+
1677
+ /* ============================================
1678
+ TYPOGRAPHY UTILITIES
1679
+ ============================================ */
1680
+
1681
+ @layer utilities {
1682
+ /* Display */
1683
+ .text-display {
1684
+ font-size: var(--text-display);
1685
+ line-height: var(--text-display-line);
1686
+ letter-spacing: var(--text-display-tracking);
1687
+ font-weight: 700;
1688
+ }
1689
+
1690
+ /* Headings */
1691
+ .text-h1 {
1692
+ font-size: var(--text-h1);
1693
+ line-height: var(--text-h1-line);
1694
+ letter-spacing: var(--text-h1-tracking);
1695
+ font-weight: 700;
1696
+ }
1697
+
1698
+ .text-h2 {
1699
+ font-size: var(--text-h2);
1700
+ line-height: var(--text-h2-line);
1701
+ letter-spacing: var(--text-h2-tracking);
1702
+ font-weight: 600;
1703
+ }
1704
+
1705
+ .text-h3 {
1706
+ font-size: var(--text-h3);
1707
+ line-height: var(--text-h3-line);
1708
+ letter-spacing: var(--text-h3-tracking);
1709
+ font-weight: 600;
1710
+ }
1711
+
1712
+ .text-h4 {
1713
+ font-size: var(--text-h4);
1714
+ line-height: var(--text-h4-line);
1715
+ letter-spacing: var(--text-h4-tracking);
1716
+ font-weight: 600;
1717
+ }
1718
+
1719
+ .text-h5 {
1720
+ font-size: var(--text-h5);
1721
+ line-height: var(--text-h5-line);
1722
+ letter-spacing: var(--text-h5-tracking);
1723
+ font-weight: 500;
1724
+ }
1725
+
1726
+ .text-h6 {
1727
+ font-size: var(--text-h6);
1728
+ line-height: var(--text-h6-line);
1729
+ letter-spacing: var(--text-h6-tracking);
1730
+ font-weight: 500;
1731
+ }
1732
+
1733
+ /* Body */
1734
+ .text-body-lg {
1735
+ font-size: var(--text-body-lg);
1736
+ line-height: var(--text-body-line);
1737
+ }
1738
+
1739
+ .text-body {
1740
+ font-size: var(--text-body);
1741
+ line-height: var(--text-body-line);
1742
+ }
1743
+
1744
+ .text-body-sm {
1745
+ font-size: var(--text-body-sm);
1746
+ line-height: var(--text-body-line);
1747
+ }
1748
+
1749
+ /* Label */
1750
+ .text-label-lg {
1751
+ font-size: var(--text-label-lg);
1752
+ line-height: var(--text-label-line);
1753
+ letter-spacing: var(--text-label-tracking);
1754
+ font-weight: 500;
1755
+ }
1756
+
1757
+ .text-label {
1758
+ font-size: var(--text-label);
1759
+ line-height: var(--text-label-line);
1760
+ letter-spacing: var(--text-label-tracking);
1761
+ font-weight: 500;
1762
+ }
1763
+
1764
+ /* Caption */
1765
+ .text-caption {
1766
+ font-size: var(--text-caption);
1767
+ line-height: var(--text-caption-line);
1768
+ color: oklch(var(--muted-foreground));
1769
+ }
1770
+
1771
+ /* Overline */
1772
+ .text-overline {
1773
+ font-size: var(--text-overline);
1774
+ letter-spacing: var(--text-overline-tracking);
1775
+ text-transform: uppercase;
1776
+ font-weight: 600;
1777
+ }
1778
+
1779
+ /* Code */
1780
+ .text-code {
1781
+ font-family: var(--font-mono);
1782
+ font-size: 0.875em;
1783
+ background-color: oklch(var(--muted));
1784
+ padding: 0.125rem 0.375rem;
1785
+ border-radius: 0.25rem;
1786
+ }
1787
+
1788
+ /* Lead paragraph */
1789
+ .text-lead {
1790
+ font-size: var(--text-body-lg);
1791
+ line-height: var(--text-body-line);
1792
+ color: oklch(var(--muted-foreground));
1793
+ }
1794
+
1795
+ /* Muted text */
1796
+ .text-muted {
1797
+ color: oklch(var(--muted-foreground));
1798
+ }
1799
+ }
1800
+
1801
+ /* ============================================
1802
+ ANIMATION UTILITIES
1803
+ ============================================ */
1804
+
1805
+ @layer utilities {
1806
+ /* Hide scrollbar */
1807
+ .scrollbar-none {
1808
+ -ms-overflow-style: none; /* IE and Edge */
1809
+ scrollbar-width: none; /* Firefox */
1810
+ }
1811
+ .scrollbar-none::-webkit-scrollbar {
1812
+ display: none; /* Chrome, Safari, Opera */
1813
+ }
1814
+
1815
+ /* Fade animations */
1816
+ .animate-fade-in {
1817
+ animation: fadeIn 0.2s ease-out;
1818
+ }
1819
+
1820
+ .animate-fade-out {
1821
+ animation: fadeOut 0.2s ease-in;
1822
+ }
1823
+
1824
+ /* Scale animations */
1825
+ .animate-scale-in {
1826
+ animation: scaleIn 0.15s ease-out;
1827
+ }
1828
+
1829
+ /* Slide animations */
1830
+ .animate-slide-in-from-top {
1831
+ animation: slideInFromTop 0.2s ease-out;
1832
+ }
1833
+
1834
+ .animate-slide-in-from-bottom {
1835
+ animation: slideInFromBottom 0.2s ease-out;
1836
+ }
1837
+
1838
+ .animate-slide-in-from-left {
1839
+ animation: slideInFromLeft 0.2s ease-out;
1840
+ }
1841
+
1842
+ .animate-slide-in-from-right {
1843
+ animation: slideInFromRight 0.2s ease-out;
1844
+ }
1845
+
1846
+ /* Pulse animation for energy indicators */
1847
+ .animate-energy-pulse {
1848
+ animation: energyPulse 2s ease-in-out infinite;
1849
+ }
1850
+
1851
+ /* Shimmer effect for loading states */
1852
+ .animate-shimmer {
1853
+ animation: shimmer 2s linear infinite;
1854
+ background: linear-gradient(
1855
+ 90deg,
1856
+ oklch(var(--muted)) 0%,
1857
+ oklch(var(--muted-foreground) / 0.1) 50%,
1858
+ oklch(var(--muted)) 100%
1859
+ );
1860
+ background-size: 200% 100%;
1861
+ }
1862
+ }
1863
+
1864
+ /* Keyframes */
1865
+ @keyframes fadeIn {
1866
+ from { opacity: 0; }
1867
+ to { opacity: 1; }
1868
+ }
1869
+
1870
+ @keyframes fadeOut {
1871
+ from { opacity: 1; }
1872
+ to { opacity: 0; }
1873
+ }
1874
+
1875
+ @keyframes scaleIn {
1876
+ from {
1877
+ opacity: 0;
1878
+ transform: scale(0.95);
1879
+ }
1880
+ to {
1881
+ opacity: 1;
1882
+ transform: scale(1);
1883
+ }
1884
+ }
1885
+
1886
+ @keyframes slideInFromTop {
1887
+ from {
1888
+ opacity: 0;
1889
+ transform: translateY(-10px);
1890
+ }
1891
+ to {
1892
+ opacity: 1;
1893
+ transform: translateY(0);
1894
+ }
1895
+ }
1896
+
1897
+ @keyframes slideInFromBottom {
1898
+ from {
1899
+ opacity: 0;
1900
+ transform: translateY(10px);
1901
+ }
1902
+ to {
1903
+ opacity: 1;
1904
+ transform: translateY(0);
1905
+ }
1906
+ }
1907
+
1908
+ @keyframes slideInFromLeft {
1909
+ from {
1910
+ opacity: 0;
1911
+ transform: translateX(-10px);
1912
+ }
1913
+ to {
1914
+ opacity: 1;
1915
+ transform: translateX(0);
1916
+ }
1917
+ }
1918
+
1919
+ @keyframes slideInFromRight {
1920
+ from {
1921
+ opacity: 0;
1922
+ transform: translateX(10px);
1923
+ }
1924
+ to {
1925
+ opacity: 1;
1926
+ transform: translateX(0);
1927
+ }
1928
+ }
1929
+
1930
+ @keyframes energyPulse {
1931
+ 0%, 100% {
1932
+ opacity: 1;
1933
+ box-shadow: 0 0 0 0 oklch(var(--accent) / 0.4);
1934
+ }
1935
+ 50% {
1936
+ opacity: 0.8;
1937
+ box-shadow: 0 0 0 8px oklch(var(--accent) / 0);
1938
+ }
1939
+ }
1940
+
1941
+ @keyframes shimmer {
1942
+ 0% { background-position: 200% 0; }
1943
+ 100% { background-position: -200% 0; }
1944
+ }
1945
+
1946
+ @keyframes pulse-glow {
1947
+ 0%, 100% {
1948
+ box-shadow: 0 0 4px 1px oklch(var(--primary) / 0.2);
1949
+ }
1950
+ 50% {
1951
+ box-shadow: 0 0 8px 2px oklch(var(--primary) / 0.35);
1952
+ }
1953
+ }
1954
+
1955
+ .animate-pulse-glow {
1956
+ animation: pulse-glow 2s ease-in-out infinite;
1957
+ }
1958
+
1959
+ /* CSS Houdini property for animating conic-gradient angle */
1960
+ @property --angle {
1961
+ syntax: '<angle>';
1962
+ initial-value: 0deg;
1963
+ inherits: false;
1964
+ }
1965
+
1966
+ @keyframes rotate-border {
1967
+ 0% {
1968
+ --angle: 0deg;
1969
+ }
1970
+ 100% {
1971
+ --angle: 360deg;
1972
+ }
1973
+ }
1974
+
1975
+ /* Border travel animation for energy flow nodes */
1976
+ .border-travel-animation {
1977
+ position: relative;
1978
+ border: 2px solid transparent;
1979
+ background:
1980
+ linear-gradient(white, white) padding-box,
1981
+ conic-gradient(
1982
+ from var(--angle, 0deg),
1983
+ rgba(34, 197, 94, 0.15) 0deg,
1984
+ rgba(34, 197, 94, 0.15) 350deg,
1985
+ #22c55e 353deg,
1986
+ #16a34a 356deg,
1987
+ #22c55e 359deg,
1988
+ rgba(34, 197, 94, 0.15) 360deg
1989
+ ) border-box;
1990
+ animation: rotate-border 3s linear infinite;
1991
+ border-radius: 0.75rem;
1992
+ }
1993
+
1994
+ .border-travel-animation.rounded-full {
1995
+ border-radius: 9999px;
1996
+ }
1997
+
1998
+ .dark .border-travel-animation {
1999
+ background:
2000
+ linear-gradient(#141414, #141414) padding-box,
2001
+ conic-gradient(
2002
+ from var(--angle, 0deg),
2003
+ rgba(34, 197, 94, 0.1) 0deg,
2004
+ rgba(34, 197, 94, 0.1) 350deg,
2005
+ #22c55e 353deg,
2006
+ #16a34a 356deg,
2007
+ #22c55e 359deg,
2008
+ rgba(34, 197, 94, 0.1) 360deg
2009
+ ) border-box;
2010
+ }
2011
+
2012
+ /* React Flow controls positioning */
2013
+ .react-flow__controls {
2014
+ bottom: 10px !important;
2015
+ top: auto !important;
2016
+ left: 10px !important;
2017
+ }
2018
+
2019
+ /* React Flow dark mode styles */
2020
+ .dark .react-flow__controls {
2021
+ background: #141414;
2022
+ border: 1px solid #262626;
2023
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
2024
+ }
2025
+
2026
+ .dark .react-flow__controls-button {
2027
+ background: #141414;
2028
+ border-bottom: 1px solid #262626;
2029
+ fill: #a3a3a3;
2030
+ }
2031
+
2032
+ .dark .react-flow__controls-button:hover {
2033
+ background: #262626;
2034
+ }
2035
+
2036
+ .dark .react-flow__controls-button svg {
2037
+ fill: #a3a3a3;
2038
+ }
2039
+
2040
+ .dark .react-flow__controls-button:hover svg {
2041
+ fill: #ffffff;
2042
+ }
2043
+
2044
+ /* Dotted background pattern */
2045
+ .bg-dot-pattern {
2046
+ background-image: radial-gradient(circle, rgba(0, 0, 0, 0.08) 1px, transparent 1px);
2047
+ background-size: 24px 24px;
2048
+ }
2049
+
2050
+ .dark .bg-dot-pattern {
2051
+ background-image: radial-gradient(circle, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
2052
+ }
2053
+
2054
+ /* Gradient fade for hero */
2055
+ .hero-gradient {
2056
+ background: linear-gradient(to bottom, transparent 0%, oklch(var(--background)) 70%, oklch(var(--background)) 100%);
2057
+ }
2058
+
2059
+ /* ============================================
2060
+ INFINITE SCROLL ANIMATION (Partner Logos)
2061
+ ============================================ */
2062
+
2063
+ @keyframes scrollLeft {
2064
+ 0% {
2065
+ transform: translateX(0);
2066
+ }
2067
+ 100% {
2068
+ transform: translateX(-50%);
2069
+ }
2070
+ }
2071
+
2072
+ @keyframes scrollRight {
2073
+ 0% {
2074
+ transform: translateX(-50%);
2075
+ }
2076
+ 100% {
2077
+ transform: translateX(0);
2078
+ }
2079
+ }
2080
+
2081
+ .animate-scroll-left {
2082
+ animation: scrollLeft linear infinite;
2083
+ }
2084
+
2085
+ .animate-scroll-right {
2086
+ animation: scrollRight linear infinite;
2087
+ }
2088
+
2089
+ /* Pause animation on hover */
2090
+ .animate-scroll-left:hover,
2091
+ .animate-scroll-right:hover {
2092
+ animation-play-state: paused;
2093
+ }
2094
+
2095
+ /* ─────────────────────────────────────────────────────────────────────────
2096
+ Selection cards — RadioCard / CheckboxCard / SwitchCard
2097
+ ---------------------------------------------------------------------------
2098
+ The whole card is the control, so focus / hover / checked all live on the
2099
+ parent surface. Every visual reads from a `--gds-selection-card-*` token
2100
+ with a semantic fallback, so a project can re-skin the cards via the
2101
+ per-project override layer (set the token at theme scope) without touching
2102
+ the component. Per-instance overrides still flow through `style={{ … }}`.
2103
+ ───────────────────────────────────────────────────────────────────────── */
2104
+ .gds-selection-card {
2105
+ position: relative;
2106
+ display: flex;
2107
+ align-items: flex-start;
2108
+ gap: var(--gds-selection-card-gap, 0.75rem);
2109
+ width: 100%;
2110
+ text-align: left;
2111
+ cursor: pointer;
2112
+ padding: var(--gds-selection-card-padding, 1rem);
2113
+ border-radius: var(--gds-selection-card-radius, var(--radius));
2114
+ border: var(--gds-selection-card-border-width, 1px) solid
2115
+ oklch(var(--gds-selection-card-border, var(--border)));
2116
+ background: oklch(var(--gds-selection-card-bg, var(--card)));
2117
+ color: oklch(var(--foreground));
2118
+ transition:
2119
+ border-color 150ms ease,
2120
+ background-color 150ms ease,
2121
+ box-shadow 150ms ease;
2122
+ }
2123
+ .gds-selection-card:hover {
2124
+ background: var(--gds-selection-card-hover-bg, oklch(var(--muted) / 0.55));
2125
+ }
2126
+ .gds-selection-card:focus-visible {
2127
+ outline: none;
2128
+ box-shadow:
2129
+ 0 0 0 2px oklch(var(--background)),
2130
+ 0 0 0 4px oklch(var(--gds-selection-card-ring, var(--ring)) / 0.85);
2131
+ }
2132
+ .gds-selection-card[data-state="checked"] {
2133
+ border-color: oklch(
2134
+ var(--gds-selection-card-selected-border, var(--foreground))
2135
+ );
2136
+ background: var(
2137
+ --gds-selection-card-selected-bg,
2138
+ oklch(var(--muted) / 0.4)
2139
+ );
2140
+ }
2141
+ .gds-selection-card:disabled,
2142
+ .gds-selection-card[data-disabled] {
2143
+ cursor: not-allowed;
2144
+ opacity: 0.5;
2145
+ }
2146
+
2147
+ /* Content blocks */
2148
+ .gds-selection-card__content {
2149
+ display: flex;
2150
+ flex-direction: column;
2151
+ gap: 0.125rem;
2152
+ flex: 1 1 auto;
2153
+ min-width: 0;
2154
+ }
2155
+ .gds-selection-card__slot {
2156
+ flex: 1 1 auto;
2157
+ min-width: 0;
2158
+ }
2159
+ .gds-selection-card__title {
2160
+ font-size: 0.875rem;
2161
+ font-weight: 500;
2162
+ line-height: 1.4;
2163
+ color: oklch(var(--foreground));
2164
+ }
2165
+ .gds-selection-card__desc {
2166
+ font-size: 0.875rem;
2167
+ line-height: 1.4;
2168
+ color: oklch(var(--muted-foreground));
2169
+ }
2170
+
2171
+ /* ── Indicators (the small glyph; differs by control type) ─────────────── */
2172
+ .gds-selection-indicator {
2173
+ display: inline-flex;
2174
+ align-items: center;
2175
+ justify-content: center;
2176
+ flex: none;
2177
+ margin-top: 0.0625rem;
2178
+ }
2179
+
2180
+ /* Radio — empty ring → filled circle with a light inner dot when checked */
2181
+ .gds-selection-indicator--radio {
2182
+ height: 1.25rem;
2183
+ width: 1.25rem;
2184
+ border-radius: 9999px;
2185
+ border: 1.5px solid oklch(var(--gds-selection-card-control, var(--border)));
2186
+ background: oklch(var(--background));
2187
+ transition:
2188
+ border-color 150ms ease,
2189
+ background-color 150ms ease;
2190
+ }
2191
+ .gds-selection-card[data-state="checked"] .gds-selection-indicator--radio {
2192
+ border-color: oklch(var(--gds-selection-card-control-checked, var(--primary)));
2193
+ background: oklch(var(--gds-selection-card-control-checked, var(--primary)));
2194
+ }
2195
+ .gds-selection-indicator__dot {
2196
+ display: block;
2197
+ height: 0.5rem;
2198
+ width: 0.5rem;
2199
+ border-radius: 9999px;
2200
+ background: oklch(var(--background));
2201
+ }
2202
+
2203
+ /* Checkbox — rounded square, check glyph when checked */
2204
+ .gds-selection-indicator--checkbox {
2205
+ height: 1.2rem;
2206
+ width: 1.2rem;
2207
+ border-radius: 0.3rem;
2208
+ border: 1.5px solid oklch(var(--gds-selection-card-control, var(--border)));
2209
+ background: oklch(var(--background));
2210
+ color: oklch(var(--background));
2211
+ transition:
2212
+ border-color 150ms ease,
2213
+ background-color 150ms ease;
2214
+ }
2215
+ .gds-selection-card[data-state="checked"] .gds-selection-indicator--checkbox {
2216
+ border-color: oklch(var(--gds-selection-card-control-checked, var(--primary)));
2217
+ background: oklch(var(--gds-selection-card-control-checked, var(--primary)));
2218
+ }
2219
+ .gds-selection-indicator__check {
2220
+ display: inline-flex;
2221
+ align-items: center;
2222
+ justify-content: center;
2223
+ color: oklch(var(--background));
2224
+ }
2225
+
2226
+ /* Switch — track + thumb that slides on checked */
2227
+ .gds-selection-indicator--switch {
2228
+ height: 1.25rem;
2229
+ width: 2.25rem;
2230
+ padding: 2px;
2231
+ border-radius: 9999px;
2232
+ background: oklch(var(--input));
2233
+ justify-content: flex-start;
2234
+ transition: background-color 150ms ease;
2235
+ }
2236
+ .gds-selection-card[data-state="checked"] .gds-selection-indicator--switch {
2237
+ background: oklch(var(--gds-selection-card-control-checked, var(--primary)));
2238
+ }
2239
+ .gds-selection-indicator__thumb {
2240
+ display: block;
2241
+ height: 1rem;
2242
+ width: 1rem;
2243
+ border-radius: 9999px;
2244
+ background: oklch(var(--background));
2245
+ box-shadow: 0 1px 2px oklch(0 0 0 / 0.25);
2246
+ transition: transform 150ms ease;
2247
+ }
2248
+ .gds-selection-card[data-state="checked"] .gds-selection-indicator__thumb {
2249
+ transform: translateX(1rem);
2250
+ }
2251
+
2252
+ /* ─────────────────────────────────────────────────────────────────────────
2253
+ Elevation — real classes (JIT-proof, always in the bundle).
2254
+ ---------------------------------------------------------------------------
2255
+ The `shadow-elevation-N` Tailwind utilities (declared in tailwind-preset.ts)
2256
+ only compile when a scanned source file actually uses the class string, so
2257
+ typing `shadow-elevation-3` in Studio output or a consumer screen produced
2258
+ no CSS. These plain classes always exist in the shipped stylesheet, exactly
2259
+ like `.gds-surface-*`, so elevation works everywhere regardless of the JIT
2260
+ content scan. Prefer these for runtime/generated UI; the Tailwind utilities
2261
+ remain available for component-internal use.
2262
+ ───────────────────────────────────────────────────────────────────────── */
2263
+ .gds-elevation-0 { box-shadow: var(--elevation-0); }
2264
+ .gds-elevation-1 { box-shadow: var(--elevation-1); }
2265
+ .gds-elevation-2 { box-shadow: var(--elevation-2); }
2266
+ .gds-elevation-3 { box-shadow: var(--elevation-3); }
2267
+ .gds-elevation-4 { box-shadow: var(--elevation-4); }
2268
+ .gds-elevation-5 { box-shadow: var(--elevation-5); }
2269
+ .gds-elevation-hot { box-shadow: var(--elevation-hot); }
2270
+ .gds-elevation-pressed { box-shadow: var(--elevation-pressed); }
2271
+ /* Single-layer atoms, for composing a custom stack */
2272
+ .gds-shadow-bevel-hi { box-shadow: var(--shadow-bevel-hi); }
2273
+ .gds-shadow-bevel-lo { box-shadow: var(--shadow-bevel-lo); }
2274
+ .gds-shadow-contact { box-shadow: var(--shadow-contact); }
2275
+ .gds-shadow-lift { box-shadow: var(--shadow-lift); }
2276
+ .gds-shadow-lift-deep { box-shadow: var(--shadow-lift-deep); }
2277
+ .gds-shadow-heat-inner { box-shadow: var(--shadow-heat-inner); }
2278
+ .gds-shadow-heat-outer { box-shadow: var(--shadow-heat-outer); }