@waveso/ui 0.5.0 → 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 (102) 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 +4 -4
  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.map +1 -1
  10. package/dist/animate.js +1 -2
  11. package/dist/animate.js.map +1 -1
  12. package/dist/autocomplete.js +8 -8
  13. package/dist/autocomplete.js.map +1 -1
  14. package/dist/avatar.js +5 -5
  15. package/dist/avatar.js.map +1 -1
  16. package/dist/badge.d.ts +1 -1
  17. package/dist/badge.d.ts.map +1 -1
  18. package/dist/badge.js +5 -6
  19. package/dist/badge.js.map +1 -1
  20. package/dist/breadcrumb.js +3 -3
  21. package/dist/breadcrumb.js.map +1 -1
  22. package/dist/button-group.js +5 -5
  23. package/dist/button-group.js.map +1 -1
  24. package/dist/button.d.ts +1 -1
  25. package/dist/button.d.ts.map +1 -1
  26. package/dist/button.js +9 -10
  27. package/dist/button.js.map +1 -1
  28. package/dist/card.js +4 -4
  29. package/dist/card.js.map +1 -1
  30. package/dist/checkbox.js +1 -1
  31. package/dist/checkbox.js.map +1 -1
  32. package/dist/combobox.d.ts.map +1 -1
  33. package/dist/combobox.js +10 -10
  34. package/dist/combobox.js.map +1 -1
  35. package/dist/context-menu.js +9 -9
  36. package/dist/context-menu.js.map +1 -1
  37. package/dist/dialog.d.ts.map +1 -1
  38. package/dist/dialog.js +4 -4
  39. package/dist/dialog.js.map +1 -1
  40. package/dist/drawer.js +4 -4
  41. package/dist/drawer.js.map +1 -1
  42. package/dist/field.js +2 -2
  43. package/dist/field.js.map +1 -1
  44. package/dist/form.js +2 -2
  45. package/dist/form.js.map +1 -1
  46. package/dist/infinite-scroll.js +2 -2
  47. package/dist/infinite-scroll.js.map +1 -1
  48. package/dist/input-group.d.ts +1 -1
  49. package/dist/input-group.js +5 -5
  50. package/dist/input-group.js.map +1 -1
  51. package/dist/input-otp.js +3 -3
  52. package/dist/input-otp.js.map +1 -1
  53. package/dist/input.js +1 -1
  54. package/dist/input.js.map +1 -1
  55. package/dist/item.js +4 -4
  56. package/dist/item.js.map +1 -1
  57. package/dist/kbd.js +1 -1
  58. package/dist/kbd.js.map +1 -1
  59. package/dist/menu.js +9 -9
  60. package/dist/menu.js.map +1 -1
  61. package/dist/menubar.js +1 -1
  62. package/dist/menubar.js.map +1 -1
  63. package/dist/popover.js +2 -2
  64. package/dist/popover.js.map +1 -1
  65. package/dist/preview-card.js +1 -1
  66. package/dist/preview-card.js.map +1 -1
  67. package/dist/progress.js +2 -2
  68. package/dist/progress.js.map +1 -1
  69. package/dist/radio.js +2 -2
  70. package/dist/radio.js.map +1 -1
  71. package/dist/scroll-area.js +2 -2
  72. package/dist/scroll-area.js.map +1 -1
  73. package/dist/select.js +8 -8
  74. package/dist/select.js.map +1 -1
  75. package/dist/separator.js +1 -1
  76. package/dist/separator.js.map +1 -1
  77. package/dist/sidebar.js +19 -19
  78. package/dist/sidebar.js.map +1 -1
  79. package/dist/skeleton.js +1 -1
  80. package/dist/skeleton.js.map +1 -1
  81. package/dist/slider.js +2 -2
  82. package/dist/slider.js.map +1 -1
  83. package/dist/styles.css +405 -173
  84. package/dist/switch.js +2 -2
  85. package/dist/switch.js.map +1 -1
  86. package/dist/table.js +4 -4
  87. package/dist/table.js.map +1 -1
  88. package/dist/tabs.js +3 -3
  89. package/dist/tabs.js.map +1 -1
  90. package/dist/textarea.js +1 -1
  91. package/dist/textarea.js.map +1 -1
  92. package/dist/toast.d.ts +3 -3
  93. package/dist/toast.d.ts.map +1 -1
  94. package/dist/toast.js +37 -10
  95. package/dist/toast.js.map +1 -1
  96. package/dist/toggle-group.js +2 -2
  97. package/dist/toggle-group.js.map +1 -1
  98. package/dist/toggle.js +3 -3
  99. package/dist/toggle.js.map +1 -1
  100. package/dist/tooltip.js +1 -1
  101. package/dist/tooltip.js.map +1 -1
  102. 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,15 +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: 200ms;
103
- --ease: cubic-bezier(0, 0.55, 0.45, 1); /* the one curve every recipe + controls (circ-out) */
104
- --blur: 8px; /* enter/exit surface blur; icons derive ¼ (see motion-scale) */
105
- --scale: 0.95; /* enter/exit scale-down */
106
- --offset: 4px; /* enter/exit translate offset */
107
- --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 SYSTEM 5 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;
108
166
  }
109
167
 
110
168
  /* ---------------------------------------------------------------------------
@@ -112,33 +170,36 @@
112
170
  * ------------------------------------------------------------------------- */
113
171
 
114
172
  .dark {
115
- /* Core */
116
- --background: oklch(0.145 0 0);
117
- --foreground: oklch(0.985 0 0);
118
- --card: oklch(0.205 0 0);
119
- --card-foreground: oklch(0.985 0 0);
120
- --popover: oklch(0.205 0 0);
121
- --popover-foreground: oklch(0.985 0 0);
122
-
123
- /* Brand */
124
- --primary: oklch(0.42 0.18 266);
125
- --primary-foreground: oklch(0.97 0.014 254.604);
126
- --secondary: oklch(0.274 0.006 286.033);
127
- --secondary-foreground: oklch(0.985 0 0);
128
- --accent: oklch(0.42 0.18 266);
129
- --accent-foreground: oklch(0.97 0.014 254.604);
130
-
131
- /* Neutral */
132
- --muted: oklch(0.269 0 0);
133
- --muted-foreground: oklch(0.708 0 0);
134
- --border: oklch(1 0 0 / 10%);
135
- --input: oklch(1 0 0 / 15%);
136
- --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;
137
197
 
138
198
  /* Status */
139
- --destructive: oklch(0.704 0.191 22.216);
140
- --success: oklch(0.72 0.17 162);
141
- --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);
142
203
 
143
204
  /* Charts */
144
205
  --chart-1: oklch(0.75 0.1 252);
@@ -152,52 +213,47 @@
152
213
  --presence-away: oklch(0.75 0.15 85);
153
214
  --presence-busy: oklch(0.65 0.2 27);
154
215
  --presence-invisible: oklch(0.6 0 0);
155
-
156
- /* Sidebar */
157
- --sidebar: oklch(0.205 0 0);
158
- --sidebar-foreground: oklch(0.985 0 0);
159
- --sidebar-primary: oklch(0.623 0.214 259.815);
160
- --sidebar-primary-foreground: oklch(0.97 0.014 254.604);
161
- --sidebar-accent: oklch(0.42 0.18 266);
162
- --sidebar-accent-foreground: oklch(0.97 0.014 254.604);
163
- --sidebar-border: oklch(1 0 0 / 10%);
164
- --sidebar-ring: oklch(0.556 0 0);
165
216
  }
166
217
 
167
218
  /* ---------------------------------------------------------------------------
168
219
  * Tailwind v4 theme mapping — maps CSS variables to Tailwind color tokens.
169
220
  * Consumers MUST include this for Tailwind classes like `bg-primary`,
170
- * `text-muted-foreground`, `border-sidebar-border` etc. to work.
221
+ * `text-muted`, `border-line` etc. to work.
171
222
  * ------------------------------------------------------------------------- */
172
223
 
173
224
  @theme inline {
174
- /* Core */
175
- --color-background: var(--background);
176
- --color-foreground: var(--foreground);
177
- --color-card: var(--card);
178
- --color-card-foreground: var(--card-foreground);
179
- --color-popover: var(--popover);
180
- --color-popover-foreground: var(--popover-foreground);
181
-
182
- /* 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. */
183
241
  --color-primary: var(--primary);
184
- --color-primary-foreground: var(--primary-foreground);
185
242
  --color-secondary: var(--secondary);
186
- --color-secondary-foreground: var(--secondary-foreground);
187
243
  --color-accent: var(--accent);
188
- --color-accent-foreground: var(--accent-foreground);
189
244
 
190
- /* Neutral */
191
- --color-muted: var(--muted);
192
- --color-muted-foreground: var(--muted-foreground);
193
- --color-border: var(--border);
194
- --color-input: var(--input);
195
- --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);
196
251
 
197
252
  /* Status */
198
253
  --color-destructive: var(--destructive);
199
254
  --color-success: var(--success);
200
255
  --color-warning: var(--warning);
256
+ --color-info: var(--info);
201
257
 
202
258
  /* Charts */
203
259
  --color-chart-1: var(--chart-1);
@@ -212,25 +268,12 @@
212
268
  --color-presence-busy: var(--presence-busy);
213
269
  --color-presence-invisible: var(--presence-invisible);
214
270
 
215
- /* Sidebar */
216
- --color-sidebar: var(--sidebar);
217
- --color-sidebar-foreground: var(--sidebar-foreground);
218
- --color-sidebar-primary: var(--sidebar-primary);
219
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
220
- --color-sidebar-accent: var(--sidebar-accent);
221
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
222
- --color-sidebar-border: var(--sidebar-border);
223
- --color-sidebar-ring: var(--sidebar-ring);
224
-
225
- /* Radius */
226
- --radius-xs: calc(var(--radius) - 6px);
227
- --radius-sm: calc(var(--radius) - 4px);
228
- --radius-md: calc(var(--radius) - 2px);
229
- --radius-lg: var(--radius);
230
- --radius-xl: calc(var(--radius) + 4px);
231
- --radius-2xl: calc(var(--radius) + 8px);
232
- --radius-3xl: calc(var(--radius) + 12px);
233
- --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) */
234
277
 
235
278
  /* Typography — passthroughs so consumers' `--font-sans` /
236
279
  * `--font-mono` flow through to Tailwind's `font-sans` /
@@ -239,6 +282,31 @@
239
282
  --font-mono: var(--font-mono);
240
283
  }
241
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
+
242
310
  /* ---------------------------------------------------------------------------
243
311
  * Utilities
244
312
  * ------------------------------------------------------------------------- */
@@ -252,14 +320,14 @@
252
320
 
253
321
  /* `motion-color` — interaction-state transition for controls: smoothly
254
322
  * animates color / background / border / ring (box-shadow) on hover, focus,
255
- * pressed, checked, etc. A plain layered `@utility`, so the unlayered
256
- * enter/exit recipes below win over it when both land on one element (e.g. a
257
- * button that also carries `motion-scale`). */
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`). */
258
326
  @utility motion-color {
259
- transition: color var(--duration) var(--ease),
260
- background-color var(--duration) var(--ease),
261
- border-color var(--duration) var(--ease),
262
- box-shadow var(--duration) var(--ease);
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);
263
331
  }
264
332
 
265
333
  /* Motion recipes — Base UI primitives emit `data-starting-style` (enter)
@@ -292,77 +360,214 @@
292
360
  }
293
361
  }
294
362
 
295
- /* `motion-fade`opacity + blur, no transform. The signature fade for content
296
- * that should blur in/out without moving use anywhere a plain fade is wanted.
297
- * Full `--blur`. NOT for full-screen scrims (use `motion-scrim`). */
298
- @utility motion-fade {
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 {
299
371
  filter: blur(0.001px);
372
+ scale: 1;
300
373
  &[data-starting-style],
301
374
  &[data-ending-style] {
302
375
  opacity: 0;
303
- filter: blur(var(--blur));
376
+ filter: blur(var(--blur-sm));
377
+ scale: var(--scale-sm);
304
378
  }
305
379
  }
306
-
307
- /* `motion-scale` — opacity + blur + scale-down. For centered content (dialog /
308
- * alert-dialog, which use the full `--blur`) and small / icon targets. Icons set
309
- * an absolute `--motion-scale-blur` (e.g. `2px`) to opt out of the surface blur:
310
- * a ~16px icon needs a small *fixed* blur, independent of the surface `--blur`
311
- * (so retuning `--blur` never drifts the icons). */
312
- @utility motion-scale {
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 {
313
391
  filter: blur(0.001px);
314
392
  scale: 1;
315
393
  &[data-starting-style],
316
394
  &[data-ending-style] {
317
395
  opacity: 0;
318
- filter: blur(var(--motion-scale-blur, var(--blur)));
319
- scale: var(--scale);
396
+ filter: blur(var(--blur-lg));
397
+ scale: var(--scale-lg);
320
398
  }
321
399
  }
322
400
 
323
- /* `motion-slide`opacity + blur + DIRECTIONAL translate, circ-out. For
324
- * popups that "open up" (popover, menu, select, tooltip, …). The popup starts
325
- * offset AWAY from its trigger's side (Base UI sets `data-[side]` on the popup)
326
- * and settles toward it; default / `side=bottom` rises up. */
327
- @utility motion-slide {
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 {
328
425
  filter: blur(0.001px);
329
426
  translate: 0;
330
427
  &[data-starting-style],
331
428
  &[data-ending-style] {
332
429
  opacity: 0;
333
- filter: blur(var(--blur));
334
- translate: 0 var(--offset); /* default + side=bottom: start lower, rise up */
430
+ filter: blur(var(--blur-md));
431
+ translate: 0 var(--offset-md); /* default + side=bottom: start lower, rise up */
335
432
  }
336
433
  &[data-side=top][data-starting-style],
337
- &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset) * -1); }
434
+ &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset-md) * -1); }
338
435
  &[data-side=left][data-starting-style],
339
436
  &[data-side=left][data-ending-style],
340
437
  &[data-side=inline-start][data-starting-style],
341
- &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset) * -1) 0; }
438
+ &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset-md) * -1) 0; }
342
439
  &[data-side=right][data-starting-style],
343
440
  &[data-side=right][data-ending-style],
344
441
  &[data-side=inline-end][data-starting-style],
345
- &[data-side=inline-end][data-ending-style] { translate: var(--offset) 0; }
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));
489
+ }
490
+ }
491
+
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 {
497
+ filter: blur(0.001px);
498
+ scale: 1;
499
+ translate: 0;
500
+ &[data-starting-style],
501
+ &[data-ending-style] {
502
+ opacity: 0;
503
+ filter: blur(var(--blur-md));
504
+ scale: var(--scale-md);
505
+ translate: 0 var(--offset-md); /* default + side=bottom: start lower, rise up */
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; }
346
517
  }
347
518
 
348
519
  /* Recipe transitions — UNLAYERED on purpose (see note above): beats any
349
520
  * layered `transition-*` utility on the same element without `!important`. */
350
521
  .motion-scrim {
351
- transition: opacity var(--duration) var(--ease);
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);
352
528
  }
353
- .motion-fade {
354
- transition: opacity var(--duration) var(--ease),
355
- filter var(--duration) var(--ease);
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);
356
533
  }
357
- .motion-scale {
358
- transition: opacity var(--duration) var(--ease),
359
- filter var(--duration) var(--ease),
360
- scale var(--duration) var(--ease);
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);
361
538
  }
362
- .motion-slide {
363
- transition: opacity var(--duration) var(--ease),
364
- filter var(--duration) var(--ease),
365
- translate var(--duration) var(--ease);
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);
366
571
  }
367
572
 
368
573
  /* ---------------------------------------------------------------------------
@@ -370,10 +575,37 @@
370
575
  * ------------------------------------------------------------------------- */
371
576
  @media (prefers-reduced-motion: reduce) {
372
577
  :root {
373
- --duration: 0ms;
374
- --blur: 0px;
375
- --scale: 1;
376
- --offset: 0px;
377
- --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;
378
583
  }
379
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
+ }