@human-synthesis/norns-ui 0.0.3 → 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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -225
  3. package/package.json +13 -3
  4. package/src/auto-import.js +81 -19
  5. package/src/components/Accordion.n +39 -0
  6. package/src/components/Audio.n +24 -0
  7. package/src/components/Autocomplete.n +134 -0
  8. package/src/components/Avatar.n +27 -0
  9. package/src/components/AvatarGroup.n +25 -0
  10. package/src/components/Badge.n +20 -0
  11. package/src/components/Banner.n +25 -0
  12. package/src/components/Breadcrumbs.n +22 -0
  13. package/src/components/Btn.n +29 -8
  14. package/src/components/ButtonGroup.n +14 -0
  15. package/src/components/Calendar.n +153 -0
  16. package/src/components/Card.n +34 -0
  17. package/src/components/Carousel.n +66 -0
  18. package/src/components/Chip.n +27 -0
  19. package/src/components/Collapsible.n +29 -0
  20. package/src/components/ColorPicker.n +62 -0
  21. package/src/components/ContextMenu.n +67 -0
  22. package/src/components/CopyButton.n +50 -0
  23. package/src/components/DataTable.n +61 -0
  24. package/src/components/DatePicker.n +56 -0
  25. package/src/components/DateRangePicker.n +47 -0
  26. package/src/components/Dialog.n +35 -22
  27. package/src/components/Dropdown.n +54 -23
  28. package/src/components/GradientText.n +21 -0
  29. package/src/components/Header.n +23 -0
  30. package/src/components/HeroBanner.n +37 -0
  31. package/src/components/HierarchicalMenu.n +38 -0
  32. package/src/components/Icon.n +16 -0
  33. package/src/components/Image.n +31 -0
  34. package/src/components/MegaMenu.n +36 -0
  35. package/src/components/MultiSelect.n +158 -0
  36. package/src/components/NumberInput.n +49 -0
  37. package/src/components/OtpField.n +67 -0
  38. package/src/components/Pagination.n +73 -0
  39. package/src/components/Popover.n +30 -15
  40. package/src/components/Progress.n +26 -0
  41. package/src/components/ProgressCircular.n +52 -0
  42. package/src/components/RippleButton.n +56 -0
  43. package/src/components/ScrollArea.n +20 -0
  44. package/src/components/Separator.n +12 -0
  45. package/src/components/Sheet.n +36 -22
  46. package/src/components/ShinyButton.n +36 -0
  47. package/src/components/Skeleton.n +27 -0
  48. package/src/components/Stepper.n +35 -0
  49. package/src/components/Surface.n +10 -0
  50. package/src/components/Tabs.n +39 -13
  51. package/src/components/TagsInput.n +66 -0
  52. package/src/components/ThemeToggler.n +43 -0
  53. package/src/components/TimePicker.n +153 -0
  54. package/src/components/Timeline.n +27 -0
  55. package/src/components/ToggleButton.n +35 -0
  56. package/src/components/ToggleButtonGroup.n +54 -0
  57. package/src/components/Toolbar.n +15 -0
  58. package/src/components/Tooltip.n +52 -19
  59. package/src/components/Tree.n +57 -0
  60. package/src/components/Uploader.n +88 -0
  61. package/src/components/Video.n +26 -0
  62. package/src/index.js +64 -1
  63. package/src/lib/behaviors/clickOutside.svelte.js +54 -0
  64. package/src/lib/behaviors/escape.svelte.js +36 -0
  65. package/src/lib/behaviors/floating.svelte.js +137 -0
  66. package/src/lib/behaviors/focusTrap.svelte.js +80 -0
  67. package/src/lib/behaviors/index.js +14 -0
  68. package/src/lib/behaviors/portal.svelte.js +32 -0
  69. package/src/lib/behaviors/rovingTabindex.svelte.js +76 -0
  70. package/src/lib/behaviors/scrollLock.svelte.js +56 -0
  71. package/src/motion/AnimatedNumber.n +55 -0
  72. package/src/motion/GradientBackground.n +25 -0
  73. package/src/motion/LiquidButton.n +48 -0
  74. package/src/motion/Reveal.n +37 -0
  75. package/src/motion/Sparkles.n +53 -0
  76. package/src/motion/index.js +18 -0
  77. package/src/styles/atoms.css +1481 -223
  78. package/src/styles/tokens.css +204 -40
  79. package/src/types/Accordion.d.ts +17 -0
  80. package/src/types/AnimatedNumber.d.ts +15 -0
  81. package/src/types/Audio.d.ts +18 -0
  82. package/src/types/Autocomplete.d.ts +17 -0
  83. package/src/types/Avatar.d.ts +14 -0
  84. package/src/types/AvatarGroup.d.ts +15 -0
  85. package/src/types/Badge.d.ts +14 -0
  86. package/src/types/Banner.d.ts +15 -0
  87. package/src/types/Breadcrumbs.d.ts +15 -0
  88. package/src/types/Btn.d.ts +10 -0
  89. package/src/types/ButtonGroup.d.ts +10 -0
  90. package/src/types/Calendar.d.ts +20 -0
  91. package/src/types/Card.d.ts +19 -0
  92. package/src/types/Carousel.d.ts +23 -0
  93. package/src/types/Chip.d.ts +12 -0
  94. package/src/types/Collapsible.d.ts +13 -0
  95. package/src/types/ColorPicker.d.ts +15 -0
  96. package/src/types/ContextMenu.d.ts +24 -0
  97. package/src/types/CopyButton.d.ts +16 -0
  98. package/src/types/DataTable.d.ts +27 -0
  99. package/src/types/DatePicker.d.ts +23 -0
  100. package/src/types/DateRangePicker.d.ts +17 -0
  101. package/src/types/Dialog.d.ts +4 -0
  102. package/src/types/Dropdown.d.ts +2 -1
  103. package/src/types/GradientBackground.d.ts +14 -0
  104. package/src/types/GradientText.d.ts +19 -0
  105. package/src/types/Header.d.ts +12 -0
  106. package/src/types/HeroBanner.d.ts +17 -0
  107. package/src/types/HierarchicalMenu.d.ts +20 -0
  108. package/src/types/Icon.d.ts +21 -0
  109. package/src/types/Image.d.ts +15 -0
  110. package/src/types/LiquidButton.d.ts +15 -0
  111. package/src/types/MegaMenu.d.ts +29 -0
  112. package/src/types/MultiSelect.d.ts +16 -0
  113. package/src/types/NumberInput.d.ts +17 -0
  114. package/src/types/OtpField.d.ts +13 -0
  115. package/src/types/Pagination.d.ts +13 -0
  116. package/src/types/Progress.d.ts +14 -0
  117. package/src/types/ProgressCircular.d.ts +14 -0
  118. package/src/types/Reveal.d.ts +20 -0
  119. package/src/types/RippleButton.d.ts +15 -0
  120. package/src/types/ScrollArea.d.ts +11 -0
  121. package/src/types/Separator.d.ts +9 -0
  122. package/src/types/Sheet.d.ts +3 -0
  123. package/src/types/ShinyButton.d.ts +15 -0
  124. package/src/types/Skeleton.d.ts +13 -0
  125. package/src/types/Sparkles.d.ts +13 -0
  126. package/src/types/Stepper.d.ts +19 -0
  127. package/src/types/Surface.d.ts +10 -0
  128. package/src/types/Tabs.d.ts +2 -0
  129. package/src/types/TagsInput.d.ts +17 -0
  130. package/src/types/ThemeToggler.d.ts +17 -0
  131. package/src/types/TimePicker.d.ts +20 -0
  132. package/src/types/Timeline.d.ts +17 -0
  133. package/src/types/ToggleButton.d.ts +16 -0
  134. package/src/types/ToggleButtonGroup.d.ts +21 -0
  135. package/src/types/Toolbar.d.ts +12 -0
  136. package/src/types/Tooltip.d.ts +3 -0
  137. package/src/types/Tree.d.ts +20 -0
  138. package/src/types/Uploader.d.ts +15 -0
  139. package/src/types/Video.d.ts +20 -0
@@ -1,33 +1,198 @@
1
1
  /*
2
2
  * Norns UI — component atom classes.
3
3
  *
4
- * Pure Tailwind utility-composition via `@layer components`. No JS, no Svelte
5
- * just classes consumers can use directly via Pug shorthand (`button.btn.btn-primary`)
6
- * or via the wrapper components (`<Btn variant="primary">`).
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
 
23
+ @layer base {
24
+ html {
25
+ font-family: var(--font-sans);
26
+ color: var(--color-fg);
27
+ background-color: var(--color-bg);
28
+ font-feature-settings: 'ss01';
29
+ }
30
+ body {
31
+ color: var(--color-fg);
32
+ background-color: var(--color-bg);
33
+ }
34
+
35
+ @media (prefers-reduced-motion: reduce) {
36
+ *,
37
+ *::before,
38
+ *::after {
39
+ animation-duration: 0.01ms !important;
40
+ animation-iteration-count: 1 !important;
41
+ transition-duration: 0.01ms !important;
42
+ scroll-behavior: auto !important;
43
+ }
44
+ }
45
+
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; }
53
+
54
+ code {
55
+ font-family: var(--font-mono);
56
+ @apply text-[0.9em] px-1 py-0.5;
57
+ background-color: var(--color-bg-muted);
58
+ color: var(--color-fg);
59
+ border-radius: var(--ui-radius-sm);
60
+ }
61
+ pre {
62
+ font-family: var(--font-mono);
63
+ @apply text-sm p-3 overflow-x-auto;
64
+ background-color: var(--color-bg-muted);
65
+ color: var(--color-fg);
66
+ border-radius: var(--ui-radius);
67
+ }
68
+ pre code {
69
+ @apply p-0 bg-transparent;
70
+ }
71
+
72
+ kbd {
73
+ font-family: var(--font-mono);
74
+ @apply text-xs px-1.5 py-0.5 border;
75
+ background-color: var(--color-bg-muted);
76
+ border-color: var(--color-border);
77
+ color: var(--color-fg);
78
+ border-radius: var(--ui-radius-sm);
79
+ }
80
+
81
+ hr { border-color: var(--color-border); }
82
+ }
83
+
13
84
  @layer components {
14
- /* --- Button --- */
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
+
171
+ /* ============================================================ */
172
+ /* Button */
173
+ /* ============================================================ */
15
174
  .btn {
16
- @apply inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium leading-none whitespace-nowrap;
17
- @apply transition-[background-color,color,box-shadow,opacity] duration-150 ease-out;
18
- @apply select-none cursor-pointer;
175
+ @apply inline-flex items-center justify-center gap-2 text-sm font-medium leading-none whitespace-nowrap;
176
+ @apply select-none cursor-pointer relative overflow-hidden;
19
177
  @apply disabled:cursor-not-allowed disabled:opacity-60;
20
- @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500;
178
+ border-radius: var(--ui-radius);
21
179
  height: var(--ui-h-md);
22
180
  padding-inline: 0.875rem;
23
- }
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);
187
+ }
188
+ .btn:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
189
+ .btn:not(:disabled):active { transform: scale(0.97); }
24
190
 
25
191
  .btn-sm {
26
192
  @apply text-xs;
27
193
  height: var(--ui-h-sm);
28
194
  padding-inline: 0.625rem;
29
195
  }
30
-
31
196
  .btn-lg {
32
197
  @apply text-base;
33
198
  height: var(--ui-h-lg);
@@ -35,330 +200,1423 @@
35
200
  }
36
201
 
37
202
  .btn-primary {
38
- @apply bg-primary-600 text-white shadow-sm hover:bg-primary-700 active:bg-primary-800;
203
+ background-color: var(--color-primary-600);
204
+ color: var(--color-fg-on-primary);
39
205
  }
206
+ .btn-primary:hover { background-color: var(--color-primary-700); }
207
+ .btn-primary:active { background-color: var(--color-primary-800); }
40
208
 
41
209
  .btn-secondary {
42
- @apply bg-neutral-100 text-neutral-900 shadow-sm hover:bg-neutral-200 active:bg-neutral-300;
210
+ background-color: var(--color-bg-muted);
211
+ color: var(--color-fg);
212
+ border: 1px solid var(--color-border);
213
+ }
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'] {
217
+ background-color: var(--color-border);
218
+ border-color: var(--color-border-strong);
43
219
  }
44
220
 
45
221
  .btn-ghost {
46
- @apply bg-transparent text-neutral-700 hover:bg-neutral-100 active:bg-neutral-200;
222
+ background-color: transparent;
223
+ color: var(--color-fg-muted);
224
+ }
225
+ .btn-ghost:hover {
226
+ background-color: var(--color-bg-muted);
227
+ color: var(--color-fg);
228
+ }
229
+ .btn-ghost[data-pressed='true'] {
230
+ background-color: var(--color-bg-muted);
231
+ color: var(--color-fg);
47
232
  }
48
233
 
49
234
  .btn-danger {
50
- @apply bg-red-600 text-white shadow-sm hover:bg-red-700 active:bg-red-800 focus-visible:ring-red-500;
235
+ background-color: var(--color-danger-600);
236
+ color: white;
237
+ }
238
+ .btn-danger:hover { background-color: var(--color-danger-700); }
239
+ .btn-danger:active { background-color: var(--color-danger-800); }
240
+ .btn-danger:focus-visible {
241
+ box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--color-danger-500);
51
242
  }
52
243
 
53
244
  .btn-link {
54
- @apply bg-transparent text-primary-600 underline-offset-4 hover:underline px-0;
245
+ @apply bg-transparent underline-offset-4 hover:underline px-0;
246
+ color: var(--color-primary-600);
55
247
  height: auto;
56
248
  }
249
+ [data-theme='dark'] .btn-link {
250
+ color: var(--color-primary-400);
251
+ }
57
252
 
58
- /* --- Spinner (used inside Btn loading state) --- */
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 */
59
296
  .ui-spinner {
60
297
  @apply inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent;
61
298
  }
62
299
 
63
- /* --- Text inputs --- */
64
- .input {
65
- @apply block w-full rounded-md border border-neutral-300 bg-white text-sm text-neutral-900 shadow-sm;
66
- @apply placeholder:text-neutral-400;
67
- @apply transition-[border-color,box-shadow] duration-150 ease-out;
68
- @apply focus:outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-500/30;
69
- @apply disabled:cursor-not-allowed disabled:bg-neutral-50 disabled:opacity-70;
70
- height: var(--ui-h-md);
71
- padding-inline: 0.75rem;
72
- }
73
-
300
+ /* ============================================================ */
301
+ /* Form controls — base shared with .control-base above */
302
+ /* ============================================================ */
74
303
  .input-sm {
75
304
  @apply text-xs;
76
305
  height: var(--ui-h-sm);
77
306
  padding-inline: 0.625rem;
78
307
  }
79
-
80
308
  .input-lg {
81
309
  @apply text-base;
82
310
  height: var(--ui-h-lg);
83
311
  padding-inline: 0.875rem;
84
312
  }
85
313
 
86
- .input-err {
87
- @apply border-red-500 focus:border-red-500 focus:ring-red-500/30;
88
- }
89
-
90
- /* Textarea overrides .input's height */
91
314
  .textarea {
92
- @apply block w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm;
93
- @apply placeholder:text-neutral-400;
94
- @apply transition-[border-color,box-shadow] duration-150 ease-out;
95
- @apply focus:outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-500/30;
96
- @apply disabled:cursor-not-allowed disabled:bg-neutral-50 disabled:opacity-70;
315
+ @apply leading-snug;
316
+ padding-block: 0.5rem;
97
317
  min-height: calc(var(--ui-h-md) * 2);
318
+ height: auto;
98
319
  }
99
320
 
100
- .textarea-err {
101
- @apply border-red-500 focus:border-red-500 focus:ring-red-500/30;
102
- }
103
-
104
- .select {
105
- @apply block w-full rounded-md border border-neutral-300 bg-white text-sm text-neutral-900 shadow-sm;
106
- @apply transition-[border-color,box-shadow] duration-150 ease-out;
107
- @apply focus:outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-500/30;
108
- @apply disabled:cursor-not-allowed disabled:bg-neutral-50 disabled:opacity-70;
109
- height: var(--ui-h-md);
110
- padding-inline: 0.75rem;
111
- }
112
-
113
- .select-err {
114
- @apply border-red-500 focus:border-red-500 focus:ring-red-500/30;
115
- }
116
-
117
- /* --- Checkbox / Radio --- */
118
- .checkbox {
119
- @apply size-4 cursor-pointer rounded border-neutral-300 text-primary-600 shadow-sm;
120
- @apply focus:outline-none focus:ring-2 focus:ring-primary-500/30 focus:ring-offset-1;
121
- @apply disabled:cursor-not-allowed disabled:opacity-60;
122
- }
123
-
124
- .checkbox-err {
125
- @apply border-red-500 focus:ring-red-500/30;
126
- }
127
-
321
+ /* Checkbox / Radio — small square/round controls. Native `accent-color`
322
+ * does the colored fill once checked. */
323
+ .checkbox,
128
324
  .radio {
129
- @apply size-4 cursor-pointer border-neutral-300 text-primary-600 shadow-sm;
130
- @apply focus:outline-none focus:ring-2 focus:ring-primary-500/30 focus:ring-offset-1;
325
+ @apply size-4 cursor-pointer;
131
326
  @apply disabled:cursor-not-allowed disabled:opacity-60;
132
- }
133
-
134
- .radio-err {
135
- @apply border-red-500 focus:ring-red-500/30;
136
- }
327
+ border: 1px solid var(--color-border-strong);
328
+ accent-color: var(--color-primary-600);
329
+ transition: box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
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); }
137
337
 
138
- /* --- Switch (styled checkbox / toggle) ---
139
- * Implemented as a checkbox the user wraps in a label; we provide the
140
- * pill track via .switch and the moving knob via ::before.
141
- */
338
+ /* Switch */
142
339
  .switch {
143
- @apply relative inline-flex h-5 w-9 shrink-0 cursor-pointer appearance-none rounded-full bg-neutral-200 shadow-inner;
144
- @apply transition-colors duration-150 ease-out;
145
- @apply checked:bg-primary-600;
146
- @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/30 focus-visible:ring-offset-1;
340
+ @apply relative inline-flex h-5 w-9 shrink-0 cursor-pointer appearance-none rounded-full;
147
341
  @apply disabled:cursor-not-allowed disabled:opacity-60;
342
+ background-color: var(--color-border-strong);
343
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease),
344
+ box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
148
345
  }
346
+ .switch:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
347
+ .switch:checked { background-color: var(--color-primary-600); }
149
348
  .switch::before {
150
349
  content: '';
151
- @apply absolute left-0.5 top-0.5 size-4 rounded-full bg-white shadow;
152
- @apply transition-transform duration-150 ease-out;
153
- }
154
- .switch:checked::before {
155
- transform: translateX(1rem);
156
- }
157
-
158
- /* --- Field (label + control + help/error) --- */
159
- .field {
160
- @apply flex flex-col;
350
+ @apply absolute left-0.5 top-0.5 size-4 rounded-full;
351
+ background-color: white;
352
+ transition: transform var(--ui-motion-fast) var(--ui-motion-ease);
161
353
  }
354
+ .switch:checked::before { transform: translateX(1rem); }
162
355
 
356
+ /* ============================================================ */
357
+ /* Field */
358
+ /* ============================================================ */
359
+ .field { @apply flex flex-col; }
163
360
  .field-label {
164
- @apply mb-1 block text-sm font-medium text-neutral-700;
361
+ @apply mb-1 block text-sm font-medium;
362
+ color: var(--color-fg);
165
363
  }
166
-
167
364
  .field-required {
168
- @apply ml-0.5 text-red-600;
365
+ @apply ml-0.5;
366
+ color: var(--color-danger-600);
169
367
  }
170
-
171
368
  .field-help {
172
- @apply mt-1 text-xs text-neutral-500;
369
+ @apply mt-1 text-xs;
370
+ color: var(--color-fg-muted);
173
371
  }
174
-
175
372
  .field-error {
176
- @apply mt-1 text-xs text-red-600;
177
- }
178
-
179
- .field-row {
180
- @apply flex items-center gap-2;
373
+ @apply mt-1 text-xs;
374
+ color: var(--color-danger-600);
181
375
  }
376
+ .field-row { @apply flex items-center gap-2; }
182
377
 
183
- /* Field-group: border-grouped collection of fields */
184
378
  .field-group {
185
- @apply space-y-3 rounded-md border border-neutral-200 p-4;
379
+ @apply space-y-3 p-4;
380
+ border: 1px solid var(--color-border);
381
+ border-radius: var(--ui-radius);
186
382
  }
187
-
188
383
  .field-group-legend {
189
- @apply -mx-1 -mt-1 px-1 text-sm font-medium text-neutral-700;
384
+ @apply -mx-1 -mt-1 px-1 text-sm font-medium;
385
+ color: var(--color-fg);
190
386
  }
191
387
 
192
- /* --- Form (vertical stack of fields) --- */
193
- .form {
194
- @apply space-y-4;
195
- }
388
+ .form { @apply space-y-4; }
196
389
 
197
- /* --- Dialog (modal) ---
198
- * Bits UI toggles `data-state="open|closed"`. We use plain CSS transitions
199
- * targeting that attribute for fade + scale; no `tailwindcss-animate`
200
- * dependency, no v4 plugin.
201
- */
390
+ /* ============================================================ */
391
+ /* Dialog */
392
+ /* ============================================================ */
202
393
  .dialog-overlay {
203
- @apply fixed inset-0 z-50 bg-black/50 backdrop-blur-sm opacity-0 transition-opacity duration-150;
204
- }
205
- .dialog-overlay[data-state='open'] {
206
- @apply opacity-100;
394
+ @apply fixed inset-0 z-50 backdrop-blur-sm;
395
+ background-color: oklch(0% 0 0 / 0.5);
396
+ opacity: 0;
397
+ transition: opacity var(--ui-motion-fast) var(--ui-motion-ease);
207
398
  }
399
+ .dialog-overlay[data-state='open'] { opacity: 1; }
208
400
 
209
401
  .dialog-content {
210
- @apply fixed left-1/2 top-1/2 z-50 w-full max-w-lg -translate-x-1/2 -translate-y-1/2;
211
- @apply rounded-lg border border-neutral-200 bg-white p-6 shadow-xl;
212
- @apply opacity-0 scale-95 transition duration-150;
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 */
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);
213
415
  }
214
416
  .dialog-content[data-state='open'] {
215
- @apply opacity-100 scale-100;
417
+ opacity: 1;
418
+ scale: 1;
216
419
  }
217
-
218
420
  .dialog-title {
219
- @apply text-lg font-semibold leading-tight tracking-tight text-neutral-900;
421
+ @apply text-lg font-semibold leading-tight tracking-tight;
422
+ color: var(--color-fg);
220
423
  }
221
-
222
424
  .dialog-description {
223
- @apply mt-1 text-sm text-neutral-600;
425
+ @apply mt-1 text-sm;
426
+ color: var(--color-fg-muted);
427
+ }
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; }
431
+
432
+ /* ============================================================ */
433
+ /* Sheet (and Drawer, which is just Sheet with side="left") */
434
+ /* ============================================================ */
435
+ .sheet-content {
436
+ @apply fixed z-50 p-6;
437
+ background-color: var(--color-bg-elevated);
438
+ color: var(--color-fg);
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);
445
+ }
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
+
456
+ /* ============================================================ */
457
+ /* Popover / Dropdown / Tooltip — all surface-elevated */
458
+ /* ============================================================ */
459
+ .popover-content {
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
+ }
467
+ .popover-content[data-state='open'] {
468
+ opacity: 1;
469
+ transform: scale(1);
224
470
  }
225
471
 
226
- .dialog-body {
227
- @apply mt-4;
472
+ .dropdown-content {
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);
481
+ }
482
+ .dropdown-content[data-state='open'] {
483
+ opacity: 1;
484
+ transform: scale(1);
485
+ }
486
+ .dropdown-item {
487
+ @apply flex cursor-pointer select-none items-center gap-2 px-2 py-1.5 text-sm outline-none;
488
+ color: var(--color-fg);
489
+ border-radius: var(--ui-radius-sm);
490
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
491
+ }
492
+ .dropdown-item:hover:not([data-disabled]),
493
+ .dropdown-item:focus-visible,
494
+ .dropdown-item[data-active='true'] {
495
+ background-color: var(--color-bg-muted);
496
+ outline: none;
497
+ }
498
+ .dropdown-item[data-disabled] {
499
+ @apply cursor-not-allowed opacity-50;
500
+ }
501
+ .dropdown-separator {
502
+ @apply -mx-1 my-1 h-px;
503
+ background-color: var(--color-border);
228
504
  }
229
505
 
230
- .dialog-actions {
231
- @apply mt-6 flex items-center justify-end gap-2;
506
+ .tooltip-content {
507
+ @apply z-50 px-2 py-1 text-xs;
508
+ background-color: var(--color-bg-inverse);
509
+ color: var(--color-bg);
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);
520
+ }
521
+ /* `.tooltip-rich` is in the `.surface-elevated` selector list above. */
522
+ .tooltip-rich {
523
+ @apply p-3 text-sm max-w-xs;
232
524
  }
233
525
 
234
- .dialog-close {
235
- @apply absolute right-3 top-3 inline-flex size-7 items-center justify-center rounded-md text-neutral-500;
236
- @apply hover:bg-neutral-100 hover:text-neutral-900;
237
- @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500;
526
+ /* ============================================================ */
527
+ /* Tabs */
528
+ /* ============================================================ */
529
+ .tabs-root { @apply space-y-3; }
530
+ .tabs-list {
531
+ @apply inline-flex h-9 items-center justify-center p-1;
532
+ background-color: var(--color-bg-muted);
533
+ color: var(--color-fg-muted);
534
+ border-radius: var(--ui-radius);
238
535
  }
536
+ .tabs-trigger {
537
+ @apply inline-flex items-center justify-center whitespace-nowrap px-3 py-1 text-sm font-medium;
538
+ @apply disabled:cursor-not-allowed disabled:opacity-50;
539
+ color: var(--color-fg-muted);
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);
546
+ }
547
+ .tabs-trigger:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
548
+ .tabs-trigger[aria-selected='true'],
549
+ .tabs-trigger[data-state='active'] {
550
+ background-color: var(--color-bg-elevated);
551
+ color: var(--color-fg);
552
+ }
553
+ .tabs-content:focus-visible { outline: none; }
239
554
 
240
- /* --- Sheet (side panel) --- */
241
- .sheet-content {
242
- @apply fixed z-50 border border-neutral-200 bg-white shadow-xl transition-transform duration-200 ease-out;
555
+ /* ============================================================ */
556
+ /* Toast */
557
+ /* ============================================================ */
558
+ .toast-region {
559
+ @apply pointer-events-none fixed bottom-4 right-4 z-[100] flex w-full max-w-sm flex-col gap-2;
243
560
  }
244
- .sheet-right {
245
- @apply right-0 top-0 h-full w-full max-w-md translate-x-full p-6;
561
+ .toast {
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);
246
565
  }
247
- .sheet-right[data-state='open'] {
248
- @apply translate-x-0;
566
+ @keyframes norns-toast-in {
567
+ from { opacity: 0; transform: translateX(1rem); }
568
+ to { opacity: 1; transform: translateX(0); }
249
569
  }
250
- .sheet-left {
251
- @apply left-0 top-0 h-full w-full max-w-md -translate-x-full p-6;
570
+ .toast-info {
571
+ background-color: var(--color-info-bg);
572
+ color: var(--color-info-fg);
573
+ border-color: var(--color-info-border);
252
574
  }
253
- .sheet-left[data-state='open'] {
254
- @apply translate-x-0;
575
+ .toast-success {
576
+ background-color: var(--color-success-bg);
577
+ color: var(--color-success-fg);
578
+ border-color: var(--color-success-border);
255
579
  }
256
- .sheet-top {
257
- @apply left-0 top-0 w-full -translate-y-full p-6;
580
+ .toast-warning {
581
+ background-color: var(--color-warning-bg);
582
+ color: var(--color-warning-fg);
583
+ border-color: var(--color-warning-border);
258
584
  }
259
- .sheet-top[data-state='open'] {
260
- @apply translate-y-0;
585
+ .toast-error {
586
+ background-color: var(--color-danger-bg);
587
+ color: var(--color-danger-fg);
588
+ border-color: var(--color-danger-border);
261
589
  }
262
- .sheet-bottom {
263
- @apply bottom-0 left-0 w-full translate-y-full p-6;
590
+ .toast-message { @apply flex-1 text-sm leading-snug; }
591
+ .toast-close {
592
+ @apply -mr-1 -mt-0.5;
593
+ color: currentColor;
594
+ opacity: 0.6;
264
595
  }
265
- .sheet-bottom[data-state='open'] {
266
- @apply translate-y-0;
596
+ .toast-close:hover { opacity: 1; background-color: transparent; color: currentColor; }
597
+
598
+ /* ============================================================ */
599
+ /* Surface / Card */
600
+ /* ============================================================ */
601
+ .surface {
602
+ background-color: var(--color-bg-elevated);
603
+ color: var(--color-fg);
604
+ border: 1px solid var(--color-border);
605
+ border-radius: var(--ui-radius);
606
+ }
607
+ .surface-glass {
608
+ background-color: color-mix(in oklab, var(--color-bg-elevated) 65%, transparent);
609
+ backdrop-filter: blur(12px);
610
+ -webkit-backdrop-filter: blur(12px);
611
+ }
612
+ .card {
613
+ background-color: var(--color-bg-elevated);
614
+ color: var(--color-fg);
615
+ border: 1px solid var(--color-border);
616
+ border-radius: var(--ui-radius-lg);
617
+ }
618
+ .card-padded { @apply p-5; }
619
+ .card-header {
620
+ @apply px-5 pt-5 pb-3;
621
+ border-bottom: 1px solid var(--color-border);
622
+ border-top-left-radius: var(--ui-radius-lg);
623
+ border-top-right-radius: var(--ui-radius-lg);
624
+ }
625
+ .card-body { @apply p-5; }
626
+ .card-footer {
627
+ @apply px-5 py-3;
628
+ border-top: 1px solid var(--color-border);
629
+ background-color: var(--color-bg-subtle);
630
+ border-bottom-left-radius: var(--ui-radius-lg);
631
+ border-bottom-right-radius: var(--ui-radius-lg);
632
+ }
633
+ .card-interactive {
634
+ @apply cursor-pointer no-underline;
635
+ color: inherit;
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
+ }
640
+ .card-interactive:hover {
641
+ border-color: var(--color-border-strong);
642
+ transform: translateY(-2px);
643
+ box-shadow: var(--ui-elev-2);
644
+ }
645
+ .card-interactive:focus-visible {
646
+ outline: none;
647
+ box-shadow: var(--ui-focus-ring);
267
648
  }
268
649
 
269
- .sheet-body {
270
- @apply mt-4;
650
+ /* ============================================================ */
651
+ /* Banner */
652
+ /* ============================================================ */
653
+ .banner {
654
+ @apply flex items-start gap-3 p-3 text-sm;
655
+ border: 1px solid;
656
+ border-radius: var(--ui-radius);
657
+ animation: norns-banner-in var(--ui-motion-med) var(--ui-motion-ease) both;
658
+ }
659
+ @keyframes norns-banner-in {
660
+ from { opacity: 0; transform: translateY(-4px); }
661
+ to { opacity: 1; transform: translateY(0); }
662
+ }
663
+ .banner-info {
664
+ background-color: var(--color-info-bg);
665
+ color: var(--color-info-fg);
666
+ border-color: var(--color-info-border);
667
+ }
668
+ .banner-success {
669
+ background-color: var(--color-success-bg);
670
+ color: var(--color-success-fg);
671
+ border-color: var(--color-success-border);
672
+ }
673
+ .banner-warning {
674
+ background-color: var(--color-warning-bg);
675
+ color: var(--color-warning-fg);
676
+ border-color: var(--color-warning-border);
677
+ }
678
+ .banner-danger {
679
+ background-color: var(--color-danger-bg);
680
+ color: var(--color-danger-fg);
681
+ border-color: var(--color-danger-border);
682
+ }
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; }
686
+
687
+ /* ============================================================ */
688
+ /* Badge / Chip */
689
+ /* ============================================================ */
690
+ .badge {
691
+ @apply inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium leading-none;
692
+ border-radius: var(--ui-radius-full);
693
+ background-color: var(--color-bg-muted);
694
+ color: var(--color-fg);
695
+ }
696
+ .badge-primary {
697
+ background-color: var(--color-primary-100);
698
+ color: var(--color-primary-900);
699
+ }
700
+ [data-theme='dark'] .badge-primary {
701
+ background-color: var(--color-primary-950);
702
+ color: var(--color-primary-200);
703
+ }
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; }
709
+
710
+ .chip {
711
+ @apply inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium;
712
+ background-color: var(--color-bg-muted);
713
+ color: var(--color-fg);
714
+ border: 1px solid var(--color-border);
715
+ border-radius: var(--ui-radius-full);
716
+ }
717
+ .chip-remove {
718
+ @apply -mr-1 inline-flex size-4 items-center justify-center rounded-full;
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);
722
+ }
723
+ .chip-remove:hover {
724
+ opacity: 1;
725
+ background-color: var(--color-bg-muted);
271
726
  }
272
727
 
273
- /* --- Popover --- */
274
- .popover-content {
275
- @apply z-50 rounded-md border border-neutral-200 bg-white p-3 text-sm shadow-md;
276
- @apply opacity-0 scale-95 transition duration-150;
728
+ /* ============================================================ */
729
+ /* Avatar */
730
+ /* ============================================================ */
731
+ .avatar {
732
+ @apply relative inline-flex shrink-0 items-center justify-center overflow-hidden font-medium;
733
+ background-color: var(--color-bg-muted);
734
+ color: var(--color-fg);
735
+ border-radius: var(--ui-radius-full);
736
+ }
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; }
742
+
743
+ .avatar-group { @apply inline-flex items-center; }
744
+ .avatar-group-item,
745
+ .avatar-group-overflow {
746
+ @apply relative inline-flex;
747
+ margin-left: -0.5rem;
748
+ box-shadow: 0 0 0 2px var(--color-bg);
749
+ border-radius: var(--ui-radius-full);
750
+ transition: transform var(--ui-motion-fast) var(--ui-motion-ease);
751
+ }
752
+ .avatar-group-item:first-child,
753
+ .avatar-group:has(.avatar-group-item:first-child) > .avatar-group-item:first-child {
754
+ margin-left: 0;
755
+ }
756
+ .avatar-group-item:hover { transform: translateY(-2px); z-index: 1; }
757
+ .avatar-group-overflow {
758
+ @apply text-xs font-medium;
759
+ background-color: var(--color-bg-muted);
760
+ color: var(--color-fg-muted);
277
761
  }
278
- .popover-content[data-state='open'] {
279
- @apply opacity-100 scale-100;
762
+
763
+ /* ============================================================ */
764
+ /* Decorative buttons (CSS-only motion) */
765
+ /* ============================================================ */
766
+ .gradient-text {
767
+ background-clip: text;
768
+ -webkit-background-clip: text;
769
+ color: transparent;
770
+ background-size: 200% 200%;
771
+ background-position: 0% 50%;
772
+ }
773
+ .gradient-text-anim { animation: norns-gradient-shift 6s ease-in-out infinite; }
774
+ @keyframes norns-gradient-shift {
775
+ 0%, 100% { background-position: 0% 50%; }
776
+ 50% { background-position: 100% 50%; }
280
777
  }
281
778
 
282
- /* --- Dropdown menu --- */
283
- .dropdown-content {
284
- @apply z-50 min-w-[8rem] rounded-md border border-neutral-200 bg-white p-1 text-sm shadow-md;
285
- @apply opacity-0 scale-95 transition duration-150;
779
+ .copy-btn-check { animation: norns-copy-pop 250ms ease-out; }
780
+ @keyframes norns-copy-pop {
781
+ 0% { transform: scale(0.5); opacity: 0; }
782
+ 70% { transform: scale(1.15); }
783
+ 100% { transform: scale(1); opacity: 1; }
286
784
  }
287
- .dropdown-content[data-state='open'] {
288
- @apply opacity-100 scale-100;
785
+
786
+ .shiny-btn { position: relative; isolation: isolate; }
787
+ .shiny-btn-shine {
788
+ position: absolute;
789
+ inset: 0;
790
+ pointer-events: none;
791
+ background: linear-gradient(
792
+ 110deg,
793
+ transparent 30%,
794
+ color-mix(in oklab, white 35%, transparent) 50%,
795
+ transparent 70%
796
+ );
797
+ background-size: 250% 100%;
798
+ background-position: 200% 0;
799
+ animation: norns-shiny 2.6s ease-in-out infinite;
800
+ mix-blend-mode: overlay;
801
+ border-radius: inherit;
802
+ }
803
+ @keyframes norns-shiny {
804
+ 0% { background-position: 200% 0; }
805
+ 100% { background-position: -150% 0; }
289
806
  }
290
- .dropdown-item {
291
- @apply flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm text-neutral-800 outline-none;
292
- @apply data-[highlighted]:bg-neutral-100 data-[highlighted]:text-neutral-900;
293
- @apply data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50;
807
+
808
+ .ripple-btn { position: relative; overflow: hidden; }
809
+ .ripple-btn-ripple {
810
+ position: absolute;
811
+ pointer-events: none;
812
+ border-radius: var(--ui-radius-full);
813
+ background-color: color-mix(in oklab, currentColor 35%, transparent);
814
+ transform: scale(0);
815
+ opacity: 0.6;
816
+ animation: norns-ripple 600ms ease-out forwards;
817
+ }
818
+ @keyframes norns-ripple {
819
+ to { transform: scale(1); opacity: 0; }
294
820
  }
295
- .dropdown-separator {
296
- @apply -mx-1 my-1 h-px bg-neutral-200;
821
+
822
+ .liquid-btn { position: relative; isolation: isolate; overflow: hidden; }
823
+ .liquid-btn-blob {
824
+ position: absolute;
825
+ inset: -25%;
826
+ background: radial-gradient(
827
+ circle,
828
+ color-mix(in oklab, white 40%, transparent),
829
+ transparent 60%
830
+ );
831
+ transform: scale(0.6);
832
+ mix-blend-mode: overlay;
833
+ pointer-events: none;
834
+ filter: blur(12px);
835
+ z-index: 0;
836
+ }
837
+ .liquid-btn-content {
838
+ @apply inline-flex items-center gap-2 relative;
839
+ z-index: 1;
297
840
  }
298
841
 
299
- /* --- Tooltip --- */
300
- .tooltip-content {
301
- @apply z-50 overflow-hidden rounded-md bg-neutral-900 px-2 py-1 text-xs text-white shadow-md;
302
- @apply opacity-0 scale-95 transition duration-150;
842
+ .sparkles-wrap { @apply relative inline-block; }
843
+ .sparkles-overlay { @apply pointer-events-none absolute inset-0 overflow-hidden; }
844
+ .sparkle {
845
+ position: absolute;
846
+ display: block;
847
+ border-radius: var(--ui-radius-full);
848
+ background: var(--sparkle-color, var(--color-primary-400));
849
+ opacity: 0;
850
+ transform: scale(0);
851
+ animation: norns-sparkle 2.4s ease-in-out infinite;
852
+ animation-fill-mode: forwards;
853
+ filter: drop-shadow(0 0 4px var(--sparkle-color, var(--color-primary-400)));
854
+ }
855
+ .sparkles-paused .sparkle { animation-play-state: paused; }
856
+ @keyframes norns-sparkle {
857
+ 0%, 100% { opacity: 0; transform: scale(0) rotate(0deg); }
858
+ 20%, 80% { opacity: 1; }
859
+ 50% { transform: scale(1) rotate(180deg); }
303
860
  }
304
- .tooltip-content[data-state='delayed-open'],
305
- .tooltip-content[data-state='instant-open'] {
306
- @apply opacity-100 scale-100;
861
+
862
+ .gradient-bg {
863
+ @apply relative overflow-hidden;
864
+ background-image: linear-gradient(
865
+ var(--gradient-bg-angle, 130deg),
866
+ var(--gradient-bg-stops, var(--color-primary-500), var(--color-info-500))
867
+ );
868
+ background-size: 300% 300%;
869
+ animation: norns-gradient-loop var(--gradient-bg-duration, 18s) ease-in-out infinite;
870
+ }
871
+ @keyframes norns-gradient-loop {
872
+ 0%, 100% { background-position: 0% 50%; }
873
+ 25% { background-position: 100% 50%; }
874
+ 50% { background-position: 100% 100%; }
875
+ 75% { background-position: 0% 100%; }
307
876
  }
308
877
 
309
- /* --- Tabs --- */
310
- .tabs-root {
311
- @apply space-y-3;
878
+ /* ============================================================ */
879
+ /* Skeleton / Progress */
880
+ /* ============================================================ */
881
+ .skeleton {
882
+ @apply block;
883
+ background-color: var(--color-bg-muted);
884
+ border-radius: var(--ui-radius-sm);
885
+ animation: norns-skeleton-pulse 1.6s ease-in-out infinite;
886
+ }
887
+ .skeleton-circle { border-radius: var(--ui-radius-full); }
888
+ @keyframes norns-skeleton-pulse {
889
+ 0%, 100% { opacity: 1; }
890
+ 50% { opacity: 0.5; }
312
891
  }
313
- .tabs-list {
314
- @apply inline-flex h-9 items-center justify-center rounded-md bg-neutral-100 p-1 text-neutral-600;
892
+
893
+ .progress {
894
+ @apply relative h-2 w-full overflow-hidden;
895
+ background-color: var(--color-bg-muted);
896
+ border-radius: var(--ui-radius-full);
897
+ }
898
+ .progress-bar {
899
+ @apply h-full;
900
+ background-color: var(--color-primary-600);
901
+ border-radius: var(--ui-radius-full);
902
+ transition: width var(--ui-motion-slow) var(--ui-motion-ease);
903
+ }
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); }
907
+ .progress-indeterminate .progress-bar {
908
+ width: 35%;
909
+ animation: norns-progress-indeterminate 1.4s ease-in-out infinite;
910
+ }
911
+ @keyframes norns-progress-indeterminate {
912
+ 0% { transform: translateX(-100%); }
913
+ 100% { transform: translateX(385%); }
315
914
  }
316
- .tabs-trigger {
317
- @apply inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1 text-sm font-medium;
318
- @apply transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500;
319
- @apply data-[state=active]:bg-white data-[state=active]:text-neutral-900 data-[state=active]:shadow-sm;
320
- @apply disabled:cursor-not-allowed disabled:opacity-50;
915
+
916
+ /* ============================================================ */
917
+ /* Header / Hero / Stepper / Breadcrumbs / Pagination */
918
+ /* ============================================================ */
919
+ .norns-header {
920
+ @apply flex items-center justify-between gap-4 px-6 py-3;
921
+ background-color: var(--color-bg);
922
+ border-bottom: 1px solid var(--color-border);
923
+ }
924
+ .norns-header-sticky {
925
+ @apply sticky top-0 z-30;
926
+ background-color: color-mix(in oklab, var(--color-bg) 85%, transparent);
927
+ backdrop-filter: blur(8px);
928
+ }
929
+ .norns-header-brand {
930
+ @apply flex items-center gap-2 font-semibold;
931
+ color: var(--color-fg);
932
+ }
933
+ .norns-header-nav { @apply flex items-center gap-1; }
934
+ .norns-header-actions { @apply flex items-center gap-2; }
935
+
936
+ .hero {
937
+ @apply relative overflow-hidden p-8 md:p-12;
938
+ background-color: var(--color-bg-elevated);
939
+ border: 1px solid var(--color-border);
940
+ border-radius: var(--ui-radius-lg);
941
+ }
942
+ .hero-center { @apply text-center; }
943
+ .hero-title {
944
+ @apply text-3xl font-bold tracking-tight md:text-4xl;
945
+ color: var(--color-fg);
946
+ }
947
+ .hero-description {
948
+ @apply mt-3 text-base md:text-lg max-w-prose;
949
+ color: var(--color-fg-muted);
950
+ }
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; }
954
+ .hero-image {
955
+ @apply pointer-events-none absolute inset-y-0 right-0 w-1/2 object-cover opacity-20;
321
956
  }
322
- .tabs-content {
323
- @apply focus-visible:outline-none;
957
+
958
+ .stepper { @apply flex items-center gap-3; }
959
+ .stepper-vertical { @apply flex-col items-stretch; }
960
+ .stepper-step {
961
+ @apply flex items-center gap-2 text-sm;
962
+ color: var(--color-fg-muted);
963
+ }
964
+ .stepper-step-current { color: var(--color-fg); font-weight: 600; }
965
+ .stepper-step-complete { color: var(--color-fg); }
966
+ .stepper-marker {
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);
971
+ background-color: var(--color-bg-muted);
972
+ color: var(--color-fg-muted);
973
+ border: 1px solid var(--color-border);
974
+ }
975
+ .stepper-step-current .stepper-marker {
976
+ background-color: var(--color-primary-600);
977
+ color: white;
978
+ border-color: var(--color-primary-600);
979
+ }
980
+ .stepper-step-complete .stepper-marker {
981
+ background-color: var(--color-success-600);
982
+ color: white;
983
+ border-color: var(--color-success-600);
984
+ }
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; }
987
+
988
+ .breadcrumbs {
989
+ @apply flex items-center gap-1 text-sm;
990
+ color: var(--color-fg-muted);
991
+ }
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; }
996
+ .breadcrumb-separator {
997
+ @apply mx-1 select-none;
998
+ color: var(--color-fg-subtle);
324
999
  }
325
1000
 
326
- /* --- Toast --- */
327
- .toast-region {
328
- @apply pointer-events-none fixed bottom-4 right-4 z-[100] flex w-full max-w-sm flex-col gap-2;
1001
+ .pagination { @apply inline-flex items-center gap-1; }
1002
+ .pagination-item {
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);
1008
+ }
1009
+ .pagination-item-active,
1010
+ .pagination-item-active:hover {
1011
+ background-color: var(--color-primary-600);
1012
+ color: white;
1013
+ }
1014
+ .pagination-item:disabled {
1015
+ @apply cursor-not-allowed opacity-40;
1016
+ }
1017
+ .pagination-ellipsis {
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);
1021
+ color: var(--color-fg-subtle);
329
1022
  }
330
- .toast {
331
- @apply pointer-events-auto flex items-start gap-3 rounded-md border border-neutral-200 bg-white p-3 shadow-md;
332
- animation: norns-toast-in 200ms ease-out;
1023
+
1024
+ /* ============================================================ */
1025
+ /* Accordion / Collapsible — both built on <details> */
1026
+ /* ============================================================ */
1027
+ .accordion {
1028
+ border: 1px solid var(--color-border);
1029
+ border-radius: var(--ui-radius);
1030
+ }
1031
+ .accordion-item { border-bottom: 1px solid var(--color-border); }
1032
+ .accordion-item:last-child { border-bottom: none; }
1033
+ .accordion-trigger {
1034
+ @apply flex w-full items-center justify-between gap-2 px-4 py-3 text-left text-sm font-medium cursor-pointer;
1035
+ color: var(--color-fg);
1036
+ background: transparent;
1037
+ list-style: none;
1038
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1039
+ }
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 {
1044
+ transform: rotate(180deg);
1045
+ }
1046
+ .accordion-chevron {
1047
+ @apply size-4 shrink-0;
1048
+ color: var(--color-fg-muted);
1049
+ transition: transform var(--ui-motion-fast) var(--ui-motion-ease);
1050
+ }
1051
+ .accordion-content {
1052
+ @apply px-4 pb-3 text-sm;
1053
+ color: var(--color-fg-muted);
333
1054
  }
334
- @keyframes norns-toast-in {
335
- from {
336
- opacity: 0;
337
- transform: translateX(1rem);
338
- }
339
- to {
340
- opacity: 1;
341
- transform: translateX(0);
342
- }
1055
+
1056
+ .collapsible {
1057
+ border: 1px solid var(--color-border);
1058
+ border-radius: var(--ui-radius);
1059
+ background-color: var(--color-bg-elevated);
1060
+ }
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);
1067
+ }
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);
343
1083
  }
344
- .toast-info {
345
- @apply border-neutral-200;
1084
+
1085
+ /* ============================================================ */
1086
+ /* Separator / Toolbar */
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
+
1092
+ .toolbar {
1093
+ @apply flex items-center gap-1 p-1;
1094
+ background-color: var(--color-bg-muted);
1095
+ border: 1px solid var(--color-border);
1096
+ border-radius: var(--ui-radius);
346
1097
  }
347
- .toast-success {
348
- @apply border-green-200 bg-green-50 text-green-900;
1098
+ .toolbar-vertical { @apply flex-col items-stretch; }
1099
+
1100
+ /* ============================================================ */
1101
+ /* ButtonGroup — covers `.btn`, `.btn-secondary[data-pressed]` */
1102
+ /* ============================================================ */
1103
+ .btn-group { @apply inline-flex; }
1104
+ .btn-group > * { border-radius: 0; }
1105
+ .btn-group > :first-child {
1106
+ border-top-left-radius: var(--ui-radius);
1107
+ border-bottom-left-radius: var(--ui-radius);
1108
+ }
1109
+ .btn-group > :last-child {
1110
+ border-top-right-radius: var(--ui-radius);
1111
+ border-bottom-right-radius: var(--ui-radius);
1112
+ }
1113
+ .btn-group > * + * { margin-left: -1px; }
1114
+
1115
+ .btn-group-vertical { @apply flex-col; }
1116
+ .btn-group-vertical > * { width: 100%; border-radius: 0; }
1117
+ .btn-group-vertical > :first-child {
1118
+ border-top-left-radius: var(--ui-radius);
1119
+ border-top-right-radius: var(--ui-radius);
349
1120
  }
350
- .toast-warning {
351
- @apply border-amber-200 bg-amber-50 text-amber-900;
1121
+ .btn-group-vertical > :last-child {
1122
+ border-bottom-left-radius: var(--ui-radius);
1123
+ border-bottom-right-radius: var(--ui-radius);
352
1124
  }
353
- .toast-error {
354
- @apply border-red-200 bg-red-50 text-red-900;
1125
+ .btn-group-vertical > * + * { margin-left: 0; margin-top: -1px; }
1126
+
1127
+ /* ============================================================ */
1128
+ /* NumberInput */
1129
+ /* ============================================================ */
1130
+ .number-input {
1131
+ @apply inline-flex items-stretch;
1132
+ border-radius: var(--ui-radius);
1133
+ }
1134
+ .number-input-field {
1135
+ @apply text-center;
1136
+ border-radius: 0;
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;
1144
+ }
1145
+ .number-step {
1146
+ @apply inline-flex items-center justify-center px-2 cursor-pointer;
1147
+ background-color: var(--color-bg-muted);
1148
+ color: var(--color-fg);
1149
+ border: 1px solid var(--color-border-strong);
1150
+ height: var(--ui-h-md);
1151
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
355
1152
  }
356
- .toast-message {
357
- @apply flex-1 text-sm leading-snug;
1153
+ .number-step:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1154
+ .number-step:first-child {
1155
+ border-top-left-radius: var(--ui-radius);
1156
+ border-bottom-left-radius: var(--ui-radius);
1157
+ border-right: none;
358
1158
  }
359
- .toast-close {
360
- @apply -mr-1 -mt-0.5 inline-flex size-6 shrink-0 items-center justify-center rounded text-neutral-500;
361
- @apply hover:bg-neutral-100 hover:text-neutral-900;
362
- @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500;
1159
+ .number-step:last-child {
1160
+ border-top-right-radius: var(--ui-radius);
1161
+ border-bottom-right-radius: var(--ui-radius);
1162
+ border-left: none;
1163
+ }
1164
+ .number-step:hover:not(:disabled) { background-color: var(--color-bg-subtle); }
1165
+ .number-step:disabled { @apply cursor-not-allowed opacity-40; }
1166
+
1167
+ /* ============================================================ */
1168
+ /* OtpField */
1169
+ /* ============================================================ */
1170
+ .otp-field { @apply inline-flex gap-2; }
1171
+ .otp-cell {
1172
+ @apply text-center font-mono text-lg;
1173
+ width: 2.75rem;
1174
+ height: 3rem;
1175
+ background-color: var(--color-bg);
1176
+ color: var(--color-fg);
1177
+ border: 1px solid var(--color-border-strong);
1178
+ border-radius: var(--ui-radius);
1179
+ transition: border-color var(--ui-motion-fast) var(--ui-motion-ease),
1180
+ box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
1181
+ }
1182
+ .otp-cell:focus-visible {
1183
+ outline: none;
1184
+ border-color: var(--color-ring);
1185
+ box-shadow: var(--ui-focus-ring);
363
1186
  }
1187
+
1188
+ /* ============================================================ */
1189
+ /* TagsInput */
1190
+ /* ============================================================ */
1191
+ .tags-input {
1192
+ @apply flex w-full flex-wrap items-center gap-1.5 px-2 py-1.5;
1193
+ min-height: var(--ui-h-md);
1194
+ background-color: var(--color-bg);
1195
+ color: var(--color-fg);
1196
+ border: 1px solid var(--color-border-strong);
1197
+ border-radius: var(--ui-radius);
1198
+ transition: border-color var(--ui-motion-fast) var(--ui-motion-ease),
1199
+ box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
1200
+ }
1201
+ .tags-input:focus-within {
1202
+ outline: none;
1203
+ border-color: var(--color-ring);
1204
+ box-shadow: var(--ui-focus-ring);
1205
+ }
1206
+ .tags-input-err { border-color: var(--color-danger-500); }
1207
+ .tags-input-field {
1208
+ @apply min-w-[6rem] flex-1 text-sm outline-none;
1209
+ background: transparent;
1210
+ color: var(--color-fg);
1211
+ }
1212
+ .tags-input-field::placeholder { color: var(--color-fg-subtle); }
1213
+
1214
+ /* ============================================================ */
1215
+ /* Timeline */
1216
+ /* ============================================================ */
1217
+ .timeline { @apply list-none p-0 m-0 relative; }
1218
+ .timeline-item {
1219
+ @apply relative grid pl-10 pb-6;
1220
+ grid-template-columns: 1fr;
1221
+ }
1222
+ .timeline-item:last-child { @apply pb-0; }
1223
+ .timeline-dot {
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);
1227
+ background-color: var(--color-primary-600);
1228
+ top: 0;
1229
+ }
1230
+ .timeline-dot-success { background-color: var(--color-success-600); }
1231
+ .timeline-dot-warning { background-color: var(--color-warning-600); }
1232
+ .timeline-dot-danger { background-color: var(--color-danger-600); }
1233
+ .timeline-dot-info { background-color: var(--color-info-600); }
1234
+ .timeline-line {
1235
+ @apply absolute w-px;
1236
+ left: 0.875rem;
1237
+ top: 1.75rem;
1238
+ bottom: -0.5rem;
1239
+ background-color: var(--color-border);
1240
+ }
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); }
1244
+
1245
+ /* ============================================================ */
1246
+ /* Combobox / Autocomplete / MultiSelect */
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;
1257
+ }
1258
+ .combobox-empty {
1259
+ @apply px-2 py-1.5 text-sm;
1260
+ color: var(--color-fg-muted);
1261
+ }
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);
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);
1279
+ }
1280
+ .multi-select-toggle:hover {
1281
+ background-color: var(--color-bg-muted);
1282
+ color: var(--color-fg);
1283
+ }
1284
+ .multi-select-toggle:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1285
+
1286
+ /* ============================================================ */
1287
+ /* Navigation menu (Hierarchical / Mega) — CSS-only hover/focus */
1288
+ /* ============================================================ */
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; }
1292
+ .nav-menu-trigger {
1293
+ @apply inline-flex items-center gap-1 px-3 text-sm font-medium cursor-pointer select-none;
1294
+ height: var(--ui-h-md);
1295
+ color: var(--color-fg-muted);
1296
+ background-color: transparent;
1297
+ border-radius: var(--ui-radius-sm);
1298
+ text-decoration: none;
1299
+ border: none;
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);
1303
+ }
1304
+ .nav-menu-trigger:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1305
+ .nav-menu-trigger:hover {
1306
+ color: var(--color-fg);
1307
+ background-color: var(--color-bg-muted);
1308
+ }
1309
+ .nav-menu-chevron {
1310
+ @apply shrink-0;
1311
+ transition: transform var(--ui-motion-med) var(--ui-motion-ease);
1312
+ }
1313
+ .nav-menu-content {
1314
+ @apply absolute top-full left-0 mt-1 z-50 invisible;
1315
+ /* base surface comes from `.surface-elevated` selector list */
1316
+ min-width: 14rem;
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);
1323
+ }
1324
+ .nav-menu-item:hover > .nav-menu-content,
1325
+ .nav-menu-item:focus-within > .nav-menu-content {
1326
+ @apply visible;
1327
+ opacity: 1;
1328
+ transform: translateY(0);
1329
+ }
1330
+ .nav-menu-item:hover > .nav-menu-trigger,
1331
+ .nav-menu-item:focus-within > .nav-menu-trigger {
1332
+ color: var(--color-fg);
1333
+ background-color: var(--color-bg-muted);
1334
+ }
1335
+ .nav-menu-item:hover > .nav-menu-trigger > .nav-menu-chevron,
1336
+ .nav-menu-item:focus-within > .nav-menu-trigger > .nav-menu-chevron {
1337
+ transform: rotate(180deg);
1338
+ }
1339
+ .nav-menu-link {
1340
+ @apply flex items-start gap-2 px-3 py-2 text-sm;
1341
+ color: var(--color-fg);
1342
+ text-decoration: none;
1343
+ border-radius: var(--ui-radius-sm);
1344
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1345
+ }
1346
+ .nav-menu-link:hover { background-color: var(--color-bg-muted); }
1347
+
1348
+ .mega-menu-grid {
1349
+ @apply grid gap-6 p-5;
1350
+ min-width: 32rem;
1351
+ }
1352
+ .mega-menu-grid.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1353
+ .mega-menu-grid.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1354
+ .mega-menu-grid.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
1355
+ .mega-menu-column { @apply flex flex-col gap-1; }
1356
+ .mega-menu-title {
1357
+ @apply text-[11px] uppercase tracking-wider px-3 mb-1 font-semibold;
1358
+ color: var(--color-fg-subtle);
1359
+ }
1360
+
1361
+ /* ============================================================ */
1362
+ /* ColorPicker */
1363
+ /* ============================================================ */
1364
+ .color-picker { @apply inline-flex items-center gap-2; }
1365
+ .color-picker-swatch {
1366
+ @apply inline-block size-9 cursor-pointer overflow-hidden;
1367
+ border: 1px solid var(--color-border-strong);
1368
+ border-radius: var(--ui-radius);
1369
+ }
1370
+ .color-picker-text { @apply font-mono; width: 8.5rem; }
1371
+
1372
+ /* ============================================================ */
1373
+ /* Uploader */
1374
+ /* ============================================================ */
1375
+ .uploader-root {
1376
+ @apply flex w-full flex-col items-center justify-center gap-2 p-6 text-center cursor-pointer;
1377
+ background-color: var(--color-bg-subtle);
1378
+ color: var(--color-fg);
1379
+ border: 2px dashed var(--color-border-strong);
1380
+ border-radius: var(--ui-radius);
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);
1384
+ }
1385
+ .uploader-root:hover { background-color: var(--color-bg-muted); }
1386
+ .uploader-root:focus-visible { outline: none; box-shadow: var(--ui-focus-ring); }
1387
+ .uploader-dragging {
1388
+ background-color: color-mix(in oklab, var(--color-primary-500) 6%, var(--color-bg-muted));
1389
+ border-color: var(--color-primary-500);
1390
+ }
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; }
1395
+ .uploader-item {
1396
+ @apply flex items-center gap-2 px-2 py-1.5;
1397
+ background-color: var(--color-bg-muted);
1398
+ border-radius: var(--ui-radius-sm);
1399
+ }
1400
+
1401
+ /* ============================================================ */
1402
+ /* Date / Time pickers + custom Calendar */
1403
+ /* ============================================================ */
1404
+ .date-picker-trigger {
1405
+ @apply inline-flex items-center gap-2 cursor-pointer text-left w-full;
1406
+ min-width: 12rem;
1407
+ }
1408
+ .date-picker-trigger[disabled] { @apply opacity-60 cursor-not-allowed; }
1409
+
1410
+ .calendar { @apply select-none; }
1411
+ .calendar-header {
1412
+ @apply flex items-center justify-between gap-1 mb-2;
1413
+ }
1414
+ .calendar-heading-btn {
1415
+ @apply flex-1 text-sm font-semibold py-1 px-2 cursor-pointer;
1416
+ color: var(--color-fg);
1417
+ background: transparent;
1418
+ border: none;
1419
+ border-radius: var(--ui-radius-sm);
1420
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
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. */
1425
+ .calendar-row {
1426
+ @apply grid gap-0.5;
1427
+ grid-template-columns: repeat(7, minmax(0, 1fr));
1428
+ }
1429
+ .calendar-weekdays { @apply mb-1; }
1430
+ .calendar-weekday {
1431
+ @apply text-center text-[11px] font-medium uppercase py-1;
1432
+ color: var(--color-fg-subtle);
1433
+ }
1434
+ .calendar-day {
1435
+ @apply h-9 w-full inline-flex items-center justify-center text-sm cursor-pointer;
1436
+ color: var(--color-fg);
1437
+ background: transparent;
1438
+ border: none;
1439
+ border-radius: var(--ui-radius-sm);
1440
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
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); }
1445
+ .calendar-day-today {
1446
+ font-weight: 600;
1447
+ box-shadow: inset 0 0 0 1px var(--color-border-strong);
1448
+ }
1449
+ .calendar-day-selected,
1450
+ .calendar-day-selected:hover:not(:disabled) {
1451
+ background-color: var(--color-primary-600);
1452
+ color: white;
1453
+ font-weight: 600;
1454
+ }
1455
+ .calendar-day-selected:hover:not(:disabled) { background-color: var(--color-primary-700); }
1456
+ .calendar-day-in-range {
1457
+ background-color: color-mix(in oklab, var(--color-primary-500) 12%, transparent);
1458
+ border-radius: 0;
1459
+ }
1460
+ .calendar-day:disabled { @apply opacity-30 cursor-not-allowed; }
1461
+
1462
+ .time-picker-popover { min-width: 14rem; }
1463
+ .time-picker-grid { @apply flex items-stretch gap-1; }
1464
+ .time-picker-col {
1465
+ @apply flex-1 flex flex-col gap-0.5 overflow-y-auto py-1;
1466
+ max-height: 14rem;
1467
+ scroll-snap-type: y mandatory;
1468
+ scrollbar-width: thin;
1469
+ }
1470
+ .time-picker-col::-webkit-scrollbar { width: 4px; }
1471
+ .time-picker-col::-webkit-scrollbar-thumb {
1472
+ background-color: var(--color-border-strong);
1473
+ border-radius: 2px;
1474
+ }
1475
+ .time-picker-sep {
1476
+ @apply self-center text-xl font-mono;
1477
+ color: var(--color-fg-muted);
1478
+ }
1479
+ .time-picker-cell {
1480
+ @apply h-8 inline-flex items-center justify-center text-sm font-mono tabular-nums cursor-pointer;
1481
+ color: var(--color-fg);
1482
+ background: transparent;
1483
+ border: none;
1484
+ border-radius: var(--ui-radius-sm);
1485
+ scroll-snap-align: center;
1486
+ flex-shrink: 0;
1487
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1488
+ }
1489
+ .time-picker-cell:hover { background-color: var(--color-bg-muted); }
1490
+ .time-picker-cell-selected,
1491
+ .time-picker-cell-selected:hover {
1492
+ background-color: var(--color-primary-600);
1493
+ color: white;
1494
+ font-weight: 600;
1495
+ }
1496
+ .time-picker-cell-selected:hover { background-color: var(--color-primary-700); }
1497
+
1498
+ /* ============================================================ */
1499
+ /* DataTable */
1500
+ /* ============================================================ */
1501
+ .data-table-root {
1502
+ @apply w-full overflow-x-auto;
1503
+ border: 1px solid var(--color-border);
1504
+ border-radius: var(--ui-radius);
1505
+ }
1506
+ .data-table { @apply w-full text-sm; border-collapse: collapse; }
1507
+ .data-table th,
1508
+ .data-table td { @apply px-3 py-2 text-left align-middle; }
1509
+ .data-table thead th {
1510
+ @apply font-semibold;
1511
+ color: var(--color-fg);
1512
+ background-color: var(--color-bg-muted);
1513
+ border-bottom: 1px solid var(--color-border);
1514
+ }
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); }
1519
+ .data-table-striped tbody tr:nth-child(even) {
1520
+ background-color: var(--color-bg-subtle);
1521
+ }
1522
+ .data-table-dense th,
1523
+ .data-table-dense td { @apply py-1; }
1524
+ .data-table-row-clickable { @apply cursor-pointer; }
1525
+ .data-table-empty td {
1526
+ @apply text-center py-6;
1527
+ color: var(--color-fg-muted);
1528
+ }
1529
+ .data-table-sort {
1530
+ @apply inline-flex items-center gap-1;
1531
+ background: transparent;
1532
+ color: inherit;
1533
+ font: inherit;
1534
+ cursor: pointer;
1535
+ }
1536
+ .data-table-sort:hover { color: var(--color-primary-600); }
1537
+
1538
+ /* ============================================================ */
1539
+ /* Tree */
1540
+ /* ============================================================ */
1541
+ .tree { @apply text-sm; color: var(--color-fg); }
1542
+ .tree ul { @apply list-none p-0 m-0; }
1543
+ .tree-row {
1544
+ @apply flex w-full items-center gap-2 py-1 pr-2 text-left cursor-pointer;
1545
+ color: var(--color-fg);
1546
+ background-color: transparent;
1547
+ border-radius: var(--ui-radius-sm);
1548
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
1549
+ }
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 {
1554
+ background-color: color-mix(in oklab, var(--color-primary-500) 12%, transparent);
1555
+ color: var(--color-fg);
1556
+ }
1557
+ .tree-chevron {
1558
+ @apply inline-flex size-4 shrink-0 items-center justify-center;
1559
+ color: var(--color-fg-muted);
1560
+ transition: transform var(--ui-motion-fast) var(--ui-motion-ease);
1561
+ }
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;
1576
+ }
1577
+ .norns-scroll-area-horizontal {
1578
+ overflow-x: auto;
1579
+ overflow-y: hidden;
1580
+ }
1581
+ .norns-scroll-area-both {
1582
+ overflow: auto;
1583
+ }
1584
+
1585
+ /* ============================================================ */
1586
+ /* Carousel */
1587
+ /* ============================================================ */
1588
+ .carousel {
1589
+ @apply relative overflow-hidden w-full;
1590
+ border-radius: var(--ui-radius);
1591
+ background-color: var(--color-bg-muted);
1592
+ }
1593
+ .carousel-track {
1594
+ @apply flex h-full;
1595
+ transition: transform 500ms var(--ui-motion-ease);
1596
+ }
1597
+ .carousel-slide { @apply h-full w-full shrink-0; }
1598
+ .carousel-arrow {
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);
1602
+ background-color: color-mix(in oklab, var(--color-bg) 70%, transparent);
1603
+ color: var(--color-fg);
1604
+ border: 1px solid var(--color-border);
1605
+ border-radius: var(--ui-radius-full);
1606
+ transition: background-color var(--ui-motion-fast) var(--ui-motion-ease);
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; }
1612
+ .carousel-dots {
1613
+ @apply absolute bottom-3 left-1/2 -translate-x-1/2 inline-flex items-center gap-1.5;
1614
+ }
1615
+ .carousel-dot {
1616
+ @apply size-2 rounded-full cursor-pointer;
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);
1620
+ }
1621
+ .carousel-dot-active { background-color: var(--color-bg-inverse); }
364
1622
  }