@deepfuture/dui-components 1.3.0 → 1.4.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/toast/toast.js ADDED
@@ -0,0 +1,448 @@
1
+ /**
2
+ * `<dui-toast>` — Styled toast item.
3
+ *
4
+ * NOTE: This is the only DUI component that overrides `render()`. The styled
5
+ * layer's contract is normally "extend with CSS only" — toasts are an
6
+ * exception because we ship built-in default icons keyed off `data-type`,
7
+ * which require a new shadow-DOM slot (`icon`) the primitive doesn't have.
8
+ * The override carefully preserves the primitive's full markup (root, title,
9
+ * description, default slot, action slot, close slot, ARIA wiring,
10
+ * `data-*` state attributes) so consumer-facing parts/slots stay stable.
11
+ */
12
+ import { css, html } from "lit";
13
+ import { DuiToastPrimitive } from "@deepfuture/dui-primitives/toast";
14
+ import "../_install.js";
15
+ import "../spinner/index.js";
16
+ import { defaultIconFor } from "./default-icons.js";
17
+ const styles = css `
18
+ /* ── Per-type color hook ──
19
+ *
20
+ * One CSS custom property the consumer can read/override; resolves per
21
+ * data-type. Default = neutral text-2 (used by default + loading types).
22
+ */
23
+ :host {
24
+ --toast-type-color: var(--text-2);
25
+ }
26
+ :host([type="success"]) {
27
+ --toast-type-color: var(--success);
28
+ }
29
+ :host([type="warning"]) {
30
+ --toast-type-color: var(--warning);
31
+ }
32
+ :host([type="info"]) {
33
+ --toast-type-color: var(--info);
34
+ }
35
+ :host([type="error"]) {
36
+ --toast-type-color: var(--destructive);
37
+ }
38
+ :host([type="loading"]) {
39
+ --toast-type-color: var(--text-2);
40
+ }
41
+
42
+ /* ── Default appearance: neutral surface, colored icon ──
43
+ *
44
+ * Sonner-default look. Surface stays --surface-2; only the icon (and the
45
+ * loading spinner) picks up the type color.
46
+ */
47
+ :host {
48
+ --toast-bg: var(--surface-2);
49
+ --toast-fg: var(--text-1);
50
+ --toast-border-color: var(--border);
51
+ --toast-border-width: var(--border-width-thin);
52
+ --toast-radius: var(--radius-md);
53
+ --toast-shadow: var(--shadow-lg);
54
+
55
+ --toast-padding-block: var(--space-3);
56
+ --toast-padding-inline: var(--space-3_5);
57
+ --toast-gap: var(--space-2_5);
58
+ --toast-text-gap: var(--space-1);
59
+ --toast-action-gap: var(--space-2);
60
+
61
+ --toast-title-size: var(--text-sm);
62
+ --toast-title-weight: var(--font-weight-semibold);
63
+ --toast-description-size: var(--text-xs);
64
+ --toast-description-color: var(--text-2);
65
+
66
+ --toast-icon-size: var(--space-4);
67
+ --toast-icon-color: var(--toast-type-color);
68
+
69
+ --toast-enter-duration: var(--duration-normal);
70
+ --toast-exit-duration: var(--duration-fast);
71
+ --toast-stack-transition: var(--ease-out-3);
72
+ }
73
+
74
+ /* ── Rich appearance: tinted surface + colored border ── */
75
+ :host([appearance="rich"][type="success"]) {
76
+ --toast-bg: var(--success-subtle);
77
+ --toast-border-color: var(--success);
78
+ --toast-fg: var(--success-text);
79
+ }
80
+ :host([appearance="rich"][type="warning"]) {
81
+ --toast-bg: var(--warning-subtle);
82
+ --toast-border-color: var(--warning);
83
+ --toast-fg: var(--warning-text);
84
+ }
85
+ :host([appearance="rich"][type="info"]) {
86
+ --toast-bg: var(--info-subtle);
87
+ --toast-border-color: var(--info);
88
+ --toast-fg: var(--info-text);
89
+ }
90
+ :host([appearance="rich"][type="error"]) {
91
+ --toast-bg: var(--destructive-subtle);
92
+ --toast-border-color: var(--destructive);
93
+ --toast-fg: var(--destructive-text);
94
+ }
95
+
96
+ /* ── Root: CSS Grid layout ──
97
+ *
98
+ * "icon title " <- close is position: absolute, not in the grid
99
+ * "icon desc "
100
+ * "icon action"
101
+ *
102
+ * Two columns: icon (auto-sized; collapses to 0 when no icon) + content
103
+ * (1fr). The icon column width is driven by the private --_icon-col
104
+ * variable so we can zero it AND zero the column-gap when the icon is
105
+ * absent (default type, no slotted icon).
106
+ */
107
+ [part="root"] {
108
+ position: relative;
109
+ display: grid;
110
+ grid-template-columns: var(--_icon-col, var(--toast-icon-size)) 1fr;
111
+ column-gap: var(--_icon-gap, var(--toast-gap));
112
+ row-gap: var(--toast-text-gap);
113
+ align-items: start;
114
+
115
+ background: var(--toast-bg);
116
+ color: var(--toast-fg);
117
+ border: var(--toast-border-width) solid var(--toast-border-color);
118
+ border-radius: var(--toast-radius);
119
+ box-shadow: var(--toast-shadow);
120
+ padding: var(--toast-padding-block) var(--toast-padding-inline);
121
+ font-family: var(--font-sans);
122
+
123
+ /* Open/close opacity transition (the primitive owns the swipe transform). */
124
+ transition:
125
+ opacity var(--toast-enter-duration) var(--ease-out-3),
126
+ transform var(--toast-enter-duration) var(--ease-out-3);
127
+ }
128
+
129
+ /* ── Icon ── */
130
+ [part="icon"] {
131
+ grid-column: 1;
132
+ grid-row: 1 / 3;
133
+ display: inline-flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ width: var(--toast-icon-size);
137
+ height: var(--toast-icon-size);
138
+ color: var(--toast-icon-color);
139
+ /* Optical alignment to title baseline (title is text-sm semibold). */
140
+ margin-top: var(--space-px);
141
+ flex-shrink: 0;
142
+ }
143
+
144
+ [part="icon"] dui-spinner {
145
+ --spinner-size: var(--toast-icon-size);
146
+ --spinner-color: var(--toast-icon-color);
147
+ }
148
+
149
+ /* When no icon (default type, no slotted icon), collapse the icon
150
+ column to zero AND remove the column gap so the text column starts
151
+ flush against the left padding. */
152
+ :host(:not([type])) [part="root"],
153
+ :host([type="default"]) [part="root"],
154
+ :host([type=""]) [part="root"] {
155
+ --_icon-col: 0px;
156
+ --_icon-gap: 0;
157
+ }
158
+ :host(:not([type])) [part="icon"],
159
+ :host([type="default"]) [part="icon"],
160
+ :host([type=""]) [part="icon"] {
161
+ display: none;
162
+ }
163
+
164
+ /* ── Title ── */
165
+ [part="title"] {
166
+ grid-column: 2;
167
+ grid-row: 1;
168
+ font-size: var(--toast-title-size);
169
+ font-weight: var(--toast-title-weight);
170
+ line-height: var(--text-sm--line-height);
171
+ color: var(--toast-fg);
172
+ margin: 0;
173
+ text-wrap: pretty;
174
+ /* Reserve room for the absolute close button so long titles don't
175
+ flow underneath it. */
176
+ padding-inline-end: var(--_close-pad, 0);
177
+ }
178
+
179
+ /* ── Description ── */
180
+ [part="description"] {
181
+ grid-column: 2;
182
+ grid-row: 2;
183
+ font-size: var(--toast-description-size);
184
+ line-height: var(--text-xs--line-height);
185
+ color: var(--toast-description-color);
186
+ margin: 0;
187
+ text-wrap: pretty;
188
+ padding-inline-end: var(--_close-pad, 0);
189
+ }
190
+
191
+ /* ── Default slot (Tier-2 content) ──
192
+ *
193
+ * Default-slotted content sits where the description would and spans
194
+ * the text column. Title/description take precedence visually but both
195
+ * can coexist (rare).
196
+ */
197
+ [part="default-content"] {
198
+ grid-column: 2;
199
+ grid-row: 2;
200
+ }
201
+ [part="default-content"][data-empty] {
202
+ display: none;
203
+ }
204
+
205
+ /* ── Action slot ── */
206
+ [part="action-wrapper"] {
207
+ grid-column: 2;
208
+ grid-row: 3;
209
+ margin-top: var(--toast-action-gap);
210
+ }
211
+ [part="action-wrapper"][data-empty] {
212
+ display: none;
213
+ }
214
+
215
+ /* Style consumer-slotted bare <button>s in the action wrapper. */
216
+ ::slotted(dui-toast-action) {
217
+ /* dui-toast-action is display:contents; per-button styling is on its
218
+ part. Nothing to do here. */
219
+ }
220
+
221
+ /* ── Close slot ──
222
+ *
223
+ * Absolute-positioned in the top-right (top-left for left-anchored
224
+ * regions). Hidden until hover on devices that have hover, always
225
+ * visible on touch.
226
+ */
227
+ [part="close-wrapper"] {
228
+ position: absolute;
229
+ top: var(--space-2);
230
+ inset-inline-end: var(--space-2);
231
+ z-index: 1;
232
+ }
233
+ [part="close-wrapper"][data-empty] {
234
+ display: none;
235
+ }
236
+
237
+ /* When the close button is present, reserve title/description padding
238
+ so long content doesn't flow underneath the absolute close. */
239
+ [part="root"]:has([part="close-wrapper"]:not([data-empty])) {
240
+ --_close-pad: var(--space-6);
241
+ }
242
+
243
+ @media (hover: hover) {
244
+ [part="close-wrapper"] {
245
+ opacity: 0;
246
+ transition: opacity var(--duration-fast) var(--ease-out-3);
247
+ }
248
+ :host(:hover) [part="close-wrapper"],
249
+ :host(:focus-within) [part="close-wrapper"],
250
+ [part="root"]:hover [part="close-wrapper"],
251
+ [part="root"]:focus-within [part="close-wrapper"] {
252
+ opacity: 1;
253
+ }
254
+ }
255
+
256
+ /* ── Open / close animations ──
257
+ *
258
+ * Enter: slide in from the anchored edge. Direction is read from
259
+ * data-position (mirrored from region onto each toast by primitive).
260
+ * Exit: opacity to 0 + scale(0.96).
261
+ */
262
+ :host([data-position^="bottom-"]) [part="root"][data-starting-style] {
263
+ opacity: 0;
264
+ transform: translateY(100%);
265
+ }
266
+ :host([data-position^="top-"]) [part="root"][data-starting-style] {
267
+ opacity: 0;
268
+ transform: translateY(-100%);
269
+ }
270
+ :host([data-position$="-right"]) [part="root"][data-starting-style] {
271
+ /* Right-anchored regions slide in from the right edge for the very
272
+ first toast — but with cascade stacking, vertical slide reads better
273
+ once a stack is established. Keep vertical. */
274
+ }
275
+
276
+ [part="root"][data-ending-style] {
277
+ opacity: 0;
278
+ transform: scale(0.96);
279
+ transition:
280
+ opacity var(--toast-exit-duration) var(--ease-out-3),
281
+ transform var(--toast-exit-duration) var(--ease-out-3);
282
+ }
283
+
284
+ /* Fade during swipe-end throw (primitive translates 3× via swipe vars). */
285
+ :host([data-swipe="end"]) [part="root"] {
286
+ opacity: 0;
287
+ }
288
+
289
+ /* ── Reduced motion: opacity-only ── */
290
+ @media (prefers-reduced-motion: reduce) {
291
+ [part="root"][data-starting-style],
292
+ [part="root"][data-ending-style] {
293
+ transform: none;
294
+ }
295
+ }
296
+ `;
297
+ /**
298
+ * `<dui-toast>` — Styled toast item.
299
+ *
300
+ * Extends the primitive with:
301
+ * - Sonner-default look (neutral surface, colored icon, title + description)
302
+ * - Built-in default icons per `type` (success/info/warning/error/loading);
303
+ * slot `icon` to override
304
+ * - `appearance="rich"` for tinted-surface mode
305
+ * - CSS Grid layout with full slot/part preservation
306
+ * - Hover-visible close button (always visible on touch)
307
+ *
308
+ * @slot title - Title text (auto-wires aria-labelledby).
309
+ * @slot description - Description text (auto-wires aria-describedby).
310
+ * @slot - Default custom content (Tier-2 escape hatch).
311
+ * @slot icon - Custom icon to replace the type-default. Empty = built-in.
312
+ * @slot action - Action button(s); typically a `<dui-toast-action>` wrapper.
313
+ * @slot close - Close affordance; typically a `<dui-toast-close>` wrapper.
314
+ *
315
+ * @csspart root - The toast container.
316
+ * @csspart icon - The icon container.
317
+ * @csspart title - The title wrapper.
318
+ * @csspart description - The description wrapper.
319
+ * @csspart action-wrapper - The action slot's grid cell.
320
+ * @csspart close-wrapper - The close slot's absolute-positioned container.
321
+ *
322
+ * @attr appearance - `"default" | "rich"`. Rich tints surface + border
323
+ * by type color. Default keeps the surface neutral.
324
+ */
325
+ export class DuiToast extends DuiToastPrimitive {
326
+ static styles = [...DuiToastPrimitive.styles, styles];
327
+ /**
328
+ * Override the primitive's render to inject an `icon` slot with built-in
329
+ * defaults. Preserves all other parts/slots/ARIA wiring exactly.
330
+ */
331
+ render() {
332
+ const role = this.priority === "assertive" ? "alert" : "status";
333
+ const ariaLive = this.priority;
334
+ // The primitive does ARIA wiring (aria-labelledby/aria-describedby)
335
+ // based on title/description slot assignment, but those flags live
336
+ // in the primitive's private state. We track our own copies via
337
+ // slotchange handlers below and use them here — same shape, just
338
+ // re-implemented in our subclass.
339
+ // Headless mode: same as primitive — only default slot.
340
+ if (this.headless) {
341
+ return html `
342
+ <div
343
+ part="root"
344
+ role="${role}"
345
+ aria-live="${ariaLive}"
346
+ aria-atomic="true"
347
+ data-type="${this.type}"
348
+ >
349
+ <slot></slot>
350
+ </div>
351
+ `;
352
+ }
353
+ const titleId = `dui-toast-title-${this.#instanceId}`;
354
+ const descId = `dui-toast-desc-${this.#instanceId}`;
355
+ return html `
356
+ <div
357
+ part="root"
358
+ role="${role}"
359
+ aria-live="${ariaLive}"
360
+ aria-atomic="true"
361
+ aria-labelledby="${this.#hasTitle ? titleId : ""}"
362
+ aria-describedby="${this.#hasDescription ? descId : ""}"
363
+ data-type="${this.type}"
364
+ >
365
+ <span part="icon">
366
+ <slot name="icon">${defaultIconFor(this.type)}</slot>
367
+ </span>
368
+ <div part="title" id="${titleId}">
369
+ <slot
370
+ name="title"
371
+ @slotchange="${this.#onTitleSlotChange}"
372
+ ></slot>
373
+ </div>
374
+ <div part="description" id="${descId}">
375
+ <slot
376
+ name="description"
377
+ @slotchange="${this.#onDescriptionSlotChange}"
378
+ ></slot>
379
+ </div>
380
+ <div part="default-content" ?data-empty="${!this.#hasDefault}">
381
+ <slot @slotchange="${this.#onDefaultSlotChange}"></slot>
382
+ </div>
383
+ <div part="action-wrapper" ?data-empty="${!this.#hasAction}">
384
+ <slot name="action" @slotchange="${this
385
+ .#onActionSlotChange}"></slot>
386
+ </div>
387
+ <div part="close-wrapper" ?data-empty="${!this.#hasClose}">
388
+ <slot name="close" @slotchange="${this
389
+ .#onCloseSlotChange}"></slot>
390
+ </div>
391
+ </div>
392
+ `;
393
+ }
394
+ // ---- Slot-assignment tracking ----
395
+ //
396
+ // ARIA wiring (title, description) plus visibility tracking for
397
+ // default / action / close wrappers. CSS can't gate visibility on
398
+ // "slot has assigned content" — `slot:empty` is always true for
399
+ // slots without fallback children regardless of whether anything
400
+ // is actually slotted.
401
+ #instanceId = crypto.randomUUID().slice(0, 8);
402
+ #hasTitle = false;
403
+ #hasDescription = false;
404
+ #hasDefault = false;
405
+ #hasAction = false;
406
+ #hasClose = false;
407
+ #slotHasContent(slot) {
408
+ return slot.assignedNodes({ flatten: true }).some((n) => n.nodeType === Node.ELEMENT_NODE ||
409
+ (n.nodeType === Node.TEXT_NODE &&
410
+ (n.textContent?.trim().length ?? 0) > 0));
411
+ }
412
+ #onTitleSlotChange = (e) => {
413
+ const next = this.#slotHasContent(e.target);
414
+ if (next !== this.#hasTitle) {
415
+ this.#hasTitle = next;
416
+ this.requestUpdate();
417
+ }
418
+ };
419
+ #onDescriptionSlotChange = (e) => {
420
+ const next = this.#slotHasContent(e.target);
421
+ if (next !== this.#hasDescription) {
422
+ this.#hasDescription = next;
423
+ this.requestUpdate();
424
+ }
425
+ };
426
+ #onDefaultSlotChange = (e) => {
427
+ const next = this.#slotHasContent(e.target);
428
+ if (next !== this.#hasDefault) {
429
+ this.#hasDefault = next;
430
+ this.requestUpdate();
431
+ }
432
+ };
433
+ #onActionSlotChange = (e) => {
434
+ const next = this.#slotHasContent(e.target);
435
+ if (next !== this.#hasAction) {
436
+ this.#hasAction = next;
437
+ this.requestUpdate();
438
+ }
439
+ };
440
+ #onCloseSlotChange = (e) => {
441
+ const next = this.#slotHasContent(e.target);
442
+ if (next !== this.#hasClose) {
443
+ this.#hasClose = next;
444
+ this.requestUpdate();
445
+ }
446
+ };
447
+ }
448
+ customElements.define(DuiToast.tagName, DuiToast);
@@ -1,2 +1,2 @@
1
- const tokensCSS = "/* =================================================================\n * DUI Design Tokens\n * =================================================================\n * Combined token system: spacing, typography, borders, elevation,\n * motion, colors, sizing, and focus.\n *\n * Tokens are declared on :root so they cascade through the entire\n * document, including into shadow DOM via CSS custom property\n * inheritance.\n *\n * Dark mode: apply data-theme=\"dark\" on <html> to switch palettes.\n * ================================================================= */\n\n:root {\n /* -----------------------------------------------------------\n * Spacing (Tailwind base-4 system)\n * ----------------------------------------------------------- */\n --space-0: 0;\n --space-px: 1px;\n --space-0_5: 0.125rem; /* 2px */\n --space-1: 0.25rem; /* 4px */\n --space-1_5: 0.375rem; /* 6px */\n --space-2: 0.5rem; /* 8px */\n --space-2_5: 0.625rem; /* 10px */\n --space-3: 0.75rem; /* 12px */\n --space-3_5: 0.875rem; /* 14px */\n --space-4: 1rem; /* 16px */\n --space-4_5: 1.125rem; /* 18px */\n --space-5: 1.25rem; /* 20px */\n --space-6: 1.5rem; /* 24px */\n --space-7: 1.75rem; /* 28px */\n --space-8: 2rem; /* 32px */\n --space-9: 2.25rem; /* 36px */\n --space-10: 2.5rem; /* 40px */\n --space-11: 2.75rem; /* 44px */\n --space-12: 3rem; /* 48px */\n --space-14: 3.5rem; /* 56px */\n --space-16: 4rem; /* 64px */\n --space-20: 5rem; /* 80px */\n --space-24: 6rem; /* 96px */\n --space-28: 7rem; /* 112px */\n --space-32: 8rem; /* 128px */\n --space-36: 9rem; /* 144px */\n --space-40: 10rem; /* 160px */\n --space-44: 11rem; /* 176px */\n --space-48: 12rem; /* 192px */\n --space-52: 13rem; /* 208px */\n --space-56: 14rem; /* 224px */\n --space-60: 15rem; /* 240px */\n --space-64: 16rem; /* 256px */\n --space-72: 18rem; /* 288px */\n --space-80: 20rem; /* 320px */\n --space-96: 24rem; /* 384px */\n\n /* -----------------------------------------------------------\n * Typography\n * ----------------------------------------------------------- */\n --font-sans:\n 'Inter', system-ui, -apple-system, sans-serif,\n \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-serif: ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif;\n --font-mono:\n 'JetBrains Mono', ui-monospace, SFMono-Regular, \"SF Mono\", Menlo,\n Consolas, \"Liberation Mono\", monospace;\n\n /* -----------------------------------------------------------\n * Type scale\n * -----------------------------------------------------------\n * Each size is paired with a line-height variable. The\n * line-height is a unitless ratio; multiplied by the size,\n * it yields the target leading (shown in comments).\n * ----------------------------------------------------------- */\n\n --text-2xs: 0.625rem; /* 10px */\n --text-2xs--line-height: calc(0.75 / 0.625); /* 12px */\n\n --text-xs: 0.75rem; /* 12px */\n --text-xs--line-height: calc(1 / 0.75); /* 16px */\n\n --text-sm: 0.875rem; /* 14px */\n --text-sm--line-height: calc(1.25 / 0.875); /* 20px */\n\n --text-base: 1rem; /* 16px */\n --text-base--line-height: calc(1.5 / 1); /* 24px */\n\n --text-lg: 1.125rem; /* 18px */\n --text-lg--line-height: calc(1.75 / 1.125); /* 28px */\n\n --text-xl: 1.25rem; /* 20px */\n --text-xl--line-height: calc(1.75 / 1.25); /* 28px */\n\n --text-2xl: 1.5rem; /* 24px */\n --text-2xl--line-height: calc(2 / 1.5); /* 32px */\n\n --text-3xl: 1.875rem; /* 30px */\n --text-3xl--line-height: calc(2.25 / 1.875); /* 36px */\n\n --text-4xl: 2.25rem; /* 36px */\n --text-4xl--line-height: calc(2.5 / 2.25); /* 40px */\n\n --text-5xl: 3rem; /* 48px */\n --text-5xl--line-height: 1;\n\n --text-6xl: 3.75rem; /* 60px */\n --text-6xl--line-height: 1;\n\n --text-7xl: 4.5rem; /* 72px */\n --text-7xl--line-height: 1;\n\n --text-8xl: 6rem; /* 96px */\n --text-8xl--line-height: 1;\n\n --text-9xl: 8rem; /* 128px */\n --text-9xl--line-height: 1;\n\n --letter-spacing-tightest: -0.02em;\n --letter-spacing-tighter: -0.015em;\n --letter-spacing-tight: -0.01em;\n --letter-spacing-normal: 0em;\n --letter-spacing-wide: 0.006em;\n --letter-spacing-wider: 0.012em;\n --letter-spacing-widest: 0.018em;\n\n --font-weight-regular: 400;\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n\n --line-height-none: 1;\n --line-height-tight: 1.25;\n --line-height-snug: 1.375;\n --line-height-normal: 1.5;\n --line-height-relaxed: 1.625;\n\n /* -----------------------------------------------------------\n * Borders\n * ----------------------------------------------------------- */\n --radius-none: 0;\n --radius-xs: 0.125rem;\n --radius-sm: 0.25rem;\n --radius-md: 0.5rem;\n --radius-lg: 1rem;\n --radius-xl: 1.5rem;\n --radius-2xl: 2rem;\n --radius-full: 9999px;\n\n --border-width-hairline: 0.5px;\n --border-width-thin: 1px;\n --border-width-medium: 2px;\n --border-width-thick: 4px;\n\n /* -----------------------------------------------------------\n * Elevation\n * ----------------------------------------------------------- */\n --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\n --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);\n --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);\n --shadow-none: 0 0 0 0 transparent;\n\n --z-base: 0;\n --z-dropdown: 700;\n --z-sticky: 800;\n --z-overlay: 900;\n --z-modal: 1000;\n --z-popover: 1100;\n --z-toast: 1200;\n --z-tooltip: 1300;\n\n /* -----------------------------------------------------------\n * Motion\n * ----------------------------------------------------------- */\n --duration-instant: 50ms;\n --duration-fastest: 75ms;\n --duration-faster: 100ms;\n --duration-fast: 150ms;\n --duration-normal: 250ms;\n --duration-slow: 400ms;\n --duration-slower: 700ms;\n\n --ease-1: cubic-bezier(0.25, 0, 0.5, 1);\n --ease-2: cubic-bezier(0.25, 0, 0.4, 1);\n --ease-3: cubic-bezier(0.25, 0, 0.3, 1);\n --ease-4: cubic-bezier(0.25, 0, 0.2, 1);\n --ease-5: cubic-bezier(0.25, 0, 0.1, 1);\n --ease-in-1: cubic-bezier(0.25, 0, 1, 1);\n --ease-in-2: cubic-bezier(0.5, 0, 1, 1);\n --ease-in-3: cubic-bezier(0.7, 0, 1, 1);\n --ease-in-4: cubic-bezier(0.9, 0, 1, 1);\n --ease-in-5: cubic-bezier(1, 0, 1, 1);\n --ease-out-1: cubic-bezier(0, 0, 0.75, 1);\n --ease-out-2: cubic-bezier(0, 0, 0.5, 1);\n --ease-out-3: cubic-bezier(0, 0, 0.3, 1);\n --ease-out-4: cubic-bezier(0, 0, 0.1, 1);\n --ease-out-5: cubic-bezier(0, 0, 0, 1);\n --ease-in-out-1: cubic-bezier(0.1, 0, 0.9, 1);\n --ease-in-out-2: cubic-bezier(0.3, 0, 0.7, 1);\n --ease-in-out-3: cubic-bezier(0.5, 0, 0.5, 1);\n --ease-in-out-4: cubic-bezier(0.7, 0, 0.3, 1);\n --ease-in-out-5: cubic-bezier(0.9, 0, 0.1, 1);\n\n --ease-sine-in: cubic-bezier(0.47, 0, 0.745, 0.715);\n --ease-sine-out: cubic-bezier(0.39, 0.575, 0.565, 1);\n --ease-sine-in-out: cubic-bezier(0.445, 0.05, 0.55, 0.95);\n --ease-quad-in: cubic-bezier(0.55, 0.085, 0.68, 0.53);\n --ease-quad-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);\n --ease-quad-in-out: cubic-bezier(0.455, 0.03, 0.515, 0.955);\n --ease-cubic-in: cubic-bezier(0.55, 0.055, 0.675, 0.19);\n --ease-cubic-out: cubic-bezier(0.215, 0.61, 0.355, 1);\n --ease-cubic-in-out: cubic-bezier(0.645, 0.045, 0.355, 1);\n --ease-quart-in: cubic-bezier(0.895, 0.03, 0.685, 0.22);\n --ease-quart-out: cubic-bezier(0.165, 0.84, 0.44, 1);\n --ease-quart-in-out: cubic-bezier(0.77, 0, 0.175, 1);\n --ease-quint-in: cubic-bezier(0.755, 0.05, 0.855, 0.06);\n --ease-quint-out: cubic-bezier(0.23, 1, 0.32, 1);\n --ease-quint-in-out: cubic-bezier(0.86, 0, 0.07, 1);\n --ease-expo-in: cubic-bezier(0.95, 0.05, 0.795, 0.035);\n --ease-expo-out: cubic-bezier(0.19, 1, 0.22, 1);\n --ease-expo-in-out: cubic-bezier(1, 0, 0, 1);\n --ease-circ-in: cubic-bezier(0.6, 0.04, 0.98, 0.335);\n --ease-circ-out: cubic-bezier(0.075, 0.82, 0.165, 1);\n --ease-circ-in-out: cubic-bezier(0.785, 0.135, 0.15, 0.86);\n\n /* -----------------------------------------------------------\n * Filters (presets for filter / backdrop-filter tokens)\n * ----------------------------------------------------------- */\n --filter-none: none;\n --filter-blur-sm: blur(4px);\n --filter-blur-md: blur(8px);\n --filter-blur-lg: blur(16px);\n --filter-blur-xl: blur(24px);\n --filter-brightness-dim: brightness(0.8);\n --filter-brightness-bright: brightness(1.15);\n --filter-saturate-muted: saturate(0.5);\n --filter-saturate-vivid: saturate(1.5);\n --filter-grayscale: grayscale(1);\n\n /* -----------------------------------------------------------\n * Clip Paths (shape presets)\n * ----------------------------------------------------------- */\n --clip-none: none;\n --clip-circle: circle(50%);\n --clip-squircle: inset(0 round 30%);\n --clip-hexagon: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);\n --clip-octagon: polygon(29.3% 0%, 70.7% 0%, 100% 29.3%, 100% 70.7%, 70.7% 100%, 29.3% 100%, 0% 70.7%, 0% 29.3%);\n --clip-chevron-right: polygon(0% 0%, 75% 0%, 100% 50%, 75% 100%, 0% 100%, 25% 50%);\n --clip-bevel: polygon(8% 0%, 92% 0%, 100% 8%, 100% 92%, 92% 100%, 8% 100%, 0% 92%, 0% 8%);\n\n /* -----------------------------------------------------------\n * Component Sizing\n * ----------------------------------------------------------- */\n --component-height-xxs: 1.25rem; /* 20px */\n --component-height-xs: 1.5rem; /* 24px */\n --component-height-sm: 1.75rem; /* 28px */\n --component-height-md: 2rem; /* 32px */\n --component-height-lg: 2.25rem; /* 36px */\n --component-height-xl: 2.5rem; /* 40px */\n\n /* -----------------------------------------------------------\n * Focus Ring\n * ----------------------------------------------------------- */\n --focus-ring-color: color-mix(in oklch, var(--accent), transparent 50%);\n --focus-ring-width: 3px;\n --focus-ring-offset: 0px;\n --focus-ring-duration: var(--duration-fast);\n --focus-ring-easing: var(--ease-out-3);\n\n /* -----------------------------------------------------------\n * Colors: Brand (theme-independent)\n * ----------------------------------------------------------- */\n --brand: oklch(0.728 0.1304 73.28);\n --brand-foreground: oklch(1 0.02 73.28);\n}\n\n/* =================================================================\n * COLOR SYSTEM\n * =================================================================\n * Built on two principles:\n * 1. Operations on a base — colors are relationships, not fixed values.\n * 2. Fewer primitives, more derivation — 4 primitives, everything\n * else computed via oklch(from ...) and color-mix().\n *\n * Layer 1: Primitives (only values a theme author picks)\n * Layer 2: Derived tokens (computed from primitives, never per-theme)\n * Layer 3: Interaction states (component-level, not tokens)\n * ================================================================= */\n\n/* -----------------------------------------------------------------\n * LIGHT THEME (default)\n * ----------------------------------------------------------------- */\n:root:not([data-theme=\"dark\"]) {\n /* --- Layer 1: Primitives --- */\n --background: oklch(0.97 0 0);\n --foreground: oklch(0.15 0 0);\n --accent: oklch(0.55 0.25 260);\n --destructive: oklch(0.55 0.22 25);\n}\n\n/* -----------------------------------------------------------------\n * DARK THEME\n * ----------------------------------------------------------------- */\n:root[data-theme=\"dark\"] {\n /* --- Layer 1: Primitives --- */\n --background: oklch(0.15 0.015 260);\n --foreground: oklch(0.93 0 0);\n --accent: oklch(0.75 0.18 260);\n --destructive: oklch(0.70 0.18 25);\n}\n\n/* -----------------------------------------------------------------\n * Layer 2: Derived tokens (computed from primitives)\n * Defined once — same rules for light and dark.\n * ----------------------------------------------------------------- */\n:root {\n /* Surfaces — lightness offsets from background.\n Positive = elevated (brighter). Negative = sunken (darker). */\n --sunken-2: oklch(from var(--background) calc(l - 0.03) c h);\n --sunken-1: oklch(from var(--background) calc(l - 0.01) c h);\n --surface-1: oklch(from var(--background) calc(l + 0.02) c h);\n --surface-2: oklch(from var(--background) calc(l + 0.05) c h);\n --surface-3: oklch(from var(--background) calc(l + 0.09) c h);\n\n /* Borders — foreground at reduced alpha.\n Semi-transparent so they adapt to any surface. */\n --border: oklch(from var(--foreground) l c h / 0.15);\n --border-strong: oklch(from var(--foreground) l c h / 0.25);\n\n /* Text tiers — foreground at reduced alpha.\n Semi-transparent so they adapt to any surface. */\n --text-1: oklch(from var(--foreground) l c h / 0.90);\n --text-2: oklch(from var(--foreground) l c h / 0.63);\n --text-3: oklch(from var(--foreground) l c h / 0.45);\n\n /* Accent surfaces — for badges, pills, tinted cards */\n --accent-subtle: oklch(from var(--accent) l c h / 0.10);\n --accent-text: oklch(from var(--accent) calc(l * 1.1) calc(c * 0.8) h);\n\n /* Destructive surfaces */\n --destructive-subtle: oklch(from var(--destructive) l c h / 0.10);\n --destructive-text: var(--destructive);\n\n /* Utility — scrim overlay for modals/dialogs */\n --scrim: oklch(from var(--foreground) l c h / 0.35);\n\n /* Meter / progress bar defaults */\n --meter-track: var(--surface-1);\n --meter-fill: var(--accent);\n\n /* Chart palette (theme-independent) */\n --chart-1: oklch(0.58 0.17 35);\n --chart-2: oklch(0.55 0.13 185);\n --chart-3: oklch(0.52 0.14 265);\n --chart-4: oklch(0.62 0.15 85);\n --chart-5: oklch(0.55 0.16 310);\n\n /* -----------------------------------------------------------\n * Prose (rich-text / markdown content)\n * ----------------------------------------------------------- */\n --prose-body: var(--text-1);\n --prose-headings: var(--foreground);\n --prose-lead: var(--text-2);\n --prose-links: var(--accent);\n --prose-links-hover: var(--accent-text);\n --prose-bold: var(--foreground);\n --prose-code: var(--foreground);\n --prose-code-bg: var(--surface-1);\n --prose-pre-bg: var(--surface-1);\n --prose-pre-border: var(--border);\n --prose-blockquote-border: var(--accent);\n --prose-blockquote-text: var(--text-2);\n --prose-hr: var(--border);\n --prose-th-border: var(--border-strong);\n --prose-td-border: var(--border);\n --prose-caption: var(--text-3);\n --prose-kbd-bg: var(--surface-2);\n --prose-kbd-border: var(--border-strong);\n}\n\n/* =================================================================\n * Base document styles\n * ================================================================= */\n:root {\n color: var(--text-1);\n font-family: var(--font-sans);\n line-height: var(--line-height-normal);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n/* =================================================================\n * Heading defaults\n * ================================================================= */\nh1 { font-size: var(--text-4xl); line-height: var(--text-4xl--line-height); } /* 36 / 40 */\nh2 { font-size: var(--text-3xl); line-height: var(--text-3xl--line-height); } /* 30 / 36 */\nh3 { font-size: var(--text-2xl); line-height: var(--text-2xl--line-height); } /* 24 / 32 */\nh4 { font-size: var(--text-xl); line-height: var(--text-xl--line-height); } /* 20 / 28 */\nh5 { font-size: var(--text-lg); line-height: var(--text-lg--line-height); } /* 18 / 28 */\nh6 { font-size: var(--text-base); line-height: var(--text-base--line-height); } /* 16 / 24 */\n\n/* =================================================================\n * Baseline grid trim — prose elements anchor visible glyph edges,\n * not CSS line-box edges, to the 4px grid.\n * ================================================================= */\nh1, h2, h3, h4, h5, h6,\np, li, blockquote, dt, dd,\nlabel, legend, caption, figcaption, th, td {\n text-box: trim-both cap alphabetic;\n}\n";
1
+ const tokensCSS = "/* =================================================================\n * DUI Design Tokens\n * =================================================================\n * Combined token system: spacing, typography, borders, elevation,\n * motion, colors, sizing, and focus.\n *\n * Tokens are declared on :root so they cascade through the entire\n * document, including into shadow DOM via CSS custom property\n * inheritance.\n *\n * Dark mode: apply data-theme=\"dark\" on <html> to switch palettes.\n * ================================================================= */\n\n:root {\n /* -----------------------------------------------------------\n * Spacing (Tailwind base-4 system)\n * ----------------------------------------------------------- */\n --space-0: 0;\n --space-px: 1px;\n --space-0_5: 0.125rem; /* 2px */\n --space-1: 0.25rem; /* 4px */\n --space-1_5: 0.375rem; /* 6px */\n --space-2: 0.5rem; /* 8px */\n --space-2_5: 0.625rem; /* 10px */\n --space-3: 0.75rem; /* 12px */\n --space-3_5: 0.875rem; /* 14px */\n --space-4: 1rem; /* 16px */\n --space-4_5: 1.125rem; /* 18px */\n --space-5: 1.25rem; /* 20px */\n --space-6: 1.5rem; /* 24px */\n --space-7: 1.75rem; /* 28px */\n --space-8: 2rem; /* 32px */\n --space-9: 2.25rem; /* 36px */\n --space-10: 2.5rem; /* 40px */\n --space-11: 2.75rem; /* 44px */\n --space-12: 3rem; /* 48px */\n --space-14: 3.5rem; /* 56px */\n --space-16: 4rem; /* 64px */\n --space-20: 5rem; /* 80px */\n --space-24: 6rem; /* 96px */\n --space-28: 7rem; /* 112px */\n --space-32: 8rem; /* 128px */\n --space-36: 9rem; /* 144px */\n --space-40: 10rem; /* 160px */\n --space-44: 11rem; /* 176px */\n --space-48: 12rem; /* 192px */\n --space-52: 13rem; /* 208px */\n --space-56: 14rem; /* 224px */\n --space-60: 15rem; /* 240px */\n --space-64: 16rem; /* 256px */\n --space-72: 18rem; /* 288px */\n --space-80: 20rem; /* 320px */\n --space-96: 24rem; /* 384px */\n\n /* -----------------------------------------------------------\n * Typography\n * ----------------------------------------------------------- */\n --font-sans:\n 'Inter', system-ui, -apple-system, sans-serif,\n \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-serif: ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif;\n --font-mono:\n 'JetBrains Mono', ui-monospace, SFMono-Regular, \"SF Mono\", Menlo,\n Consolas, \"Liberation Mono\", monospace;\n\n /* -----------------------------------------------------------\n * Type scale\n * -----------------------------------------------------------\n * Each size is paired with a line-height variable. The\n * line-height is a unitless ratio; multiplied by the size,\n * it yields the target leading (shown in comments).\n * ----------------------------------------------------------- */\n\n --text-2xs: 0.625rem; /* 10px */\n --text-2xs--line-height: calc(0.75 / 0.625); /* 12px */\n\n --text-xs: 0.75rem; /* 12px */\n --text-xs--line-height: calc(1 / 0.75); /* 16px */\n\n --text-sm: 0.875rem; /* 14px */\n --text-sm--line-height: calc(1.25 / 0.875); /* 20px */\n\n --text-base: 1rem; /* 16px */\n --text-base--line-height: calc(1.5 / 1); /* 24px */\n\n --text-lg: 1.125rem; /* 18px */\n --text-lg--line-height: calc(1.75 / 1.125); /* 28px */\n\n --text-xl: 1.25rem; /* 20px */\n --text-xl--line-height: calc(1.75 / 1.25); /* 28px */\n\n --text-2xl: 1.5rem; /* 24px */\n --text-2xl--line-height: calc(2 / 1.5); /* 32px */\n\n --text-3xl: 1.875rem; /* 30px */\n --text-3xl--line-height: calc(2.25 / 1.875); /* 36px */\n\n --text-4xl: 2.25rem; /* 36px */\n --text-4xl--line-height: calc(2.5 / 2.25); /* 40px */\n\n --text-5xl: 3rem; /* 48px */\n --text-5xl--line-height: 1;\n\n --text-6xl: 3.75rem; /* 60px */\n --text-6xl--line-height: 1;\n\n --text-7xl: 4.5rem; /* 72px */\n --text-7xl--line-height: 1;\n\n --text-8xl: 6rem; /* 96px */\n --text-8xl--line-height: 1;\n\n --text-9xl: 8rem; /* 128px */\n --text-9xl--line-height: 1;\n\n --letter-spacing-tightest: -0.02em;\n --letter-spacing-tighter: -0.015em;\n --letter-spacing-tight: -0.01em;\n --letter-spacing-normal: 0em;\n --letter-spacing-wide: 0.006em;\n --letter-spacing-wider: 0.012em;\n --letter-spacing-widest: 0.018em;\n\n --font-weight-regular: 400;\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n\n --line-height-none: 1;\n --line-height-tight: 1.25;\n --line-height-snug: 1.375;\n --line-height-normal: 1.5;\n --line-height-relaxed: 1.625;\n\n /* -----------------------------------------------------------\n * Borders\n * ----------------------------------------------------------- */\n --radius-none: 0;\n --radius-xs: 0.125rem;\n --radius-sm: 0.25rem;\n --radius-md: 0.5rem;\n --radius-lg: 1rem;\n --radius-xl: 1.5rem;\n --radius-2xl: 2rem;\n --radius-full: 9999px;\n\n --border-width-hairline: 0.5px;\n --border-width-thin: 1px;\n --border-width-medium: 2px;\n --border-width-thick: 4px;\n\n /* -----------------------------------------------------------\n * Elevation\n * ----------------------------------------------------------- */\n --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\n --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);\n --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);\n --shadow-none: 0 0 0 0 transparent;\n\n --z-base: 0;\n --z-dropdown: 700;\n --z-sticky: 800;\n --z-overlay: 900;\n --z-modal: 1000;\n --z-popover: 1100;\n --z-toast: 1200;\n --z-tooltip: 1300;\n\n /* -----------------------------------------------------------\n * Motion\n * ----------------------------------------------------------- */\n --duration-instant: 50ms;\n --duration-fastest: 75ms;\n --duration-faster: 100ms;\n --duration-fast: 150ms;\n --duration-normal: 250ms;\n --duration-slow: 400ms;\n --duration-slower: 700ms;\n\n --ease-1: cubic-bezier(0.25, 0, 0.5, 1);\n --ease-2: cubic-bezier(0.25, 0, 0.4, 1);\n --ease-3: cubic-bezier(0.25, 0, 0.3, 1);\n --ease-4: cubic-bezier(0.25, 0, 0.2, 1);\n --ease-5: cubic-bezier(0.25, 0, 0.1, 1);\n --ease-in-1: cubic-bezier(0.25, 0, 1, 1);\n --ease-in-2: cubic-bezier(0.5, 0, 1, 1);\n --ease-in-3: cubic-bezier(0.7, 0, 1, 1);\n --ease-in-4: cubic-bezier(0.9, 0, 1, 1);\n --ease-in-5: cubic-bezier(1, 0, 1, 1);\n --ease-out-1: cubic-bezier(0, 0, 0.75, 1);\n --ease-out-2: cubic-bezier(0, 0, 0.5, 1);\n --ease-out-3: cubic-bezier(0, 0, 0.3, 1);\n --ease-out-4: cubic-bezier(0, 0, 0.1, 1);\n --ease-out-5: cubic-bezier(0, 0, 0, 1);\n --ease-in-out-1: cubic-bezier(0.1, 0, 0.9, 1);\n --ease-in-out-2: cubic-bezier(0.3, 0, 0.7, 1);\n --ease-in-out-3: cubic-bezier(0.5, 0, 0.5, 1);\n --ease-in-out-4: cubic-bezier(0.7, 0, 0.3, 1);\n --ease-in-out-5: cubic-bezier(0.9, 0, 0.1, 1);\n\n --ease-sine-in: cubic-bezier(0.47, 0, 0.745, 0.715);\n --ease-sine-out: cubic-bezier(0.39, 0.575, 0.565, 1);\n --ease-sine-in-out: cubic-bezier(0.445, 0.05, 0.55, 0.95);\n --ease-quad-in: cubic-bezier(0.55, 0.085, 0.68, 0.53);\n --ease-quad-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);\n --ease-quad-in-out: cubic-bezier(0.455, 0.03, 0.515, 0.955);\n --ease-cubic-in: cubic-bezier(0.55, 0.055, 0.675, 0.19);\n --ease-cubic-out: cubic-bezier(0.215, 0.61, 0.355, 1);\n --ease-cubic-in-out: cubic-bezier(0.645, 0.045, 0.355, 1);\n --ease-quart-in: cubic-bezier(0.895, 0.03, 0.685, 0.22);\n --ease-quart-out: cubic-bezier(0.165, 0.84, 0.44, 1);\n --ease-quart-in-out: cubic-bezier(0.77, 0, 0.175, 1);\n --ease-quint-in: cubic-bezier(0.755, 0.05, 0.855, 0.06);\n --ease-quint-out: cubic-bezier(0.23, 1, 0.32, 1);\n --ease-quint-in-out: cubic-bezier(0.86, 0, 0.07, 1);\n --ease-expo-in: cubic-bezier(0.95, 0.05, 0.795, 0.035);\n --ease-expo-out: cubic-bezier(0.19, 1, 0.22, 1);\n --ease-expo-in-out: cubic-bezier(1, 0, 0, 1);\n --ease-circ-in: cubic-bezier(0.6, 0.04, 0.98, 0.335);\n --ease-circ-out: cubic-bezier(0.075, 0.82, 0.165, 1);\n --ease-circ-in-out: cubic-bezier(0.785, 0.135, 0.15, 0.86);\n\n /* -----------------------------------------------------------\n * Filters (presets for filter / backdrop-filter tokens)\n * ----------------------------------------------------------- */\n --filter-none: none;\n --filter-blur-sm: blur(4px);\n --filter-blur-md: blur(8px);\n --filter-blur-lg: blur(16px);\n --filter-blur-xl: blur(24px);\n --filter-brightness-dim: brightness(0.8);\n --filter-brightness-bright: brightness(1.15);\n --filter-saturate-muted: saturate(0.5);\n --filter-saturate-vivid: saturate(1.5);\n --filter-grayscale: grayscale(1);\n\n /* -----------------------------------------------------------\n * Clip Paths (shape presets)\n * ----------------------------------------------------------- */\n --clip-none: none;\n --clip-circle: circle(50%);\n --clip-squircle: inset(0 round 30%);\n --clip-hexagon: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);\n --clip-octagon: polygon(29.3% 0%, 70.7% 0%, 100% 29.3%, 100% 70.7%, 70.7% 100%, 29.3% 100%, 0% 70.7%, 0% 29.3%);\n --clip-chevron-right: polygon(0% 0%, 75% 0%, 100% 50%, 75% 100%, 0% 100%, 25% 50%);\n --clip-bevel: polygon(8% 0%, 92% 0%, 100% 8%, 100% 92%, 92% 100%, 8% 100%, 0% 92%, 0% 8%);\n\n /* -----------------------------------------------------------\n * Component Sizing\n * ----------------------------------------------------------- */\n --component-height-xxs: 1.25rem; /* 20px */\n --component-height-xs: 1.5rem; /* 24px */\n --component-height-sm: 1.75rem; /* 28px */\n --component-height-md: 2rem; /* 32px */\n --component-height-lg: 2.25rem; /* 36px */\n --component-height-xl: 2.5rem; /* 40px */\n\n /* -----------------------------------------------------------\n * Focus Ring\n * ----------------------------------------------------------- */\n --focus-ring-color: color-mix(in oklch, var(--accent), transparent 50%);\n --focus-ring-width: 3px;\n --focus-ring-offset: 0px;\n --focus-ring-duration: var(--duration-fast);\n --focus-ring-easing: var(--ease-out-3);\n\n /* -----------------------------------------------------------\n * Colors: Brand (theme-independent)\n * ----------------------------------------------------------- */\n --brand: oklch(0.728 0.1304 73.28);\n --brand-foreground: oklch(1 0.02 73.28);\n}\n\n/* =================================================================\n * COLOR SYSTEM\n * =================================================================\n * Built on two principles:\n * 1. Operations on a base — colors are relationships, not fixed values.\n * 2. Fewer primitives, more derivation — 4 primitives, everything\n * else computed via oklch(from ...) and color-mix().\n *\n * Layer 1: Primitives (only values a theme author picks)\n * Layer 2: Derived tokens (computed from primitives, never per-theme)\n * Layer 3: Interaction states (component-level, not tokens)\n * ================================================================= */\n\n/* -----------------------------------------------------------------\n * LIGHT THEME (default)\n * ----------------------------------------------------------------- */\n:root:not([data-theme=\"dark\"]) {\n /* --- Layer 1: Primitives --- */\n --background: oklch(0.97 0 0);\n --foreground: oklch(0.15 0 0);\n --accent: oklch(0.55 0.25 260);\n --destructive: oklch(0.55 0.22 25);\n --success: oklch(0.55 0.18 145);\n --warning: oklch(0.65 0.18 70);\n --info: oklch(0.55 0.18 230);\n}\n\n/* -----------------------------------------------------------------\n * DARK THEME\n * ----------------------------------------------------------------- */\n:root[data-theme=\"dark\"] {\n /* --- Layer 1: Primitives --- */\n --background: oklch(0.15 0.015 260);\n --foreground: oklch(0.93 0 0);\n --accent: oklch(0.75 0.18 260);\n --destructive: oklch(0.70 0.18 25);\n --success: oklch(0.72 0.16 145);\n --warning: oklch(0.78 0.15 70);\n --info: oklch(0.72 0.15 230);\n}\n\n/* -----------------------------------------------------------------\n * Layer 2: Derived tokens (computed from primitives)\n * Defined once — same rules for light and dark.\n * ----------------------------------------------------------------- */\n:root {\n /* Surfaces — lightness offsets from background.\n Positive = elevated (brighter). Negative = sunken (darker). */\n --sunken-2: oklch(from var(--background) calc(l - 0.03) c h);\n --sunken-1: oklch(from var(--background) calc(l - 0.01) c h);\n --surface-1: oklch(from var(--background) calc(l + 0.02) c h);\n --surface-2: oklch(from var(--background) calc(l + 0.05) c h);\n --surface-3: oklch(from var(--background) calc(l + 0.09) c h);\n\n /* Borders — foreground at reduced alpha.\n Semi-transparent so they adapt to any surface. */\n --border: oklch(from var(--foreground) l c h / 0.15);\n --border-strong: oklch(from var(--foreground) l c h / 0.25);\n\n /* Text tiers — foreground at reduced alpha.\n Semi-transparent so they adapt to any surface. */\n --text-1: oklch(from var(--foreground) l c h / 0.90);\n --text-2: oklch(from var(--foreground) l c h / 0.63);\n --text-3: oklch(from var(--foreground) l c h / 0.45);\n\n /* Accent surfaces — for badges, pills, tinted cards */\n --accent-subtle: oklch(from var(--accent) l c h / 0.10);\n --accent-text: oklch(from var(--accent) calc(l * 1.1) calc(c * 0.8) h);\n\n /* Destructive surfaces */\n --destructive-subtle: oklch(from var(--destructive) l c h / 0.10);\n --destructive-text: var(--destructive);\n\n /* Success surfaces */\n --success-subtle: oklch(from var(--success) l c h / 0.10);\n --success-text: oklch(from var(--success) calc(l * 1.05) calc(c * 0.9) h);\n\n /* Warning surfaces */\n --warning-subtle: oklch(from var(--warning) l c h / 0.10);\n --warning-text: oklch(from var(--warning) calc(l * 1.05) calc(c * 0.9) h);\n\n /* Info surfaces */\n --info-subtle: oklch(from var(--info) l c h / 0.10);\n --info-text: oklch(from var(--info) calc(l * 1.05) calc(c * 0.9) h);\n\n /* Utility — scrim overlay for modals/dialogs */\n --scrim: oklch(from var(--foreground) l c h / 0.35);\n\n /* Meter / progress bar defaults */\n --meter-track: var(--surface-1);\n --meter-fill: var(--accent);\n\n /* Chart palette (theme-independent) */\n --chart-1: oklch(0.58 0.17 35);\n --chart-2: oklch(0.55 0.13 185);\n --chart-3: oklch(0.52 0.14 265);\n --chart-4: oklch(0.62 0.15 85);\n --chart-5: oklch(0.55 0.16 310);\n\n /* -----------------------------------------------------------\n * Prose (rich-text / markdown content)\n * ----------------------------------------------------------- */\n --prose-body: var(--text-1);\n --prose-headings: var(--foreground);\n --prose-lead: var(--text-2);\n --prose-links: var(--accent);\n --prose-links-hover: var(--accent-text);\n --prose-bold: var(--foreground);\n --prose-code: var(--foreground);\n --prose-code-bg: var(--surface-1);\n --prose-pre-bg: var(--surface-1);\n --prose-pre-border: var(--border);\n --prose-blockquote-border: var(--accent);\n --prose-blockquote-text: var(--text-2);\n --prose-hr: var(--border);\n --prose-th-border: var(--border-strong);\n --prose-td-border: var(--border);\n --prose-caption: var(--text-3);\n --prose-kbd-bg: var(--surface-2);\n --prose-kbd-border: var(--border-strong);\n}\n\n/* =================================================================\n * Base document styles\n * ================================================================= */\n:root {\n color: var(--text-1);\n font-family: var(--font-sans);\n line-height: var(--line-height-normal);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n/* =================================================================\n * Heading defaults\n * ================================================================= */\nh1 { font-size: var(--text-4xl); line-height: var(--text-4xl--line-height); } /* 36 / 40 */\nh2 { font-size: var(--text-3xl); line-height: var(--text-3xl--line-height); } /* 30 / 36 */\nh3 { font-size: var(--text-2xl); line-height: var(--text-2xl--line-height); } /* 24 / 32 */\nh4 { font-size: var(--text-xl); line-height: var(--text-xl--line-height); } /* 20 / 28 */\nh5 { font-size: var(--text-lg); line-height: var(--text-lg--line-height); } /* 18 / 28 */\nh6 { font-size: var(--text-base); line-height: var(--text-base--line-height); } /* 16 / 24 */\n\n/* =================================================================\n * Baseline grid trim — prose elements anchor visible glyph edges,\n * not CSS line-box edges, to the 4px grid.\n * ================================================================= */\nh1, h2, h3, h4, h5, h6,\np, li, blockquote, dt, dd,\nlabel, legend, caption, figcaption, th, td {\n text-box: trim-both cap alphabetic;\n}\n";
2
2
  export { tokensCSS };
package/tokens/tokens.css CHANGED
@@ -296,6 +296,9 @@
296
296
  --foreground: oklch(0.15 0 0);
297
297
  --accent: oklch(0.55 0.25 260);
298
298
  --destructive: oklch(0.55 0.22 25);
299
+ --success: oklch(0.55 0.18 145);
300
+ --warning: oklch(0.65 0.18 70);
301
+ --info: oklch(0.55 0.18 230);
299
302
  }
300
303
 
301
304
  /* -----------------------------------------------------------------
@@ -307,6 +310,9 @@
307
310
  --foreground: oklch(0.93 0 0);
308
311
  --accent: oklch(0.75 0.18 260);
309
312
  --destructive: oklch(0.70 0.18 25);
313
+ --success: oklch(0.72 0.16 145);
314
+ --warning: oklch(0.78 0.15 70);
315
+ --info: oklch(0.72 0.15 230);
310
316
  }
311
317
 
312
318
  /* -----------------------------------------------------------------
@@ -341,6 +347,18 @@
341
347
  --destructive-subtle: oklch(from var(--destructive) l c h / 0.10);
342
348
  --destructive-text: var(--destructive);
343
349
 
350
+ /* Success surfaces */
351
+ --success-subtle: oklch(from var(--success) l c h / 0.10);
352
+ --success-text: oklch(from var(--success) calc(l * 1.05) calc(c * 0.9) h);
353
+
354
+ /* Warning surfaces */
355
+ --warning-subtle: oklch(from var(--warning) l c h / 0.10);
356
+ --warning-text: oklch(from var(--warning) calc(l * 1.05) calc(c * 0.9) h);
357
+
358
+ /* Info surfaces */
359
+ --info-subtle: oklch(from var(--info) l c h / 0.10);
360
+ --info-text: oklch(from var(--info) calc(l * 1.05) calc(c * 0.9) h);
361
+
344
362
  /* Utility — scrim overlay for modals/dialogs */
345
363
  --scrim: oklch(from var(--foreground) l c h / 0.35);
346
364