@muten/shadcn 0.0.3 → 0.0.5

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.
package/globals.css CHANGED
@@ -6,37 +6,159 @@
6
6
  /* Scan THIS plugin's parts so any utility used inside them is generated, even though they're imported from
7
7
  node_modules (which Tailwind's auto-scan skips). Relative to this file. */
8
8
  @source "./registry";
9
- @theme {
10
- --color-background: hsl(0 0% 3.9%);
11
- --color-foreground: hsl(0 0% 98%);
12
- --color-card: hsl(0 0% 3.9%);
13
- --color-card-foreground: hsl(0 0% 98%);
14
- --color-popover: hsl(0 0% 3.9%);
15
- --color-popover-foreground: hsl(0 0% 98%);
16
- --color-primary: hsl(0 0% 98%);
17
- --color-primary-foreground: hsl(0 0% 9%);
18
- --color-secondary: hsl(0 0% 14.9%);
19
- --color-secondary-foreground: hsl(0 0% 98%);
20
- --color-muted: hsl(0 0% 14.9%);
21
- --color-muted-foreground: hsl(0 0% 63.9%);
22
- --color-accent: hsl(0 0% 14.9%);
23
- --color-accent-foreground: hsl(0 0% 98%);
24
- --color-destructive: hsl(0 62.8% 30.6%);
25
- --color-destructive-foreground: hsl(0 0% 98%);
26
- --color-border: hsl(0 0% 14.9%);
27
- --color-input: hsl(0 0% 14.9%);
28
- --color-ring: hsl(0 0% 83.1%);
29
-
30
- /* shadcn radius scale: rounded-sm/md/lg derive from --radius, exactly like shadcn's tailwind config. */
31
- --radius: 0.5rem;
9
+ /* Make the `dark:` utility variant respond to a `.dark` CLASS (not the OS media query) - shadcn's v4 setup,
10
+ so a `<html class="dark">` flips both the tokens below AND every dark: utility in the recipes. */
11
+ @custom-variant dark (&:is(.dark, .dark *, [data-theme="dark"], [data-theme="dark"] *));
12
+
13
+ /* shadcn's EXACT default tokens (base color: neutral), verbatim from ui.shadcn.com. :root = light, .dark = dark;
14
+ an app opts into dark with <html class="dark">. @theme inline maps them to Tailwind's color vars so every
15
+ bg, text and border utility tracks the active mode (the inline keyword is what lets .dark override them).
16
+ NOTE: never write an asterisk-slash sequence in this comment - it closes the CSS comment early and drops :root. */
17
+ :root {
18
+ --radius: 0.625rem;
19
+ /* refined ease-out for all overlay/disclosure motion - tune here once, every component follows. */
20
+ --mu-ease: cubic-bezier(0.16, 1, 0.3, 1);
21
+ --mu-motion: 180ms;
22
+ --background: oklch(1 0 0);
23
+ --foreground: oklch(0.145 0 0);
24
+ --card: oklch(1 0 0);
25
+ --card-foreground: oklch(0.145 0 0);
26
+ --popover: oklch(1 0 0);
27
+ --popover-foreground: oklch(0.145 0 0);
28
+ --primary: oklch(0.205 0 0);
29
+ --primary-foreground: oklch(0.985 0 0);
30
+ --secondary: oklch(0.97 0 0);
31
+ --secondary-foreground: oklch(0.205 0 0);
32
+ --muted: oklch(0.97 0 0);
33
+ --muted-foreground: oklch(0.556 0 0);
34
+ --accent: oklch(0.97 0 0);
35
+ --accent-foreground: oklch(0.205 0 0);
36
+ --destructive: oklch(0.577 0.245 27.325);
37
+ --border: oklch(0.922 0 0);
38
+ --input: oklch(0.922 0 0);
39
+ --ring: oklch(0.708 0 0);
40
+ --chart-1: oklch(0.646 0.222 41.116);
41
+ --chart-2: oklch(0.6 0.118 184.704);
42
+ --chart-3: oklch(0.398 0.07 227.392);
43
+ --chart-4: oklch(0.828 0.189 84.429);
44
+ --chart-5: oklch(0.769 0.188 70.08);
45
+ --sidebar: oklch(0.985 0 0);
46
+ --sidebar-foreground: oklch(0.145 0 0);
47
+ --sidebar-primary: oklch(0.205 0 0);
48
+ --sidebar-primary-foreground: oklch(0.985 0 0);
49
+ --sidebar-accent: oklch(0.97 0 0);
50
+ --sidebar-accent-foreground: oklch(0.205 0 0);
51
+ --sidebar-border: oklch(0.922 0 0);
52
+ --sidebar-ring: oklch(0.708 0 0);
53
+ }
54
+ .dark, [data-theme="dark"] {
55
+ --background: oklch(0.145 0 0);
56
+ --foreground: oklch(0.985 0 0);
57
+ --card: oklch(0.205 0 0);
58
+ --card-foreground: oklch(0.985 0 0);
59
+ --popover: oklch(0.205 0 0);
60
+ --popover-foreground: oklch(0.985 0 0);
61
+ --primary: oklch(0.922 0 0);
62
+ --primary-foreground: oklch(0.205 0 0);
63
+ --secondary: oklch(0.269 0 0);
64
+ --secondary-foreground: oklch(0.985 0 0);
65
+ --muted: oklch(0.269 0 0);
66
+ --muted-foreground: oklch(0.708 0 0);
67
+ --accent: oklch(0.269 0 0);
68
+ --accent-foreground: oklch(0.985 0 0);
69
+ --destructive: oklch(0.704 0.191 22.216);
70
+ --border: oklch(1 0 0 / 10%);
71
+ --input: oklch(1 0 0 / 15%);
72
+ --ring: oklch(0.556 0 0);
73
+ --chart-1: oklch(0.488 0.243 264.376);
74
+ --chart-2: oklch(0.696 0.17 162.48);
75
+ --chart-3: oklch(0.769 0.188 70.08);
76
+ --chart-4: oklch(0.627 0.265 303.9);
77
+ --chart-5: oklch(0.645 0.246 16.439);
78
+ --sidebar: oklch(0.205 0 0);
79
+ --sidebar-foreground: oklch(0.985 0 0);
80
+ --sidebar-primary: oklch(0.488 0.243 264.376);
81
+ --sidebar-primary-foreground: oklch(0.985 0 0);
82
+ --sidebar-accent: oklch(0.269 0 0);
83
+ --sidebar-accent-foreground: oklch(0.985 0 0);
84
+ --sidebar-border: oklch(1 0 0 / 10%);
85
+ --sidebar-ring: oklch(0.556 0 0);
86
+ }
87
+ @theme inline {
32
88
  --radius-sm: calc(var(--radius) - 4px);
33
89
  --radius-md: calc(var(--radius) - 2px);
34
90
  --radius-lg: var(--radius);
91
+ --radius-xl: calc(var(--radius) + 4px);
92
+ --color-background: var(--background);
93
+ --color-foreground: var(--foreground);
94
+ --color-card: var(--card);
95
+ --color-card-foreground: var(--card-foreground);
96
+ --color-popover: var(--popover);
97
+ --color-popover-foreground: var(--popover-foreground);
98
+ --color-primary: var(--primary);
99
+ --color-primary-foreground: var(--primary-foreground);
100
+ --color-secondary: var(--secondary);
101
+ --color-secondary-foreground: var(--secondary-foreground);
102
+ --color-muted: var(--muted);
103
+ --color-muted-foreground: var(--muted-foreground);
104
+ --color-accent: var(--accent);
105
+ --color-accent-foreground: var(--accent-foreground);
106
+ --color-destructive: var(--destructive);
107
+ --color-destructive-foreground: oklch(1 0 0); /* not a shadcn token anymore (destructive uses text-white); kept so any untouched recipe still resolves */
108
+ --color-border: var(--border);
109
+ --color-input: var(--input);
110
+ --color-ring: var(--ring);
111
+ --color-chart-1: var(--chart-1);
112
+ --color-chart-2: var(--chart-2);
113
+ --color-chart-3: var(--chart-3);
114
+ --color-chart-4: var(--chart-4);
115
+ --color-chart-5: var(--chart-5);
116
+ --color-sidebar: var(--sidebar);
117
+ --color-sidebar-foreground: var(--sidebar-foreground);
118
+ --color-sidebar-primary: var(--sidebar-primary);
119
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
120
+ --color-sidebar-accent: var(--sidebar-accent);
121
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
122
+ --color-sidebar-border: var(--sidebar-border);
123
+ --color-sidebar-ring: var(--sidebar-ring);
124
+ }
125
+
126
+ /* Premium elevation scale - modern 3-layer soft shadows (Vercel/Linear/Stripe feel): a tight contact layer +
127
+ a mid layer + a wide ambient layer, each roughly doubling blur/offset while opacity stays LOW (4-12%). That
128
+ is what makes cards float "airy" instead of looking stamped-on. Overrides Tailwind's defaults so every
129
+ card / dialog / popover / button gets it. Tuned for light surfaces; on dark UIs elevation reads mostly through
130
+ the border + a lighter surface (see .dark --card), so the shadow stays subtle by design. */
131
+ @theme {
132
+ --shadow: 0 1px 2px -1px rgb(0 0 0 / 0.06), 0 1px 3px 0 rgb(0 0 0 / 0.04);
133
+ --shadow-2xs: 0 1px 2px 0 rgb(0 0 0 / 0.04);
134
+ --shadow-xs: 0 1px 2px -1px rgb(0 0 0 / 0.06), 0 1px 3px 0 rgb(0 0 0 / 0.04);
135
+ --shadow-sm: 0 1px 2px -1px rgb(0 0 0 / 0.06), 0 2px 4px 0 rgb(0 0 0 / 0.05);
136
+ --shadow-md: 0 2px 4px -2px rgb(0 0 0 / 0.06), 0 4px 8px -2px rgb(0 0 0 / 0.06), 0 8px 16px -4px rgb(0 0 0 / 0.05);
137
+ --shadow-lg: 0 2px 4px -2px rgb(0 0 0 / 0.05), 0 8px 16px -4px rgb(0 0 0 / 0.08), 0 16px 32px -8px rgb(0 0 0 / 0.06);
138
+ --shadow-xl: 0 4px 8px -4px rgb(0 0 0 / 0.06), 0 12px 24px -6px rgb(0 0 0 / 0.10), 0 24px 48px -12px rgb(0 0 0 / 0.08);
139
+ --shadow-2xl: 0 8px 16px -6px rgb(0 0 0 / 0.08), 0 24px 48px -12px rgb(0 0 0 / 0.12), 0 40px 72px -16px rgb(0 0 0 / 0.10);
35
140
  }
36
141
 
37
142
  /* shadcn's base: every border/outline defaults to the token (so a bare `border` is subtle, not currentColor). */
38
143
  @layer base {
39
- * { border-color: var(--color-border); outline-color: color-mix(in srgb, var(--color-ring) 50%, transparent); }
144
+ * { @apply border-border outline-ring/50; }
145
+ body { @apply bg-background text-foreground; }
146
+
147
+ /* muten's runtime emits a "Skip to content" link as the first node; hide it until keyboard-focused (else it
148
+ shows as raw text at the top-left of every app that only imports this plugin's globals). */
149
+ .mu-skip-link { @apply absolute -left-[9999px] top-2 z-[1000] rounded-md bg-foreground px-4 py-2 text-sm text-background; }
150
+ .mu-skip-link:focus { @apply left-2; }
151
+
152
+ /* Icons: muten emits an Icon as <span class="mu-icon"><svg>…</svg></span>. The raw <svg> is inline, so it
153
+ sits on the text baseline INSIDE its span and looks a touch high/low next to a label. Make every icon a
154
+ centered inline-flex box (default 1em = text size) with a block svg → icon+text align optically EVERYWHERE
155
+ (nav items, pills, links…), not only inside .btn/.toggle. Size utilities (size-4, text-lg) still win. */
156
+ .mu-icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 1em; height: 1em; line-height: 1; }
157
+ .mu-icon > svg { display: block; width: 100%; height: 100%; }
158
+
159
+ /* Interactive elements get a pointer cursor - Tailwind v4's preflight leaves <button> as the default arrow,
160
+ which reads as "not clickable" on tabs / accordion triggers / menu items. This covers every button-based part. */
161
+ button:not(:disabled), [role="tab"], [role="button"], summary, label:has(> input:not([disabled])) { cursor: pointer; }
40
162
 
41
163
  /* Fully themed scrollbars (track + thumb), so the OS default never clashes with the theme. */
42
164
  * { scrollbar-width: thin; scrollbar-color: var(--color-border) transparent; }
@@ -48,40 +170,47 @@
48
170
  }
49
171
 
50
172
  @layer components {
51
- /* ---- Button (buttonVariants) - sizes default in .btn; .btn-sm/-lg/-icon override ---- */
52
- .btn { @apply inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all duration-200 active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 focus-visible:ring-[3px] focus-visible:ring-ring/50 h-9 px-4 py-2; }
53
- .btn-default { @apply bg-primary text-primary-foreground shadow hover:bg-primary/90; }
54
- .btn-destructive { @apply bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90; }
55
- .btn-outline { @apply border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground; }
56
- .btn-secondary { @apply bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80; }
57
- .btn-ghost { @apply text-foreground hover:bg-accent hover:text-accent-foreground; }
173
+ /* ---- Button (buttonVariants) - shadcn new-york-v4 (stable, what `shadcn add button` installs) ----
174
+ Ported verbatim; the only utilities dropped are `has-[>svg]:px-*` (per-icon padding tweak that needs a
175
+ raw <svg> child - muten renders icons as iconify-icon). Icon sizing is the `.btn svg` rule below. */
176
+ .btn { @apply inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium leading-none transition-all shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] active:scale-[0.97] aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 disabled:pointer-events-none disabled:opacity-50 h-8 px-3; }
177
+ .btn-default { @apply bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 active:bg-primary/80; }
178
+ .btn-destructive { @apply bg-destructive text-white shadow-xs hover:bg-destructive/90 active:bg-destructive/80 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60; }
179
+ .btn-outline { @apply border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground active:bg-accent/70 dark:bg-input/30 dark:border-input dark:hover:bg-input/50 dark:active:bg-input/70; }
180
+ .btn-secondary { @apply bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80 active:bg-secondary/70; }
181
+ .btn-ghost { @apply hover:bg-accent hover:text-accent-foreground active:bg-accent/70 dark:hover:bg-accent/50 dark:active:bg-accent/70; }
58
182
  .btn-link { @apply text-primary underline-offset-4 hover:underline; }
59
- .btn-sm { @apply h-8 rounded-md px-3 text-xs; }
60
- .btn-lg { @apply h-10 rounded-md px-8; }
61
- .btn-icon { @apply h-9 w-9 shrink-0 px-0; }
62
- /* shadcn sizes any icon inside a button (its [&_svg]:size-4); so `Button { Icon } class("btn btn-ghost btn-icon")` just works */
183
+ .btn-xs { @apply h-6 gap-1 rounded-md px-2 text-xs; }
184
+ .btn-sm { @apply h-7 rounded-md gap-1.5 px-2.5; }
185
+ .btn-lg { @apply h-9 rounded-md px-5; }
186
+ .btn-icon { @apply size-8; }
187
+ .btn-icon-xs { @apply size-6 rounded-md; }
188
+ .btn-icon-sm { @apply size-7; }
189
+ .btn-icon-lg { @apply size-9; }
190
+ /* shadcn sizes any icon inside a button (its [&_svg]:size-4); the xs sizes drop to size-3. */
63
191
  .btn svg, .btn .mu-icon, .btn iconify-icon { width: 1rem; height: 1rem; flex-shrink: 0; }
192
+ .btn-xs svg, .btn-xs .mu-icon, .btn-xs iconify-icon, .btn-icon-xs svg, .btn-icon-xs .mu-icon, .btn-icon-xs iconify-icon { width: .75rem; height: .75rem; }
64
193
 
65
194
  /* ---- Form controls ---- */
66
- .input { @apply flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50; }
67
- .textarea { @apply flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50; }
195
+ .input { @apply flex h-8 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base md:text-sm shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50; }
196
+ .textarea { @apply flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base md:text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 disabled:cursor-not-allowed disabled:opacity-50; }
68
197
  .label { @apply text-sm font-medium leading-none; }
69
- .native-select { @apply flex h-9 w-full items-center rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50; }
198
+ .native-select { @apply flex h-8 w-full items-center rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive dark:bg-input/30 disabled:cursor-not-allowed disabled:opacity-50; }
70
199
 
71
200
  /* ---- Card ---- */
72
- .card { @apply rounded-xl border bg-card text-card-foreground shadow; }
73
- .card-header { @apply flex flex-col space-y-1.5 p-6; }
74
- .card-title { @apply font-semibold leading-none tracking-tight; }
201
+ .card { @apply bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm; }
202
+ .card-header { @apply grid auto-rows-min items-start gap-1.5 px-6; }
203
+ .card-title { @apply font-semibold leading-none; }
75
204
  .card-description { @apply text-sm text-muted-foreground; }
76
- .card-content { @apply p-6 pt-0; }
77
- .card-footer { @apply flex flex-row items-center p-6 pt-0; }
205
+ .card-content { @apply px-6; }
206
+ .card-footer { @apply flex flex-row items-center px-6; }
78
207
 
79
208
  /* ---- Badge (badgeVariants) ---- */
80
- .badge { @apply inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-semibold w-fit whitespace-nowrap shrink-0 gap-1 transition-colors duration-200; }
81
- .badge-default { @apply border-transparent bg-primary text-primary-foreground; }
82
- .badge-secondary { @apply border-transparent bg-secondary text-secondary-foreground; }
83
- .badge-destructive { @apply border-transparent bg-destructive text-destructive-foreground; }
84
- .badge-outline { @apply text-foreground; }
209
+ .badge { @apply inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium leading-none w-fit whitespace-nowrap shrink-0 gap-1 overflow-hidden transition-[color,box-shadow] [&>svg]:size-3 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40; }
210
+ .badge-default { @apply border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90; }
211
+ .badge-secondary { @apply border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90; }
212
+ .badge-destructive { @apply border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60; }
213
+ .badge-outline { @apply text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground; }
85
214
  /* Notification count pill (rounded-full, for an unseen counter) + a bare status dot. Default primary;
86
215
  add .badge-count-destructive for a red alert count. */
87
216
  .badge-count { @apply inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-[10px] font-semibold leading-none text-primary-foreground; }
@@ -89,11 +218,11 @@
89
218
  .badge-dot { @apply inline-block size-2 shrink-0 rounded-full bg-primary; }
90
219
 
91
220
  /* ---- Alert ---- */
92
- .alert { @apply relative w-full rounded-lg border px-4 py-3 text-sm flex flex-row gap-3 items-start; }
93
- .alert-default { @apply bg-background text-foreground; }
94
- .alert-destructive { @apply border-destructive/50 text-destructive; }
95
- .alert-title { @apply font-medium leading-none tracking-tight; }
96
- .alert-description { @apply text-sm opacity-90; }
221
+ .alert { @apply relative w-full rounded-lg border px-4 py-3 text-sm flex flex-row gap-3 items-start bg-card text-card-foreground; }
222
+ .alert-default { @apply bg-card text-card-foreground; }
223
+ .alert-destructive { @apply bg-card text-destructive; }
224
+ .alert-title { @apply font-medium tracking-tight; }
225
+ .alert-description { @apply text-sm text-muted-foreground; }
97
226
 
98
227
  /* ---- Avatar ---- */
99
228
  .avatar { @apply relative flex size-10 shrink-0 overflow-hidden rounded-full; }
@@ -151,9 +280,12 @@
151
280
  .checkbox-icon-on { transform: scale(1); opacity: 1; }
152
281
 
153
282
  /* ---- Toggle / Toggle Group (toggle .toggle-on from the page state) ---- */
154
- .toggle { @apply inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium h-9 min-w-9 px-2 outline-none transition-all duration-200 active:scale-[0.98] hover:bg-muted hover:text-muted-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50; }
283
+ .toggle { @apply inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium h-8 min-w-8 px-2 outline-none transition-all duration-200 active:scale-[0.98] hover:bg-muted hover:text-muted-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50; }
155
284
  .toggle-on { @apply bg-accent text-accent-foreground; }
156
285
  .toggle-group { @apply inline-flex flex-row items-center gap-1; }
286
+ /* size + center any icon inside a toggle (a bare <svg>/iconify sits on the text baseline otherwise -> the
287
+ "B" looks off). `block` + fixed size makes the flex `items-center` truly center it against the label. */
288
+ .toggle svg, .toggle .mu-icon, .toggle iconify-icon { display: block; width: 1rem; height: 1rem; flex-shrink: 0; }
157
289
 
158
290
  /* ---- Radio Group ---- */
159
291
  .radio-group { @apply flex flex-col gap-3; }
@@ -183,30 +315,31 @@
183
315
  .accordion-content { @apply pb-4 text-sm text-muted-foreground; }
184
316
 
185
317
  /* ---- Tabs (active panel via .hidden toggle so panel state survives switching) ---- */
186
- .tabs-list { @apply inline-flex flex-row items-center justify-center gap-1 rounded-lg bg-muted p-1 text-muted-foreground; }
187
- .tabs-trigger { @apply inline-flex items-center justify-center gap-1.5 rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap outline-none transition-all duration-200 focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50; }
188
- .tabs-trigger-active { @apply bg-background text-foreground shadow-sm; }
189
- .tabs-content { @apply mt-2 outline-none; }
318
+ .tabs-list { @apply inline-flex h-9 w-fit flex-row items-center justify-center rounded-lg bg-muted p-[3px] text-muted-foreground; }
319
+ .tabs-trigger { @apply inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground dark:text-muted-foreground outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50; transition: color var(--mu-motion) var(--mu-ease), background-color var(--mu-motion) var(--mu-ease), box-shadow var(--mu-motion) var(--mu-ease), border-color var(--mu-motion) var(--mu-ease); }
320
+ .tabs-trigger-active { @apply bg-background text-foreground shadow-sm dark:border-input dark:bg-input/30 dark:text-foreground; }
321
+ /* the panel fades+slides in whenever it goes from display:none (.hidden) to shown - i.e. on every tab switch. */
322
+ .tabs-content { @apply mt-2 flex-1 outline-none; animation: mu-tab-in var(--mu-motion) var(--mu-ease); }
190
323
 
191
324
  /* ---- Dialog (modal: page owns `open`; overlay always renders, fades+zooms via .dialog-overlay-open) ---- */
192
- .dialog-overlay { @apply fixed inset-0 z-50 flex items-center justify-center p-4 opacity-0 invisible; transition: opacity 200ms ease, visibility 200ms ease; }
325
+ .dialog-overlay { @apply fixed inset-0 z-50 flex items-center justify-center p-4 opacity-0 invisible; transition: opacity var(--mu-motion) var(--mu-ease), visibility var(--mu-motion) var(--mu-ease); }
193
326
  .dialog-overlay-open { @apply opacity-100 visible; }
194
- .dialog-backdrop { @apply fixed inset-0 bg-black/80; }
195
- .dialog-content { @apply relative z-10 w-full max-w-lg rounded-lg border bg-background p-6 shadow-lg flex flex-col gap-4 scale-95 transition-transform duration-200; }
327
+ .dialog-backdrop { @apply fixed inset-0 bg-black/50; }
328
+ .dialog-content { @apply relative z-10 w-full max-w-[calc(100%-2rem)] sm:max-w-lg rounded-lg border bg-background p-6 shadow-lg flex flex-col gap-4 scale-95; transition: scale var(--mu-motion) var(--mu-ease), translate var(--mu-motion) var(--mu-ease); }
196
329
  .dialog-overlay-open .dialog-content { @apply scale-100; }
197
- .dialog-title { @apply text-lg font-semibold leading-none tracking-tight; }
330
+ .dialog-title { @apply text-lg font-semibold leading-none; }
198
331
  .dialog-description { @apply text-sm text-muted-foreground; }
199
- .dialog-footer { @apply flex flex-row justify-end gap-2; }
332
+ .dialog-footer { @apply flex flex-col-reverse gap-2 sm:flex-row sm:justify-end; }
200
333
 
201
334
  /* ---- Tooltip (pure CSS :hover, no state) ---- */
202
335
  .tooltip-wrap { @apply relative inline-flex; }
203
- .tooltip-content { @apply pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 -translate-x-1/2 whitespace-nowrap rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground shadow-md opacity-0 invisible; transition: opacity 200ms ease, visibility 200ms ease; }
204
- .tooltip-wrap:hover .tooltip-content { @apply opacity-100 visible; }
336
+ .tooltip-content { @apply pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 origin-bottom -translate-x-1/2 translate-y-1 scale-95 whitespace-nowrap rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground shadow-md opacity-0 invisible; transition: opacity var(--mu-motion) var(--mu-ease), scale var(--mu-motion) var(--mu-ease), translate var(--mu-motion) var(--mu-ease), visibility var(--mu-motion) var(--mu-ease); }
337
+ .tooltip-wrap:hover .tooltip-content { @apply opacity-100 visible translate-y-0 scale-100; }
205
338
 
206
339
  /* ---- Dropdown Menu (page owns `open`; absolute panel below the trigger; backdrop catches outside clicks) ---- */
207
340
  .dropdown { @apply relative inline-flex; }
208
341
  .dropdown-backdrop { @apply fixed inset-0 z-40; }
209
- .dropdown-content { @apply absolute top-full right-0 z-50 mt-1 min-w-48 rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col origin-top-right opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
342
+ .dropdown-content { @apply absolute top-full right-0 z-50 mt-1 min-w-48 rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col origin-top-right opacity-0 invisible scale-95; transition: opacity var(--mu-motion) var(--mu-ease), scale var(--mu-motion) var(--mu-ease), translate var(--mu-motion) var(--mu-ease), visibility var(--mu-motion) var(--mu-ease); }
210
343
  .dropdown-content-open { @apply opacity-100 visible scale-100; }
211
344
  .dropdown-item { @apply flex flex-row items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors duration-200 hover:bg-accent hover:text-accent-foreground; }
212
345
  .dropdown-label { @apply px-2 py-1.5 text-sm font-semibold; }
@@ -215,37 +348,37 @@
215
348
  /* ---- Popover (page owns `open`; absolute panel below the trigger) ---- */
216
349
  .popover { @apply relative inline-flex; }
217
350
  .popover-backdrop { @apply fixed inset-0 z-40; }
218
- .popover-content { @apply absolute top-full left-0 z-50 mt-1 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md flex flex-col gap-2 origin-top opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
351
+ .popover-content { @apply absolute top-full left-0 z-50 mt-1 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md flex flex-col gap-2 origin-top opacity-0 invisible scale-95; transition: opacity var(--mu-motion) var(--mu-ease), scale var(--mu-motion) var(--mu-ease), translate var(--mu-motion) var(--mu-ease), visibility var(--mu-motion) var(--mu-ease); }
219
352
  .popover-content-open { @apply opacity-100 visible scale-100; }
220
353
 
221
354
  /* ---- Sheet (side panel; slides in from the right via translate) ---- */
222
- .sheet-overlay { @apply fixed inset-0 z-50 opacity-0 invisible; transition: opacity 300ms ease, visibility 300ms ease; }
355
+ .sheet-overlay { @apply fixed inset-0 z-50 opacity-0 invisible; transition: opacity 260ms var(--mu-ease), visibility 260ms var(--mu-ease); }
223
356
  .sheet-overlay-open { @apply opacity-100 visible; }
224
- .sheet-backdrop { @apply fixed inset-0 bg-black/80; }
225
- .sheet-content { @apply fixed inset-y-0 right-0 z-10 h-full w-3/4 max-w-sm border-l bg-background p-6 shadow-lg flex flex-col gap-4 translate-x-full; transition: transform 300ms ease; }
357
+ .sheet-backdrop { @apply fixed inset-0 bg-black/50; }
358
+ .sheet-content { @apply fixed inset-y-0 right-0 z-10 h-full w-3/4 max-w-sm border-l bg-background p-6 shadow-lg flex flex-col gap-4 translate-x-full; transition: translate 260ms var(--mu-ease); }
226
359
  .sheet-overlay-open .sheet-content { @apply translate-x-0; }
227
360
  .sheet-title { @apply text-lg font-semibold text-foreground; }
228
361
  .sheet-description { @apply text-sm text-muted-foreground; }
229
362
 
230
363
  /* ---- Drawer (bottom sheet; slides up via translate) ---- */
231
- .drawer-overlay { @apply fixed inset-0 z-50 opacity-0 invisible; transition: opacity 300ms ease, visibility 300ms ease; }
364
+ .drawer-overlay { @apply fixed inset-0 z-50 opacity-0 invisible; transition: opacity 260ms var(--mu-ease), visibility 260ms var(--mu-ease); }
232
365
  .drawer-overlay-open { @apply opacity-100 visible; }
233
- .drawer-backdrop { @apply fixed inset-0 bg-black/80; }
234
- .drawer-content { @apply fixed inset-x-0 bottom-0 z-10 mt-24 flex flex-col gap-4 rounded-t-lg border bg-background p-6 translate-y-full; transition: transform 300ms ease; }
366
+ .drawer-backdrop { @apply fixed inset-0 bg-black/50; }
367
+ .drawer-content { @apply fixed inset-x-0 bottom-0 z-10 mt-24 flex flex-col gap-4 rounded-t-lg border bg-background p-6 translate-y-full; transition: translate 260ms var(--mu-ease); }
235
368
  .drawer-overlay-open .drawer-content { @apply translate-y-0; }
236
369
  .drawer-handle { @apply mx-auto h-2 w-24 rounded-full bg-muted; }
237
370
 
238
371
  /* ---- Hover Card (pure CSS :hover, richer than a tooltip) ---- */
239
372
  .hover-card { @apply relative inline-flex; }
240
373
  .hover-card-trigger { @apply underline underline-offset-4 cursor-pointer; }
241
- .hover-card-content { @apply pointer-events-none absolute top-full left-0 z-50 mt-2 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md flex flex-col gap-2 opacity-0 invisible; transition: opacity 200ms ease, visibility 200ms ease; }
242
- .hover-card:hover .hover-card-content { @apply opacity-100 visible; }
374
+ .hover-card-content { @apply pointer-events-none absolute top-full left-0 z-50 mt-2 w-64 origin-top -translate-y-1 scale-95 rounded-md border bg-popover p-4 text-popover-foreground shadow-md flex flex-col gap-2 opacity-0 invisible; transition: opacity var(--mu-motion) var(--mu-ease), scale var(--mu-motion) var(--mu-ease), translate var(--mu-motion) var(--mu-ease), visibility var(--mu-motion) var(--mu-ease); }
375
+ .hover-card:hover .hover-card-content { @apply opacity-100 visible translate-y-0 scale-100; }
243
376
 
244
377
  /* ---- Select (trigger shows the value; items check when selected) ---- */
245
378
  .select-wrap { @apply relative inline-flex flex-col w-full max-w-xs; }
246
379
  .select-backdrop { @apply fixed inset-0 z-40; }
247
- .select-trigger { @apply flex h-9 w-full items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50; }
248
- .select-content { @apply absolute top-full left-0 z-50 mt-1 w-full min-w-32 rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col origin-top opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
380
+ .select-trigger { @apply flex h-8 w-full items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs outline-none transition-[color,box-shadow] data-[placeholder]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 disabled:cursor-not-allowed disabled:opacity-50; }
381
+ .select-content { @apply absolute top-full left-0 z-50 mt-1 w-full min-w-32 rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col origin-top opacity-0 invisible scale-95; transition: opacity var(--mu-motion) var(--mu-ease), scale var(--mu-motion) var(--mu-ease), translate var(--mu-motion) var(--mu-ease), visibility var(--mu-motion) var(--mu-ease); }
249
382
  .select-content-open { @apply opacity-100 visible scale-100; }
250
383
  .select-item { @apply flex flex-row items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors duration-200 hover:bg-accent hover:text-accent-foreground; }
251
384
 
@@ -272,7 +405,7 @@
272
405
  .menubar-item { @apply relative; }
273
406
  .menubar-trigger { @apply flex flex-row items-center rounded-sm px-3 py-1 text-sm font-medium outline-none transition-colors duration-200 hover:bg-accent hover:text-accent-foreground; }
274
407
  .menubar-trigger-active { @apply bg-accent text-accent-foreground; }
275
- .menubar-content { @apply absolute top-full left-0 z-50 mt-1 min-w-48 rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col origin-top opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
408
+ .menubar-content { @apply absolute top-full left-0 z-50 mt-1 min-w-48 rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col origin-top opacity-0 invisible scale-95; transition: opacity var(--mu-motion) var(--mu-ease), scale var(--mu-motion) var(--mu-ease), translate var(--mu-motion) var(--mu-ease), visibility var(--mu-motion) var(--mu-ease); }
276
409
  .menubar-content-open { @apply opacity-100 visible scale-100; }
277
410
 
278
411
  /* ---- Navigation Menu (hover-driven, pure CSS) ---- */
@@ -287,7 +420,7 @@
287
420
 
288
421
  /* ---- Combobox (a Select with a search box; page filters items with `where`) ---- */
289
422
  .combobox-trigger { @apply flex h-9 w-full items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 hover:bg-accent/40 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50; }
290
- .combobox-content { @apply absolute top-full left-0 z-50 mt-1 w-full rounded-md border bg-popover text-popover-foreground shadow-md flex flex-col origin-top opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
423
+ .combobox-content { @apply absolute top-full left-0 z-50 mt-1 w-full rounded-md border bg-popover text-popover-foreground shadow-md flex flex-col origin-top opacity-0 invisible scale-95; transition: opacity var(--mu-motion) var(--mu-ease), scale var(--mu-motion) var(--mu-ease), translate var(--mu-motion) var(--mu-ease), visibility var(--mu-motion) var(--mu-ease); }
291
424
  .combobox-content-open { @apply opacity-100 visible scale-100; }
292
425
  .combobox-search { @apply flex h-9 w-full border-b border-border bg-transparent px-3 text-sm outline-none placeholder:text-muted-foreground; }
293
426
  .combobox-list { @apply flex flex-col p-1 overflow-auto max-h-60; }
@@ -302,12 +435,14 @@
302
435
  .command-shortcut { @apply ml-auto text-xs tracking-widest text-muted-foreground; }
303
436
 
304
437
  /* ---- Table - style muten's DataTable to look shadcn: DataTable @rows columns(...) class("data-table") ---- */
305
- .data-table { @apply w-full caption-bottom text-sm; }
306
- .data-table thead tr { @apply border-b; }
307
- .data-table th { @apply h-11 px-3 text-left align-middle font-medium text-muted-foreground whitespace-nowrap; }
308
- .data-table tbody tr { @apply border-b transition-colors duration-200 hover:bg-muted/50; }
309
- .data-table tbody tr:last-child { @apply border-0; }
310
- .data-table td { @apply p-3 align-middle whitespace-nowrap; }
438
+ /* .mu-datatable is the base class the DataTable primitive always gets, so a bare `DataTable @rows columns(...)`
439
+ is styled out of the box (no class needed). Safe: DataTable is a leaf, never reused with another class. */
440
+ .data-table, .mu-datatable { @apply w-full caption-bottom text-sm; }
441
+ .data-table thead tr, .mu-datatable thead tr { @apply border-b; }
442
+ .data-table th, .mu-datatable th { @apply h-11 px-3 text-left align-middle font-medium text-muted-foreground whitespace-nowrap; }
443
+ .data-table tbody tr, .mu-datatable tbody tr { @apply border-b transition-colors duration-200 hover:bg-muted/50; }
444
+ .data-table tbody tr:last-child, .mu-datatable tbody tr:last-child { @apply border-0; }
445
+ .data-table td, .mu-datatable td { @apply p-3 align-middle whitespace-nowrap; }
311
446
 
312
447
  /* ---- Slider (Custom; the host JS builds .slider-range + .slider-thumb inside .slider) ---- */
313
448
  .slider { @apply relative h-2 w-full rounded-full bg-primary/20 cursor-pointer select-none; touch-action: none; }
@@ -345,7 +480,7 @@
345
480
  /* ---- Carousel (Custom; host renders slides from a list + prev/next) ---- */
346
481
  .carousel { @apply relative w-full max-w-xs; }
347
482
  .carousel-viewport { @apply overflow-hidden rounded-lg; }
348
- .carousel-track { @apply flex flex-row; transition: transform 300ms ease; }
483
+ .carousel-track { @apply flex flex-row; transition: translate 260ms var(--mu-ease); }
349
484
  .carousel-slide { @apply flex min-w-full aspect-square items-center justify-center rounded-lg border bg-card text-4xl font-semibold select-none; }
350
485
  .carousel-prev { @apply absolute top-1/2 -left-4 -translate-y-1/2 inline-flex size-8 items-center justify-center rounded-full border border-input bg-background shadow outline-none transition-colors duration-200 hover:bg-accent; }
351
486
  .carousel-next { @apply absolute top-1/2 -right-4 -translate-y-1/2 inline-flex size-8 items-center justify-center rounded-full border border-input bg-background shadow outline-none transition-colors duration-200 hover:bg-accent; }
@@ -375,11 +510,17 @@
375
510
  .number-field-input { @apply h-full w-14 min-w-0 border-0 bg-transparent text-center text-sm outline-none; -moz-appearance: textfield; }
376
511
  .number-field-input::-webkit-inner-spin-button, .number-field-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
377
512
 
378
- /* ---- Segmented Control (single-select toolbar; like Tabs but for a toolbar) ---- */
513
+ /* ---- Segmented Control (single-select toolbar; like Tabs but for a toolbar). `.segmented-fill` makes the
514
+ items stretch full-width (a top-level tab bar: Forms · Scheduling · PDF …). Use IconTab for icon+label. ---- */
379
515
  .segmented { @apply inline-flex flex-row items-center gap-1 rounded-lg bg-muted p-1 text-muted-foreground; }
380
- .segmented-item { @apply inline-flex items-center justify-center gap-1.5 rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap outline-none transition-all duration-200 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50; }
516
+ .segmented-fill { @apply flex w-full; }
517
+ .segmented-fill .segmented-item { @apply flex-1; }
518
+ .segmented-item { @apply inline-flex items-center justify-center gap-1.5 rounded-md px-3 h-8 text-sm font-medium leading-none whitespace-nowrap outline-none transition-all duration-200 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50; }
381
519
  .segmented-item-active { @apply bg-background text-foreground shadow-sm; }
382
520
 
521
+ /* ---- Pill (a hero eyebrow: rounded-full chip with an icon + text in the slot). Bigger than a Badge. ---- */
522
+ .pill { @apply inline-flex flex-row items-center gap-1.5 rounded-full border px-4 py-1.5 text-sm font-medium leading-none w-fit whitespace-nowrap transition-colors; }
523
+
383
524
  /* ---- File Input / Dropzone (Custom; click or drag-drop) ---- */
384
525
  .dropzone { @apply flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed border-input p-8 text-center text-sm text-muted-foreground cursor-pointer transition-colors duration-200 hover:border-ring hover:bg-accent/30; }
385
526
  .dropzone-over { @apply border-ring bg-accent/50; }
@@ -389,7 +530,7 @@
389
530
  .color-picker { @apply relative inline-flex; }
390
531
  .color-swatch { @apply inline-flex h-9 items-center gap-2 rounded-md border border-input bg-transparent px-2 text-sm shadow-sm outline-none transition-colors duration-200 hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50; }
391
532
  .color-swatch-dot { @apply size-5 rounded-sm border border-border shrink-0; }
392
- .color-popover { @apply absolute top-full left-0 z-50 mt-1 w-56 rounded-md border bg-popover p-3 text-popover-foreground shadow-md flex flex-col gap-3 origin-top opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
533
+ .color-popover { @apply absolute top-full left-0 z-50 mt-1 w-56 rounded-md border bg-popover p-3 text-popover-foreground shadow-md flex flex-col gap-3 origin-top opacity-0 invisible scale-95; transition: opacity var(--mu-motion) var(--mu-ease), scale var(--mu-motion) var(--mu-ease), translate var(--mu-motion) var(--mu-ease), visibility var(--mu-motion) var(--mu-ease); }
393
534
  .color-popover-open { @apply opacity-100 visible scale-100; }
394
535
  .color-sv { position: relative; width: 100%; height: 8rem; border-radius: calc(var(--radius) - 2px); cursor: crosshair; }
395
536
  .color-sv-cursor, .color-hue-cursor { position: absolute; border: 2px solid #fff; border-radius: 9999px; transform: translate(-50%, -50%); box-shadow: 0 0 0 1px rgba(0,0,0,.4); pointer-events: none; }
@@ -473,6 +614,13 @@
473
614
  .sidebar-group-label { @apply px-2 py-1 text-xs font-medium text-muted-foreground; }
474
615
  .sidebar-item { @apply flex flex-row items-center gap-2 rounded-md px-2 py-1.5 text-sm whitespace-nowrap outline-none transition-colors duration-200 hover:bg-accent; }
475
616
  .sidebar-item-active { @apply bg-accent text-accent-foreground font-medium; }
617
+ /* collapsed rail: hide every label + the header wordmark, and center what remains, so the sidebar becomes a
618
+ clean icon strip instead of clipping text. The `> :not(.mu-icon)` keeps the header's icon, drops its label. */
619
+ .sidebar-collapsed .sidebar-item-label,
620
+ .sidebar-collapsed .sidebar-group-label,
621
+ .sidebar-collapsed .sidebar-header > :not(.mu-icon) { display: none; }
622
+ .sidebar-collapsed .sidebar-item,
623
+ .sidebar-collapsed .sidebar-header { @apply justify-center px-0; }
476
624
 
477
625
  /* ---- Sortable List (Custom; drag rows to reorder, e.g. layers) ---- */
478
626
  .sortable-list { @apply flex flex-col gap-1; }
@@ -516,8 +664,165 @@
516
664
  .btn-group > *:first-child { @apply rounded-l-md; }
517
665
  .btn-group > *:last-child { @apply rounded-r-md; }
518
666
  .btn-group > * + * { @apply -ml-px; }
519
- .input-group { @apply flex flex-row h-9 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50; }
667
+ .input-group { @apply flex flex-row h-8 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50; }
520
668
  .input-group-control { @apply h-full flex-1 border-0 bg-transparent p-0 text-sm shadow-none outline-none placeholder:text-muted-foreground; }
669
+
670
+ /* ---- Stepper (horizontal by default; add .stepper-vertical). Put a state on each .stepper-step:
671
+ -done (completed, filled) / -active (current, ringed) / bare (upcoming). Compose with `each` + the Step part. ---- */
672
+ .stepper { @apply flex flex-row items-start w-full; }
673
+ .stepper-step { @apply relative flex flex-1 flex-col items-center text-center min-w-0; }
674
+ .stepper-step:not(:last-child)::after { content: ""; @apply absolute top-4 left-1/2 h-0.5 w-full -translate-y-1/2 bg-border transition-colors duration-300; }
675
+ .stepper-step-done::after { @apply bg-primary; }
676
+ .stepper-indicator { @apply relative z-10 flex size-8 shrink-0 items-center justify-center rounded-full border bg-background text-sm font-medium text-muted-foreground transition-all duration-300; }
677
+ .stepper-step-active .stepper-indicator { @apply border-primary text-foreground ring-4 ring-ring/20; }
678
+ .stepper-step-done .stepper-indicator { @apply border-primary bg-primary text-primary-foreground; }
679
+ .stepper-step-done .stepper-num { @apply hidden; }
680
+ .stepper-label { @apply mt-2 px-2 text-xs font-medium text-muted-foreground transition-colors; }
681
+ .stepper-step-active .stepper-label, .stepper-step-done .stepper-label { @apply text-foreground; }
682
+ /* vertical variant */
683
+ .stepper-vertical { @apply flex-col items-stretch; }
684
+ .stepper-vertical .stepper-step { @apply flex-row items-start gap-3 flex-none pb-8 text-left; }
685
+ .stepper-vertical .stepper-step:last-child { @apply pb-0; }
686
+ .stepper-vertical .stepper-step:not(:last-child)::after { @apply top-8 left-4 h-full w-0.5 translate-y-0 -translate-x-1/2; }
687
+ .stepper-vertical .stepper-label { @apply mt-1 px-0; }
688
+
689
+ /* ---- Timeline (vertical event list: dot + connector + content). Compose with `each` + the TimelineItem part. ---- */
690
+ .timeline { @apply flex flex-col; }
691
+ .timeline-item { @apply relative flex flex-row gap-4 pb-6; }
692
+ .timeline-item:last-child { @apply pb-0; }
693
+ .timeline-item:not(:last-child)::before { content: ""; @apply absolute left-[0.4375rem] top-4 bottom-0 w-0.5 bg-border; }
694
+ .timeline-dot { @apply relative z-10 mt-1 size-3.5 shrink-0 rounded-full border-2 border-primary bg-background; }
695
+ .timeline-dot-muted { @apply border-muted-foreground; }
696
+ .timeline-content { @apply flex flex-col gap-1 pb-1; }
697
+ .timeline-title { @apply text-sm font-medium leading-none; }
698
+ .timeline-time { @apply text-xs text-muted-foreground; }
699
+ .timeline-desc { @apply text-sm text-muted-foreground; }
700
+
701
+ /* ═══════════════ BLOCKS - section-level patterns (a landing = compose these) ═══════════════ */
702
+ /* ---- FeatureGrid: a responsive grid of feature cards (icon + title + copy) ---- */
703
+ .feature-grid { @apply grid grid-cols-3 gap-4 w-full max-lg:grid-cols-2 max-sm:grid-cols-1; }
704
+ .feature-card { @apply flex flex-col gap-3 rounded-xl border bg-card p-6 transition-shadow hover:shadow-md; }
705
+ .feature-card-icon { @apply flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary; }
706
+ .feature-card-title { @apply text-base font-semibold leading-none; }
707
+ .feature-card-desc { @apply text-sm text-muted-foreground leading-relaxed; }
708
+
709
+ /* ---- Pricing: a row of tiers (name · price · features · CTA). `.pricing-tier-featured` highlights one. ---- */
710
+ .pricing-grid { @apply grid grid-cols-3 gap-4 w-full items-start max-lg:grid-cols-2 max-sm:grid-cols-1; }
711
+ .pricing-tier { @apply flex flex-col gap-5 rounded-2xl border bg-card p-6; }
712
+ .pricing-tier-featured { @apply border-primary ring-1 ring-primary shadow-lg; }
713
+ .pricing-name { @apply text-base font-semibold; }
714
+ .pricing-desc { @apply text-sm text-muted-foreground; }
715
+ .pricing-price { @apply text-4xl font-bold tracking-tight; }
716
+ .pricing-period { @apply text-sm text-muted-foreground; }
717
+ .pricing-features { @apply flex flex-col gap-2.5 flex-1; }
718
+ .pricing-feature { @apply flex flex-row items-center gap-2 text-sm; }
719
+
720
+ /* ---- StatBand: a divided row of KPI stats (value + label), uniform, 2-up on mobile ---- */
721
+ .stat-band { @apply grid grid-cols-4 gap-px border-y border-border bg-border overflow-hidden max-sm:grid-cols-2; }
722
+ .stat { @apply flex flex-col items-center gap-1 bg-background px-6 py-10 text-center; }
723
+ .stat-value { @apply text-4xl font-bold tracking-tight; }
724
+ .stat-label { @apply text-sm text-muted-foreground; }
725
+
726
+ /* ---- LogoCloud: the "trusted by" strip - a wrapping row of logos, muted until hover. GENERIC: an item holds
727
+ ANY logo type - an <img>, an inline SVG/Icon, or a text wordmark - and each is normalized to the same height. ---- */
728
+ .logo-cloud { @apply flex flex-row flex-wrap items-center justify-center gap-x-10 gap-y-6; }
729
+ .logo-cloud-item { @apply flex flex-row items-center justify-center gap-2 h-8 leading-none text-muted-foreground opacity-70 grayscale transition duration-300 hover:opacity-100 hover:grayscale-0 [&>img]:h-7 [&>img]:w-auto [&>svg]:h-7 [&>svg]:w-auto [&_.mu-icon]:text-2xl; }
730
+
731
+ /* ---- NavPill: the floating glass navbar (the most-used modern header). Wrap it in a sticky, padded container:
732
+ Stack class("sticky top-0 z-50 px-4 pt-4") { Header class("nav-pill max-w-5xl mx-auto w-full") { ... } } ---- */
733
+ .nav-pill { @apply flex flex-row items-center justify-between gap-4 rounded-full border border-border/60 bg-background/70 px-2 py-2 pl-5 shadow-lg backdrop-blur-lg; }
734
+
735
+ /* ---- FeatureRow: a numbered feature row (index · title · copy) with its own gutters, so `01` never kisses the
736
+ edge. Compose with a divider wrapper: Stack class("feature-rows") { FeatureRow-like Stacks }. ---- */
737
+ .feature-rows { @apply flex flex-col gap-px overflow-hidden rounded-xl border bg-border; }
738
+ .feature-row { @apply grid grid-cols-12 gap-6 bg-card px-6 sm:px-8 py-10 max-md:grid-cols-1 max-md:gap-3; }
739
+
740
+ /* ---- BentoGallery: a tiling gallery on a fixed row-height grid, so cells of different column widths always
741
+ line up (no ragged rows). Children pick their own column span; the fixed auto-rows keep every cell uniform. ---- */
742
+ .bento-gallery { @apply grid grid-cols-6 gap-4 max-sm:grid-cols-2; grid-auto-rows: 240px; }
743
+ .bento-cell { @apply relative overflow-hidden rounded-xl; }
744
+
745
+ /* ---- Arrow nudge: drop `arrow-hover` on any Button/Link with a trailing Icon - the last icon slides on hover
746
+ (the classic "call to action" micro-interaction). Respects reduced motion via the global rule below. ---- */
747
+ .arrow-hover .mu-icon { @apply transition-transform duration-200; }
748
+ .arrow-hover:hover .mu-icon:last-child { translate: 3px 0; }
521
749
  }
522
750
 
523
751
  @keyframes progress-indeterminate { 0% { left: -40%; } 100% { left: 100%; } }
752
+ @keyframes mu-tab-in { from { opacity: 0; translate: 0 6px; } to { opacity: 1; translate: 0 0; } }
753
+
754
+ /* ═══════════════ Motion - page animations the library ships (drop on any node via class) ═══════════════
755
+ Two kinds: (1) ENTRANCE (`animate-*`) plays once on mount - for hero/above-the-fold; add d1..d6 to stagger.
756
+ (2) SCROLL-REVEAL (`reveal`, `reveal-left`) fades a node up as it scrolls into view, using pure-CSS scroll-driven
757
+ animations (no JS, no IntersectionObserver). Where the browser lacks support, or the user prefers reduced
758
+ motion, every one degrades to "just visible" - never a hidden node. */
759
+ @keyframes mu-fade-up { from { opacity: 0; translate: 0 1.5rem; } to { opacity: 1; translate: 0 0; } }
760
+ @keyframes mu-fade-in { from { opacity: 0; } to { opacity: 1; } }
761
+ @keyframes mu-scale-in { from { opacity: 0; scale: 0.94; } to { opacity: 1; scale: 1; } }
762
+ @keyframes mu-slide-in { from { opacity: 0; translate: -1.5rem 0; } to { opacity: 1; translate: 0 0; } }
763
+ @keyframes mu-reveal-scale{ from { opacity: 0; scale: 0.92; translate: 0 1rem; } to { opacity: 1; scale: 1; translate: 0 0; } }
764
+ /* nav pill: airy + shadowless at the top, settles + deepens its shadow once you scroll, and reverses smoothly
765
+ when you return to the top (scroll-position driven, so "back to top" is free). */
766
+ @keyframes mu-nav-scroll { from { scale: 1.015; box-shadow: 0 4px 12px -6px rgb(0 0 0 / 0.10); } to { scale: 0.98; box-shadow: 0 16px 36px -10px rgb(0 0 0 / 0.30); } }
767
+ @keyframes mu-float { 0%, 100% { translate: 0 0; } 50% { translate: 0 -6px; } }
768
+
769
+ @layer utilities {
770
+ .animate-fade-up { animation: mu-fade-up 0.6s var(--mu-ease) both; }
771
+ .animate-fade-in { animation: mu-fade-in 0.6s var(--mu-ease) both; }
772
+ .animate-scale-in { animation: mu-scale-in 0.5s var(--mu-ease) both; }
773
+ .animate-slide-in { animation: mu-slide-in 0.6s var(--mu-ease) both; }
774
+ /* stagger delays (safe names - Tailwind's delay-* is transition-delay, these are animation-delay) */
775
+ .d1 { animation-delay: 60ms; } .d2 { animation-delay: 120ms; } .d3 { animation-delay: 180ms; }
776
+ .d4 { animation-delay: 240ms; } .d5 { animation-delay: 300ms; } .d6 { animation-delay: 360ms; }
777
+
778
+ /* hover-lift - a card floats up + deepens its shadow on hover (the classic interactive-card feel) */
779
+ .hover-lift { transition: translate 300ms var(--mu-ease), box-shadow 300ms var(--mu-ease); }
780
+ .hover-lift:hover { @apply -translate-y-1 shadow-xl; }
781
+
782
+ /* ── Micro-interactions (drop the class on any node) ── */
783
+ /* link-underline - an underline that grows from the left on hover; perfect for nav links */
784
+ .link-underline { position: relative; }
785
+ .link-underline::after { content: ""; position: absolute; left: 0; bottom: -3px; height: 1.5px; width: 100%; background: currentColor; transform-origin: left; scale: 0 1; transition: scale 250ms var(--mu-ease); }
786
+ .link-underline:hover::after { scale: 1 1; }
787
+ /* hover-glow - a soft, brand-tinted glow on hover; for CTAs and feature cards */
788
+ .hover-glow { transition: box-shadow 300ms var(--mu-ease); }
789
+ .hover-glow:hover { box-shadow: 0 10px 30px -6px color-mix(in srgb, var(--color-primary) 45%, transparent); }
790
+ /* press - a tactile scale-down while the pointer is held; for any clickable */
791
+ .press { transition: scale 120ms var(--mu-ease); }
792
+ .press:active { scale: 0.96; }
793
+ /* float - a gentle, endless bob; for a hero badge or accent icon */
794
+ .float { animation: mu-float 3.5s ease-in-out infinite; }
795
+
796
+ /* scroll-reveal - scrubs an entrance as the element enters the viewport (pure CSS, no JS) */
797
+ @supports (animation-timeline: view()) {
798
+ .reveal { opacity: 0; animation: mu-fade-up 1s linear both; animation-timeline: view(); animation-range: entry 5% cover 32%; }
799
+ .reveal-left { opacity: 0; animation: mu-slide-in 1s linear both; animation-timeline: view(); animation-range: entry 5% cover 32%; }
800
+ .reveal-scale { opacity: 0; animation: mu-reveal-scale 1s linear both; animation-timeline: view(); animation-range: entry 5% cover 30%; }
801
+ }
802
+ /* nav-scroll - the navbar animates with document scroll and returns to rest at the top (pure CSS) */
803
+ @supports (animation-timeline: scroll()) {
804
+ .nav-scroll { animation: mu-nav-scroll linear both; animation-timeline: scroll(root); animation-range: 0 140px; }
805
+ }
806
+ }
807
+
808
+ @media (prefers-reduced-motion: reduce) {
809
+ .animate-fade-up, .animate-fade-in, .animate-scale-in, .animate-slide-in, .reveal, .reveal-left, .reveal-scale, .nav-scroll, .float {
810
+ animation: none !important; opacity: 1 !important; translate: none !important; scale: none !important;
811
+ }
812
+ .arrow-hover:hover .mu-icon:last-child, .hover-lift:hover { translate: none !important; }
813
+ .press:active { scale: none !important; }
814
+ }
815
+
816
+ /* ---- Code highlight tokens (the Code component + Markdown docs) - muten grammar colors, theme-friendly ---- */
817
+ .tk-comment { color: color-mix(in srgb, var(--color-muted-foreground) 90%, transparent); font-style: italic; }
818
+ .tk-string { color: #9ece6a; }
819
+ .tk-keyword { color: #7aa2f7; font-weight: 500; }
820
+ .tk-type { color: #7dcfff; }
821
+ .tk-primitive { color: #bb9af7; }
822
+ .tk-modifier { color: #f7768e; }
823
+ .tk-method { color: #7dcfff; }
824
+ .tk-component { color: #e0af68; }
825
+ .tk-constant { color: #ff9e64; }
826
+ .tk-number { color: #ff9e64; }
827
+ .tk-operator { color: var(--color-muted-foreground); }
828
+ .tk-variable { color: var(--color-foreground); }