@waveso/ui 0.5.0 → 0.7.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 (104) hide show
  1. package/README.md +152 -68
  2. package/dist/accordion.js +3 -3
  3. package/dist/accordion.js.map +1 -1
  4. package/dist/action-bar.js +2 -2
  5. package/dist/action-bar.js.map +1 -1
  6. package/dist/alert-dialog.js +4 -4
  7. package/dist/alert-dialog.js.map +1 -1
  8. package/dist/alert.js +5 -5
  9. package/dist/alert.js.map +1 -1
  10. package/dist/animate.d.ts.map +1 -1
  11. package/dist/animate.js +1 -2
  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 +2 -2
  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 +4 -4
  40. package/dist/dialog.js.map +1 -1
  41. package/dist/drawer.js +4 -4
  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.d.ts +1 -1
  57. package/dist/item.js +4 -4
  58. package/dist/item.js.map +1 -1
  59. package/dist/kbd.js +1 -1
  60. package/dist/kbd.js.map +1 -1
  61. package/dist/menu.js +9 -9
  62. package/dist/menu.js.map +1 -1
  63. package/dist/menubar.js +1 -1
  64. package/dist/menubar.js.map +1 -1
  65. package/dist/popover.js +2 -2
  66. package/dist/popover.js.map +1 -1
  67. package/dist/preview-card.js +1 -1
  68. package/dist/preview-card.js.map +1 -1
  69. package/dist/progress.js +2 -2
  70. package/dist/progress.js.map +1 -1
  71. package/dist/radio.js +2 -2
  72. package/dist/radio.js.map +1 -1
  73. package/dist/scroll-area.js +2 -2
  74. package/dist/scroll-area.js.map +1 -1
  75. package/dist/select.js +8 -8
  76. package/dist/select.js.map +1 -1
  77. package/dist/separator.js +1 -1
  78. package/dist/separator.js.map +1 -1
  79. package/dist/sidebar.js +19 -19
  80. package/dist/sidebar.js.map +1 -1
  81. package/dist/skeleton.js +1 -1
  82. package/dist/skeleton.js.map +1 -1
  83. package/dist/slider.js +2 -2
  84. package/dist/slider.js.map +1 -1
  85. package/dist/styles.css +493 -202
  86. package/dist/switch.js +2 -2
  87. package/dist/switch.js.map +1 -1
  88. package/dist/table.js +5 -5
  89. package/dist/table.js.map +1 -1
  90. package/dist/tabs.js +3 -3
  91. package/dist/tabs.js.map +1 -1
  92. package/dist/textarea.js +1 -1
  93. package/dist/textarea.js.map +1 -1
  94. package/dist/toast.d.ts +3 -3
  95. package/dist/toast.d.ts.map +1 -1
  96. package/dist/toast.js +37 -10
  97. package/dist/toast.js.map +1 -1
  98. package/dist/toggle-group.js +2 -2
  99. package/dist/toggle-group.js.map +1 -1
  100. package/dist/toggle.js +3 -3
  101. package/dist/toggle.js.map +1 -1
  102. package/dist/tooltip.js +1 -1
  103. package/dist/tooltip.js.map +1 -1
  104. package/package.json +9 -20
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,56 +25,146 @@
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
+ /* Wave — brand ramp. 500 = Wave Blue #0074DE.
35
+ * A true cobalt signal: calm, trustable, solid, constant. */
36
+ --wave-50: #EEF8FF;
37
+ --wave-100: #D6EEFF;
38
+ --wave-200: #AEDDFF;
39
+ --wave-300: #7BC3FF;
40
+ --wave-400: #3EA3FF;
41
+ --wave-500: #0074DE;
42
+ --wave-600: #0061C6;
43
+ --wave-700: #004C9F;
44
+ --wave-800: #003976;
45
+ --wave-900: #042954;
46
+ --wave-950: #061C37;
47
+
48
+ /* ── Neutral theme palettes (primitives) ─────────────────────────────────
49
+ * Three complete neutral ramps, one per theme. The theme class on <html>
50
+ * wires the ACTIVE one into the `--ui-*` alias (step 2); the semantic layer
51
+ * reads `--ui-*`, never these directly. `--wave-*` (brand) is shared by all
52
+ * three. Default theme = Graphite.
53
+ * ──────────────────────────────────────────────────────────────────────── */
54
+
55
+ /* Graphite — neutral grey (default) */
56
+ --graphite-50: #FAFAFB;
57
+ --graphite-100: #F3F3F4;
58
+ --graphite-200: #E5E5E8;
59
+ --graphite-300: #D1D2D7;
60
+ --graphite-400: #ABACB2;
61
+ --graphite-500: #8B8C94;
62
+ --graphite-600: #6C6D76;
63
+ --graphite-700: #4C4D55;
64
+ --graphite-800: #3B3C44;
65
+ --graphite-900: #323339;
66
+ --graphite-950: #2C2D32;
67
+
68
+ /* Ink — deep navy surfaces, neutral-cool structure (page #020812, surface #050F1E).
69
+ * Chroma fades from full navy at the dark surfaces to near-neutral at the light
70
+ * end, so text/borders read as a distinct layer rather than melting into navy. */
71
+ --ink-50: #F2F4F6;
72
+ --ink-100: #DFE1E5;
73
+ --ink-200: #C5C9CE;
74
+ --ink-300: #AAAFB6;
75
+ --ink-400: #8C929A;
76
+ --ink-500: #6E747D;
77
+ --ink-600: #4F5763;
78
+ --ink-700: #323D4C;
79
+ --ink-800: #162336;
80
+ --ink-900: #050F1E;
81
+ --ink-950: #020812;
82
+
83
+ /* Paper — cool near-neutral (anchor #EFF0EB @ 200 = foundation) */
84
+ --paper-50: #FCFDFA;
85
+ --paper-100: #F6F7F3;
86
+ --paper-200: #EFF0EB;
87
+ --paper-300: #D7D8D2;
88
+ --paper-400: #AAACA5;
89
+ --paper-500: #83847D;
90
+ --paper-600: #63645E;
91
+ --paper-700: #494A44;
92
+ --paper-800: #30312C;
93
+ --paper-900: #1B1C18;
94
+ --paper-950: #0C0D09;
95
+
96
+ /* Active neutral alias — the theme class on <html> repoints these to one of
97
+ * the ramps above (step 3). Default (no theme class) = Graphite. The semantic
98
+ * layer below reads ONLY `--ui-*`; components never touch a ramp directly. */
99
+ --ui-50: var(--graphite-50);
100
+ --ui-100: var(--graphite-100);
101
+ --ui-200: var(--graphite-200);
102
+ --ui-300: var(--graphite-300);
103
+ --ui-400: var(--graphite-400);
104
+ --ui-500: var(--graphite-500);
105
+ --ui-600: var(--graphite-600);
106
+ --ui-700: var(--graphite-700);
107
+ --ui-800: var(--graphite-800);
108
+ --ui-900: var(--graphite-900);
109
+ --ui-950: var(--graphite-950);
110
+
111
+ /* ========================================================================
112
+ * Semantic tokens — light (default). The spec's `.light` block lives in
113
+ * `:root` so consumers get the light theme without needing a `.light` class.
114
+ * ====================================================================== */
115
+
116
+ /* Backgrounds = elevation */
117
+ --foundation: var(--ui-200);
118
+ --surface: var(--ui-100);
119
+ --elevated: var(--ui-50);
120
+
121
+ /* Contrast = emphasis — highest-contrast text → muted → soft. */
122
+ --contrast: var(--ui-950);
123
+ --muted: var(--ui-600);
124
+ --soft: var(--ui-500);
125
+
126
+ /* Inverse — an inverted neutral pair (e.g. the dark tooltip). Aliases, so
127
+ * they auto-flip with `.dark` (var() re-resolves at the use site). */
128
+ --surface-inverse: var(--contrast);
129
+ --contrast-inverse: var(--foundation);
130
+
131
+ /* Brand — `primary` wired to the Wave ramp; `secondary` is the neutral fill
132
+ * (secondary/ghost buttons, hovers, selected) at ion-300; `accent` is the
133
+ * bright signal (Wave Blue 300, the strand now-glow). No per-fill on-color
134
+ * tokens: text on the saturated fills is
135
+ * `text-white`, text on the neutral `secondary` is `text-contrast`. */
136
+ --primary: var(--wave-500);
137
+ --secondary: var(--ui-300);
138
+ --accent: var(--wave-300); /* the bright signal — the strand's "now" cursor glow */
139
+
140
+ /* Borders = structure — mixed from the ACTIVE palette so they auto-match
141
+ * every theme: a hairline of the darkest neutral in light mode. */
142
+ --line: color-mix(in srgb, var(--ui-950) 7%, transparent);
143
+ --edge: color-mix(in srgb, var(--ui-950) 14%, transparent);
144
+ --solid: var(--ui-300);
145
+
146
+ /* Rings = brand themed */
147
+ --focus: var(--primary);
148
+
149
+ /* Shadow — two dials for the layered `shadow-*` scale: `--shadow-color` (HSL
150
+ * components — a deep cool-tinted near-black, never pure #000 which reads
151
+ * heavy) and `--shadow-strength` (per-layer alpha / intensity). Both resolve
152
+ * per :root/.dark, so retinting OR dimming every shadow is one token. For a
153
+ * colored surface set a named tint, e.g. `--shadow-color-primary`. */
154
+ --shadow-color: 220deg 45% 6%;
155
+ --shadow-strength: 0.22;
54
156
 
55
157
  /* 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);
59
-
60
- /* Charts */
61
- --chart-1: oklch(0.809 0.105 251.813);
62
- --chart-2: oklch(0.623 0.214 259.815);
63
- --chart-3: oklch(0.546 0.245 262.881);
64
- --chart-4: oklch(0.488 0.243 264.376);
65
- --chart-5: oklch(0.424 0.199 265.638);
158
+ --destructive: oklch(0.620 0.200 25);
159
+ --success: oklch(0.710 0.185 155); /* cool emerald — distinct from the warning yellow, kept ownable (off the Apple/Tailwind default) */
160
+ --warning: oklch(0.830 0.156 93);
161
+ --info: var(--wave-500); /* the one blue — Wave Blue (= --primary / --focus) */
66
162
 
67
163
  /* Presence */
68
- --presence-online: oklch(0.72 0.19 145);
69
- --presence-away: oklch(0.82 0.17 85);
70
- --presence-busy: oklch(0.63 0.22 27);
71
- --presence-invisible: oklch(0.55 0 0);
72
-
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);
164
+ --presence-online: oklch(0.710 0.185 155); /* = success colour */
165
+ --presence-away: oklch(0.830 0.156 93); /* = warning colour */
166
+ --presence-busy: oklch(0.620 0.200 25); /* = destructive colour */
167
+ --presence-invisible: var(--foundation); /* invisible = the page itself; pair with a border so the dot reads as a hollow ring */
82
168
 
83
169
  /* Radius */
84
170
  --radius: 0.625rem;
@@ -96,15 +182,33 @@
96
182
  * native / HIG behavior; real links keep their own pointer regardless. */
97
183
  --cursor-clickable: pointer;
98
184
 
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 */
185
+ /* Motion — tokens consumed by the `motion-*` recipes (see Utilities). Override
186
+ * any value in your own :root. `prefers-reduced-motion` zeroes them (bottom of
187
+ * file), so every recipe collapses safely.
188
+ *
189
+ * THE SYSTEM 5 scalable magnitudes × 3 tiers (sm/md/lg) + 1 fixed signature:
190
+ * scalable --duration · --blur · --scale · --offset · --stagger
191
+ * fixed → --ease (the one curve — never tiered)
192
+ * tiers sm = controls/icons · md = popups · lg = dialogs
193
+ * sm/lg zoom in place; md also slides. `--offset`/`--stagger` sm+lg are
194
+ * defined ahead of use (wired per surface as we build them out). */
195
+ --ease: cubic-bezier(0.22, 1, 0.36, 1); /* the one curve — never tiered */
196
+
197
+ --duration-sm: 150ms; /* controls, icons */
198
+ --duration-md: 200ms; /* popups */
199
+ --duration-lg: 250ms; /* dialogs */
200
+ --blur-sm: 2px;
201
+ --blur-md: 4px;
202
+ --blur-lg: 8px;
203
+ --scale-sm: 0.95;
204
+ --scale-md: 0.9;
205
+ --scale-lg: 0.85;
206
+ --offset-sm: 2px; /* item/icon nudge */
207
+ --offset-md: 4px; /* popup slide */
208
+ --offset-lg: 8px;
209
+ --stagger-sm: 30ms; /* list-item delay */
210
+ --stagger-md: 60ms;
211
+ --stagger-lg: 90ms;
108
212
  }
109
213
 
110
214
  /* ---------------------------------------------------------------------------
@@ -112,99 +216,110 @@
112
216
  * ------------------------------------------------------------------------- */
113
217
 
114
218
  .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);
219
+ /* Backgrounds = elevation */
220
+ --foundation: var(--ui-950);
221
+ --surface: var(--ui-900);
222
+ --elevated: var(--ui-800);
223
+
224
+ /* Contrast = emphasis */
225
+ --contrast: var(--ui-100);
226
+ --muted: var(--ui-400);
227
+ --soft: var(--ui-600);
228
+
229
+ /* Brand only `secondary` differs from light; mirrored to ion-700 so the
230
+ * neutral fill stands out against the dark surfaces. primary / accent /
231
+ * inverse / focus auto-flip or stay constant via the `:root` definitions. */
232
+ --secondary: var(--ui-700);
233
+
234
+ /* Borders = structure — a hairline of the lightest neutral (auto-matches theme). */
235
+ --line: color-mix(in srgb, var(--ui-50) 5%, transparent);
236
+ --edge: color-mix(in srgb, var(--ui-50) 13%, transparent);
237
+ --solid: var(--ui-700);
238
+
239
+ /* Shadow — darker tint, lower strength: a near-black shadow on a dark
240
+ * surface reads heavier than on white, so dim it. */
241
+ --shadow-color: 220deg 40% 3%;
242
+ --shadow-strength: 0.18;
137
243
 
138
244
  /* 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);
142
-
143
- /* Charts */
144
- --chart-1: oklch(0.75 0.1 252);
145
- --chart-2: oklch(0.58 0.2 260);
146
- --chart-3: oklch(0.5 0.22 263);
147
- --chart-4: oklch(0.45 0.2 265);
148
- --chart-5: oklch(0.38 0.17 266);
245
+ /* Slightly BRIGHTER than the :root (light) values.
246
+ * Never darker, or the colour recedes on dark surfaces (the old reversal bug). */
247
+ --destructive: oklch(0.630 0.200 25);
248
+ --success: oklch(0.735 0.185 155); /* green A, hue-aware lift (+.025) brighter than light */
249
+ --warning: oklch(0.850 0.156 93);
250
+ --info: var(--wave-400); /* brighter Wave Blue for legible text-info on dark surfaces */
149
251
 
150
252
  /* Presence */
151
- --presence-online: oklch(0.65 0.17 145);
152
- --presence-away: oklch(0.75 0.15 85);
153
- --presence-busy: oklch(0.65 0.2 27);
154
- --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);
253
+ --presence-online: oklch(0.735 0.185 155); /* brighter on dark, like status */
254
+ --presence-away: oklch(0.850 0.156 93);
255
+ --presence-busy: oklch(0.630 0.200 25);
256
+ --presence-invisible: var(--foundation); /* re-declared so it re-resolves to the dark foundation */
257
+ }
258
+
259
+ /* ---------------------------------------------------------------------------
260
+ * Theme presets — each repoints the `--ui-*` alias to one neutral ramp. Put a
261
+ * class on a root element: `<html class="theme-paper">`. Graphite is also the bare
262
+ * `:root` default, so `theme-graphite` is optional/explicit. Light vs dark is the
263
+ * separate `.dark` axis — they compose (e.g. `class="theme-paper dark"`).
264
+ * ------------------------------------------------------------------------- */
265
+ .theme-graphite {
266
+ --ui-50: var(--graphite-50); --ui-100: var(--graphite-100); --ui-200: var(--graphite-200);
267
+ --ui-300: var(--graphite-300); --ui-400: var(--graphite-400); --ui-500: var(--graphite-500);
268
+ --ui-600: var(--graphite-600); --ui-700: var(--graphite-700); --ui-800: var(--graphite-800);
269
+ --ui-900: var(--graphite-900); --ui-950: var(--graphite-950);
270
+ }
271
+ .theme-ink {
272
+ --ui-50: var(--ink-50); --ui-100: var(--ink-100); --ui-200: var(--ink-200);
273
+ --ui-300: var(--ink-300); --ui-400: var(--ink-400); --ui-500: var(--ink-500);
274
+ --ui-600: var(--ink-600); --ui-700: var(--ink-700); --ui-800: var(--ink-800);
275
+ --ui-900: var(--ink-900); --ui-950: var(--ink-950);
276
+ }
277
+ .theme-paper {
278
+ --ui-50: var(--paper-50); --ui-100: var(--paper-100); --ui-200: var(--paper-200);
279
+ --ui-300: var(--paper-300); --ui-400: var(--paper-400); --ui-500: var(--paper-500);
280
+ --ui-600: var(--paper-600); --ui-700: var(--paper-700); --ui-800: var(--paper-800);
281
+ --ui-900: var(--paper-900); --ui-950: var(--paper-950);
165
282
  }
166
283
 
167
284
  /* ---------------------------------------------------------------------------
168
285
  * Tailwind v4 theme mapping — maps CSS variables to Tailwind color tokens.
169
286
  * Consumers MUST include this for Tailwind classes like `bg-primary`,
170
- * `text-muted-foreground`, `border-sidebar-border` etc. to work.
287
+ * `text-muted`, `border-line` etc. to work.
171
288
  * ------------------------------------------------------------------------- */
172
289
 
173
290
  @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 */
291
+ /* Surfaces */
292
+ --color-foundation: var(--foundation);
293
+ --color-surface: var(--surface);
294
+ --color-elevated: var(--elevated);
295
+
296
+ /* Contrast / text — `contrast` (highest emphasis) → `muted` → `soft`. */
297
+ --color-contrast: var(--contrast);
298
+ --color-muted: var(--muted);
299
+ --color-soft: var(--soft);
300
+
301
+ /* Inverse — maps the `:root` inverse aliases to utilities. */
302
+ --color-surface-inverse: var(--surface-inverse);
303
+ --color-contrast-inverse: var(--contrast-inverse);
304
+
305
+ /* Brand — saturated fills use `text-white`, the neutral `secondary` uses
306
+ * `text-contrast`. No per-fill on-color tokens. */
183
307
  --color-primary: var(--primary);
184
- --color-primary-foreground: var(--primary-foreground);
185
308
  --color-secondary: var(--secondary);
186
- --color-secondary-foreground: var(--secondary-foreground);
187
309
  --color-accent: var(--accent);
188
- --color-accent-foreground: var(--accent-foreground);
189
310
 
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);
311
+ /* Structure — `line` (faint divider), `edge` (input border), `solid` (opaque
312
+ * border), `focus` (brand-themed ring = primary). */
313
+ --color-line: var(--line);
314
+ --color-edge: var(--edge);
315
+ --color-solid: var(--solid);
316
+ --color-focus: var(--focus);
196
317
 
197
318
  /* Status */
198
319
  --color-destructive: var(--destructive);
199
320
  --color-success: var(--success);
200
321
  --color-warning: var(--warning);
201
-
202
- /* Charts */
203
- --color-chart-1: var(--chart-1);
204
- --color-chart-2: var(--chart-2);
205
- --color-chart-3: var(--chart-3);
206
- --color-chart-4: var(--chart-4);
207
- --color-chart-5: var(--chart-5);
322
+ --color-info: var(--info);
208
323
 
209
324
  /* Presence */
210
325
  --color-presence-online: var(--presence-online);
@@ -212,25 +327,12 @@
212
327
  --color-presence-busy: var(--presence-busy);
213
328
  --color-presence-invisible: var(--presence-invisible);
214
329
 
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);
330
+ /* Radius — 3-tier scale, spacing-aligned + concentric (outer = inner + one
331
+ * --spacing); synced 1:1 with the motion sm/md/lg tiers. `--spacing` is
332
+ * Tailwind-provided (0.25rem = 4px). */
333
+ --radius-sm: calc(var(--radius) - var(--spacing)); /* 6px — small controls */
334
+ --radius-md: var(--radius); /* 10px — default (buttons, inputs, menus, popovers) */
335
+ --radius-lg: calc(var(--radius) + var(--spacing)); /* 14px — large surfaces (dialogs, drawers, cards) */
234
336
 
235
337
  /* Typography — passthroughs so consumers' `--font-sans` /
236
338
  * `--font-mono` flow through to Tailwind's `font-sans` /
@@ -239,6 +341,31 @@
239
341
  --font-mono: var(--font-mono);
240
342
  }
241
343
 
344
+ /* Shadow scale — layered, tinted by `--shadow-color` and dimmed by
345
+ * `--shadow-strength` (both resolve per :root/.dark, so retinting or dimming
346
+ * every shadow is one token). Tiers differ by layer count + spread, not alpha.
347
+ * Overrides Tailwind's shadow-sm/md/lg. */
348
+ @theme {
349
+ --shadow-sm:
350
+ 0px 0.5px 0.7px hsl(var(--shadow-color) / var(--shadow-strength)),
351
+ 0px 0.8px 1px -1.2px hsl(var(--shadow-color) / var(--shadow-strength)),
352
+ 0px 2px 2.5px -2.5px hsl(var(--shadow-color) / var(--shadow-strength));
353
+ --shadow-md:
354
+ 0px 0.5px 0.7px hsl(var(--shadow-color) / var(--shadow-strength)),
355
+ 0px 1.6px 2px -0.8px hsl(var(--shadow-color) / var(--shadow-strength)),
356
+ 0px 4.1px 5.2px -1.7px hsl(var(--shadow-color) / var(--shadow-strength)),
357
+ 0px 10px 12.6px -2.5px hsl(var(--shadow-color) / var(--shadow-strength));
358
+ --shadow-lg:
359
+ 0px 0.5px 0.7px hsl(var(--shadow-color) / var(--shadow-strength)),
360
+ 0px 2.9px 3.7px -0.4px hsl(var(--shadow-color) / var(--shadow-strength)),
361
+ 0px 5.4px 6.8px -0.7px hsl(var(--shadow-color) / var(--shadow-strength)),
362
+ 0px 8.9px 11.2px -1.1px hsl(var(--shadow-color) / var(--shadow-strength)),
363
+ 0px 14.3px 18px -1.4px hsl(var(--shadow-color) / var(--shadow-strength)),
364
+ 0px 22.3px 28.1px -1.8px hsl(var(--shadow-color) / var(--shadow-strength)),
365
+ 0px 33.9px 42.7px -2.1px hsl(var(--shadow-color) / var(--shadow-strength)),
366
+ 0px 50px 62.9px -2.5px hsl(var(--shadow-color) / var(--shadow-strength));
367
+ }
368
+
242
369
  /* ---------------------------------------------------------------------------
243
370
  * Utilities
244
371
  * ------------------------------------------------------------------------- */
@@ -252,14 +379,14 @@
252
379
 
253
380
  /* `motion-color` — interaction-state transition for controls: smoothly
254
381
  * 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`). */
382
+ * pressed, checked, etc. Runs at the `sm` tier (controls are small + fast). A
383
+ * plain layered `@utility`, so the unlayered enter/exit recipes below win over
384
+ * it when both land on one element (e.g. a button that also carries `motion-scale-sm`). */
258
385
  @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);
386
+ transition: color var(--duration-sm) var(--ease),
387
+ background-color var(--duration-sm) var(--ease),
388
+ border-color var(--duration-sm) var(--ease),
389
+ box-shadow var(--duration-sm) var(--ease);
263
390
  }
264
391
 
265
392
  /* Motion recipes — Base UI primitives emit `data-starting-style` (enter)
@@ -292,77 +419,214 @@
292
419
  }
293
420
  }
294
421
 
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 {
422
+ /* Enter/exit recipes THREE idioms, each tiered sm/md/lg.
423
+ * `motion-scale-*` ZOOMs (opacity + blur + scale)icons (sm), dialogs (lg).
424
+ * `motion-slide-*` SLIDES (opacity + blur + directional translate) popups (md).
425
+ * `motion-fade-*` FADES in place (opacity + blur, no transform).
426
+ * Tiers: sm = controls/icons · md = popups · lg = dialogs. Pick ONE idiom +
427
+ * tier per surface (don't compose — each declares its own unlayered transition).
428
+ * Companion idioms: `motion-color` (controls) and `motion-scrim` (backdrops). */
429
+ @utility motion-scale-sm {
299
430
  filter: blur(0.001px);
431
+ scale: 1;
300
432
  &[data-starting-style],
301
433
  &[data-ending-style] {
302
434
  opacity: 0;
303
- filter: blur(var(--blur));
435
+ filter: blur(var(--blur-sm));
436
+ scale: var(--scale-sm);
304
437
  }
305
438
  }
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 {
439
+ @utility motion-scale-md {
440
+ filter: blur(0.001px);
441
+ scale: 1;
442
+ &[data-starting-style],
443
+ &[data-ending-style] {
444
+ opacity: 0;
445
+ filter: blur(var(--blur-md));
446
+ scale: var(--scale-md);
447
+ }
448
+ }
449
+ @utility motion-scale-lg {
313
450
  filter: blur(0.001px);
314
451
  scale: 1;
315
452
  &[data-starting-style],
316
453
  &[data-ending-style] {
317
454
  opacity: 0;
318
- filter: blur(var(--motion-scale-blur, var(--blur)));
319
- scale: var(--scale);
455
+ filter: blur(var(--blur-lg));
456
+ scale: var(--scale-lg);
457
+ }
458
+ }
459
+
460
+ /* `motion-slide-*` — directional slide: the surface starts offset AWAY from its
461
+ * trigger's `data-[side]` (Base UI sets it) and settles toward it; default /
462
+ * side=bottom rises up. Opacity + blur + translate (`--offset-*`), NO scale. */
463
+ @utility motion-slide-sm {
464
+ filter: blur(0.001px);
465
+ translate: 0;
466
+ &[data-starting-style],
467
+ &[data-ending-style] {
468
+ opacity: 0;
469
+ filter: blur(var(--blur-sm));
470
+ translate: 0 var(--offset-sm);
471
+ }
472
+ &[data-side=top][data-starting-style],
473
+ &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset-sm) * -1); }
474
+ &[data-side=left][data-starting-style],
475
+ &[data-side=left][data-ending-style],
476
+ &[data-side=inline-start][data-starting-style],
477
+ &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset-sm) * -1) 0; }
478
+ &[data-side=right][data-starting-style],
479
+ &[data-side=right][data-ending-style],
480
+ &[data-side=inline-end][data-starting-style],
481
+ &[data-side=inline-end][data-ending-style] { translate: var(--offset-sm) 0; }
482
+ }
483
+ @utility motion-slide-md {
484
+ filter: blur(0.001px);
485
+ translate: 0;
486
+ &[data-starting-style],
487
+ &[data-ending-style] {
488
+ opacity: 0;
489
+ filter: blur(var(--blur-md));
490
+ translate: 0 var(--offset-md); /* default + side=bottom: start lower, rise up */
491
+ }
492
+ &[data-side=top][data-starting-style],
493
+ &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset-md) * -1); }
494
+ &[data-side=left][data-starting-style],
495
+ &[data-side=left][data-ending-style],
496
+ &[data-side=inline-start][data-starting-style],
497
+ &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset-md) * -1) 0; }
498
+ &[data-side=right][data-starting-style],
499
+ &[data-side=right][data-ending-style],
500
+ &[data-side=inline-end][data-starting-style],
501
+ &[data-side=inline-end][data-ending-style] { translate: var(--offset-md) 0; }
502
+ }
503
+ @utility motion-slide-lg {
504
+ filter: blur(0.001px);
505
+ translate: 0;
506
+ &[data-starting-style],
507
+ &[data-ending-style] {
508
+ opacity: 0;
509
+ filter: blur(var(--blur-lg));
510
+ translate: 0 var(--offset-lg);
511
+ }
512
+ &[data-side=top][data-starting-style],
513
+ &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset-lg) * -1); }
514
+ &[data-side=left][data-starting-style],
515
+ &[data-side=left][data-ending-style],
516
+ &[data-side=inline-start][data-starting-style],
517
+ &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset-lg) * -1) 0; }
518
+ &[data-side=right][data-starting-style],
519
+ &[data-side=right][data-ending-style],
520
+ &[data-side=inline-end][data-starting-style],
521
+ &[data-side=inline-end][data-ending-style] { translate: var(--offset-lg) 0; }
522
+ }
523
+
524
+ /* `motion-fade-*` — fade in place: opacity + blur, no transform. The signature
525
+ * blur-fade for content that should appear without moving or scaling. */
526
+ @utility motion-fade-sm {
527
+ filter: blur(0.001px);
528
+ &[data-starting-style],
529
+ &[data-ending-style] {
530
+ opacity: 0;
531
+ filter: blur(var(--blur-sm));
532
+ }
533
+ }
534
+ @utility motion-fade-md {
535
+ filter: blur(0.001px);
536
+ &[data-starting-style],
537
+ &[data-ending-style] {
538
+ opacity: 0;
539
+ filter: blur(var(--blur-md));
540
+ }
541
+ }
542
+ @utility motion-fade-lg {
543
+ filter: blur(0.001px);
544
+ &[data-starting-style],
545
+ &[data-ending-style] {
546
+ opacity: 0;
547
+ filter: blur(var(--blur-lg));
320
548
  }
321
549
  }
322
550
 
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 {
551
+ /* `motion-pop-md` — COMBINED idiom: scale + slide together (dropdowns "pop up
552
+ * while zooming"). The one surface that wants two idioms so it's a single
553
+ * recipe with one transition. Don't try `motion-scale-md motion-slide-md`: their
554
+ * transitions collide and scale won't animate. md only (no sm/lg consumer). */
555
+ @utility motion-pop-md {
328
556
  filter: blur(0.001px);
557
+ scale: 1;
329
558
  translate: 0;
330
559
  &[data-starting-style],
331
560
  &[data-ending-style] {
332
561
  opacity: 0;
333
- filter: blur(var(--blur));
334
- translate: 0 var(--offset); /* default + side=bottom: start lower, rise up */
562
+ filter: blur(var(--blur-md));
563
+ scale: var(--scale-md);
564
+ translate: 0 var(--offset-md); /* default + side=bottom: start lower, rise up */
335
565
  }
336
566
  &[data-side=top][data-starting-style],
337
- &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset) * -1); }
567
+ &[data-side=top][data-ending-style] { translate: 0 calc(var(--offset-md) * -1); }
338
568
  &[data-side=left][data-starting-style],
339
569
  &[data-side=left][data-ending-style],
340
570
  &[data-side=inline-start][data-starting-style],
341
- &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset) * -1) 0; }
571
+ &[data-side=inline-start][data-ending-style] { translate: calc(var(--offset-md) * -1) 0; }
342
572
  &[data-side=right][data-starting-style],
343
573
  &[data-side=right][data-ending-style],
344
574
  &[data-side=inline-end][data-starting-style],
345
- &[data-side=inline-end][data-ending-style] { translate: var(--offset) 0; }
575
+ &[data-side=inline-end][data-ending-style] { translate: var(--offset-md) 0; }
346
576
  }
347
577
 
348
578
  /* Recipe transitions — UNLAYERED on purpose (see note above): beats any
349
579
  * layered `transition-*` utility on the same element without `!important`. */
350
580
  .motion-scrim {
351
- transition: opacity var(--duration) var(--ease);
581
+ transition: opacity var(--duration-lg) var(--ease);
352
582
  }
353
- .motion-fade {
354
- transition: opacity var(--duration) var(--ease),
355
- filter var(--duration) var(--ease);
583
+ .motion-scale-sm {
584
+ transition: opacity var(--duration-sm) var(--ease),
585
+ filter var(--duration-sm) var(--ease),
586
+ scale var(--duration-sm) var(--ease);
356
587
  }
357
- .motion-scale {
358
- transition: opacity var(--duration) var(--ease),
359
- filter var(--duration) var(--ease),
360
- scale var(--duration) var(--ease);
588
+ .motion-scale-md {
589
+ transition: opacity var(--duration-md) var(--ease),
590
+ filter var(--duration-md) var(--ease),
591
+ scale var(--duration-md) var(--ease);
361
592
  }
362
- .motion-slide {
363
- transition: opacity var(--duration) var(--ease),
364
- filter var(--duration) var(--ease),
365
- translate var(--duration) var(--ease);
593
+ .motion-scale-lg {
594
+ transition: opacity var(--duration-lg) var(--ease),
595
+ filter var(--duration-lg) var(--ease),
596
+ scale var(--duration-lg) var(--ease);
597
+ }
598
+ .motion-slide-sm {
599
+ transition: opacity var(--duration-sm) var(--ease),
600
+ filter var(--duration-sm) var(--ease),
601
+ translate var(--duration-sm) var(--ease);
602
+ }
603
+ .motion-slide-md {
604
+ transition: opacity var(--duration-md) var(--ease),
605
+ filter var(--duration-md) var(--ease),
606
+ translate var(--duration-md) var(--ease);
607
+ }
608
+ .motion-slide-lg {
609
+ transition: opacity var(--duration-lg) var(--ease),
610
+ filter var(--duration-lg) var(--ease),
611
+ translate var(--duration-lg) var(--ease);
612
+ }
613
+ .motion-fade-sm {
614
+ transition: opacity var(--duration-sm) var(--ease),
615
+ filter var(--duration-sm) var(--ease);
616
+ }
617
+ .motion-fade-md {
618
+ transition: opacity var(--duration-md) var(--ease),
619
+ filter var(--duration-md) var(--ease);
620
+ }
621
+ .motion-fade-lg {
622
+ transition: opacity var(--duration-lg) var(--ease),
623
+ filter var(--duration-lg) var(--ease);
624
+ }
625
+ .motion-pop-md {
626
+ transition: opacity var(--duration-md) var(--ease),
627
+ filter var(--duration-md) var(--ease),
628
+ scale var(--duration-md) var(--ease),
629
+ translate var(--duration-md) var(--ease);
366
630
  }
367
631
 
368
632
  /* ---------------------------------------------------------------------------
@@ -370,10 +634,37 @@
370
634
  * ------------------------------------------------------------------------- */
371
635
  @media (prefers-reduced-motion: reduce) {
372
636
  :root {
373
- --duration: 0ms;
374
- --blur: 0px;
375
- --scale: 1;
376
- --offset: 0px;
377
- --stagger: 0ms;
637
+ --duration-sm: 0ms; --duration-md: 0ms; --duration-lg: 0ms;
638
+ --blur-sm: 0px; --blur-md: 0px; --blur-lg: 0px;
639
+ --scale-sm: 1; --scale-md: 1; --scale-lg: 1;
640
+ --offset-sm: 0px; --offset-md: 0px; --offset-lg: 0px;
641
+ --stagger-sm: 0ms; --stagger-md: 0ms; --stagger-lg: 0ms;
378
642
  }
379
643
  }
644
+
645
+ /* ---------------------------------------------------------------------------
646
+ * Keyframe utilities — self-hosted (replaces the former `tw-animate-css` dep,
647
+ * now removed). Only the three the library actually uses; `animate-spin` /
648
+ * `animate-pulse` come from Tailwind core. Registering via `@theme` generates
649
+ * the `animate-accordion-down` / `animate-accordion-up` / `animate-caret-blink`
650
+ * utilities. Accordion height runs on our motion tokens (`md` tier + the curve)
651
+ * and uses Base UI's `--accordion-panel-height` (the Radix var tw-animate
652
+ * targeted is never set under Base UI). Collapses under reduced motion via
653
+ * `--duration-md`. */
654
+ @theme {
655
+ --animate-accordion-down: accordion-down var(--duration-md) var(--ease);
656
+ --animate-accordion-up: accordion-up var(--duration-md) var(--ease);
657
+ --animate-caret-blink: caret-blink 1.25s ease-out infinite;
658
+ }
659
+ @keyframes accordion-down {
660
+ from { height: 0; }
661
+ to { height: var(--accordion-panel-height); }
662
+ }
663
+ @keyframes accordion-up {
664
+ from { height: var(--accordion-panel-height); }
665
+ to { height: 0; }
666
+ }
667
+ @keyframes caret-blink {
668
+ 0%, 70%, 100% { opacity: 1; }
669
+ 20%, 50% { opacity: 0; }
670
+ }