@human-synthesis/norns-ui 0.0.5 → 0.0.6

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 (50) hide show
  1. package/README.md +32 -20
  2. package/package.json +7 -3
  3. package/src/auto-import.js +1 -4
  4. package/src/components/Accordion.n +24 -23
  5. package/src/components/Autocomplete.n +108 -33
  6. package/src/components/Collapsible.n +8 -11
  7. package/src/components/ContextMenu.n +55 -36
  8. package/src/components/DatePicker.n +19 -31
  9. package/src/components/DateRangePicker.n +17 -29
  10. package/src/components/Dialog.n +35 -22
  11. package/src/components/Dropdown.n +54 -23
  12. package/src/components/MultiSelect.n +138 -36
  13. package/src/components/Popover.n +30 -15
  14. package/src/components/ScrollArea.n +9 -20
  15. package/src/components/Sheet.n +36 -22
  16. package/src/components/Tabs.n +39 -13
  17. package/src/components/TimePicker.n +52 -65
  18. package/src/components/ToggleButton.n +18 -12
  19. package/src/components/ToggleButtonGroup.n +31 -11
  20. package/src/components/Tooltip.n +52 -19
  21. package/src/index.js +1 -4
  22. package/src/lib/behaviors/clickOutside.svelte.js +54 -0
  23. package/src/lib/behaviors/escape.svelte.js +36 -0
  24. package/src/lib/behaviors/floating.svelte.js +137 -0
  25. package/src/lib/behaviors/focusTrap.svelte.js +80 -0
  26. package/src/lib/behaviors/index.js +14 -0
  27. package/src/lib/behaviors/portal.svelte.js +32 -0
  28. package/src/lib/behaviors/rovingTabindex.svelte.js +76 -0
  29. package/src/lib/behaviors/scrollLock.svelte.js +56 -0
  30. package/src/styles/atoms.css +622 -919
  31. package/src/styles/tokens.css +33 -0
  32. package/src/types/Collapsible.d.ts +1 -0
  33. package/src/types/ContextMenu.d.ts +6 -1
  34. package/src/types/DatePicker.d.ts +7 -2
  35. package/src/types/DateRangePicker.d.ts +3 -3
  36. package/src/types/Dialog.d.ts +4 -0
  37. package/src/types/Dropdown.d.ts +2 -1
  38. package/src/types/MultiSelect.d.ts +3 -0
  39. package/src/types/ScrollArea.d.ts +1 -1
  40. package/src/types/Sheet.d.ts +3 -0
  41. package/src/types/Tabs.d.ts +2 -0
  42. package/src/types/ToggleButton.d.ts +2 -1
  43. package/src/types/ToggleButtonGroup.d.ts +2 -1
  44. package/src/types/Tooltip.d.ts +3 -0
  45. package/src/components/Drawer.n +0 -49
  46. package/src/components/PreviewCard.n +0 -30
  47. package/src/components/RichTooltip.n +0 -33
  48. package/src/types/Drawer.d.ts +0 -18
  49. package/src/types/PreviewCard.d.ts +0 -16
  50. package/src/types/RichTooltip.d.ts +0 -16
@@ -1,17 +1,26 @@
1
1
  /*
2
2
  * Norns UI — component atom classes.
3
3
  *
4
- * Pure Tailwind utility-composition via `@layer components`. No JS, no Svelte.
5
- * All atoms reference *role tokens* from tokens.css never raw color shades.
6
- * That's how light/dark theme swap works through a single point of change.
4
+ * Pure Tailwind utility composition via `@layer components`. No JS, no Svelte.
5
+ * Atoms reference role tokens (and the new elevation/motion/icon-btn tokens
6
+ * in tokens.css), never raw color shades. That's how light/dark theme swap
7
+ * works through a single point of change.
7
8
  *
8
- * Naming convention: `<atom>-<variant>` and `<atom>-<size>`. AI-predictable.
9
+ * Naming: `<atom>-<variant>` and `<atom>-<size>`. Predictable, AI-friendly.
10
+ *
11
+ * Design rules — every atom obeys these unless it has a documented reason:
12
+ * 1. Radius: `--ui-radius` for floating panels & full-size controls,
13
+ * `--ui-radius-sm` for items inside menus, `--ui-radius-lg` only for
14
+ * Dialog and HeroBanner.
15
+ * 2. Hover on any interactive surface uses `bg-bg-muted`. No exceptions.
16
+ * 3. Focus uses the `--ui-focus-ring` shadow on `:focus-visible`.
17
+ * 4. Transitions use `--ui-motion-fast` + `--ui-motion-ease`.
18
+ * 5. Shadows use `--ui-elev-{1,2,3}`. No raw shadow utilities.
9
19
  */
10
20
 
11
21
  @reference 'tailwindcss';
12
22
 
13
23
  @layer base {
14
- /* Body */
15
24
  html {
16
25
  font-family: var(--font-sans);
17
26
  color: var(--color-fg);
@@ -23,9 +32,6 @@
23
32
  background-color: var(--color-bg);
24
33
  }
25
34
 
26
- /* Reduced-motion: globally short-circuit our transitions/animations.
27
- * Components using prefers-reduced-motion: reduce explicitly will get this.
28
- */
29
35
  @media (prefers-reduced-motion: reduce) {
30
36
  *,
31
37
  *::before,
@@ -37,84 +43,156 @@
37
43
  }
38
44
  }
39
45
 
40
- /* Heading hierarchy — applied to bare h1..h6 unless overridden via Tailwind. */
41
- h1 {
42
- @apply text-4xl font-bold tracking-tight leading-tight;
43
- }
44
- h2 {
45
- @apply text-3xl font-semibold tracking-tight leading-tight;
46
- }
47
- h3 {
48
- @apply text-2xl font-semibold tracking-tight leading-snug;
49
- }
50
- h4 {
51
- @apply text-xl font-semibold leading-snug;
52
- }
53
- h5 {
54
- @apply text-lg font-semibold leading-snug;
55
- }
56
- h6 {
57
- @apply text-base font-semibold;
58
- }
46
+ /* Heading hierarchy */
47
+ h1 { @apply text-4xl font-bold tracking-tight leading-tight; }
48
+ h2 { @apply text-3xl font-semibold tracking-tight leading-tight; }
49
+ h3 { @apply text-2xl font-semibold tracking-tight leading-snug; }
50
+ h4 { @apply text-xl font-semibold leading-snug; }
51
+ h5 { @apply text-lg font-semibold leading-snug; }
52
+ h6 { @apply text-base font-semibold; }
59
53
 
60
- /* Inline code + pre */
61
54
  code {
62
55
  font-family: var(--font-mono);
63
- @apply text-[0.9em] px-1 py-0.5 rounded;
56
+ @apply text-[0.9em] px-1 py-0.5;
64
57
  background-color: var(--color-bg-muted);
65
58
  color: var(--color-fg);
59
+ border-radius: var(--ui-radius-sm);
66
60
  }
67
61
  pre {
68
62
  font-family: var(--font-mono);
69
- @apply text-sm p-3 rounded overflow-x-auto;
63
+ @apply text-sm p-3 overflow-x-auto;
70
64
  background-color: var(--color-bg-muted);
71
65
  color: var(--color-fg);
66
+ border-radius: var(--ui-radius);
72
67
  }
73
68
  pre code {
74
69
  @apply p-0 bg-transparent;
75
70
  }
76
71
 
77
- /* Keyboard */
78
72
  kbd {
79
73
  font-family: var(--font-mono);
80
- @apply text-xs px-1.5 py-0.5 rounded border;
74
+ @apply text-xs px-1.5 py-0.5 border;
81
75
  background-color: var(--color-bg-muted);
82
76
  border-color: var(--color-border);
83
77
  color: var(--color-fg);
78
+ border-radius: var(--ui-radius-sm);
84
79
  }
85
80
 
86
- /* Default form-control reset on dark */
87
- hr {
88
- border-color: var(--color-border);
89
- }
81
+ hr { border-color: var(--color-border); }
90
82
  }
91
83
 
92
84
  @layer components {
85
+ /* ============================================================ */
86
+ /* Shared mixins */
87
+ /* ============================================================ */
88
+
89
+ /* `.ui-ring` — single canonical focus indicator. Components compose:
90
+ * .btn { ... }
91
+ * .btn:focus-visible { box-shadow: var(--ui-focus-ring); }
92
+ * or apply this helper class directly. */
93
+ .ui-ring:focus-visible {
94
+ outline: none;
95
+ box-shadow: var(--ui-focus-ring);
96
+ }
97
+
98
+ /* `.surface-elevated` — public class for Popover, Dropdown, Tooltip-rich,
99
+ * Dialog, Sheet, Toast, nav-menu-content, etc. Each of those atoms also
100
+ * extends this rule via selector lists below, so consumers can apply
101
+ * either name. */
102
+ .surface-elevated,
103
+ .popover-content,
104
+ .dropdown-content,
105
+ .tooltip-rich,
106
+ .dialog-content,
107
+ .sheet-content,
108
+ .toast,
109
+ .nav-menu-content {
110
+ background-color: var(--color-bg-elevated);
111
+ color: var(--color-fg);
112
+ border: 1px solid var(--color-border);
113
+ border-radius: var(--ui-radius);
114
+ box-shadow: var(--ui-elev-2);
115
+ }
116
+
117
+ /* `.control-base` — public alias. Form-control atoms below extend this
118
+ * rule via selector lists, so `.input`, `.textarea`, `.select`, etc. all
119
+ * share one source of truth for size/padding/border/focus/disabled. */
120
+ .control-base,
121
+ .input,
122
+ .textarea,
123
+ .select,
124
+ .color-picker-text,
125
+ .date-picker-trigger {
126
+ @apply block w-full text-sm leading-none;
127
+ background-color: var(--color-bg);
128
+ color: var(--color-fg);
129
+ border: 1px solid var(--color-border-strong);
130
+ border-radius: var(--ui-radius);
131
+ height: var(--ui-h-md);
132
+ padding-inline: 0.75rem;
133
+ transition: border-color var(--ui-motion-fast) var(--ui-motion-ease),
134
+ background-color var(--ui-motion-fast) var(--ui-motion-ease),
135
+ box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
136
+ }
137
+ .control-base::placeholder,
138
+ .input::placeholder,
139
+ .textarea::placeholder,
140
+ .select::placeholder { color: var(--color-fg-subtle); }
141
+ .control-base:focus-visible,
142
+ .input:focus-visible,
143
+ .textarea:focus-visible,
144
+ .select:focus-visible,
145
+ .date-picker-trigger:focus-visible {
146
+ outline: none;
147
+ border-color: var(--color-ring);
148
+ box-shadow: var(--ui-focus-ring);
149
+ }
150
+ .control-base:disabled,
151
+ .input:disabled,
152
+ .textarea:disabled,
153
+ .select:disabled {
154
+ @apply cursor-not-allowed opacity-70;
155
+ background-color: var(--color-bg-muted);
156
+ }
157
+ .control-err,
158
+ .input-err,
159
+ .textarea-err,
160
+ .select-err {
161
+ border-color: var(--color-danger-500);
162
+ }
163
+ .control-err:focus-visible,
164
+ .input-err:focus-visible,
165
+ .textarea-err:focus-visible,
166
+ .select-err:focus-visible {
167
+ border-color: var(--color-danger-500);
168
+ box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--color-danger-500);
169
+ }
170
+
93
171
  /* ============================================================ */
94
172
  /* Button */
95
173
  /* ============================================================ */
96
174
  .btn {
97
175
  @apply inline-flex items-center justify-center gap-2 text-sm font-medium leading-none whitespace-nowrap;
98
- @apply transition-[background-color,color,border-color,transform,opacity] duration-150 ease-out;
99
176
  @apply select-none cursor-pointer relative overflow-hidden;
100
177
  @apply disabled:cursor-not-allowed disabled:opacity-60;
101
- @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
102
- --tw-ring-color: var(--color-ring);
103
- --tw-ring-offset-color: var(--color-bg);
104
178
  border-radius: var(--ui-radius);
105
179
  height: var(--ui-h-md);
106
180
  padding-inline: 0.875rem;
181
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease),
182
+ color var(--ui-motion-fast) var(--ui-motion-ease),
183
+ border-color var(--ui-motion-fast) var(--ui-motion-ease),
184
+ transform var(--ui-motion-fast) var(--ui-motion-ease),
185
+ box-shadow var(--ui-motion-fast) var(--ui-motion-ease),
186
+ opacity var(--ui-motion-fast) var(--ui-motion-ease);
107
187
  }
108
- .btn:not(:disabled):active {
109
- transform: scale(0.97);
110
- }
188
+ .btn:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
189
+ .btn:not(:disabled):active { transform: scale(0.97); }
111
190
 
112
191
  .btn-sm {
113
192
  @apply text-xs;
114
193
  height: var(--ui-h-sm);
115
194
  padding-inline: 0.625rem;
116
195
  }
117
-
118
196
  .btn-lg {
119
197
  @apply text-base;
120
198
  height: var(--ui-h-lg);
@@ -125,23 +203,19 @@
125
203
  background-color: var(--color-primary-600);
126
204
  color: var(--color-fg-on-primary);
127
205
  }
128
- .btn-primary:hover {
129
- background-color: var(--color-primary-700);
130
- }
131
- .btn-primary:active {
132
- background-color: var(--color-primary-800);
133
- }
206
+ .btn-primary:hover { background-color: var(--color-primary-700); }
207
+ .btn-primary:active { background-color: var(--color-primary-800); }
134
208
 
135
209
  .btn-secondary {
136
210
  background-color: var(--color-bg-muted);
137
211
  color: var(--color-fg);
138
212
  border: 1px solid var(--color-border);
139
213
  }
140
- .btn-secondary:hover {
141
- background-color: var(--color-bg-subtle);
142
- }
143
- .btn-secondary:active {
214
+ .btn-secondary:hover { background-color: var(--color-bg-subtle); }
215
+ .btn-secondary:active { background-color: var(--color-border); }
216
+ .btn-secondary[data-pressed='true'] {
144
217
  background-color: var(--color-border);
218
+ border-color: var(--color-border-strong);
145
219
  }
146
220
 
147
221
  .btn-ghost {
@@ -152,19 +226,19 @@
152
226
  background-color: var(--color-bg-muted);
153
227
  color: var(--color-fg);
154
228
  }
229
+ .btn-ghost[data-pressed='true'] {
230
+ background-color: var(--color-bg-muted);
231
+ color: var(--color-fg);
232
+ }
155
233
 
156
234
  .btn-danger {
157
235
  background-color: var(--color-danger-600);
158
236
  color: white;
159
237
  }
160
- .btn-danger:hover {
161
- background-color: var(--color-danger-700);
162
- }
163
- .btn-danger:active {
164
- background-color: var(--color-danger-800);
165
- }
238
+ .btn-danger:hover { background-color: var(--color-danger-700); }
239
+ .btn-danger:active { background-color: var(--color-danger-800); }
166
240
  .btn-danger:focus-visible {
167
- --tw-ring-color: var(--color-danger-500);
241
+ box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--color-danger-500);
168
242
  }
169
243
 
170
244
  .btn-link {
@@ -176,38 +250,56 @@
176
250
  color: var(--color-primary-400);
177
251
  }
178
252
 
179
- /* ============================================================ */
180
- /* Spinner (used inside Btn loading state) */
181
- /* ============================================================ */
253
+ /* Square icon-button — close buttons, calendar nav, chevron triggers, etc.
254
+ * The selector list extends `.btn-icon` to consumer atoms so they share
255
+ * one source of truth without `@apply`-ing across custom classes. */
256
+ .btn-icon,
257
+ .dialog-close,
258
+ .toast-close,
259
+ .combobox-trigger,
260
+ .calendar-nav,
261
+ .pagination-item {
262
+ @apply inline-flex items-center justify-center;
263
+ width: var(--ui-icon-btn-md);
264
+ height: var(--ui-icon-btn-md);
265
+ padding: 0;
266
+ border-radius: var(--ui-radius-sm);
267
+ color: var(--color-fg-muted);
268
+ background: transparent;
269
+ border: none;
270
+ cursor: pointer;
271
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease),
272
+ color var(--ui-motion-fast) var(--ui-motion-ease);
273
+ }
274
+ .btn-icon:hover,
275
+ .dialog-close:hover,
276
+ .combobox-trigger:hover,
277
+ .calendar-nav:hover,
278
+ .pagination-item:hover {
279
+ background-color: var(--color-bg-muted);
280
+ color: var(--color-fg);
281
+ }
282
+ .btn-icon:focus-visible,
283
+ .dialog-close:focus-visible,
284
+ .toast-close:focus-visible,
285
+ .combobox-trigger:focus-visible,
286
+ .calendar-nav:focus-visible,
287
+ .pagination-item:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
288
+ .btn-icon-sm,
289
+ .dialog-close,
290
+ .toast-close,
291
+ .combobox-trigger,
292
+ .calendar-nav { width: var(--ui-icon-btn-sm); height: var(--ui-icon-btn-sm); }
293
+ .btn-icon-lg { width: var(--ui-icon-btn-lg); height: var(--ui-icon-btn-lg); }
294
+
295
+ /* Spinner used inside Btn loading state */
182
296
  .ui-spinner {
183
297
  @apply inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent;
184
298
  }
185
299
 
186
300
  /* ============================================================ */
187
- /* Inputs */
301
+ /* Form controls — base shared with .control-base above */
188
302
  /* ============================================================ */
189
- .input {
190
- @apply block w-full text-sm leading-none;
191
- @apply transition-[border-color,background-color] duration-150 ease-out;
192
- @apply focus:outline-none focus:ring-2;
193
- @apply disabled:cursor-not-allowed disabled:opacity-70;
194
- background-color: var(--color-bg);
195
- color: var(--color-fg);
196
- border: 1px solid var(--color-border-strong);
197
- border-radius: var(--ui-radius);
198
- height: var(--ui-h-md);
199
- padding-inline: 0.75rem;
200
- --tw-ring-color: color-mix(in oklab, var(--color-ring) 30%, transparent);
201
- }
202
- .input::placeholder {
203
- color: var(--color-fg-subtle);
204
- }
205
- .input:focus {
206
- border-color: var(--color-ring);
207
- }
208
- .input:disabled {
209
- background-color: var(--color-bg-muted);
210
- }
211
303
  .input-sm {
212
304
  @apply text-xs;
213
305
  height: var(--ui-h-sm);
@@ -218,123 +310,53 @@
218
310
  height: var(--ui-h-lg);
219
311
  padding-inline: 0.875rem;
220
312
  }
221
- .input-err {
222
- border-color: var(--color-danger-500);
223
- }
224
- .input-err:focus {
225
- border-color: var(--color-danger-500);
226
- --tw-ring-color: color-mix(in oklab, var(--color-danger-500) 30%, transparent);
227
- }
228
313
 
229
- /* Textarea */
230
314
  .textarea {
231
- @apply block w-full text-sm leading-snug px-3 py-2;
232
- @apply transition-[border-color,background-color] duration-150 ease-out;
233
- @apply focus:outline-none focus:ring-2;
234
- @apply disabled:cursor-not-allowed disabled:opacity-70;
235
- background-color: var(--color-bg);
236
- color: var(--color-fg);
237
- border: 1px solid var(--color-border-strong);
238
- border-radius: var(--ui-radius);
315
+ @apply leading-snug;
316
+ padding-block: 0.5rem;
239
317
  min-height: calc(var(--ui-h-md) * 2);
240
- --tw-ring-color: color-mix(in oklab, var(--color-ring) 30%, transparent);
241
- }
242
- .textarea::placeholder {
243
- color: var(--color-fg-subtle);
244
- }
245
- .textarea:focus {
246
- border-color: var(--color-ring);
247
- }
248
- .textarea:disabled {
249
- background-color: var(--color-bg-muted);
250
- }
251
- .textarea-err {
252
- border-color: var(--color-danger-500);
253
- }
254
- .textarea-err:focus {
255
- border-color: var(--color-danger-500);
256
- --tw-ring-color: color-mix(in oklab, var(--color-danger-500) 30%, transparent);
257
- }
258
-
259
- /* Select */
260
- .select {
261
- @apply block w-full text-sm leading-none;
262
- @apply transition-[border-color,background-color] duration-150 ease-out;
263
- @apply focus:outline-none focus:ring-2;
264
- @apply disabled:cursor-not-allowed disabled:opacity-70;
265
- background-color: var(--color-bg);
266
- color: var(--color-fg);
267
- border: 1px solid var(--color-border-strong);
268
- border-radius: var(--ui-radius);
269
- height: var(--ui-h-md);
270
- padding-inline: 0.75rem;
271
- --tw-ring-color: color-mix(in oklab, var(--color-ring) 30%, transparent);
272
- }
273
- .select:focus {
274
- border-color: var(--color-ring);
275
- }
276
- .select:disabled {
277
- background-color: var(--color-bg-muted);
278
- }
279
- .select-err {
280
- border-color: var(--color-danger-500);
318
+ height: auto;
281
319
  }
282
320
 
283
- /* Checkbox / Radio */
284
- .checkbox {
285
- @apply size-4 cursor-pointer rounded;
286
- @apply focus:outline-none focus:ring-2 focus:ring-offset-1;
287
- @apply disabled:cursor-not-allowed disabled:opacity-60;
288
- border: 1px solid var(--color-border-strong);
289
- accent-color: var(--color-primary-600);
290
- --tw-ring-color: color-mix(in oklab, var(--color-ring) 30%, transparent);
291
- --tw-ring-offset-color: var(--color-bg);
292
- }
293
- .checkbox-err {
294
- border-color: var(--color-danger-500);
295
- }
321
+ /* Checkbox / Radio — small square/round controls. Native `accent-color`
322
+ * does the colored fill once checked. */
323
+ .checkbox,
296
324
  .radio {
297
- @apply size-4 cursor-pointer rounded-full;
298
- @apply focus:outline-none focus:ring-2 focus:ring-offset-1;
325
+ @apply size-4 cursor-pointer;
299
326
  @apply disabled:cursor-not-allowed disabled:opacity-60;
300
327
  border: 1px solid var(--color-border-strong);
301
328
  accent-color: var(--color-primary-600);
302
- --tw-ring-color: color-mix(in oklab, var(--color-ring) 30%, transparent);
303
- --tw-ring-offset-color: var(--color-bg);
304
- }
305
- .radio-err {
306
- border-color: var(--color-danger-500);
329
+ transition: box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
307
330
  }
331
+ .checkbox { border-radius: var(--ui-radius-sm); }
332
+ .radio { border-radius: var(--ui-radius-full); }
333
+ .checkbox:focus-visible,
334
+ .radio:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
335
+ .checkbox-err,
336
+ .radio-err { border-color: var(--color-danger-500); }
308
337
 
309
338
  /* Switch */
310
339
  .switch {
311
340
  @apply relative inline-flex h-5 w-9 shrink-0 cursor-pointer appearance-none rounded-full;
312
- @apply transition-colors duration-150 ease-out;
313
- @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1;
314
341
  @apply disabled:cursor-not-allowed disabled:opacity-60;
315
342
  background-color: var(--color-border-strong);
316
- --tw-ring-color: color-mix(in oklab, var(--color-ring) 30%, transparent);
317
- --tw-ring-offset-color: var(--color-bg);
318
- }
319
- .switch:checked {
320
- background-color: var(--color-primary-600);
343
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease),
344
+ box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
321
345
  }
346
+ .switch:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
347
+ .switch:checked { background-color: var(--color-primary-600); }
322
348
  .switch::before {
323
349
  content: '';
324
350
  @apply absolute left-0.5 top-0.5 size-4 rounded-full;
325
- @apply transition-transform duration-150 ease-out;
326
351
  background-color: white;
352
+ transition: transform var(--ui-motion-fast) var(--ui-motion-ease);
327
353
  }
328
- .switch:checked::before {
329
- transform: translateX(1rem);
330
- }
354
+ .switch:checked::before { transform: translateX(1rem); }
331
355
 
332
356
  /* ============================================================ */
333
357
  /* Field */
334
358
  /* ============================================================ */
335
- .field {
336
- @apply flex flex-col;
337
- }
359
+ .field { @apply flex flex-col; }
338
360
  .field-label {
339
361
  @apply mb-1 block text-sm font-medium;
340
362
  color: var(--color-fg);
@@ -351,9 +373,7 @@
351
373
  @apply mt-1 text-xs;
352
374
  color: var(--color-danger-600);
353
375
  }
354
- .field-row {
355
- @apply flex items-center gap-2;
356
- }
376
+ .field-row { @apply flex items-center gap-2; }
357
377
 
358
378
  .field-group {
359
379
  @apply space-y-3 p-4;
@@ -365,31 +385,37 @@
365
385
  color: var(--color-fg);
366
386
  }
367
387
 
368
- /* Form */
369
- .form {
370
- @apply space-y-4;
371
- }
388
+ .form { @apply space-y-4; }
372
389
 
373
390
  /* ============================================================ */
374
391
  /* Dialog */
375
392
  /* ============================================================ */
376
393
  .dialog-overlay {
377
- @apply fixed inset-0 z-50 backdrop-blur-sm opacity-0 transition-opacity duration-150;
394
+ @apply fixed inset-0 z-50 backdrop-blur-sm;
378
395
  background-color: oklch(0% 0 0 / 0.5);
396
+ opacity: 0;
397
+ transition: opacity var(--ui-motion-fast) var(--ui-motion-ease);
379
398
  }
380
- .dialog-overlay[data-state='open'] {
381
- @apply opacity-100;
382
- }
399
+ .dialog-overlay[data-state='open'] { opacity: 1; }
400
+
383
401
  .dialog-content {
384
- @apply fixed left-1/2 top-1/2 z-50 w-full max-w-lg -translate-x-1/2 -translate-y-1/2 p-6;
385
- @apply opacity-0 scale-95 transition duration-150 shadow-xl;
386
- background-color: var(--color-bg-elevated);
387
- color: var(--color-fg);
388
- border: 1px solid var(--color-border);
402
+ /* Centered via translate longhand so it doesn't compose with the
403
+ * scale animation below. Tailwind v4 emits `translate:` for its
404
+ * `-translate-x-*` utilities; mixing them with `transform:` here
405
+ * would double the offset. */
406
+ @apply fixed left-1/2 top-1/2 z-50 w-full max-w-lg p-6;
407
+ translate: -50% -50%;
408
+ /* base surface comes from `.surface-elevated` selector list */
389
409
  border-radius: var(--ui-radius-lg);
410
+ box-shadow: var(--ui-elev-3);
411
+ opacity: 0;
412
+ scale: 0.96;
413
+ transition: opacity var(--ui-motion-fast) var(--ui-motion-ease),
414
+ scale var(--ui-motion-fast) var(--ui-motion-ease);
390
415
  }
391
416
  .dialog-content[data-state='open'] {
392
- @apply opacity-100 scale-100;
417
+ opacity: 1;
418
+ scale: 1;
393
419
  }
394
420
  .dialog-title {
395
421
  @apply text-lg font-semibold leading-tight tracking-tight;
@@ -399,92 +425,75 @@
399
425
  @apply mt-1 text-sm;
400
426
  color: var(--color-fg-muted);
401
427
  }
402
- .dialog-body {
403
- @apply mt-4;
404
- }
405
- .dialog-actions {
406
- @apply mt-6 flex items-center justify-end gap-2;
407
- }
408
- .dialog-close {
409
- @apply absolute right-3 top-3 inline-flex size-7 items-center justify-center rounded-md;
410
- @apply focus-visible:outline-none focus-visible:ring-2;
411
- color: var(--color-fg-muted);
412
- --tw-ring-color: var(--color-ring);
413
- }
414
- .dialog-close:hover {
415
- background-color: var(--color-bg-muted);
416
- color: var(--color-fg);
417
- }
428
+ .dialog-body { @apply mt-4; }
429
+ .dialog-actions { @apply mt-6 flex items-center justify-end gap-2; }
430
+ .dialog-close { @apply absolute right-3 top-3; }
418
431
 
419
432
  /* ============================================================ */
420
- /* Sheet */
433
+ /* Sheet (and Drawer, which is just Sheet with side="left") */
421
434
  /* ============================================================ */
422
435
  .sheet-content {
423
- @apply fixed z-50 transition-transform duration-200 ease-out shadow-xl;
436
+ @apply fixed z-50 p-6;
424
437
  background-color: var(--color-bg-elevated);
425
438
  color: var(--color-fg);
426
439
  border: 1px solid var(--color-border);
440
+ box-shadow: var(--ui-elev-3);
441
+ /* Same precaution as `.dialog-content`: drive the slide via the
442
+ * `translate:` longhand and avoid mixing with `transform:` so they
443
+ * don't compose. */
444
+ transition: translate var(--ui-motion-med) var(--ui-motion-ease);
427
445
  }
428
- .sheet-right {
429
- @apply right-0 top-0 h-full w-full max-w-md translate-x-full p-6;
430
- }
431
- .sheet-right[data-state='open'] {
432
- @apply translate-x-0;
433
- }
434
- .sheet-left {
435
- @apply left-0 top-0 h-full w-full max-w-md -translate-x-full p-6;
436
- }
437
- .sheet-left[data-state='open'] {
438
- @apply translate-x-0;
439
- }
440
- .sheet-top {
441
- @apply left-0 top-0 w-full -translate-y-full p-6;
442
- }
443
- .sheet-top[data-state='open'] {
444
- @apply translate-y-0;
445
- }
446
- .sheet-bottom {
447
- @apply bottom-0 left-0 w-full translate-y-full p-6;
448
- }
449
- .sheet-bottom[data-state='open'] {
450
- @apply translate-y-0;
451
- }
452
- .sheet-body {
453
- @apply mt-4;
454
- }
446
+ .sheet-right { @apply right-0 top-0 h-full w-full max-w-md; translate: 100% 0; }
447
+ .sheet-left { @apply left-0 top-0 h-full w-full max-w-md; translate: -100% 0; }
448
+ .sheet-top { @apply left-0 top-0 w-full; translate: 0 -100%; }
449
+ .sheet-bottom { @apply bottom-0 left-0 w-full; translate: 0 100%; }
450
+ .sheet-right[data-state='open'],
451
+ .sheet-left[data-state='open'],
452
+ .sheet-top[data-state='open'],
453
+ .sheet-bottom[data-state='open'] { translate: 0 0; }
454
+ .sheet-body { @apply mt-4; }
455
455
 
456
456
  /* ============================================================ */
457
- /* Popover / Dropdown / Tooltip */
457
+ /* Popover / Dropdown / Tooltip — all surface-elevated */
458
458
  /* ============================================================ */
459
459
  .popover-content {
460
- @apply z-50 p-3 text-sm shadow-md;
461
- @apply opacity-0 scale-95 transition duration-150;
462
- background-color: var(--color-bg-elevated);
463
- color: var(--color-fg);
464
- border: 1px solid var(--color-border);
465
- border-radius: var(--ui-radius);
460
+ @apply z-50 p-3 text-sm;
461
+ /* base surface comes from `.surface-elevated` selector list */
462
+ opacity: 0;
463
+ transform: scale(0.96);
464
+ transition: opacity var(--ui-motion-fast) var(--ui-motion-ease),
465
+ transform var(--ui-motion-fast) var(--ui-motion-ease);
466
466
  }
467
467
  .popover-content[data-state='open'] {
468
- @apply opacity-100 scale-100;
468
+ opacity: 1;
469
+ transform: scale(1);
469
470
  }
470
471
 
471
472
  .dropdown-content {
472
- @apply z-50 min-w-[8rem] p-1 text-sm shadow-md;
473
- @apply opacity-0 scale-95 transition duration-150;
474
- background-color: var(--color-bg-elevated);
475
- color: var(--color-fg);
476
- border: 1px solid var(--color-border);
477
- border-radius: var(--ui-radius);
473
+ @apply z-50 text-sm;
474
+ /* base surface comes from `.surface-elevated` selector list */
475
+ min-width: 8rem;
476
+ padding: 0.25rem;
477
+ opacity: 0;
478
+ transform: scale(0.96);
479
+ transition: opacity var(--ui-motion-fast) var(--ui-motion-ease),
480
+ transform var(--ui-motion-fast) var(--ui-motion-ease);
478
481
  }
479
482
  .dropdown-content[data-state='open'] {
480
- @apply opacity-100 scale-100;
483
+ opacity: 1;
484
+ transform: scale(1);
481
485
  }
482
486
  .dropdown-item {
483
- @apply flex cursor-pointer select-none items-center px-2 py-1.5 text-sm outline-none rounded-sm;
487
+ @apply flex cursor-pointer select-none items-center gap-2 px-2 py-1.5 text-sm outline-none;
484
488
  color: var(--color-fg);
489
+ border-radius: var(--ui-radius-sm);
490
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
485
491
  }
486
- .dropdown-item[data-highlighted] {
492
+ .dropdown-item:hover:not([data-disabled]),
493
+ .dropdown-item:focus-visible,
494
+ .dropdown-item[data-active='true'] {
487
495
  background-color: var(--color-bg-muted);
496
+ outline: none;
488
497
  }
489
498
  .dropdown-item[data-disabled] {
490
499
  @apply cursor-not-allowed opacity-50;
@@ -495,23 +504,29 @@
495
504
  }
496
505
 
497
506
  .tooltip-content {
498
- @apply z-50 overflow-hidden px-2 py-1 text-xs shadow-md;
499
- @apply opacity-0 scale-95 transition duration-150;
507
+ @apply z-50 px-2 py-1 text-xs;
500
508
  background-color: var(--color-bg-inverse);
501
509
  color: var(--color-bg);
502
510
  border-radius: var(--ui-radius-sm);
511
+ box-shadow: var(--ui-elev-2);
512
+ opacity: 0;
513
+ transform: scale(0.96);
514
+ transition: opacity var(--ui-motion-fast) var(--ui-motion-ease),
515
+ transform var(--ui-motion-fast) var(--ui-motion-ease);
516
+ }
517
+ .tooltip-content[data-state='open'] {
518
+ opacity: 1;
519
+ transform: scale(1);
503
520
  }
504
- .tooltip-content[data-state='delayed-open'],
505
- .tooltip-content[data-state='instant-open'] {
506
- @apply opacity-100 scale-100;
521
+ /* `.tooltip-rich` is in the `.surface-elevated` selector list above. */
522
+ .tooltip-rich {
523
+ @apply p-3 text-sm max-w-xs;
507
524
  }
508
525
 
509
526
  /* ============================================================ */
510
527
  /* Tabs */
511
528
  /* ============================================================ */
512
- .tabs-root {
513
- @apply space-y-3;
514
- }
529
+ .tabs-root { @apply space-y-3; }
515
530
  .tabs-list {
516
531
  @apply inline-flex h-9 items-center justify-center p-1;
517
532
  background-color: var(--color-bg-muted);
@@ -519,19 +534,23 @@
519
534
  border-radius: var(--ui-radius);
520
535
  }
521
536
  .tabs-trigger {
522
- @apply inline-flex items-center justify-center whitespace-nowrap px-3 py-1 text-sm font-medium rounded-sm;
523
- @apply transition-colors focus-visible:outline-none focus-visible:ring-2;
537
+ @apply inline-flex items-center justify-center whitespace-nowrap px-3 py-1 text-sm font-medium;
524
538
  @apply disabled:cursor-not-allowed disabled:opacity-50;
525
539
  color: var(--color-fg-muted);
526
- --tw-ring-color: var(--color-ring);
540
+ background: transparent;
541
+ border: none;
542
+ cursor: pointer;
543
+ border-radius: var(--ui-radius-sm);
544
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease),
545
+ color var(--ui-motion-fast) var(--ui-motion-ease);
527
546
  }
547
+ .tabs-trigger:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
548
+ .tabs-trigger[aria-selected='true'],
528
549
  .tabs-trigger[data-state='active'] {
529
550
  background-color: var(--color-bg-elevated);
530
551
  color: var(--color-fg);
531
552
  }
532
- .tabs-content {
533
- @apply focus-visible:outline-none;
534
- }
553
+ .tabs-content:focus-visible { outline: none; }
535
554
 
536
555
  /* ============================================================ */
537
556
  /* Toast */
@@ -540,22 +559,13 @@
540
559
  @apply pointer-events-none fixed bottom-4 right-4 z-[100] flex w-full max-w-sm flex-col gap-2;
541
560
  }
542
561
  .toast {
543
- @apply pointer-events-auto flex items-start gap-3 p-3 shadow-md;
544
- background-color: var(--color-bg-elevated);
545
- color: var(--color-fg);
546
- border: 1px solid var(--color-border);
547
- border-radius: var(--ui-radius);
548
- animation: norns-toast-in 200ms ease-out;
562
+ @apply pointer-events-auto flex items-start gap-3 p-3;
563
+ /* base surface comes from `.surface-elevated` selector list */
564
+ animation: norns-toast-in var(--ui-motion-med) var(--ui-motion-ease);
549
565
  }
550
566
  @keyframes norns-toast-in {
551
- from {
552
- opacity: 0;
553
- transform: translateX(1rem);
554
- }
555
- to {
556
- opacity: 1;
557
- transform: translateX(0);
558
- }
567
+ from { opacity: 0; transform: translateX(1rem); }
568
+ to { opacity: 1; transform: translateX(0); }
559
569
  }
560
570
  .toast-info {
561
571
  background-color: var(--color-info-bg);
@@ -577,22 +587,16 @@
577
587
  color: var(--color-danger-fg);
578
588
  border-color: var(--color-danger-border);
579
589
  }
580
- .toast-message {
581
- @apply flex-1 text-sm leading-snug;
582
- }
590
+ .toast-message { @apply flex-1 text-sm leading-snug; }
583
591
  .toast-close {
584
- @apply -mr-1 -mt-0.5 inline-flex size-6 shrink-0 items-center justify-center rounded;
585
- @apply focus-visible:outline-none focus-visible:ring-2;
592
+ @apply -mr-1 -mt-0.5;
586
593
  color: currentColor;
587
594
  opacity: 0.6;
588
- --tw-ring-color: var(--color-ring);
589
- }
590
- .toast-close:hover {
591
- opacity: 1;
592
595
  }
596
+ .toast-close:hover { opacity: 1; background-color: transparent; color: currentColor; }
593
597
 
594
598
  /* ============================================================ */
595
- /* Card / Surface */
599
+ /* Surface / Card */
596
600
  /* ============================================================ */
597
601
  .surface {
598
602
  background-color: var(--color-bg-elevated);
@@ -610,21 +614,15 @@
610
614
  color: var(--color-fg);
611
615
  border: 1px solid var(--color-border);
612
616
  border-radius: var(--ui-radius-lg);
613
- /* No overflow:hidden — would clip popovers / menu dropdowns inside.
614
- * Header & footer round their own corners explicitly. */
615
- }
616
- .card-padded {
617
- @apply p-5;
618
617
  }
618
+ .card-padded { @apply p-5; }
619
619
  .card-header {
620
620
  @apply px-5 pt-5 pb-3;
621
621
  border-bottom: 1px solid var(--color-border);
622
622
  border-top-left-radius: var(--ui-radius-lg);
623
623
  border-top-right-radius: var(--ui-radius-lg);
624
624
  }
625
- .card-body {
626
- @apply p-5;
627
- }
625
+ .card-body { @apply p-5; }
628
626
  .card-footer {
629
627
  @apply px-5 py-3;
630
628
  border-top: 1px solid var(--color-border);
@@ -635,16 +633,18 @@
635
633
  .card-interactive {
636
634
  @apply cursor-pointer no-underline;
637
635
  color: inherit;
638
- transition: border-color 150ms ease-out, transform 150ms ease-out, box-shadow 150ms ease-out;
636
+ transition: border-color var(--ui-motion-fast) var(--ui-motion-ease),
637
+ transform var(--ui-motion-fast) var(--ui-motion-ease),
638
+ box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
639
639
  }
640
640
  .card-interactive:hover {
641
641
  border-color: var(--color-border-strong);
642
642
  transform: translateY(-2px);
643
- box-shadow: 0 12px 24px -8px oklch(0% 0 0 / 0.10);
643
+ box-shadow: var(--ui-elev-2);
644
644
  }
645
645
  .card-interactive:focus-visible {
646
- outline: 2px solid var(--color-ring);
647
- outline-offset: 2px;
646
+ outline: none;
647
+ box-shadow: var(--ui-focus-ring);
648
648
  }
649
649
 
650
650
  /* ============================================================ */
@@ -654,17 +654,11 @@
654
654
  @apply flex items-start gap-3 p-3 text-sm;
655
655
  border: 1px solid;
656
656
  border-radius: var(--ui-radius);
657
- animation: norns-banner-in 250ms cubic-bezier(0.21, 1.02, 0.73, 1) both;
657
+ animation: norns-banner-in var(--ui-motion-med) var(--ui-motion-ease) both;
658
658
  }
659
659
  @keyframes norns-banner-in {
660
- from {
661
- opacity: 0;
662
- transform: translateY(-4px);
663
- }
664
- to {
665
- opacity: 1;
666
- transform: translateY(0);
667
- }
660
+ from { opacity: 0; transform: translateY(-4px); }
661
+ to { opacity: 1; transform: translateY(0); }
668
662
  }
669
663
  .banner-info {
670
664
  background-color: var(--color-info-bg);
@@ -686,18 +680,12 @@
686
680
  color: var(--color-danger-fg);
687
681
  border-color: var(--color-danger-border);
688
682
  }
689
- .banner-icon {
690
- @apply shrink-0 mt-0.5;
691
- }
692
- .banner-body {
693
- @apply flex-1;
694
- }
695
- .banner-actions {
696
- @apply shrink-0 flex gap-2 ml-auto;
697
- }
683
+ .banner-icon { @apply shrink-0 mt-0.5; }
684
+ .banner-body { @apply flex-1; }
685
+ .banner-actions { @apply shrink-0 flex gap-2 ml-auto; }
698
686
 
699
687
  /* ============================================================ */
700
- /* Badge */
688
+ /* Badge / Chip */
701
689
  /* ============================================================ */
702
690
  .badge {
703
691
  @apply inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium leading-none;
@@ -713,29 +701,12 @@
713
701
  background-color: var(--color-primary-950);
714
702
  color: var(--color-primary-200);
715
703
  }
716
- .badge-success {
717
- background-color: var(--color-success-bg);
718
- color: var(--color-success-fg);
719
- }
720
- .badge-warning {
721
- background-color: var(--color-warning-bg);
722
- color: var(--color-warning-fg);
723
- }
724
- .badge-danger {
725
- background-color: var(--color-danger-bg);
726
- color: var(--color-danger-fg);
727
- }
728
- .badge-info {
729
- background-color: var(--color-info-bg);
730
- color: var(--color-info-fg);
731
- }
732
- .badge-sm {
733
- @apply text-[10px] px-1.5;
734
- }
704
+ .badge-success { background-color: var(--color-success-bg); color: var(--color-success-fg); }
705
+ .badge-warning { background-color: var(--color-warning-bg); color: var(--color-warning-fg); }
706
+ .badge-danger { background-color: var(--color-danger-bg); color: var(--color-danger-fg); }
707
+ .badge-info { background-color: var(--color-info-bg); color: var(--color-info-fg); }
708
+ .badge-sm { @apply text-[10px] px-1.5; }
735
709
 
736
- /* ============================================================ */
737
- /* Chip */
738
- /* ============================================================ */
739
710
  .chip {
740
711
  @apply inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium;
741
712
  background-color: var(--color-bg-muted);
@@ -746,14 +717,16 @@
746
717
  .chip-remove {
747
718
  @apply -mr-1 inline-flex size-4 items-center justify-center rounded-full;
748
719
  opacity: 0.6;
720
+ transition: opacity var(--ui-motion-fast) var(--ui-motion-ease),
721
+ background-color var(--ui-motion-fast) var(--ui-motion-ease);
749
722
  }
750
723
  .chip-remove:hover {
751
724
  opacity: 1;
752
- background-color: var(--color-border);
725
+ background-color: var(--color-bg-muted);
753
726
  }
754
727
 
755
728
  /* ============================================================ */
756
- /* Avatar */
729
+ /* Avatar */
757
730
  /* ============================================================ */
758
731
  .avatar {
759
732
  @apply relative inline-flex shrink-0 items-center justify-center overflow-hidden font-medium;
@@ -761,48 +734,35 @@
761
734
  color: var(--color-fg);
762
735
  border-radius: var(--ui-radius-full);
763
736
  }
764
- .avatar-sm {
765
- @apply size-6 text-xs;
766
- }
767
- .avatar-md {
768
- @apply size-8 text-sm;
769
- }
770
- .avatar-lg {
771
- @apply size-12 text-base;
772
- }
773
- .avatar-xl {
774
- @apply size-16 text-lg;
775
- }
776
- .avatar img {
777
- @apply h-full w-full object-cover;
778
- }
737
+ .avatar-sm { @apply size-6 text-xs; }
738
+ .avatar-md { @apply size-8 text-sm; }
739
+ .avatar-lg { @apply size-12 text-base; }
740
+ .avatar-xl { @apply size-16 text-lg; }
741
+ .avatar img { @apply h-full w-full object-cover; }
779
742
 
780
- /* AvatarGroup */
781
- .avatar-group {
782
- @apply inline-flex items-center;
783
- }
743
+ .avatar-group { @apply inline-flex items-center; }
784
744
  .avatar-group-item,
785
745
  .avatar-group-overflow {
786
- @apply relative inline-flex transition-transform duration-150 ease-out;
746
+ @apply relative inline-flex;
787
747
  margin-left: -0.5rem;
788
748
  box-shadow: 0 0 0 2px var(--color-bg);
789
749
  border-radius: var(--ui-radius-full);
750
+ transition: transform var(--ui-motion-fast) var(--ui-motion-ease);
790
751
  }
791
752
  .avatar-group-item:first-child,
792
753
  .avatar-group:has(.avatar-group-item:first-child) > .avatar-group-item:first-child {
793
754
  margin-left: 0;
794
755
  }
795
- .avatar-group-item:hover {
796
- transform: translateY(-2px);
797
- z-index: 1;
798
- }
756
+ .avatar-group-item:hover { transform: translateY(-2px); z-index: 1; }
799
757
  .avatar-group-overflow {
800
758
  @apply text-xs font-medium;
801
759
  background-color: var(--color-bg-muted);
802
760
  color: var(--color-fg-muted);
803
761
  }
804
762
 
805
- /* GradientText */
763
+ /* ============================================================ */
764
+ /* Decorative buttons (CSS-only motion) */
765
+ /* ============================================================ */
806
766
  .gradient-text {
807
767
  background-clip: text;
808
768
  -webkit-background-clip: text;
@@ -810,29 +770,20 @@
810
770
  background-size: 200% 200%;
811
771
  background-position: 0% 50%;
812
772
  }
813
- .gradient-text-anim {
814
- animation: norns-gradient-shift 6s ease-in-out infinite;
815
- }
773
+ .gradient-text-anim { animation: norns-gradient-shift 6s ease-in-out infinite; }
816
774
  @keyframes norns-gradient-shift {
817
775
  0%, 100% { background-position: 0% 50%; }
818
776
  50% { background-position: 100% 50%; }
819
777
  }
820
778
 
821
- /* CopyButton */
822
- .copy-btn-check {
823
- animation: norns-copy-pop 250ms ease-out;
824
- }
779
+ .copy-btn-check { animation: norns-copy-pop 250ms ease-out; }
825
780
  @keyframes norns-copy-pop {
826
781
  0% { transform: scale(0.5); opacity: 0; }
827
782
  70% { transform: scale(1.15); }
828
783
  100% { transform: scale(1); opacity: 1; }
829
784
  }
830
785
 
831
- /* ShinyButton sweeping shine overlay */
832
- .shiny-btn {
833
- position: relative;
834
- isolation: isolate;
835
- }
786
+ .shiny-btn { position: relative; isolation: isolate; }
836
787
  .shiny-btn-shine {
837
788
  position: absolute;
838
789
  inset: 0;
@@ -854,33 +805,21 @@
854
805
  100% { background-position: -150% 0; }
855
806
  }
856
807
 
857
- /* RippleButton */
858
- .ripple-btn {
859
- position: relative;
860
- overflow: hidden;
861
- }
808
+ .ripple-btn { position: relative; overflow: hidden; }
862
809
  .ripple-btn-ripple {
863
810
  position: absolute;
864
811
  pointer-events: none;
865
- border-radius: 9999px;
812
+ border-radius: var(--ui-radius-full);
866
813
  background-color: color-mix(in oklab, currentColor 35%, transparent);
867
814
  transform: scale(0);
868
815
  opacity: 0.6;
869
816
  animation: norns-ripple 600ms ease-out forwards;
870
817
  }
871
818
  @keyframes norns-ripple {
872
- to {
873
- transform: scale(1);
874
- opacity: 0;
875
- }
819
+ to { transform: scale(1); opacity: 0; }
876
820
  }
877
821
 
878
- /* LiquidButton (motion subpath) */
879
- .liquid-btn {
880
- position: relative;
881
- isolation: isolate;
882
- overflow: hidden;
883
- }
822
+ .liquid-btn { position: relative; isolation: isolate; overflow: hidden; }
884
823
  .liquid-btn-blob {
885
824
  position: absolute;
886
825
  inset: -25%;
@@ -900,17 +839,12 @@
900
839
  z-index: 1;
901
840
  }
902
841
 
903
- /* Sparkles (motion subpath) */
904
- .sparkles-wrap {
905
- @apply relative inline-block;
906
- }
907
- .sparkles-overlay {
908
- @apply pointer-events-none absolute inset-0 overflow-hidden;
909
- }
842
+ .sparkles-wrap { @apply relative inline-block; }
843
+ .sparkles-overlay { @apply pointer-events-none absolute inset-0 overflow-hidden; }
910
844
  .sparkle {
911
845
  position: absolute;
912
846
  display: block;
913
- border-radius: 9999px;
847
+ border-radius: var(--ui-radius-full);
914
848
  background: var(--sparkle-color, var(--color-primary-400));
915
849
  opacity: 0;
916
850
  transform: scale(0);
@@ -918,16 +852,13 @@
918
852
  animation-fill-mode: forwards;
919
853
  filter: drop-shadow(0 0 4px var(--sparkle-color, var(--color-primary-400)));
920
854
  }
921
- .sparkles-paused .sparkle {
922
- animation-play-state: paused;
923
- }
855
+ .sparkles-paused .sparkle { animation-play-state: paused; }
924
856
  @keyframes norns-sparkle {
925
857
  0%, 100% { opacity: 0; transform: scale(0) rotate(0deg); }
926
858
  20%, 80% { opacity: 1; }
927
859
  50% { transform: scale(1) rotate(180deg); }
928
860
  }
929
861
 
930
- /* GradientBackground (motion subpath) */
931
862
  .gradient-bg {
932
863
  @apply relative overflow-hidden;
933
864
  background-image: linear-gradient(
@@ -945,7 +876,7 @@
945
876
  }
946
877
 
947
878
  /* ============================================================ */
948
- /* Skeleton */
879
+ /* Skeleton / Progress */
949
880
  /* ============================================================ */
950
881
  .skeleton {
951
882
  @apply block;
@@ -953,38 +884,26 @@
953
884
  border-radius: var(--ui-radius-sm);
954
885
  animation: norns-skeleton-pulse 1.6s ease-in-out infinite;
955
886
  }
956
- .skeleton-circle {
957
- border-radius: var(--ui-radius-full);
958
- }
887
+ .skeleton-circle { border-radius: var(--ui-radius-full); }
959
888
  @keyframes norns-skeleton-pulse {
960
889
  0%, 100% { opacity: 1; }
961
890
  50% { opacity: 0.5; }
962
891
  }
963
892
 
964
- /* ============================================================ */
965
- /* Progress */
966
- /* ============================================================ */
967
893
  .progress {
968
894
  @apply relative h-2 w-full overflow-hidden;
969
895
  background-color: var(--color-bg-muted);
970
896
  border-radius: var(--ui-radius-full);
971
897
  }
972
898
  .progress-bar {
973
- @apply h-full transition-[width] duration-300 ease-out;
899
+ @apply h-full;
974
900
  background-color: var(--color-primary-600);
975
901
  border-radius: var(--ui-radius-full);
902
+ transition: width var(--ui-motion-slow) var(--ui-motion-ease);
976
903
  }
977
- .progress-bar-success {
978
- background-color: var(--color-success-600);
979
- }
980
- .progress-bar-warning {
981
- background-color: var(--color-warning-600);
982
- }
983
- .progress-bar-danger {
984
- background-color: var(--color-danger-600);
985
- }
986
-
987
- /* Indeterminate (uses CSS keyframes only) */
904
+ .progress-bar-success { background-color: var(--color-success-600); }
905
+ .progress-bar-warning { background-color: var(--color-warning-600); }
906
+ .progress-bar-danger { background-color: var(--color-danger-600); }
988
907
  .progress-indeterminate .progress-bar {
989
908
  width: 35%;
990
909
  animation: norns-progress-indeterminate 1.4s ease-in-out infinite;
@@ -995,7 +914,7 @@
995
914
  }
996
915
 
997
916
  /* ============================================================ */
998
- /* Header (composite) */
917
+ /* Header / Hero / Stepper / Breadcrumbs / Pagination */
999
918
  /* ============================================================ */
1000
919
  .norns-header {
1001
920
  @apply flex items-center justify-between gap-4 px-6 py-3;
@@ -1011,25 +930,16 @@
1011
930
  @apply flex items-center gap-2 font-semibold;
1012
931
  color: var(--color-fg);
1013
932
  }
1014
- .norns-header-nav {
1015
- @apply flex items-center gap-1;
1016
- }
1017
- .norns-header-actions {
1018
- @apply flex items-center gap-2;
1019
- }
933
+ .norns-header-nav { @apply flex items-center gap-1; }
934
+ .norns-header-actions { @apply flex items-center gap-2; }
1020
935
 
1021
- /* ============================================================ */
1022
- /* HeroBanner (composite) */
1023
- /* ============================================================ */
1024
936
  .hero {
1025
937
  @apply relative overflow-hidden p-8 md:p-12;
1026
938
  background-color: var(--color-bg-elevated);
1027
939
  border: 1px solid var(--color-border);
1028
940
  border-radius: var(--ui-radius-lg);
1029
941
  }
1030
- .hero-center {
1031
- @apply text-center;
1032
- }
942
+ .hero-center { @apply text-center; }
1033
943
  .hero-title {
1034
944
  @apply text-3xl font-bold tracking-tight md:text-4xl;
1035
945
  color: var(--color-fg);
@@ -1038,41 +948,26 @@
1038
948
  @apply mt-3 text-base md:text-lg max-w-prose;
1039
949
  color: var(--color-fg-muted);
1040
950
  }
1041
- .hero-center .hero-description {
1042
- @apply mx-auto;
1043
- }
1044
- .hero-actions {
1045
- @apply mt-6 flex flex-wrap items-center gap-3;
1046
- }
1047
- .hero-center .hero-actions {
1048
- @apply justify-center;
1049
- }
951
+ .hero-center .hero-description { @apply mx-auto; }
952
+ .hero-actions { @apply mt-6 flex flex-wrap items-center gap-3; }
953
+ .hero-center .hero-actions { @apply justify-center; }
1050
954
  .hero-image {
1051
955
  @apply pointer-events-none absolute inset-y-0 right-0 w-1/2 object-cover opacity-20;
1052
956
  }
1053
957
 
1054
- /* ============================================================ */
1055
- /* Stepper */
1056
- /* ============================================================ */
1057
- .stepper {
1058
- @apply flex items-center gap-3;
1059
- }
1060
- .stepper-vertical {
1061
- @apply flex-col items-stretch;
1062
- }
958
+ .stepper { @apply flex items-center gap-3; }
959
+ .stepper-vertical { @apply flex-col items-stretch; }
1063
960
  .stepper-step {
1064
961
  @apply flex items-center gap-2 text-sm;
1065
962
  color: var(--color-fg-muted);
1066
963
  }
1067
- .stepper-step-current {
1068
- color: var(--color-fg);
1069
- font-weight: 600;
1070
- }
1071
- .stepper-step-complete {
1072
- color: var(--color-fg);
1073
- }
964
+ .stepper-step-current { color: var(--color-fg); font-weight: 600; }
965
+ .stepper-step-complete { color: var(--color-fg); }
1074
966
  .stepper-marker {
1075
- @apply inline-flex size-7 shrink-0 items-center justify-center rounded-full text-xs font-semibold;
967
+ @apply inline-flex shrink-0 items-center justify-center text-xs font-semibold;
968
+ width: var(--ui-icon-btn-md);
969
+ height: var(--ui-icon-btn-md);
970
+ border-radius: var(--ui-radius-full);
1076
971
  background-color: var(--color-bg-muted);
1077
972
  color: var(--color-fg-muted);
1078
973
  border: 1px solid var(--color-border);
@@ -1087,145 +982,126 @@
1087
982
  color: white;
1088
983
  border-color: var(--color-success-600);
1089
984
  }
1090
- .stepper-divider {
1091
- @apply h-px flex-1;
1092
- background-color: var(--color-border);
1093
- }
1094
- .stepper-vertical .stepper-divider {
1095
- @apply h-6 w-px ml-3;
1096
- }
985
+ .stepper-divider { @apply h-px flex-1; background-color: var(--color-border); }
986
+ .stepper-vertical .stepper-divider { @apply h-6 w-px ml-3; }
1097
987
 
1098
- /* ============================================================ */
1099
- /* Breadcrumbs */
1100
- /* ============================================================ */
1101
988
  .breadcrumbs {
1102
989
  @apply flex items-center gap-1 text-sm;
1103
990
  color: var(--color-fg-muted);
1104
991
  }
1105
- .breadcrumb-item {
1106
- @apply inline-flex items-center gap-1;
1107
- }
1108
- .breadcrumb-link {
1109
- color: var(--color-fg-muted);
1110
- }
1111
- .breadcrumb-link:hover {
1112
- color: var(--color-fg);
1113
- }
1114
- .breadcrumb-current {
1115
- color: var(--color-fg);
1116
- font-weight: 500;
1117
- }
992
+ .breadcrumb-item { @apply inline-flex items-center gap-1; }
993
+ .breadcrumb-link { color: var(--color-fg-muted); }
994
+ .breadcrumb-link:hover { color: var(--color-fg); }
995
+ .breadcrumb-current { color: var(--color-fg); font-weight: 500; }
1118
996
  .breadcrumb-separator {
1119
997
  @apply mx-1 select-none;
1120
998
  color: var(--color-fg-subtle);
1121
999
  }
1122
1000
 
1123
- /* ============================================================ */
1124
- /* Pagination */
1125
- /* ============================================================ */
1126
- .pagination {
1127
- @apply inline-flex items-center gap-1;
1128
- }
1001
+ .pagination { @apply inline-flex items-center gap-1; }
1129
1002
  .pagination-item {
1130
- @apply inline-flex size-8 items-center justify-center text-sm;
1131
- color: var(--color-fg-muted);
1132
- border-radius: var(--ui-radius-sm);
1133
- }
1134
- .pagination-item:hover {
1135
- background-color: var(--color-bg-muted);
1136
- color: var(--color-fg);
1137
- }
1138
- .pagination-item-active {
1139
- background-color: var(--color-primary-600);
1140
- color: white;
1003
+ /* base + hover come from `.btn-icon` selector list above. The pagination
1004
+ * variant is full-md icon-button size (8x8 in original), so override
1005
+ * the small default. */
1006
+ width: var(--ui-icon-btn-md);
1007
+ height: var(--ui-icon-btn-md);
1141
1008
  }
1009
+ .pagination-item-active,
1142
1010
  .pagination-item-active:hover {
1143
- background-color: var(--color-primary-700);
1011
+ background-color: var(--color-primary-600);
1144
1012
  color: white;
1145
1013
  }
1146
1014
  .pagination-item:disabled {
1147
1015
  @apply cursor-not-allowed opacity-40;
1148
1016
  }
1149
1017
  .pagination-ellipsis {
1150
- @apply inline-flex size-8 items-center justify-center text-sm select-none;
1018
+ @apply inline-flex items-center justify-center text-sm select-none;
1019
+ width: var(--ui-icon-btn-md);
1020
+ height: var(--ui-icon-btn-md);
1151
1021
  color: var(--color-fg-subtle);
1152
1022
  }
1153
1023
 
1154
1024
  /* ============================================================ */
1155
- /* Accordion */
1025
+ /* Accordion / Collapsible — both built on <details> */
1156
1026
  /* ============================================================ */
1157
1027
  .accordion {
1158
- @apply divide-y;
1159
1028
  border: 1px solid var(--color-border);
1160
1029
  border-radius: var(--ui-radius);
1161
1030
  }
1162
- .accordion :where(.divide-y) > * {
1163
- border-color: var(--color-border);
1164
- }
1165
- .accordion-item {
1166
- border-bottom: 1px solid var(--color-border);
1167
- }
1168
- .accordion-item:last-child {
1169
- border-bottom: none;
1170
- }
1031
+ .accordion-item { border-bottom: 1px solid var(--color-border); }
1032
+ .accordion-item:last-child { border-bottom: none; }
1171
1033
  .accordion-trigger {
1172
- @apply flex w-full items-center justify-between gap-2 px-4 py-3 text-left text-sm font-medium;
1173
- @apply transition-colors focus-visible:outline-none focus-visible:ring-2;
1034
+ @apply flex w-full items-center justify-between gap-2 px-4 py-3 text-left text-sm font-medium cursor-pointer;
1174
1035
  color: var(--color-fg);
1175
- background-color: transparent;
1176
- --tw-ring-color: var(--color-ring);
1177
- }
1178
- .accordion-trigger:hover {
1179
- background-color: var(--color-bg-muted);
1036
+ background: transparent;
1037
+ list-style: none;
1038
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1180
1039
  }
1181
- .accordion-trigger[data-state='open'] .accordion-chevron {
1040
+ .accordion-trigger::-webkit-details-marker { display: none; }
1041
+ .accordion-trigger:hover { background-color: var(--color-bg-muted); }
1042
+ .accordion-trigger:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1043
+ .accordion-item[open] > .accordion-trigger > .accordion-chevron {
1182
1044
  transform: rotate(180deg);
1183
1045
  }
1184
1046
  .accordion-chevron {
1185
- @apply size-4 shrink-0 transition-transform duration-150;
1047
+ @apply size-4 shrink-0;
1186
1048
  color: var(--color-fg-muted);
1049
+ transition: transform var(--ui-motion-fast) var(--ui-motion-ease);
1187
1050
  }
1188
1051
  .accordion-content {
1189
1052
  @apply px-4 pb-3 text-sm;
1190
1053
  color: var(--color-fg-muted);
1191
1054
  }
1192
1055
 
1193
- /* ============================================================ */
1194
- /* Separator */
1195
- /* ============================================================ */
1196
- .separator {
1197
- background-color: var(--color-border);
1056
+ .collapsible {
1057
+ border: 1px solid var(--color-border);
1058
+ border-radius: var(--ui-radius);
1059
+ background-color: var(--color-bg-elevated);
1198
1060
  }
1199
- .separator-horizontal {
1200
- @apply h-px w-full;
1061
+ .collapsible-trigger {
1062
+ @apply flex w-full items-center gap-2 px-3 py-2 text-left text-sm font-medium cursor-pointer;
1063
+ color: var(--color-fg);
1064
+ background: transparent;
1065
+ list-style: none;
1066
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1201
1067
  }
1202
- .separator-vertical {
1203
- @apply w-px h-full self-stretch;
1068
+ .collapsible-trigger::-webkit-details-marker { display: none; }
1069
+ .collapsible-trigger:hover { background-color: var(--color-bg-muted); }
1070
+ .collapsible-trigger:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1071
+ .collapsible[open] > .collapsible-trigger > .collapsible-chevron {
1072
+ transform: rotate(90deg);
1073
+ }
1074
+ .collapsible-chevron {
1075
+ @apply size-4 shrink-0;
1076
+ color: var(--color-fg-muted);
1077
+ transition: transform var(--ui-motion-fast) var(--ui-motion-ease);
1078
+ }
1079
+ .collapsible-title { @apply flex-1; }
1080
+ .collapsible-content {
1081
+ @apply px-3 pb-3 pt-1 text-sm;
1082
+ color: var(--color-fg-muted);
1204
1083
  }
1205
1084
 
1206
1085
  /* ============================================================ */
1207
- /* Toolbar */
1086
+ /* Separator / Toolbar */
1208
1087
  /* ============================================================ */
1088
+ .separator { background-color: var(--color-border); }
1089
+ .separator-horizontal { @apply h-px w-full; }
1090
+ .separator-vertical { @apply w-px h-full self-stretch; }
1091
+
1209
1092
  .toolbar {
1210
1093
  @apply flex items-center gap-1 p-1;
1211
1094
  background-color: var(--color-bg-muted);
1212
1095
  border: 1px solid var(--color-border);
1213
1096
  border-radius: var(--ui-radius);
1214
1097
  }
1215
- .toolbar-vertical {
1216
- @apply flex-col items-stretch;
1217
- }
1098
+ .toolbar-vertical { @apply flex-col items-stretch; }
1218
1099
 
1219
1100
  /* ============================================================ */
1220
- /* ButtonGroup */
1101
+ /* ButtonGroup — covers `.btn`, `.btn-secondary[data-pressed]` */
1221
1102
  /* ============================================================ */
1222
- .btn-group {
1223
- @apply inline-flex;
1224
- }
1225
- .btn-group > .btn,
1226
- .btn-group > .toggle-btn {
1227
- border-radius: 0;
1228
- }
1103
+ .btn-group { @apply inline-flex; }
1104
+ .btn-group > * { border-radius: 0; }
1229
1105
  .btn-group > :first-child {
1230
1106
  border-top-left-radius: var(--ui-radius);
1231
1107
  border-bottom-left-radius: var(--ui-radius);
@@ -1234,95 +1110,19 @@
1234
1110
  border-top-right-radius: var(--ui-radius);
1235
1111
  border-bottom-right-radius: var(--ui-radius);
1236
1112
  }
1237
- .btn-group > * + * {
1238
- margin-left: -1px;
1239
- }
1240
- .btn-group-vertical {
1241
- @apply flex-col;
1242
- }
1243
- .btn-group-vertical > * {
1244
- width: 100%;
1245
- }
1113
+ .btn-group > * + * { margin-left: -1px; }
1114
+
1115
+ .btn-group-vertical { @apply flex-col; }
1116
+ .btn-group-vertical > * { width: 100%; border-radius: 0; }
1246
1117
  .btn-group-vertical > :first-child {
1247
- border-radius: 0;
1248
1118
  border-top-left-radius: var(--ui-radius);
1249
1119
  border-top-right-radius: var(--ui-radius);
1250
1120
  }
1251
1121
  .btn-group-vertical > :last-child {
1252
- border-radius: 0;
1253
1122
  border-bottom-left-radius: var(--ui-radius);
1254
1123
  border-bottom-right-radius: var(--ui-radius);
1255
1124
  }
1256
- .btn-group-vertical > * + * {
1257
- margin-left: 0;
1258
- margin-top: -1px;
1259
- }
1260
-
1261
- /* ============================================================ */
1262
- /* ToggleButton */
1263
- /* ============================================================ */
1264
- .toggle-btn {
1265
- @apply inline-flex items-center justify-center gap-1.5 text-sm font-medium;
1266
- @apply transition-colors focus-visible:outline-none focus-visible:ring-2;
1267
- @apply disabled:cursor-not-allowed disabled:opacity-60;
1268
- height: var(--ui-h-md);
1269
- padding-inline: 0.625rem;
1270
- background-color: transparent;
1271
- color: var(--color-fg-muted);
1272
- border: 1px solid var(--color-border);
1273
- border-radius: var(--ui-radius);
1274
- --tw-ring-color: var(--color-ring);
1275
- }
1276
- .toggle-btn:hover {
1277
- background-color: var(--color-bg-muted);
1278
- color: var(--color-fg);
1279
- }
1280
- .toggle-btn[data-state='on'] {
1281
- background-color: var(--color-bg-muted);
1282
- color: var(--color-fg);
1283
- border-color: var(--color-border-strong);
1284
- }
1285
- .toggle-btn-sm {
1286
- height: var(--ui-h-sm);
1287
- @apply text-xs px-2;
1288
- }
1289
- .toggle-btn-lg {
1290
- height: var(--ui-h-lg);
1291
- @apply text-base px-3;
1292
- }
1293
-
1294
- /* ============================================================ */
1295
- /* Collapsible */
1296
- /* ============================================================ */
1297
- .collapsible {
1298
- border: 1px solid var(--color-border);
1299
- border-radius: var(--ui-radius);
1300
- background-color: var(--color-bg-elevated);
1301
- }
1302
- .collapsible-trigger {
1303
- @apply flex w-full items-center gap-2 px-3 py-2 text-left text-sm font-medium;
1304
- @apply transition-colors focus-visible:outline-none focus-visible:ring-2;
1305
- color: var(--color-fg);
1306
- background-color: transparent;
1307
- --tw-ring-color: var(--color-ring);
1308
- }
1309
- .collapsible-trigger:hover {
1310
- background-color: var(--color-bg-muted);
1311
- }
1312
- .collapsible-chevron {
1313
- @apply size-4 shrink-0 transition-transform duration-150;
1314
- color: var(--color-fg-muted);
1315
- }
1316
- .collapsible-chevron-open {
1317
- transform: rotate(90deg);
1318
- }
1319
- .collapsible-title {
1320
- @apply flex-1;
1321
- }
1322
- .collapsible-content {
1323
- @apply px-3 pb-3 pt-1 text-sm;
1324
- color: var(--color-fg-muted);
1325
- }
1125
+ .btn-group-vertical > * + * { margin-left: 0; margin-top: -1px; }
1326
1126
 
1327
1127
  /* ============================================================ */
1328
1128
  /* NumberInput */
@@ -1335,15 +1135,22 @@
1335
1135
  @apply text-center;
1336
1136
  border-radius: 0;
1337
1137
  min-width: 4rem;
1138
+ -moz-appearance: textfield;
1139
+ }
1140
+ .number-input-field::-webkit-outer-spin-button,
1141
+ .number-input-field::-webkit-inner-spin-button {
1142
+ -webkit-appearance: none;
1143
+ margin: 0;
1338
1144
  }
1339
1145
  .number-step {
1340
- @apply inline-flex items-center justify-center px-2;
1146
+ @apply inline-flex items-center justify-center px-2 cursor-pointer;
1341
1147
  background-color: var(--color-bg-muted);
1342
1148
  color: var(--color-fg);
1343
1149
  border: 1px solid var(--color-border-strong);
1344
- --tw-ring-color: var(--color-ring);
1345
1150
  height: var(--ui-h-md);
1151
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1346
1152
  }
1153
+ .number-step:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1347
1154
  .number-step:first-child {
1348
1155
  border-top-left-radius: var(--ui-radius);
1349
1156
  border-bottom-left-radius: var(--ui-radius);
@@ -1354,44 +1161,28 @@
1354
1161
  border-bottom-right-radius: var(--ui-radius);
1355
1162
  border-left: none;
1356
1163
  }
1357
- .number-step:hover:not(:disabled) {
1358
- background-color: var(--color-bg-subtle);
1359
- }
1360
- .number-step:disabled {
1361
- @apply cursor-not-allowed opacity-40;
1362
- }
1363
- .number-input-field {
1364
- border-radius: 0;
1365
- }
1366
- /* Hide native spinners since we have our own buttons */
1367
- .number-input-field::-webkit-outer-spin-button,
1368
- .number-input-field::-webkit-inner-spin-button {
1369
- -webkit-appearance: none;
1370
- margin: 0;
1371
- }
1372
- .number-input-field {
1373
- -moz-appearance: textfield;
1374
- }
1164
+ .number-step:hover:not(:disabled) { background-color: var(--color-bg-subtle); }
1165
+ .number-step:disabled { @apply cursor-not-allowed opacity-40; }
1375
1166
 
1376
1167
  /* ============================================================ */
1377
1168
  /* OtpField */
1378
1169
  /* ============================================================ */
1379
- .otp-field {
1380
- @apply inline-flex gap-2;
1381
- }
1170
+ .otp-field { @apply inline-flex gap-2; }
1382
1171
  .otp-cell {
1383
1172
  @apply text-center font-mono text-lg;
1384
- @apply focus:outline-none focus:ring-2;
1385
1173
  width: 2.75rem;
1386
1174
  height: 3rem;
1387
1175
  background-color: var(--color-bg);
1388
1176
  color: var(--color-fg);
1389
1177
  border: 1px solid var(--color-border-strong);
1390
1178
  border-radius: var(--ui-radius);
1391
- --tw-ring-color: color-mix(in oklab, var(--color-ring) 30%, transparent);
1179
+ transition: border-color var(--ui-motion-fast) var(--ui-motion-ease),
1180
+ box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
1392
1181
  }
1393
- .otp-cell:focus {
1182
+ .otp-cell:focus-visible {
1183
+ outline: none;
1394
1184
  border-color: var(--color-ring);
1185
+ box-shadow: var(--ui-focus-ring);
1395
1186
  }
1396
1187
 
1397
1188
  /* ============================================================ */
@@ -1399,44 +1190,40 @@
1399
1190
  /* ============================================================ */
1400
1191
  .tags-input {
1401
1192
  @apply flex w-full flex-wrap items-center gap-1.5 px-2 py-1.5;
1402
- @apply transition-colors focus-within:ring-2;
1403
1193
  min-height: var(--ui-h-md);
1404
1194
  background-color: var(--color-bg);
1405
1195
  color: var(--color-fg);
1406
1196
  border: 1px solid var(--color-border-strong);
1407
1197
  border-radius: var(--ui-radius);
1408
- --tw-ring-color: color-mix(in oklab, var(--color-ring) 30%, transparent);
1198
+ transition: border-color var(--ui-motion-fast) var(--ui-motion-ease),
1199
+ box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
1409
1200
  }
1410
1201
  .tags-input:focus-within {
1202
+ outline: none;
1411
1203
  border-color: var(--color-ring);
1204
+ box-shadow: var(--ui-focus-ring);
1412
1205
  }
1413
- .tags-input-err {
1414
- border-color: var(--color-danger-500);
1415
- }
1206
+ .tags-input-err { border-color: var(--color-danger-500); }
1416
1207
  .tags-input-field {
1417
1208
  @apply min-w-[6rem] flex-1 text-sm outline-none;
1418
1209
  background: transparent;
1419
1210
  color: var(--color-fg);
1420
1211
  }
1421
- .tags-input-field::placeholder {
1422
- color: var(--color-fg-subtle);
1423
- }
1212
+ .tags-input-field::placeholder { color: var(--color-fg-subtle); }
1424
1213
 
1425
1214
  /* ============================================================ */
1426
1215
  /* Timeline */
1427
1216
  /* ============================================================ */
1428
- .timeline {
1429
- @apply list-none p-0 m-0 relative;
1430
- }
1217
+ .timeline { @apply list-none p-0 m-0 relative; }
1431
1218
  .timeline-item {
1432
1219
  @apply relative grid pl-10 pb-6;
1433
1220
  grid-template-columns: 1fr;
1434
1221
  }
1435
- .timeline-item:last-child {
1436
- @apply pb-0;
1437
- }
1222
+ .timeline-item:last-child { @apply pb-0; }
1438
1223
  .timeline-dot {
1439
- @apply absolute left-0 inline-flex size-7 items-center justify-center rounded-full text-white;
1224
+ @apply absolute left-0 inline-flex items-center justify-center rounded-full text-white;
1225
+ width: var(--ui-icon-btn-md);
1226
+ height: var(--ui-icon-btn-md);
1440
1227
  background-color: var(--color-primary-600);
1441
1228
  top: 0;
1442
1229
  }
@@ -1451,85 +1238,94 @@
1451
1238
  bottom: -0.5rem;
1452
1239
  background-color: var(--color-border);
1453
1240
  }
1454
- .timeline-time {
1455
- @apply text-xs;
1456
- color: var(--color-fg-subtle);
1457
- }
1458
- .timeline-title {
1459
- @apply text-sm font-semibold;
1460
- color: var(--color-fg);
1461
- }
1462
- .timeline-description {
1463
- @apply text-sm mt-0.5;
1464
- color: var(--color-fg-muted);
1465
- }
1241
+ .timeline-time { @apply text-xs; color: var(--color-fg-subtle); }
1242
+ .timeline-title { @apply text-sm font-semibold; color: var(--color-fg); }
1243
+ .timeline-description { @apply text-sm mt-0.5; color: var(--color-fg-muted); }
1466
1244
 
1467
1245
  /* ============================================================ */
1468
1246
  /* Combobox / Autocomplete / MultiSelect */
1469
- /* ============================================================ */
1470
- .combobox-input-wrap {
1471
- @apply relative inline-flex w-full items-stretch;
1247
+ /* (Listbox styling — uses dropdown-content + dropdown-item) */
1248
+ /* ============================================================ */
1249
+ .combobox-input-wrap { @apply relative inline-flex w-full items-stretch; }
1250
+ .combobox-trigger { @apply absolute right-1 top-1/2 -translate-y-1/2; }
1251
+ /* Combobox-listbox is rendered with class="dropdown-content combobox-listbox"
1252
+ * so it inherits the elevated surface; this rule adds size constraints. */
1253
+ .combobox-listbox {
1254
+ max-height: 16rem;
1255
+ overflow-y: auto;
1256
+ min-width: 12rem;
1472
1257
  }
1473
- .combobox-input {
1474
- @apply pr-9;
1258
+ .combobox-empty {
1259
+ @apply px-2 py-1.5 text-sm;
1260
+ color: var(--color-fg-muted);
1475
1261
  }
1476
- .combobox-trigger {
1477
- @apply absolute right-1 top-1/2 -translate-y-1/2 inline-flex size-7 items-center justify-center rounded;
1262
+
1263
+ /* MultiSelect uses `.tags-input` as the wrapper so selected chips
1264
+ * render inline with the typed input. The `.multi-select` modifier
1265
+ * makes the wrapper a click target that focuses the inner input. */
1266
+ .multi-select { cursor: text; }
1267
+ .multi-select-toggle {
1268
+ @apply inline-flex items-center justify-center;
1269
+ width: var(--ui-icon-btn-sm);
1270
+ height: var(--ui-icon-btn-sm);
1271
+ padding: 0;
1272
+ border-radius: var(--ui-radius-sm);
1478
1273
  color: var(--color-fg-muted);
1274
+ background: transparent;
1275
+ border: none;
1276
+ cursor: pointer;
1277
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease),
1278
+ color var(--ui-motion-fast) var(--ui-motion-ease);
1479
1279
  }
1480
- .combobox-trigger:hover {
1280
+ .multi-select-toggle:hover {
1481
1281
  background-color: var(--color-bg-muted);
1282
+ color: var(--color-fg);
1482
1283
  }
1284
+ .multi-select-toggle:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1483
1285
 
1484
1286
  /* ============================================================ */
1485
- /* Navigation menu (Hierarchical / Mega) — CSS-only hover/focus */
1486
- /* No JS, no Bits-UI dep. Submenus open on hover OR keyboard */
1487
- /* focus inside the parent <li>. */
1287
+ /* Navigation menu (Hierarchical / Mega) — CSS-only hover/focus */
1488
1288
  /* ============================================================ */
1489
- .nav-menu {
1490
- @apply relative z-10;
1491
- }
1492
- .nav-menu-list {
1493
- @apply flex items-center gap-0 list-none p-0 m-0 flex-wrap;
1494
- }
1495
- .nav-menu-item {
1496
- @apply relative;
1497
- }
1289
+ .nav-menu { @apply relative z-10; }
1290
+ .nav-menu-list { @apply flex items-center gap-0 list-none p-0 m-0 flex-wrap; }
1291
+ .nav-menu-item { @apply relative; }
1498
1292
  .nav-menu-trigger {
1499
- @apply inline-flex items-center gap-1 px-3 text-sm font-medium rounded-md;
1500
- @apply transition-colors focus-visible:outline-none focus-visible:ring-2;
1501
- @apply cursor-pointer select-none;
1293
+ @apply inline-flex items-center gap-1 px-3 text-sm font-medium cursor-pointer select-none;
1502
1294
  height: var(--ui-h-md);
1503
1295
  color: var(--color-fg-muted);
1504
1296
  background-color: transparent;
1505
- --tw-ring-color: var(--color-ring);
1297
+ border-radius: var(--ui-radius-sm);
1506
1298
  text-decoration: none;
1507
1299
  border: none;
1508
1300
  font-family: inherit;
1301
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease),
1302
+ color var(--ui-motion-fast) var(--ui-motion-ease);
1509
1303
  }
1304
+ .nav-menu-trigger:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1510
1305
  .nav-menu-trigger:hover {
1511
1306
  color: var(--color-fg);
1512
1307
  background-color: var(--color-bg-muted);
1513
1308
  }
1514
1309
  .nav-menu-chevron {
1515
- @apply transition-transform duration-200 shrink-0;
1310
+ @apply shrink-0;
1311
+ transition: transform var(--ui-motion-med) var(--ui-motion-ease);
1516
1312
  }
1517
1313
  .nav-menu-content {
1518
- @apply absolute top-full left-0 mt-1 z-50;
1519
- @apply opacity-0 invisible -translate-y-1 transition-[opacity,transform,visibility] duration-200 ease-out;
1520
- background-color: var(--color-bg-elevated);
1521
- border: 1px solid var(--color-border);
1522
- border-radius: var(--ui-radius);
1523
- box-shadow:
1524
- 0 10px 32px -8px oklch(0% 0 0 / 0.18),
1525
- 0 4px 8px -4px oklch(0% 0 0 / 0.06);
1314
+ @apply absolute top-full left-0 mt-1 z-50 invisible;
1315
+ /* base surface comes from `.surface-elevated` selector list */
1526
1316
  min-width: 14rem;
1527
1317
  padding: 0.25rem;
1318
+ opacity: 0;
1319
+ transform: translateY(-4px);
1320
+ transition: opacity var(--ui-motion-med) var(--ui-motion-ease),
1321
+ transform var(--ui-motion-med) var(--ui-motion-ease),
1322
+ visibility var(--ui-motion-med) var(--ui-motion-ease);
1528
1323
  }
1529
- /* Open on hover, keyboard focus inside, OR aria-expanded=true. */
1530
1324
  .nav-menu-item:hover > .nav-menu-content,
1531
1325
  .nav-menu-item:focus-within > .nav-menu-content {
1532
- @apply opacity-100 visible translate-y-0;
1326
+ @apply visible;
1327
+ opacity: 1;
1328
+ transform: translateY(0);
1533
1329
  }
1534
1330
  .nav-menu-item:hover > .nav-menu-trigger,
1535
1331
  .nav-menu-item:focus-within > .nav-menu-trigger {
@@ -1541,15 +1337,14 @@
1541
1337
  transform: rotate(180deg);
1542
1338
  }
1543
1339
  .nav-menu-link {
1544
- @apply flex items-start gap-2 px-3 py-2 text-sm rounded transition-colors;
1340
+ @apply flex items-start gap-2 px-3 py-2 text-sm;
1545
1341
  color: var(--color-fg);
1546
1342
  text-decoration: none;
1343
+ border-radius: var(--ui-radius-sm);
1344
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1547
1345
  }
1548
- .nav-menu-link:hover {
1549
- background-color: var(--color-bg-muted);
1550
- }
1346
+ .nav-menu-link:hover { background-color: var(--color-bg-muted); }
1551
1347
 
1552
- /* MegaMenu — wider, multi-column, anchored to the row not the trigger. */
1553
1348
  .mega-menu-grid {
1554
1349
  @apply grid gap-6 p-5;
1555
1350
  min-width: 32rem;
@@ -1557,86 +1352,46 @@
1557
1352
  .mega-menu-grid.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1558
1353
  .mega-menu-grid.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1559
1354
  .mega-menu-grid.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
1560
- .mega-menu-column {
1561
- @apply flex flex-col gap-1;
1562
- }
1355
+ .mega-menu-column { @apply flex flex-col gap-1; }
1563
1356
  .mega-menu-title {
1564
1357
  @apply text-[11px] uppercase tracking-wider px-3 mb-1 font-semibold;
1565
1358
  color: var(--color-fg-subtle);
1566
1359
  }
1567
1360
 
1568
- /* ============================================================ */
1569
- /* ScrollArea */
1570
- /* ============================================================ */
1571
- .scroll-area-viewport {
1572
- @apply h-full w-full rounded-[inherit];
1573
- }
1574
- .scroll-area-scrollbar {
1575
- @apply flex select-none touch-none p-0.5 transition-colors;
1576
- background-color: transparent;
1577
- }
1578
- .scroll-area-scrollbar[data-orientation='vertical'] {
1579
- width: 0.5rem;
1580
- }
1581
- .scroll-area-scrollbar[data-orientation='horizontal'] {
1582
- height: 0.5rem;
1583
- }
1584
- .scroll-area-thumb {
1585
- @apply relative flex-1 rounded-full;
1586
- background-color: var(--color-border-strong);
1587
- }
1588
- .scroll-area-thumb:hover {
1589
- background-color: var(--color-fg-subtle);
1590
- }
1591
-
1592
1361
  /* ============================================================ */
1593
1362
  /* ColorPicker */
1594
1363
  /* ============================================================ */
1595
- .color-picker {
1596
- @apply inline-flex items-center gap-2;
1597
- }
1364
+ .color-picker { @apply inline-flex items-center gap-2; }
1598
1365
  .color-picker-swatch {
1599
1366
  @apply inline-block size-9 cursor-pointer overflow-hidden;
1600
1367
  border: 1px solid var(--color-border-strong);
1601
1368
  border-radius: var(--ui-radius);
1602
1369
  }
1603
- .color-picker-text {
1604
- @apply font-mono;
1605
- width: 8.5rem;
1606
- }
1370
+ .color-picker-text { @apply font-mono; width: 8.5rem; }
1607
1371
 
1608
1372
  /* ============================================================ */
1609
1373
  /* Uploader */
1610
1374
  /* ============================================================ */
1611
1375
  .uploader-root {
1612
1376
  @apply flex w-full flex-col items-center justify-center gap-2 p-6 text-center cursor-pointer;
1613
- @apply transition-colors focus-visible:outline-none focus-visible:ring-2;
1614
1377
  background-color: var(--color-bg-subtle);
1615
1378
  color: var(--color-fg);
1616
1379
  border: 2px dashed var(--color-border-strong);
1617
1380
  border-radius: var(--ui-radius);
1618
- --tw-ring-color: var(--color-ring);
1619
- }
1620
- .uploader-root:hover {
1621
- background-color: var(--color-bg-muted);
1381
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease),
1382
+ border-color var(--ui-motion-fast) var(--ui-motion-ease),
1383
+ box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
1622
1384
  }
1385
+ .uploader-root:hover { background-color: var(--color-bg-muted); }
1386
+ .uploader-root:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1623
1387
  .uploader-dragging {
1624
1388
  background-color: color-mix(in oklab, var(--color-primary-500) 6%, var(--color-bg-muted));
1625
1389
  border-color: var(--color-primary-500);
1626
1390
  }
1627
- .uploader-text {
1628
- @apply flex flex-col items-center gap-0.5;
1629
- }
1630
- .uploader-primary {
1631
- @apply text-sm font-medium;
1632
- }
1633
- .uploader-helper {
1634
- @apply text-xs;
1635
- color: var(--color-fg-muted);
1636
- }
1637
- .uploader-list {
1638
- @apply mt-2 flex flex-col gap-1 list-none p-0;
1639
- }
1391
+ .uploader-text { @apply flex flex-col items-center gap-0.5; }
1392
+ .uploader-primary { @apply text-sm font-medium; }
1393
+ .uploader-helper { @apply text-xs; color: var(--color-fg-muted); }
1394
+ .uploader-list { @apply mt-2 flex flex-col gap-1 list-none p-0; }
1640
1395
  .uploader-item {
1641
1396
  @apply flex items-center gap-2 px-2 py-1.5;
1642
1397
  background-color: var(--color-bg-muted);
@@ -1644,107 +1399,75 @@
1644
1399
  }
1645
1400
 
1646
1401
  /* ============================================================ */
1647
- /* Date pickers (Popover-based) + custom Calendar */
1402
+ /* Date / Time pickers + custom Calendar */
1648
1403
  /* ============================================================ */
1649
1404
  .date-picker-trigger {
1650
1405
  @apply inline-flex items-center gap-2 cursor-pointer text-left w-full;
1651
1406
  min-width: 12rem;
1652
1407
  }
1653
- .date-picker-trigger[disabled] {
1654
- @apply opacity-60 cursor-not-allowed;
1655
- }
1408
+ .date-picker-trigger[disabled] { @apply opacity-60 cursor-not-allowed; }
1656
1409
 
1657
- .calendar {
1658
- @apply select-none;
1659
- }
1410
+ .calendar { @apply select-none; }
1660
1411
  .calendar-header {
1661
1412
  @apply flex items-center justify-between gap-1 mb-2;
1662
1413
  }
1663
1414
  .calendar-heading-btn {
1664
- @apply flex-1 text-sm font-semibold rounded py-1 px-2 transition-colors;
1415
+ @apply flex-1 text-sm font-semibold py-1 px-2 cursor-pointer;
1665
1416
  color: var(--color-fg);
1666
1417
  background: transparent;
1667
1418
  border: none;
1668
- cursor: pointer;
1669
- }
1670
- .calendar-heading-btn:hover {
1671
- background-color: var(--color-bg-muted);
1672
- }
1673
- .calendar-nav {
1674
- @apply inline-flex size-7 items-center justify-center rounded transition-colors;
1675
- color: var(--color-fg-muted);
1676
- background: transparent;
1677
- border: none;
1678
- cursor: pointer;
1679
- }
1680
- .calendar-nav:hover {
1681
- background-color: var(--color-bg-muted);
1682
- color: var(--color-fg);
1419
+ border-radius: var(--ui-radius-sm);
1420
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1683
1421
  }
1422
+ .calendar-heading-btn:hover { background-color: var(--color-bg-muted); }
1423
+ .calendar-heading-btn:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1424
+ /* `.calendar-nav` is in the `.btn-icon` selector list above. */
1684
1425
  .calendar-row {
1685
1426
  @apply grid gap-0.5;
1686
1427
  grid-template-columns: repeat(7, minmax(0, 1fr));
1687
1428
  }
1688
- .calendar-weekdays {
1689
- @apply mb-1;
1690
- }
1429
+ .calendar-weekdays { @apply mb-1; }
1691
1430
  .calendar-weekday {
1692
1431
  @apply text-center text-[11px] font-medium uppercase py-1;
1693
1432
  color: var(--color-fg-subtle);
1694
1433
  }
1695
1434
  .calendar-day {
1696
- @apply h-9 w-full inline-flex items-center justify-center rounded-md text-sm transition-colors;
1435
+ @apply h-9 w-full inline-flex items-center justify-center text-sm cursor-pointer;
1697
1436
  color: var(--color-fg);
1698
1437
  background: transparent;
1699
1438
  border: none;
1700
- cursor: pointer;
1701
- }
1702
- .calendar-day:hover:not(:disabled) {
1703
- background-color: var(--color-bg-muted);
1704
- }
1705
- .calendar-day:focus-visible {
1706
- outline: 2px solid var(--color-ring);
1707
- outline-offset: -2px;
1708
- }
1709
- .calendar-day-outside {
1710
- color: var(--color-fg-subtle);
1439
+ border-radius: var(--ui-radius-sm);
1440
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1711
1441
  }
1442
+ .calendar-day:hover:not(:disabled) { background-color: var(--color-bg-muted); }
1443
+ .calendar-day:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1444
+ .calendar-day-outside { color: var(--color-fg-subtle); }
1712
1445
  .calendar-day-today {
1713
1446
  font-weight: 600;
1714
1447
  box-shadow: inset 0 0 0 1px var(--color-border-strong);
1715
1448
  }
1716
- .calendar-day-selected {
1449
+ .calendar-day-selected,
1450
+ .calendar-day-selected:hover:not(:disabled) {
1717
1451
  background-color: var(--color-primary-600);
1718
1452
  color: white;
1719
1453
  font-weight: 600;
1720
1454
  }
1721
- .calendar-day-selected:hover:not(:disabled) {
1722
- background-color: var(--color-primary-700);
1723
- }
1455
+ .calendar-day-selected:hover:not(:disabled) { background-color: var(--color-primary-700); }
1724
1456
  .calendar-day-in-range {
1725
1457
  background-color: color-mix(in oklab, var(--color-primary-500) 12%, transparent);
1726
1458
  border-radius: 0;
1727
1459
  }
1728
- .calendar-day:disabled {
1729
- @apply opacity-30 cursor-not-allowed;
1730
- }
1460
+ .calendar-day:disabled { @apply opacity-30 cursor-not-allowed; }
1731
1461
 
1732
- /* TimePicker scrollable hour/minute columns */
1733
- .time-picker-popover {
1734
- min-width: 14rem;
1735
- }
1736
- .time-picker-grid {
1737
- @apply flex items-stretch gap-1;
1738
- }
1462
+ .time-picker-popover { min-width: 14rem; }
1463
+ .time-picker-grid { @apply flex items-stretch gap-1; }
1739
1464
  .time-picker-col {
1740
1465
  @apply flex-1 flex flex-col gap-0.5 overflow-y-auto py-1;
1741
1466
  max-height: 14rem;
1742
1467
  scroll-snap-type: y mandatory;
1743
1468
  scrollbar-width: thin;
1744
1469
  }
1745
- .time-picker-col::-webkit-scrollbar {
1746
- width: 4px;
1747
- }
1470
+ .time-picker-col::-webkit-scrollbar { width: 4px; }
1748
1471
  .time-picker-col::-webkit-scrollbar-thumb {
1749
1472
  background-color: var(--color-border-strong);
1750
1473
  border-radius: 2px;
@@ -1754,25 +1477,23 @@
1754
1477
  color: var(--color-fg-muted);
1755
1478
  }
1756
1479
  .time-picker-cell {
1757
- @apply h-8 inline-flex items-center justify-center rounded text-sm font-mono tabular-nums transition-colors;
1480
+ @apply h-8 inline-flex items-center justify-center text-sm font-mono tabular-nums cursor-pointer;
1758
1481
  color: var(--color-fg);
1759
1482
  background: transparent;
1760
1483
  border: none;
1761
- cursor: pointer;
1484
+ border-radius: var(--ui-radius-sm);
1762
1485
  scroll-snap-align: center;
1763
1486
  flex-shrink: 0;
1487
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1764
1488
  }
1765
- .time-picker-cell:hover {
1766
- background-color: var(--color-bg-muted);
1767
- }
1768
- .time-picker-cell-selected {
1489
+ .time-picker-cell:hover { background-color: var(--color-bg-muted); }
1490
+ .time-picker-cell-selected,
1491
+ .time-picker-cell-selected:hover {
1769
1492
  background-color: var(--color-primary-600);
1770
1493
  color: white;
1771
1494
  font-weight: 600;
1772
1495
  }
1773
- .time-picker-cell-selected:hover {
1774
- background-color: var(--color-primary-700);
1775
- }
1496
+ .time-picker-cell-selected:hover { background-color: var(--color-primary-700); }
1776
1497
 
1777
1498
  /* ============================================================ */
1778
1499
  /* DataTable */
@@ -1782,42 +1503,25 @@
1782
1503
  border: 1px solid var(--color-border);
1783
1504
  border-radius: var(--ui-radius);
1784
1505
  }
1785
- .data-table {
1786
- @apply w-full text-sm;
1787
- border-collapse: collapse;
1788
- }
1506
+ .data-table { @apply w-full text-sm; border-collapse: collapse; }
1789
1507
  .data-table th,
1790
- .data-table td {
1791
- @apply px-3 py-2 text-left align-middle;
1792
- }
1508
+ .data-table td { @apply px-3 py-2 text-left align-middle; }
1793
1509
  .data-table thead th {
1794
1510
  @apply font-semibold;
1795
1511
  color: var(--color-fg);
1796
1512
  background-color: var(--color-bg-muted);
1797
1513
  border-bottom: 1px solid var(--color-border);
1798
1514
  }
1799
- .data-table-sticky thead th {
1800
- @apply sticky top-0 z-10;
1801
- }
1802
- .data-table tbody tr {
1803
- border-bottom: 1px solid var(--color-border);
1804
- }
1805
- .data-table tbody tr:last-child {
1806
- border-bottom: none;
1807
- }
1808
- .data-table tbody tr:hover {
1809
- background-color: var(--color-bg-muted);
1810
- }
1515
+ .data-table-sticky thead th { @apply sticky top-0 z-10; }
1516
+ .data-table tbody tr { border-bottom: 1px solid var(--color-border); }
1517
+ .data-table tbody tr:last-child { border-bottom: none; }
1518
+ .data-table tbody tr:hover { background-color: var(--color-bg-muted); }
1811
1519
  .data-table-striped tbody tr:nth-child(even) {
1812
1520
  background-color: var(--color-bg-subtle);
1813
1521
  }
1814
1522
  .data-table-dense th,
1815
- .data-table-dense td {
1816
- @apply py-1;
1817
- }
1818
- .data-table-row-clickable {
1819
- @apply cursor-pointer;
1820
- }
1523
+ .data-table-dense td { @apply py-1; }
1524
+ .data-table-row-clickable { @apply cursor-pointer; }
1821
1525
  .data-table-empty td {
1822
1526
  @apply text-center py-6;
1823
1527
  color: var(--color-fg-muted);
@@ -1829,49 +1533,53 @@
1829
1533
  font: inherit;
1830
1534
  cursor: pointer;
1831
1535
  }
1832
- .data-table-sort:hover {
1833
- color: var(--color-primary-600);
1834
- }
1536
+ .data-table-sort:hover { color: var(--color-primary-600); }
1835
1537
 
1836
1538
  /* ============================================================ */
1837
- /* Tree (recursive) */
1539
+ /* Tree */
1838
1540
  /* ============================================================ */
1839
- .tree {
1840
- @apply text-sm;
1841
- color: var(--color-fg);
1842
- }
1843
- .tree ul {
1844
- @apply list-none p-0 m-0;
1845
- }
1541
+ .tree { @apply text-sm; color: var(--color-fg); }
1542
+ .tree ul { @apply list-none p-0 m-0; }
1846
1543
  .tree-row {
1847
- @apply flex w-full items-center gap-2 py-1 pr-2 text-left rounded-sm;
1848
- @apply transition-colors focus-visible:outline-none focus-visible:ring-2;
1544
+ @apply flex w-full items-center gap-2 py-1 pr-2 text-left cursor-pointer;
1849
1545
  color: var(--color-fg);
1850
1546
  background-color: transparent;
1851
- --tw-ring-color: var(--color-ring);
1852
- }
1853
- .tree-row:hover {
1854
- background-color: var(--color-bg-muted);
1547
+ border-radius: var(--ui-radius-sm);
1548
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1855
1549
  }
1856
- .tree-row-selected {
1550
+ .tree-row:hover { background-color: var(--color-bg-muted); }
1551
+ .tree-row:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1552
+ .tree-row-selected,
1553
+ .tree-row-selected:hover {
1857
1554
  background-color: color-mix(in oklab, var(--color-primary-500) 12%, transparent);
1858
1555
  color: var(--color-fg);
1859
1556
  }
1860
- .tree-row-selected:hover {
1861
- background-color: color-mix(in oklab, var(--color-primary-500) 18%, transparent);
1862
- }
1863
1557
  .tree-chevron {
1864
- @apply inline-flex size-4 shrink-0 items-center justify-center transition-transform duration-150;
1558
+ @apply inline-flex size-4 shrink-0 items-center justify-center;
1865
1559
  color: var(--color-fg-muted);
1560
+ transition: transform var(--ui-motion-fast) var(--ui-motion-ease);
1866
1561
  }
1867
- .tree-chevron-open {
1868
- transform: rotate(90deg);
1562
+ .tree-chevron-open { transform: rotate(90deg); }
1563
+ .tree-chevron-spacer { @apply inline-block size-4 shrink-0; }
1564
+ .tree-label { @apply truncate; }
1565
+
1566
+ /* ============================================================ */
1567
+ /* ScrollArea */
1568
+ /* Atom-defined so the overflow rules ship in atoms.css; cannot */
1569
+ /* rely on Tailwind utility generation inside library `cn()` */
1570
+ /* calls because Tailwind v4 doesn't scan workspace-dep sources */
1571
+ /* by default. */
1572
+ /* ============================================================ */
1573
+ .norns-scroll-area {
1574
+ overflow-y: auto;
1575
+ overflow-x: hidden;
1869
1576
  }
1870
- .tree-chevron-spacer {
1871
- @apply inline-block size-4 shrink-0;
1577
+ .norns-scroll-area-horizontal {
1578
+ overflow-x: auto;
1579
+ overflow-y: hidden;
1872
1580
  }
1873
- .tree-label {
1874
- @apply truncate;
1581
+ .norns-scroll-area-both {
1582
+ overflow: auto;
1875
1583
  }
1876
1584
 
1877
1585
  /* ============================================================ */
@@ -1883,37 +1591,32 @@
1883
1591
  background-color: var(--color-bg-muted);
1884
1592
  }
1885
1593
  .carousel-track {
1886
- @apply flex h-full transition-transform duration-500 ease-out;
1887
- }
1888
- .carousel-slide {
1889
- @apply h-full w-full shrink-0;
1594
+ @apply flex h-full;
1595
+ transition: transform 500ms var(--ui-motion-ease);
1890
1596
  }
1597
+ .carousel-slide { @apply h-full w-full shrink-0; }
1891
1598
  .carousel-arrow {
1892
- @apply absolute top-1/2 -translate-y-1/2 inline-flex size-9 items-center justify-center;
1893
- @apply transition-colors focus-visible:outline-none focus-visible:ring-2;
1599
+ @apply absolute top-1/2 -translate-y-1/2 inline-flex items-center justify-center cursor-pointer;
1600
+ width: var(--ui-icon-btn-lg);
1601
+ height: var(--ui-icon-btn-lg);
1894
1602
  background-color: color-mix(in oklab, var(--color-bg) 70%, transparent);
1895
1603
  color: var(--color-fg);
1896
1604
  border: 1px solid var(--color-border);
1897
1605
  border-radius: var(--ui-radius-full);
1898
- --tw-ring-color: var(--color-ring);
1899
- }
1900
- .carousel-arrow:hover {
1901
- background-color: var(--color-bg);
1902
- }
1903
- .carousel-arrow-prev {
1904
- @apply left-3;
1905
- }
1906
- .carousel-arrow-next {
1907
- @apply right-3;
1606
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1908
1607
  }
1608
+ .carousel-arrow:hover { background-color: var(--color-bg); }
1609
+ .carousel-arrow:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1610
+ .carousel-arrow-prev { @apply left-3; }
1611
+ .carousel-arrow-next { @apply right-3; }
1909
1612
  .carousel-dots {
1910
1613
  @apply absolute bottom-3 left-1/2 -translate-x-1/2 inline-flex items-center gap-1.5;
1911
1614
  }
1912
1615
  .carousel-dot {
1913
- @apply size-2 rounded-full transition-colors;
1616
+ @apply size-2 rounded-full cursor-pointer;
1914
1617
  background-color: color-mix(in oklab, var(--color-bg-inverse) 50%, transparent);
1618
+ border: none;
1619
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1915
1620
  }
1916
- .carousel-dot-active {
1917
- background-color: var(--color-bg-inverse);
1918
- }
1621
+ .carousel-dot-active { background-color: var(--color-bg-inverse); }
1919
1622
  }