@gratiaos/ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +243 -0
  2. package/README.md +170 -0
  3. package/dist/hooks/index.d.ts +2 -0
  4. package/dist/hooks/index.d.ts.map +1 -0
  5. package/dist/hooks/index.js +2 -0
  6. package/dist/hooks/index.js.map +1 -0
  7. package/dist/hooks/useMissingScrew.d.ts +40 -0
  8. package/dist/hooks/useMissingScrew.d.ts.map +1 -0
  9. package/dist/hooks/useMissingScrew.js +76 -0
  10. package/dist/hooks/useMissingScrew.js.map +1 -0
  11. package/dist/index.d.ts +9 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +9 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/pad/Button.d.ts +10 -0
  16. package/dist/pad/Button.d.ts.map +1 -0
  17. package/dist/pad/Button.js +7 -0
  18. package/dist/pad/Button.js.map +1 -0
  19. package/dist/pad/Card.d.ts +8 -0
  20. package/dist/pad/Card.d.ts.map +1 -0
  21. package/dist/pad/Card.js +3 -0
  22. package/dist/pad/Card.js.map +1 -0
  23. package/dist/primitives/badge.d.ts +66 -0
  24. package/dist/primitives/badge.d.ts.map +1 -0
  25. package/dist/primitives/badge.js +23 -0
  26. package/dist/primitives/badge.js.map +1 -0
  27. package/dist/primitives/button.d.ts +60 -0
  28. package/dist/primitives/button.d.ts.map +1 -0
  29. package/dist/primitives/button.js +39 -0
  30. package/dist/primitives/button.js.map +1 -0
  31. package/dist/primitives/card.d.ts +54 -0
  32. package/dist/primitives/card.d.ts.map +1 -0
  33. package/dist/primitives/card.js +11 -0
  34. package/dist/primitives/card.js.map +1 -0
  35. package/dist/primitives/field.d.ts +107 -0
  36. package/dist/primitives/field.d.ts.map +1 -0
  37. package/dist/primitives/field.js +154 -0
  38. package/dist/primitives/field.js.map +1 -0
  39. package/dist/primitives/pill.d.ts +59 -0
  40. package/dist/primitives/pill.d.ts.map +1 -0
  41. package/dist/primitives/pill.js +26 -0
  42. package/dist/primitives/pill.js.map +1 -0
  43. package/dist/primitives/slot.d.ts +7 -0
  44. package/dist/primitives/slot.d.ts.map +1 -0
  45. package/dist/primitives/slot.js +9 -0
  46. package/dist/primitives/slot.js.map +1 -0
  47. package/dist/primitives/toast.d.ts +125 -0
  48. package/dist/primitives/toast.d.ts.map +1 -0
  49. package/dist/primitives/toast.js +462 -0
  50. package/dist/primitives/toast.js.map +1 -0
  51. package/package.json +87 -0
  52. package/styles/badge.css +175 -0
  53. package/styles/base.css +7 -0
  54. package/styles/button.css +257 -0
  55. package/styles/card.css +195 -0
  56. package/styles/field.css +195 -0
  57. package/styles/pad.css +140 -0
  58. package/styles/pill.css +167 -0
  59. package/styles/theme.css +492 -0
  60. package/styles/toast.css +286 -0
@@ -0,0 +1,492 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ Garden Core — Theme (gratia)
3
+ Purpose: semantic tokens + depth/motion for Pad & scenes
4
+ Whisper: “rest is repair; motion is gentle.”
5
+ ──────────────────────────────────────────────────────────── */
6
+ @import 'tailwindcss';
7
+
8
+ @theme {
9
+ /* === Type & scale === */
10
+ --font-sans: 'Inter Variable', Inter, ui-sans-serif, system-ui, sans-serif;
11
+ --text-sm: 0.875rem;
12
+ --text-base: 1rem;
13
+ --text-lg: 1.125rem;
14
+ --text-xs: 0.75rem; /* 12px */
15
+ --text-2xs: 0.6875rem; /* 11px — micro labels (Badge, meta) */
16
+
17
+ /* === Global semantics (light defaults) === */
18
+ --color-surface: oklch(97% 0.01 180);
19
+ --color-elev: oklch(96% 0.005 180);
20
+ --color-text: oklch(27% 0.02 275);
21
+
22
+ /* Accent & on-accent */
23
+ --color-accent: oklch(62% 0.09 150);
24
+ --color-on-accent: oklch(15% 0.01 180);
25
+
26
+ /* === Tone scale (semantic feedback colors) === */
27
+ --color-positive: oklch(62% 0.09 150); /* leaf / growth */
28
+ --color-warning: oklch(80% 0.14 80); /* amber / alert */
29
+ --color-danger: oklch(60% 0.18 27); /* red / stop */
30
+
31
+ /* Borders */
32
+ --color-border: oklch(90% 0.02 155);
33
+
34
+ /* Text subtleties — expose as utilities: text-muted, text-subtle, text-faint */
35
+ --color-muted: color-mix(in oklab, var(--color-text) 72%, transparent);
36
+ --color-subtle: color-mix(in oklab, var(--color-text) 60%, transparent);
37
+ --color-faint: color-mix(in oklab, var(--color-text) 45%, transparent);
38
+
39
+ /* Radius & shadow (custom; shadow-* namespace only generates when named accordingly) */
40
+ --radius-pill: 9999px;
41
+ --radius-2xl: 1.25rem;
42
+ /* Custom card shadow used via var(--shadow-card) */
43
+ --shadow-card: 0 1px 2px rgb(0 0 0 / 0.04), 0 8px 24px -10px rgb(0 0 0 / 0.15);
44
+
45
+ /* === Depth system tokens (visual + motion) === */
46
+ /* Motion & easing (namespaced so utilities like `ease-soft` are available) */
47
+ --ease-soft: cubic-bezier(0.25, 0.1, 0.25, 1);
48
+ --ease-deep: cubic-bezier(0.2, 0, 0, 1);
49
+
50
+ /* Optional durations (used via var() in component CSS; Tailwind doesn't expose duration-* via @theme) */
51
+ --duration-snug: 160ms;
52
+ --duration-slow: 420ms;
53
+
54
+ /* Scene transition tokens (Pad scene enter/exit) */
55
+ --scene-enter-duration: 360ms;
56
+ --scene-exit-duration: 300ms;
57
+ --scene-enter-ease: var(--ease-deep);
58
+ --scene-exit-ease: var(--ease-soft);
59
+
60
+ /* Optional depth-affine visual helpers (opt-in per element) */
61
+ --layer-scale-0: 1;
62
+ --layer-scale-1: 1.005;
63
+ --layer-scale-2: 1.01;
64
+ --layer-scale-3: 1.015;
65
+
66
+ --layer-blur-0: 0px;
67
+ --layer-blur-1: 0.2px;
68
+ --layer-blur-2: 0.6px;
69
+ --layer-blur-3: 1px;
70
+
71
+ /* Shadows per depth (D0..D3) — utilities: shadow-depth-0 ... shadow-depth-3 */
72
+ --shadow-depth-0: 0 0 0 0 rgb(0 0 0 / 0);
73
+ --shadow-depth-1: 0 2px 6px rgb(0 0 0 / 0.08), 0 8px 16px -8px rgb(0 0 0 / 0.18);
74
+ --shadow-depth-2: 0 2px 6px rgb(0 0 0 / 0.07), 0 12px 26px -12px rgb(0 0 0 / 0.2);
75
+ --shadow-depth-3: 0 6px 16px rgb(0 0 0 / 0.12), 0 22px 48px -18px rgb(0 0 0 / 0.3);
76
+
77
+ /* Inset shadows per depth — utilities: inset-shadow-depth-0 ... inset-shadow-depth-3 */
78
+ --inset-shadow-depth-0: inset 0 0 0 0 rgb(0 0 0 / 0);
79
+ --inset-shadow-depth-1: inset 0 1px 1px rgb(0 0 0 / 0.06);
80
+ --inset-shadow-depth-2: inset 0 2px 3px rgb(0 0 0 / 0.12);
81
+ --inset-shadow-depth-3: inset 0 3px 6px rgb(0 0 0 / 0.18);
82
+
83
+ /* Drop shadows per depth — utilities: drop-shadow-depth-0 ... drop-shadow-depth-3 */
84
+ --drop-shadow-depth-0: 0 0 0 rgb(0 0 0 / 0);
85
+ --drop-shadow-depth-1: 0 1px 1px rgb(0 0 0 / 0.12);
86
+ --drop-shadow-depth-2: 0 3px 3px rgb(0 0 0 / 0.18);
87
+ --drop-shadow-depth-3: 0 6px 6px rgb(0 0 0 / 0.24);
88
+
89
+ /* Layer opacities per depth (used as variables, not utilities) */
90
+ --layer-opacity-0: 0.85;
91
+ --layer-opacity-1: 0.98;
92
+ --layer-opacity-2: 0.965;
93
+ --layer-opacity-3: 0.94;
94
+
95
+ /* Timeline ambient opacities (used by scenes) */
96
+ --timeline-heavy-ambient: 0.82;
97
+ --timeline-slow-ambient: 0.9;
98
+ --timeline-flow-ambient: 1;
99
+
100
+ /* Ambient animations (utilities: animate-breathe, animate-twinkle) */
101
+ --animate-breathe: breathe 4s ease-in-out infinite;
102
+ --animate-twinkle: twinkle 3.2s ease-in-out infinite;
103
+
104
+ @keyframes breathe {
105
+ 0%,
106
+ 100% {
107
+ filter: brightness(100%);
108
+ transform: translateY(0);
109
+ }
110
+ 50% {
111
+ filter: brightness(114%);
112
+ transform: translateY(-1px);
113
+ }
114
+ }
115
+ @keyframes twinkle {
116
+ 0%,
117
+ 100% {
118
+ opacity: 0.6;
119
+ }
120
+ 50% {
121
+ opacity: 1;
122
+ }
123
+ }
124
+
125
+ @keyframes sceneIn {
126
+ from {
127
+ opacity: 0;
128
+ transform: translateY(4px) scale(0.995);
129
+ filter: blur(1px);
130
+ }
131
+ to {
132
+ opacity: 1;
133
+ transform: translateY(0) scale(1);
134
+ filter: blur(0);
135
+ }
136
+ }
137
+ @keyframes sceneOut {
138
+ from {
139
+ opacity: 1;
140
+ transform: translateY(0) scale(1);
141
+ filter: blur(0);
142
+ }
143
+ to {
144
+ opacity: 0;
145
+ transform: translateY(2px) scale(0.995);
146
+ filter: blur(0.6px);
147
+ }
148
+ }
149
+ }
150
+
151
+ /* === Runtime dark mapping (no duplicate @theme) === */
152
+ @layer base {
153
+ :root[data-theme='dark'],
154
+ :root.dark {
155
+ --color-surface: oklch(21% 0.07 241);
156
+ --color-elev: oklch(18% 0.02 200);
157
+ --color-text: oklch(97% 0.01 180);
158
+
159
+ /* Slightly cooler/glow accent at night */
160
+ --color-accent: oklch(78% 0.12 190);
161
+ --color-on-accent: oklch(16% 0.02 200);
162
+
163
+ --color-positive: oklch(75% 0.12 150);
164
+ --color-warning: oklch(82% 0.14 80);
165
+ --color-danger: oklch(68% 0.16 27);
166
+
167
+ --color-border: oklch(38% 0.03 180);
168
+
169
+ /* Darker card shadow */
170
+ --shadow-card: 0 1px 2px rgb(0 0 0 / 0.12), 0 8px 18px -8px rgb(0 0 0 / 0.35);
171
+
172
+ /* Depth shadows tuned for dark */
173
+ --shadow-depth-0: 0 0 0 0 rgb(0 0 0 / 0);
174
+ --shadow-depth-1: 0 1px 2px rgb(0 0 0 / 0.12), 0 6px 14px -8px rgb(0 0 0 / 0.28);
175
+ --shadow-depth-2: 0 2px 8px rgb(0 0 0 / 0.18), 0 14px 30px -12px rgb(0 0 0 / 0.36);
176
+ --shadow-depth-3: 0 8px 20px rgb(0 0 0 / 0.28), 0 26px 54px -20px rgb(0 0 0 / 0.5);
177
+
178
+ --inset-shadow-depth-0: inset 0 0 0 0 rgb(0 0 0 / 0);
179
+ --inset-shadow-depth-1: inset 0 1px 1px rgb(0 0 0 / 0.12);
180
+ --inset-shadow-depth-2: inset 0 2px 3px rgb(0 0 0 / 0.2);
181
+ --inset-shadow-depth-3: inset 0 3px 6px rgb(0 0 0 / 0.3);
182
+
183
+ --drop-shadow-depth-0: 0 0 0 rgb(0 0 0 / 0);
184
+ --drop-shadow-depth-1: 0 1px 1px rgb(0 0 0 / 0.25);
185
+ --drop-shadow-depth-2: 0 3px 3px rgb(0 0 0 / 0.32);
186
+ --drop-shadow-depth-3: 0 6px 6px rgb(0 0 0 / 0.42);
187
+ }
188
+
189
+ /* Respect system preference when no explicit choice */
190
+ @media (prefers-color-scheme: dark) {
191
+ :root:not([data-theme]) {
192
+ --color-surface: oklch(21% 0.07 241);
193
+ --color-elev: oklch(18% 0.02 200);
194
+ --color-text: oklch(97% 0.01 180);
195
+ --color-accent: oklch(78% 0.12 190);
196
+ --color-on-accent: oklch(16% 0.02 200);
197
+
198
+ --color-positive: oklch(75% 0.12 150);
199
+ --color-warning: oklch(82% 0.14 80);
200
+ --color-danger: oklch(68% 0.16 27);
201
+
202
+ --color-border: oklch(38% 0.03 180);
203
+ --shadow-card: 0 1px 2px rgb(0 0 0 / 0.12), 0 8px 18px -8px rgb(0 0 0 / 0.35);
204
+
205
+ --inset-shadow-depth-0: inset 0 0 0 0 rgb(0 0 0 / 0);
206
+ --inset-shadow-depth-1: inset 0 1px 1px rgb(0 0 0 / 0.12);
207
+ --inset-shadow-depth-2: inset 0 2px 3px rgb(0 0 0 / 0.2);
208
+ --inset-shadow-depth-3: inset 0 3px 6px rgb(0 0 0 / 0.3);
209
+
210
+ --drop-shadow-depth-0: 0 0 0 rgb(0 0 0 / 0);
211
+ --drop-shadow-depth-1: 0 1px 1px rgb(0 0 0 / 0.25);
212
+ --drop-shadow-depth-2: 0 3px 3px rgb(0 0 0 / 0.32);
213
+ --drop-shadow-depth-3: 0 6px 6px rgb(0 0 0 / 0.42);
214
+ }
215
+ }
216
+
217
+ /* Depth mapping helpers — set current depth variables from [data-depth] */
218
+ :root {
219
+ --depth-shadow: var(--shadow-depth-0);
220
+ --depth-opacity: var(--layer-opacity-0);
221
+ }
222
+ [data-depth='1'] {
223
+ --depth-shadow: var(--shadow-depth-1);
224
+ --depth-opacity: var(--layer-opacity-1);
225
+ }
226
+ [data-depth='2'] {
227
+ --depth-shadow: var(--shadow-depth-2);
228
+ --depth-opacity: var(--layer-opacity-2);
229
+ }
230
+ [data-depth='3'] {
231
+ --depth-shadow: var(--shadow-depth-3);
232
+ --depth-opacity: var(--layer-opacity-3);
233
+ }
234
+
235
+ /* Pad mood → token remap
236
+ These scopes re-tune accent/hero/scene timings without changing structure.
237
+ Apply on any Pad root: <section data-pad-mood="soft|focused|celebratory">…</section>
238
+ */
239
+ :root[data-pad-mood='soft'],
240
+ [data-pad-mood='soft'] {
241
+ /* soften accent & motion */
242
+ --accent-strength: 0.75;
243
+ --ring-alpha: 0.16;
244
+ --glow-intensity: 0.08;
245
+ --grain-strength: 0.1;
246
+ --grain-size: 160px; /* larger tile -> softer grain geometry */
247
+ --track-glow: color-mix(in oklab, var(--color-accent) 45%, transparent);
248
+ --track-shadow: color-mix(in oklab, var(--color-text) 25%, transparent);
249
+ --scene-highlight: color-mix(in oklab, var(--color-accent) 55%, transparent);
250
+ --presence-dot-color: color-mix(in oklab, var(--color-accent) 65%, white 15%);
251
+ --ghost-trail-color: color-mix(in oklab, var(--color-accent) 18%, transparent);
252
+
253
+ /* calmer hero gradient and slower entrances */
254
+ --pad-hero-start: color-mix(in oklab, var(--accent) 8%, var(--surface));
255
+ --pad-hero-end: color-mix(in oklab, var(--accent) 2%, var(--surface));
256
+ --scene-enter-duration: 420ms;
257
+ --scene-exit-duration: 360ms;
258
+ --scene-enter-ease: var(--ease-soft);
259
+ --scene-exit-ease: var(--ease-soft);
260
+ }
261
+
262
+ :root[data-pad-mood='focused'],
263
+ [data-pad-mood='focused'] {
264
+ /* tighten motion; stronger accent presence */
265
+ --accent-strength: 1.08;
266
+ --ring-alpha: 0.22;
267
+ --glow-intensity: 0.12;
268
+ --grain-strength: 0.12;
269
+ --grain-size: 130px; /* tighter tile -> slightly crisper grain */
270
+ --track-glow: color-mix(in oklab, var(--color-accent) 65%, transparent);
271
+ --track-shadow: color-mix(in oklab, var(--color-accent) 28%, transparent);
272
+ --scene-highlight: color-mix(in oklab, var(--color-accent) 70%, transparent);
273
+ --presence-dot-color: color-mix(in oklab, var(--color-accent) 78%, white 12%);
274
+ --ghost-trail-color: color-mix(in oklab, var(--color-accent) 24%, transparent);
275
+
276
+ --pad-hero-start: color-mix(in oklab, var(--accent) 16%, var(--surface));
277
+ --pad-hero-end: color-mix(in oklab, var(--accent) 6%, var(--surface));
278
+ --scene-enter-duration: 300ms;
279
+ --scene-exit-duration: 260ms;
280
+ --scene-enter-ease: var(--ease-deep);
281
+ --scene-exit-ease: var(--ease-deep);
282
+ }
283
+
284
+ :root[data-pad-mood='celebratory'],
285
+ [data-pad-mood='celebratory'] {
286
+ /* vivid accent, brisk motion, brighter hero */
287
+ --accent-strength: 1.18;
288
+ --ring-alpha: 0.26;
289
+ --glow-intensity: 0.18;
290
+ --grain-strength: 0.14;
291
+ --grain-size: 120px; /* smallest tile -> more lively grain */
292
+ --track-glow: color-mix(in oklab, var(--color-accent) 90%, white 10%);
293
+ --track-shadow: color-mix(in oklab, var(--color-accent) 35%, transparent);
294
+ --scene-highlight: color-mix(in oklab, var(--color-accent) 92%, white 8%);
295
+ --presence-dot-color: color-mix(in oklab, var(--color-accent) 94%, white 12%);
296
+ --ghost-trail-color: color-mix(in oklab, var(--color-accent) 32%, transparent);
297
+
298
+ --pad-hero-start: color-mix(in oklab, var(--accent) 24%, var(--surface));
299
+ --pad-hero-end: color-mix(in oklab, var(--accent) 12%, var(--surface));
300
+ --scene-enter-duration: 280ms;
301
+ --scene-exit-duration: 240ms;
302
+ --scene-enter-ease: var(--ease-deep);
303
+ --scene-exit-ease: var(--ease-deep);
304
+ }
305
+
306
+ /* Reverse‑the‑Poles (RTP) — abundance mode
307
+ why: security is a floor, not a leash | what: flip safe defaults | how: dataset flag */
308
+ :root[data-mode='rtp'] {
309
+ --composer-privacy-default: sealed; /* start private; sharing is opt‑in */
310
+ --rest-is-repair: 1; /* surfaces gentle hints in UI */
311
+ /* capacity stays at 3 by design (small honest wins) */
312
+ }
313
+ }
314
+
315
+ /* === Aliases for custom CSS & calc() ===
316
+ These do not create utilities; they are convenience variables.
317
+ */
318
+ :root {
319
+ --surface: var(--color-surface);
320
+ --elev: var(--color-elev);
321
+ --text: var(--color-text);
322
+ --accent: var(--color-accent);
323
+ --on-accent: var(--color-on-accent);
324
+ --border: var(--color-border);
325
+
326
+ --text-muted: var(--color-muted);
327
+ --text-subtle: var(--color-subtle);
328
+ --text-faint: var(--color-faint);
329
+ --color-fill-subtle: color-mix(in oklab, var(--color-text) 8%, var(--color-surface));
330
+
331
+ --sheet-radius: var(--radius-2xl);
332
+ --shadow-card-ambient: var(--shadow-card);
333
+
334
+ /* Covenant & RTP-friendly defaults (read by UI; override via [data-mode]) */
335
+ --composer-privacy-default: public; /* flipped to `sealed` in RTP */
336
+ --capacity-units-per-day: 3; /* UI renders three capacity dots */
337
+ --rtp-hint-default: "ask, don't test"; /* language-interrupt default hint */
338
+ --rest-is-repair: 0; /* 1 when RTP mode is active */
339
+
340
+ /* Motion — durations */
341
+ --dur-pulse: 700ms; /* UI chip pulse duration */
342
+ --dur-toast: 5s; /* or 4800ms */
343
+
344
+ /* Pad hero gradient defaults (used by PadChrome) */
345
+ --pad-hero-start: color-mix(in oklab, var(--accent) 12%, var(--surface));
346
+ --pad-hero-end: color-mix(in oklab, var(--accent) 3%, var(--surface));
347
+
348
+ /* Pad mood tokens (default values; remapped by [data-pad-mood]) */
349
+ --accent-strength: 1; /* 0.0–1.2 — scales accent usage */
350
+ --glow-intensity: 0.1; /* 0–1 — subtle halo/glow */
351
+ --ring-alpha: 0.18; /* 0–1 — whisper ring strength */
352
+ --grain-strength: 0.12; /* 0–1 — ambient grain strength */
353
+ --grain-size: 140px; /* px — grain tile size used by card grain overlay */
354
+ --track-glow: color-mix(in oklab, var(--color-accent) 55%, transparent);
355
+ --track-shadow: color-mix(in oklab, var(--color-text) 25%, transparent);
356
+ --scene-highlight: color-mix(in oklab, var(--color-accent) 60%, transparent);
357
+ --presence-dot-color: color-mix(in oklab, var(--color-accent) 70%, white 15%);
358
+ --ghost-trail-color: color-mix(in oklab, var(--color-accent) 22%, transparent);
359
+ }
360
+
361
+ @layer utilities {
362
+ .border-transparent {
363
+ border-color: transparent;
364
+ }
365
+ .text-2xs {
366
+ font-size: var(--text-2xs);
367
+ }
368
+ .elev-1 {
369
+ @apply shadow-depth-1;
370
+ }
371
+ .elev-2 {
372
+ @apply shadow-depth-2;
373
+ }
374
+ .elev-3 {
375
+ @apply shadow-depth-3;
376
+ }
377
+ .depth-ambient {
378
+ box-shadow: var(--depth-shadow);
379
+ opacity: var(--depth-opacity);
380
+ transition: box-shadow var(--duration-snug) var(--ease-soft), opacity var(--duration-snug) var(--ease-soft),
381
+ filter var(--duration-snug) var(--ease-soft), transform var(--duration-snug) var(--ease-soft);
382
+ }
383
+ .ambient-heavy {
384
+ opacity: var(--timeline-heavy-ambient);
385
+ transition: opacity var(--duration-slow) var(--ease-soft);
386
+ }
387
+ .ambient-slow {
388
+ opacity: var(--timeline-slow-ambient);
389
+ transition: opacity var(--duration-snug) var(--ease-soft);
390
+ }
391
+ .ambient-flow {
392
+ opacity: var(--timeline-flow-ambient);
393
+ transition: opacity var(--duration-snug) var(--ease-deep);
394
+ }
395
+
396
+ /* Scene enter/exit helpers (hooked to Pad scene transitions) */
397
+ .scene-enter {
398
+ animation: sceneIn var(--scene-enter-duration) var(--scene-enter-ease);
399
+ will-change: opacity, transform, filter;
400
+ }
401
+ .scene-exit {
402
+ animation: sceneOut var(--scene-exit-duration) var(--scene-exit-ease);
403
+ will-change: opacity, transform, filter;
404
+ }
405
+
406
+ /* Depth-aware visual layer helper — apply to a child that should respond to [data-depth] */
407
+ .wave-visual {
408
+ transition: opacity var(--duration-snug) var(--ease-soft), filter var(--duration-snug) var(--ease-soft),
409
+ transform var(--duration-snug) var(--ease-soft);
410
+ }
411
+ [data-depth='0'] .wave-visual {
412
+ opacity: var(--layer-opacity-0);
413
+ filter: blur(var(--layer-blur-0));
414
+ transform: scale(var(--layer-scale-0));
415
+ }
416
+ [data-depth='1'] .wave-visual {
417
+ opacity: var(--layer-opacity-1);
418
+ filter: blur(var(--layer-blur-1));
419
+ transform: scale(var(--layer-scale-1));
420
+ }
421
+ [data-depth='2'] .wave-visual {
422
+ opacity: var(--layer-opacity-2);
423
+ filter: blur(var(--layer-blur-2));
424
+ transform: scale(var(--layer-scale-2));
425
+ }
426
+ [data-depth='3'] .wave-visual {
427
+ opacity: var(--layer-opacity-3);
428
+ filter: blur(var(--layer-blur-3));
429
+ transform: scale(var(--layer-scale-3));
430
+ }
431
+
432
+ /* Whisper ring — subtle focus/hover halo for interactive bits */
433
+ .whisper-ring {
434
+ outline: 0;
435
+ box-shadow: 0 0 0 0 transparent, inset 0 0 0 0 transparent;
436
+ transition: box-shadow var(--duration-snug) var(--ease-soft);
437
+ }
438
+ .whisper-ring:is(:hover, :focus-visible) {
439
+ box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) calc(60% * var(--accent-strength)), transparent),
440
+ 0 0 0 6px color-mix(in oklab, var(--accent) calc(12% * var(--accent-strength)), transparent);
441
+ }
442
+
443
+ /* Mood glow — soft ambient halo that scales with pad mood tokens
444
+ Uses:
445
+ --glow-intensity (0..1)
446
+ --accent-strength (0..1.2)
447
+ --accent (color)
448
+ Apply to any element with rounded corners for best effect.
449
+ */
450
+ .mood-glow {
451
+ position: relative;
452
+ isolation: isolate; /* keep ::after within this stacking context */
453
+ z-index: 0;
454
+ transition: box-shadow var(--duration-snug) var(--ease-soft);
455
+ }
456
+ .mood-glow::after {
457
+ content: '';
458
+ position: absolute;
459
+ inset: -1px; /* tiny bleed to avoid seams on borders */
460
+ border-radius: inherit; /* follow host radius */
461
+ pointer-events: none;
462
+ z-index: -1;
463
+ opacity: clamp(0, var(--glow-intensity), 1);
464
+ transition: box-shadow var(--duration-snug) var(--ease-soft), opacity var(--duration-snug) var(--ease-soft);
465
+ /* ambient halo; relies on box-shadow so it stays GPU-friendly */
466
+ box-shadow: 0 0 calc(24px * var(--glow-intensity)) calc(6px * var(--glow-intensity))
467
+ color-mix(in oklab, var(--accent) calc(18% * var(--accent-strength)), transparent);
468
+ }
469
+ .mood-glow:is(:hover, :focus-within)::after {
470
+ /* slight lift on interaction */
471
+ box-shadow: 0 0 calc(28px * var(--glow-intensity)) calc(8px * var(--glow-intensity))
472
+ color-mix(in oklab, var(--accent) calc(26% * var(--accent-strength)), transparent);
473
+ }
474
+
475
+ /* Animation utilities — apply tokenized keyframes with one class */
476
+ .animate-breathe {
477
+ animation: var(--animate-breathe);
478
+ }
479
+ .animate-twinkle {
480
+ animation: var(--animate-twinkle);
481
+ }
482
+
483
+ /* Respect reduced motion: disable ambient animations */
484
+ @media (prefers-reduced-motion: reduce) {
485
+ .animate-breathe,
486
+ .animate-twinkle,
487
+ .scene-enter,
488
+ .scene-exit {
489
+ animation: none !important;
490
+ }
491
+ }
492
+ }