@gradeui/ui 2.1.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +5 -12
  2. package/components/ui/button.md +11 -7
  3. package/components/ui/dropdown-menu.md +1 -0
  4. package/components/ui/logo.md +8 -6
  5. package/components/ui/map.md +3 -0
  6. package/components/ui/media-surface.md +1 -0
  7. package/components/ui/sidebar.md +2 -1
  8. package/components/ui/swatch.md +88 -0
  9. package/dist/contracts.js +6 -6
  10. package/dist/contracts.js.map +1 -1
  11. package/dist/contracts.mjs +6 -6
  12. package/dist/contracts.mjs.map +1 -1
  13. package/dist/index.d.mts +617 -393
  14. package/dist/index.d.ts +617 -393
  15. package/dist/index.js +602 -65
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +602 -65
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/map/google.d.mts +1 -1
  20. package/dist/map/google.d.ts +1 -1
  21. package/dist/map/google.js +1 -1
  22. package/dist/map/google.js.map +1 -1
  23. package/dist/map/google.mjs +1 -1
  24. package/dist/map/google.mjs.map +1 -1
  25. package/dist/map/leaflet.d.mts +1 -1
  26. package/dist/map/leaflet.d.ts +1 -1
  27. package/dist/map/leaflet.js +2 -2
  28. package/dist/map/leaflet.js.map +1 -1
  29. package/dist/map/leaflet.mjs +2 -2
  30. package/dist/map/leaflet.mjs.map +1 -1
  31. package/dist/map/mapbox.d.mts +1 -1
  32. package/dist/map/mapbox.d.ts +1 -1
  33. package/dist/map/mapbox.js +2 -2
  34. package/dist/map/mapbox.js.map +1 -1
  35. package/dist/map/mapbox.mjs +2 -2
  36. package/dist/map/mapbox.mjs.map +1 -1
  37. package/dist/map/maplibre.d.mts +1 -1
  38. package/dist/map/maplibre.d.ts +1 -1
  39. package/dist/map/maplibre.js +1 -1
  40. package/dist/map/maplibre.js.map +1 -1
  41. package/dist/map/maplibre.mjs +1 -1
  42. package/dist/map/maplibre.mjs.map +1 -1
  43. package/dist/styles.css +1 -1
  44. package/dist/{types-BxywIwvG.d.mts → types-B45Uirkp.d.mts} +23 -0
  45. package/dist/{types-BxywIwvG.d.ts → types-B45Uirkp.d.ts} +23 -0
  46. package/package.json +3 -6
  47. package/styles/globals.css +2402 -0
  48. package/dist/tailwind-preset.d.mts +0 -20
  49. package/dist/tailwind-preset.d.ts +0 -20
  50. package/dist/tailwind-preset.js +0 -2
  51. package/dist/tailwind-preset.js.map +0 -1
  52. package/dist/tailwind-preset.mjs +0 -2
  53. package/dist/tailwind-preset.mjs.map +0 -1
@@ -0,0 +1,2402 @@
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
+ /* Fidelity fade — how long a MediaSurface takes to cross-fade between
834
+ full imagery and the wireframe placeholder when `data-fidelity`
835
+ flips on an ancestor (Studio's toggle, an embed's `fidelity`
836
+ parameter). One knob, read by the "MediaSurface fidelity" rules
837
+ further down. */
838
+ --gds-media-fidelity-fade: 280ms;
839
+
840
+ /* Transparency checkerboard — the standard alpha backdrop drawn behind
841
+ image previews (Studio's inspector image well; anywhere else that
842
+ needs to say "these pixels are transparent"). Consumed as
843
+ `repeating-conic-gradient(var(--gds-media-checker-color) 0% 25%,
844
+ transparent 0% 50%)` sized by the size token. Mode-aware for free:
845
+ the colour rides on --muted-foreground at low alpha, so it reads as
846
+ grey/white in light mode and grey/dark in dark without a `.dark`
847
+ override. */
848
+ --gds-media-checker-color: oklch(var(--muted-foreground) / 0.16);
849
+ --gds-media-checker-size: 14px;
850
+
851
+ /* Canvas fill — the standard backdrop behind a screen when it doesn't fill
852
+ its frame: the letterbox bars in an embed/share, and the stage a
853
+ `<ScreenAnimator>` reveals when it flies in or pulls below 1× zoom. One
854
+ token so every "canvas" surface (embed, share, animator) matches and a
855
+ theme/brand can restyle it in one place. Mode-aware: a soft neutral in
856
+ light mode and a deep near-black in dark (see `.dark` below) — a neutral
857
+ cinematic backdrop that flatters a screen sitting on top of it in either
858
+ mode. Set it to `transparent` to let the host page show through. */
859
+ --gds-canvas-fill: #e8e8ec;
860
+
861
+ /* Map marker chrome — the baseline "lift" every MapMarker child gets
862
+ so pin labels never melt into the tiles (see the map-marker rule
863
+ near the fidelity block). Map tiles are EXTERNAL imagery — they
864
+ don't ride the theme's surface tokens — so marker content needs
865
+ its own guaranteed separation in both modes: a 1px border + a soft
866
+ ambient shadow. Light mode: a slightly-darker-than---border line
867
+ reads against pale tiles. Dark mode override below flips to a
868
+ light hairline (a dark border on dark tiles lifts nothing). */
869
+ --gds-map-marker-border: oklch(var(--border));
870
+ --gds-map-marker-shadow: 0 1px 3px oklch(0 0 0 / 0.22),
871
+ 0 0 0 1px oklch(0 0 0 / 0.05);
872
+
873
+ /* Sidebar knobs — the width pair drives the collapsed/expanded animation
874
+ on `<Sidebar>` (compound nav primitive). Header height + section
875
+ padding + per-section gap are exposed so consumers can retune density
876
+ without prop drilling. */
877
+ --gds-sidebar-width: 16rem;
878
+ --gds-sidebar-collapsed-width: 4rem;
879
+ --gds-sidebar-header-height: 3.25rem;
880
+ --gds-sidebar-content-py: 0.5rem;
881
+ --gds-sidebar-section-px: 0.5rem;
882
+ --gds-sidebar-section-gap: 0.125rem;
883
+
884
+ /* Carousel knobs — every visual dimension of <Carousel> is driven from
885
+ this stanza so consumers can re-skin the slideshow (dot shape, arrow
886
+ chrome, transition feel) without prop-drilling. The component reads
887
+ these as `var(--gds-carousel-* , <fallback>)` so missing values are
888
+ safe. */
889
+ --gds-carousel-gap: 0;
890
+ --gds-carousel-radius: var(--gds-media-radius, 0.5rem);
891
+ --gds-carousel-fade-ms: 200ms;
892
+ --gds-carousel-slide-basis: 100%;
893
+ /* Dots */
894
+ --gds-carousel-dot-size: 0.5rem;
895
+ --gds-carousel-dot-active-width: 1.25rem;
896
+ --gds-carousel-dot-color: oklch(var(--muted-foreground) / 0.4);
897
+ --gds-carousel-dot-active-color: oklch(var(--primary));
898
+ --gds-carousel-dots-gap: 0.75rem; /* between viewport and below-dots */
899
+ --gds-carousel-dots-spacing: 0.5rem; /* between adjacent dots */
900
+ --gds-carousel-dots-inset: 0.75rem; /* overlay position from bottom */
901
+ /* Arrows */
902
+ --gds-carousel-arrow-size: 2.25rem;
903
+ --gds-carousel-arrow-inset: 0.75rem;
904
+ --gds-carousel-arrow-bg: oklch(var(--background) / 0.85);
905
+ --gds-carousel-arrow-hover-bg: oklch(var(--background));
906
+ --gds-carousel-arrow-fg: oklch(var(--foreground));
907
+ --gds-carousel-arrow-backdrop: blur(6px);
908
+ --gds-carousel-arrow-shadow: 0 2px 8px oklch(0 0 0 / 0.12);
909
+
910
+ /* Alert surface pairs — paler tinted surface + readable deep text for each
911
+ status colour. Pre-hydration fallbacks; the JS theme pipeline overwrites
912
+ these via lib/themes/oklch.ts#deriveAlertPair. Kept here so the Alert
913
+ component has the right tokens before the theme provider mounts. */
914
+ --destructive-soft: 0.965 0.045 27;
915
+ --destructive-deep: 0.380 0.220 27;
916
+ --success-soft: 0.965 0.040 145;
917
+ --success-deep: 0.380 0.180 145;
918
+ --warning-soft: 0.965 0.040 60;
919
+ --warning-deep: 0.380 0.180 60;
920
+ --info-soft: 0.965 0.044 240;
921
+ --info-deep: 0.380 0.200 240;
922
+ --highlight-soft: 0.965 0.040 95;
923
+ --highlight-deep: 0.380 0.180 95;
924
+
925
+ /* Code surface tokens — used by `<Code>` (marketing/docs code blocks).
926
+ Token roles map to prism's tag types; the component reads them as
927
+ `color: var(--gds-code-<role>)` so the palette inverts with the
928
+ theme without us swapping prism themes at runtime. Light values
929
+ are tuned against a near-paper surface; the .dark block below
930
+ mirrors them for dark mode.
931
+
932
+ diff-added / diff-removed pairs are intentionally desaturated
933
+ surfaces with strong text — same pattern as the destructive-soft /
934
+ -deep alert pairs. Line-highlight is a quieter accent (no green/
935
+ red implication, just "look here"). */
936
+ --gds-code-bg: oklch(0.985 0.0018 85);
937
+ --gds-code-fg: oklch(0.220 0.012 250);
938
+ --gds-code-comment: oklch(0.620 0.030 250);
939
+ --gds-code-punctuation: oklch(0.420 0.020 250);
940
+ --gds-code-property: oklch(0.550 0.180 25);
941
+ --gds-code-number: oklch(0.560 0.180 35);
942
+ --gds-code-string: oklch(0.500 0.140 145);
943
+ --gds-code-operator: oklch(0.480 0.140 280);
944
+ --gds-code-keyword: oklch(0.490 0.220 285);
945
+ --gds-code-function: oklch(0.500 0.200 250);
946
+ --gds-code-variable: oklch(0.530 0.180 25);
947
+ --gds-code-tag: oklch(0.520 0.200 15);
948
+ --gds-code-attr-name: oklch(0.510 0.150 60);
949
+ --gds-code-attr-value: oklch(0.500 0.140 145);
950
+ --gds-code-line-highlight-bg: oklch(var(--selected-glow) / 0.10);
951
+ --gds-code-line-highlight-marker: oklch(var(--selected-glow) / 0.65);
952
+ --gds-code-diff-added-bg: oklch(var(--success) / 0.12);
953
+ --gds-code-diff-added-fg: oklch(0.300 0.140 145);
954
+ --gds-code-diff-removed-bg: oklch(var(--destructive) / 0.10);
955
+ --gds-code-diff-removed-fg: oklch(0.360 0.180 27);
956
+ }
957
+
958
+ .dark {
959
+ /* ----------------------------------------
960
+ Semantic Tokens — Studio dark defaults (synced from
961
+ apps/docs/app/globals.css, A7.1 — see the :root note).
962
+ ---------------------------------------- */
963
+ --background: 0.170 0.0048 85;
964
+ --foreground: 0.985 0.0018 85;
965
+ --card: 0.245 0.0096 85; /* neutral 900 */
966
+ --card-foreground: 0.985 0.0018 85;
967
+ --popover: 0.245 0.0096 85;
968
+ --popover-foreground: 0.985 0.0018 85;
969
+ --primary: 0.955 0.0048 85; /* primary 400 */
970
+ --primary-foreground: 0.170 0.0048 85;
971
+ --secondary: 0.325 0.0144 85; /* neutral 800 */
972
+ --secondary-foreground: 0.895 0.0090 85;
973
+ --muted: 0.325 0.0144 85;
974
+ --muted-foreground: 0.720 0.0168 85;
975
+ --accent: 0.720 0.0252 85;
976
+ --accent-foreground: 0.170 0.0072 85;
977
+ --destructive: 0.680 0.220 27;
978
+ --destructive-foreground: 0.990 0.005 27;
979
+ --border: 0.325 0.0144 85;
980
+ --input: 0.325 0.0144 85;
981
+ --ring: 0.820 0.0132 85;
982
+
983
+ --success: 0.720 0.180 145;
984
+ --warning: 0.800 0.180 60;
985
+ --info: 0.700 0.200 240;
986
+ --highlight: 0.880 0.180 95;
987
+
988
+ /* Selection pair — dark mode. Solid fill stays vivid; halo lifts in
989
+ lightness so it can glow against the dark canvas. */
990
+ --selected: 0.62 0.22 258;
991
+ --selected-foreground: 0.985 0.0018 85;
992
+ --selected-glow: 0.74 0.20 258;
993
+
994
+ /* Accent glow — dark mode. Same theme-driven default (primary), but
995
+ the dark variants of --primary already carry the appropriate lift
996
+ for a glow against the dark canvas. */
997
+ --accent-glow: var(--primary);
998
+
999
+ /* Canvas fill — dark mode. Deep near-black cinematic backdrop (the light
1000
+ default in :root is a soft neutral). One token, mode-scoped: letterbox
1001
+ bars in embed/share/preview and the ScreenAnimator stage all match. */
1002
+ --gds-canvas-fill: #0b0b0e;
1003
+
1004
+ /* Map marker chrome — dark mode: a light hairline + deeper shadow.
1005
+ Dark-mode pin surfaces (bg-card etc.) sit on dark tiles, so the
1006
+ border has to be the bright element; the alpha keeps it a lift,
1007
+ not a frame. */
1008
+ --gds-map-marker-border: oklch(1 0 0 / 0.3);
1009
+ --gds-map-marker-shadow: 0 1px 3px oklch(0 0 0 / 0.5),
1010
+ 0 0 0 1px oklch(0 0 0 / 0.4);
1011
+
1012
+ /* Alert surface pairs — dark-mode variant: tint sits just above the dark
1013
+ background without going gray; deep text stays bright and readable. */
1014
+ --destructive-soft: 0.220 0.075 27;
1015
+ --destructive-deep: 0.820 0.198 27;
1016
+ --success-soft: 0.220 0.075 145;
1017
+ --success-deep: 0.820 0.162 145;
1018
+ --warning-soft: 0.220 0.075 60;
1019
+ --warning-deep: 0.820 0.162 60;
1020
+ --info-soft: 0.220 0.075 240;
1021
+ --info-deep: 0.820 0.180 240;
1022
+ --highlight-soft: 0.220 0.075 95;
1023
+ --highlight-deep: 0.820 0.162 95;
1024
+
1025
+ /* -- Elevation: bump alphas in dark mode. Shadows fade against
1026
+ dark surfaces; without this the lift looks anemic. -- */
1027
+ --shadow-bevel-hi-alpha: 0.14;
1028
+ --shadow-bevel-lo-alpha: 0.40;
1029
+ --shadow-contact-alpha: 0.45;
1030
+ --shadow-lift-alpha: 0.50;
1031
+ --shadow-lift-deep-alpha: 0.60;
1032
+
1033
+ /* -- Surface: glass needs higher opacity in dark mode or the card
1034
+ loses its boundary against the canvas. -- */
1035
+ --surface-alpha-translucent: 0.74;
1036
+ --surface-alpha-glass: 0.48;
1037
+ --surface-alpha-glass-strong: 0.32;
1038
+ --surface-edge-alpha: 0.06;
1039
+
1040
+ /* Code surface tokens — dark mode mirror. Background sits a touch
1041
+ above the page bg so the code block reads as a distinct surface
1042
+ even on a dark hero. Tokens lift in lightness to stay readable. */
1043
+ --gds-code-bg: oklch(0.205 0.010 250);
1044
+ --gds-code-fg: oklch(0.920 0.012 250);
1045
+ --gds-code-comment: oklch(0.580 0.024 250);
1046
+ --gds-code-punctuation: oklch(0.760 0.018 250);
1047
+ --gds-code-property: oklch(0.780 0.150 25);
1048
+ --gds-code-number: oklch(0.790 0.150 60);
1049
+ --gds-code-string: oklch(0.780 0.140 145);
1050
+ --gds-code-operator: oklch(0.770 0.130 280);
1051
+ --gds-code-keyword: oklch(0.760 0.180 285);
1052
+ --gds-code-function: oklch(0.770 0.150 250);
1053
+ --gds-code-variable: oklch(0.790 0.160 25);
1054
+ --gds-code-tag: oklch(0.780 0.180 15);
1055
+ --gds-code-attr-name: oklch(0.800 0.140 60);
1056
+ --gds-code-attr-value: oklch(0.780 0.140 145);
1057
+ --gds-code-line-highlight-bg: oklch(var(--selected-glow) / 0.16);
1058
+ --gds-code-line-highlight-marker: oklch(var(--selected-glow) / 0.85);
1059
+ --gds-code-diff-added-bg: oklch(var(--success) / 0.18);
1060
+ --gds-code-diff-added-fg: oklch(0.870 0.160 145);
1061
+ --gds-code-diff-removed-bg: oklch(var(--destructive) / 0.18);
1062
+ --gds-code-diff-removed-fg: oklch(0.870 0.180 27);
1063
+ }
1064
+
1065
+ /* ============================================
1066
+ ENERGY THEME — scoped overrides.
1067
+ Engaged when data-grade-theme="energy" is set on :root (either by the
1068
+ pre-hydration inline script or GradeThemeProvider). Mirrors the Energy
1069
+ built-in (hues all at 175, teal primary, Geist sans).
1070
+ ============================================ */
1071
+ :root[data-grade-theme="energy"] {
1072
+ --background: 0.985 0.0012 175; /* neutral 50 */
1073
+ --foreground: 0.170 0.0032 175; /* neutral 950 */
1074
+ --card: 1 0 0;
1075
+ --card-foreground: 0.170 0.0032 175;
1076
+ --popover: 1 0 0;
1077
+ --popover-foreground: 0.170 0.0032 175;
1078
+ --primary: 0.610 0.170 175; /* primary 500 — teal */
1079
+ --primary-foreground: 0.985 0.015 175;
1080
+ --secondary: 0.955 0.0032 175; /* neutral 100 */
1081
+ --secondary-foreground: 0.415 0.012 175; /* neutral 700 */
1082
+ --muted: 0.955 0.0032 175;
1083
+ --muted-foreground: 0.610 0.0136 175;
1084
+ --accent: 0.610 0.170 175;
1085
+ --accent-foreground: 0.985 0.015 175;
1086
+ --border: 0.895 0.006 175;
1087
+ --input: 0.895 0.006 175;
1088
+ --ring: 0.610 0.170 175;
1089
+ --radius: 0.5rem;
1090
+
1091
+ --font-sans: var(--font-geist), system-ui, sans-serif;
1092
+ --font-display: var(--font-geist), system-ui, sans-serif;
1093
+ }
1094
+
1095
+ .dark[data-grade-theme="energy"] {
1096
+ --background: 0.170 0.0032 175;
1097
+ --foreground: 0.985 0.0012 175;
1098
+ --card: 0.245 0.0064 175;
1099
+ --card-foreground: 0.985 0.0012 175;
1100
+ --popover: 0.245 0.0064 175;
1101
+ --popover-foreground: 0.985 0.0012 175;
1102
+ --primary: 0.720 0.140 175;
1103
+ --primary-foreground: 0.170 0.040 175;
1104
+ --secondary: 0.325 0.0096 175;
1105
+ --secondary-foreground: 0.895 0.006 175;
1106
+ --muted: 0.325 0.0096 175;
1107
+ --muted-foreground: 0.720 0.0112 175;
1108
+ --accent: 0.720 0.140 175;
1109
+ --accent-foreground: 0.170 0.040 175;
1110
+ --border: 0.325 0.0096 175;
1111
+ --input: 0.325 0.0096 175;
1112
+ --ring: 0.720 0.140 175;
1113
+ }
1114
+
1115
+ @layer base {
1116
+ * {
1117
+ @apply border-border;
1118
+ }
1119
+
1120
+ /* Smooth theme transitions */
1121
+ html {
1122
+ transition-property: background-color, border-color;
1123
+ transition-duration: 200ms;
1124
+ transition-timing-function: ease-in-out;
1125
+ }
1126
+
1127
+ body {
1128
+ @apply bg-background text-foreground;
1129
+ font-feature-settings: "rlig" 1, "calt" 1;
1130
+ font-size: var(--text-body);
1131
+ line-height: var(--text-body-line);
1132
+ /* Theme-driven width cut (variable-font wdth axis). Inherits into
1133
+ every span/component, so a theme set to e.g. 90% re-cuts ALL text;
1134
+ per-element font-stretch-[…] utilities still override. */
1135
+ font-stretch: var(--font-body-stretch, normal);
1136
+ }
1137
+
1138
+ /* Theme-driven heading font.
1139
+ Naked <h1>–<h4> elements pick up the active theme's display font
1140
+ (falls back to sans if the theme doesn't set one). Utility classes
1141
+ like .text-h1 still win if they set their own font, since classes
1142
+ outrank tag selectors. */
1143
+ h1, h2, h3, h4 {
1144
+ font-family: var(--font-display, var(--font-sans));
1145
+ font-weight: var(--font-heading-weight, 600);
1146
+ letter-spacing: var(--font-heading-tracking, -0.01em);
1147
+ font-stretch: var(--font-display-stretch, var(--font-body-stretch, normal));
1148
+ }
1149
+
1150
+ /* Link styles */
1151
+ a:not([class]) {
1152
+ @apply text-primary underline-offset-4 hover:underline;
1153
+ }
1154
+ }
1155
+
1156
+ /* ============================================
1157
+ APP SHELL GRID
1158
+ Five slots — Header, Nav, Aside, Main, Footer — placed via CSS-grid
1159
+ template areas keyed off [data-nav] on the shell root. Slot order in
1160
+ JSX doesn't matter; each slot has a fixed grid-area class assignment.
1161
+ ============================================ */
1162
+
1163
+ .gds-app-shell-header { grid-area: header; }
1164
+ .gds-app-shell-nav { grid-area: nav; }
1165
+ .gds-app-shell-aside { grid-area: aside; }
1166
+ .gds-app-shell-main { grid-area: main; }
1167
+ .gds-app-shell-footer { grid-area: footer; }
1168
+
1169
+ /* nav="none" — Header / Main / Footer stacked vertically. */
1170
+ .gds-app-shell[data-nav="none"] {
1171
+ grid-template-areas: "header" "main" "footer";
1172
+ grid-template-rows: auto 1fr auto;
1173
+ }
1174
+
1175
+ /* nav="top" — Header (site chrome), then in-app top nav, then Main. */
1176
+ .gds-app-shell[data-nav="top"] {
1177
+ grid-template-areas: "header" "nav" "main" "footer";
1178
+ grid-template-rows: auto auto 1fr auto;
1179
+ }
1180
+
1181
+ /* nav="side" — Header full width, Nav rail + Main below, Footer full width. */
1182
+ .gds-app-shell[data-nav="side"] {
1183
+ grid-template-areas:
1184
+ "header header"
1185
+ "nav main"
1186
+ "footer footer";
1187
+ grid-template-rows: auto 1fr auto;
1188
+ grid-template-columns: auto 1fr;
1189
+ }
1190
+
1191
+ /* nav="three-pane" — Header full width, Nav rail + fixed Aside + flex Main,
1192
+ Footer full width. Aside width is set by --gds-app-shell-aside (default
1193
+ 320px) so callers can override per-screen via inline style or a parent
1194
+ class without forking the component. */
1195
+ .gds-app-shell[data-nav="three-pane"] {
1196
+ grid-template-areas:
1197
+ "header header header"
1198
+ "nav aside main"
1199
+ "footer footer footer";
1200
+ grid-template-rows: auto 1fr auto;
1201
+ grid-template-columns: auto var(--gds-app-shell-aside, 320px) 1fr;
1202
+ }
1203
+
1204
+ /* ============================================
1205
+ THEME-DRIVEN COMPONENT SHAPES
1206
+ Keyed off data attributes that GradeThemeProvider sets on <html>.
1207
+ Each rule is an additive opt-in — themes that don't set a style get
1208
+ the component's default look.
1209
+ ============================================ */
1210
+
1211
+ /* Card styles */
1212
+ [data-card-style="outlined"] .gds-card {
1213
+ box-shadow: none;
1214
+ }
1215
+
1216
+ [data-card-style="flat"] .gds-card {
1217
+ box-shadow: none;
1218
+ border-color: transparent;
1219
+ background-color: oklch(var(--muted));
1220
+ }
1221
+
1222
+ [data-card-style="elevated"] .gds-card {
1223
+ box-shadow: var(--gds-shadow-lg);
1224
+ border-color: transparent;
1225
+ }
1226
+
1227
+ [data-card-style="glass"] .gds-card {
1228
+ background-color: oklch(var(--card) / 0.6);
1229
+ backdrop-filter: blur(12px);
1230
+ -webkit-backdrop-filter: blur(12px);
1231
+ border-color: oklch(var(--border) / 0.5);
1232
+ box-shadow: var(--gds-shadow-md);
1233
+ }
1234
+
1235
+ /* Button shapes — themes can set buttonShape: "pill" | "square" | "sharp".
1236
+ The Button component's radius comes from --radius (shadcn), so we override
1237
+ border-radius directly on the gds-button class when a shape is set. */
1238
+ [data-button-shape="pill"] .gds-button {
1239
+ border-radius: 9999px;
1240
+ }
1241
+
1242
+ [data-button-shape="square"] .gds-button,
1243
+ [data-button-shape="sharp"] .gds-button {
1244
+ border-radius: 0;
1245
+ }
1246
+
1247
+ /* ============================================
1248
+ BUTTON — RAISED VARIANT
1249
+ ============================================
1250
+ Tactile "physical key" treatment composed from elevation tokens.
1251
+ Tone is driven by `--btn-glow` (defaults to --selected-glow); the
1252
+ heat-inner / heat-outer layers in the elevation system already read
1253
+ from --btn-glow so per-button overrides flow through automatically.
1254
+
1255
+ Tabs and Toggle pick this up too via the same data-state / aria
1256
+ selectors below — TabsTrigger emits `data-state="active"`, Toggle
1257
+ emits `data-state="on"`, both listed. */
1258
+ .gds-button-raised {
1259
+ /* Cascade: per-element style override > --accent-glow (theme accent,
1260
+ defaults to --primary) > --selected-glow (system selection blue).
1261
+ Was hardcoded to --selected-glow before May 2026 — the user
1262
+ flagged "should pull from theme accent, not selection blue", which
1263
+ this fallback chain solves without breaking existing per-button
1264
+ style overrides like `style={{ "--btn-glow": "var(--warning)" }}`. */
1265
+ --btn-glow: var(--accent-glow, var(--selected-glow));
1266
+
1267
+ box-shadow: var(--elevation-3);
1268
+
1269
+ transition:
1270
+ box-shadow var(--gds-transition-base) var(--gds-ease-out),
1271
+ transform var(--gds-transition-fast) var(--gds-ease-out),
1272
+ background-color var(--gds-transition-base) var(--gds-ease-out);
1273
+ }
1274
+
1275
+ /* Surface for the STANDALONE raised look. June 2026: raised became a
1276
+ TRAIT (the `raised` prop on Button adds .gds-button-raised only, so
1277
+ elevation + glow compose with ANY colour variant — raised primary,
1278
+ raised outline). This companion class supplies the classic neutral
1279
+ "key" surface and is applied only by the variant="raised" alias,
1280
+ which is kept for back-compat. */
1281
+ .gds-button-raised-surface {
1282
+ background-color: oklch(var(--secondary));
1283
+ color: oklch(var(--secondary-foreground));
1284
+ }
1285
+
1286
+ .gds-button-raised:hover {
1287
+ box-shadow: var(--elevation-hot);
1288
+ }
1289
+
1290
+ .gds-button-raised:active {
1291
+ transform: translateY(1px);
1292
+ box-shadow: var(--elevation-pressed);
1293
+ }
1294
+
1295
+ .gds-button-raised[data-state="on"],
1296
+ .gds-button-raised[data-state="active"],
1297
+ .gds-button-raised[aria-pressed="true"],
1298
+ .gds-button-raised[data-selected="true"] {
1299
+ background-color: oklch(var(--selected) / 0.18);
1300
+ color: oklch(var(--foreground));
1301
+ box-shadow:
1302
+ var(--shadow-bevel-hi),
1303
+ inset 0 0 0 1px oklch(var(--selected) / 0.55),
1304
+ var(--shadow-heat-inner),
1305
+ 0 0 0 1px oklch(var(--selected) / 0.20),
1306
+ var(--shadow-heat-outer),
1307
+ var(--shadow-lift);
1308
+ }
1309
+
1310
+ .gds-button-raised:focus-visible {
1311
+ outline: none;
1312
+ box-shadow:
1313
+ var(--shadow-bevel-hi),
1314
+ var(--shadow-bevel-lo),
1315
+ 0 0 0 2px oklch(var(--background)),
1316
+ 0 0 0 4px oklch(var(--btn-glow) / 0.70),
1317
+ var(--shadow-lift);
1318
+ }
1319
+
1320
+ /* ============================================
1321
+ CODE CURSOR — blinking caret for `<Code>` typewriter / terminal demos
1322
+ ============================================
1323
+ Rendered as a 1ch-wide block at the tail of the active line. Uses
1324
+ currentColor so it inherits the surrounding token colour (which is
1325
+ `--gds-code-fg` by default but can be set per-line by the consumer).
1326
+ The 1.05s cycle is the canonical iOS/macOS caret blink rate. */
1327
+ @keyframes gds-code-caret-blink {
1328
+ 0%, 49% { opacity: 1; }
1329
+ 50%, 100% { opacity: 0; }
1330
+ }
1331
+ .gds-code-cursor {
1332
+ display: inline-block;
1333
+ width: 0.55em;
1334
+ height: 1.05em;
1335
+ vertical-align: text-bottom;
1336
+ margin-left: 0.05em;
1337
+ background-color: currentColor;
1338
+ border-radius: 1px;
1339
+ animation: gds-code-caret-blink 1.05s steps(1, end) infinite;
1340
+ }
1341
+ @media (prefers-reduced-motion: reduce) {
1342
+ .gds-code-cursor { animation: none; opacity: 1; }
1343
+ }
1344
+
1345
+ /* ============================================
1346
+ DEMO CURSOR — generic blinking caret used by lib/demo/ consumers
1347
+ ============================================
1348
+ The shared caret used wherever a gradeui component scripts typing
1349
+ (Code's terminal lines, Composer's inline cursor, future surfaces).
1350
+ Parameterised via CSS variables so each host can dial dimensions
1351
+ and colour without forking the class.
1352
+
1353
+ --gds-demo-cursor-color defaults to currentColor
1354
+ --gds-demo-cursor-width defaults to 0.55em
1355
+ --gds-demo-cursor-height defaults to 1.05em
1356
+ --gds-demo-cursor-radius defaults to 1px
1357
+
1358
+ Two variants — `inline` (default; thin caret for text) and `block`
1359
+ (square cell for terminal demos). The block variant swaps width to
1360
+ match height. */
1361
+ .gds-demo-cursor {
1362
+ display: inline-block;
1363
+ width: var(--gds-demo-cursor-width, 0.55em);
1364
+ height: var(--gds-demo-cursor-height, 1.05em);
1365
+ vertical-align: text-bottom;
1366
+ margin-left: 0.05em;
1367
+ background-color: var(--gds-demo-cursor-color, currentColor);
1368
+ border-radius: var(--gds-demo-cursor-radius, 1px);
1369
+ animation: gds-code-caret-blink 1.05s steps(1, end) infinite;
1370
+ }
1371
+ .gds-demo-cursor[data-gds-variant="block"] {
1372
+ width: var(--gds-demo-cursor-height, 1.05em);
1373
+ border-radius: 0;
1374
+ }
1375
+ @media (prefers-reduced-motion: reduce) {
1376
+ .gds-demo-cursor { animation: none; opacity: 1; }
1377
+ }
1378
+
1379
+ /* ============================================
1380
+ COMPOSER — token palette for <Composer>
1381
+ ============================================
1382
+ The generic text composition surface (chat / comments / post body).
1383
+ Tokens here drive the editor card, toolbar, mention pills, and
1384
+ attachment chips. Theme via consumer CSS to retune any surface
1385
+ without forking the component.
1386
+
1387
+ Two themes shipped inline: light + dark via .dark scoping. */
1388
+ :root {
1389
+ --gds-composer-bg: var(--background);
1390
+ --gds-composer-fg: var(--foreground);
1391
+ --gds-composer-muted-fg: var(--muted-foreground);
1392
+ --gds-composer-border: var(--border);
1393
+ --gds-composer-action-fg: var(--muted-foreground);
1394
+ --gds-composer-toolbar-fg: var(--muted-foreground);
1395
+ --gds-composer-toolbar-hover-bg: var(--muted);
1396
+ --gds-composer-toolbar-active-bg: var(--accent);
1397
+ --gds-composer-toolbar-active-fg: var(--accent-foreground);
1398
+ --gds-composer-chip-remove-bg: oklch(0.15 0 0);
1399
+ --gds-composer-chip-remove-fg: #fff;
1400
+ --gds-composer-mention-bg: oklch(var(--primary) / 0.12);
1401
+ --gds-composer-mention-fg: oklch(var(--primary));
1402
+ --gds-composer-mention-slash-bg: oklch(0.6 0.12 280 / 0.12);
1403
+ --gds-composer-mention-slash-fg: oklch(0.6 0.12 280);
1404
+ }
1405
+ .dark {
1406
+ --gds-composer-chip-remove-bg: #fff;
1407
+ --gds-composer-chip-remove-fg: oklch(0.15 0 0);
1408
+ }
1409
+
1410
+ /* Mention pill — applied via Lexical theme.beautifulMentions.
1411
+ Renders as an inline rounded chip with a subtle background and
1412
+ the trigger char + value. */
1413
+ .gds-composer-mention {
1414
+ display: inline-block;
1415
+ padding: 0 0.4em;
1416
+ margin: 0 0.05em;
1417
+ border-radius: 0.4em;
1418
+ background: var(--gds-composer-mention-bg);
1419
+ color: var(--gds-composer-mention-fg);
1420
+ font-weight: 500;
1421
+ cursor: default;
1422
+ }
1423
+ .gds-composer-mention-slash {
1424
+ background: var(--gds-composer-mention-slash-bg);
1425
+ color: var(--gds-composer-mention-slash-fg);
1426
+ }
1427
+
1428
+ /* Pullquote — styled variant of blockquote. The Lexical node
1429
+ carries a __pullquote sentinel; this rule applies whenever the
1430
+ blockquote DOM is tagged with data-gds-pullquote (set via a
1431
+ future custom node) OR carries the .gds-composer-pullquote class. */
1432
+ .gds-composer-pullquote,
1433
+ [data-gds-pullquote="true"] {
1434
+ border-left: 3px solid var(--primary);
1435
+ padding-left: 1rem;
1436
+ font-size: 1.125rem;
1437
+ font-style: normal;
1438
+ font-weight: 500;
1439
+ }
1440
+
1441
+ /* ============================================
1442
+ SURFACE CLASSES
1443
+ ============================================
1444
+ Apply one to any container. Composes with elevation classes —
1445
+ `<div class="gds-surface-glass shadow-elevation-4">` is a glass
1446
+ popover sitting at popover elevation, no conflict.
1447
+
1448
+ --card is theme-aware, so each theme automatically gets its own
1449
+ "frosted X" tint without per-theme overrides. */
1450
+ .gds-surface-solid {
1451
+ background-color: var(--surface-solid);
1452
+ }
1453
+
1454
+ .gds-surface-translucent {
1455
+ background-color: var(--surface-translucent);
1456
+ }
1457
+
1458
+ .gds-surface-glass {
1459
+ background-color: var(--surface-glass);
1460
+ backdrop-filter: blur(var(--surface-blur-glass));
1461
+ -webkit-backdrop-filter: blur(var(--surface-blur-glass));
1462
+ border: 1px solid oklch(var(--border) / 0.5);
1463
+ box-shadow: var(--surface-edge);
1464
+ }
1465
+
1466
+ .gds-surface-glass-strong {
1467
+ background-color: var(--surface-glass-strong);
1468
+ backdrop-filter: blur(var(--surface-blur-strong));
1469
+ -webkit-backdrop-filter: blur(var(--surface-blur-strong));
1470
+ border: 1px solid oklch(var(--border) / 0.4);
1471
+ box-shadow: var(--surface-edge);
1472
+ }
1473
+
1474
+ /* ============================================
1475
+ AURA — RING (pulsing outer halo)
1476
+ ============================================
1477
+ Apply `.gds-aura-ring` to any element. Animation duration & easing
1478
+ are CSS vars (`--aura-pulse-duration`, `--aura-pulse-ease`) — set
1479
+ them at the consumer level to retune without rewriting keyframes.
1480
+ Tone defaults to --aura-color (= --selected-glow); override per
1481
+ element for danger / success attention. */
1482
+ @keyframes gds-aura-pulse {
1483
+ 0%, 100% {
1484
+ box-shadow:
1485
+ 0 0 0 0 oklch(var(--aura-color) / var(--aura-ring-alpha-min)),
1486
+ 0 0 0 0 oklch(var(--aura-color) / 0);
1487
+ }
1488
+ 50% {
1489
+ box-shadow:
1490
+ 0 0 0 var(--aura-ring-spread) oklch(var(--aura-color) / var(--aura-ring-alpha-max)),
1491
+ 0 0 var(--aura-ring-blur) 0 oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.6));
1492
+ }
1493
+ }
1494
+
1495
+ .gds-aura-ring {
1496
+ animation: gds-aura-pulse var(--aura-pulse-duration) var(--aura-pulse-ease) infinite;
1497
+ }
1498
+
1499
+ /* ============================================
1500
+ AURA — GRADIENT (rotating conic border)
1501
+ ============================================
1502
+ Uses a ::before pseudo with a conic-gradient masked to just the
1503
+ border ring (via the standard mask-composite trick). The angle is
1504
+ animated via @property so the gradient rotates rather than redrawing.
1505
+ Requires position:relative on the host element; class adds it. */
1506
+ @property --aura-gradient-angle {
1507
+ syntax: "<angle>";
1508
+ initial-value: 0deg;
1509
+ inherits: false;
1510
+ }
1511
+
1512
+ @keyframes gds-aura-gradient-spin {
1513
+ to { --aura-gradient-angle: 360deg; }
1514
+ }
1515
+
1516
+ .gds-aura-gradient {
1517
+ position: relative;
1518
+ isolation: isolate;
1519
+ }
1520
+
1521
+ .gds-aura-gradient::before {
1522
+ content: "";
1523
+ position: absolute;
1524
+ inset: 0;
1525
+ border-radius: inherit;
1526
+ padding: var(--aura-gradient-thickness);
1527
+ background: conic-gradient(
1528
+ from var(--aura-gradient-angle),
1529
+ oklch(var(--aura-color) / 0) 0deg,
1530
+ oklch(var(--aura-color) / 0.9) 90deg,
1531
+ oklch(var(--aura-color) / 0) 180deg,
1532
+ oklch(var(--aura-color) / 0.9) 270deg,
1533
+ oklch(var(--aura-color) / 0) 360deg
1534
+ );
1535
+ /* mask trick: paint only the border ring, not the body */
1536
+ -webkit-mask:
1537
+ linear-gradient(#000 0 0) content-box,
1538
+ linear-gradient(#000 0 0);
1539
+ -webkit-mask-composite: xor;
1540
+ mask:
1541
+ linear-gradient(#000 0 0) content-box,
1542
+ linear-gradient(#000 0 0);
1543
+ mask-composite: exclude;
1544
+ pointer-events: none;
1545
+ animation: gds-aura-gradient-spin var(--aura-gradient-duration) var(--aura-gradient-ease) infinite;
1546
+ }
1547
+
1548
+ /* ============================================
1549
+ AURA — SHIMMER (diagonal sweep)
1550
+ ============================================
1551
+ Highlight sweeps across the element periodically. Lives on a
1552
+ ::after pseudo with `overflow: hidden` applied to the host so the
1553
+ sweep clips to the element's shape. Uses background-position rather
1554
+ than transform so it composites cheaply with the gradient + ring
1555
+ layers. */
1556
+ @keyframes gds-aura-shimmer-sweep {
1557
+ 0% { background-position: -150% 0; opacity: 0; }
1558
+ 8% { opacity: var(--aura-shimmer-alpha); }
1559
+ 60% { background-position: 250% 0; opacity: var(--aura-shimmer-alpha); }
1560
+ 62% { opacity: 0; }
1561
+ 100% { background-position: 250% 0; opacity: 0; }
1562
+ }
1563
+
1564
+ .gds-aura-shimmer {
1565
+ position: relative;
1566
+ overflow: hidden;
1567
+ isolation: isolate;
1568
+ }
1569
+
1570
+ .gds-aura-shimmer::after {
1571
+ content: "";
1572
+ position: absolute;
1573
+ inset: 0;
1574
+ border-radius: inherit;
1575
+ background-image: linear-gradient(
1576
+ 105deg,
1577
+ transparent 30%,
1578
+ oklch(var(--aura-color) / 1) 50%,
1579
+ transparent 70%
1580
+ );
1581
+ background-size: var(--aura-shimmer-width) 200%;
1582
+ background-repeat: no-repeat;
1583
+ pointer-events: none;
1584
+ animation: gds-aura-shimmer-sweep
1585
+ calc(var(--aura-shimmer-duration) + var(--aura-shimmer-delay-between))
1586
+ var(--aura-shimmer-ease) infinite;
1587
+ }
1588
+
1589
+ /* Reduce-motion: aura animations are decorative. Honor the user's
1590
+ pref by holding at the "peak" frame instead of animating. */
1591
+ @media (prefers-reduced-motion: reduce) {
1592
+ .gds-aura-ring,
1593
+ .gds-aura-gradient::before,
1594
+ .gds-aura-shimmer::after {
1595
+ animation: none;
1596
+ }
1597
+ .gds-aura-ring {
1598
+ box-shadow:
1599
+ 0 0 0 var(--aura-ring-spread) oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.6)),
1600
+ 0 0 var(--aura-ring-blur) 0 oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.4));
1601
+ }
1602
+ }
1603
+
1604
+ /* ============================================
1605
+ MOTION TOGGLE — data-motion="off" on <html>
1606
+ ============================================
1607
+ The manual motion switch (lib/motion). Mirrors the OS reduced-motion
1608
+ behaviour but driven by an attribute, so it can be flipped from a toolbar
1609
+ and posted into Studio's Fast Frame / embed iframes (grade:set-motion).
1610
+ Reduce-only: this never forces motion ON, it only suppresses it.
1611
+
1612
+ Broad reset first — neutralises animation/transition on arbitrary
1613
+ (generated-screen) content, the same shape the canonical
1614
+ prefers-reduced-motion reset uses. */
1615
+ [data-motion="off"] *,
1616
+ [data-motion="off"] *::before,
1617
+ [data-motion="off"] *::after {
1618
+ animation-duration: 0.01ms !important;
1619
+ animation-iteration-count: 1 !important;
1620
+ transition-duration: 0.01ms !important;
1621
+ scroll-behavior: auto !important;
1622
+ }
1623
+
1624
+ /* Aura keeps its bespoke static fallback (the broad reset can't supply the
1625
+ resting box-shadow). */
1626
+ [data-motion="off"] .gds-aura-ring {
1627
+ box-shadow:
1628
+ 0 0 0 var(--aura-ring-spread) oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.6)),
1629
+ 0 0 var(--aura-ring-blur) 0 oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.4));
1630
+ }
1631
+
1632
+ /* ============================================
1633
+ MEDIASURFACE FIDELITY — data-fidelity on any ancestor
1634
+ ============================================
1635
+ The pure-CSS half of MediaSurface's filled/wireframe model. The
1636
+ component keeps its tiered placeholder MOUNTED beneath a filled
1637
+ image and stamps `data-filled` on it; these rules decide what's
1638
+ visible. Flipping `data-fidelity="wireframe"` on ANY ancestor
1639
+ (Studio's iframe root, an embed wrapper, a docs demo) cross-fades
1640
+ imagery out and the placeholder back in — no React, so the toggle
1641
+ works in static embeds and respects the data-motion="off" reset
1642
+ above (transition collapses to 0.01ms).
1643
+
1644
+ Default (full fidelity): a filled slot hides its placeholder with
1645
+ `visibility: hidden` — NOT unmount — so transparent imagery (logos
1646
+ on alpha) never shows the glyph/`--gds-media-placeholder-bg`
1647
+ through its pixels, while the layer stays in the DOM for this very
1648
+ toggle. The `visibility` transition is delayed by the fade length
1649
+ (the standard fade-then-hide pattern) so the placeholder finishes
1650
+ fading before it stops hit-testing/painting. */
1651
+ [data-gds-part="media-surface-placeholder"][data-filled] {
1652
+ opacity: 0;
1653
+ visibility: hidden;
1654
+ transition:
1655
+ opacity var(--gds-media-fidelity-fade, 280ms) ease,
1656
+ visibility 0s linear var(--gds-media-fidelity-fade, 280ms);
1657
+ }
1658
+ [data-gds-part="media-surface-content"] {
1659
+ transition: opacity var(--gds-media-fidelity-fade, 280ms) ease;
1660
+ }
1661
+
1662
+ /* Wireframe: imagery fades out, placeholder fades back in. Content is
1663
+ opacity-faded (not display-toggled) so the swap reads as a cross-fade;
1664
+ pointer-events off so the invisible imagery can't intercept clicks. */
1665
+ [data-fidelity="wireframe"] [data-gds-part="media-surface-placeholder"][data-filled] {
1666
+ opacity: 1;
1667
+ visibility: visible;
1668
+ transition-delay: 0s, 0s;
1669
+ }
1670
+ [data-fidelity="wireframe"] [data-gds-part="media-surface-content"] {
1671
+ opacity: 0;
1672
+ pointer-events: none;
1673
+ }
1674
+
1675
+ /* ============================================
1676
+ MAP MARKER LIFT — every pin gets a border
1677
+ ============================================
1678
+ Map tiles are external imagery and don't follow the theme, so marker
1679
+ content (Badge price pins, numbered markers, avatars) can melt into
1680
+ them — illegible dark-on-dark pins were the symptom. Guarantee the
1681
+ lift at the DS level: every DIRECT child of a MapMarker's content
1682
+ wrapper gets a 1px border + ambient shadow from the mode-aware
1683
+ `--gds-map-marker-*` pair (light hairline on dark tiles, dark line
1684
+ on light). Being unlayered, this wins over a Badge's own
1685
+ border-transparent utility — deliberate: the border is the floor,
1686
+ not a suggestion. Backgrounds/text are NOT touched; content keeps
1687
+ its own surface tokens. */
1688
+ [data-gds-part="map-marker-content"] > * {
1689
+ border: 1px solid var(--gds-map-marker-border);
1690
+ box-shadow: var(--gds-map-marker-shadow);
1691
+ }
1692
+
1693
+ /* Vendor font reset — Leaflet (`.leaflet-container`) and MapLibre
1694
+ (`.maplibregl-map`) both hard-set `font-family` on their map
1695
+ containers, which beats inheritance and strands marker content on
1696
+ the vendor stack ("pins don't carry the custom font" — they can,
1697
+ the vendor CSS was just in the way). Re-assert the theme stack at
1698
+ the marker-content boundary so Badges/Cards inside pins type-match
1699
+ the rest of the screen, including custom uploaded faces riding
1700
+ `--font-sans`. */
1701
+ [data-gds-part="map-marker-content"] {
1702
+ font-family: var(--font-sans);
1703
+ }
1704
+
1705
+ /* ============================================
1706
+ STREAM-IN — data-gds-streaming on <html>
1707
+ ============================================
1708
+ Live-draw treatment for streaming renders: while the attribute is
1709
+ stamped on <html>, every element ENTERING the DOM fades + rises in.
1710
+
1711
+ CURRENTLY DORMANT — the Fast Frame sandbox no longer stamps the
1712
+ attribute. Speculative drafts moved to double-buffered full
1713
+ remounts (every compile re-inserts EVERY element, so this rule made
1714
+ the whole frame pulse on each draft tick rather than easing in just
1715
+ the new content). Kept because the treatment is right for a future
1716
+ diff-aware renderer that only inserts the appended elements.
1717
+
1718
+ While stamped, every element ENTERING the DOM fades + rises in.
1719
+ Implemented with @starting-style + transition rather than a
1720
+ keyframe animation on purpose:
1721
+ - `@starting-style` only fires on first render of an element, so
1722
+ React-preserved nodes don't re-animate on every draft compile —
1723
+ only the newly streamed-in elements move.
1724
+ - It composes via `transition`, leaving each component's own
1725
+ `animation` (pulse, spin, aura) untouched. The universal
1726
+ transition does temporarily override component transitions while
1727
+ streaming, which is acceptable: the screen is read-only while it
1728
+ draws.
1729
+ Tunable via the --gds-stream-in-* tokens. Respects both the OS
1730
+ reduced-motion preference and the manual data-motion="off" toggle
1731
+ (the broad reset above zeroes transition-duration). */
1732
+ :root {
1733
+ --gds-stream-in-duration: 240ms;
1734
+ --gds-stream-in-ease: cubic-bezier(0.22, 1, 0.36, 1);
1735
+ --gds-stream-in-rise: 6px;
1736
+ }
1737
+
1738
+ html[data-gds-streaming] body * {
1739
+ transition:
1740
+ opacity var(--gds-stream-in-duration) var(--gds-stream-in-ease),
1741
+ translate var(--gds-stream-in-duration) var(--gds-stream-in-ease);
1742
+ }
1743
+
1744
+ html[data-gds-streaming] body * {
1745
+ @starting-style {
1746
+ opacity: 0;
1747
+ translate: 0 var(--gds-stream-in-rise);
1748
+ }
1749
+ }
1750
+
1751
+ @media (prefers-reduced-motion: reduce) {
1752
+ html[data-gds-streaming] body * {
1753
+ transition: none;
1754
+ }
1755
+ }
1756
+
1757
+ /* ============================================
1758
+ EDIT FLASH — data-gds-flash on changed nodes
1759
+ ============================================
1760
+ "Show me what the AI touched": when an edit turn (or any sealed
1761
+ re-render) commits, the Fast Frame sandbox diffs the old and new
1762
+ frames by data-gds-source-id and stamps `data-gds-flash` on the
1763
+ innermost changed nodes. One short pulse — outline ring + a faint
1764
+ primary wash — then the attribute is removed on animationend so the
1765
+ next edit re-triggers cleanly. Tunable via the --gds-edit-flash-*
1766
+ tokens. Honours reduced motion + the manual data-motion="off" toggle
1767
+ (the broad animation reset above zeroes it). */
1768
+ :root {
1769
+ --gds-edit-flash-duration: 900ms;
1770
+ --gds-edit-flash-color: var(--primary);
1771
+ }
1772
+
1773
+ [data-gds-flash] {
1774
+ animation: gds-edit-flash var(--gds-edit-flash-duration) ease-out;
1775
+ }
1776
+
1777
+ @keyframes gds-edit-flash {
1778
+ 0% {
1779
+ outline: 2px solid oklch(var(--gds-edit-flash-color) / 0.9);
1780
+ outline-offset: 2px;
1781
+ background-color: oklch(var(--gds-edit-flash-color) / 0.1);
1782
+ }
1783
+ 60% {
1784
+ outline: 2px solid oklch(var(--gds-edit-flash-color) / 0.45);
1785
+ outline-offset: 2px;
1786
+ background-color: oklch(var(--gds-edit-flash-color) / 0.04);
1787
+ }
1788
+ 100% {
1789
+ outline: 2px solid transparent;
1790
+ outline-offset: 2px;
1791
+ background-color: transparent;
1792
+ }
1793
+ }
1794
+
1795
+ @media (prefers-reduced-motion: reduce) {
1796
+ [data-gds-flash] {
1797
+ animation: none;
1798
+ }
1799
+ }
1800
+
1801
+ /* ============================================
1802
+ TYPOGRAPHY UTILITIES
1803
+ ============================================ */
1804
+
1805
+ @layer utilities {
1806
+ /* Display */
1807
+ .text-display {
1808
+ font-size: var(--text-display);
1809
+ line-height: var(--text-display-line);
1810
+ letter-spacing: var(--text-display-tracking);
1811
+ font-weight: 700;
1812
+ }
1813
+
1814
+ /* Headings */
1815
+ .text-h1 {
1816
+ font-size: var(--text-h1);
1817
+ line-height: var(--text-h1-line);
1818
+ letter-spacing: var(--text-h1-tracking);
1819
+ font-weight: 700;
1820
+ }
1821
+
1822
+ .text-h2 {
1823
+ font-size: var(--text-h2);
1824
+ line-height: var(--text-h2-line);
1825
+ letter-spacing: var(--text-h2-tracking);
1826
+ font-weight: 600;
1827
+ }
1828
+
1829
+ .text-h3 {
1830
+ font-size: var(--text-h3);
1831
+ line-height: var(--text-h3-line);
1832
+ letter-spacing: var(--text-h3-tracking);
1833
+ font-weight: 600;
1834
+ }
1835
+
1836
+ .text-h4 {
1837
+ font-size: var(--text-h4);
1838
+ line-height: var(--text-h4-line);
1839
+ letter-spacing: var(--text-h4-tracking);
1840
+ font-weight: 600;
1841
+ }
1842
+
1843
+ .text-h5 {
1844
+ font-size: var(--text-h5);
1845
+ line-height: var(--text-h5-line);
1846
+ letter-spacing: var(--text-h5-tracking);
1847
+ font-weight: 500;
1848
+ }
1849
+
1850
+ .text-h6 {
1851
+ font-size: var(--text-h6);
1852
+ line-height: var(--text-h6-line);
1853
+ letter-spacing: var(--text-h6-tracking);
1854
+ font-weight: 500;
1855
+ }
1856
+
1857
+ /* Body */
1858
+ .text-body-lg {
1859
+ font-size: var(--text-body-lg);
1860
+ line-height: var(--text-body-line);
1861
+ }
1862
+
1863
+ .text-body {
1864
+ font-size: var(--text-body);
1865
+ line-height: var(--text-body-line);
1866
+ }
1867
+
1868
+ .text-body-sm {
1869
+ font-size: var(--text-body-sm);
1870
+ line-height: var(--text-body-line);
1871
+ }
1872
+
1873
+ /* Label */
1874
+ .text-label-lg {
1875
+ font-size: var(--text-label-lg);
1876
+ line-height: var(--text-label-line);
1877
+ letter-spacing: var(--text-label-tracking);
1878
+ font-weight: 500;
1879
+ }
1880
+
1881
+ .text-label {
1882
+ font-size: var(--text-label);
1883
+ line-height: var(--text-label-line);
1884
+ letter-spacing: var(--text-label-tracking);
1885
+ font-weight: 500;
1886
+ }
1887
+
1888
+ /* Caption */
1889
+ .text-caption {
1890
+ font-size: var(--text-caption);
1891
+ line-height: var(--text-caption-line);
1892
+ color: oklch(var(--muted-foreground));
1893
+ }
1894
+
1895
+ /* Overline */
1896
+ .text-overline {
1897
+ font-size: var(--text-overline);
1898
+ letter-spacing: var(--text-overline-tracking);
1899
+ text-transform: uppercase;
1900
+ font-weight: 600;
1901
+ }
1902
+
1903
+ /* Code */
1904
+ .text-code {
1905
+ font-family: var(--font-mono);
1906
+ font-size: 0.875em;
1907
+ background-color: oklch(var(--muted));
1908
+ padding: 0.125rem 0.375rem;
1909
+ border-radius: 0.25rem;
1910
+ }
1911
+
1912
+ /* Lead paragraph */
1913
+ .text-lead {
1914
+ font-size: var(--text-body-lg);
1915
+ line-height: var(--text-body-line);
1916
+ color: oklch(var(--muted-foreground));
1917
+ }
1918
+
1919
+ /* Muted text */
1920
+ .text-muted {
1921
+ color: oklch(var(--muted-foreground));
1922
+ }
1923
+ }
1924
+
1925
+ /* ============================================
1926
+ ANIMATION UTILITIES
1927
+ ============================================ */
1928
+
1929
+ @layer utilities {
1930
+ /* Hide scrollbar */
1931
+ .scrollbar-none {
1932
+ -ms-overflow-style: none; /* IE and Edge */
1933
+ scrollbar-width: none; /* Firefox */
1934
+ }
1935
+ .scrollbar-none::-webkit-scrollbar {
1936
+ display: none; /* Chrome, Safari, Opera */
1937
+ }
1938
+
1939
+ /* Fade animations */
1940
+ .animate-fade-in {
1941
+ animation: fadeIn 0.2s ease-out;
1942
+ }
1943
+
1944
+ .animate-fade-out {
1945
+ animation: fadeOut 0.2s ease-in;
1946
+ }
1947
+
1948
+ /* Scale animations */
1949
+ .animate-scale-in {
1950
+ animation: scaleIn 0.15s ease-out;
1951
+ }
1952
+
1953
+ /* Slide animations */
1954
+ .animate-slide-in-from-top {
1955
+ animation: slideInFromTop 0.2s ease-out;
1956
+ }
1957
+
1958
+ .animate-slide-in-from-bottom {
1959
+ animation: slideInFromBottom 0.2s ease-out;
1960
+ }
1961
+
1962
+ .animate-slide-in-from-left {
1963
+ animation: slideInFromLeft 0.2s ease-out;
1964
+ }
1965
+
1966
+ .animate-slide-in-from-right {
1967
+ animation: slideInFromRight 0.2s ease-out;
1968
+ }
1969
+
1970
+ /* Pulse animation for energy indicators */
1971
+ .animate-energy-pulse {
1972
+ animation: energyPulse 2s ease-in-out infinite;
1973
+ }
1974
+
1975
+ /* Shimmer effect for loading states */
1976
+ .animate-shimmer {
1977
+ animation: shimmer 2s linear infinite;
1978
+ background: linear-gradient(
1979
+ 90deg,
1980
+ oklch(var(--muted)) 0%,
1981
+ oklch(var(--muted-foreground) / 0.1) 50%,
1982
+ oklch(var(--muted)) 100%
1983
+ );
1984
+ background-size: 200% 100%;
1985
+ }
1986
+ }
1987
+
1988
+ /* Keyframes */
1989
+ @keyframes fadeIn {
1990
+ from { opacity: 0; }
1991
+ to { opacity: 1; }
1992
+ }
1993
+
1994
+ @keyframes fadeOut {
1995
+ from { opacity: 1; }
1996
+ to { opacity: 0; }
1997
+ }
1998
+
1999
+ @keyframes scaleIn {
2000
+ from {
2001
+ opacity: 0;
2002
+ transform: scale(0.95);
2003
+ }
2004
+ to {
2005
+ opacity: 1;
2006
+ transform: scale(1);
2007
+ }
2008
+ }
2009
+
2010
+ @keyframes slideInFromTop {
2011
+ from {
2012
+ opacity: 0;
2013
+ transform: translateY(-10px);
2014
+ }
2015
+ to {
2016
+ opacity: 1;
2017
+ transform: translateY(0);
2018
+ }
2019
+ }
2020
+
2021
+ @keyframes slideInFromBottom {
2022
+ from {
2023
+ opacity: 0;
2024
+ transform: translateY(10px);
2025
+ }
2026
+ to {
2027
+ opacity: 1;
2028
+ transform: translateY(0);
2029
+ }
2030
+ }
2031
+
2032
+ @keyframes slideInFromLeft {
2033
+ from {
2034
+ opacity: 0;
2035
+ transform: translateX(-10px);
2036
+ }
2037
+ to {
2038
+ opacity: 1;
2039
+ transform: translateX(0);
2040
+ }
2041
+ }
2042
+
2043
+ @keyframes slideInFromRight {
2044
+ from {
2045
+ opacity: 0;
2046
+ transform: translateX(10px);
2047
+ }
2048
+ to {
2049
+ opacity: 1;
2050
+ transform: translateX(0);
2051
+ }
2052
+ }
2053
+
2054
+ @keyframes energyPulse {
2055
+ 0%, 100% {
2056
+ opacity: 1;
2057
+ box-shadow: 0 0 0 0 oklch(var(--accent) / 0.4);
2058
+ }
2059
+ 50% {
2060
+ opacity: 0.8;
2061
+ box-shadow: 0 0 0 8px oklch(var(--accent) / 0);
2062
+ }
2063
+ }
2064
+
2065
+ @keyframes shimmer {
2066
+ 0% { background-position: 200% 0; }
2067
+ 100% { background-position: -200% 0; }
2068
+ }
2069
+
2070
+ @keyframes pulse-glow {
2071
+ 0%, 100% {
2072
+ box-shadow: 0 0 4px 1px oklch(var(--primary) / 0.2);
2073
+ }
2074
+ 50% {
2075
+ box-shadow: 0 0 8px 2px oklch(var(--primary) / 0.35);
2076
+ }
2077
+ }
2078
+
2079
+ .animate-pulse-glow {
2080
+ animation: pulse-glow 2s ease-in-out infinite;
2081
+ }
2082
+
2083
+ /* CSS Houdini property for animating conic-gradient angle */
2084
+ @property --angle {
2085
+ syntax: '<angle>';
2086
+ initial-value: 0deg;
2087
+ inherits: false;
2088
+ }
2089
+
2090
+ @keyframes rotate-border {
2091
+ 0% {
2092
+ --angle: 0deg;
2093
+ }
2094
+ 100% {
2095
+ --angle: 360deg;
2096
+ }
2097
+ }
2098
+
2099
+ /* Border travel animation for energy flow nodes */
2100
+ .border-travel-animation {
2101
+ position: relative;
2102
+ border: 2px solid transparent;
2103
+ background:
2104
+ linear-gradient(white, white) padding-box,
2105
+ conic-gradient(
2106
+ from var(--angle, 0deg),
2107
+ rgba(34, 197, 94, 0.15) 0deg,
2108
+ rgba(34, 197, 94, 0.15) 350deg,
2109
+ #22c55e 353deg,
2110
+ #16a34a 356deg,
2111
+ #22c55e 359deg,
2112
+ rgba(34, 197, 94, 0.15) 360deg
2113
+ ) border-box;
2114
+ animation: rotate-border 3s linear infinite;
2115
+ border-radius: 0.75rem;
2116
+ }
2117
+
2118
+ .border-travel-animation.rounded-full {
2119
+ border-radius: 9999px;
2120
+ }
2121
+
2122
+ .dark .border-travel-animation {
2123
+ background:
2124
+ linear-gradient(#141414, #141414) padding-box,
2125
+ conic-gradient(
2126
+ from var(--angle, 0deg),
2127
+ rgba(34, 197, 94, 0.1) 0deg,
2128
+ rgba(34, 197, 94, 0.1) 350deg,
2129
+ #22c55e 353deg,
2130
+ #16a34a 356deg,
2131
+ #22c55e 359deg,
2132
+ rgba(34, 197, 94, 0.1) 360deg
2133
+ ) border-box;
2134
+ }
2135
+
2136
+ /* React Flow controls positioning */
2137
+ .react-flow__controls {
2138
+ bottom: 10px !important;
2139
+ top: auto !important;
2140
+ left: 10px !important;
2141
+ }
2142
+
2143
+ /* React Flow dark mode styles */
2144
+ .dark .react-flow__controls {
2145
+ background: #141414;
2146
+ border: 1px solid #262626;
2147
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
2148
+ }
2149
+
2150
+ .dark .react-flow__controls-button {
2151
+ background: #141414;
2152
+ border-bottom: 1px solid #262626;
2153
+ fill: #a3a3a3;
2154
+ }
2155
+
2156
+ .dark .react-flow__controls-button:hover {
2157
+ background: #262626;
2158
+ }
2159
+
2160
+ .dark .react-flow__controls-button svg {
2161
+ fill: #a3a3a3;
2162
+ }
2163
+
2164
+ .dark .react-flow__controls-button:hover svg {
2165
+ fill: #ffffff;
2166
+ }
2167
+
2168
+ /* Dotted background pattern */
2169
+ .bg-dot-pattern {
2170
+ background-image: radial-gradient(circle, rgba(0, 0, 0, 0.08) 1px, transparent 1px);
2171
+ background-size: 24px 24px;
2172
+ }
2173
+
2174
+ .dark .bg-dot-pattern {
2175
+ background-image: radial-gradient(circle, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
2176
+ }
2177
+
2178
+ /* Gradient fade for hero */
2179
+ .hero-gradient {
2180
+ background: linear-gradient(to bottom, transparent 0%, oklch(var(--background)) 70%, oklch(var(--background)) 100%);
2181
+ }
2182
+
2183
+ /* ============================================
2184
+ INFINITE SCROLL ANIMATION (Partner Logos)
2185
+ ============================================ */
2186
+
2187
+ @keyframes scrollLeft {
2188
+ 0% {
2189
+ transform: translateX(0);
2190
+ }
2191
+ 100% {
2192
+ transform: translateX(-50%);
2193
+ }
2194
+ }
2195
+
2196
+ @keyframes scrollRight {
2197
+ 0% {
2198
+ transform: translateX(-50%);
2199
+ }
2200
+ 100% {
2201
+ transform: translateX(0);
2202
+ }
2203
+ }
2204
+
2205
+ .animate-scroll-left {
2206
+ animation: scrollLeft linear infinite;
2207
+ }
2208
+
2209
+ .animate-scroll-right {
2210
+ animation: scrollRight linear infinite;
2211
+ }
2212
+
2213
+ /* Pause animation on hover */
2214
+ .animate-scroll-left:hover,
2215
+ .animate-scroll-right:hover {
2216
+ animation-play-state: paused;
2217
+ }
2218
+
2219
+ /* ─────────────────────────────────────────────────────────────────────────
2220
+ Selection cards — RadioCard / CheckboxCard / SwitchCard
2221
+ ---------------------------------------------------------------------------
2222
+ The whole card is the control, so focus / hover / checked all live on the
2223
+ parent surface. Every visual reads from a `--gds-selection-card-*` token
2224
+ with a semantic fallback, so a project can re-skin the cards via the
2225
+ per-project override layer (set the token at theme scope) without touching
2226
+ the component. Per-instance overrides still flow through `style={{ … }}`.
2227
+ ───────────────────────────────────────────────────────────────────────── */
2228
+ .gds-selection-card {
2229
+ position: relative;
2230
+ display: flex;
2231
+ align-items: flex-start;
2232
+ gap: var(--gds-selection-card-gap, 0.75rem);
2233
+ width: 100%;
2234
+ text-align: left;
2235
+ cursor: pointer;
2236
+ padding: var(--gds-selection-card-padding, 1rem);
2237
+ border-radius: var(--gds-selection-card-radius, var(--radius));
2238
+ border: var(--gds-selection-card-border-width, 1px) solid
2239
+ oklch(var(--gds-selection-card-border, var(--border)));
2240
+ background: oklch(var(--gds-selection-card-bg, var(--card)));
2241
+ color: oklch(var(--foreground));
2242
+ transition:
2243
+ border-color 150ms ease,
2244
+ background-color 150ms ease,
2245
+ box-shadow 150ms ease;
2246
+ }
2247
+ .gds-selection-card:hover {
2248
+ background: var(--gds-selection-card-hover-bg, oklch(var(--muted) / 0.55));
2249
+ }
2250
+ .gds-selection-card:focus-visible {
2251
+ outline: none;
2252
+ box-shadow:
2253
+ 0 0 0 2px oklch(var(--background)),
2254
+ 0 0 0 4px oklch(var(--gds-selection-card-ring, var(--ring)) / 0.85);
2255
+ }
2256
+ .gds-selection-card[data-state="checked"] {
2257
+ border-color: oklch(
2258
+ var(--gds-selection-card-selected-border, var(--foreground))
2259
+ );
2260
+ background: var(
2261
+ --gds-selection-card-selected-bg,
2262
+ oklch(var(--muted) / 0.4)
2263
+ );
2264
+ }
2265
+ .gds-selection-card:disabled,
2266
+ .gds-selection-card[data-disabled] {
2267
+ cursor: not-allowed;
2268
+ opacity: 0.5;
2269
+ }
2270
+
2271
+ /* Content blocks */
2272
+ .gds-selection-card__content {
2273
+ display: flex;
2274
+ flex-direction: column;
2275
+ gap: 0.125rem;
2276
+ flex: 1 1 auto;
2277
+ min-width: 0;
2278
+ }
2279
+ .gds-selection-card__slot {
2280
+ flex: 1 1 auto;
2281
+ min-width: 0;
2282
+ }
2283
+ .gds-selection-card__title {
2284
+ font-size: 0.875rem;
2285
+ font-weight: 500;
2286
+ line-height: 1.4;
2287
+ color: oklch(var(--foreground));
2288
+ }
2289
+ .gds-selection-card__desc {
2290
+ font-size: 0.875rem;
2291
+ line-height: 1.4;
2292
+ color: oklch(var(--muted-foreground));
2293
+ }
2294
+
2295
+ /* ── Indicators (the small glyph; differs by control type) ─────────────── */
2296
+ .gds-selection-indicator {
2297
+ display: inline-flex;
2298
+ align-items: center;
2299
+ justify-content: center;
2300
+ flex: none;
2301
+ margin-top: 0.0625rem;
2302
+ }
2303
+
2304
+ /* Radio — empty ring → filled circle with a light inner dot when checked */
2305
+ .gds-selection-indicator--radio {
2306
+ height: 1.25rem;
2307
+ width: 1.25rem;
2308
+ border-radius: 9999px;
2309
+ border: 1.5px solid oklch(var(--gds-selection-card-control, var(--border)));
2310
+ background: oklch(var(--background));
2311
+ transition:
2312
+ border-color 150ms ease,
2313
+ background-color 150ms ease;
2314
+ }
2315
+ .gds-selection-card[data-state="checked"] .gds-selection-indicator--radio {
2316
+ border-color: oklch(var(--gds-selection-card-control-checked, var(--primary)));
2317
+ background: oklch(var(--gds-selection-card-control-checked, var(--primary)));
2318
+ }
2319
+ .gds-selection-indicator__dot {
2320
+ display: block;
2321
+ height: 0.5rem;
2322
+ width: 0.5rem;
2323
+ border-radius: 9999px;
2324
+ background: oklch(var(--background));
2325
+ }
2326
+
2327
+ /* Checkbox — rounded square, check glyph when checked */
2328
+ .gds-selection-indicator--checkbox {
2329
+ height: 1.2rem;
2330
+ width: 1.2rem;
2331
+ border-radius: 0.3rem;
2332
+ border: 1.5px solid oklch(var(--gds-selection-card-control, var(--border)));
2333
+ background: oklch(var(--background));
2334
+ color: oklch(var(--background));
2335
+ transition:
2336
+ border-color 150ms ease,
2337
+ background-color 150ms ease;
2338
+ }
2339
+ .gds-selection-card[data-state="checked"] .gds-selection-indicator--checkbox {
2340
+ border-color: oklch(var(--gds-selection-card-control-checked, var(--primary)));
2341
+ background: oklch(var(--gds-selection-card-control-checked, var(--primary)));
2342
+ }
2343
+ .gds-selection-indicator__check {
2344
+ display: inline-flex;
2345
+ align-items: center;
2346
+ justify-content: center;
2347
+ color: oklch(var(--background));
2348
+ }
2349
+
2350
+ /* Switch — track + thumb that slides on checked */
2351
+ .gds-selection-indicator--switch {
2352
+ height: 1.25rem;
2353
+ width: 2.25rem;
2354
+ padding: 2px;
2355
+ border-radius: 9999px;
2356
+ background: oklch(var(--input));
2357
+ justify-content: flex-start;
2358
+ transition: background-color 150ms ease;
2359
+ }
2360
+ .gds-selection-card[data-state="checked"] .gds-selection-indicator--switch {
2361
+ background: oklch(var(--gds-selection-card-control-checked, var(--primary)));
2362
+ }
2363
+ .gds-selection-indicator__thumb {
2364
+ display: block;
2365
+ height: 1rem;
2366
+ width: 1rem;
2367
+ border-radius: 9999px;
2368
+ background: oklch(var(--background));
2369
+ box-shadow: 0 1px 2px oklch(0 0 0 / 0.25);
2370
+ transition: transform 150ms ease;
2371
+ }
2372
+ .gds-selection-card[data-state="checked"] .gds-selection-indicator__thumb {
2373
+ transform: translateX(1rem);
2374
+ }
2375
+
2376
+ /* ─────────────────────────────────────────────────────────────────────────
2377
+ Elevation — real classes (JIT-proof, always in the bundle).
2378
+ ---------------------------------------------------------------------------
2379
+ The `shadow-elevation-N` Tailwind utilities (declared in tailwind-preset.ts)
2380
+ only compile when a scanned source file actually uses the class string, so
2381
+ typing `shadow-elevation-3` in Studio output or a consumer screen produced
2382
+ no CSS. These plain classes always exist in the shipped stylesheet, exactly
2383
+ like `.gds-surface-*`, so elevation works everywhere regardless of the JIT
2384
+ content scan. Prefer these for runtime/generated UI; the Tailwind utilities
2385
+ remain available for component-internal use.
2386
+ ───────────────────────────────────────────────────────────────────────── */
2387
+ .gds-elevation-0 { box-shadow: var(--elevation-0); }
2388
+ .gds-elevation-1 { box-shadow: var(--elevation-1); }
2389
+ .gds-elevation-2 { box-shadow: var(--elevation-2); }
2390
+ .gds-elevation-3 { box-shadow: var(--elevation-3); }
2391
+ .gds-elevation-4 { box-shadow: var(--elevation-4); }
2392
+ .gds-elevation-5 { box-shadow: var(--elevation-5); }
2393
+ .gds-elevation-hot { box-shadow: var(--elevation-hot); }
2394
+ .gds-elevation-pressed { box-shadow: var(--elevation-pressed); }
2395
+ /* Single-layer atoms, for composing a custom stack */
2396
+ .gds-shadow-bevel-hi { box-shadow: var(--shadow-bevel-hi); }
2397
+ .gds-shadow-bevel-lo { box-shadow: var(--shadow-bevel-lo); }
2398
+ .gds-shadow-contact { box-shadow: var(--shadow-contact); }
2399
+ .gds-shadow-lift { box-shadow: var(--shadow-lift); }
2400
+ .gds-shadow-lift-deep { box-shadow: var(--shadow-lift-deep); }
2401
+ .gds-shadow-heat-inner { box-shadow: var(--shadow-heat-inner); }
2402
+ .gds-shadow-heat-outer { box-shadow: var(--shadow-heat-outer); }