@waveso/ui 0.4.1 → 0.6.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 (103) hide show
  1. package/dist/accordion.js +2 -2
  2. package/dist/accordion.js.map +1 -1
  3. package/dist/action-bar.js +2 -2
  4. package/dist/action-bar.js.map +1 -1
  5. package/dist/alert-dialog.js +5 -5
  6. package/dist/alert-dialog.js.map +1 -1
  7. package/dist/alert.js +5 -5
  8. package/dist/alert.js.map +1 -1
  9. package/dist/animate.d.ts +5 -2
  10. package/dist/animate.d.ts.map +1 -1
  11. package/dist/animate.js +13 -8
  12. package/dist/animate.js.map +1 -1
  13. package/dist/autocomplete.js +8 -8
  14. package/dist/autocomplete.js.map +1 -1
  15. package/dist/avatar.js +5 -5
  16. package/dist/avatar.js.map +1 -1
  17. package/dist/badge.d.ts +1 -1
  18. package/dist/badge.d.ts.map +1 -1
  19. package/dist/badge.js +5 -6
  20. package/dist/badge.js.map +1 -1
  21. package/dist/breadcrumb.js +3 -3
  22. package/dist/breadcrumb.js.map +1 -1
  23. package/dist/button-group.js +5 -5
  24. package/dist/button-group.js.map +1 -1
  25. package/dist/button.d.ts +1 -1
  26. package/dist/button.d.ts.map +1 -1
  27. package/dist/button.js +9 -10
  28. package/dist/button.js.map +1 -1
  29. package/dist/card.js +4 -4
  30. package/dist/card.js.map +1 -1
  31. package/dist/checkbox.js +1 -1
  32. package/dist/checkbox.js.map +1 -1
  33. package/dist/combobox.d.ts.map +1 -1
  34. package/dist/combobox.js +10 -10
  35. package/dist/combobox.js.map +1 -1
  36. package/dist/context-menu.js +9 -9
  37. package/dist/context-menu.js.map +1 -1
  38. package/dist/dialog.d.ts.map +1 -1
  39. package/dist/dialog.js +5 -5
  40. package/dist/dialog.js.map +1 -1
  41. package/dist/drawer.js +5 -5
  42. package/dist/drawer.js.map +1 -1
  43. package/dist/field.js +2 -2
  44. package/dist/field.js.map +1 -1
  45. package/dist/form.js +2 -2
  46. package/dist/form.js.map +1 -1
  47. package/dist/infinite-scroll.js +2 -2
  48. package/dist/infinite-scroll.js.map +1 -1
  49. package/dist/input-group.d.ts +1 -1
  50. package/dist/input-group.js +5 -5
  51. package/dist/input-group.js.map +1 -1
  52. package/dist/input-otp.js +3 -3
  53. package/dist/input-otp.js.map +1 -1
  54. package/dist/input.js +1 -1
  55. package/dist/input.js.map +1 -1
  56. package/dist/item.js +4 -4
  57. package/dist/item.js.map +1 -1
  58. package/dist/kbd.js +1 -1
  59. package/dist/kbd.js.map +1 -1
  60. package/dist/menu.js +9 -9
  61. package/dist/menu.js.map +1 -1
  62. package/dist/menubar.js +1 -1
  63. package/dist/menubar.js.map +1 -1
  64. package/dist/popover.js +2 -2
  65. package/dist/popover.js.map +1 -1
  66. package/dist/preview-card.js +1 -1
  67. package/dist/preview-card.js.map +1 -1
  68. package/dist/progress.js +2 -2
  69. package/dist/progress.js.map +1 -1
  70. package/dist/radio.js +2 -2
  71. package/dist/radio.js.map +1 -1
  72. package/dist/scroll-area.js +2 -2
  73. package/dist/scroll-area.js.map +1 -1
  74. package/dist/select.js +8 -8
  75. package/dist/select.js.map +1 -1
  76. package/dist/separator.js +1 -1
  77. package/dist/separator.js.map +1 -1
  78. package/dist/sidebar.js +19 -19
  79. package/dist/sidebar.js.map +1 -1
  80. package/dist/skeleton.js +1 -1
  81. package/dist/skeleton.js.map +1 -1
  82. package/dist/slider.js +3 -3
  83. package/dist/slider.js.map +1 -1
  84. package/dist/styles.css +451 -161
  85. package/dist/switch.js +2 -2
  86. package/dist/switch.js.map +1 -1
  87. package/dist/table.js +4 -4
  88. package/dist/table.js.map +1 -1
  89. package/dist/tabs.js +3 -3
  90. package/dist/tabs.js.map +1 -1
  91. package/dist/textarea.js +1 -1
  92. package/dist/textarea.js.map +1 -1
  93. package/dist/toast.d.ts +3 -3
  94. package/dist/toast.d.ts.map +1 -1
  95. package/dist/toast.js +38 -11
  96. package/dist/toast.js.map +1 -1
  97. package/dist/toggle-group.js +2 -2
  98. package/dist/toggle-group.js.map +1 -1
  99. package/dist/toggle.js +3 -3
  100. package/dist/toggle.js.map +1 -1
  101. package/dist/tooltip.js +1 -1
  102. package/dist/tooltip.js.map +1 -1
  103. package/package.json +2 -5
package/dist/styles.css CHANGED
@@ -6,16 +6,12 @@
6
6
  * @import 'tailwindcss';
7
7
  * @import '@waveso/ui/styles.css';
8
8
  *
9
- * This brings the `tw-animate-css` keyframe utilities the components
10
- * rely on, the `dark` variant, every theme token (override in your own
11
- * :root / .dark), motion tokens + recipes, and the `@source` rule. No
12
- * other library-related imports needed.
9
+ * This brings the `dark` variant, every theme token (override in your own
10
+ * :root / .dark), motion tokens + recipes, the few self-hosted keyframe
11
+ * utilities the components need, and the `@source` rule. No other
12
+ * library-related imports needed — zero runtime dependencies.
13
13
  */
14
14
 
15
- /* Keyframe animation utilities used by popup components
16
- * (`data-open:animate-in`, `fade-in-0`, `zoom-in-95`, etc.). */
17
- @import "tw-animate-css";
18
-
19
15
  /* Auto-register component source files so Tailwind generates CSS for all
20
16
  * utility classes used by @waveso/ui components. Resolves relative to this
21
17
  * file's location in node_modules/@waveso/ui/dist/. */
@@ -29,33 +25,87 @@
29
25
  * ------------------------------------------------------------------------- */
30
26
 
31
27
  :root {
32
- /* Core */
33
- --background: oklch(1 0 0);
34
- --foreground: oklch(0.145 0 0);
35
- --card: oklch(1 0 0);
36
- --card-foreground: oklch(0.145 0 0);
37
- --popover: oklch(1 0 0);
38
- --popover-foreground: oklch(0.145 0 0);
39
-
40
- /* Brand */
41
- --primary: oklch(0.488 0.243 264.376);
42
- --primary-foreground: oklch(0.97 0.014 254.604);
43
- --secondary: oklch(0.967 0.001 286.375);
44
- --secondary-foreground: oklch(0.21 0.006 285.885);
45
- --accent: oklch(0.488 0.243 264.376);
46
- --accent-foreground: oklch(0.97 0.014 254.604);
47
-
48
- /* Neutral */
49
- --muted: oklch(0.97 0 0);
50
- --muted-foreground: oklch(0.556 0 0);
51
- --border: oklch(0.922 0 0);
52
- --input: oklch(0.922 0 0);
53
- --ring: oklch(0.708 0 0);
28
+ /* ========================================================================
29
+ * Palettes — raw primitives. No `--color-` prefix on purpose: these live in
30
+ * `:root`, not `@theme`, so they generate NO utilities (no `bg-ion-300`).
31
+ * They're referenced by the semantic tokens below, never used directly.
32
+ * ====================================================================== */
33
+
34
+ /* Ion neutral ramp */
35
+ --ion-50: #FAFAFB;
36
+ --ion-100: #F3F3F4;
37
+ --ion-200: #E5E5E8;
38
+ --ion-300: #D1D2D7;
39
+ --ion-400: #ABACB2;
40
+ --ion-500: #8B8C94;
41
+ --ion-600: #6C6D76;
42
+ --ion-700: #4C4D55;
43
+ --ion-800: #3B3C44;
44
+ --ion-900: #323339;
45
+ --ion-950: #2C2D32;
46
+
47
+ /* Wave brand ramp */
48
+ --wave-50: #EEF3FF;
49
+ --wave-100: #DCE6FF;
50
+ --wave-200: #B8C9FF;
51
+ --wave-300: #8FA8FF;
52
+ --wave-400: #6A8DFF;
53
+ --wave-500: #315FE8;
54
+ --wave-600: #274FC7;
55
+ --wave-700: #1C3F96;
56
+ --wave-800: #142B69;
57
+ --wave-900: #0D1C46;
58
+ --wave-950: #061237;
59
+
60
+ /* ========================================================================
61
+ * Semantic tokens — light (default). The spec's `.light` block lives in
62
+ * `:root` so consumers get the light theme without needing a `.light` class.
63
+ * ====================================================================== */
64
+
65
+ /* Backgrounds = elevation */
66
+ --foundation: var(--ion-200);
67
+ --surface: var(--ion-100);
68
+ --elevated: var(--ion-50);
69
+
70
+ /* Contrast = emphasis — highest-contrast text → muted → soft. */
71
+ --contrast: var(--ion-950);
72
+ --muted: var(--ion-600);
73
+ --soft: var(--ion-500);
74
+
75
+ /* Inverse — an inverted neutral pair (e.g. the dark tooltip). Aliases, so
76
+ * they auto-flip with `.dark` (var() re-resolves at the use site). */
77
+ --surface-inverse: var(--contrast);
78
+ --contrast-inverse: var(--foundation);
79
+
80
+ /* Brand — `primary` wired to the Wave ramp; `secondary` is the neutral fill
81
+ * (secondary/ghost buttons, hovers, selected) at ion-300; `accent` is the
82
+ * magenta. No per-fill on-color tokens: text on the saturated fills is
83
+ * `text-white`, text on the neutral `secondary` is `text-contrast`. */
84
+ --primary: var(--wave-500);
85
+ --secondary: var(--ion-300);
86
+ --accent: #9A466A;
87
+
88
+ /* Borders = structure */
89
+ --line: rgba(44, 45, 50, 0.055);
90
+ --edge: rgba(44, 45, 50, 0.11);
91
+ --solid: var(--ion-300);
92
+
93
+ /* Rings = brand themed */
94
+ --focus: var(--primary);
95
+
96
+ /* Shadow — two dials for the layered `shadow-*` scale: `--shadow-color` (HSL
97
+ * components — a deep cool-tinted near-black, never pure #000 which reads
98
+ * heavy) and `--shadow-strength` (per-layer alpha / intensity). Both resolve
99
+ * per :root/.dark, so retinting OR dimming every shadow is one token. For a
100
+ * colored surface set a named tint, e.g. `--shadow-color-primary`. */
101
+ --shadow-color: 220deg 45% 6%;
102
+ --shadow-strength: 0.22;
54
103
 
55
104
  /* Status */
56
- --destructive: oklch(0.58 0.22 27);
57
- --success: oklch(0.59 0.18 163);
58
- --warning: oklch(0.75 0.18 85);
105
+ --destructive: oklch(0.63 0.22 27);
106
+ --success: oklch(0.72 0.19 145);
107
+ --warning: oklch(0.82 0.17 85);
108
+ --info: oklch(0.62 0.18 250);
59
109
 
60
110
  /* Charts */
61
111
  --chart-1: oklch(0.809 0.105 251.813);
@@ -70,16 +120,6 @@
70
120
  --presence-busy: oklch(0.63 0.22 27);
71
121
  --presence-invisible: oklch(0.55 0 0);
72
122
 
73
- /* Sidebar */
74
- --sidebar: oklch(0.985 0 0);
75
- --sidebar-foreground: oklch(0.145 0 0);
76
- --sidebar-primary: oklch(0.546 0.245 262.881);
77
- --sidebar-primary-foreground: oklch(0.97 0.014 254.604);
78
- --sidebar-accent: oklch(0.488 0.243 264.376);
79
- --sidebar-accent-foreground: oklch(0.97 0.014 254.604);
80
- --sidebar-border: oklch(0.922 0 0);
81
- --sidebar-ring: oklch(0.708 0 0);
82
-
83
123
  /* Radius */
84
124
  --radius: 0.625rem;
85
125
 
@@ -96,16 +136,33 @@
96
136
  * native / HIG behavior; real links keep their own pointer regardless. */
97
137
  --cursor-clickable: pointer;
98
138
 
99
- /* Motion — tokens consumed by the `motion-*` recipes (see Utilities).
100
- * Override any value in your own :root. `prefers-reduced-motion` zeroes
101
- * them in one block (bottom of file), so every recipe collapses safely. */
102
- --duration: 250ms;
103
- --ease: ease-out; /* defaultfade/blur, no transform */
104
- --spring: cubic-bezier(0.34, 1.45, 0.64, 1); /* only with translate/scale */
105
- --blur: 2px; /* enter/exit blur amount */
106
- --scale: 0.97; /* enter/exit scale-down */
107
- --offset: 4px; /* enter/exit translate offset */
108
- --stagger: 70ms; /* per-item list delay */
139
+ /* Motion — tokens consumed by the `motion-*` recipes (see Utilities). Override
140
+ * any value in your own :root. `prefers-reduced-motion` zeroes them (bottom of
141
+ * file), so every recipe collapses safely.
142
+ *
143
+ * THE SYSTEM5 scalable magnitudes × 3 tiers (sm/md/lg) + 1 fixed signature:
144
+ * scalable → --duration · --blur · --scale · --offset · --stagger
145
+ * fixed → --ease (the one curve — never tiered)
146
+ * tiers sm = controls/icons · md = popups · lg = dialogs
147
+ * sm/lg zoom in place; md also slides. `--offset`/`--stagger` sm+lg are
148
+ * defined ahead of use (wired per surface as we build them out). */
149
+ --ease: cubic-bezier(0.22, 1, 0.36, 1); /* the one curve — never tiered */
150
+
151
+ --duration-sm: 150ms; /* controls, icons */
152
+ --duration-md: 200ms; /* popups */
153
+ --duration-lg: 250ms; /* dialogs */
154
+ --blur-sm: 2px;
155
+ --blur-md: 4px;
156
+ --blur-lg: 8px;
157
+ --scale-sm: 0.95;
158
+ --scale-md: 0.9;
159
+ --scale-lg: 0.85;
160
+ --offset-sm: 2px; /* item/icon nudge */
161
+ --offset-md: 4px; /* popup slide */
162
+ --offset-lg: 8px;
163
+ --stagger-sm: 30ms; /* list-item delay */
164
+ --stagger-md: 60ms;
165
+ --stagger-lg: 90ms;
109
166
  }
110
167
 
111
168
  /* ---------------------------------------------------------------------------
@@ -113,33 +170,36 @@
113
170
  * ------------------------------------------------------------------------- */
114
171
 
115
172
  .dark {
116
- /* Core */
117
- --background: oklch(0.145 0 0);
118
- --foreground: oklch(0.985 0 0);
119
- --card: oklch(0.205 0 0);
120
- --card-foreground: oklch(0.985 0 0);
121
- --popover: oklch(0.205 0 0);
122
- --popover-foreground: oklch(0.985 0 0);
123
-
124
- /* Brand */
125
- --primary: oklch(0.42 0.18 266);
126
- --primary-foreground: oklch(0.97 0.014 254.604);
127
- --secondary: oklch(0.274 0.006 286.033);
128
- --secondary-foreground: oklch(0.985 0 0);
129
- --accent: oklch(0.42 0.18 266);
130
- --accent-foreground: oklch(0.97 0.014 254.604);
131
-
132
- /* Neutral */
133
- --muted: oklch(0.269 0 0);
134
- --muted-foreground: oklch(0.708 0 0);
135
- --border: oklch(1 0 0 / 10%);
136
- --input: oklch(1 0 0 / 15%);
137
- --ring: oklch(0.556 0 0);
173
+ /* Backgrounds = elevation */
174
+ --foundation: var(--ion-950);
175
+ --surface: var(--ion-900);
176
+ --elevated: var(--ion-800);
177
+
178
+ /* Contrast = emphasis */
179
+ --contrast: var(--ion-100);
180
+ --muted: var(--ion-400);
181
+ --soft: var(--ion-600);
182
+
183
+ /* Brand only `secondary` differs from light; mirrored to ion-700 so the
184
+ * neutral fill stands out against the dark surfaces. primary / accent /
185
+ * inverse / focus auto-flip or stay constant via the `:root` definitions. */
186
+ --secondary: var(--ion-700);
187
+
188
+ /* Borders = structure */
189
+ --line: rgba(246, 246, 241, 0.045);
190
+ --edge: rgba(246, 246, 241, 0.12);
191
+ --solid: var(--ion-700);
192
+
193
+ /* Shadow — darker tint, lower strength: a near-black shadow on a dark
194
+ * surface reads heavier than on white, so dim it. */
195
+ --shadow-color: 220deg 40% 3%;
196
+ --shadow-strength: 0.18;
138
197
 
139
198
  /* Status */
140
- --destructive: oklch(0.704 0.191 22.216);
141
- --success: oklch(0.72 0.17 162);
142
- --warning: oklch(0.82 0.17 85);
199
+ --destructive: oklch(0.65 0.2 27);
200
+ --success: oklch(0.65 0.17 145);
201
+ --warning: oklch(0.75 0.15 85);
202
+ --info: oklch(0.70 0.16 250);
143
203
 
144
204
  /* Charts */
145
205
  --chart-1: oklch(0.75 0.1 252);
@@ -153,52 +213,47 @@
153
213
  --presence-away: oklch(0.75 0.15 85);
154
214
  --presence-busy: oklch(0.65 0.2 27);
155
215
  --presence-invisible: oklch(0.6 0 0);
156
-
157
- /* Sidebar */
158
- --sidebar: oklch(0.205 0 0);
159
- --sidebar-foreground: oklch(0.985 0 0);
160
- --sidebar-primary: oklch(0.623 0.214 259.815);
161
- --sidebar-primary-foreground: oklch(0.97 0.014 254.604);
162
- --sidebar-accent: oklch(0.42 0.18 266);
163
- --sidebar-accent-foreground: oklch(0.97 0.014 254.604);
164
- --sidebar-border: oklch(1 0 0 / 10%);
165
- --sidebar-ring: oklch(0.556 0 0);
166
216
  }
167
217
 
168
218
  /* ---------------------------------------------------------------------------
169
219
  * Tailwind v4 theme mapping — maps CSS variables to Tailwind color tokens.
170
220
  * Consumers MUST include this for Tailwind classes like `bg-primary`,
171
- * `text-muted-foreground`, `border-sidebar-border` etc. to work.
221
+ * `text-muted`, `border-line` etc. to work.
172
222
  * ------------------------------------------------------------------------- */
173
223
 
174
224
  @theme inline {
175
- /* Core */
176
- --color-background: var(--background);
177
- --color-foreground: var(--foreground);
178
- --color-card: var(--card);
179
- --color-card-foreground: var(--card-foreground);
180
- --color-popover: var(--popover);
181
- --color-popover-foreground: var(--popover-foreground);
182
-
183
- /* Brand */
225
+ /* Surfaces */
226
+ --color-foundation: var(--foundation);
227
+ --color-surface: var(--surface);
228
+ --color-elevated: var(--elevated);
229
+
230
+ /* Contrast / text — `contrast` (highest emphasis) → `muted` → `soft`. */
231
+ --color-contrast: var(--contrast);
232
+ --color-muted: var(--muted);
233
+ --color-soft: var(--soft);
234
+
235
+ /* Inverse — maps the `:root` inverse aliases to utilities. */
236
+ --color-surface-inverse: var(--surface-inverse);
237
+ --color-contrast-inverse: var(--contrast-inverse);
238
+
239
+ /* Brand — saturated fills use `text-white`, the neutral `secondary` uses
240
+ * `text-contrast`. No per-fill on-color tokens. */
184
241
  --color-primary: var(--primary);
185
- --color-primary-foreground: var(--primary-foreground);
186
242
  --color-secondary: var(--secondary);
187
- --color-secondary-foreground: var(--secondary-foreground);
188
243
  --color-accent: var(--accent);
189
- --color-accent-foreground: var(--accent-foreground);
190
244
 
191
- /* Neutral */
192
- --color-muted: var(--muted);
193
- --color-muted-foreground: var(--muted-foreground);
194
- --color-border: var(--border);
195
- --color-input: var(--input);
196
- --color-ring: var(--ring);
245
+ /* Structure — `line` (faint divider), `edge` (input border), `solid` (opaque
246
+ * border), `focus` (brand-themed ring = primary). */
247
+ --color-line: var(--line);
248
+ --color-edge: var(--edge);
249
+ --color-solid: var(--solid);
250
+ --color-focus: var(--focus);
197
251
 
198
252
  /* Status */
199
253
  --color-destructive: var(--destructive);
200
254
  --color-success: var(--success);
201
255
  --color-warning: var(--warning);
256
+ --color-info: var(--info);
202
257
 
203
258
  /* Charts */
204
259
  --color-chart-1: var(--chart-1);
@@ -213,33 +268,45 @@
213
268
  --color-presence-busy: var(--presence-busy);
214
269
  --color-presence-invisible: var(--presence-invisible);
215
270
 
216
- /* Sidebar */
217
- --color-sidebar: var(--sidebar);
218
- --color-sidebar-foreground: var(--sidebar-foreground);
219
- --color-sidebar-primary: var(--sidebar-primary);
220
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
221
- --color-sidebar-accent: var(--sidebar-accent);
222
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
223
- --color-sidebar-border: var(--sidebar-border);
224
- --color-sidebar-ring: var(--sidebar-ring);
225
-
226
- /* Radius */
227
- --radius-xs: calc(var(--radius) - 6px);
228
- --radius-sm: calc(var(--radius) - 4px);
229
- --radius-md: calc(var(--radius) - 2px);
230
- --radius-lg: var(--radius);
231
- --radius-xl: calc(var(--radius) + 4px);
232
- --radius-2xl: calc(var(--radius) + 8px);
233
- --radius-3xl: calc(var(--radius) + 12px);
234
- --radius-4xl: calc(var(--radius) + 16px);
271
+ /* Radius — 3-tier scale, spacing-aligned + concentric (outer = inner + one
272
+ * --spacing); synced 1:1 with the motion sm/md/lg tiers. `--spacing` is
273
+ * Tailwind-provided (0.25rem = 4px). */
274
+ --radius-sm: calc(var(--radius) - var(--spacing)); /* 6px — small controls */
275
+ --radius-md: var(--radius); /* 10px — default (buttons, inputs, menus, popovers) */
276
+ --radius-lg: calc(var(--radius) + var(--spacing)); /* 14px — large surfaces (dialogs, drawers, cards) */
235
277
 
236
278
  /* Typography — passthroughs so consumers' `--font-sans` /
237
- * `--font-geist-mono` flow through to Tailwind's `font-sans` /
279
+ * `--font-mono` flow through to Tailwind's `font-sans` /
238
280
  * `font-mono` utilities. */
239
281
  --font-sans: var(--font-sans);
240
282
  --font-mono: var(--font-mono);
241
283
  }
242
284
 
285
+ /* Shadow scale — layered, tinted by `--shadow-color` and dimmed by
286
+ * `--shadow-strength` (both resolve per :root/.dark, so retinting or dimming
287
+ * every shadow is one token). Tiers differ by layer count + spread, not alpha.
288
+ * Overrides Tailwind's shadow-sm/md/lg. */
289
+ @theme {
290
+ --shadow-sm:
291
+ 0px 0.5px 0.7px hsl(var(--shadow-color) / var(--shadow-strength)),
292
+ 0px 0.8px 1px -1.2px hsl(var(--shadow-color) / var(--shadow-strength)),
293
+ 0px 2px 2.5px -2.5px hsl(var(--shadow-color) / var(--shadow-strength));
294
+ --shadow-md:
295
+ 0px 0.5px 0.7px hsl(var(--shadow-color) / var(--shadow-strength)),
296
+ 0px 1.6px 2px -0.8px hsl(var(--shadow-color) / var(--shadow-strength)),
297
+ 0px 4.1px 5.2px -1.7px hsl(var(--shadow-color) / var(--shadow-strength)),
298
+ 0px 10px 12.6px -2.5px hsl(var(--shadow-color) / var(--shadow-strength));
299
+ --shadow-lg:
300
+ 0px 0.5px 0.7px hsl(var(--shadow-color) / var(--shadow-strength)),
301
+ 0px 2.9px 3.7px -0.4px hsl(var(--shadow-color) / var(--shadow-strength)),
302
+ 0px 5.4px 6.8px -0.7px hsl(var(--shadow-color) / var(--shadow-strength)),
303
+ 0px 8.9px 11.2px -1.1px hsl(var(--shadow-color) / var(--shadow-strength)),
304
+ 0px 14.3px 18px -1.4px hsl(var(--shadow-color) / var(--shadow-strength)),
305
+ 0px 22.3px 28.1px -1.8px hsl(var(--shadow-color) / var(--shadow-strength)),
306
+ 0px 33.9px 42.7px -2.1px hsl(var(--shadow-color) / var(--shadow-strength)),
307
+ 0px 50px 62.9px -2.5px hsl(var(--shadow-color) / var(--shadow-strength));
308
+ }
309
+
243
310
  /* ---------------------------------------------------------------------------
244
311
  * Utilities
245
312
  * ------------------------------------------------------------------------- */
@@ -251,60 +318,256 @@
251
318
  cursor: var(--cursor-clickable);
252
319
  }
253
320
 
321
+ /* `motion-color` — interaction-state transition for controls: smoothly
322
+ * animates color / background / border / ring (box-shadow) on hover, focus,
323
+ * pressed, checked, etc. Runs at the `sm` tier (controls are small + fast). A
324
+ * plain layered `@utility`, so the unlayered enter/exit recipes below win over
325
+ * it when both land on one element (e.g. a button that also carries `motion-scale-sm`). */
326
+ @utility motion-color {
327
+ transition: color var(--duration-sm) var(--ease),
328
+ background-color var(--duration-sm) var(--ease),
329
+ border-color var(--duration-sm) var(--ease),
330
+ box-shadow var(--duration-sm) var(--ease);
331
+ }
332
+
254
333
  /* Motion recipes — Base UI primitives emit `data-starting-style` (enter)
255
- * and `data-ending-style` (exit). Each recipe declares its `transition`,
256
- * resting state, and enter/exit state in one `@utility` (the `utilities`
257
- * layer) so `group-has` overrides (also utilities) win over the resting
258
- * state by normal cascade order. Co-locating `transition` here is only safe
259
- * because the library ships no `transition-all` nothing else in the
260
- * utilities layer sets `transition` on a recipe-bearing element and clobbers
261
- * it. (If `transition-all` ever returns, these break; keep them specific.)
334
+ * and `data-ending-style` (exit). Each recipe's resting + enter/exit state
335
+ * lives in `@utility` (the `utilities` layer) so `group-has` overrides (also
336
+ * utilities) win over the resting state by normal cascade order.
337
+ *
338
+ * The `transition` is declared UNLAYERED (right after these blocks), NOT
339
+ * inside `@utility`. Recipes routinely sit on elements that ALSO carry a
340
+ * Tailwind `transition-*` utility e.g. the combobox chevron is an
341
+ * `InputGroupButton`, i.e. a `Button` with `transition-[color,…]`. Both set
342
+ * the `transition` property; in the SAME layer the Button's wins and the
343
+ * recipe animates instantly (no transition — the bug this guards against).
344
+ * An unlayered declaration beats ANY layered `transition-*` per the CSS
345
+ * cascade, with NO `!important` (banned — see feedback_security). The
346
+ * resting/enter/exit declarations stay layered so `group-has` still wins.
262
347
  *
263
348
  * `0.001px` resting blur defeats Lightning CSS's `blur(0) → blur()`
264
349
  * minification (which emits invalid CSS); it is visually equivalent to 0. */
265
350
 
266
- /* `motion-fade` — opacity + blur. For simple show/hide on small surfaces. */
267
- @utility motion-fade {
268
- transition: opacity var(--duration) var(--ease),
269
- filter var(--duration) var(--ease);
270
- filter: blur(0.001px);
351
+ /* `motion-scrim` — opacity only, no blur. For full-screen backdrops: animating
352
+ * blur across the whole viewport janks (a flat dim shows none anyway). The
353
+ * scrim only fades its dim — the frosted-glass look comes from a STATIC
354
+ * `backdrop-filter` on the backdrop, faded by this opacity (not an animated
355
+ * `filter`, which is what would jank). */
356
+ @utility motion-scrim {
271
357
  &[data-starting-style],
272
358
  &[data-ending-style] {
273
359
  opacity: 0;
274
- filter: blur(var(--blur));
275
360
  }
276
361
  }
277
362
 
278
- /* `motion-scale`opacity + blur + scale-down. For icon-sized targets. */
279
- @utility motion-scale {
280
- transition: opacity var(--duration) var(--ease),
281
- filter var(--duration) var(--ease),
282
- scale var(--duration) var(--ease);
363
+ /* Enter/exit recipes THREE idioms, each tiered sm/md/lg.
364
+ * `motion-scale-*` ZOOMs (opacity + blur + scale) — icons (sm), dialogs (lg).
365
+ * `motion-slide-*` SLIDES (opacity + blur + directional translate) — popups (md).
366
+ * `motion-fade-*` FADES in place (opacity + blur, no transform).
367
+ * Tiers: sm = controls/icons · md = popups · lg = dialogs. Pick ONE idiom +
368
+ * tier per surface (don't compose — each declares its own unlayered transition).
369
+ * Companion idioms: `motion-color` (controls) and `motion-scrim` (backdrops). */
370
+ @utility motion-scale-sm {
371
+ filter: blur(0.001px);
372
+ scale: 1;
373
+ &[data-starting-style],
374
+ &[data-ending-style] {
375
+ opacity: 0;
376
+ filter: blur(var(--blur-sm));
377
+ scale: var(--scale-sm);
378
+ }
379
+ }
380
+ @utility motion-scale-md {
381
+ filter: blur(0.001px);
382
+ scale: 1;
383
+ &[data-starting-style],
384
+ &[data-ending-style] {
385
+ opacity: 0;
386
+ filter: blur(var(--blur-md));
387
+ scale: var(--scale-md);
388
+ }
389
+ }
390
+ @utility motion-scale-lg {
283
391
  filter: blur(0.001px);
284
392
  scale: 1;
285
393
  &[data-starting-style],
286
394
  &[data-ending-style] {
287
395
  opacity: 0;
288
- filter: blur(var(--blur));
289
- scale: var(--scale);
396
+ filter: blur(var(--blur-lg));
397
+ scale: var(--scale-lg);
398
+ }
399
+ }
400
+
401
+ /* `motion-slide-*` — directional slide: the surface starts offset AWAY from its
402
+ * trigger's `data-[side]` (Base UI sets it) and settles toward it; default /
403
+ * side=bottom rises up. Opacity + blur + translate (`--offset-*`), NO scale. */
404
+ @utility motion-slide-sm {
405
+ filter: blur(0.001px);
406
+ translate: 0;
407
+ &[data-starting-style],
408
+ &[data-ending-style] {
409
+ opacity: 0;
410
+ filter: blur(var(--blur-sm));
411
+ translate: 0 var(--offset-sm);
412
+ }
413
+ &[data-side=top][data-starting-style],
414
+ &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset-sm) * -1); }
415
+ &[data-side=left][data-starting-style],
416
+ &[data-side=left][data-ending-style],
417
+ &[data-side=inline-start][data-starting-style],
418
+ &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset-sm) * -1) 0; }
419
+ &[data-side=right][data-starting-style],
420
+ &[data-side=right][data-ending-style],
421
+ &[data-side=inline-end][data-starting-style],
422
+ &[data-side=inline-end][data-ending-style] { translate: var(--offset-sm) 0; }
423
+ }
424
+ @utility motion-slide-md {
425
+ filter: blur(0.001px);
426
+ translate: 0;
427
+ &[data-starting-style],
428
+ &[data-ending-style] {
429
+ opacity: 0;
430
+ filter: blur(var(--blur-md));
431
+ translate: 0 var(--offset-md); /* default + side=bottom: start lower, rise up */
432
+ }
433
+ &[data-side=top][data-starting-style],
434
+ &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset-md) * -1); }
435
+ &[data-side=left][data-starting-style],
436
+ &[data-side=left][data-ending-style],
437
+ &[data-side=inline-start][data-starting-style],
438
+ &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset-md) * -1) 0; }
439
+ &[data-side=right][data-starting-style],
440
+ &[data-side=right][data-ending-style],
441
+ &[data-side=inline-end][data-starting-style],
442
+ &[data-side=inline-end][data-ending-style] { translate: var(--offset-md) 0; }
443
+ }
444
+ @utility motion-slide-lg {
445
+ filter: blur(0.001px);
446
+ translate: 0;
447
+ &[data-starting-style],
448
+ &[data-ending-style] {
449
+ opacity: 0;
450
+ filter: blur(var(--blur-lg));
451
+ translate: 0 var(--offset-lg);
452
+ }
453
+ &[data-side=top][data-starting-style],
454
+ &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset-lg) * -1); }
455
+ &[data-side=left][data-starting-style],
456
+ &[data-side=left][data-ending-style],
457
+ &[data-side=inline-start][data-starting-style],
458
+ &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset-lg) * -1) 0; }
459
+ &[data-side=right][data-starting-style],
460
+ &[data-side=right][data-ending-style],
461
+ &[data-side=inline-end][data-starting-style],
462
+ &[data-side=inline-end][data-ending-style] { translate: var(--offset-lg) 0; }
463
+ }
464
+
465
+ /* `motion-fade-*` — fade in place: opacity + blur, no transform. The signature
466
+ * blur-fade for content that should appear without moving or scaling. */
467
+ @utility motion-fade-sm {
468
+ filter: blur(0.001px);
469
+ &[data-starting-style],
470
+ &[data-ending-style] {
471
+ opacity: 0;
472
+ filter: blur(var(--blur-sm));
473
+ }
474
+ }
475
+ @utility motion-fade-md {
476
+ filter: blur(0.001px);
477
+ &[data-starting-style],
478
+ &[data-ending-style] {
479
+ opacity: 0;
480
+ filter: blur(var(--blur-md));
481
+ }
482
+ }
483
+ @utility motion-fade-lg {
484
+ filter: blur(0.001px);
485
+ &[data-starting-style],
486
+ &[data-ending-style] {
487
+ opacity: 0;
488
+ filter: blur(var(--blur-lg));
290
489
  }
291
490
  }
292
491
 
293
- /* `motion-rise` — opacity + blur + translate-y, spring ease. For larger
294
- * content surfaces (alerts, menus, cards)spring overshoot only reads
295
- * with translate. */
296
- @utility motion-rise {
297
- transition: opacity var(--duration) var(--spring),
298
- filter var(--duration) var(--spring),
299
- translate var(--duration) var(--spring);
492
+ /* `motion-pop-md` — COMBINED idiom: scale + slide together (dropdowns "pop up
493
+ * while zooming"). The one surface that wants two idioms so it's a single
494
+ * recipe with one transition. Don't try `motion-scale-md motion-slide-md`: their
495
+ * transitions collide and scale won't animate. md only (no sm/lg consumer). */
496
+ @utility motion-pop-md {
300
497
  filter: blur(0.001px);
498
+ scale: 1;
301
499
  translate: 0;
302
500
  &[data-starting-style],
303
501
  &[data-ending-style] {
304
502
  opacity: 0;
305
- filter: blur(var(--blur));
306
- translate: 0 var(--offset);
503
+ filter: blur(var(--blur-md));
504
+ scale: var(--scale-md);
505
+ translate: 0 var(--offset-md); /* default + side=bottom: start lower, rise up */
307
506
  }
507
+ &[data-side=top][data-starting-style],
508
+ &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset-md) * -1); }
509
+ &[data-side=left][data-starting-style],
510
+ &[data-side=left][data-ending-style],
511
+ &[data-side=inline-start][data-starting-style],
512
+ &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset-md) * -1) 0; }
513
+ &[data-side=right][data-starting-style],
514
+ &[data-side=right][data-ending-style],
515
+ &[data-side=inline-end][data-starting-style],
516
+ &[data-side=inline-end][data-ending-style] { translate: var(--offset-md) 0; }
517
+ }
518
+
519
+ /* Recipe transitions — UNLAYERED on purpose (see note above): beats any
520
+ * layered `transition-*` utility on the same element without `!important`. */
521
+ .motion-scrim {
522
+ transition: opacity var(--duration-lg) var(--ease);
523
+ }
524
+ .motion-scale-sm {
525
+ transition: opacity var(--duration-sm) var(--ease),
526
+ filter var(--duration-sm) var(--ease),
527
+ scale var(--duration-sm) var(--ease);
528
+ }
529
+ .motion-scale-md {
530
+ transition: opacity var(--duration-md) var(--ease),
531
+ filter var(--duration-md) var(--ease),
532
+ scale var(--duration-md) var(--ease);
533
+ }
534
+ .motion-scale-lg {
535
+ transition: opacity var(--duration-lg) var(--ease),
536
+ filter var(--duration-lg) var(--ease),
537
+ scale var(--duration-lg) var(--ease);
538
+ }
539
+ .motion-slide-sm {
540
+ transition: opacity var(--duration-sm) var(--ease),
541
+ filter var(--duration-sm) var(--ease),
542
+ translate var(--duration-sm) var(--ease);
543
+ }
544
+ .motion-slide-md {
545
+ transition: opacity var(--duration-md) var(--ease),
546
+ filter var(--duration-md) var(--ease),
547
+ translate var(--duration-md) var(--ease);
548
+ }
549
+ .motion-slide-lg {
550
+ transition: opacity var(--duration-lg) var(--ease),
551
+ filter var(--duration-lg) var(--ease),
552
+ translate var(--duration-lg) var(--ease);
553
+ }
554
+ .motion-fade-sm {
555
+ transition: opacity var(--duration-sm) var(--ease),
556
+ filter var(--duration-sm) var(--ease);
557
+ }
558
+ .motion-fade-md {
559
+ transition: opacity var(--duration-md) var(--ease),
560
+ filter var(--duration-md) var(--ease);
561
+ }
562
+ .motion-fade-lg {
563
+ transition: opacity var(--duration-lg) var(--ease),
564
+ filter var(--duration-lg) var(--ease);
565
+ }
566
+ .motion-pop-md {
567
+ transition: opacity var(--duration-md) var(--ease),
568
+ filter var(--duration-md) var(--ease),
569
+ scale var(--duration-md) var(--ease),
570
+ translate var(--duration-md) var(--ease);
308
571
  }
309
572
 
310
573
  /* ---------------------------------------------------------------------------
@@ -312,10 +575,37 @@
312
575
  * ------------------------------------------------------------------------- */
313
576
  @media (prefers-reduced-motion: reduce) {
314
577
  :root {
315
- --duration: 0ms;
316
- --blur: 0px;
317
- --scale: 1;
318
- --offset: 0px;
319
- --stagger: 0ms;
578
+ --duration-sm: 0ms; --duration-md: 0ms; --duration-lg: 0ms;
579
+ --blur-sm: 0px; --blur-md: 0px; --blur-lg: 0px;
580
+ --scale-sm: 1; --scale-md: 1; --scale-lg: 1;
581
+ --offset-sm: 0px; --offset-md: 0px; --offset-lg: 0px;
582
+ --stagger-sm: 0ms; --stagger-md: 0ms; --stagger-lg: 0ms;
320
583
  }
321
584
  }
585
+
586
+ /* ---------------------------------------------------------------------------
587
+ * Keyframe utilities — self-hosted (replaces the former `tw-animate-css` dep,
588
+ * now removed). Only the three the library actually uses; `animate-spin` /
589
+ * `animate-pulse` come from Tailwind core. Registering via `@theme` generates
590
+ * the `animate-accordion-down` / `animate-accordion-up` / `animate-caret-blink`
591
+ * utilities. Accordion height runs on our motion tokens (`md` tier + the curve)
592
+ * and uses Base UI's `--accordion-panel-height` (the Radix var tw-animate
593
+ * targeted is never set under Base UI). Collapses under reduced motion via
594
+ * `--duration-md`. */
595
+ @theme {
596
+ --animate-accordion-down: accordion-down var(--duration-md) var(--ease);
597
+ --animate-accordion-up: accordion-up var(--duration-md) var(--ease);
598
+ --animate-caret-blink: caret-blink 1.25s ease-out infinite;
599
+ }
600
+ @keyframes accordion-down {
601
+ from { height: 0; }
602
+ to { height: var(--accordion-panel-height); }
603
+ }
604
+ @keyframes accordion-up {
605
+ from { height: var(--accordion-panel-height); }
606
+ to { height: 0; }
607
+ }
608
+ @keyframes caret-blink {
609
+ 0%, 70%, 100% { opacity: 1; }
610
+ 20%, 50% { opacity: 0; }
611
+ }