@heyramzi/ui 1.53.1 → 1.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +6 -0
- package/package.json +2 -2
- package/src/agents/agent-avatar.svelte +3 -2
- package/src/agents/helpers.ts +9 -0
- package/src/components/data-table/sortable-head.svelte +1 -1
- package/src/components/docs/doc-page.svelte +161 -0
- package/src/components/docs/index.ts +12 -0
- package/src/components/docs/on-this-page.svelte +217 -0
- package/src/components/docs/scroll-spy.svelte.ts +97 -0
- package/src/components/field-row/field-row.svelte +57 -0
- package/src/components/field-row/index.ts +1 -0
- package/src/components/list-page/toolbar-search.svelte +1 -1
- package/src/components/list-page/toolbar-trigger.ts +1 -1
- package/src/components/picker/trigger.ts +2 -2
- package/src/components/section-card/index.ts +1 -0
- package/src/components/section-card/section-card.svelte +43 -0
- package/src/components/section-header/index.ts +1 -0
- package/src/components/section-header/section-header.svelte +54 -0
- package/src/components/section-header/section-header.test.ts +25 -0
- package/src/components/segmented/collapse.test.ts +10 -0
- package/src/components/segmented/collapse.ts +5 -0
- package/src/components/segmented/segmented-filter.svelte +5 -4
- package/src/components/segmented/segmented-track.svelte +20 -0
- package/src/components/segmented/segmented.test.ts +1 -1
- package/src/components/segmented/segmented.ts +2 -2
- package/src/components/table-view/index.ts +3 -0
- package/src/components/table-view/table-view.svelte +103 -0
- package/src/components/table-view/table-view.test.ts +53 -0
- package/src/components/table-view/types.ts +14 -0
- package/src/components/theme/index.ts +17 -0
- package/src/components/theme/theme-provider.svelte +47 -0
- package/src/components/theme/theme-provider.test.ts +61 -0
- package/src/effects/spotlight/Spotlight.svelte +0 -1
- package/src/lib/tone.test.ts +10 -0
- package/src/lib/tone.ts +1 -1
- package/src/shadcn/badge/badge.svelte +1 -1
- package/src/shadcn/button/button.svelte +10 -1
- package/src/shadcn/button/button.test.ts +19 -0
- package/src/shadcn/checkbox/checkbox.svelte +1 -1
- package/src/shadcn/context-menu/context-menu-separator.svelte +4 -1
- package/src/shadcn/dropdown-menu/dropdown-menu-separator.svelte +4 -1
- package/src/shadcn/input/input.svelte +2 -2
- package/src/shadcn/progress/progress.svelte +8 -2
- package/src/shadcn/progress/tone.test.ts +13 -0
- package/src/shadcn/progress/tone.ts +15 -0
- package/src/shadcn/sidebar/sidebar-group-label.svelte +1 -1
- package/src/shadcn/table/table-cell.svelte +21 -1
- package/src/shadcn/table/table-cell.test.ts +16 -0
- package/src/tokens/base.css +183 -0
- package/src/tokens/craft.css +179 -0
- package/src/tokens/index.css +19 -0
- package/src/tokens/palette.css +410 -0
- package/src/tokens/primitives.css +223 -0
- package/scripts/relative-imports.mjs +0 -39
- package/src/tokens.css +0 -993
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
2
|
+
|
|
3
|
+
/* Font families */
|
|
4
|
+
@theme {
|
|
5
|
+
--font-sans: var(--font-satoshi), system-ui, sans-serif;
|
|
6
|
+
--font-heading: var(--font-manrope), system-ui, sans-serif;
|
|
7
|
+
--font-mono: var(--font-space-grotesk), ui-monospace, monospace;
|
|
8
|
+
--font-quote: var(--font-space-grotesk), ui-monospace, monospace;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* OKLCH Color Variables - Single source of truth for all colors */
|
|
12
|
+
/* Palette: indigo, sand and terra, with a neutral gray scale. */
|
|
13
|
+
:root {
|
|
14
|
+
color-scheme: light;
|
|
15
|
+
/* === Font Variables === */
|
|
16
|
+
/* Each consuming app defines @font-face and sets these to the actual font family names */
|
|
17
|
+
--font-satoshi: "Satoshi", system-ui, sans-serif;
|
|
18
|
+
--font-manrope: "Manrope", system-ui, sans-serif;
|
|
19
|
+
--font-space-grotesk: "Space Grotesk", ui-monospace, monospace;
|
|
20
|
+
|
|
21
|
+
/* === Core Gray Scale (7 shades, zero-chroma neutral) === */
|
|
22
|
+
--gray-50: oklch(0.98 0 0); /* Almost white — backgrounds */
|
|
23
|
+
--gray-100: oklch(0.94 0 0); /* Very light gray — subtle backgrounds */
|
|
24
|
+
--gray-200: oklch(0.92 0 0); /* Light gray — borders */
|
|
25
|
+
--gray-300: oklch(0.84 0 0); /* Muted — disabled, placeholder text */
|
|
26
|
+
--gray-500: oklch(0.65 0 0); /* Medium gray — secondary text */
|
|
27
|
+
--gray-700: oklch(0.45 0 0); /* Dark gray — body text */
|
|
28
|
+
--gray-900: oklch(0.15 0 0); /* Near-black — headings, primary text */
|
|
29
|
+
|
|
30
|
+
/* === Accent Colors (3 chromatic) — 60/30/10 split === */
|
|
31
|
+
--brand-indigo: oklch(0.5 0.2 272); /* Primary 60% — CTAs, links, active states, brand mark */
|
|
32
|
+
--brand-indigo-light: oklch(0.68 0.16 272); /* Dark mode variant */
|
|
33
|
+
--brand-sand: oklch(0.78 0.08 40); /* Warmth 30% — highlights, badges, soft accents */
|
|
34
|
+
--brand-terra: oklch(0.65 0.2 30); /* Signal 10% — warnings, attention, energy */
|
|
35
|
+
|
|
36
|
+
/* === Surface System (6 named colors) === */
|
|
37
|
+
/* Dark surfaces (indigo-tinted, h258-264) Light pendants (warm-tinted, h55) */
|
|
38
|
+
--brand-ink: oklch(0.12 0.04 258); /* Base — page background */
|
|
39
|
+
--brand-night: oklch(0.18 0.05 262); /* Surface — cards, panels ↔ Linen */
|
|
40
|
+
--brand-veil: oklch(0.28 0.06 264); /* Borders, dividers ↔ Mist */
|
|
41
|
+
--brand-dusk: oklch(0.22 0.05 262); /* Between night/veil — hover surfaces, inputs */
|
|
42
|
+
--brand-cream: oklch(0.97 0.01 55); /* On-dark text, light backgrounds */
|
|
43
|
+
--brand-linen: oklch(0.985 0.003 55); /* Light surface — cards ↔ Night */
|
|
44
|
+
--brand-mist: oklch(0.9 0.006 55); /* Light borders, dividers ↔ Veil */
|
|
45
|
+
|
|
46
|
+
/* === Indigo Tint Scale (5-step, core = 600) === */
|
|
47
|
+
--indigo-100: oklch(0.92 0.04 272);
|
|
48
|
+
--indigo-200: oklch(0.82 0.08 272);
|
|
49
|
+
--indigo-400: oklch(0.68 0.16 272);
|
|
50
|
+
--indigo-600: oklch(0.5 0.2 272); /* = --brand-indigo */
|
|
51
|
+
--indigo-800: oklch(0.34 0.18 272);
|
|
52
|
+
|
|
53
|
+
/* === Sand Tint Scale (5-step, core = 400) === */
|
|
54
|
+
--sand-100: oklch(0.94 0.02 40);
|
|
55
|
+
--sand-200: oklch(0.88 0.04 40);
|
|
56
|
+
--sand-400: oklch(0.78 0.08 40); /* = --brand-sand */
|
|
57
|
+
--sand-600: oklch(0.64 0.1 40);
|
|
58
|
+
--sand-800: oklch(0.5 0.08 40);
|
|
59
|
+
|
|
60
|
+
/* === Terra Tint Scale (5-step, core = 400) === */
|
|
61
|
+
--terra-100: oklch(0.92 0.05 30);
|
|
62
|
+
--terra-200: oklch(0.82 0.1 30);
|
|
63
|
+
--terra-400: oklch(0.65 0.2 30); /* = --brand-terra */
|
|
64
|
+
--terra-600: oklch(0.52 0.2 30);
|
|
65
|
+
--terra-800: oklch(0.38 0.15 30);
|
|
66
|
+
|
|
67
|
+
/* === Status Tint Scales (5-step, mirror indigo/sand/terra) === */
|
|
68
|
+
/* WHY: status badges use bg-X-100 text-X-600 dark:bg-X-800/30 dark:text-X-400 */
|
|
69
|
+
/* Success (green) — positive states */
|
|
70
|
+
--success-100: oklch(0.94 0.05 150);
|
|
71
|
+
--success-200: oklch(0.88 0.09 148);
|
|
72
|
+
--success-400: oklch(0.75 0.15 146);
|
|
73
|
+
--success-600: oklch(0.62 0.17 145); /* = --success */
|
|
74
|
+
--success-800: oklch(0.45 0.13 148);
|
|
75
|
+
/* Warning (amber) — attention, caution */
|
|
76
|
+
--warning-100: oklch(0.95 0.05 85);
|
|
77
|
+
--warning-200: oklch(0.9 0.09 82);
|
|
78
|
+
--warning-400: oklch(0.82 0.13 78);
|
|
79
|
+
--warning-600: oklch(0.68 0.14 70);
|
|
80
|
+
--warning-800: oklch(0.5 0.11 65);
|
|
81
|
+
/* Info (blue) — informational */
|
|
82
|
+
--info-100: oklch(0.93 0.04 250);
|
|
83
|
+
--info-200: oklch(0.85 0.08 250);
|
|
84
|
+
--info-400: oklch(0.72 0.14 250);
|
|
85
|
+
--info-600: oklch(0.6 0.16 252);
|
|
86
|
+
--info-800: oklch(0.45 0.15 255);
|
|
87
|
+
|
|
88
|
+
/* === Categorical Palette (diagram groups, tags, chart series) === */
|
|
89
|
+
/* WHY: identity colors for grouping/visual distinction, not semantic state. */
|
|
90
|
+
--cat-blue: oklch(0.62 0.18 250);
|
|
91
|
+
--cat-green: oklch(0.65 0.17 150);
|
|
92
|
+
--cat-amber: oklch(0.75 0.14 75);
|
|
93
|
+
--cat-pink: oklch(0.65 0.21 350);
|
|
94
|
+
--cat-teal: oklch(0.68 0.12 195);
|
|
95
|
+
--cat-orange: oklch(0.7 0.19 45);
|
|
96
|
+
--cat-rose: oklch(0.64 0.22 15);
|
|
97
|
+
--cat-cyan: oklch(0.7 0.12 220);
|
|
98
|
+
--cat-yellow: oklch(0.85 0.16 95);
|
|
99
|
+
--cat-indigo: oklch(0.58 0.2 272);
|
|
100
|
+
--cat-purple: oklch(0.58 0.22 305);
|
|
101
|
+
|
|
102
|
+
/* === Semantic Colors === */
|
|
103
|
+
--success: oklch(0.62 0.17 145); /* Green — positive states */
|
|
104
|
+
--error: oklch(0.6 0.2 25); /* Terra-dark — destructive actions */
|
|
105
|
+
--warning: oklch(0.78 0.14 75); /* Amber — warnings, attention */
|
|
106
|
+
--info: oklch(0.68 0.16 250); /* Blue — informational */
|
|
107
|
+
|
|
108
|
+
/* === Pure Colors === */
|
|
109
|
+
--white: oklch(1 0 0);
|
|
110
|
+
--black: oklch(0 0 0);
|
|
111
|
+
|
|
112
|
+
/* === Sidebar Colors === */
|
|
113
|
+
/* Sidebar is one shade darker than content — Linear-style depth */
|
|
114
|
+
--sidebar: oklch(0.96 0 0);
|
|
115
|
+
--sidebar-foreground: oklch(0.45 0 0);
|
|
116
|
+
--sidebar-primary: oklch(0.15 0 0);
|
|
117
|
+
--sidebar-primary-foreground: oklch(0.98 0 0);
|
|
118
|
+
--sidebar-accent: oklch(0.92 0 0);
|
|
119
|
+
--sidebar-accent-foreground: oklch(0.15 0 0);
|
|
120
|
+
--sidebar-border: oklch(0.91 0 0);
|
|
121
|
+
--sidebar-ring: var(--brand-indigo);
|
|
122
|
+
|
|
123
|
+
/* === Shadow tint ===
|
|
124
|
+
The one knob the design layer exposes. Pure black greys out warm surfaces,
|
|
125
|
+
so the shadow takes the darkest surface's hue. Radius, focus and motion live there. */
|
|
126
|
+
--elev-tint: oklch(0.12 0.04 258); /* = --brand-ink */
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@theme {
|
|
130
|
+
/* Gray scale utilities (bg-gray-100, border-gray-200, text-gray-300, etc.) */
|
|
131
|
+
--color-gray-50: var(--gray-50);
|
|
132
|
+
--color-gray-100: var(--gray-100);
|
|
133
|
+
--color-gray-200: var(--gray-200);
|
|
134
|
+
--color-gray-300: var(--gray-300);
|
|
135
|
+
--color-gray-500: var(--gray-500);
|
|
136
|
+
--color-gray-700: var(--gray-700);
|
|
137
|
+
--color-gray-900: var(--gray-900);
|
|
138
|
+
|
|
139
|
+
/* Brand colors (V6 names) */
|
|
140
|
+
--color-brand-indigo: var(--brand-indigo);
|
|
141
|
+
--color-brand-indigo-light: var(--brand-indigo-light);
|
|
142
|
+
--color-brand-sand: var(--brand-sand);
|
|
143
|
+
--color-brand-terra: var(--brand-terra);
|
|
144
|
+
/* Surface system */
|
|
145
|
+
--color-brand-ink: var(--brand-ink);
|
|
146
|
+
--color-brand-night: var(--brand-night);
|
|
147
|
+
--color-brand-veil: var(--brand-veil);
|
|
148
|
+
--color-brand-dusk: var(--brand-dusk);
|
|
149
|
+
--color-brand-cream: var(--brand-cream);
|
|
150
|
+
--color-brand-linen: var(--brand-linen);
|
|
151
|
+
--color-brand-mist: var(--brand-mist);
|
|
152
|
+
|
|
153
|
+
/* Tint scales */
|
|
154
|
+
--color-indigo-100: var(--indigo-100);
|
|
155
|
+
--color-indigo-200: var(--indigo-200);
|
|
156
|
+
--color-indigo-400: var(--indigo-400);
|
|
157
|
+
--color-indigo-600: var(--indigo-600);
|
|
158
|
+
--color-indigo-800: var(--indigo-800);
|
|
159
|
+
--color-sand-100: var(--sand-100);
|
|
160
|
+
--color-sand-200: var(--sand-200);
|
|
161
|
+
--color-sand-400: var(--sand-400);
|
|
162
|
+
--color-sand-600: var(--sand-600);
|
|
163
|
+
--color-sand-800: var(--sand-800);
|
|
164
|
+
--color-terra-100: var(--terra-100);
|
|
165
|
+
--color-terra-200: var(--terra-200);
|
|
166
|
+
--color-terra-400: var(--terra-400);
|
|
167
|
+
--color-terra-600: var(--terra-600);
|
|
168
|
+
--color-terra-800: var(--terra-800);
|
|
169
|
+
|
|
170
|
+
/* Semantic colors */
|
|
171
|
+
--color-success: var(--success);
|
|
172
|
+
--color-error: var(--error);
|
|
173
|
+
--color-warning: var(--warning);
|
|
174
|
+
--color-info: var(--info);
|
|
175
|
+
|
|
176
|
+
/* Status tint scales (bg-success-100, text-warning-600, etc.) */
|
|
177
|
+
--color-success-100: var(--success-100);
|
|
178
|
+
--color-success-200: var(--success-200);
|
|
179
|
+
--color-success-400: var(--success-400);
|
|
180
|
+
--color-success-600: var(--success-600);
|
|
181
|
+
--color-success-800: var(--success-800);
|
|
182
|
+
--color-warning-100: var(--warning-100);
|
|
183
|
+
--color-warning-200: var(--warning-200);
|
|
184
|
+
--color-warning-400: var(--warning-400);
|
|
185
|
+
--color-warning-600: var(--warning-600);
|
|
186
|
+
--color-warning-800: var(--warning-800);
|
|
187
|
+
--color-info-100: var(--info-100);
|
|
188
|
+
--color-info-200: var(--info-200);
|
|
189
|
+
--color-info-400: var(--info-400);
|
|
190
|
+
--color-info-600: var(--info-600);
|
|
191
|
+
--color-info-800: var(--info-800);
|
|
192
|
+
|
|
193
|
+
/* Categorical palette (diagram groups, tags, chart series) */
|
|
194
|
+
--color-cat-blue: var(--cat-blue);
|
|
195
|
+
--color-cat-green: var(--cat-green);
|
|
196
|
+
--color-cat-amber: var(--cat-amber);
|
|
197
|
+
--color-cat-pink: var(--cat-pink);
|
|
198
|
+
--color-cat-teal: var(--cat-teal);
|
|
199
|
+
--color-cat-orange: var(--cat-orange);
|
|
200
|
+
--color-cat-rose: var(--cat-rose);
|
|
201
|
+
--color-cat-cyan: var(--cat-cyan);
|
|
202
|
+
--color-cat-yellow: var(--cat-yellow);
|
|
203
|
+
--color-cat-indigo: var(--cat-indigo);
|
|
204
|
+
--color-cat-purple: var(--cat-purple);
|
|
205
|
+
|
|
206
|
+
/* A hue as text or icon on its own tint: 40% toward the foreground clears AA in both themes.
|
|
207
|
+
A tone's 10% tint under plain destructive text is 3.6:1; the ink mix is not. */
|
|
208
|
+
--color-cat-cyan-ink: color-mix(in oklab, var(--color-cat-cyan) 60%, var(--color-foreground));
|
|
209
|
+
--color-cat-teal-ink: color-mix(in oklab, var(--color-cat-teal) 60%, var(--color-foreground));
|
|
210
|
+
--color-cat-purple-ink: color-mix(in oklab, var(--color-cat-purple) 60%, var(--color-foreground));
|
|
211
|
+
--color-cat-amber-ink: color-mix(in oklab, var(--color-cat-amber) 60%, var(--color-foreground));
|
|
212
|
+
--color-success-ink: color-mix(in oklab, var(--color-success) 60%, var(--color-foreground));
|
|
213
|
+
--color-destructive-ink: color-mix(in oklab, var(--color-destructive) 60%, var(--color-foreground));
|
|
214
|
+
|
|
215
|
+
/* Light theme color system — uses named surface colors (linen/mist pendants of night/veil) */
|
|
216
|
+
--color-background: var(--gray-50);
|
|
217
|
+
--color-foreground: var(--gray-900);
|
|
218
|
+
--color-foreground-muted: var(--gray-700);
|
|
219
|
+
|
|
220
|
+
--color-card: var(--brand-linen); /* Surface — pendant of night */
|
|
221
|
+
--color-card-foreground: var(--gray-900);
|
|
222
|
+
|
|
223
|
+
--color-popover: var(--brand-linen);
|
|
224
|
+
--color-popover-foreground: var(--gray-900);
|
|
225
|
+
|
|
226
|
+
--color-primary: var(--gray-900);
|
|
227
|
+
--color-primary-foreground: var(--gray-50);
|
|
228
|
+
|
|
229
|
+
--color-secondary: var(--gray-100);
|
|
230
|
+
--color-secondary-foreground: var(--gray-900);
|
|
231
|
+
|
|
232
|
+
--color-muted: var(--gray-100);
|
|
233
|
+
--color-muted-foreground: var(--gray-700);
|
|
234
|
+
|
|
235
|
+
--color-accent: var(--gray-100);
|
|
236
|
+
--color-accent-foreground: var(--gray-900);
|
|
237
|
+
|
|
238
|
+
--color-destructive: var(--error);
|
|
239
|
+
--color-destructive-foreground: var(--gray-50);
|
|
240
|
+
|
|
241
|
+
--color-border: var(--brand-mist); /* Borders — pendant of veil */
|
|
242
|
+
--color-input: var(--brand-mist);
|
|
243
|
+
/* WHY the accent, not gray-900: a near-black halo reads as an error first.
|
|
244
|
+
Focus is the one moment the brand speaks in chrome. */
|
|
245
|
+
--color-ring: var(--brand-indigo);
|
|
246
|
+
|
|
247
|
+
/* Sidebar colors */
|
|
248
|
+
--color-sidebar: var(--sidebar);
|
|
249
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
250
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
251
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
252
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
253
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
254
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
255
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
256
|
+
|
|
257
|
+
/* A spinner reads as a speed. Tailwind's 1s looks like thinking, not working,
|
|
258
|
+
and `animate-spin` is on every loading glyph, so this is a cheap win. */
|
|
259
|
+
--animate-spin: spin 700ms linear infinite;
|
|
260
|
+
|
|
261
|
+
/* Every bare `transition-colors` / `transition-all` reads these two. Tailwind's
|
|
262
|
+
150ms is the right beat on the wrong ease, and ~200 call sites used it. */
|
|
263
|
+
--default-transition-duration: var(--duration-quick);
|
|
264
|
+
--default-transition-timing-function: var(--ease-smooth-out);
|
|
265
|
+
|
|
266
|
+
/* Collapsible animations, on the craft layer's scale and asymmetric: an open
|
|
267
|
+
invites, a close gets out of the way. The old `0.2s ease-out` was off-scale. */
|
|
268
|
+
--animate-collapsible-down: collapsible-down var(--duration-fast) var(--ease-smooth-out);
|
|
269
|
+
--animate-collapsible-up: collapsible-up var(--duration-quick) var(--ease-smooth-out);
|
|
270
|
+
|
|
271
|
+
@keyframes collapsible-down {
|
|
272
|
+
from {
|
|
273
|
+
height: 0;
|
|
274
|
+
}
|
|
275
|
+
to {
|
|
276
|
+
height: var(--bits-collapsible-content-height);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@keyframes collapsible-up {
|
|
281
|
+
from {
|
|
282
|
+
height: var(--bits-collapsible-content-height);
|
|
283
|
+
}
|
|
284
|
+
to {
|
|
285
|
+
height: 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* Accordion animations, on the same scale and the same asymmetry. */
|
|
290
|
+
--animate-accordion-down: accordion-down var(--duration-fast) var(--ease-smooth-out);
|
|
291
|
+
--animate-accordion-up: accordion-up var(--duration-quick) var(--ease-smooth-out);
|
|
292
|
+
|
|
293
|
+
@keyframes accordion-down {
|
|
294
|
+
from {
|
|
295
|
+
height: 0;
|
|
296
|
+
}
|
|
297
|
+
to {
|
|
298
|
+
height: var(--bits-accordion-content-height);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@keyframes accordion-up {
|
|
303
|
+
from {
|
|
304
|
+
height: var(--bits-accordion-content-height);
|
|
305
|
+
}
|
|
306
|
+
to {
|
|
307
|
+
height: 0;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.dark {
|
|
313
|
+
/* WHY: LayerChart halos axis labels with light-dark(), which reads color-scheme, not the .dark class. */
|
|
314
|
+
color-scheme: dark;
|
|
315
|
+
/* Dark theme — named surface system (ink/night/veil) */
|
|
316
|
+
--color-background: var(--brand-ink); /* Base — page background */
|
|
317
|
+
--color-foreground: var(--brand-cream);
|
|
318
|
+
--color-foreground-muted: var(--gray-300);
|
|
319
|
+
|
|
320
|
+
--color-card: var(--brand-night); /* Surface — cards, panels */
|
|
321
|
+
--color-card-foreground: var(--brand-cream);
|
|
322
|
+
|
|
323
|
+
--color-popover: var(--brand-night);
|
|
324
|
+
--color-popover-foreground: var(--brand-cream);
|
|
325
|
+
|
|
326
|
+
--color-primary: var(--brand-cream);
|
|
327
|
+
--color-primary-foreground: var(--gray-900);
|
|
328
|
+
|
|
329
|
+
--color-secondary: var(--brand-dusk); /* Between night (0.18) and veil (0.28) */
|
|
330
|
+
--color-secondary-foreground: var(--brand-cream);
|
|
331
|
+
|
|
332
|
+
--color-muted: var(--brand-dusk); /* Between night and veil — hover surfaces */
|
|
333
|
+
--color-muted-foreground: var(--gray-300);
|
|
334
|
+
|
|
335
|
+
--color-accent: var(--brand-dusk); /* Between night and veil — accent surfaces */
|
|
336
|
+
--color-accent-foreground: var(--brand-cream);
|
|
337
|
+
|
|
338
|
+
--color-destructive: var(--terra-400); /* Lighter terra for dark bg */
|
|
339
|
+
--color-destructive-foreground: var(--brand-cream);
|
|
340
|
+
|
|
341
|
+
--color-border: var(--brand-veil); /* Named border color */
|
|
342
|
+
--color-input: var(--brand-dusk); /* Between night and veil — input surfaces */
|
|
343
|
+
--color-ring: var(--brand-indigo-light);
|
|
344
|
+
|
|
345
|
+
/* Brand colors for dark theme — lighter variants for contrast on ink bg */
|
|
346
|
+
--color-brand-indigo: var(--brand-indigo-light); /* L=0.68 on ink L=0.12 = ΔL 0.56 ✓ */
|
|
347
|
+
|
|
348
|
+
/* Dark sidebar — uses named surface system */
|
|
349
|
+
--sidebar: var(--brand-night);
|
|
350
|
+
--sidebar-foreground: var(--brand-cream);
|
|
351
|
+
--sidebar-primary: var(--brand-indigo-light);
|
|
352
|
+
--sidebar-primary-foreground: oklch(1 0 0);
|
|
353
|
+
--sidebar-accent: oklch(0.24 0.05 262); /* Between night and veil */
|
|
354
|
+
--sidebar-accent-foreground: var(--brand-cream);
|
|
355
|
+
--sidebar-border: var(--brand-veil);
|
|
356
|
+
--sidebar-ring: var(--brand-indigo-light);
|
|
357
|
+
|
|
358
|
+
--color-sidebar: var(--sidebar);
|
|
359
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
360
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
361
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
362
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
363
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
364
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
365
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* === shadcn bare-name bridge ===
|
|
369
|
+
WHY: shared packages with raw CSS read `var(--card)` and friends with a light
|
|
370
|
+
fallback. We name them `--color-*`, so they painted white cards on ink. */
|
|
371
|
+
|
|
372
|
+
/* One `:root` block covers both themes, since `.dark` sits on html too. Set a
|
|
373
|
+
`--color-*` on a subtree and you must override the bare name there as well. */
|
|
374
|
+
:root {
|
|
375
|
+
--background: var(--color-background);
|
|
376
|
+
--foreground: var(--color-foreground);
|
|
377
|
+
|
|
378
|
+
--card: var(--color-card);
|
|
379
|
+
--card-foreground: var(--color-card-foreground);
|
|
380
|
+
|
|
381
|
+
--popover: var(--color-popover);
|
|
382
|
+
--popover-foreground: var(--color-popover-foreground);
|
|
383
|
+
|
|
384
|
+
--primary: var(--color-primary);
|
|
385
|
+
--primary-foreground: var(--color-primary-foreground);
|
|
386
|
+
|
|
387
|
+
--secondary: var(--color-secondary);
|
|
388
|
+
--secondary-foreground: var(--color-secondary-foreground);
|
|
389
|
+
|
|
390
|
+
--muted: var(--color-muted);
|
|
391
|
+
--muted-foreground: var(--color-muted-foreground);
|
|
392
|
+
|
|
393
|
+
--accent: var(--color-accent);
|
|
394
|
+
--accent-foreground: var(--color-accent-foreground);
|
|
395
|
+
|
|
396
|
+
--destructive: var(--color-destructive);
|
|
397
|
+
--destructive-foreground: var(--color-destructive-foreground);
|
|
398
|
+
|
|
399
|
+
--border: var(--color-border);
|
|
400
|
+
--input: var(--color-input);
|
|
401
|
+
--ring: var(--color-ring);
|
|
402
|
+
|
|
403
|
+
/* Chart series. Indigo leads, green second. Terra sits at 4 since a rate gauge
|
|
404
|
+
reads as caution, not identity. Every value has a dark variant. */
|
|
405
|
+
--chart-1: var(--color-brand-indigo);
|
|
406
|
+
--chart-2: var(--cat-green);
|
|
407
|
+
--chart-3: var(--brand-sand);
|
|
408
|
+
--chart-4: var(--brand-terra);
|
|
409
|
+
--chart-5: var(--cat-cyan);
|
|
410
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/* The bits-ui state variants every primitive in `./shadcn` is written against.
|
|
2
|
+
*
|
|
3
|
+
* bits-ui v2 writes `data-checked` / `data-open` and v1 wrote
|
|
4
|
+
* `data-state="checked"`, so each variant matches both and a component does not
|
|
5
|
+
* have to know which it is talking to. They live here rather than in a
|
|
6
|
+
* consumer's stylesheet because the classes that need them ship in this
|
|
7
|
+
* package: an app that defines only the palette would render a switch that
|
|
8
|
+
* never moves. */
|
|
9
|
+
@custom-variant data-open {
|
|
10
|
+
&:where([data-state="open"]),
|
|
11
|
+
&:where([data-open]:not([data-open="false"])) {
|
|
12
|
+
@slot;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@custom-variant data-closed {
|
|
17
|
+
&:where([data-state="closed"]),
|
|
18
|
+
&:where([data-closed]:not([data-closed="false"])) {
|
|
19
|
+
@slot;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@custom-variant data-checked {
|
|
24
|
+
&:where([data-state="checked"]),
|
|
25
|
+
&:where([data-checked]:not([data-checked="false"])) {
|
|
26
|
+
@slot;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@custom-variant data-unchecked {
|
|
31
|
+
&:where([data-state="unchecked"]),
|
|
32
|
+
&:where([data-unchecked]:not([data-unchecked="false"])) {
|
|
33
|
+
@slot;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@custom-variant data-selected {
|
|
38
|
+
&:where([data-selected]) {
|
|
39
|
+
@slot;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@custom-variant data-disabled {
|
|
44
|
+
&:where([data-disabled="true"]),
|
|
45
|
+
&:where([data-disabled]:not([data-disabled="false"])) {
|
|
46
|
+
@slot;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@custom-variant data-active {
|
|
51
|
+
&:where([data-state="active"]),
|
|
52
|
+
&:where([data-active]:not([data-active="false"])) {
|
|
53
|
+
@slot;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@custom-variant data-horizontal {
|
|
58
|
+
&:where([data-orientation="horizontal"]) {
|
|
59
|
+
@slot;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@custom-variant data-vertical {
|
|
64
|
+
&:where([data-orientation="vertical"]) {
|
|
65
|
+
@slot;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@layer base {
|
|
70
|
+
/* One ring for the whole kit. `:where` keeps it at specificity zero on top of
|
|
71
|
+
the layer, so a control that needs a different ring writes one and wins. */
|
|
72
|
+
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
|
|
73
|
+
outline: none;
|
|
74
|
+
box-shadow: var(--ring-focus);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* The accent at a low alpha, not the browser blue, because a selected word is
|
|
78
|
+
the one moment the palette shows up inside running text. */
|
|
79
|
+
::selection {
|
|
80
|
+
background: color-mix(in oklab, var(--ring-color) 24%, transparent);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* A default macOS overlay scrollbar paints a black bar on a dark panel. Track
|
|
84
|
+
transparent, thumb on the line token, so it belongs to whatever surface it
|
|
85
|
+
is over. The border is what insets the thumb without a second element. */
|
|
86
|
+
:where(html) {
|
|
87
|
+
scrollbar-color: var(--color-border, var(--border)) transparent;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
::-webkit-scrollbar {
|
|
91
|
+
width: 10px;
|
|
92
|
+
height: 10px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
::-webkit-scrollbar-track {
|
|
96
|
+
background: transparent;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
::-webkit-scrollbar-thumb {
|
|
100
|
+
border: 3px solid transparent;
|
|
101
|
+
background-clip: padding-box;
|
|
102
|
+
border-radius: 999px;
|
|
103
|
+
background-color: var(--color-border, var(--border));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@media (hover: hover) and (pointer: fine) {
|
|
107
|
+
::-webkit-scrollbar-thumb:hover {
|
|
108
|
+
background-color: var(--color-muted-foreground, var(--muted-foreground));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@layer components {
|
|
114
|
+
/* Three moves the kit repeats. They are classes rather than tokens because a
|
|
115
|
+
component wants one name, not four declarations, and because the reduced
|
|
116
|
+
motion guard then lives in one place instead of in every component.
|
|
117
|
+
|
|
118
|
+
Namespaced `t-` to match the transitions-dev snippets, which these sit
|
|
119
|
+
beside rather than duplicate. */
|
|
120
|
+
|
|
121
|
+
/* A control that reads as a key: it travels down a pixel under the finger.
|
|
122
|
+
The travel is what sells it, because a colour change alone reads as a hover
|
|
123
|
+
state, not as a press. */
|
|
124
|
+
.t-press {
|
|
125
|
+
transition:
|
|
126
|
+
background-color var(--duration-quick) var(--ease-smooth-out),
|
|
127
|
+
border-color var(--duration-quick) var(--ease-smooth-out),
|
|
128
|
+
color var(--duration-quick) var(--ease-smooth-out),
|
|
129
|
+
box-shadow var(--duration-quick) var(--ease-smooth-out),
|
|
130
|
+
transform var(--duration-micro) var(--ease-smooth-out);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.t-press:active:not(:disabled, [aria-disabled="true"]) {
|
|
134
|
+
transform: translateY(1px);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* A card that answers the pointer. It rises by a pixel and gains the next
|
|
138
|
+
step of elevation, so the hover reads as the card coming forward rather
|
|
139
|
+
than as its border changing colour. */
|
|
140
|
+
.t-lift {
|
|
141
|
+
transition:
|
|
142
|
+
box-shadow var(--duration-fast) var(--ease-smooth-out),
|
|
143
|
+
transform var(--duration-fast) var(--ease-smooth-out);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Only where a pointer hovers: on a phone a tap would leave the card lifted. */
|
|
147
|
+
@media (hover: hover) and (pointer: fine) {
|
|
148
|
+
.t-lift:hover {
|
|
149
|
+
transform: translateY(-1px);
|
|
150
|
+
box-shadow: var(--elev-raised);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* A surface arriving. The open carries the distance, the scale and the blur;
|
|
155
|
+
the close drops all three and takes less time, per the open/close
|
|
156
|
+
asymmetry rule in transitions-dev. */
|
|
157
|
+
.t-enter {
|
|
158
|
+
animation: t-enter var(--duration-fast) var(--ease-smooth-out) both;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.t-exit {
|
|
162
|
+
animation: t-exit var(--duration-quick) var(--ease-smooth-out) both;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@media (prefers-reduced-motion: reduce) {
|
|
166
|
+
.t-press,
|
|
167
|
+
.t-lift {
|
|
168
|
+
transition: none;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.t-press:active {
|
|
172
|
+
transform: none;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (hover: hover) and (pointer: fine) {
|
|
176
|
+
.t-lift:hover {
|
|
177
|
+
transform: none;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.t-enter,
|
|
182
|
+
.t-exit {
|
|
183
|
+
animation-duration: 1ms;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@keyframes t-enter {
|
|
189
|
+
from {
|
|
190
|
+
opacity: 0;
|
|
191
|
+
transform: translateY(var(--distance-micro)) scale(var(--scale-medium));
|
|
192
|
+
filter: blur(var(--blur-small));
|
|
193
|
+
}
|
|
194
|
+
to {
|
|
195
|
+
opacity: 1;
|
|
196
|
+
transform: none;
|
|
197
|
+
filter: none;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* A mark confirming a state that has already changed: it arrives after the box
|
|
202
|
+
it sits in, so it grows into place instead of fading. */
|
|
203
|
+
@keyframes t-check {
|
|
204
|
+
from {
|
|
205
|
+
opacity: 0;
|
|
206
|
+
transform: scale(0.4);
|
|
207
|
+
}
|
|
208
|
+
to {
|
|
209
|
+
opacity: 1;
|
|
210
|
+
transform: scale(1);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@keyframes t-exit {
|
|
215
|
+
from {
|
|
216
|
+
opacity: 1;
|
|
217
|
+
transform: none;
|
|
218
|
+
}
|
|
219
|
+
to {
|
|
220
|
+
opacity: 0;
|
|
221
|
+
transform: scale(var(--scale-tiny));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// Rewrites every `#src/` and `#scripts/` specifier to a relative path, in place, before publish.
|
|
2
|
-
// WHY: Vite cannot resolve a package's own `#` imports inside node_modules (ai-doc/references/publishing-packages.md).
|
|
3
|
-
// It edits the source tree, so only scripts/release.sh runs it, right before `pnpm publish`, and restores the tree after.
|
|
4
|
-
// It is not a `build` script: `pnpm -r build` would rewrite the working tree.
|
|
5
|
-
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
7
|
-
|
|
8
|
-
const root = resolve(import.meta.dirname, "..");
|
|
9
|
-
const targets = { src: join(root, "src"), scripts: join(root, "scripts") };
|
|
10
|
-
const TEXT = /\.(ts|js|mjs|svelte|css)$/;
|
|
11
|
-
const SPECIFIER = /(["'`])#(src|scripts)\/([^"'`]+)\1/g;
|
|
12
|
-
|
|
13
|
-
async function* walk(dir) {
|
|
14
|
-
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
15
|
-
if (entry.name === "node_modules") continue;
|
|
16
|
-
const full = join(dir, entry.name);
|
|
17
|
-
if (entry.isDirectory()) yield* walk(full);
|
|
18
|
-
else if (TEXT.test(entry.name)) yield full;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let files = 0;
|
|
23
|
-
let specifiers = 0;
|
|
24
|
-
for (const dir of Object.values(targets)) {
|
|
25
|
-
for await (const file of walk(dir)) {
|
|
26
|
-
const text = await readFile(file, "utf8");
|
|
27
|
-
const next = text.replace(SPECIFIER, (_, quote, base, rest) => {
|
|
28
|
-
specifiers++;
|
|
29
|
-
let rel = relative(dirname(file), join(targets[base], rest));
|
|
30
|
-
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
31
|
-
return `${quote}${rel}${quote}`;
|
|
32
|
-
});
|
|
33
|
-
if (next !== text) {
|
|
34
|
-
await writeFile(file, next);
|
|
35
|
-
files++;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
console.log(`relative-imports: ${specifiers} specifiers in ${files} files`);
|