@bysages/core 0.0.0 → 0.1.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/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import tokensStyles from "@bysages/tokens/styles";
1
2
  //#region src/theme.ts
2
3
  /** Each scene pairs with a default accent — the pigment that carries its
3
4
  * context (civic identity is zhusha, never an error color; tech stays
@@ -6,7 +7,12 @@ const SCENE_DEFAULT_ACCENT = {
6
7
  civic: "zhusha",
7
8
  enterprise: "qinghua",
8
9
  studio: "celadon",
9
- tech: "ink"
10
+ tech: "ink",
11
+ cupertino: "ink",
12
+ expressive: "zhusha",
13
+ fluent: "qinghua",
14
+ material: "celadon",
15
+ sketch: "zhusha"
10
16
  };
11
17
  /** Each scene also pairs with a contrast tier — civic serves elders, so it
12
18
  * speaks at the loud tier by default. "auto" contrast resolves here;
@@ -15,7 +21,12 @@ const SCENE_DEFAULT_CONTRAST = {
15
21
  civic: "high",
16
22
  enterprise: "normal",
17
23
  studio: "normal",
18
- tech: "normal"
24
+ tech: "normal",
25
+ cupertino: "normal",
26
+ expressive: "normal",
27
+ fluent: "normal",
28
+ material: "normal",
29
+ sketch: "normal"
19
30
  };
20
31
  const STORAGE_KEY = "bs-theme";
21
32
  const DARK_QUERY = "(prefers-color-scheme: dark)";
@@ -205,19 +216,25 @@ function detachDynamicLight() {
205
216
  //#endregion
206
217
  //#region src/ink-ripple.ts
207
218
  /**
208
- * Ink ripple — the press feedback of the paper-and-ink register. Where
209
- * Material spreads a hard circle, ink bleeds: a soft wash of the element's
210
- * own pigment diluting outward from the press point, gone before it draws
211
- * attention to itself.
219
+ * Ink ripple — the press feedback of the paper-and-ink register: a wash of
220
+ * the element's own pigment bleeding outward from the press point. The
221
+ * bleed is two-phase, answering the hand rather than the click — it spreads
222
+ * while the pointer holds (`data-ripple="press"`) and dissolves when it
223
+ * lifts (`data-ripple="release"`). A scene retunes the wash through the
224
+ * `--bs-ripple-*` tokens; a register that presses without a wash silences
225
+ * it with a zero opacity.
212
226
  *
213
227
  * The bleed's CSS lives in the base stylesheet, keyed on
214
- * `data-motion~="ink-ripple"`; this module watches presses and marks the
215
- * press point. Reduced motion collapses the bleed to a state change, as
228
+ * `data-motion~="ink-ripple"`; this module watches pointers and marks the
229
+ * phase. Reduced motion collapses the bleed to a state change, as
216
230
  * everywhere else in the system.
217
231
  *
218
232
  * @module
219
233
  */
220
234
  const RIPPLE_SELECTOR = "[data-motion~='ink-ripple']";
235
+ /** A wash released before this long still plays out to here before it
236
+ * dissolves — a quick tap earns a visible wave, not a flicker. */
237
+ const MIN_PRESS_MS = 225;
221
238
  let detachPress;
222
239
  /** Watch for presses anywhere inside `root` (default: the document) and
223
240
  * bleed ink from the press point of the nearest opt-in element. Safe to
@@ -225,9 +242,7 @@ let detachPress;
225
242
  function attachInkRipple(root) {
226
243
  detachInkRipple();
227
244
  if (typeof document === "undefined") return () => {};
228
- const onEnd = (event) => {
229
- if (event.target instanceof HTMLElement) delete event.target.dataset.ripple;
230
- };
245
+ const held = /* @__PURE__ */ new Map();
231
246
  const onDown = (event) => {
232
247
  if (!(event instanceof PointerEvent)) return;
233
248
  if (event.pointerType !== "touch" && event.button !== 0) return;
@@ -236,18 +251,44 @@ function attachInkRipple(root) {
236
251
  const host = target.closest(RIPPLE_SELECTOR);
237
252
  if (!(host instanceof HTMLElement)) return;
238
253
  const rect = host.getBoundingClientRect();
239
- host.style.setProperty("--bs-ripple-x", `${(event.clientX - rect.left).toFixed(1)}px`);
240
- host.style.setProperty("--bs-ripple-y", `${(event.clientY - rect.top).toFixed(1)}px`);
254
+ const px = event.clientX - rect.left;
255
+ const py = event.clientY - rect.top;
256
+ host.style.setProperty("--bs-ripple-x", `${px.toFixed(1)}px`);
257
+ host.style.setProperty("--bs-ripple-y", `${py.toFixed(1)}px`);
258
+ host.style.setProperty("--bs-ripple-dx", `${(rect.width / 2 - px).toFixed(1)}px`);
259
+ host.style.setProperty("--bs-ripple-dy", `${(rect.height / 2 - py).toFixed(1)}px`);
241
260
  delete host.dataset.ripple;
242
261
  host.offsetWidth;
243
- host.dataset.ripple = "run";
262
+ host.dataset.ripple = "press";
263
+ held.set(event.pointerId, {
264
+ host,
265
+ at: performance.now()
266
+ });
267
+ };
268
+ const onUp = (event) => {
269
+ if (!(event instanceof PointerEvent)) return;
270
+ const press = held.get(event.pointerId);
271
+ if (!press) return;
272
+ held.delete(event.pointerId);
273
+ const remain = MIN_PRESS_MS - (performance.now() - press.at);
274
+ if (remain > 0) setTimeout(() => {
275
+ if (press.host.dataset.ripple === "press") press.host.dataset.ripple = "release";
276
+ }, remain);
277
+ else press.host.dataset.ripple = "release";
278
+ };
279
+ const onAnimationEnd = (event) => {
280
+ if (event.target instanceof HTMLElement && event.target.dataset.ripple === "release") delete event.target.dataset.ripple;
244
281
  };
245
282
  const scope = root ?? document;
246
283
  scope.addEventListener("pointerdown", onDown, { passive: true });
247
- scope.addEventListener("animationend", onEnd, true);
284
+ scope.addEventListener("pointerup", onUp, { passive: true });
285
+ scope.addEventListener("pointercancel", onUp, { passive: true });
286
+ scope.addEventListener("animationend", onAnimationEnd, true);
248
287
  detachPress = () => {
249
288
  scope.removeEventListener("pointerdown", onDown);
250
- scope.removeEventListener("animationend", onEnd, true);
289
+ scope.removeEventListener("pointerup", onUp);
290
+ scope.removeEventListener("pointercancel", onUp);
291
+ scope.removeEventListener("animationend", onAnimationEnd, true);
251
292
  };
252
293
  return detachPress;
253
294
  }
@@ -257,10 +298,6 @@ function detachInkRipple() {
257
298
  detachPress = void 0;
258
299
  }
259
300
  //#endregion
260
- //#region ../tokens/dist/js/styles.mjs
261
- /** Stylesheet for the full token layer, generated from dist/css/index.css. */
262
- var styles_default = "@layer bs.tokens;\n\n@layer bs.tokens {\n /**\n * Do not edit directly, this file was auto-generated.\n */\n\n :root, :host {\n --bs-color-gray-25: oklch(0.985 0.003 92);\n --bs-color-gray-50: oklch(0.968 0.005 90);\n --bs-color-gray-100: oklch(0.928 0.007 88);\n --bs-color-gray-200: oklch(0.872 0.009 86);\n --bs-color-gray-300: oklch(0.795 0.011 84);\n --bs-color-gray-400: oklch(0.665 0.013 82);\n --bs-color-gray-500: oklch(0.568 0.014 80);\n --bs-color-gray-600: oklch(0.482 0.014 78);\n --bs-color-gray-700: oklch(0.405 0.013 76);\n --bs-color-gray-800: oklch(0.318 0.011 76);\n --bs-color-gray-850: oklch(0.272 0.01 76);\n --bs-color-gray-900: oklch(0.23 0.009 76);\n --bs-color-gray-925: oklch(0.188 0.008 76);\n --bs-color-gray-950: oklch(0.15 0.007 76);\n --bs-color-gray-975: oklch(0.118 0.006 76);\n --bs-color-brand-50: oklch(0.962 0.014 255);\n --bs-color-brand-100: oklch(0.93 0.031 255);\n --bs-color-brand-200: oklch(0.885 0.056 255);\n --bs-color-brand-300: oklch(0.815 0.09 255);\n --bs-color-brand-400: oklch(0.72 0.122 255);\n --bs-color-brand-500: oklch(0.6 0.15 255);\n --bs-color-brand-600: oklch(0.53 0.155 255);\n --bs-color-brand-700: oklch(0.46 0.15 255);\n --bs-color-brand-800: oklch(0.395 0.13 255);\n --bs-color-brand-900: oklch(0.33 0.105 255);\n --bs-color-brand-950: oklch(0.26 0.08 255);\n --bs-color-celadon-50: oklch(0.962 0.014 205);\n --bs-color-celadon-100: oklch(0.928 0.028 205);\n --bs-color-celadon-200: oklch(0.88 0.05 205);\n --bs-color-celadon-300: oklch(0.81 0.072 205);\n --bs-color-celadon-400: oklch(0.715 0.082 205);\n --bs-color-celadon-500: oklch(0.63 0.085 205);\n --bs-color-celadon-600: oklch(0.56 0.085 205);\n --bs-color-celadon-700: oklch(0.485 0.08 205);\n --bs-color-celadon-800: oklch(0.41 0.068 205);\n --bs-color-celadon-900: oklch(0.34 0.054 205);\n --bs-color-celadon-950: oklch(0.265 0.04 205);\n --bs-color-zhusha-50: oklch(0.964 0.016 28);\n --bs-color-zhusha-100: oklch(0.926 0.036 28);\n --bs-color-zhusha-200: oklch(0.874 0.066 28);\n --bs-color-zhusha-300: oklch(0.806 0.098 28);\n --bs-color-zhusha-400: oklch(0.716 0.13 28);\n --bs-color-zhusha-500: oklch(0.625 0.15 28);\n --bs-color-zhusha-600: oklch(0.55 0.16 28);\n --bs-color-zhusha-700: oklch(0.472 0.148 28);\n --bs-color-zhusha-800: oklch(0.398 0.125 28);\n --bs-color-zhusha-900: oklch(0.328 0.098 28);\n --bs-color-zhusha-950: oklch(0.254 0.072 28);\n --bs-color-success-50: oklch(0.962 0.024 155);\n --bs-color-success-100: oklch(0.922 0.048 155);\n --bs-color-success-200: oklch(0.868 0.082 155);\n --bs-color-success-300: oklch(0.794 0.108 155);\n --bs-color-success-400: oklch(0.71 0.122 155);\n --bs-color-success-500: oklch(0.6 0.112 155);\n --bs-color-success-600: oklch(0.52 0.1 155);\n --bs-color-success-700: oklch(0.445 0.088 155);\n --bs-color-success-800: oklch(0.375 0.072 155);\n --bs-color-success-900: oklch(0.31 0.056 155);\n --bs-color-success-950: oklch(0.24 0.04 155);\n --bs-color-warning-50: oklch(0.968 0.028 78);\n --bs-color-warning-100: oklch(0.934 0.056 74);\n --bs-color-warning-200: oklch(0.892 0.086 70);\n --bs-color-warning-300: oklch(0.836 0.112 67);\n --bs-color-warning-400: oklch(0.76 0.126 66);\n --bs-color-warning-500: oklch(0.665 0.125 65);\n --bs-color-warning-600: oklch(0.55 0.122 65);\n --bs-color-warning-700: oklch(0.495 0.11 63);\n --bs-color-warning-800: oklch(0.415 0.092 62);\n --bs-color-warning-900: oklch(0.34 0.074 62);\n --bs-color-warning-950: oklch(0.262 0.054 62);\n --bs-color-danger-50: oklch(0.964 0.018 25);\n --bs-color-danger-100: oklch(0.924 0.04 25);\n --bs-color-danger-200: oklch(0.87 0.07 25);\n --bs-color-danger-300: oklch(0.798 0.106 25);\n --bs-color-danger-400: oklch(0.712 0.138 25);\n --bs-color-danger-500: oklch(0.6 0.15 25);\n --bs-color-danger-600: oklch(0.51 0.15 25);\n --bs-color-danger-700: oklch(0.44 0.135 25);\n --bs-color-danger-800: oklch(0.372 0.112 25);\n --bs-color-danger-900: oklch(0.306 0.088 25);\n --bs-color-danger-950: oklch(0.236 0.064 25);\n --bs-color-info-50: oklch(0.962 0.014 250);\n --bs-color-info-100: oklch(0.924 0.03 250);\n --bs-color-info-200: oklch(0.872 0.054 250);\n --bs-color-info-300: oklch(0.802 0.082 250);\n --bs-color-info-400: oklch(0.718 0.11 250);\n --bs-color-info-500: oklch(0.6 0.125 250);\n --bs-color-info-600: oklch(0.505 0.125 250);\n --bs-color-info-700: oklch(0.432 0.112 250);\n --bs-color-info-800: oklch(0.364 0.092 250);\n --bs-color-info-900: oklch(0.3 0.072 250);\n --bs-color-info-950: oklch(0.232 0.052 250);\n --bs-space-1: 0.25rem;\n --bs-space-2: 0.5rem;\n --bs-space-3: 0.75rem;\n --bs-space-4: 1rem;\n --bs-space-5: 1.25rem;\n --bs-space-6: 1.5rem;\n --bs-space-7: 1.75rem;\n --bs-space-8: 2rem;\n --bs-space-10: 2.5rem;\n --bs-space-12: 3rem;\n --bs-space-16: 4rem;\n --bs-space-20: 5rem;\n --bs-space-24: 6rem;\n --bs-radius-none: 0rem;\n --bs-radius-xs: 0.25rem;\n --bs-radius-sm: 0.375rem;\n --bs-radius-md: 0.5rem;\n --bs-radius-lg: 0.75rem;\n --bs-radius-xl: 1rem;\n --bs-radius-2xl: 1.5rem;\n --bs-radius-full: 9999px;\n }\n\n\n /* Light theme — light as shadow, the paper-and-ink register: hierarchy reads through\n the paper ladder (the ground sits in ambient shade, raised surfaces catch\n the light), hairline borders, and restrained cast shadows in warm ink.\n Controls stay crisp — a paper-white surface, one hairline, and shadow-xs.\n Primary defaults to ink so the interface stays monochrome and solemn; the\n pigment accents (qinghua, celadon, zhusha) live in accent.css. */\n\n :root,\n :host,\n [data-theme=\"light\"],\n :host([data-theme=\"light\"]) {\n color-scheme: light;\n\n /* Surfaces: the ground rests in ambient shade; raised surfaces catch the\n light. Warm paper whites throughout — pure #ffffff never appears, it\n would read clinical against the ink. */\n --bs-color-surface-0: var(--bs-color-gray-50);\n --bs-color-surface-1: var(--bs-color-gray-25);\n --bs-color-surface-2: oklch(0.988 0.004 95);\n --bs-color-surface-3: oklch(0.988 0.004 95);\n --bs-color-surface-4: oklch(0.988 0.004 95);\n --bs-color-surface-5: oklch(0.988 0.004 95);\n --bs-color-surface-inset: var(--bs-color-gray-100);\n /* The inverted surface — ink on paper — hosts floating labels like\n tooltips that must read against the page, never blend into it. */\n --bs-color-surface-inverse: var(--bs-color-gray-800);\n --bs-color-scrim: oklch(0.15 0.007 76 / 0.4);\n\n --bs-color-text-primary: var(--bs-color-gray-900);\n --bs-color-text-secondary: var(--bs-color-gray-600);\n --bs-color-text-tertiary: var(--bs-color-gray-500);\n --bs-color-text-disabled: var(--bs-color-gray-400);\n --bs-color-text-inverse: var(--bs-color-surface-2);\n /* Ink on a solid pigment fill: paper under daylight, kneaded bright\n under lacquer night (see the dark theme). */\n --bs-color-ink-on-fill: var(--bs-color-surface-2);\n --bs-ink-knead: 0%;\n\n --bs-color-border: var(--bs-color-gray-200);\n --bs-color-border-strong: var(--bs-color-gray-300);\n\n /* Focus is a halo of light: a crisp inner line inside a soft, wide glow.\n The inset variant serves parts flush against a container edge (steppers),\n where an outer halo would spill past it. */\n --bs-color-focus: var(--bs-color-primary);\n --bs-focus-ring-color: color-mix(in oklab, var(--bs-color-focus) 16%, transparent);\n --bs-focus-ring-width: 8px;\n --bs-focus-ring:\n 0 0 0 1px var(--bs-color-focus), 0 0 0 var(--bs-focus-ring-width) var(--bs-focus-ring-color);\n --bs-focus-ring-inset:\n inset 0 0 0 1px var(--bs-color-focus), inset 0 0 0 4px var(--bs-focus-ring-color);\n\n /* Primary is ink by default — solemn, unhurried, industry-neutral. The\n hover deepens the ink into the paper; the pigment accents override the\n whole block (see accent.css). */\n --bs-color-primary: var(--bs-color-gray-800);\n --bs-color-primary-hover: var(--bs-color-gray-900);\n --bs-color-primary-active: var(--bs-color-gray-925);\n --bs-color-primary-text: var(--bs-color-surface-2);\n /* The solid-fill body: pigments rest at full strength on paper — night\n kneads them (see the dark theme), daylight does not. */\n --bs-color-primary-fill: var(--bs-color-primary);\n --bs-color-primary-fill-hover: var(--bs-color-primary-hover);\n --bs-color-primary-subtle: var(--bs-color-gray-100);\n --bs-color-primary-subtle-text: var(--bs-color-gray-800);\n --bs-color-primary-border: var(--bs-color-gray-300);\n\n /* Fixed semantic pigments — bamboo, ochre, cinnabar, ultramarine. They do\n not follow the accent theme: an error is always cinnabar-red, a warning\n always ochre, whatever pigment the brand rides. */\n --bs-color-success: var(--bs-color-success-600);\n --bs-color-success-hover: var(--bs-color-success-700);\n --bs-color-success-subtle: var(--bs-color-success-100);\n --bs-color-success-subtle-text: var(--bs-color-success-800);\n\n --bs-color-warning: var(--bs-color-warning-600);\n --bs-color-warning-hover: var(--bs-color-warning-700);\n --bs-color-warning-subtle: var(--bs-color-warning-100);\n --bs-color-warning-subtle-text: var(--bs-color-warning-800);\n\n --bs-color-danger: var(--bs-color-danger-600);\n --bs-color-danger-hover: var(--bs-color-danger-700);\n --bs-color-danger-subtle: var(--bs-color-danger-100);\n --bs-color-danger-subtle-text: var(--bs-color-danger-800);\n\n --bs-color-info: var(--bs-color-info-600);\n --bs-color-info-hover: var(--bs-color-info-700);\n --bs-color-info-subtle: var(--bs-color-info-100);\n --bs-color-info-subtle-text: var(--bs-color-info-800);\n\n /* Lighting — elevation shadows are computed, never copy-pasted. Every\n shadow draws its ink from --bs-shadow-ink, this theme's warm shadow hue,\n shifts with --bs-light-x/-y (where the key light stands), and spreads\n with --bs-light-reach (how high the surface floats). Custom properties\n resolve their own var() references at the declaring element, so these\n composite tokens always carry theme ink; a colored surface that casts in\n its own color composes its shadow inline from these parts mixed with its\n tint — see the dialog trigger. Restraint ladder: controls carry only\n shadow-xs; cards rise on elevation-1/2; menus and popovers separate on\n elevation-3; dialogs own elevation-4/5. */\n --bs-shadow-ink: oklch(0.3 0.02 80);\n --bs-light-x: 0px;\n --bs-light-y: 0px;\n --bs-light-reach: 1;\n /* Weighted hairlines: the 1px edge everything rides, and the 3px\n status edge an alert earns. */\n --bs-hairline: 1px;\n --bs-hairline-strong: 3px;\n --bs-shadow-xs: var(--bs-light-x) calc(1px * var(--bs-light-reach) + var(--bs-light-y))\n calc(2px * var(--bs-light-reach)) 0 color-mix(in oklab, var(--bs-shadow-ink) 5%, transparent);\n --bs-elevation-0: none;\n --bs-elevation-1: var(--bs-light-x) calc(1px * var(--bs-light-reach) + var(--bs-light-y))\n calc(2px * var(--bs-light-reach)) 0 color-mix(in oklab, var(--bs-shadow-ink) 7%, transparent);\n --bs-elevation-2:\n var(--bs-light-x) calc(1px * var(--bs-light-reach) + var(--bs-light-y))\n calc(2px * var(--bs-light-reach)) 0 color-mix(in oklab, var(--bs-shadow-ink) 6%, transparent),\n var(--bs-light-x) calc(3px * var(--bs-light-reach) + var(--bs-light-y))\n calc(8px * var(--bs-light-reach)) calc(-2px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 9%, transparent);\n --bs-elevation-3:\n var(--bs-light-x) calc(2px * var(--bs-light-reach) + var(--bs-light-y))\n calc(4px * var(--bs-light-reach)) calc(-2px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 7%, transparent),\n var(--bs-light-x) calc(5px * var(--bs-light-reach) + var(--bs-light-y))\n calc(12px * var(--bs-light-reach)) calc(-3px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 10%, transparent),\n var(--bs-light-x) calc(12px * var(--bs-light-reach) + var(--bs-light-y))\n calc(24px * var(--bs-light-reach)) calc(-6px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 8%, transparent);\n --bs-elevation-4:\n var(--bs-light-x) calc(4px * var(--bs-light-reach) + var(--bs-light-y))\n calc(8px * var(--bs-light-reach)) calc(-4px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 8%, transparent),\n var(--bs-light-x) calc(10px * var(--bs-light-reach) + var(--bs-light-y))\n calc(28px * var(--bs-light-reach)) calc(-6px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 12%, transparent),\n var(--bs-light-x) calc(28px * var(--bs-light-reach) + var(--bs-light-y))\n calc(56px * var(--bs-light-reach)) calc(-12px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 12%, transparent);\n --bs-elevation-5:\n var(--bs-light-x) calc(6px * var(--bs-light-reach) + var(--bs-light-y))\n calc(12px * var(--bs-light-reach)) calc(-6px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 10%, transparent),\n var(--bs-light-x) calc(16px * var(--bs-light-reach) + var(--bs-light-y))\n calc(40px * var(--bs-light-reach)) calc(-8px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 14%, transparent),\n var(--bs-light-x) calc(40px * var(--bs-light-reach) + var(--bs-light-y))\n calc(80px * var(--bs-light-reach)) calc(-16px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 14%, transparent);\n }\n\n\n /* Dark theme — lacquer night: the room dims into warm lacquer black,\n and text turns to paper. Surfaces still brighten as they rise (e0 darkest\n → e5 lightest) — at night the ladder of light, not the cast shadow, does\n the separating. Primary inverts: paper-white buttons set ink on fire\n gently against the dark. */\n\n [data-theme=\"dark\"],\n :host([data-theme=\"dark\"]) {\n color-scheme: dark;\n\n /* Lacquer surfaces: warm black at every step, never pure #000. */\n --bs-color-surface-0: oklch(0.165 0.008 75);\n --bs-color-surface-1: oklch(0.205 0.009 75);\n --bs-color-surface-2: oklch(0.245 0.01 76);\n --bs-color-surface-3: oklch(0.272 0.01 76);\n --bs-color-surface-4: oklch(0.29 0.01 76);\n --bs-color-surface-5: oklch(0.31 0.01 76);\n --bs-color-surface-inset: oklch(0.135 0.007 75);\n /* The inverted surface — paper on lacquer — hosts floating labels like\n tooltips; at night the inversion reads as a slip of bright paper. */\n --bs-color-surface-inverse: var(--bs-color-gray-100);\n --bs-color-scrim: oklch(0.05 0.01 75 / 0.6);\n\n --bs-color-text-primary: oklch(0.925 0.006 90);\n --bs-color-text-secondary: var(--bs-color-gray-300);\n --bs-color-text-tertiary: var(--bs-color-gray-400);\n --bs-color-text-disabled: var(--bs-color-gray-500);\n --bs-color-text-inverse: var(--bs-color-gray-900);\n /* Ink on a solid pigment fill: pigments knead a fifth of ink into their\n bodies at night (a mid-tone fill carries neither deep nor pale text\n past 4.5:1), so the ink turns bright paper. */\n --bs-color-ink-on-fill: var(--bs-color-gray-25);\n --bs-ink-knead: 20%;\n\n --bs-color-border: oklch(0.31 0.01 78);\n --bs-color-border-strong: oklch(0.4 0.012 78);\n\n /* The halo reads brighter at night, like any light source does. */\n --bs-color-focus: var(--bs-color-primary);\n --bs-focus-ring-color: color-mix(in oklab, var(--bs-color-focus) 22%, transparent);\n --bs-focus-ring-width: 8px;\n --bs-focus-ring:\n 0 0 0 1px var(--bs-color-focus), 0 0 0 var(--bs-focus-ring-width) var(--bs-focus-ring-color);\n --bs-focus-ring-inset:\n inset 0 0 0 1px var(--bs-color-focus), inset 0 0 0 4px var(--bs-focus-ring-color);\n\n /* Primary inverts to paper — ink set down on lacquer. */\n --bs-color-primary: oklch(0.93 0.007 90);\n --bs-color-primary-hover: var(--bs-color-gray-200);\n --bs-color-primary-active: var(--bs-color-gray-300);\n --bs-color-primary-text: var(--bs-color-gray-900);\n --bs-color-primary-fill: var(--bs-color-primary);\n --bs-color-primary-fill-hover: var(--bs-color-primary-hover);\n --bs-color-primary-subtle: color-mix(in oklab, var(--bs-color-primary) 10%, transparent);\n --bs-color-primary-subtle-text: var(--bs-color-gray-200);\n --bs-color-primary-border: var(--bs-color-gray-700);\n\n /* Semantic pigments lift to their lighter steps at night; the fixed hue\n relationships (bamboo / ochre / cinnabar / ultramarine) never change. */\n --bs-color-success: var(--bs-color-success-500);\n --bs-color-success-hover: var(--bs-color-success-400);\n --bs-color-success-subtle: color-mix(in oklab, var(--bs-color-success) 16%, transparent);\n --bs-color-success-subtle-text: var(--bs-color-success-300);\n\n --bs-color-warning: var(--bs-color-warning-500);\n --bs-color-warning-hover: var(--bs-color-warning-400);\n --bs-color-warning-subtle: color-mix(in oklab, var(--bs-color-warning) 16%, transparent);\n --bs-color-warning-subtle-text: var(--bs-color-warning-300);\n\n --bs-color-danger: var(--bs-color-danger-500);\n --bs-color-danger-hover: var(--bs-color-danger-400);\n --bs-color-danger-subtle: color-mix(in oklab, var(--bs-color-danger) 16%, transparent);\n --bs-color-danger-subtle-text: var(--bs-color-danger-300);\n\n --bs-color-info: var(--bs-color-info-500);\n --bs-color-info-hover: var(--bs-color-info-400);\n --bs-color-info-subtle: color-mix(in oklab, var(--bs-color-info) 16%, transparent);\n --bs-color-info-subtle-text: var(--bs-color-info-300);\n\n /* Elevation in the dark: cast shadows need far more opacity to read against\n a dark ground, so every level carries a faint warm hairline of light to\n cut the raised surface out of the one below it. Same lighting parts as\n light — ink from --bs-shadow-ink, shifted by --bs-light-x/-y, spread by\n --bs-light-reach; colored surfaces compose their tint inline. */\n --bs-shadow-ink: oklch(0.04 0.01 75);\n --bs-light-x: 0px;\n --bs-light-y: 0px;\n --bs-light-reach: 1;\n --bs-hairline: 1px;\n --bs-hairline-strong: 3px;\n --bs-shadow-xs: var(--bs-light-x) calc(1px * var(--bs-light-reach) + var(--bs-light-y))\n calc(2px * var(--bs-light-reach)) 0 color-mix(in oklab, var(--bs-shadow-ink) 35%, transparent);\n --bs-elevation-0: none;\n --bs-elevation-1:\n 0 0 0 1px oklch(0.95 0.01 90 / 0.04),\n var(--bs-light-x) calc(1px * var(--bs-light-reach) + var(--bs-light-y))\n calc(2px * var(--bs-light-reach)) 0 color-mix(in oklab, var(--bs-shadow-ink) 32%, transparent);\n --bs-elevation-2:\n 0 0 0 1px oklch(0.95 0.01 90 / 0.05),\n var(--bs-light-x) calc(2px * var(--bs-light-reach) + var(--bs-light-y))\n calc(4px * var(--bs-light-reach)) calc(-1px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 40%, transparent),\n var(--bs-light-x) calc(4px * var(--bs-light-reach) + var(--bs-light-y))\n calc(12px * var(--bs-light-reach)) calc(-2px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 36%, transparent);\n --bs-elevation-3:\n 0 0 0 1px oklch(0.95 0.01 90 / 0.06),\n var(--bs-light-x) calc(4px * var(--bs-light-reach) + var(--bs-light-y))\n calc(8px * var(--bs-light-reach)) calc(-3px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 44%, transparent),\n var(--bs-light-x) calc(10px * var(--bs-light-reach) + var(--bs-light-y))\n calc(24px * var(--bs-light-reach)) calc(-4px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 40%, transparent);\n --bs-elevation-4:\n 0 0 0 1px oklch(0.95 0.01 90 / 0.07),\n var(--bs-light-x) calc(8px * var(--bs-light-reach) + var(--bs-light-y))\n calc(16px * var(--bs-light-reach)) calc(-6px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 48%, transparent),\n var(--bs-light-x) calc(24px * var(--bs-light-reach) + var(--bs-light-y))\n calc(48px * var(--bs-light-reach)) calc(-8px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 44%, transparent);\n --bs-elevation-5:\n 0 0 0 1px oklch(0.95 0.01 90 / 0.08),\n var(--bs-light-x) calc(12px * var(--bs-light-reach) + var(--bs-light-y))\n calc(24px * var(--bs-light-reach)) calc(-8px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 52%, transparent),\n var(--bs-light-x) calc(40px * var(--bs-light-reach) + var(--bs-light-y))\n calc(80px * var(--bs-light-reach)) calc(-12px * var(--bs-light-reach))\n color-mix(in oklab, var(--bs-shadow-ink) 48%, transparent);\n }\n\n\n /* Accent themes — the pigments. Primary is ink by default (theme-light/dark);\n switching [data-accent] lays a different mineral pigment under the same\n interface: qinghua (cobalt, the blue of blue-and-white porcelain), celadon\n (aquatic green-blue), zhusha (cinnabar, the seal-paste red used sparingly\n by civic sites for identity — never for errors; danger stays cinnabar-red\n by way of --bs-color-danger, a separate token). Each accent redefines the\n primary block and the focus hue; the fixed semantic pigments never follow. */\n\n [data-accent=\"qinghua\"] {\n --bs-color-primary: var(--bs-color-brand-600);\n --bs-color-primary-hover: var(--bs-color-brand-700);\n --bs-color-primary-active: var(--bs-color-brand-800);\n --bs-color-primary-text: oklch(0.988 0.004 95);\n --bs-color-primary-subtle: var(--bs-color-brand-100);\n --bs-color-primary-subtle-text: var(--bs-color-brand-800);\n --bs-color-primary-border: var(--bs-color-brand-300);\n --bs-color-focus: var(--bs-color-primary);\n }\n\n [data-theme=\"dark\"] [data-accent=\"qinghua\"],\n [data-theme=\"dark\"][data-accent=\"qinghua\"],\n :host([data-theme=\"dark\"][data-accent=\"qinghua\"]) {\n --bs-color-primary: var(--bs-color-brand-500);\n --bs-color-primary-hover: var(--bs-color-brand-600);\n --bs-color-primary-active: var(--bs-color-brand-700);\n /* A mid-tone pigment body carries neither deep nor pale text past\n 4.5:1, so the solid fill kneads toward black and reads with bright\n paper ink. */\n --bs-color-primary-fill: color-mix(in oklab, var(--bs-color-brand-500) 80%, black);\n --bs-color-primary-fill-hover: color-mix(in oklab, var(--bs-color-brand-500) 70%, black);\n --bs-color-primary-text: var(--bs-color-gray-25);\n --bs-color-primary-subtle: color-mix(in oklab, var(--bs-color-primary) 16%, transparent);\n --bs-color-primary-subtle-text: var(--bs-color-brand-300);\n --bs-color-primary-border: var(--bs-color-brand-700);\n }\n\n [data-accent=\"celadon\"] {\n --bs-color-primary: var(--bs-color-celadon-600);\n --bs-color-primary-hover: var(--bs-color-celadon-700);\n --bs-color-primary-active: var(--bs-color-celadon-800);\n --bs-color-primary-text: oklch(0.988 0.004 95);\n --bs-color-primary-subtle: var(--bs-color-celadon-100);\n --bs-color-primary-subtle-text: var(--bs-color-celadon-800);\n --bs-color-primary-border: var(--bs-color-celadon-300);\n --bs-color-focus: var(--bs-color-primary);\n }\n\n [data-theme=\"dark\"] [data-accent=\"celadon\"],\n [data-theme=\"dark\"][data-accent=\"celadon\"],\n :host([data-theme=\"dark\"][data-accent=\"celadon\"]) {\n --bs-color-primary: var(--bs-color-celadon-500);\n --bs-color-primary-hover: var(--bs-color-celadon-600);\n --bs-color-primary-active: var(--bs-color-celadon-700);\n /* A mid-tone pigment body carries neither deep nor pale text past\n 4.5:1, so the solid fill kneads toward black and reads with bright\n paper ink. */\n --bs-color-primary-fill: color-mix(in oklab, var(--bs-color-celadon-500) 80%, black);\n --bs-color-primary-fill-hover: color-mix(in oklab, var(--bs-color-celadon-500) 70%, black);\n --bs-color-primary-text: var(--bs-color-gray-25);\n --bs-color-primary-subtle: color-mix(in oklab, var(--bs-color-primary) 16%, transparent);\n --bs-color-primary-subtle-text: var(--bs-color-celadon-300);\n --bs-color-primary-border: var(--bs-color-celadon-700);\n }\n\n [data-accent=\"zhusha\"] {\n --bs-color-primary: var(--bs-color-zhusha-600);\n --bs-color-primary-hover: var(--bs-color-zhusha-700);\n --bs-color-primary-active: var(--bs-color-zhusha-800);\n --bs-color-primary-text: oklch(0.988 0.004 95);\n --bs-color-primary-subtle: var(--bs-color-zhusha-100);\n --bs-color-primary-subtle-text: var(--bs-color-zhusha-800);\n --bs-color-primary-border: var(--bs-color-zhusha-300);\n --bs-color-focus: var(--bs-color-primary);\n }\n\n [data-theme=\"dark\"] [data-accent=\"zhusha\"],\n [data-theme=\"dark\"][data-accent=\"zhusha\"],\n :host([data-theme=\"dark\"][data-accent=\"zhusha\"]) {\n --bs-color-primary: var(--bs-color-zhusha-500);\n --bs-color-primary-hover: var(--bs-color-zhusha-600);\n --bs-color-primary-active: var(--bs-color-zhusha-700);\n /* A mid-tone pigment body carries neither deep nor pale text past\n 4.5:1, so the solid fill kneads toward black and reads with bright\n paper ink. */\n --bs-color-primary-fill: color-mix(in oklab, var(--bs-color-zhusha-500) 80%, black);\n --bs-color-primary-fill-hover: color-mix(in oklab, var(--bs-color-zhusha-500) 70%, black);\n --bs-color-primary-text: var(--bs-color-gray-25);\n --bs-color-primary-subtle: color-mix(in oklab, var(--bs-color-primary) 16%, transparent);\n --bs-color-primary-subtle-text: var(--bs-color-zhusha-300);\n --bs-color-primary-border: var(--bs-color-zhusha-700);\n }\n\n\n /* Scene presets — one attribute that retunes the temperament of the whole\n interface for its audience: civic serves elders and civic desks (square\n seal-cut geometry, generous targets, loud contrast, an unhurried pace),\n tech serves modern efficiency products (sharp, dense, quick), enterprise\n is the quiet commercial baseline, studio the literati gathering for\n design and editorial work.\n\n A scene moves only what the paper allows: shape (radius), density, motion\n pace, where the key light stands, and how far it reaches — never\n interaction, never the semantic pigments. Each scene pairs with a default\n accent and (for civic) the high-contrast tier; those pairings are applied\n by the theme engine as plain data attributes, so the accent and contrast\n rules — including their dark variants — carry them unchanged.\n\n Explicit attributes always outrank the scene's bundled defaults: a\n `[data-accent]`, `[data-contrast]`, or `[data-density]` set alongside\n `[data-scene]` wins by cascade order. Note `:root` ties `[data-scene]` in\n specificity (both 0,1,0), so this file must sit after every stylesheet\n that declares these variables on `:root` (themes, primitives via accent's\n placement) and before density.css, whose explicit tiers must win over\n scene defaults. */\n\n [data-scene=\"civic\"] {\n /* 典章 — for elders and civic desks: the square-cut register of official\n documents. Corners go true square (a seal is not round), targets grow\n to the spacious ladder (48px controls), the pace slows so states are\n never missed, and the light stays flat so nothing hides in a shadow. */\n --bs-radius-sm: 0rem;\n --bs-radius-md: 0.125rem;\n --bs-radius-lg: 0.25rem;\n --bs-radius-xl: 0.375rem;\n --bs-radius-2xl: 0.5rem;\n --bs-control-height-sm: 2.25rem;\n --bs-control-height-md: 2.625rem;\n --bs-control-height-lg: 3rem;\n --bs-density-scale: 1.5;\n --bs-motion-scale: 1.25;\n --bs-light-x: 0px;\n --bs-light-y: 0px;\n --bs-light-reach: 0.7;\n }\n\n [data-scene=\"enterprise\"] {\n /* 信笺 — the measured commercial baseline: default geometry at a calmer\n light, so long working sessions read quiet and the hairlines do the\n talking. */\n --bs-density-scale: 1;\n --bs-motion-scale: 1;\n --bs-light-x: 0px;\n --bs-light-y: 0px;\n --bs-light-reach: 0.85;\n }\n\n [data-scene=\"studio\"] {\n /* 雅集 — the literati gathering for design and editorial work: controls\n keep the 6px seal edge, vessels round out generously, whitespace\n breathes, and the light comes from the upper left, casting each\n element's shadow gently toward the lower right. */\n --bs-radius-md: 0.625rem;\n --bs-radius-lg: 0.875rem;\n --bs-radius-xl: 1.25rem;\n --bs-radius-2xl: 1.75rem;\n --bs-control-height-sm: 2rem;\n --bs-control-height-md: 2.25rem;\n --bs-control-height-lg: 2.75rem;\n --bs-density-scale: 1.25;\n --bs-motion-scale: 1;\n --bs-light-x: 0.125rem;\n --bs-light-y: 0.125rem;\n --bs-light-reach: 1.25;\n }\n\n [data-scene=\"tech\"] {\n /* 司南 — the modern precision instrument: near-square corners, dense\n rows, a snappy pace, and a flat restrained light where hairlines carry\n all the hierarchy. */\n --bs-radius-sm: 0.125rem;\n --bs-radius-md: 0.25rem;\n --bs-radius-lg: 0.5rem;\n --bs-radius-xl: 0.75rem;\n --bs-radius-2xl: 1rem;\n --bs-control-height-sm: 1.5rem;\n --bs-control-height-md: 1.75rem;\n --bs-control-height-lg: 2rem;\n --bs-density-scale: 0.75;\n --bs-motion-scale: 0.85;\n --bs-light-x: 0px;\n --bs-light-y: 0px;\n --bs-light-reach: 0.5;\n }\n\n\n /* Density — decoration compresses, information never does: density scales whitespace and decoration\n only. Type sizes and contrast stay fixed so compact can never trade away\n readability. Control heights ride the density directly — a compact\n workbench and an elder-friendly spacious screen are different products,\n not the same one nudged by two pixels. */\n\n /* The default scale sits behind :where() so a scene preset (an attribute,\n same 0,1,0 as :root) can override it by cascade order, while the explicit\n [data-density] tiers below still outrank scenes that come before them. */\n :where(:root),\n :where(:host) {\n --bs-density-scale: 1;\n }\n\n :root,\n :host {\n /* Interior control parts (thumbs, knobs, check boxes, close buttons) are\n decoration riding a control, not the control itself: they scale with\n density and keep no readable floor, so a compact density actually\n makes them smaller. */\n --bs-part-size-sm: calc(1rem * var(--bs-density-scale));\n --bs-part-size-md: calc(1.25rem * var(--bs-density-scale));\n --bs-part-size-lg: calc(1.75rem * var(--bs-density-scale));\n\n /* Density-scaled whitespace. */\n --bs-padding-xs: calc(var(--bs-space-1) * var(--bs-density-scale));\n --bs-padding-sm: calc(var(--bs-space-2) * var(--bs-density-scale));\n --bs-padding-md: calc(var(--bs-space-3) * var(--bs-density-scale));\n --bs-padding-lg: calc(var(--bs-space-4) * var(--bs-density-scale));\n --bs-padding-xl: calc(var(--bs-space-6) * var(--bs-density-scale));\n --bs-gap-xs: calc(var(--bs-space-1) * var(--bs-density-scale));\n --bs-gap-sm: calc(var(--bs-space-2) * var(--bs-density-scale));\n --bs-gap-md: calc(var(--bs-space-3) * var(--bs-density-scale));\n --bs-gap-lg: calc(var(--bs-space-5) * var(--bs-density-scale));\n --bs-gap-xl: calc(var(--bs-space-8) * var(--bs-density-scale));\n }\n\n /* Control heights per tier — the cross-system ladders (28/32/40 sits at\n default; compact matches the Fiori/Ant workbench register; spacious\n reaches the 48px targets elderly-vision research asks for). The base\n tier sits behind :where() like the scale above, so a scene's bundled\n ladder overrides it by cascade order while the explicit [data-density]\n tiers below still outrank both. */\n :where(:root),\n :where(:host) {\n --bs-control-height-sm: 1.75rem;\n --bs-control-height-md: 2rem;\n --bs-control-height-lg: 2.5rem;\n }\n\n [data-density=\"compact\"],\n :host([data-density=\"compact\"]) {\n --bs-density-scale: 0.75;\n --bs-control-height-sm: 1.5rem;\n --bs-control-height-md: 1.75rem;\n --bs-control-height-lg: 2rem;\n }\n\n [data-density=\"comfortable\"],\n :host([data-density=\"comfortable\"]) {\n --bs-density-scale: 1.25;\n --bs-control-height-sm: 2rem;\n --bs-control-height-md: 2.25rem;\n --bs-control-height-lg: 2.75rem;\n }\n\n [data-density=\"spacious\"],\n :host([data-density=\"spacious\"]) {\n --bs-density-scale: 1.5;\n --bs-control-height-sm: 2.25rem;\n --bs-control-height-md: 2.625rem;\n --bs-control-height-lg: 3rem;\n }\n\n\n /* Contrast tier — softness comes from light and shadow, never from low contrast: the aesthetic softens through\n light, never through gray-on-gray. The high tier pulls secondary text and\n borders up to primary strength and widens the focus halo; it exists so\n older and low-vision users get the same design, just louder. Surface\n steps stay untouched — rows signal hover by one step of shade, and\n flattening the ladder would silence every hover in the system. */\n\n [data-contrast=\"high\"],\n :host([data-contrast=\"high\"]) {\n --bs-color-text-secondary: var(--bs-color-text-primary);\n --bs-color-text-tertiary: var(--bs-color-text-secondary);\n --bs-color-border: var(--bs-color-border-strong);\n --bs-focus-ring-color: color-mix(in oklab, var(--bs-color-focus) 30%, transparent);\n }\n\n [data-theme=\"light\"][data-contrast=\"high\"],\n :host([data-theme=\"light\"][data-contrast=\"high\"]) {\n --bs-color-border-strong: var(--bs-color-gray-400);\n }\n\n [data-theme=\"dark\"][data-contrast=\"high\"],\n :host([data-theme=\"dark\"][data-contrast=\"high\"]) {\n --bs-color-border-strong: var(--bs-color-gray-500);\n }\n\n\n /* Typography — two tracks: the voice and the chrome. The voice is the song\n stack (upright, structured strokes — Noto Serif SC falling back through the\n platform faces); headings and editorial narrative ride it, the way Western\n systems let a serif carry voice. The chrome is the modern hei stack — MiSans\n and HarmonyOS Sans lead, falling back into PingFang / YaHei so mixed CJK-Latin lines stay\n on one rhythm. Sizes are fixed rem values outside the density scale; compact\n UI may choose --bs-font-size-sm but never below it. */\n\n :root,\n :host {\n --bs-font-sans:\n MiSans, \"HarmonyOS Sans SC\", \"PingFang SC\", \"Microsoft YaHei\", system-ui, -apple-system,\n \"Segoe UI\", Roboto, \"Noto Sans CJK SC\", sans-serif;\n --bs-font-serif:\n \"Noto Serif SC\", \"Source Han Serif SC\", \"Songti SC\", SimSun, \"Times New Roman\", Georgia, serif;\n --bs-font-mono:\n ui-monospace, \"Cascadia Code\", \"JetBrains Mono\", Consolas, \"PingFang SC\", \"Microsoft YaHei\",\n monospace;\n\n --bs-font-size-xs: 0.75rem;\n --bs-font-size-sm: 0.8125rem;\n --bs-font-size-md: 0.875rem;\n --bs-font-size-base: 0.9375rem;\n --bs-font-size-lg: 1.0625rem;\n --bs-font-size-xl: 1.25rem;\n --bs-font-size-2xl: 1.5rem;\n --bs-font-size-3xl: 1.875rem;\n --bs-font-size-4xl: 2.25rem;\n\n --bs-line-height-tight: 1.25;\n --bs-line-height-snug: 1.375;\n --bs-line-height-normal: 1.5;\n --bs-line-height-relaxed: 1.625;\n\n /* Control labels carry a whisper of tracking — upright and unhurried is a\n property of CJK typesetting, not of any single typeface. */\n --bs-tracking-label: 0.02em;\n\n --bs-font-weight-regular: 400;\n --bs-font-weight-medium: 500;\n --bs-font-weight-semibold: 600;\n --bs-font-weight-bold: 700;\n }\n\n\n /* Motion — the ink-and-light grammar. Three rules and a floor:\n Light needs time — shadows transition about 1.5x slower than the property\n that raised them, so a shadow reads as cast light, not a sticker.\n Ink bleeds — surfaces enter by fading AND dissolving (blur to sharp),\n never popping. Puppets have strings — moving parts overshoot on the spring\n curve, and lists follow one another with a small stagger.\n Tokens drive all transitions; the reduced-motion block retunes them instead\n of removing states, so opacity still communicates elevation changes. */\n\n :root,\n :host {\n /* Durations ride --bs-motion-scale so a scene preset can tune the pace\n (a snappier instrument, an unhurried civic register) without touching\n the reduced-motion floor below, which declares 1ms directly. */\n --bs-duration-instant: calc(80ms * var(--bs-motion-scale, 1));\n --bs-duration-fast: calc(140ms * var(--bs-motion-scale, 1));\n --bs-duration-base: calc(200ms * var(--bs-motion-scale, 1));\n --bs-duration-slow: calc(320ms * var(--bs-motion-scale, 1));\n\n --bs-ease-out: cubic-bezier(0.16, 1, 0.3, 1);\n --bs-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);\n --bs-ease-in: cubic-bezier(0.7, 0, 0.84, 0);\n --bs-ease-spring: cubic-bezier(0.34, 1.3, 0.5, 1);\n\n /* Stagger step for lists and menus — each follower comes up 24ms after\n the one before it, like puppets lifted in sequence. */\n --bs-stagger-step: 24ms;\n }\n\n /* Surfaces dissolve in like ink settling into paper: a fade, a soft focus\n pulling sharp, and a one-percent settle. Panels and menus apply it on\n their open state; exits stay plain opacity fades — faster, unobtrusive. */\n @keyframes bs-ink-in {\n from {\n opacity: 0;\n filter: blur(3px);\n scale: 1.01;\n }\n to {\n opacity: 1;\n filter: blur(0);\n scale: 1;\n }\n }\n\n /* Followers rise into place on the string — a short lift with the spring\n curve. Combine with per-item `animation-delay: calc(var(--bs-i) * var(--bs-stagger-step))`. */\n @keyframes bs-item-in {\n from {\n opacity: 0;\n translate: 0 4px;\n }\n to {\n opacity: 1;\n translate: 0 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n :root,\n :host {\n --bs-duration-instant: 1ms;\n --bs-duration-fast: 1ms;\n --bs-duration-base: 1ms;\n --bs-duration-slow: 1ms;\n --bs-stagger-step: 0ms;\n }\n }\n\n\n /* Overlay stacking — one shared base for every dismissible layer (dialog,\n popover, menu, select, …), ordered by zag's --layer-index offset so any\n nesting combination stacks correctly. Per-component bases are forbidden. */\n :root,\n :host {\n --bs-z-overlay: 1000;\n }\n}\n";
263
- //#endregion
264
301
  //#region src/styles/base.ts
265
302
  /** Document-level guard rules. The machines drive visibility with the
266
303
  * `hidden` attribute (select/combobox item indicators), which any author
@@ -269,6 +306,21 @@ const baseCss = `
269
306
  [hidden] {
270
307
  display: none !important;
271
308
  }
309
+
310
+ /* The field baseline: containers that hold text or choices fill their
311
+ container — width is the layout's decision, never the component's.
312
+ Zero specificity (:where), so a family's own declaration always wins
313
+ without a fight; intrinsic controls (buttons, chips, markers) are
314
+ simply not on the list. */
315
+ :where([data-scope="select"], [data-scope="combobox"], [data-scope="listbox"],
316
+ [data-scope="cascade-select"], [data-scope="tree-select"],
317
+ [data-scope="number-input"], [data-scope="password-input"],
318
+ [data-scope="date-input"], [data-scope="date-picker"],
319
+ [data-scope="color-picker"], [data-scope="tags-input"],
320
+ [data-scope="fieldset"], [data-scope="file-upload"],
321
+ [data-scope="signature-pad"])[data-part="root"] {
322
+ inline-size: 100%;
323
+ }
272
324
  `;
273
325
  /** Press feedback that rides the motion attribute, not any one component —
274
326
  * anything with `data-motion~="ink-ripple"` bleeds from its press point. */
@@ -291,38 +343,60 @@ const inkRippleCss = `
291
343
  border-radius: 100%;
292
344
  background: radial-gradient(
293
345
  circle closest-side,
294
- var(--_ripple-pigment, color-mix(in oklab, var(--bs-color-primary) 30%, transparent)),
295
- transparent 80%
346
+ var(--bs-ripple-pigment) var(--bs-ripple-core, 0%),
347
+ transparent var(--bs-ripple-fade, 80%)
296
348
  );
297
349
  scale: 0;
298
350
  opacity: 0;
299
351
  pointer-events: none;
300
352
  }
301
353
 
302
- [data-motion~="ink-ripple"][data-ripple="run"]::after {
303
- animation: bs-ink-ripple calc(650ms * var(--bs-motion-scale, 1)) var(--bs-ease-out);
354
+ [data-motion~="ink-ripple"][data-ripple="press"]::after {
355
+ animation: bs-ripple-press var(--bs-ripple-duration, calc(650ms * var(--bs-motion-scale, 1)))
356
+ var(--bs-ripple-ease, var(--bs-ease-out)) forwards;
304
357
  }
305
358
 
306
- /* Ink into water: the wash sweeps across the whole surface at full
307
- strength first, and only once it has covered the body does it start
308
- dissolving never fading while it still has ground to cover. */
309
- @keyframes bs-ink-ripple {
310
- 0% {
311
- scale: 0.15;
312
- opacity: 0.9;
359
+ /* On release the press phase keeps running to full cover — re-declaring
360
+ the same-name animation carries it over instead of restarting while
361
+ the dissolve fades on top of it. */
362
+ [data-motion~="ink-ripple"][data-ripple="release"]::after {
363
+ animation: bs-ripple-press var(--bs-ripple-duration, calc(650ms * var(--bs-motion-scale, 1)))
364
+ var(--bs-ripple-ease, var(--bs-ease-out)) forwards,
365
+ bs-ripple-fade var(--bs-ripple-release, calc(320ms * var(--bs-motion-scale, 1))) ease-out
366
+ forwards;
367
+ }
368
+
369
+ /* Ink wells up from the fingertip: the wash starts small at the press
370
+ point and grows while drifting toward the element's center (--bs-ripple-dx/dy,
371
+ set by the press watcher), staying at full strength while the pointer
372
+ holds — the paper is wet until the hand lifts. Peak opacity rides
373
+ --bs-ripple-opacity so a scene can quiet the wash or silence it
374
+ entirely; --bs-ripple-spread caps how far it reaches. */
375
+ @keyframes bs-ripple-press {
376
+ from {
377
+ translate: -50% -50%;
378
+ scale: 0.2;
379
+ opacity: var(--bs-ripple-opacity, 0.9);
313
380
  }
314
- 45% {
315
- scale: 1;
316
- opacity: 0.9;
381
+ to {
382
+ translate: calc(-50% + var(--bs-ripple-dx, 0px)) calc(-50% + var(--bs-ripple-dy, 0px));
383
+ scale: var(--bs-ripple-spread, 1);
384
+ opacity: var(--bs-ripple-opacity, 0.9);
317
385
  }
318
- 100% {
319
- scale: 1;
386
+ }
387
+
388
+ @keyframes bs-ripple-fade {
389
+ from {
390
+ opacity: var(--bs-ripple-opacity, 0.9);
391
+ }
392
+ to {
320
393
  opacity: 0;
321
394
  }
322
395
  }
323
396
 
324
397
  @media (prefers-reduced-motion: reduce) {
325
- [data-motion~="ink-ripple"][data-ripple="run"]::after {
398
+ [data-motion~="ink-ripple"][data-ripple="press"]::after,
399
+ [data-motion~="ink-ripple"][data-ripple="release"]::after {
326
400
  animation-duration: 1ms;
327
401
  }
328
402
  }
@@ -352,6 +426,7 @@ const accordionCss = `
352
426
  /* Triggers are quiet rows on the paper — no chrome of their own; the
353
427
  hairline rule carries the structure instead. */
354
428
  [data-scope="accordion"][data-part="item-trigger"] {
429
+ box-sizing: border-box;
355
430
  display: flex;
356
431
  align-items: center;
357
432
  justify-content: space-between;
@@ -474,6 +549,15 @@ const accordionCss = `
474
549
  }
475
550
  `;
476
551
  //#endregion
552
+ //#region src/styles/components/affix.ts
553
+ const affixCss = `
554
+ /* The nail draws nothing of its own: it is one sticky instruction on a
555
+ carrier the caller sizes and surfaces. */
556
+ [data-scope="affix"][data-part="root"] {
557
+ position: sticky;
558
+ }
559
+ `;
560
+ //#endregion
477
561
  //#region src/styles/components/ai.ts
478
562
  const aiCss = `
479
563
  /* The conversation column: messages stack with section-band air, the
@@ -505,7 +589,11 @@ const aiCss = `
505
589
  border-radius: var(--bs-radius-lg);
506
590
  background: var(--bs-color-surface-1);
507
591
  inline-size: fit-content;
508
- max-inline-size: min(75%, 30rem);
592
+ /* A fixed ceiling, not a percentage: the percentage feeds the
593
+ fit-content resolution a reference it cannot satisfy (the parent's
594
+ own width is being fit-content from this very box), and short
595
+ messages then collapse toward min-content — one glyph per line. */
596
+ max-inline-size: 30rem;
509
597
  }
510
598
 
511
599
  /* Markdown response: the ink is set with relaxed leading, code rides
@@ -751,6 +839,78 @@ const aiCss = `
751
839
  text-decoration-color: var(--bs-color-border-strong);
752
840
  }
753
841
 
842
+ /* Attachments: the files riding the prompt's header — paper chips on
843
+ the recessed vessel, square-cut like every control. Uploading is a
844
+ dashed ghost, error speaks in danger ink; no chip casts a shadow. */
845
+ [data-scope="ai"][data-part="attachments"] {
846
+ display: flex;
847
+ flex-wrap: wrap;
848
+ gap: var(--bs-space-1);
849
+ }
850
+
851
+ [data-scope="ai"][data-part="attachment"] {
852
+ display: inline-flex;
853
+ align-items: center;
854
+ gap: var(--bs-space-1);
855
+ max-inline-size: 100%;
856
+ padding-block: calc(var(--bs-space-1) / 2);
857
+ padding-inline: var(--bs-space-2);
858
+ border: 1px solid var(--bs-color-border);
859
+ border-radius: var(--bs-radius-sm);
860
+ background: var(--bs-color-surface);
861
+ color: var(--bs-color-text-secondary);
862
+ font-size: var(--bs-font-size-sm);
863
+ transition: border-color var(--bs-duration-fast) var(--bs-ease-out);
864
+ }
865
+
866
+ [data-scope="ai"][data-part="attachment"]:hover {
867
+ border-color: var(--bs-color-border-strong);
868
+ }
869
+
870
+ [data-scope="ai"][data-part="attachment"] svg {
871
+ inline-size: 0.875rem;
872
+ block-size: 0.875rem;
873
+ flex: none;
874
+ color: var(--bs-color-text-tertiary);
875
+ }
876
+
877
+ [data-scope="ai"][data-part="attachment"][data-status="uploading"] {
878
+ border-style: dashed;
879
+ color: var(--bs-color-text-tertiary);
880
+ }
881
+
882
+ [data-scope="ai"][data-part="attachment"][data-status="error"] {
883
+ border-color: var(--bs-color-danger);
884
+ color: var(--bs-color-danger);
885
+ }
886
+
887
+ [data-scope="ai"][data-part="attachment"] > span {
888
+ overflow: hidden;
889
+ text-overflow: ellipsis;
890
+ white-space: nowrap;
891
+ }
892
+
893
+ [data-scope="ai"][data-part="attachment"] [data-remove] {
894
+ display: inline-flex;
895
+ align-items: center;
896
+ flex: none;
897
+ padding: 0;
898
+ border: none;
899
+ background: none;
900
+ color: inherit;
901
+ cursor: pointer;
902
+ }
903
+
904
+ [data-scope="ai"][data-part="attachment"] [data-remove]:hover {
905
+ color: var(--bs-color-text-primary);
906
+ }
907
+
908
+ [data-scope="ai"][data-part="attachment"] [data-remove]:focus-visible {
909
+ outline: none;
910
+ border-radius: var(--bs-radius-sm);
911
+ box-shadow: var(--bs-focus-ring);
912
+ }
913
+
754
914
  /* Actions row: a quiet line of shared ghost buttons. */
755
915
  [data-scope="ai"][data-part="actions"] {
756
916
  display: flex;
@@ -761,10 +921,13 @@ const aiCss = `
761
921
  /* The prompt: one vessel — the shared field bared to the paper and
762
922
  self-growing on the machine's autoresize. It starts a single line tall,
763
923
  the submit seal riding that line; as the text grows the seal settles
764
- onto the last line. The halo answers the vessel, not the control. */
924
+ onto the last line. The rows above and below (attachments, the model
925
+ and its switches) are slots — absent when empty, so the bare vessel
926
+ stays one quiet line. The halo answers the vessel, not the control. */
765
927
  [data-scope="ai"][data-part="prompt"] {
766
928
  display: flex;
767
- align-items: flex-end;
929
+ flex-direction: column;
930
+ align-items: stretch;
768
931
  gap: var(--bs-space-2);
769
932
  padding: var(--bs-space-2);
770
933
  border: 1px solid var(--bs-color-border);
@@ -780,6 +943,30 @@ const aiCss = `
780
943
  box-shadow: var(--bs-focus-ring);
781
944
  }
782
945
 
946
+ /* The writing row: the field between its optional shoulders, the seal
947
+ and the tools riding the text's last line. */
948
+ [data-scope="ai"][data-part="prompt-main"] {
949
+ display: flex;
950
+ align-items: flex-end;
951
+ gap: var(--bs-space-2);
952
+ min-inline-size: 0;
953
+ }
954
+
955
+ [data-scope="ai"][data-part="prompt-header"] {
956
+ display: flex;
957
+ flex-wrap: wrap;
958
+ align-items: center;
959
+ gap: var(--bs-space-1);
960
+ }
961
+
962
+ [data-scope="ai"][data-part="prompt-leading"],
963
+ [data-scope="ai"][data-part="prompt-trailing"] {
964
+ display: flex;
965
+ align-items: flex-end;
966
+ gap: var(--bs-space-1);
967
+ flex: none;
968
+ }
969
+
783
970
  [data-scope="ai"][data-part="prompt"] [data-scope="field"][data-part="root"] {
784
971
  flex: 1;
785
972
  min-inline-size: 0;
@@ -808,10 +995,31 @@ const aiCss = `
808
995
  resize: none;
809
996
  }
810
997
 
998
+ /* The tool row beneath the writing: the model and its switches — the
999
+ seal closes the row at its far end. */
811
1000
  [data-scope="ai"][data-part="prompt-footer"] {
1001
+ display: flex;
1002
+ flex-wrap: wrap;
1003
+ align-items: center;
1004
+ gap: var(--bs-space-1);
1005
+ flex: none;
1006
+ }
1007
+
1008
+ /* The send-side group: it drifts to the row's far end and keeps the
1009
+ model picker and the seal shoulder to shoulder. */
1010
+ [data-scope="ai"][data-part="prompt-end"] {
812
1011
  display: flex;
813
1012
  align-items: center;
1013
+ gap: var(--bs-space-2);
814
1014
  flex: none;
1015
+ margin-inline-start: auto;
1016
+ }
1017
+
1018
+ /* The tool row holds fixed-width chrome: a field family dropped in
1019
+ here keeps its natural width, not the form-field full-bleed
1020
+ baseline. */
1021
+ [data-scope="ai"][data-part="prompt-footer"] [data-part="root"] {
1022
+ inline-size: auto;
815
1023
  }
816
1024
 
817
1025
  [data-scope="ai"][data-part="prompt"] [data-scope="field"][data-part="textarea"]:focus,
@@ -1161,6 +1369,25 @@ const angleSliderCss = labelCss("angle-slider") + `
1161
1369
  }
1162
1370
  `;
1163
1371
  //#endregion
1372
+ //#region src/styles/components/aspect-ratio.ts
1373
+ const aspectRatioCss = `
1374
+ /* The frame that keeps its shape: the box holds the ratio the wrapper
1375
+ hands over, whatever width it is dealt. */
1376
+ [data-scope="aspect-ratio"][data-part="root"] {
1377
+ position: relative;
1378
+ inline-size: 100%;
1379
+ aspect-ratio: var(--bs-aspect-ratio, 1 / 1);
1380
+ }
1381
+
1382
+ /* The child owns the frame's face: it fills the box the ratio draws. */
1383
+ [data-scope="aspect-ratio"][data-part="root"] > * {
1384
+ position: absolute;
1385
+ inset: 0;
1386
+ inline-size: 100%;
1387
+ block-size: 100%;
1388
+ }
1389
+ `;
1390
+ //#endregion
1164
1391
  //#region src/styles/components/avatar.ts
1165
1392
  const avatarCss = `
1166
1393
  [data-scope="avatar"][data-part="root"] {
@@ -1175,7 +1402,7 @@ const avatarCss = `
1175
1402
  justify-content: center;
1176
1403
  inline-size: var(--bs-avatar-size);
1177
1404
  block-size: var(--bs-avatar-size);
1178
- border-radius: calc(var(--bs-radius-lg) * 100);
1405
+ border-radius: var(--bs-radius-full);
1179
1406
  background: var(--bs-color-surface-inset);
1180
1407
  color: var(--bs-color-text-secondary);
1181
1408
  font-size: calc(var(--bs-font-size-sm) * 1.125);
@@ -1225,6 +1452,57 @@ const avatarGroupCss = `
1225
1452
  }
1226
1453
  `;
1227
1454
  //#endregion
1455
+ //#region src/styles/components/back-top.ts
1456
+ const backTopCss = `
1457
+ /* A way-home control: a small floating tile at the page's corner. The
1458
+ paper, hairline and halo are the button's; this family owns the
1459
+ mooring and the entrance. It floats, so it casts — one rung of
1460
+ elevation, recomposed from the lighting parts. */
1461
+ [data-scope="back-top"][data-part="root"] {
1462
+ position: fixed;
1463
+ inset-inline-end: var(--bs-space-6);
1464
+ inset-block-end: var(--bs-space-6);
1465
+ z-index: var(--bs-z-overlay);
1466
+ transition:
1467
+ opacity var(--bs-duration-base) var(--bs-ease-out),
1468
+ translate var(--bs-duration-base) var(--bs-ease-spring);
1469
+ }
1470
+
1471
+ /* Hidden sinks below the page and steps out of the reading order —
1472
+ states remain, animation does not. */
1473
+ [data-scope="back-top"][data-part="root"][data-state="hidden"] {
1474
+ opacity: 0;
1475
+ translate: 0 var(--bs-space-2);
1476
+ visibility: hidden;
1477
+ pointer-events: none;
1478
+ }
1479
+
1480
+ /* The floating tile rises a rung; hovering lifts one more on the same
1481
+ slow shadow clock the lighting engine keeps. */
1482
+ [data-scope="back-top"][data-part="root"][data-state="shown"] [data-scope="button"][data-part="root"],
1483
+ [data-scope="back-top"][data-part="root"][data-state="shown"]:hover [data-scope="button"][data-part="root"] {
1484
+ --bs-shadow-color: color-mix(in oklab, var(--bs-shadow-ink) 30%, transparent);
1485
+ box-shadow: var(--bs-elevation-2);
1486
+ transition: box-shadow 220ms var(--bs-ease-out);
1487
+ }
1488
+
1489
+ [data-scope="back-top"][data-part="root"][data-state="shown"]:hover [data-scope="button"][data-part="root"] {
1490
+ box-shadow: var(--bs-elevation-3);
1491
+ }
1492
+
1493
+ [data-scope="back-top"][data-part="root"][data-state="shown"]:active [data-scope="button"][data-part="root"] {
1494
+ box-shadow: none;
1495
+ }
1496
+
1497
+ /* The return trip rides the page's own scroll container, offered only
1498
+ while the reader allows motion. */
1499
+ @media (prefers-reduced-motion: no-preference) {
1500
+ html:has([data-scope="back-top"][data-part="root"][data-state="shown"]) {
1501
+ scroll-behavior: smooth;
1502
+ }
1503
+ }
1504
+ `;
1505
+ //#endregion
1228
1506
  //#region src/styles/components/badge.ts
1229
1507
  const badgeCss = `
1230
1508
  /* A small seal of state: flat pigment fill, tracked caps-height ink. The
@@ -1480,7 +1758,7 @@ const buttonCss = `
1480
1758
  the pigment it carries. Each variant declares its fill/ink defaults,
1481
1759
  semantic tones re-point the pigment, and hover/focus ride the same
1482
1760
  variables. Ink is the solemn default. */
1483
- [data-scope="button"][data-part="root"] {
1761
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant]) {
1484
1762
  --_pigment: var(--bs-color-primary);
1485
1763
  /* The deep register of the pigment for the subtle wash — the 600-step
1486
1764
  tone itself fails 4.5:1 on its own wash, so washed fills read from
@@ -1490,6 +1768,10 @@ const buttonCss = `
1490
1768
  --_fill-hover: transparent;
1491
1769
  --_ink: var(--bs-color-text-secondary);
1492
1770
  --_edge: transparent;
1771
+ }
1772
+
1773
+ /* The recipe's own body: the layout and states of the button proper. */
1774
+ [data-scope="button"][data-part="root"] {
1493
1775
  display: inline-flex;
1494
1776
  align-items: center;
1495
1777
  justify-content: center;
@@ -1501,7 +1783,7 @@ const buttonCss = `
1501
1783
  systems use 15-16px at this height, 24px at large). */
1502
1784
  padding: 0 var(--bs-padding-lg);
1503
1785
  border: 1px solid var(--_edge);
1504
- border-radius: var(--bs-radius-sm);
1786
+ border-radius: var(--bs-radius-control, var(--bs-radius-sm));
1505
1787
  background: var(--_fill);
1506
1788
  color: var(--_ink);
1507
1789
  font: inherit;
@@ -1553,19 +1835,19 @@ const buttonCss = `
1553
1835
  block-size: 1rem;
1554
1836
  }
1555
1837
 
1556
- [data-scope="button"][data-part="root"][data-tone="danger"] {
1838
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-tone="danger"] {
1557
1839
  --_pigment: var(--bs-color-danger);
1558
1840
  --_ink-strong: var(--bs-color-danger-subtle-text);
1559
1841
  }
1560
- [data-scope="button"][data-part="root"][data-tone="success"] {
1842
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-tone="success"] {
1561
1843
  --_pigment: var(--bs-color-success);
1562
1844
  --_ink-strong: var(--bs-color-success-subtle-text);
1563
1845
  }
1564
- [data-scope="button"][data-part="root"][data-tone="warning"] {
1846
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-tone="warning"] {
1565
1847
  --_pigment: var(--bs-color-warning);
1566
1848
  --_ink-strong: var(--bs-color-warning-subtle-text);
1567
1849
  }
1568
- [data-scope="button"][data-part="root"][data-tone="info"] {
1850
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-tone="info"] {
1569
1851
  --_pigment: var(--bs-color-info);
1570
1852
  --_ink-strong: var(--bs-color-info-subtle-text);
1571
1853
  }
@@ -1573,7 +1855,7 @@ const buttonCss = `
1573
1855
  /* Solid: the flat pigment fill, no lit edge at rest. On hover the ink
1574
1856
  bleeds — the pigment casts a small shadow of its own color, on a slower
1575
1857
  transition than the fill (light needs time). */
1576
- [data-scope="button"][data-part="root"][data-variant="solid"] {
1858
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="solid"] {
1577
1859
  /* The solid body follows --bs-color-primary-fill: full pigment on
1578
1860
  paper, kneaded toward black at night, and the theme keeps the ink
1579
1861
  on it legible in both registers. */
@@ -1581,63 +1863,64 @@ const buttonCss = `
1581
1863
  --_fill-hover: var(--bs-color-primary-fill-hover);
1582
1864
  --_ink: var(--bs-color-primary-text);
1583
1865
  /* On a filled body the wash must read against the fill, not sink into
1584
- it — the ink that blooms on a fill is the fill's own ink. */
1585
- --_ripple-pigment: color-mix(in oklab, var(--_ink) 30%, transparent);
1866
+ it — the ink that blooms on a fill is the fill's own ink (opaque; the
1867
+ wash's concentration lives in --bs-ripple-opacity alone). */
1868
+ --bs-ripple-pigment: var(--_ink);
1586
1869
  box-shadow: none;
1587
1870
  }
1588
1871
 
1589
1872
  /* The fixed pigments knead by the theme's measure — a mid-tone body at
1590
1873
  night carries neither deep nor pale text past 4.5:1, so its ink turns
1591
1874
  bright paper. */
1592
- [data-scope="button"][data-part="root"][data-variant="solid"][data-tone="danger"] {
1875
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="solid"][data-tone="danger"] {
1593
1876
  --_fill: color-mix(in oklab, var(--_pigment) calc(100% - var(--bs-ink-knead, 0%)), black);
1594
1877
  --_fill-hover: color-mix(in oklab, var(--_pigment) calc(85% - var(--bs-ink-knead, 0%)), black);
1595
1878
  --_ink: var(--bs-color-ink-on-fill);
1596
1879
  }
1597
- [data-scope="button"][data-part="root"][data-variant="solid"][data-tone="success"] {
1880
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="solid"][data-tone="success"] {
1598
1881
  --_fill: color-mix(in oklab, var(--_pigment) calc(100% - var(--bs-ink-knead, 0%)), black);
1599
1882
  --_fill-hover: color-mix(in oklab, var(--_pigment) calc(85% - var(--bs-ink-knead, 0%)), black);
1600
1883
  --_ink: var(--bs-color-ink-on-fill);
1601
1884
  }
1602
- [data-scope="button"][data-part="root"][data-variant="solid"][data-tone="warning"] {
1885
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="solid"][data-tone="warning"] {
1603
1886
  --_fill: color-mix(in oklab, var(--_pigment) calc(100% - var(--bs-ink-knead, 0%)), black);
1604
1887
  --_fill-hover: color-mix(in oklab, var(--_pigment) calc(85% - var(--bs-ink-knead, 0%)), black);
1605
1888
  --_ink: var(--bs-color-ink-on-fill);
1606
1889
  }
1607
- [data-scope="button"][data-part="root"][data-variant="solid"][data-tone="info"] {
1890
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="solid"][data-tone="info"] {
1608
1891
  --_fill: color-mix(in oklab, var(--_pigment) calc(100% - var(--bs-ink-knead, 0%)), black);
1609
1892
  --_fill-hover: color-mix(in oklab, var(--_pigment) calc(85% - var(--bs-ink-knead, 0%)), black);
1610
1893
  --_ink: var(--bs-color-ink-on-fill);
1611
1894
  }
1612
1895
 
1613
- [data-scope="button"][data-part="root"][data-variant="solid"]:hover:not(:disabled) {
1896
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="solid"]:hover:not(:disabled) {
1614
1897
  box-shadow: var(--bs-light-x) calc(1px * var(--bs-light-reach) + var(--bs-light-y))
1615
1898
  calc(3px * var(--bs-light-reach)) 0 color-mix(in oklab, var(--_pigment) 28%, transparent);
1616
1899
  }
1617
1900
 
1618
1901
  /* Outline: paper on a hairline, the hairline deepening on hover. */
1619
- [data-scope="button"][data-part="root"][data-variant="outline"] {
1902
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="outline"] {
1620
1903
  --_fill: var(--bs-color-surface-2);
1621
1904
  --_ink: var(--bs-color-text-primary);
1622
1905
  --_edge: var(--bs-color-border);
1623
1906
  }
1624
1907
 
1625
1908
  /* Ghost: bare ink that borrows the subtle surface under the cursor. */
1626
- [data-scope="button"][data-part="root"][data-variant="ghost"] {
1909
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="ghost"] {
1627
1910
  --_fill-hover: var(--bs-color-surface-0);
1628
1911
  box-shadow: none;
1629
1912
  }
1630
1913
 
1631
1914
  /* Subtle: a wash of the pigment with its deep register on top. */
1632
- [data-scope="button"][data-part="root"][data-variant="subtle"] {
1915
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="subtle"] {
1633
1916
  --_fill: color-mix(in oklab, var(--_pigment) 12%, transparent);
1634
1917
  --_fill-hover: color-mix(in oklab, var(--_pigment) 20%, transparent);
1635
1918
  --_ink: var(--_ink-strong);
1636
1919
  box-shadow: none;
1637
1920
  }
1638
1921
 
1639
- [data-scope="button"][data-part="root"][data-variant="outline"]:hover:not(:disabled),
1640
- [data-scope="button"][data-part="root"][data-variant="outline"]:focus-visible {
1922
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="outline"]:hover:not(:disabled),
1923
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant])[data-variant="outline"]:focus-visible {
1641
1924
  --_edge: var(--bs-color-border-strong);
1642
1925
  }
1643
1926
 
@@ -1664,67 +1947,178 @@ const buttonCss = `
1664
1947
  box-shadow: none;
1665
1948
  }
1666
1949
 
1667
- /* As-child hosting: an overlay trigger that hosts a Button overwrites the
1668
- button's anatomy (data-scope reads the overlay family, not "button"),
1669
- so the rules above never match. The variant seals survive, though —
1670
- the ghost register re-asserts itself here for every overlay trigger at
1671
- once. The bare [data-scope] keeps specificity above any family's base,
1672
- and the state branches above its open/focus/disabled rules. */
1673
- [data-scope][data-part="trigger"][data-variant="ghost"] {
1950
+ /* As-child hosting: an overlay trigger that hosts a Button overwrites
1951
+ the button's anatomy (data-scope reads the overlay family, not
1952
+ "button"), so the body rules never match. The variant, tone and size
1953
+ seals survive, though and the variable rules above match them
1954
+ through the bare [data-scope], so this layout block only has to ride
1955
+ the same variables. The bare [data-scope] keeps specificity above any
1956
+ family's base, and the state branches above its open/focus/disabled
1957
+ rules. */
1958
+ [data-scope][data-part="trigger"][data-variant] {
1674
1959
  display: inline-flex;
1675
1960
  align-items: center;
1676
1961
  justify-content: center;
1677
1962
  gap: var(--bs-space-2);
1678
1963
  flex: none;
1679
- block-size: var(--bs-control-height-sm);
1680
- padding: 0 var(--bs-padding-md);
1681
- border: none;
1682
- border-radius: var(--bs-radius-sm);
1683
- background: transparent;
1684
- color: var(--bs-color-text-secondary);
1964
+ block-size: var(--bs-control-height-md);
1965
+ /* One step wider than the shell register, like the body above. */
1966
+ padding: 0 var(--bs-padding-lg);
1967
+ border: 1px solid var(--_edge);
1968
+ border-radius: var(--bs-radius-control, var(--bs-radius-sm));
1969
+ background: var(--_fill);
1970
+ color: var(--_ink);
1685
1971
  font: inherit;
1686
- font-size: var(--bs-font-size-sm);
1972
+ font-size: var(--bs-font-size-md);
1687
1973
  font-weight: var(--bs-font-weight-medium);
1688
1974
  letter-spacing: var(--bs-tracking-label);
1689
1975
  white-space: nowrap;
1690
1976
  text-decoration: none;
1691
1977
  cursor: pointer;
1692
1978
  user-select: none;
1693
- box-shadow: none;
1979
+ box-shadow: var(--bs-shadow-xs);
1694
1980
  transition:
1695
1981
  background-color var(--bs-duration-fast) var(--bs-ease-out),
1982
+ border-color var(--bs-duration-fast) var(--bs-ease-out),
1696
1983
  color var(--bs-duration-fast) var(--bs-ease-out),
1697
1984
  box-shadow calc(var(--bs-duration-fast) * 1.5) var(--bs-ease-out);
1698
1985
  }
1699
1986
 
1987
+ [data-scope][data-part="trigger"][data-variant][data-size="sm"] {
1988
+ block-size: var(--bs-control-height-sm);
1989
+ padding: 0 var(--bs-padding-md);
1990
+ font-size: var(--bs-font-size-sm);
1991
+ }
1992
+
1993
+ [data-scope][data-part="trigger"][data-variant][data-size="lg"] {
1994
+ block-size: var(--bs-control-height-lg);
1995
+ padding: 0 var(--bs-padding-xl);
1996
+ }
1997
+
1700
1998
  /* Icon-only: the silhouette is the seal — width equals height. */
1701
- [data-scope][data-part="trigger"][data-variant="ghost"][data-square="true"] {
1999
+ [data-scope][data-part="trigger"][data-variant][data-square="true"] {
1702
2000
  inline-size: var(--bs-control-height-sm);
1703
2001
  padding-inline: 0;
1704
2002
  }
1705
2003
 
1706
- [data-scope][data-part="trigger"][data-variant="ghost"]:hover {
1707
- background: var(--bs-color-surface-0);
2004
+ [data-scope][data-part="trigger"][data-variant][data-size="md"][data-square="true"] {
2005
+ inline-size: var(--bs-control-height-md);
2006
+ }
2007
+
2008
+ [data-scope][data-part="trigger"][data-variant][data-size="lg"][data-square="true"] {
2009
+ inline-size: var(--bs-control-height-lg);
2010
+ }
2011
+
2012
+ [data-scope][data-part="trigger"][data-variant]:hover:not(:disabled) {
2013
+ background: var(--_fill-hover);
1708
2014
  }
1709
2015
 
1710
2016
  /* Open keeps the focus look: Zag hands focus to the overlay itself, so
1711
2017
  :focus-visible alone would drop the halo the moment it opens. */
1712
- [data-scope][data-part="trigger"][data-variant="ghost"]:focus-visible,
1713
- [data-scope][data-part="trigger"][data-variant="ghost"][data-state="open"] {
2018
+ [data-scope][data-part="trigger"][data-variant]:focus-visible,
2019
+ [data-scope][data-part="trigger"][data-variant][data-state="open"] {
1714
2020
  outline: none;
2021
+ border-color: var(--bs-color-primary);
1715
2022
  box-shadow: var(--bs-focus-ring);
1716
2023
  }
1717
2024
 
1718
- [data-scope][data-part="trigger"][data-variant="ghost"]:active {
2025
+ [data-scope][data-part="trigger"][data-variant]:active:not(:disabled) {
1719
2026
  box-shadow: none;
1720
2027
  }
1721
2028
 
1722
- [data-scope][data-part="trigger"][data-variant="ghost"][data-disabled] {
1723
- background: var(--bs-color-surface-inset);
1724
- color: var(--bs-color-text-disabled);
1725
- box-shadow: none;
2029
+ [data-scope][data-part="trigger"][data-variant]:disabled,
2030
+ [data-scope][data-part="trigger"][data-variant][data-disabled] {
2031
+ --_fill: var(--bs-color-surface-inset);
2032
+ --_fill-hover: var(--bs-color-surface-inset);
2033
+ --_ink: var(--bs-color-text-disabled);
2034
+ --_edge: transparent;
1726
2035
  cursor: not-allowed;
2036
+ box-shadow: none;
2037
+ }
2038
+ `;
2039
+ //#endregion
2040
+ //#region src/styles/components/button-group.ts
2041
+ const buttonGroupCss = `
2042
+ /* Buttons fused into one control: the group owns only the joinery. Corners
2043
+ trim where the members meet, a shared hairline merges through a 1px
2044
+ overlap, and the hovered or focused member steps above the pile so its
2045
+ deepened edge and halo are not painted over by the neighbor. Selection
2046
+ is not this family's business — each button keeps the variant it was
2047
+ given, solid beside outline beside ghost. */
2048
+ [data-scope="button-group"][data-part="root"] {
2049
+ display: inline-flex;
2050
+ }
2051
+
2052
+ [data-scope="button-group"][data-part="root"][data-orientation="vertical"] {
2053
+ flex-direction: column;
2054
+ /* The column reads as one slab: members fill to the widest. */
2055
+ align-items: stretch;
2056
+ }
2057
+
2058
+ /* One register for the whole group: the group's size retunes every
2059
+ member's height the way a control register should — one knob, not one
2060
+ per button. */
2061
+ [data-scope="button-group"][data-part="root"][data-size="sm"] > [data-scope="button"][data-part="root"] {
2062
+ block-size: var(--bs-control-height-sm);
2063
+ padding: 0 var(--bs-padding-md);
2064
+ font-size: var(--bs-font-size-sm);
2065
+ }
2066
+
2067
+ [data-scope="button-group"][data-part="root"][data-size="lg"] > [data-scope="button"][data-part="root"] {
2068
+ block-size: var(--bs-control-height-lg);
2069
+ padding: 0 var(--bs-padding-xl);
2070
+ }
2071
+
2072
+ /* The seam: each member after the first slides one pixel over its
2073
+ predecessor's trailing hairline, so two borders read as one. */
2074
+ [data-scope="button-group"][data-part="root"] > [data-scope="button"][data-part="root"] + [data-scope="button"][data-part="root"] {
2075
+ margin-inline-start: -1px;
2076
+ }
2077
+
2078
+ [data-scope="button-group"][data-part="root"][data-orientation="vertical"] > [data-scope="button"][data-part="root"] + [data-scope="button"][data-part="root"] {
2079
+ margin-inline-start: 0;
2080
+ margin-block-start: -1px;
2081
+ }
2082
+
2083
+ /* 方寸为章, trimmed where the seals meet: interior corners square off,
2084
+ the first keeps its start side and the last its end side. */
2085
+ [data-scope="button-group"][data-part="root"][data-orientation="horizontal"] > [data-scope="button"][data-part="root"]:not(:first-child) {
2086
+ border-start-start-radius: 0;
2087
+ border-end-start-radius: 0;
2088
+ }
2089
+
2090
+ [data-scope="button-group"][data-part="root"][data-orientation="horizontal"] > [data-scope="button"][data-part="root"]:not(:last-child) {
2091
+ border-start-end-radius: 0;
2092
+ border-end-end-radius: 0;
2093
+ }
2094
+
2095
+ [data-scope="button-group"][data-part="root"][data-orientation="vertical"] > [data-scope="button"][data-part="root"]:not(:first-child) {
2096
+ border-start-start-radius: 0;
2097
+ border-start-end-radius: 0;
2098
+ }
2099
+
2100
+ [data-scope="button-group"][data-part="root"][data-orientation="vertical"] > [data-scope="button"][data-part="root"]:not(:last-child) {
2101
+ border-end-start-radius: 0;
2102
+ border-end-end-radius: 0;
2103
+ }
2104
+
2105
+ /* Raised while hovered or focused: the deepened hairline and the focus
2106
+ halo paint above the neighbors instead of vanishing under them. */
2107
+ [data-scope="button-group"][data-part="root"] > [data-scope="button"][data-part="root"]:hover,
2108
+ [data-scope="button-group"][data-part="root"] > [data-scope="button"][data-part="root"]:focus-visible {
2109
+ z-index: 1;
1727
2110
  }
2111
+
2112
+ /* The group may set the members' corner register: data-radius retunes
2113
+ --bs-radius-control, which every member's border-radius consumes, so
2114
+ the fused whole — first edge, last edge, and the trimmed seams —
2115
+ keeps one corner story. */
2116
+ [data-scope="button-group"][data-part="root"][data-radius="sm"] { --bs-radius-control: var(--bs-radius-sm); }
2117
+ [data-scope="button-group"][data-part="root"][data-radius="md"] { --bs-radius-control: var(--bs-radius-md); }
2118
+ [data-scope="button-group"][data-part="root"][data-radius="lg"] { --bs-radius-control: var(--bs-radius-lg); }
2119
+ [data-scope="button-group"][data-part="root"][data-radius="xl"] { --bs-radius-control: var(--bs-radius-xl); }
2120
+ [data-scope="button-group"][data-part="root"][data-radius="2xl"] { --bs-radius-control: var(--bs-radius-2xl); }
2121
+ [data-scope="button-group"][data-part="root"][data-radius="full"] { --bs-radius-control: var(--bs-radius-full); }
1728
2122
  `;
1729
2123
  //#endregion
1730
2124
  //#region src/styles/components/calendar.ts
@@ -1972,7 +2366,7 @@ const carouselCss = `
1972
2366
  block-size: calc(var(--bs-space-2) + 2px);
1973
2367
  padding: 0;
1974
2368
  border: none;
1975
- border-radius: calc(var(--bs-radius-lg) * 100);
2369
+ border-radius: var(--bs-radius-full);
1976
2370
  background: var(--bs-color-border-strong);
1977
2371
  cursor: pointer;
1978
2372
  transition: background-color var(--bs-duration-fast) var(--bs-ease-out);
@@ -2000,19 +2394,173 @@ const carouselCss = `
2000
2394
  }
2001
2395
  `;
2002
2396
  //#endregion
2003
- //#region src/styles/components/checkbox.ts
2004
- const checkboxCss = labelCss("checkbox") + `
2005
- [data-scope="checkbox"][data-part="root"] {
2006
- display: inline-flex;
2397
+ //#region src/styles/components/cascade-select.ts
2398
+ const cascadeSelectCss = positionerCss("cascade-select") + popupContentCss("cascade-select", "0rem") + `
2399
+ /* The trigger reads as a field — the input recipe — with the joined
2400
+ labels as its ink and a quiet chevron at the inline end. */
2401
+ [data-scope="cascade-select"][data-part="control"] {
2402
+ display: flex;
2403
+ inline-size: 100%;
2404
+ }
2405
+
2406
+ [data-scope="cascade-select"][data-part="trigger"] {
2407
+ box-sizing: border-box;
2408
+ display: flex;
2409
+ flex: 1;
2007
2410
  align-items: center;
2411
+ justify-content: space-between;
2008
2412
  gap: var(--bs-space-2);
2413
+ inline-size: 100%;
2414
+ block-size: var(--bs-control-height-md);
2415
+ padding-inline: var(--bs-padding-md);
2416
+ border: 1px solid var(--bs-color-border);
2417
+ border-radius: var(--bs-radius-sm);
2418
+ background: var(--bs-color-surface-2);
2009
2419
  color: var(--bs-color-text-primary);
2010
- font-size: var(--bs-font-size-sm);
2420
+ font: inherit;
2421
+ font-size: var(--bs-font-size-md);
2422
+ text-align: start;
2423
+ cursor: pointer;
2424
+ transition: border-color var(--bs-duration-fast) var(--bs-ease-out);
2011
2425
  }
2012
2426
 
2013
- /* A group of checkboxes is one field: the rows stack, and the invalid
2014
- * pigment bleeds onto every row inside. */
2015
- [data-scope="checkbox"][data-part="group"] {
2427
+ [data-scope="cascade-select"][data-part="trigger"][data-placeholder-shown]
2428
+ [data-part="value-text"] {
2429
+ color: var(--bs-color-text-tertiary);
2430
+ }
2431
+
2432
+ [data-scope="cascade-select"][data-part="trigger"]:hover:not(:focus):not(:disabled) {
2433
+ border-color: var(--bs-color-border-strong);
2434
+ }
2435
+
2436
+ [data-scope="cascade-select"][data-part="trigger"]:focus-visible {
2437
+ outline: none;
2438
+ border-color: var(--bs-color-primary);
2439
+ box-shadow: var(--bs-focus-ring);
2440
+ }
2441
+
2442
+ [data-scope="cascade-select"][data-part="trigger"][data-invalid] {
2443
+ border-color: var(--bs-color-danger);
2444
+ }
2445
+
2446
+ [data-scope="cascade-select"][data-part="trigger"]:disabled {
2447
+ border-color: var(--bs-color-border);
2448
+ background: var(--bs-color-surface-inset);
2449
+ color: var(--bs-color-text-disabled);
2450
+ cursor: not-allowed;
2451
+ }
2452
+
2453
+ [data-scope="cascade-select"][data-part="indicator"] {
2454
+ display: grid;
2455
+ place-items: center;
2456
+ color: var(--bs-color-text-tertiary);
2457
+ }
2458
+
2459
+ [data-scope="cascade-select"][data-part="indicator"] svg {
2460
+ inline-size: var(--bs-space-4);
2461
+ block-size: var(--bs-space-4);
2462
+ transition: transform var(--bs-duration-fast) var(--bs-ease-out);
2463
+ }
2464
+
2465
+ [data-scope="cascade-select"][data-part="trigger"][data-state="open"]
2466
+ [data-part="indicator"]
2467
+ svg {
2468
+ transform: rotate(180deg);
2469
+ }
2470
+
2471
+ /* The vessel is a corridor: one column per walked level, a hairline
2472
+ between neighbours, each column scrolling past its own grove. */
2473
+ [data-scope="cascade-select"][data-part="content"] {
2474
+ display: flex;
2475
+ }
2476
+
2477
+ [data-scope="cascade-select"][data-part="list"] {
2478
+ box-sizing: border-box;
2479
+ flex-shrink: 0;
2480
+ min-inline-size: 11rem;
2481
+ max-block-size: 16rem;
2482
+ margin: 0;
2483
+ padding: var(--bs-space-1);
2484
+ list-style: none;
2485
+ overflow-block: auto;
2486
+ }
2487
+
2488
+ [data-scope="cascade-select"][data-part="list"] + [data-part="list"] {
2489
+ border-inline-start: 1px solid var(--bs-color-border);
2490
+ }
2491
+
2492
+ [data-scope="cascade-select"][data-part="item"] {
2493
+ display: flex;
2494
+ align-items: center;
2495
+ gap: var(--bs-space-2);
2496
+ min-block-size: var(--bs-control-height-sm);
2497
+ padding: 0 var(--bs-space-2);
2498
+ border-radius: var(--bs-radius-sm);
2499
+ color: var(--bs-color-text-primary);
2500
+ font-size: var(--bs-font-size-sm);
2501
+ cursor: pointer;
2502
+ }
2503
+
2504
+ [data-scope="cascade-select"][data-part="item"]:hover:not([data-state="checked"], [data-disabled]),
2505
+ [data-scope="cascade-select"][data-part="item"][data-highlighted]:not([data-state="checked"]) {
2506
+ background: var(--bs-color-surface-0);
2507
+ }
2508
+
2509
+ [data-scope="cascade-select"][data-part="item"]:focus-visible {
2510
+ outline: none;
2511
+ box-shadow: var(--bs-focus-ring-inset);
2512
+ }
2513
+
2514
+ [data-scope="cascade-select"][data-part="item"][data-state="checked"] {
2515
+ background: var(--bs-color-primary);
2516
+ color: var(--bs-color-primary-text);
2517
+ font-weight: var(--bs-font-weight-medium);
2518
+ }
2519
+
2520
+ [data-scope="cascade-select"][data-part="item"][data-disabled] {
2521
+ color: var(--bs-color-text-disabled);
2522
+ cursor: not-allowed;
2523
+ }
2524
+
2525
+ [data-scope="cascade-select"][data-part="item-text"] {
2526
+ flex: 1;
2527
+ overflow: hidden;
2528
+ text-overflow: ellipsis;
2529
+ white-space: nowrap;
2530
+ }
2531
+
2532
+ [data-scope="cascade-select"][data-part="branch-indicator"],
2533
+ [data-scope="cascade-select"][data-part="item-indicator"] {
2534
+ display: grid;
2535
+ place-items: center;
2536
+ color: var(--bs-color-text-tertiary);
2537
+ }
2538
+
2539
+ [data-scope="cascade-select"][data-part="branch-indicator"] svg,
2540
+ [data-scope="cascade-select"][data-part="item-indicator"] svg {
2541
+ inline-size: var(--bs-space-4);
2542
+ block-size: var(--bs-space-4);
2543
+ }
2544
+
2545
+ [data-scope="cascade-select"][data-part="item"][data-state="checked"] [data-part="item-indicator"],
2546
+ [data-scope="cascade-select"][data-part="item"][data-highlighted] [data-part="branch-indicator"] {
2547
+ color: currentColor;
2548
+ }
2549
+ `;
2550
+ //#endregion
2551
+ //#region src/styles/components/checkbox.ts
2552
+ const checkboxCss = labelCss("checkbox") + `
2553
+ [data-scope="checkbox"][data-part="root"] {
2554
+ display: inline-flex;
2555
+ align-items: center;
2556
+ gap: var(--bs-space-2);
2557
+ color: var(--bs-color-text-primary);
2558
+ font-size: var(--bs-font-size-sm);
2559
+ }
2560
+
2561
+ /* A group of checkboxes is one field: the rows stack, and the invalid
2562
+ * pigment bleeds onto every row inside. */
2563
+ [data-scope="checkbox"][data-part="group"] {
2016
2564
  display: flex;
2017
2565
  flex-direction: column;
2018
2566
  gap: var(--bs-space-2);
@@ -2029,8 +2577,8 @@ const checkboxCss = labelCss("checkbox") + `
2029
2577
  align-items: center;
2030
2578
  justify-content: center;
2031
2579
  flex-shrink: 0;
2032
- inline-size: var(--bs-part-size-md);
2033
- block-size: var(--bs-part-size-md);
2580
+ inline-size: var(--bs-part-size-sm);
2581
+ block-size: var(--bs-part-size-sm);
2034
2582
  border: 1px solid var(--bs-color-border);
2035
2583
  border-radius: var(--bs-radius-sm);
2036
2584
  background: var(--bs-color-surface-2);
@@ -2090,8 +2638,8 @@ const checkboxCss = labelCss("checkbox") + `
2090
2638
  }
2091
2639
 
2092
2640
  [data-scope="checkbox"][data-part="indicator"] svg {
2093
- inline-size: calc(var(--bs-part-size-md) * 0.7);
2094
- block-size: calc(var(--bs-part-size-md) * 0.7);
2641
+ inline-size: calc(var(--bs-part-size-sm) * 0.7);
2642
+ block-size: calc(var(--bs-part-size-sm) * 0.7);
2095
2643
  }
2096
2644
 
2097
2645
  [data-scope="checkbox"][data-part="root"]:has([data-disabled]) {
@@ -2103,6 +2651,24 @@ const checkboxCss = labelCss("checkbox") + `
2103
2651
  }
2104
2652
  `;
2105
2653
  //#endregion
2654
+ //#region src/styles/components/checkbox-group.ts
2655
+ const checkboxGroupCss = `
2656
+ /* The group stacks its seals by default; the horizontal layout keeps the
2657
+ natural reading order and wraps when the container narrows. */
2658
+ [data-scope="checkbox-group"][data-part="root"] {
2659
+ display: grid;
2660
+ gap: var(--bs-space-3);
2661
+ max-inline-size: 20rem;
2662
+ }
2663
+
2664
+ [data-scope="checkbox-group"][data-part="root"][data-layout="horizontal"] {
2665
+ display: flex;
2666
+ flex-wrap: wrap;
2667
+ gap: var(--bs-space-2) var(--bs-space-4);
2668
+ max-inline-size: none;
2669
+ }
2670
+ `;
2671
+ //#endregion
2106
2672
  //#region src/styles/components/chip.ts
2107
2673
  const chipCss = `
2108
2674
  /* A counting coin: one small number, round as a seal impression. It sits
@@ -2313,6 +2879,7 @@ const collapsibleCss = `
2313
2879
  /* A collapsible stands alone, so its trigger is a full control: paper
2314
2880
  surface, one hairline, resting on xs. */
2315
2881
  [data-scope="collapsible"][data-part="trigger"] {
2882
+ box-sizing: border-box;
2316
2883
  display: flex;
2317
2884
  align-items: center;
2318
2885
  justify-content: space-between;
@@ -2452,7 +3019,6 @@ const colorPickerCss = labelCss("color-picker") + positionerCss("color-picker")
2452
3019
  display: flex;
2453
3020
  flex-direction: column;
2454
3021
  gap: var(--bs-space-2);
2455
- max-inline-size: 17rem;
2456
3022
  }
2457
3023
 
2458
3024
  [data-scope="color-picker"][data-part="root"][data-disabled] {
@@ -2521,7 +3087,7 @@ const colorPickerCss = labelCss("color-picker") + positionerCss("color-picker")
2521
3087
  [data-scope="color-picker"][data-part="swatch"] {
2522
3088
  position: absolute;
2523
3089
  inset: var(--bs-space-1);
2524
- border-radius: calc(var(--bs-radius-sm) - 2px);
3090
+ border-radius: var(--bs-radius-xs);
2525
3091
  box-shadow: inset 0 0 0 1px var(--bs-color-border);
2526
3092
  }
2527
3093
 
@@ -2531,7 +3097,7 @@ const colorPickerCss = labelCss("color-picker") + positionerCss("color-picker")
2531
3097
  [data-scope="color-picker"][data-part="transparency-grid"] {
2532
3098
  position: absolute;
2533
3099
  inset: var(--bs-space-1);
2534
- border-radius: calc(var(--bs-radius-sm) - 2px);
3100
+ border-radius: var(--bs-radius-xs);
2535
3101
  }
2536
3102
 
2537
3103
  /* The geometry vars live on the content: the popup teleports to body, out
@@ -2566,7 +3132,7 @@ const colorPickerCss = labelCss("color-picker") + positionerCss("color-picker")
2566
3132
  position: absolute;
2567
3133
  inline-size: var(--bs-color-picker-thumb);
2568
3134
  block-size: var(--bs-color-picker-thumb);
2569
- border-radius: 9999px;
3135
+ border-radius: var(--bs-radius-full);
2570
3136
  outline: none;
2571
3137
  transform: translate(-50%, -50%);
2572
3138
  box-shadow:
@@ -2598,7 +3164,7 @@ const colorPickerCss = labelCss("color-picker") + positionerCss("color-picker")
2598
3164
  position: absolute;
2599
3165
  inline-size: var(--bs-color-picker-thumb);
2600
3166
  block-size: var(--bs-color-picker-thumb);
2601
- border-radius: 9999px;
3167
+ border-radius: var(--bs-radius-full);
2602
3168
  outline: none;
2603
3169
  transform: translate(-50%, -50%);
2604
3170
  box-shadow:
@@ -3060,108 +3626,359 @@ const comboboxCss = labelCss("combobox") + positionerCss("combobox") + popupCont
3060
3626
  }
3061
3627
  `;
3062
3628
  //#endregion
3063
- //#region src/styles/components/date-input.ts
3064
- const dateInputCss = labelCss("date-input") + `
3065
- [data-scope="date-input"][data-part="root"] {
3629
+ //#region src/styles/components/command.ts
3630
+ const commandCss = `
3631
+ /* The palette's footing: full-viewport, the sheet docked a fifth of the
3632
+ way down (structural placement, not a spacing value). */
3633
+ [data-scope="command"][data-part="positioner"] {
3634
+ position: fixed;
3635
+ inset: 0;
3636
+ z-index: calc(var(--bs-z-overlay) + var(--layer-index, 0));
3066
3637
  display: flex;
3067
- flex-direction: column;
3068
- gap: var(--bs-space-2);
3638
+ justify-content: center;
3639
+ align-items: flex-start;
3640
+ padding: 20dvh var(--bs-padding-lg) var(--bs-padding-lg);
3069
3641
  }
3070
3642
 
3071
- [data-scope="date-input"][data-part="control"] {
3643
+ /* The sheet: a wide vessel at the top of the elevation ladder, entering
3644
+ as ink dissolving into paper. */
3645
+ [data-scope="command"][data-part="content"] {
3646
+ position: relative;
3647
+ box-sizing: border-box;
3648
+ inline-size: min(36rem, 100%);
3072
3649
  display: flex;
3073
- align-items: center;
3074
- gap: var(--bs-space-2);
3650
+ flex-direction: column;
3651
+ padding: var(--bs-padding-md);
3652
+ border: 1px solid var(--bs-color-border);
3653
+ border-radius: var(--bs-radius-lg);
3654
+ background: var(--bs-color-surface-2);
3655
+ color: var(--bs-color-text-primary);
3656
+ box-shadow: var(--bs-elevation-4);
3657
+ transition:
3658
+ opacity var(--bs-duration-base) var(--bs-ease-out),
3659
+ translate var(--bs-duration-base) var(--bs-ease-spring);
3075
3660
  }
3076
3661
 
3077
- /* The segment group IS the field — the same border + surface + focus halo
3078
- recipe as every input, no shadow. */
3079
- [data-scope="date-input"][data-part="segment-group"] {
3662
+ [data-scope="command"][data-state="open"][data-part="content"] {
3663
+ box-shadow: var(--bs-elevation-5);
3664
+ animation: bs-ink-in var(--bs-duration-slow) var(--bs-ease-out);
3665
+ }
3666
+
3667
+ /* The search field carries the same recipe as every input: border +
3668
+ surface + focus halo, never a shadow lift. */
3669
+ [data-scope="command"][data-part="input"] {
3080
3670
  box-sizing: border-box;
3081
- flex: 1;
3082
- display: flex;
3083
- align-items: center;
3084
- min-width: 0;
3085
- block-size: var(--bs-control-height-md);
3671
+ inline-size: 100%;
3672
+ block-size: var(--bs-control-height-lg);
3086
3673
  padding: 0 var(--bs-padding-md);
3087
- border: 1px solid var(--bs-color-border);
3088
- border-radius: var(--bs-radius-sm);
3089
- background: var(--bs-color-surface-2);
3674
+ border: none;
3675
+ border-block-end: 1px solid var(--bs-color-border);
3676
+ border-radius: 0;
3677
+ background: transparent;
3090
3678
  color: var(--bs-color-text-primary);
3091
3679
  font: inherit;
3092
3680
  font-size: var(--bs-font-size-md);
3093
- cursor: text;
3094
- transition: border-color var(--bs-duration-fast) var(--bs-ease-out);
3095
3681
  }
3096
3682
 
3097
- [data-scope="date-input"][data-part="segment-group"]:hover:not([data-disabled], [data-readonly]) {
3098
- border-color: var(--bs-color-border-strong);
3683
+ [data-scope="command"][data-part="input"]::placeholder {
3684
+ color: var(--bs-color-text-tertiary);
3099
3685
  }
3100
3686
 
3101
- [data-scope="date-input"][data-part="segment-group"][data-focus] {
3102
- border-color: var(--bs-color-primary);
3103
- box-shadow: var(--bs-focus-ring);
3687
+ [data-scope="command"][data-part="input"]:focus,
3688
+ [data-scope="command"][data-part="input"]:focus-visible {
3689
+ outline: none;
3690
+ box-shadow: var(--bs-focus-ring-inset);
3104
3691
  }
3105
3692
 
3106
- [data-scope="date-input"][data-part="segment-group"][data-invalid] {
3107
- border-color: var(--bs-color-danger);
3693
+ /* The ledger: the machine's content grafts onto this list, so the
3694
+ vessel stays inside the sheet instead of floating a second popup. */
3695
+ [data-scope="command"][data-part="list"] {
3696
+ display: flex;
3697
+ flex-direction: column;
3698
+ gap: var(--bs-space-1);
3699
+ margin-block-start: var(--bs-space-2);
3700
+ max-block-size: min(50dvh, 24rem);
3701
+ padding: var(--bs-space-1);
3702
+ overflow-y: auto;
3703
+ overscroll-behavior: contain;
3108
3704
  }
3109
3705
 
3110
- [data-scope="date-input"][data-part="segment-group"][data-invalid][data-focus] {
3111
- box-shadow: var(--bs-focus-ring);
3706
+ [data-scope="command"][data-part="list"][hidden] {
3707
+ display: none;
3112
3708
  }
3113
3709
 
3114
- [data-scope="date-input"][data-part="segment-group"][data-disabled] {
3115
- border-color: var(--bs-color-border);
3116
- background: var(--bs-color-surface-inset);
3117
- color: var(--bs-color-text-disabled);
3118
- cursor: not-allowed;
3710
+ [data-scope="command"][data-part="group"] {
3711
+ display: flex;
3712
+ flex-direction: column;
3713
+ gap: var(--bs-space-1);
3119
3714
  }
3120
3715
 
3121
- [data-scope="date-input"][data-part="segment-group"][data-readonly] {
3122
- background: var(--bs-color-surface-inset);
3716
+ [data-scope="command"][data-part="group"] + [data-scope="command"][data-part="group"] {
3717
+ margin-block-start: var(--bs-space-2);
3123
3718
  }
3124
3719
 
3125
- [data-scope="date-input"][data-part="segment"] {
3126
- display: inline-flex;
3127
- align-items: center;
3128
- justify-content: center;
3129
- min-width: 2ch;
3130
- padding: 0 2px;
3131
- border-radius: var(--bs-radius-xs);
3132
- font-variant-numeric: tabular-nums;
3133
- caret-color: transparent;
3134
- outline: none;
3720
+ /* Group headings whisper: small, tracked, never competing with rows. */
3721
+ [data-scope="command"][data-part="group-label"] {
3722
+ padding: var(--bs-space-1) var(--bs-space-2);
3723
+ color: var(--bs-color-text-tertiary);
3724
+ font-size: var(--bs-font-size-xs);
3725
+ font-weight: var(--bs-font-weight-medium);
3726
+ letter-spacing: var(--bs-tracking-label);
3727
+ text-transform: uppercase;
3728
+ user-select: none;
3135
3729
  }
3136
3730
 
3137
- /* The focused segment takes the flat primary fill ink where the eye is. */
3138
- [data-scope="date-input"][data-part="segment"]:focus {
3139
- background: var(--bs-color-primary);
3140
- color: var(--bs-color-primary-text);
3731
+ /* Rows are quiet ink: the highlight is light on the row, not a fill. */
3732
+ [data-scope="command"][data-part="item"] {
3733
+ display: flex;
3734
+ align-items: center;
3735
+ justify-content: space-between;
3736
+ gap: var(--bs-space-2);
3737
+ min-block-size: var(--bs-control-height-sm);
3738
+ padding: 0 var(--bs-space-2);
3739
+ border-radius: var(--bs-radius-sm);
3740
+ color: var(--bs-color-text-primary);
3741
+ font-size: var(--bs-font-size-sm);
3742
+ cursor: pointer;
3743
+ user-select: none;
3744
+ transition: background-color var(--bs-duration-fast) var(--bs-ease-out);
3141
3745
  }
3142
3746
 
3143
- [data-scope="date-input"][data-part="segment"][data-placeholder-shown] {
3144
- color: var(--bs-color-text-tertiary);
3747
+ [data-scope="command"][data-part="item"][data-highlighted] {
3748
+ background: var(--bs-color-surface-0);
3145
3749
  }
3146
3750
 
3147
- [data-scope="date-input"][data-part="segment"]:focus[data-placeholder-shown] {
3148
- color: var(--bs-color-primary-text);
3751
+ [data-scope="command"][data-part="item"][data-disabled] {
3752
+ color: var(--bs-color-text-disabled);
3753
+ cursor: not-allowed;
3149
3754
  }
3150
3755
 
3151
- /* Separators are type, not controls — never take the ink. */
3152
- [data-scope="date-input"][data-part="segment"][data-type="literal"] {
3153
- min-width: 0;
3154
- padding: 0;
3155
- color: var(--bs-color-text-tertiary);
3156
- user-select: none;
3756
+ [data-scope="command"][data-part="item-label"] {
3757
+ flex: 1;
3758
+ overflow: hidden;
3759
+ text-overflow: ellipsis;
3760
+ white-space: nowrap;
3157
3761
  }
3158
3762
 
3159
- /* The hidden input still hosts form autofillvisually gone, structurally
3160
- present. */
3161
- [data-scope="date-input"][data-part="hidden-input"] {
3162
- position: absolute;
3163
- inline-size: 1px;
3164
- block-size: 1px;
3763
+ /* The hint is a keycap in miniaturethe kbd recipe, riding the row. */
3764
+ [data-scope="command"][data-part="item-hint"] {
3765
+ flex: none;
3766
+ display: inline-flex;
3767
+ align-items: center;
3768
+ min-inline-size: 1.75em;
3769
+ padding: 0.125em 0.375em;
3770
+ border: 1px solid var(--bs-color-border);
3771
+ border-radius: var(--bs-radius-sm);
3772
+ background: var(--bs-color-surface-2);
3773
+ box-shadow: 0 1px 0 var(--bs-color-border);
3774
+ color: var(--bs-color-text-secondary);
3775
+ font-size: max(var(--bs-font-size-xs), 0.8125em);
3776
+ line-height: 1.2;
3777
+ font-variant-numeric: tabular-nums;
3778
+ white-space: nowrap;
3779
+ }
3780
+
3781
+ [data-scope="command"][data-part="item"][data-highlighted] [data-scope="command"][data-part="item-hint"] {
3782
+ background: var(--bs-color-surface-1);
3783
+ }
3784
+
3785
+ [data-scope="command"][data-part="empty"] {
3786
+ padding: var(--bs-space-4);
3787
+ color: var(--bs-color-text-tertiary);
3788
+ font-size: var(--bs-font-size-sm);
3789
+ text-align: center;
3790
+ }
3791
+ `;
3792
+ //#endregion
3793
+ //#region src/styles/components/comment.ts
3794
+ const commentCss = `
3795
+ /* A voice on the record: the portrait hangs left, the body carries the
3796
+ byline, the ink, and the row of answers — each in its own register,
3797
+ so the thread reads at a scan. */
3798
+ [data-scope="comment"][data-part="root"] {
3799
+ display: flex;
3800
+ gap: var(--bs-gap-md);
3801
+ padding-block: var(--bs-padding-sm);
3802
+ }
3803
+
3804
+ [data-scope="comment"][data-part="avatar"] {
3805
+ display: flex;
3806
+ flex: none;
3807
+ }
3808
+
3809
+ [data-scope="comment"][data-part="body"] {
3810
+ display: flex;
3811
+ flex: 1;
3812
+ flex-direction: column;
3813
+ gap: var(--bs-space-1);
3814
+ min-inline-size: 0;
3815
+ }
3816
+
3817
+ [data-scope="comment"][data-part="header"] {
3818
+ display: flex;
3819
+ align-items: baseline;
3820
+ gap: var(--bs-space-2);
3821
+ }
3822
+
3823
+ [data-scope="comment"][data-part="author"] {
3824
+ color: var(--bs-color-text-primary);
3825
+ font-weight: var(--bs-font-weight-medium);
3826
+ }
3827
+
3828
+ [data-scope="comment"][data-part="datetime"] {
3829
+ color: var(--bs-color-text-tertiary);
3830
+ font-size: var(--bs-font-size-xs);
3831
+ }
3832
+
3833
+ [data-scope="comment"][data-part="content"] {
3834
+ color: var(--bs-color-text-primary);
3835
+ line-height: var(--bs-line-height-relaxed);
3836
+ }
3837
+
3838
+ [data-scope="comment"][data-part="actions"] {
3839
+ display: flex;
3840
+ align-items: center;
3841
+ gap: var(--bs-space-4);
3842
+ margin-block-start: var(--bs-space-1);
3843
+ font-size: var(--bs-font-size-sm);
3844
+ }
3845
+ `;
3846
+ //#endregion
3847
+ //#region src/styles/components/container.ts
3848
+ const containerCss = `
3849
+ /* The reading frame. The measures are set in ch — the rendered width of
3850
+ the zero glyph — so the clamp tracks the typeset text itself instead
3851
+ of a fixed visual value: the same measure holds fewer Latin
3852
+ characters than CJK ones, and both keep the line short enough to
3853
+ cross without losing its head. Padding keeps the ink off the page
3854
+ edges when the viewport runs narrower than the measure. */
3855
+ [data-scope="container"][data-part="root"] {
3856
+ inline-size: 100%;
3857
+ margin-inline: auto;
3858
+ }
3859
+
3860
+ [data-scope="container"][data-part="root"][data-size="narrow"] {
3861
+ max-inline-size: 48ch;
3862
+ }
3863
+ [data-scope="container"][data-part="root"][data-size="readable"] {
3864
+ max-inline-size: 72ch;
3865
+ }
3866
+ [data-scope="container"][data-part="root"][data-size="wide"] {
3867
+ max-inline-size: 96ch;
3868
+ }
3869
+
3870
+ /* Full leaves the measure to the page — the clamp simply lifts. */
3871
+ [data-scope="container"][data-part="root"][data-size="full"] {
3872
+ max-inline-size: none;
3873
+ }
3874
+
3875
+ [data-scope="container"][data-part="root"][data-padding] {
3876
+ padding-inline: var(--bs-padding-lg);
3877
+ }
3878
+ `;
3879
+ //#endregion
3880
+ //#region src/styles/components/date-input.ts
3881
+ const dateInputCss = labelCss("date-input") + `
3882
+ [data-scope="date-input"][data-part="root"] {
3883
+ display: flex;
3884
+ flex-direction: column;
3885
+ gap: var(--bs-space-2);
3886
+ }
3887
+
3888
+ [data-scope="date-input"][data-part="control"] {
3889
+ display: flex;
3890
+ align-items: center;
3891
+ gap: var(--bs-space-2);
3892
+ }
3893
+
3894
+ /* The segment group IS the field — the same border + surface + focus halo
3895
+ recipe as every input, no shadow. */
3896
+ [data-scope="date-input"][data-part="segment-group"] {
3897
+ box-sizing: border-box;
3898
+ flex: 1;
3899
+ display: flex;
3900
+ align-items: center;
3901
+ min-width: 0;
3902
+ block-size: var(--bs-control-height-md);
3903
+ padding: 0 var(--bs-padding-md);
3904
+ border: 1px solid var(--bs-color-border);
3905
+ border-radius: var(--bs-radius-sm);
3906
+ background: var(--bs-color-surface-2);
3907
+ color: var(--bs-color-text-primary);
3908
+ font: inherit;
3909
+ font-size: var(--bs-font-size-md);
3910
+ cursor: text;
3911
+ transition: border-color var(--bs-duration-fast) var(--bs-ease-out);
3912
+ }
3913
+
3914
+ [data-scope="date-input"][data-part="segment-group"]:hover:not([data-disabled], [data-readonly]) {
3915
+ border-color: var(--bs-color-border-strong);
3916
+ }
3917
+
3918
+ [data-scope="date-input"][data-part="segment-group"][data-focus] {
3919
+ border-color: var(--bs-color-primary);
3920
+ box-shadow: var(--bs-focus-ring);
3921
+ }
3922
+
3923
+ [data-scope="date-input"][data-part="segment-group"][data-invalid] {
3924
+ border-color: var(--bs-color-danger);
3925
+ }
3926
+
3927
+ [data-scope="date-input"][data-part="segment-group"][data-invalid][data-focus] {
3928
+ box-shadow: var(--bs-focus-ring);
3929
+ }
3930
+
3931
+ [data-scope="date-input"][data-part="segment-group"][data-disabled] {
3932
+ border-color: var(--bs-color-border);
3933
+ background: var(--bs-color-surface-inset);
3934
+ color: var(--bs-color-text-disabled);
3935
+ cursor: not-allowed;
3936
+ }
3937
+
3938
+ [data-scope="date-input"][data-part="segment-group"][data-readonly] {
3939
+ background: var(--bs-color-surface-inset);
3940
+ }
3941
+
3942
+ [data-scope="date-input"][data-part="segment"] {
3943
+ display: inline-flex;
3944
+ align-items: center;
3945
+ justify-content: center;
3946
+ min-width: 2ch;
3947
+ padding: 0 2px;
3948
+ border-radius: var(--bs-radius-xs);
3949
+ font-variant-numeric: tabular-nums;
3950
+ caret-color: transparent;
3951
+ outline: none;
3952
+ }
3953
+
3954
+ /* The focused segment takes the flat primary fill — ink where the eye is. */
3955
+ [data-scope="date-input"][data-part="segment"]:focus {
3956
+ background: var(--bs-color-primary);
3957
+ color: var(--bs-color-primary-text);
3958
+ }
3959
+
3960
+ [data-scope="date-input"][data-part="segment"][data-placeholder-shown] {
3961
+ color: var(--bs-color-text-tertiary);
3962
+ }
3963
+
3964
+ [data-scope="date-input"][data-part="segment"]:focus[data-placeholder-shown] {
3965
+ color: var(--bs-color-primary-text);
3966
+ }
3967
+
3968
+ /* Separators are type, not controls — never take the ink. */
3969
+ [data-scope="date-input"][data-part="segment"][data-type="literal"] {
3970
+ min-width: 0;
3971
+ padding: 0;
3972
+ color: var(--bs-color-text-tertiary);
3973
+ user-select: none;
3974
+ }
3975
+
3976
+ /* The hidden input still hosts form autofill — visually gone, structurally
3977
+ present. */
3978
+ [data-scope="date-input"][data-part="hidden-input"] {
3979
+ position: absolute;
3980
+ inline-size: 1px;
3981
+ block-size: 1px;
3165
3982
  opacity: 0;
3166
3983
  pointer-events: none;
3167
3984
  }
@@ -3430,6 +4247,10 @@ const datePickerCss = labelCss("date-picker") + positionerCss("date-picker") + p
3430
4247
  }
3431
4248
 
3432
4249
  [data-scope="date-picker"][data-part="table"] {
4250
+ /* Table layout is the geometry here — the columns must share the
4251
+ panel — so a host's prose reset that blockifies tables (docs sites
4252
+ do this for their markdown tables) must not reach inside. */
4253
+ display: table;
3433
4254
  inline-size: 100%;
3434
4255
  border-collapse: separate;
3435
4256
  border-spacing: 0;
@@ -3540,6 +4361,52 @@ const datePickerCss = labelCss("date-picker") + positionerCss("date-picker") + p
3540
4361
  }
3541
4362
  `;
3542
4363
  //#endregion
4364
+ //#region src/styles/components/descriptions.ts
4365
+ const descriptionsCss = `
4366
+ /* The horizontal ledger: term column, detail column, one hairline
4367
+ between rows — a table that never becomes one. */
4368
+ [data-scope="descriptions"][data-part="root"] {
4369
+ margin: 0;
4370
+ display: flex;
4371
+ flex-direction: column;
4372
+ gap: var(--bs-space-2);
4373
+ font-size: var(--bs-font-size-md);
4374
+ }
4375
+
4376
+ [data-scope="descriptions"][data-part="item"] {
4377
+ display: grid;
4378
+ grid-template-columns: minmax(6em, max-content) 1fr;
4379
+ gap: var(--bs-space-4);
4380
+ padding-block: var(--bs-space-2);
4381
+ border-block-end: 1px solid var(--bs-color-border);
4382
+ }
4383
+
4384
+ [data-scope="descriptions"][data-part="item"]:last-child {
4385
+ border-block-end: none;
4386
+ }
4387
+
4388
+ [data-scope="descriptions"][data-part="term"] {
4389
+ color: var(--bs-color-text-tertiary);
4390
+ font-size: var(--bs-font-size-sm);
4391
+ letter-spacing: var(--bs-tracking-label);
4392
+ }
4393
+
4394
+ [data-scope="descriptions"][data-part="detail"] {
4395
+ margin: 0;
4396
+ color: var(--bs-color-text-primary);
4397
+ }
4398
+
4399
+ /* The vertical ledger: each pair stacked, for narrow measures. */
4400
+ [data-scope="descriptions"][data-part="root"][data-layout="vertical"] [data-part="item"] {
4401
+ grid-template-columns: 1fr;
4402
+ gap: var(--bs-space-1);
4403
+ }
4404
+
4405
+ [data-scope="descriptions"][data-layout="vertical"] [data-part="detail"] {
4406
+ color: var(--bs-color-text-secondary);
4407
+ }
4408
+ `;
4409
+ //#endregion
3543
4410
  //#region src/styles/components/dialog.ts
3544
4411
  const dialogCss = `
3545
4412
  [data-scope="dialog"][data-part="trigger"] {
@@ -3808,6 +4675,31 @@ const drawerCss = positionerCss("drawer") + `
3808
4675
  background: var(--bs-color-text-tertiary);
3809
4676
  }
3810
4677
 
4678
+ /* A side sheet's handle turns on its side and rides the leading edge:
4679
+ the hand drags along the same axis it dismisses, so the bar reads as
4680
+ a rail, not a lid. */
4681
+ [data-scope="drawer"][data-part="content"][data-swipe-direction="left"] [data-scope="drawer"][data-part="grabber"],
4682
+ [data-scope="drawer"][data-part="content"][data-swipe-direction="right"] [data-scope="drawer"][data-part="grabber"] {
4683
+ position: absolute;
4684
+ inset-block: 0;
4685
+ inline-size: auto;
4686
+ padding-inline: var(--bs-space-2);
4687
+ }
4688
+
4689
+ [data-scope="drawer"][data-part="content"][data-swipe-direction="left"] [data-scope="drawer"][data-part="grabber"] {
4690
+ inset-inline-start: 0;
4691
+ }
4692
+
4693
+ [data-scope="drawer"][data-part="content"][data-swipe-direction="right"] [data-scope="drawer"][data-part="grabber"] {
4694
+ inset-inline-end: 0;
4695
+ }
4696
+
4697
+ [data-scope="drawer"][data-part="content"][data-swipe-direction="left"] [data-scope="drawer"][data-part="grabber-indicator"],
4698
+ [data-scope="drawer"][data-part="content"][data-swipe-direction="right"] [data-scope="drawer"][data-part="grabber-indicator"] {
4699
+ inline-size: var(--bs-space-1);
4700
+ block-size: var(--bs-space-8);
4701
+ }
4702
+
3811
4703
  /* The trigger is a seal-cut control: paper on a hairline, deepening on
3812
4704
  hover, the shadow letting go under the press. */
3813
4705
  [data-scope="drawer"][data-part="trigger"] {
@@ -3948,23 +4840,67 @@ const drawerCss = positionerCss("drawer") + `
3948
4840
  }
3949
4841
  `;
3950
4842
  //#endregion
4843
+ //#region src/styles/components/dynamic-input.ts
4844
+ const dynamicInputCss = `
4845
+ /* A column of entry rows: the rows keep their rhythm on the density gap,
4846
+ the group itself stays frameless — the inputs carry the recipe. */
4847
+ [data-scope="dynamic-input"][data-part="root"] {
4848
+ display: flex;
4849
+ flex-direction: column;
4850
+ gap: var(--bs-gap-sm);
4851
+ inline-size: 100%;
4852
+ }
4853
+
4854
+ [data-scope="dynamic-input"][data-part="row"] {
4855
+ display: flex;
4856
+ align-items: center;
4857
+ gap: var(--bs-gap-xs);
4858
+ }
4859
+
4860
+ /* The entry stretches to the row; the remove seal keeps its own width. */
4861
+ [data-scope="dynamic-input"][data-part="row"] [data-scope="input"][data-part="root"] {
4862
+ flex: 1;
4863
+ min-inline-size: 0;
4864
+ }
4865
+
4866
+ [data-scope="dynamic-input"][data-part="add"] {
4867
+ display: flex;
4868
+ }
4869
+ `;
4870
+ //#endregion
3951
4871
  //#region src/styles/components/editable.ts
3952
4872
  const editableCss = labelCss("editable") + `
3953
4873
  [data-scope="editable"][data-part="root"] {
3954
- display: flex;
3955
- flex-direction: column;
4874
+ display: grid;
4875
+ grid-template-columns: 1fr auto;
4876
+ grid-template-areas:
4877
+ "label label"
4878
+ "area control";
3956
4879
  gap: var(--bs-space-2);
4880
+ align-items: center;
3957
4881
  inline-size: 100%;
3958
4882
  }
3959
4883
 
4884
+ [data-scope="editable"][data-part="label"] {
4885
+ grid-area: label;
4886
+ }
4887
+
3960
4888
  [data-scope="editable"][data-part="root"][data-disabled] {
3961
4889
  color: var(--bs-color-text-disabled);
3962
4890
  }
3963
4891
 
4892
+ /* The text and its triggers share one row: the field stretches, the
4893
+ seals sit at its right shoulder. */
3964
4894
  [data-scope="editable"][data-part="area"] {
4895
+ grid-area: area;
3965
4896
  position: relative;
3966
4897
  display: flex;
3967
4898
  align-items: center;
4899
+ min-inline-size: 0;
4900
+ }
4901
+
4902
+ [data-scope="editable"][data-part="control"] {
4903
+ grid-area: control;
3968
4904
  }
3969
4905
 
3970
4906
  /* Preview and input share one geometry so the swap never shifts the page;
@@ -4101,12 +5037,37 @@ const editableCss = labelCss("editable") + `
4101
5037
  }
4102
5038
  `;
4103
5039
  //#endregion
5040
+ //#region src/styles/components/ellipsis.ts
5041
+ const ellipsisCss = `
5042
+ /* One line, cut: the overflow knife draws the ellipsis at the box edge.
5043
+ The element is a span, stood up as a block — the cut only works on a
5044
+ block-level box. */
5045
+ [data-scope="ellipsis"][data-part="root"] {
5046
+ display: block;
5047
+ overflow: hidden;
5048
+ text-overflow: ellipsis;
5049
+ white-space: nowrap;
5050
+ }
5051
+
5052
+ /* N lines, clamped: the box-line algorithm counts the lines the
5053
+ wrapper hands over in --bs-ellipsis-lines. Word wrapping stays on —
5054
+ a long word must never force the clamp to cut mid-glyph. */
5055
+ [data-scope="ellipsis"][data-part="root"][data-multiline] {
5056
+ display: -webkit-box;
5057
+ overflow-wrap: break-word;
5058
+ white-space: normal;
5059
+ -webkit-box-orient: vertical;
5060
+ -webkit-line-clamp: var(--bs-ellipsis-lines);
5061
+ }
5062
+ `;
5063
+ //#endregion
4104
5064
  //#region src/styles/components/empty.ts
4105
5065
  const emptyCss = `
4106
5066
  /* An empty state: the page holds its breath — a quiet mark, one line of
4107
5067
  ink, and room for the next action. Centered, generous with whitespace. */
4108
5068
  [data-scope="empty"][data-part="root"] {
4109
5069
  /* Full width is the component's own property, not the stage's stretch. */
5070
+ box-sizing: border-box;
4110
5071
  inline-size: 100%;
4111
5072
  display: flex;
4112
5073
  flex-direction: column;
@@ -4153,17 +5114,10 @@ const emptyCss = `
4153
5114
  `;
4154
5115
  //#endregion
4155
5116
  //#region src/styles/components/field.ts
4156
- const fieldCss = labelCss("field") + `
4157
- [data-scope="field"][data-part="root"] {
4158
- display: flex;
4159
- flex-direction: column;
4160
- align-items: start;
4161
- gap: var(--bs-space-2);
4162
- inline-size: 100%;
4163
- }
4164
-
4165
- /* The control itself: inputs rely on border + surface + focus halo, never
4166
- a shadow — a field sits on the paper, it does not float above it. */
5117
+ /** The control recipe alone: border + surface + focus halo, the register
5118
+ * every text entry rides. The bare Input and Textarea families re-scope
5119
+ * this to their own anatomy instead of copying it. */
5120
+ const fieldControlCss = `
4167
5121
  [data-scope="field"][data-part="input"],
4168
5122
  [data-scope="field"][data-part="textarea"],
4169
5123
  [data-scope="field"][data-part="select"] {
@@ -4187,6 +5141,20 @@ const fieldCss = labelCss("field") + `
4187
5141
  block-size: var(--bs-control-height-md);
4188
5142
  }
4189
5143
 
5144
+ /* Size tiers ride the control-height ladder, as anywhere. */
5145
+ [data-scope="field"][data-part="input"][data-size="sm"],
5146
+ [data-scope="field"][data-part="select"][data-size="sm"] {
5147
+ block-size: var(--bs-control-height-sm);
5148
+ padding-inline: var(--bs-padding-sm);
5149
+ font-size: var(--bs-font-size-sm);
5150
+ }
5151
+
5152
+ [data-scope="field"][data-part="input"][data-size="lg"],
5153
+ [data-scope="field"][data-part="select"][data-size="lg"] {
5154
+ block-size: var(--bs-control-height-lg);
5155
+ padding-inline: var(--bs-padding-lg);
5156
+ }
5157
+
4190
5158
  [data-scope="field"][data-part="textarea"] {
4191
5159
  min-block-size: calc(var(--bs-control-height-md) * 2 + var(--bs-space-2));
4192
5160
  padding-block: var(--bs-space-2);
@@ -4227,7 +5195,16 @@ const fieldCss = labelCss("field") + `
4227
5195
  color: var(--bs-color-text-disabled);
4228
5196
  cursor: not-allowed;
4229
5197
  }
4230
-
5198
+ `;
5199
+ const fieldCss = labelCss("field") + `
5200
+ [data-scope="field"][data-part="root"] {
5201
+ display: flex;
5202
+ flex-direction: column;
5203
+ align-items: start;
5204
+ gap: var(--bs-space-2);
5205
+ inline-size: 100%;
5206
+ }
5207
+ ` + fieldControlCss + `
4231
5208
  /* The required mark whispers, never shouts: a quiet pigment point after
4232
5209
  the label. */
4233
5210
  [data-scope="field"][data-part="required-indicator"] {
@@ -4250,6 +5227,49 @@ const fieldCss = labelCss("field") + `
4250
5227
  [data-scope="field"][data-part="root"][data-disabled] {
4251
5228
  color: var(--bs-color-text-disabled);
4252
5229
  }
5230
+
5231
+ /* The floating-label variant, opted in per field with [data-float]. The
5232
+ label starts riding the control at placeholder height and floats to
5233
+ the control's top edge once the reader focuses or types. :has() reads
5234
+ the real input element so the pairing survives whatever part marks the
5235
+ machine writes; the empty-looking space placeholder keeps
5236
+ :placeholder-shown honest for an untouched field. */
5237
+ [data-scope="field"][data-float][data-part="root"] {
5238
+ position: relative;
5239
+ /* Room above the control edge for the label once it is afloat. */
5240
+ margin-block-start: var(--bs-space-4);
5241
+ }
5242
+
5243
+ [data-scope="field"][data-float][data-part="root"] [data-part="label"] {
5244
+ position: absolute;
5245
+ inset-inline-start: var(--bs-padding-md);
5246
+ inset-block-start: calc(var(--bs-control-height-md) / 2);
5247
+ translate: 0 -50%;
5248
+ color: var(--bs-color-text-tertiary);
5249
+ pointer-events: none;
5250
+ /* The control's own paper under the label cuts the hairline it rides,
5251
+ the way a legend cuts a fieldset border. */
5252
+ padding: 0 var(--bs-space-1);
5253
+ background: var(--bs-color-surface-2);
5254
+ transition:
5255
+ inset-block-start var(--bs-duration-fast) var(--bs-ease-out),
5256
+ color var(--bs-duration-fast) var(--bs-ease-out),
5257
+ font-size var(--bs-duration-fast) var(--bs-ease-out);
5258
+ }
5259
+
5260
+ [data-scope="field"][data-float][data-part="root"]:has(input:focus) [data-part="label"],
5261
+ [data-scope="field"][data-float][data-part="root"]:has(input:not(:placeholder-shown)) [data-part="label"],
5262
+ [data-scope="field"][data-float][data-part="root"]:has(textarea:not(:placeholder-shown)) [data-part="label"] {
5263
+ inset-block-start: 0;
5264
+ font-size: var(--bs-font-size-xs);
5265
+ color: var(--bs-color-text-secondary);
5266
+ }
5267
+
5268
+ /* While the reader is in the field, the afloat label carries the same
5269
+ pigment as the haloed hairline. */
5270
+ [data-scope="field"][data-float][data-part="root"]:has(input:focus) [data-part="label"] {
5271
+ color: var(--bs-color-primary);
5272
+ }
4253
5273
  `;
4254
5274
  //#endregion
4255
5275
  //#region src/styles/components/fieldset.ts
@@ -4312,7 +5332,6 @@ const fileUploadCss = labelCss("file-upload") + `
4312
5332
  align-items: flex-start;
4313
5333
  gap: var(--bs-space-3);
4314
5334
  inline-size: 100%;
4315
- max-inline-size: 24rem;
4316
5335
  }
4317
5336
 
4318
5337
  /* The choose-files button rides the control recipe: paper fill, one
@@ -4555,6 +5574,136 @@ const fileUploadCss = labelCss("file-upload") + `
4555
5574
  }
4556
5575
  `;
4557
5576
  //#endregion
5577
+ //#region src/styles/components/float-button.ts
5578
+ const floatButtonCss = `
5579
+ /* A float: the trigger moors at the page corner and the actions fan out
5580
+ from it. It floats, so it casts — and vessels are round: the trigger
5581
+ and every action take the vessel radius, one rung of elevation at
5582
+ rest, lifting another under the pointer on the slow shadow clock. */
5583
+ [data-scope="float-button"][data-part="root"] {
5584
+ position: fixed;
5585
+ display: flex;
5586
+ /* The fan grows away from the mooring: first in the DOM sits nearest
5587
+ the corner, the actions rise (or hang) beyond it. */
5588
+ flex-direction: column-reverse;
5589
+ gap: var(--bs-space-2);
5590
+ z-index: calc(var(--bs-z-overlay) + var(--layer-index, 0));
5591
+ }
5592
+
5593
+ /* Top moorings hang the actions below the trigger instead. */
5594
+ [data-scope="float-button"][data-part="root"][data-placement^="top"] {
5595
+ flex-direction: column;
5596
+ }
5597
+
5598
+ /* The stack hugs its corner: start moorings align inward from the
5599
+ start edge, end moorings from the end edge. */
5600
+ [data-scope="float-button"][data-part="root"][data-placement$="-start"] {
5601
+ align-items: flex-start;
5602
+ }
5603
+
5604
+ [data-scope="float-button"][data-part="root"][data-placement$="-end"] {
5605
+ align-items: flex-end;
5606
+ }
5607
+
5608
+ [data-scope="float-button"][data-part="root"][data-placement="bottom-end"] {
5609
+ inset-block-end: var(--bs-space-6);
5610
+ inset-inline-end: var(--bs-space-6);
5611
+ }
5612
+
5613
+ [data-scope="float-button"][data-part="root"][data-placement="bottom-start"] {
5614
+ inset-block-end: var(--bs-space-6);
5615
+ inset-inline-start: var(--bs-space-6);
5616
+ }
5617
+
5618
+ [data-scope="float-button"][data-part="root"][data-placement="top-end"] {
5619
+ inset-block-start: var(--bs-space-6);
5620
+ inset-inline-end: var(--bs-space-6);
5621
+ }
5622
+
5623
+ [data-scope="float-button"][data-part="root"][data-placement="top-start"] {
5624
+ inset-block-start: var(--bs-space-6);
5625
+ inset-inline-start: var(--bs-space-6);
5626
+ }
5627
+
5628
+ /* The vessel register for both the trigger and the actions — above the
5629
+ recipe's own variant and active rules, so a solid trigger still casts
5630
+ at rest and everything settles back into the page when pressed. */
5631
+ [data-scope="float-button"][data-part="root"] [data-scope="button"][data-part="root"] {
5632
+ border-radius: var(--bs-radius-lg);
5633
+ --bs-shadow-color: color-mix(in oklab, var(--bs-shadow-ink) 30%, transparent);
5634
+ box-shadow: var(--bs-elevation-2);
5635
+ }
5636
+
5637
+ [data-scope="float-button"][data-part="root"] [data-scope="button"][data-part="root"]:hover:not(:disabled) {
5638
+ box-shadow: var(--bs-elevation-3);
5639
+ }
5640
+
5641
+ [data-scope="float-button"][data-part="root"] [data-scope="button"][data-part="root"]:active:not(:disabled) {
5642
+ box-shadow: none;
5643
+ }
5644
+
5645
+ /* Each action rides a row: the round button, and a small annotation
5646
+ surfaced beside it toward the page center while the group is open.
5647
+ The label is prose, not a control — the pointer passes through to
5648
+ the button. */
5649
+ [data-scope="float-button"][data-part="item"] {
5650
+ display: inline-flex;
5651
+ align-items: center;
5652
+ gap: var(--bs-space-2);
5653
+ flex-direction: row-reverse;
5654
+ }
5655
+
5656
+ [data-scope="float-button"][data-part="root"][data-placement$="-start"] [data-scope="float-button"][data-part="item"] {
5657
+ flex-direction: row;
5658
+ }
5659
+
5660
+ [data-scope="float-button"][data-part="item-label"] {
5661
+ padding: var(--bs-space-1) var(--bs-space-2);
5662
+ border: 1px solid var(--bs-color-border);
5663
+ border-radius: var(--bs-radius-sm);
5664
+ background: var(--bs-color-surface-2);
5665
+ color: var(--bs-color-text-secondary);
5666
+ font-size: var(--bs-font-size-xs);
5667
+ letter-spacing: var(--bs-tracking-label);
5668
+ white-space: nowrap;
5669
+ pointer-events: none;
5670
+ }
5671
+
5672
+ /* The fan folds, it never pops: actions fade and drift toward the
5673
+ trigger, and only once the fold has finished do they leave the
5674
+ reading order — visibility rounds the transition off, so a closed
5675
+ group is unfocusable without a bounce. States remain; animation does
5676
+ not. */
5677
+ [data-scope="float-button"][data-part="item"] {
5678
+ transition:
5679
+ opacity var(--bs-duration-base) var(--bs-ease-out),
5680
+ translate var(--bs-duration-base) var(--bs-ease-spring),
5681
+ visibility var(--bs-duration-base);
5682
+ }
5683
+
5684
+ [data-scope="float-button"][data-part="item-label"] {
5685
+ transition: opacity var(--bs-duration-fast) var(--bs-ease-out);
5686
+ }
5687
+
5688
+ [data-scope="float-button"][data-part="root"][data-state="closed"] [data-scope="float-button"][data-part="item"] {
5689
+ opacity: 0;
5690
+ visibility: hidden;
5691
+ pointer-events: none;
5692
+ }
5693
+
5694
+ [data-scope="float-button"][data-part="root"][data-placement^="bottom"][data-state="closed"] [data-scope="float-button"][data-part="item"] {
5695
+ translate: 0 var(--bs-space-2);
5696
+ }
5697
+
5698
+ [data-scope="float-button"][data-part="root"][data-placement^="top"][data-state="closed"] [data-scope="float-button"][data-part="item"] {
5699
+ translate: 0 calc(var(--bs-space-2) * -1);
5700
+ }
5701
+
5702
+ [data-scope="float-button"][data-part="root"][data-state="closed"] [data-scope="float-button"][data-part="item-label"] {
5703
+ opacity: 0;
5704
+ }
5705
+ `;
5706
+ //#endregion
4558
5707
  //#region src/styles/components/floating-panel.ts
4559
5708
  const floatingPanelCss = positionerCss("floating-panel") + popupContentCss("floating-panel", "20rem") + `
4560
5709
  [data-scope="floating-panel"][data-part="positioner"] {
@@ -4808,6 +5957,43 @@ const floatingPanelCss = positionerCss("floating-panel") + popupContentCss("floa
4808
5957
  }
4809
5958
  `;
4810
5959
  //#endregion
5960
+ //#region src/styles/components/form.ts
5961
+ const formCss = `
5962
+ /* The form is a quiet container — fields stack in one column and the
5963
+ spacing belongs to the grid, not to each field's margins. */
5964
+ [data-scope="form"][data-part="root"] {
5965
+ display: grid;
5966
+ gap: var(--bs-space-4);
5967
+ max-inline-size: 34rem;
5968
+ }
5969
+
5970
+ /* The FormField's wrapper keeps the field from stretching its own
5971
+ content wider than the grid column. */
5972
+ [data-form-field] {
5973
+ min-inline-size: 0;
5974
+ }
5975
+ `;
5976
+ //#endregion
5977
+ //#region src/styles/components/grid.ts
5978
+ const gridCss = `
5979
+ /* The alignment lattice. minmax(0, 1fr) — not a bare 1fr: a long word
5980
+ or a wide child would otherwise stretch its track past the lattice
5981
+ and shove every sibling aside. The column count and the gap ride CSS
5982
+ variables the wrapper points at their tokens. */
5983
+ [data-scope="grid"][data-part="root"] {
5984
+ display: grid;
5985
+ grid-template-columns: repeat(var(--bs-grid-columns, 12), minmax(0, 1fr));
5986
+ gap: var(--bs-grid-gap, var(--bs-space-3));
5987
+ }
5988
+
5989
+ /* The auto-fill lattice: as many tracks as the container fits, each at
5990
+ least --bs-grid-min-child-width. The wrapper sets the variable and
5991
+ this attribute together — one never arrives without the other. */
5992
+ [data-scope="grid"][data-part="root"][data-autofill] {
5993
+ grid-template-columns: repeat(auto-fill, minmax(var(--bs-grid-min-child-width), 1fr));
5994
+ }
5995
+ `;
5996
+ //#endregion
4811
5997
  //#region src/styles/components/highlight.ts
4812
5998
  const highlightCss = `
4813
5999
  /* Search hits are strokes of pigment on the page, not neon. The highlight
@@ -4864,6 +6050,136 @@ const hoverCardCss = popupContentCss("hover-card") + positionerCss("hover-card")
4864
6050
  }
4865
6051
  `;
4866
6052
  //#endregion
6053
+ //#region src/styles/components/icon.ts
6054
+ const iconCss = `
6055
+ /* The inkwell: one box at the glyph's optical measure, riding the
6056
+ text's own color — the fill comes from currentColor, so the icon
6057
+ carries no pigment of its own. The size steps follow the surrounding
6058
+ font size, so an icon sits in a line of any size with no breakpoint
6059
+ of its own; inherit is that same one-em default, named. */
6060
+ [data-scope="icon"][data-part="root"] {
6061
+ --_size: 1em;
6062
+ display: inline-flex;
6063
+ align-items: center;
6064
+ justify-content: center;
6065
+ flex: none;
6066
+ inline-size: var(--_size);
6067
+ block-size: var(--_size);
6068
+ font-size: var(--_size);
6069
+ fill: currentColor;
6070
+ }
6071
+
6072
+ [data-scope="icon"][data-part="root"][data-size="sm"] {
6073
+ --_size: 0.75em;
6074
+ }
6075
+ [data-scope="icon"][data-part="root"][data-size="md"] {
6076
+ --_size: 1em;
6077
+ }
6078
+ [data-scope="icon"][data-part="root"][data-size="lg"] {
6079
+ --_size: 1.25em;
6080
+ }
6081
+
6082
+ /* The glyph fills the box it is given — a standard measure is the
6083
+ whole point of the well. */
6084
+ [data-scope="icon"][data-part="root"] > svg {
6085
+ display: block;
6086
+ inline-size: 100%;
6087
+ block-size: 100%;
6088
+ }
6089
+ `;
6090
+ //#endregion
6091
+ //#region src/styles/components/image.ts
6092
+ const imageCss = `
6093
+ /* A framed picture that announces its own arrival: while the source
6094
+ loads, the frame keeps the skeleton's breath; the picture dissolves
6095
+ in when it lands; a broken source leaves the fallback. The frame's
6096
+ size belongs to the consumer, like the skeleton's. */
6097
+ [data-scope="image"][data-part="root"] {
6098
+ --_fit: cover;
6099
+ position: relative;
6100
+ display: block;
6101
+ margin: 0;
6102
+ overflow: hidden;
6103
+ border-radius: var(--bs-radius-sm);
6104
+ background: var(--bs-color-surface-inset);
6105
+ }
6106
+
6107
+ [data-scope="image"][data-part="root"][data-fit="contain"] {
6108
+ --_fit: contain;
6109
+ }
6110
+
6111
+ [data-scope="image"][data-part="root"][data-fit="fill"] {
6112
+ --_fit: fill;
6113
+ }
6114
+
6115
+ [data-scope="image"][data-part="root"][data-fit="none"] {
6116
+ --_fit: none;
6117
+ }
6118
+
6119
+ /* Waiting is the skeleton's breath — same wash, same pace. */
6120
+ [data-scope="image"][data-part="root"][data-state="loading"] {
6121
+ animation: bs-image-breathe 1600ms var(--bs-ease-in-out) infinite;
6122
+ }
6123
+
6124
+ [data-scope="image"][data-part="img"] {
6125
+ display: block;
6126
+ inline-size: 100%;
6127
+ block-size: 100%;
6128
+ object-fit: var(--_fit);
6129
+ /* The picture arrives unseen and dissolves in — ink settling, never
6130
+ a flash of half-drawn pixels. */
6131
+ opacity: 0;
6132
+ transition: opacity var(--bs-duration-slow) var(--bs-ease-out);
6133
+ }
6134
+
6135
+ [data-scope="image"][data-part="root"][data-state="loaded"] [data-scope="image"][data-part="img"] {
6136
+ opacity: 1;
6137
+ }
6138
+
6139
+ /* A broken source leaves nothing but the fallback: the browser's own
6140
+ broken-image mark would argue with the page. */
6141
+ [data-scope="image"][data-part="root"][data-state="error"] [data-scope="image"][data-part="img"] {
6142
+ display: none;
6143
+ }
6144
+
6145
+ [data-scope="image"][data-part="fallback"] {
6146
+ position: absolute;
6147
+ inset: 0;
6148
+ display: grid;
6149
+ place-items: center;
6150
+ background: var(--bs-color-surface-inset);
6151
+ color: var(--bs-color-text-tertiary);
6152
+ }
6153
+
6154
+ [data-scope="image"][data-part="fallback"] svg {
6155
+ inline-size: var(--bs-space-8);
6156
+ block-size: var(--bs-space-8);
6157
+ fill: none;
6158
+ stroke: currentColor;
6159
+ stroke-width: 1.5;
6160
+ stroke-linecap: round;
6161
+ stroke-linejoin: round;
6162
+ }
6163
+
6164
+ @media (prefers-reduced-motion: reduce) {
6165
+ [data-scope="image"][data-part="root"][data-state="loading"] {
6166
+ animation: none;
6167
+ opacity: 0.7;
6168
+ }
6169
+ }
6170
+
6171
+ @keyframes bs-image-breathe {
6172
+ 0%,
6173
+ 100% {
6174
+ opacity: 1;
6175
+ }
6176
+
6177
+ 50% {
6178
+ opacity: 0.55;
6179
+ }
6180
+ }
6181
+ `;
6182
+ //#endregion
4867
6183
  //#region src/styles/components/image-cropper.ts
4868
6184
  const imageCropperCss = labelCss("image-cropper") + `
4869
6185
  [data-scope="image-cropper"][data-part="root"] {
@@ -5027,37 +6343,229 @@ const imageCropperCss = labelCss("image-cropper") + `
5027
6343
  }
5028
6344
  `;
5029
6345
  //#endregion
5030
- //#region src/styles/components/tree-view.ts
5031
- const treeViewCss = labelCss("tree-view") + `
5032
- [data-scope="tree-view"][data-part="root"] {
5033
- /* The indent system: depth comes from the machine as --depth per node;
5034
- every offset below derives from these three knobs. */
5035
- --bs-tree-indent: 1rem;
5036
- --bs-tree-icon: 1rem;
5037
- --bs-tree-row-block: var(--bs-space-2);
5038
- /* Full width is the component's own property, not the stage's stretch. */
5039
- inline-size: 100%;
5040
- display: flex;
5041
- flex-direction: column;
5042
- gap: var(--bs-space-2);
6346
+ //#region src/styles/components/image-viewer.ts
6347
+ const imageViewerCss = `
6348
+ /* The lightbox rides the dialog machine for scrim, stacking, focus and
6349
+ Escape, but it owns the whole screen: the extra class out-specifies
6350
+ the sheet chrome no paper vessel, just the picture floating over
6351
+ the dimmed page. The layer math repeats the dialog's on purpose: the
6352
+ viewer must stand correct even where the dialog's own sheet is
6353
+ never styled. */
6354
+ [data-scope="dialog"][data-part="backdrop"].bs-image-viewer-backdrop {
6355
+ position: fixed;
6356
+ inset: 0;
6357
+ /* One below its positioner, from the same shared base. */
6358
+ z-index: calc(var(--bs-z-overlay) + var(--layer-index, 0) - 1);
6359
+ background: var(--bs-color-scrim);
6360
+ transition: opacity var(--bs-duration-slow) var(--bs-ease-out);
5043
6361
  }
5044
6362
 
5045
- [data-scope="tree-view"][data-part="tree"] {
5046
- display: flex;
5047
- flex-direction: column;
5048
- color: var(--bs-color-text-primary);
5049
- font-size: var(--bs-font-size-sm);
6363
+ [data-scope="dialog"][data-part="positioner"].bs-image-viewer-positioner {
6364
+ position: fixed;
6365
+ inset: 0;
6366
+ z-index: calc(var(--bs-z-overlay) + var(--layer-index, 0));
6367
+ display: grid;
6368
+ /* One row capped at the viewport: an auto row would let the picture's
6369
+ intrinsic height stretch the content past the screen, carrying the
6370
+ toolbar out of reach below the fold. */
6371
+ grid-template-rows: minmax(0, 1fr);
6372
+ padding: 0;
5050
6373
  }
5051
6374
 
5052
- /* Rows are quiet: no hairlines, no shadows — selection and hover are pure
5053
- light on the paper. */
5054
- [data-scope="tree-view"][data-part="branch-control"],
5055
- [data-scope="tree-view"][data-part="item"] {
6375
+ [data-scope="dialog"][data-part="content"].bs-image-viewer-content {
5056
6376
  position: relative;
5057
6377
  display: flex;
5058
- align-items: center;
5059
- gap: var(--bs-space-2);
5060
- /* No page reset may be assumed: 100% must count the row's own padding. */
6378
+ flex-direction: column;
6379
+ gap: var(--bs-space-4);
6380
+ box-sizing: border-box;
6381
+ inline-size: 100%;
6382
+ block-size: 100%;
6383
+ max-block-size: none;
6384
+ overflow: visible;
6385
+ padding: 0;
6386
+ border: none;
6387
+ border-radius: 0;
6388
+ background: transparent;
6389
+ box-shadow: none;
6390
+ }
6391
+
6392
+ [data-scope="dialog"][data-part="content"].bs-image-viewer-content:focus,
6393
+ [data-scope="dialog"][data-part="content"].bs-image-viewer-content:focus-visible {
6394
+ outline: none;
6395
+ }
6396
+
6397
+ /* The panel grammar still holds: the lightbox dissolves in — the room
6398
+ darkening, never a flash. */
6399
+ [data-scope="dialog"][data-part="content"].bs-image-viewer-content[data-state="open"] {
6400
+ animation: bs-ink-in var(--bs-duration-slow) var(--bs-ease-out);
6401
+ }
6402
+
6403
+ /* The picture takes the row the toolbar leaves and keeps its shape
6404
+ inside it — contain centers whatever the frame cannot hold. The
6405
+ minimum height must be let go of explicitly, or the picture's own
6406
+ height would pin the row and push the tools off the screen. The
6407
+ hand's zoom and quarter-turns compose on one transform, which never
6408
+ animates into place on open — zoom is the reader's hand, not the
6409
+ ink's. */
6410
+ [data-scope="image-viewer"][data-part="viewport"] {
6411
+ display: block;
6412
+ flex: 1 1 auto;
6413
+ min-block-size: 0;
6414
+ min-inline-size: 0;
6415
+ inline-size: 100%;
6416
+ object-fit: contain;
6417
+ user-select: none;
6418
+ transition: transform var(--bs-duration-base) var(--bs-ease-out);
6419
+ }
6420
+
6421
+ [data-scope="image-viewer"][data-part="toolbar"] {
6422
+ display: flex;
6423
+ flex: none;
6424
+ justify-content: center;
6425
+ gap: var(--bs-space-2);
6426
+ /* The tools ride a small lacquer tray: a translucent ink that keeps
6427
+ the room dark in either register, held clear of the picture above
6428
+ and of the page's edge below. The group inside carries the joinery;
6429
+ its corners stay at the control register. */
6430
+ inline-size: max-content;
6431
+ margin-inline: auto;
6432
+ margin-block-end: var(--bs-space-6);
6433
+ padding: var(--bs-space-1);
6434
+ border-radius: var(--bs-radius-control, var(--bs-radius-sm));
6435
+ background: color-mix(in oklab, var(--bs-color-gray-900) 72%, transparent);
6436
+ }
6437
+
6438
+ /* Ghost ink over the dark scrim must be the paper-bright ink that never
6439
+ flips with the theme, and the hover wash follows as a pale breath. */
6440
+ [data-scope="image-viewer"][data-part="toolbar"] [data-scope="button"][data-part="root"] {
6441
+ --_ink: var(--bs-color-text-on-scrim);
6442
+ --_fill-hover: color-mix(in oklab, var(--bs-color-surface-2) 16%, transparent);
6443
+ }
6444
+ `;
6445
+ //#endregion
6446
+ //#region src/styles/components/input-group.ts
6447
+ const inputGroupCss = `
6448
+ /* The merged control: one hairline and one halo for the whole family.
6449
+ The input inside surrenders its own frame so attachments read as parts
6450
+ of the same seal, not buttons bolted onto a field. */
6451
+ [data-scope="input-group"][data-part="root"] {
6452
+ display: inline-flex;
6453
+ align-items: stretch;
6454
+ border: 1px solid var(--bs-color-border);
6455
+ border-radius: var(--bs-radius-sm);
6456
+ background: var(--bs-color-surface-2);
6457
+ /* The attachments are square-cut cells; the seal's rounding clips
6458
+ them, so the corners read as one shape instead of a field with
6459
+ blocks bolted on. */
6460
+ overflow: hidden;
6461
+ transition:
6462
+ border-color var(--bs-duration-fast) var(--bs-ease-out),
6463
+ box-shadow var(--bs-duration-fast) var(--bs-ease-out);
6464
+ }
6465
+
6466
+ [data-scope="input-group"][data-part="root"]:hover {
6467
+ border-color: var(--bs-color-border-strong);
6468
+ }
6469
+
6470
+ /* Focus is light arriving at the group: one halo, however many controls
6471
+ sit inside. */
6472
+ [data-scope="input-group"][data-part="root"]:focus-within {
6473
+ border-color: var(--bs-color-primary);
6474
+ box-shadow: var(--bs-focus-ring);
6475
+ }
6476
+
6477
+ /* The entry wears no frame of its own — it stretches to fill what is
6478
+ left of the seal, its own halo silenced in favor of the group's. The
6479
+ compound selectors outrank the field recipe's focus and disabled
6480
+ branches without touching the shared stylesheet. */
6481
+ [data-scope="input-group"][data-part="root"] [data-scope="input"][data-part="root"],
6482
+ [data-scope="input-group"][data-part="root"] [data-scope="textarea"][data-part="root"] {
6483
+ flex: 1;
6484
+ inline-size: auto;
6485
+ min-inline-size: 0;
6486
+ border: none;
6487
+ border-radius: 0;
6488
+ background: transparent;
6489
+ box-shadow: none;
6490
+ }
6491
+
6492
+ [data-scope="input-group"][data-part="root"] [data-scope="input"][data-part="root"]:focus,
6493
+ [data-scope="input-group"][data-part="root"] [data-scope="input"][data-part="root"]:focus-visible,
6494
+ [data-scope="input-group"][data-part="root"] [data-scope="textarea"][data-part="root"]:focus,
6495
+ [data-scope="input-group"][data-part="root"] [data-scope="textarea"][data-part="root"]:focus-visible {
6496
+ border: none;
6497
+ box-shadow: none;
6498
+ }
6499
+
6500
+ /* Disabled keeps the group's paper too — only the ink steps back. */
6501
+ [data-scope="input-group"][data-part="root"] [data-scope="input"][data-part="root"]:disabled,
6502
+ [data-scope="input-group"][data-part="root"] [data-scope="textarea"][data-part="root"]:disabled {
6503
+ background: transparent;
6504
+ }
6505
+
6506
+ /* A button living in an attachment focuses inside the clipped seal, so
6507
+ its halo wears the inset variant — the outward glow would be cut to
6508
+ ugly half-corners by the group's rounding. */
6509
+ [data-scope="input-group"][data-part="root"] [data-scope="button"][data-part="root"]:focus-visible {
6510
+ box-shadow: var(--bs-focus-ring-inset);
6511
+ }
6512
+
6513
+ /* An attachment cell: a recessed face between the reader's fixed words
6514
+ and the ink they type. Hairlines do the separating — leading cells
6515
+ part from the entry on the inline end, trailing on the inline start,
6516
+ so any root/addon order divides correctly. */
6517
+ [data-scope="input-group"][data-part="addon"] {
6518
+ display: inline-flex;
6519
+ flex: none;
6520
+ align-items: center;
6521
+ gap: var(--bs-gap-xs);
6522
+ padding: 0 var(--bs-padding-md);
6523
+ background: var(--bs-color-surface-inset);
6524
+ color: var(--bs-color-text-secondary);
6525
+ font-size: var(--bs-font-size-sm);
6526
+ white-space: nowrap;
6527
+ }
6528
+
6529
+ [data-scope="input-group"][data-part="addon"]:not(:first-child) {
6530
+ border-inline-start: 1px solid var(--bs-color-border);
6531
+ }
6532
+
6533
+ [data-scope="input-group"][data-part="addon"]:not(:last-child) {
6534
+ border-inline-end: 1px solid var(--bs-color-border);
6535
+ }
6536
+ `;
6537
+ //#endregion
6538
+ //#region src/styles/components/tree-view.ts
6539
+ const treeViewCss = labelCss("tree-view") + `
6540
+ [data-scope="tree-view"][data-part="root"] {
6541
+ /* The indent system: depth comes from the machine as --depth per node;
6542
+ every offset below derives from these three knobs. */
6543
+ --bs-tree-indent: 1rem;
6544
+ --bs-tree-icon: 1rem;
6545
+ --bs-tree-row-block: var(--bs-space-2);
6546
+ /* Full width is the component's own property, not the stage's stretch. */
6547
+ inline-size: 100%;
6548
+ display: flex;
6549
+ flex-direction: column;
6550
+ gap: var(--bs-space-2);
6551
+ }
6552
+
6553
+ [data-scope="tree-view"][data-part="tree"] {
6554
+ display: flex;
6555
+ flex-direction: column;
6556
+ color: var(--bs-color-text-primary);
6557
+ font-size: var(--bs-font-size-sm);
6558
+ }
6559
+
6560
+ /* Rows are quiet: no hairlines, no shadows — selection and hover are pure
6561
+ light on the paper. */
6562
+ [data-scope="tree-view"][data-part="branch-control"],
6563
+ [data-scope="tree-view"][data-part="item"] {
6564
+ position: relative;
6565
+ display: flex;
6566
+ align-items: center;
6567
+ gap: var(--bs-space-2);
6568
+ /* No page reset may be assumed: 100% must count the row's own padding. */
5061
6569
  box-sizing: border-box;
5062
6570
  inline-size: 100%;
5063
6571
  padding-block: var(--bs-tree-row-block);
@@ -5290,6 +6798,154 @@ const kbdCss = `
5290
6798
  }
5291
6799
  `;
5292
6800
  //#endregion
6801
+ //#region src/styles/components/layout.ts
6802
+ const layoutCss = `
6803
+ /* The application skeleton: a full-height grid that holds the header on
6804
+ top, the footer at the foot, and the flow between them. When a sider
6805
+ is declared a fixed rail joins the columns; the minmax(0, 1fr) — not
6806
+ a bare 1fr — keeps a wide child from stretching its track and
6807
+ shoving every sibling aside. */
6808
+ [data-scope="layout"][data-part="root"] {
6809
+ display: grid;
6810
+ min-block-size: 100dvh;
6811
+ grid-template-columns: minmax(0, 1fr);
6812
+ grid-template-rows: auto minmax(0, 1fr) auto;
6813
+ grid-template-areas:
6814
+ "header"
6815
+ "content"
6816
+ "footer";
6817
+ }
6818
+
6819
+ /* With a sider the rail owns one whole row span, so the header and the
6820
+ footer reach it only on the flow side. */
6821
+ [data-scope="layout"][data-part="root"][data-sider="start"] {
6822
+ grid-template-columns: auto minmax(0, 1fr);
6823
+ grid-template-areas:
6824
+ "sider header"
6825
+ "sider content"
6826
+ "sider footer";
6827
+ }
6828
+
6829
+ [data-scope="layout"][data-part="root"][data-sider="end"] {
6830
+ grid-template-columns: minmax(0, 1fr) auto;
6831
+ grid-template-areas:
6832
+ "header sider"
6833
+ "content sider"
6834
+ "footer sider";
6835
+ }
6836
+
6837
+ [data-scope="layout"][data-part="header"] {
6838
+ grid-area: header;
6839
+ padding: var(--bs-padding-md) var(--bs-padding-xl);
6840
+ border-block-end: 1px solid var(--bs-color-border);
6841
+ }
6842
+
6843
+ [data-scope="layout"][data-part="footer"] {
6844
+ grid-area: footer;
6845
+ padding: var(--bs-padding-md) var(--bs-padding-xl);
6846
+ border-block-start: 1px solid var(--bs-color-border);
6847
+ }
6848
+
6849
+ /* The rail: a panel on the first surface, separated from the flow by
6850
+ one hairline. The inline size rides the variable the wrapper writes —
6851
+ a layout parameter, not a visual token — so the collapse animates by
6852
+ re-pointing it, and the resizable rail's floor and ceiling clamp it
6853
+ between the caller's bounds. */
6854
+ [data-scope="layout"][data-part="sider"] {
6855
+ position: relative;
6856
+ grid-area: sider;
6857
+ inline-size: clamp(
6858
+ var(--bs-layout-sider-min, 12rem),
6859
+ var(--bs-layout-sider-width, 16rem),
6860
+ var(--bs-layout-sider-max, 24rem)
6861
+ );
6862
+ overflow: hidden;
6863
+ background: var(--bs-color-surface-1);
6864
+ border-inline-end: 1px solid var(--bs-color-border);
6865
+ transition: inline-size var(--bs-duration-slow) var(--bs-ease-out);
6866
+ }
6867
+
6868
+ /* The hand leads: while dragging, the rail follows the pointer without
6869
+ the easing catching up behind it. */
6870
+ [data-scope="layout"][data-part="sider"][data-dragging] {
6871
+ transition: none;
6872
+ }
6873
+
6874
+ /* The resize hairline: a generous hit strip at the flow edge whose ink
6875
+ answers to the hand — quiet until hovered, primary while gripped or
6876
+ keyed. It stays inside the rail, whose overflow clips anything that
6877
+ pokes out — the strip and its ink both live inboard of the border. */
6878
+ [data-scope="layout"][data-part="sider-resize"] {
6879
+ position: absolute;
6880
+ inset-block: 0;
6881
+ inset-inline-end: 0;
6882
+ inline-size: var(--bs-space-2);
6883
+ cursor: ew-resize;
6884
+ touch-action: none;
6885
+ }
6886
+
6887
+ [data-scope="layout"][data-part="sider-resize"]::before {
6888
+ content: "";
6889
+ position: absolute;
6890
+ inset-block: 0;
6891
+ inset-inline-end: calc(var(--bs-space-2) / 2);
6892
+ inline-size: 1px;
6893
+ background: var(--bs-color-border);
6894
+ transition: background-color var(--bs-duration-fast) var(--bs-ease-out);
6895
+ }
6896
+
6897
+ [data-scope="layout"][data-part="sider-resize"]:hover::before,
6898
+ [data-scope="layout"][data-part="sider-resize"]:focus-visible::before,
6899
+ [data-scope="layout"][data-part="sider-resize"][data-dragging]::before {
6900
+ background: var(--bs-color-primary);
6901
+ }
6902
+
6903
+ [data-scope="layout"][data-part="sider-resize"]:focus-visible {
6904
+ outline: none;
6905
+ }
6906
+
6907
+ /* An end-side rail wears its handle on the other edge, and the pull
6908
+ toward the flow points the other way — the wrapper already mirrors
6909
+ the arithmetic. */
6910
+ [data-scope="layout"][data-part="root"][data-sider="end"]
6911
+ [data-scope="layout"][data-part="sider-resize"] {
6912
+ inset-inline-end: auto;
6913
+ inset-inline-start: 0;
6914
+ }
6915
+
6916
+ [data-scope="layout"][data-part="root"][data-sider="end"]
6917
+ [data-scope="layout"][data-part="sider-resize"]::before {
6918
+ inset-inline-end: auto;
6919
+ inset-inline-start: calc(var(--bs-space-2) / 2);
6920
+ }
6921
+
6922
+ /* The hairline always faces the flow, so an end sider wears it on its
6923
+ other edge. */
6924
+ [data-scope="layout"][data-part="root"][data-sider="end"]
6925
+ [data-scope="layout"][data-part="sider"] {
6926
+ border-inline-end: none;
6927
+ border-inline-start: 1px solid var(--bs-color-border);
6928
+ }
6929
+
6930
+ /* A resizable rail retires its own border: the resize hairline that
6931
+ rides the flow edge is the one line — the border would draw a second
6932
+ one beside it. */
6933
+ [data-scope="layout"][data-part="root"][data-sider="start"]
6934
+ [data-scope="layout"][data-part="sider"][data-resizable] {
6935
+ border-inline-end: none;
6936
+ }
6937
+
6938
+ [data-scope="layout"][data-part="root"][data-sider="end"]
6939
+ [data-scope="layout"][data-part="sider"][data-resizable] {
6940
+ border-inline-start: none;
6941
+ }
6942
+
6943
+ [data-scope="layout"][data-part="content"] {
6944
+ grid-area: content;
6945
+ padding: var(--bs-padding-xl);
6946
+ }
6947
+ `;
6948
+ //#endregion
5293
6949
  //#region src/styles/components/link.ts
5294
6950
  const linkCss = `
5295
6951
  /* A link is ink in the accent's voice: quiet at rest, deepening under
@@ -5326,6 +6982,87 @@ const linkCss = `
5326
6982
  }
5327
6983
  `;
5328
6984
  //#endregion
6985
+ //#region src/styles/components/list.ts
6986
+ const listCss = `
6987
+ /* A ledger of rows: Leading carries the mark, Content the title and
6988
+ its quiet echo, Actions the way out. The hairline between rows is
6989
+ the bordered variant's; the hover wash belongs to the interactive
6990
+ registers — a row that reads as clickable must answer the pointer. */
6991
+ [data-scope="list"][data-part="root"] {
6992
+ display: flex;
6993
+ flex-direction: column;
6994
+ margin: 0;
6995
+ padding: 0;
6996
+ list-style: none;
6997
+ }
6998
+
6999
+ [data-scope="list"][data-part="item"] {
7000
+ display: flex;
7001
+ align-items: center;
7002
+ gap: var(--bs-gap-sm);
7003
+ padding: var(--bs-padding-sm) var(--bs-padding-md);
7004
+ transition: background-color var(--bs-duration-fast) var(--bs-ease-out);
7005
+ }
7006
+
7007
+ /* The bordered variant draws one hairline between rows — never before
7008
+ the first, so the ledger starts clean. */
7009
+ [data-scope="list"][data-part="root"][data-bordered]
7010
+ [data-scope="list"][data-part="item"]
7011
+ + [data-scope="list"][data-part="item"] {
7012
+ border-block-start: 1px solid var(--bs-color-border);
7013
+ }
7014
+
7015
+ /* A row the caller made clickable (role="button") owns the pointer and
7016
+ the hover wash; focus rides the inset halo, since the row is the
7017
+ full bleed of its container. */
7018
+ [data-scope="list"][data-part="item"][role="button"] {
7019
+ cursor: pointer;
7020
+ }
7021
+
7022
+ [data-scope="list"][data-part="root"][data-hoverable] [data-scope="list"][data-part="item"]:hover,
7023
+ [data-scope="list"][data-part="item"][role="button"]:hover {
7024
+ background: var(--bs-color-surface-0);
7025
+ }
7026
+
7027
+ [data-scope="list"][data-part="item"]:focus-visible {
7028
+ outline: none;
7029
+ box-shadow: var(--bs-focus-ring-inset);
7030
+ }
7031
+
7032
+ [data-scope="list"][data-part="leading"] {
7033
+ display: flex;
7034
+ flex: none;
7035
+ align-items: center;
7036
+ }
7037
+
7038
+ [data-scope="list"][data-part="content"] {
7039
+ display: flex;
7040
+ flex: 1;
7041
+ flex-direction: column;
7042
+ gap: var(--bs-space-1);
7043
+ min-inline-size: 0;
7044
+ }
7045
+
7046
+ [data-scope="list"][data-part="title"] {
7047
+ color: var(--bs-color-text-primary);
7048
+ font-weight: var(--bs-font-weight-medium);
7049
+ }
7050
+
7051
+ [data-scope="list"][data-part="description"] {
7052
+ color: var(--bs-color-text-secondary);
7053
+ font-size: var(--bs-font-size-sm);
7054
+ line-height: var(--bs-line-height-relaxed);
7055
+ }
7056
+
7057
+ [data-scope="list"][data-part="actions"] {
7058
+ display: flex;
7059
+ flex: none;
7060
+ align-items: center;
7061
+ gap: var(--bs-space-2);
7062
+ margin-inline-start: auto;
7063
+ }
7064
+ `;
7065
+ //#endregion
5329
7066
  //#region src/styles/components/listbox.ts
5330
7067
  const listboxCss = labelCss("listbox") + popupContentCss("listbox", "16rem") + `
5331
7068
  [data-scope="listbox"][data-part="root"] {
@@ -5594,6 +7331,116 @@ const marqueeCss = `
5594
7331
  }
5595
7332
  `;
5596
7333
  //#endregion
7334
+ //#region src/styles/components/masonry.ts
7335
+ const masonryCss = `
7336
+ /* The wall of uneven heights, cut by the browser's multi-column
7337
+ layout. Filling runs down each column before crossing to the next —
7338
+ the order is column-first; a row-flow wall needs grid masonry, which
7339
+ browsers do not ship yet. */
7340
+ [data-scope="masonry"][data-part="root"] {
7341
+ column-count: var(--bs-masonry-columns, 3);
7342
+ column-gap: var(--bs-masonry-gap, var(--bs-space-3));
7343
+ }
7344
+
7345
+ /* Each stone stays whole — never split across two columns — and the
7346
+ gap below it holds the next stone of the column at the named
7347
+ distance. */
7348
+ [data-scope="masonry"][data-part="root"] > * {
7349
+ break-inside: avoid;
7350
+ margin-block-end: var(--bs-masonry-gap, var(--bs-space-3));
7351
+ }
7352
+ `;
7353
+ //#endregion
7354
+ //#region src/styles/components/mentions.ts
7355
+ const mentionsCss = `
7356
+ /* The field: a textarea in the standard field recipe — border + surface
7357
+ + focus halo, never a shadow lift. */
7358
+ [data-scope="mentions"][data-part="root"] {
7359
+ display: flex;
7360
+ }
7361
+
7362
+ [data-scope="mentions"][data-part="textarea"] {
7363
+ box-sizing: border-box;
7364
+ flex: 1;
7365
+ min-inline-size: 0;
7366
+ min-block-size: calc(var(--bs-control-height-md) + var(--bs-space-4));
7367
+ padding: var(--bs-space-2) var(--bs-padding-md);
7368
+ border: 1px solid var(--bs-color-border);
7369
+ border-radius: var(--bs-radius-sm);
7370
+ background: var(--bs-color-surface-2);
7371
+ color: var(--bs-color-text-primary);
7372
+ font: inherit;
7373
+ font-size: var(--bs-font-size-md);
7374
+ line-height: var(--bs-line-height-relaxed);
7375
+ resize: vertical;
7376
+ transition: border-color var(--bs-duration-fast) var(--bs-ease-out);
7377
+ }
7378
+
7379
+ [data-scope="mentions"][data-part="textarea"]::placeholder {
7380
+ color: var(--bs-color-text-tertiary);
7381
+ }
7382
+
7383
+ [data-scope="mentions"][data-part="textarea"]:hover:not([disabled], [readonly]) {
7384
+ border-color: var(--bs-color-border-strong);
7385
+ }
7386
+
7387
+ [data-scope="mentions"][data-part="textarea"]:focus,
7388
+ [data-scope="mentions"][data-part="textarea"]:focus-visible {
7389
+ outline: none;
7390
+ border-color: var(--bs-color-primary);
7391
+ box-shadow: var(--bs-focus-ring);
7392
+ }
7393
+
7394
+ [data-scope="mentions"][data-part="textarea"][disabled] {
7395
+ border-color: var(--bs-color-border);
7396
+ background: var(--bs-color-surface-inset);
7397
+ color: var(--bs-color-text-disabled);
7398
+ cursor: not-allowed;
7399
+ }
7400
+
7401
+ /* The candidate vessel: the popup recipe — paper, one hairline,
7402
+ elevation 3 — riding the popover positioner's shared ladder. */
7403
+ [data-scope="mentions"][data-part="popup"] {
7404
+ box-sizing: border-box;
7405
+ min-inline-size: 10rem;
7406
+ max-block-size: 14rem;
7407
+ display: flex;
7408
+ flex-direction: column;
7409
+ padding: var(--bs-space-1);
7410
+ border: 1px solid var(--bs-color-border);
7411
+ border-radius: var(--bs-radius-lg);
7412
+ background: var(--bs-color-surface-2);
7413
+ color: var(--bs-color-text-primary);
7414
+ box-shadow: var(--bs-elevation-3);
7415
+ overflow-y: auto;
7416
+ outline: none;
7417
+ }
7418
+
7419
+ [data-scope="mentions"][data-state="open"][data-part="popup"] {
7420
+ animation: bs-ink-in var(--bs-duration-base) var(--bs-ease-out);
7421
+ }
7422
+
7423
+ /* Options are rows of light like every list: hover is the machine's
7424
+ highlight, the keyboard's row is the active one — and the two are the
7425
+ same stroke, so pointer and arrows never disagree. */
7426
+ [data-scope="mentions"][data-part="option"] {
7427
+ display: flex;
7428
+ align-items: center;
7429
+ gap: var(--bs-space-2);
7430
+ min-block-size: var(--bs-control-height-sm);
7431
+ padding: 0 var(--bs-padding-sm);
7432
+ border-radius: var(--bs-radius-sm);
7433
+ font-size: var(--bs-font-size-sm);
7434
+ cursor: pointer;
7435
+ user-select: none;
7436
+ transition: background-color var(--bs-duration-fast) var(--bs-ease-out);
7437
+ }
7438
+
7439
+ [data-scope="mentions"][data-part="option"][data-active] {
7440
+ background: var(--bs-color-surface-0);
7441
+ }
7442
+ `;
7443
+ //#endregion
5597
7444
  //#region src/styles/components/menu.ts
5598
7445
  const menuCss = positionerCss("menu") + popupContentCss("menu", "12rem") + `
5599
7446
  [data-scope="menu"][data-part="content"] {
@@ -5795,6 +7642,66 @@ const menuCss = positionerCss("menu") + popupContentCss("menu", "12rem") + `
5795
7642
  }
5796
7643
  `;
5797
7644
  //#endregion
7645
+ //#region src/styles/components/menubar.ts
7646
+ const menubarCss = `
7647
+ /* The bar: one hairline of rest under a row of quiet triggers. */
7648
+ [data-scope="menubar"][data-part="root"] {
7649
+ display: flex;
7650
+ align-items: center;
7651
+ gap: var(--bs-space-1);
7652
+ padding: var(--bs-space-1) 0;
7653
+ border-block-end: 1px solid var(--bs-color-border);
7654
+ }
7655
+
7656
+ /* The triggers are ghost buttons — terrain, not controls: no fill, no
7657
+ hairline of their own, the bar's hairline doing the framing. */
7658
+ [data-scope="menubar"][data-part="trigger"] {
7659
+ display: inline-flex;
7660
+ align-items: center;
7661
+ block-size: var(--bs-control-height-sm);
7662
+ padding: 0 var(--bs-padding-sm);
7663
+ border: none;
7664
+ border-radius: var(--bs-radius-sm);
7665
+ background: transparent;
7666
+ color: var(--bs-color-text-secondary);
7667
+ font: inherit;
7668
+ font-size: var(--bs-font-size-sm);
7669
+ letter-spacing: var(--bs-tracking-label);
7670
+ white-space: nowrap;
7671
+ cursor: pointer;
7672
+ user-select: none;
7673
+ transition:
7674
+ background-color var(--bs-duration-fast) var(--bs-ease-out),
7675
+ color var(--bs-duration-fast) var(--bs-ease-out);
7676
+ }
7677
+
7678
+ [data-scope="menubar"][data-part="trigger"]:hover {
7679
+ background: var(--bs-color-surface-0);
7680
+ color: var(--bs-color-text-primary);
7681
+ }
7682
+
7683
+ /* Open keeps the focus look: the machine hands focus into the menu, so
7684
+ :focus-visible alone would drop the halo the moment it opens. */
7685
+ [data-scope="menubar"][data-part="trigger"]:focus-visible,
7686
+ [data-scope="menubar"][data-part="trigger"][data-state="open"] {
7687
+ outline: none;
7688
+ background: var(--bs-color-surface-0);
7689
+ color: var(--bs-color-text-primary);
7690
+ box-shadow: var(--bs-focus-ring);
7691
+ }
7692
+
7693
+ /* The popup keeps the menu parts and the menu stylesheet; only the
7694
+ danger rows ride this scope's knowledge of the bar's callers. */
7695
+ [data-scope="menu"][data-part="item"][data-danger] {
7696
+ color: var(--bs-color-danger);
7697
+ }
7698
+
7699
+ [data-scope="menu"][data-part="item"][data-danger][data-state="checked"] {
7700
+ background: var(--bs-color-danger);
7701
+ color: var(--bs-color-ink-on-fill);
7702
+ }
7703
+ `;
7704
+ //#endregion
5798
7705
  //#region src/styles/components/meter.ts
5799
7706
  const meterCss = `
5800
7707
  /* A measure in the world, not a task in flight: how much of the toner
@@ -5838,70 +7745,17 @@ const meterCss = `
5838
7745
  overflow: hidden;
5839
7746
  block-size: var(--bs-space-1);
5840
7747
  border-radius: var(--bs-radius-sm);
5841
- background: var(--bs-color-surface-0);
5842
- }
5843
-
5844
- [data-scope="meter"][data-part="range"] {
5845
- /* The fill reads the root's declared share of the scale. */
5846
- display: block;
5847
- inline-size: var(--_percent, 0%);
5848
- block-size: 100%;
5849
- border-radius: inherit;
5850
- background: var(--_pigment);
5851
- transition: inline-size var(--bs-duration-slow) var(--bs-ease-out);
5852
- }
5853
- `;
5854
- //#endregion
5855
- //#region src/styles/components/typography.ts
5856
- const typographyCss = `
5857
- /* The typographic voices, named so prose can ask for one: display and
5858
- heading ride the song-serif, the rest ride the hei. Nothing here is
5859
- decorative — hierarchy is size, weight, and space. */
5860
- [data-scope="typography"][data-part="display"] {
5861
- margin: 0;
5862
- color: var(--bs-color-text-primary);
5863
- font-family: var(--bs-font-serif);
5864
- font-size: var(--bs-font-size-4xl);
5865
- font-weight: var(--bs-font-weight-semibold);
5866
- line-height: var(--bs-line-height-tight);
5867
- }
5868
-
5869
- [data-scope="typography"][data-part="heading"] {
5870
- margin: 0;
5871
- color: var(--bs-color-text-primary);
5872
- font-family: var(--bs-font-serif);
5873
- font-size: var(--bs-font-size-2xl);
5874
- font-weight: var(--bs-font-weight-semibold);
5875
- line-height: var(--bs-line-height-snug);
5876
- }
5877
-
5878
- [data-scope="typography"][data-part="lead"] {
5879
- margin: 0;
5880
- color: var(--bs-color-text-secondary);
5881
- font-size: var(--bs-font-size-lg);
5882
- line-height: var(--bs-line-height-relaxed);
5883
- }
5884
-
5885
- [data-scope="typography"][data-part="body"] {
5886
- margin: 0;
5887
- color: var(--bs-color-text-secondary);
5888
- font-size: var(--bs-font-size-base);
5889
- line-height: var(--bs-line-height-relaxed);
5890
- }
5891
-
5892
- [data-scope="typography"][data-part="muted"] {
5893
- margin: 0;
5894
- color: var(--bs-color-text-tertiary);
5895
- font-size: var(--bs-font-size-sm);
5896
- line-height: var(--bs-line-height-relaxed);
7748
+ background: var(--bs-color-surface-0);
5897
7749
  }
5898
7750
 
5899
- [data-scope="typography"][data-part="label"] {
5900
- margin: 0;
5901
- color: var(--bs-color-text-secondary);
5902
- font-size: var(--bs-font-size-sm);
5903
- font-weight: var(--bs-font-weight-medium);
5904
- letter-spacing: var(--bs-tracking-label);
7751
+ [data-scope="meter"][data-part="range"] {
7752
+ /* The fill reads the root's declared share of the scale. */
7753
+ display: block;
7754
+ inline-size: var(--_percent, 0%);
7755
+ block-size: 100%;
7756
+ border-radius: inherit;
7757
+ background: var(--_pigment);
7758
+ transition: inline-size var(--bs-duration-slow) var(--bs-ease-out);
5905
7759
  }
5906
7760
  `;
5907
7761
  //#endregion
@@ -6580,6 +8434,31 @@ const pinInputCss = labelCss("pin-input") + `
6580
8434
  }
6581
8435
  `;
6582
8436
  //#endregion
8437
+ //#region src/styles/components/popconfirm.ts
8438
+ const popconfirmCss = `
8439
+ /* The popconfirm rides the popover vessel for its paper, hairline and
8440
+ elevation; these rules only lay out the question and its answers.
8441
+ The extra class out-specifies any part-level popover padding so the
8442
+ panel reads as one tight question, not a floating card. */
8443
+ .bs-popconfirm {
8444
+ max-inline-size: 18rem;
8445
+ padding: var(--bs-padding-md);
8446
+ }
8447
+
8448
+ .bs-popconfirm [data-part="message"] {
8449
+ margin: 0 0 var(--bs-space-3);
8450
+ color: var(--bs-color-text-primary);
8451
+ font-size: var(--bs-font-size-sm);
8452
+ line-height: var(--bs-line-height-relaxed);
8453
+ }
8454
+
8455
+ .bs-popconfirm [data-part="actions"] {
8456
+ display: flex;
8457
+ justify-content: flex-end;
8458
+ gap: var(--bs-space-2);
8459
+ }
8460
+ `;
8461
+ //#endregion
6583
8462
  //#region src/styles/components/popover.ts
6584
8463
  const popoverCss = positionerCss("popover") + popupContentCss("popover", "20rem") + `
6585
8464
  [data-scope="popover"][data-part="content"] {
@@ -6735,14 +8614,16 @@ const progressCss = labelCss("progress") + `
6735
8614
  text-align: end;
6736
8615
  }
6737
8616
 
6738
- /* The track is a hairline groove pressed into the paper — ink will run
6739
- through it, so it stays quiet until the fill arrives. */
8617
+ /* The track is a groove pressed into the paper — ink will run through it,
8618
+ so it stays quiet until the fill arrives. It sits one mix-step below the
8619
+ ground it is drawn on (the ground itself), or the groove reads as
8620
+ nothing; the pressed depth keeps it visible like M3's tone-based track. */
6740
8621
  [data-scope="progress"][data-part="track"] {
6741
8622
  grid-column: 1 / -1;
6742
8623
  overflow: hidden;
6743
8624
  block-size: var(--bs-space-1);
6744
8625
  border-radius: var(--bs-radius-sm);
6745
- background: var(--bs-color-surface-0);
8626
+ background: color-mix(in oklab, var(--bs-color-surface-0) 96%, black);
6746
8627
  }
6747
8628
 
6748
8629
  [data-scope="progress"][data-part="track"][data-orientation="vertical"] {
@@ -6809,7 +8690,7 @@ const progressCss = labelCss("progress") + `
6809
8690
  }
6810
8691
 
6811
8692
  [data-scope="progress"][data-part="circle-track"] {
6812
- stroke: var(--bs-color-surface-0);
8693
+ stroke: color-mix(in oklab, var(--bs-color-surface-0) 92%, black);
6813
8694
  }
6814
8695
 
6815
8696
  [data-scope="progress"][data-part="circle-range"] {
@@ -6838,6 +8719,83 @@ const progressCss = labelCss("progress") + `
6838
8719
  }
6839
8720
  `;
6840
8721
  //#endregion
8722
+ //#region src/styles/components/progress-group.ts
8723
+ const progressGroupCss = `
8724
+ /* One bar, several verdicts: the track lays the segments shoulder to
8725
+ shoulder, each speaking its own pigment (the badge's --_pigment
8726
+ mode), and the legend reads them back beneath. */
8727
+ [data-scope="progress-group"][data-part="root"] {
8728
+ display: flex;
8729
+ flex-direction: column;
8730
+ gap: var(--bs-space-2);
8731
+ inline-size: 100%;
8732
+ font-size: var(--bs-font-size-sm);
8733
+ }
8734
+
8735
+ [data-scope="progress-group"][data-part="track"] {
8736
+ display: flex;
8737
+ overflow: hidden;
8738
+ block-size: var(--bs-space-2);
8739
+ border-radius: var(--bs-radius-xs);
8740
+ background: var(--bs-color-surface-inset);
8741
+ }
8742
+
8743
+ :is([data-scope="progress-group"][data-part="segment"], [data-scope="progress-group"][data-part="legend-item"]) {
8744
+ --_pigment: var(--bs-color-primary);
8745
+ }
8746
+
8747
+ :is([data-scope="progress-group"][data-part="segment"], [data-scope="progress-group"][data-part="legend-item"])[data-pigment="success"] {
8748
+ --_pigment: var(--bs-color-success);
8749
+ }
8750
+
8751
+ :is([data-scope="progress-group"][data-part="segment"], [data-scope="progress-group"][data-part="legend-item"])[data-pigment="warning"] {
8752
+ --_pigment: var(--bs-color-warning);
8753
+ }
8754
+
8755
+ :is([data-scope="progress-group"][data-part="segment"], [data-scope="progress-group"][data-part="legend-item"])[data-pigment="danger"] {
8756
+ --_pigment: var(--bs-color-danger);
8757
+ }
8758
+
8759
+ :is([data-scope="progress-group"][data-part="segment"], [data-scope="progress-group"][data-part="legend-item"])[data-pigment="info"] {
8760
+ --_pigment: var(--bs-color-info);
8761
+ }
8762
+
8763
+ [data-scope="progress-group"][data-part="segment"] {
8764
+ overflow: hidden;
8765
+ background: var(--_pigment);
8766
+ /* The fill flows at the measure's pace — width trails the data like
8767
+ the single bar's does. The track's rounded mask rounds the ends;
8768
+ the segments themselves stay square so the bar reads as one. */
8769
+ transition: inline-size var(--bs-duration-slow) var(--bs-ease-out);
8770
+ }
8771
+
8772
+ [data-scope="progress-group"][data-part="legend"] {
8773
+ display: flex;
8774
+ flex-wrap: wrap;
8775
+ gap: var(--bs-space-2) var(--bs-gap-md);
8776
+ color: var(--bs-color-text-secondary);
8777
+ }
8778
+
8779
+ [data-scope="progress-group"][data-part="legend-item"] {
8780
+ display: inline-flex;
8781
+ align-items: center;
8782
+ gap: var(--bs-space-2);
8783
+ }
8784
+
8785
+ [data-scope="progress-group"][data-part="swatch"] {
8786
+ flex: none;
8787
+ inline-size: var(--bs-space-2);
8788
+ block-size: var(--bs-space-2);
8789
+ border-radius: var(--bs-radius-xs);
8790
+ background: var(--_pigment);
8791
+ }
8792
+
8793
+ [data-scope="progress-group"][data-part="legend-value"] {
8794
+ color: var(--bs-color-text-primary);
8795
+ font-variant-numeric: tabular-nums;
8796
+ }
8797
+ `;
8798
+ //#endregion
6841
8799
  //#region src/styles/components/qr-code.ts
6842
8800
  const qrCodeCss = `
6843
8801
  [data-scope="qr-code"][data-part="root"] {
@@ -6959,8 +8917,8 @@ const radioGroupCss = labelCss("radio-group") + `
6959
8917
  align-items: center;
6960
8918
  justify-content: center;
6961
8919
  flex-shrink: 0;
6962
- inline-size: var(--bs-part-size-md);
6963
- block-size: var(--bs-part-size-md);
8920
+ inline-size: var(--bs-part-size-sm);
8921
+ block-size: var(--bs-part-size-sm);
6964
8922
  border: 1px solid var(--bs-color-border);
6965
8923
  border-radius: var(--bs-radius-full);
6966
8924
  background: var(--bs-color-surface-2);
@@ -6989,8 +8947,8 @@ const radioGroupCss = labelCss("radio-group") + `
6989
8947
 
6990
8948
  [data-scope="radio-group"][data-part="item-control"][data-state="checked"]::before {
6991
8949
  content: "";
6992
- inline-size: calc(var(--bs-part-size-md) * 0.3);
6993
- block-size: calc(var(--bs-part-size-md) * 0.3);
8950
+ inline-size: calc(var(--bs-part-size-sm) * 0.5);
8951
+ block-size: calc(var(--bs-part-size-sm) * 0.5);
6994
8952
  border-radius: var(--bs-radius-full);
6995
8953
  background: var(--bs-color-primary-text);
6996
8954
  }
@@ -7019,8 +8977,8 @@ const radioGroupCss = labelCss("radio-group") + `
7019
8977
  the spring — position comes from the machine. */
7020
8978
  [data-scope="radio-group"][data-part="indicator"] {
7021
8979
  position: absolute;
7022
- inline-size: var(--bs-part-size-md);
7023
- block-size: var(--bs-part-size-md);
8980
+ inline-size: var(--bs-part-size-sm);
8981
+ block-size: var(--bs-part-size-sm);
7024
8982
  border-radius: var(--bs-radius-full);
7025
8983
  background: var(--bs-color-primary);
7026
8984
  transition:
@@ -7332,6 +9290,7 @@ const selectCss = labelCss("select") + positionerCss("select") + popupContentCss
7332
9290
  /* The control is the field: the recipe's border, surface and focus halo
7333
9291
  ride on it, with the trigger and its passengers sitting inside. */
7334
9292
  [data-scope="select"][data-part="control"] {
9293
+ box-sizing: border-box;
7335
9294
  display: flex;
7336
9295
  align-items: center;
7337
9296
  gap: var(--bs-space-1);
@@ -7714,104 +9673,6 @@ const skeletonCss = `
7714
9673
  }
7715
9674
  `;
7716
9675
  //#endregion
7717
- //#region src/styles/components/spinner.ts
7718
- const spinnerCss = `
7719
- /* A wheel of waiting: one arc of ink turning about its center. Quiet by
7720
- default — it reports progress without claiming attention. */
7721
- [data-scope="spinner"][data-part="root"] {
7722
- display: inline-flex;
7723
- flex: none;
7724
- color: var(--bs-color-text-tertiary);
7725
- animation: bs-spinner-turn 0.9s linear infinite;
7726
- }
7727
-
7728
- [data-scope="spinner"][data-part="root"][data-size="sm"] {
7729
- inline-size: var(--bs-part-size-sm);
7730
- block-size: var(--bs-part-size-sm);
7731
- }
7732
- [data-scope="spinner"][data-part="root"][data-size="md"] {
7733
- inline-size: var(--bs-part-size-md);
7734
- block-size: var(--bs-part-size-md);
7735
- }
7736
- [data-scope="spinner"][data-part="root"][data-size="lg"] {
7737
- inline-size: var(--bs-part-size-lg);
7738
- block-size: var(--bs-part-size-lg);
7739
- }
7740
-
7741
- [data-scope="spinner"][data-part="root"] svg {
7742
- inline-size: 100%;
7743
- block-size: 100%;
7744
- }
7745
-
7746
- @keyframes bs-spinner-turn {
7747
- to {
7748
- transform: rotate(1turn);
7749
- }
7750
- }
7751
-
7752
- @media (prefers-reduced-motion: reduce) {
7753
- [data-scope="spinner"][data-part="root"] {
7754
- animation-duration: 1ms;
7755
- animation-iteration-count: 1;
7756
- }
7757
- }
7758
- `;
7759
- //#endregion
7760
- //#region src/styles/components/stat.ts
7761
- const statCss = `
7762
- /* One figure on the page: the label whispers what it is, the value
7763
- states it plainly in tabular figures, the delta reads the direction
7764
- in the fixed semantic pigments. */
7765
- [data-scope="stat"][data-part="root"] {
7766
- display: flex;
7767
- flex-direction: column;
7768
- gap: var(--bs-space-1);
7769
- }
7770
-
7771
- [data-scope="stat"][data-part="label"] {
7772
- margin: 0;
7773
- color: var(--bs-color-text-secondary);
7774
- font-size: var(--bs-font-size-sm);
7775
- font-weight: var(--bs-font-weight-medium);
7776
- letter-spacing: var(--bs-tracking-label);
7777
- }
7778
-
7779
- /* The value carries the ink: large, semibold, tabular so figures
7780
- align across a row of stats. */
7781
- [data-scope="stat"][data-part="value"] {
7782
- color: var(--bs-color-text-primary);
7783
- font-size: var(--bs-font-size-3xl);
7784
- font-weight: var(--bs-font-weight-semibold);
7785
- font-variant-numeric: tabular-nums;
7786
- line-height: var(--bs-line-height-tight);
7787
- }
7788
-
7789
- [data-scope="stat"][data-part="delta"] {
7790
- display: inline-flex;
7791
- align-items: center;
7792
- gap: var(--bs-space-1);
7793
- color: var(--bs-color-text-tertiary);
7794
- font-size: var(--bs-font-size-sm);
7795
- font-weight: var(--bs-font-weight-medium);
7796
- font-variant-numeric: tabular-nums;
7797
- }
7798
-
7799
- [data-scope="stat"][data-part="delta"][data-direction="up"] {
7800
- color: var(--bs-color-success);
7801
- }
7802
-
7803
- [data-scope="stat"][data-part="delta"][data-direction="down"] {
7804
- color: var(--bs-color-danger);
7805
- }
7806
-
7807
- [data-scope="stat"][data-part="description"] {
7808
- margin: 0;
7809
- color: var(--bs-color-text-tertiary);
7810
- font-size: var(--bs-font-size-sm);
7811
- line-height: var(--bs-line-height-relaxed);
7812
- }
7813
- `;
7814
- //#endregion
7815
9676
  //#region src/styles/components/slider.ts
7816
9677
  const sliderCss = labelCss("slider") + `
7817
9678
  [data-scope="slider"][data-part="root"] {
@@ -7880,15 +9741,17 @@ const sliderCss = labelCss("slider") + `
7880
9741
  }
7881
9742
 
7882
9743
  /* The thumb is a paper seal riding the ink: surface fill, primary hairline,
7883
- focus turns the ring primary — never a background change. Its size is a
7884
- part size, not a control height, so density and scene actually shrink it. */
9744
+ focus turns the ring primary — never a background change. Its size rides
9745
+ the control height at a fixed share (five eighths), not the density
9746
+ scale — scenes that grow the targets grow the thumb with them, never
9747
+ past the track it sits on. */
7885
9748
  [data-scope="slider"][data-part="thumb"] {
7886
9749
  box-sizing: border-box;
7887
9750
  display: flex;
7888
9751
  align-items: center;
7889
9752
  justify-content: center;
7890
- inline-size: var(--bs-part-size-md);
7891
- block-size: var(--bs-part-size-md);
9753
+ inline-size: calc(var(--bs-control-height-md) * 0.625);
9754
+ block-size: calc(var(--bs-control-height-md) * 0.625);
7892
9755
  border: 1px solid var(--bs-color-primary);
7893
9756
  border-radius: var(--bs-radius-full);
7894
9757
  background: var(--bs-color-surface-2);
@@ -7970,18 +9833,107 @@ const sliderCss = labelCss("slider") + `
7970
9833
  translate: -50% 0;
7971
9834
  }
7972
9835
 
7973
- [data-scope="slider"][data-part="marker"][data-state="at-value"],
7974
- [data-scope="slider"][data-part="marker"][data-state="under-value"] {
7975
- color: var(--bs-color-primary);
9836
+ [data-scope="slider"][data-part="marker"][data-state="at-value"],
9837
+ [data-scope="slider"][data-part="marker"][data-state="under-value"] {
9838
+ color: var(--bs-color-primary);
9839
+ }
9840
+
9841
+ [data-scope="slider"][data-part="marker"][data-state="at-value"]::before,
9842
+ [data-scope="slider"][data-part="marker"][data-state="under-value"]::before {
9843
+ background: var(--bs-color-primary);
9844
+ }
9845
+
9846
+ [data-scope="slider"][data-part="marker"][data-disabled] {
9847
+ color: var(--bs-color-text-disabled);
9848
+ }
9849
+ `;
9850
+ //#endregion
9851
+ //#region src/styles/components/spinner.ts
9852
+ const spinnerCss = `
9853
+ /* A wheel of waiting: one arc of ink turning about its center. Quiet by
9854
+ default — it reports progress without claiming attention. */
9855
+ [data-scope="spinner"][data-part="root"] {
9856
+ display: inline-flex;
9857
+ flex: none;
9858
+ color: var(--bs-color-text-tertiary);
9859
+ animation: bs-spinner-turn 0.9s linear infinite;
9860
+ }
9861
+
9862
+ [data-scope="spinner"][data-part="root"][data-size="sm"] {
9863
+ inline-size: var(--bs-part-size-sm);
9864
+ block-size: var(--bs-part-size-sm);
9865
+ }
9866
+ [data-scope="spinner"][data-part="root"][data-size="md"] {
9867
+ inline-size: var(--bs-part-size-md);
9868
+ block-size: var(--bs-part-size-md);
9869
+ }
9870
+ [data-scope="spinner"][data-part="root"][data-size="lg"] {
9871
+ inline-size: var(--bs-part-size-lg);
9872
+ block-size: var(--bs-part-size-lg);
9873
+ }
9874
+
9875
+ [data-scope="spinner"][data-part="root"] svg {
9876
+ inline-size: 100%;
9877
+ block-size: 100%;
9878
+ }
9879
+
9880
+ @keyframes bs-spinner-turn {
9881
+ to {
9882
+ transform: rotate(1turn);
9883
+ }
9884
+ }
9885
+
9886
+ @media (prefers-reduced-motion: reduce) {
9887
+ [data-scope="spinner"][data-part="root"] {
9888
+ animation-duration: 1ms;
9889
+ animation-iteration-count: 1;
9890
+ }
9891
+ }
9892
+ `;
9893
+ //#endregion
9894
+ //#region src/styles/components/split-button.ts
9895
+ const splitButtonCss = `
9896
+ /* The split: a primary action and its dropdown arrow seam-fused into one
9897
+ control — the button group's joinery closed to two members. The main
9898
+ button gives up its end corners; the arrow gives up its start corners
9899
+ and slides one pixel over the trailing hairline, redrawing the seam as
9900
+ a single line of border color (a cut of paper through a solid fill). */
9901
+ [data-scope="split-button"][data-part="root"] {
9902
+ display: inline-flex;
9903
+ }
9904
+
9905
+ [data-scope="split-button"][data-part="root"] > [data-scope="button"][data-part="root"] {
9906
+ border-start-end-radius: 0;
9907
+ border-end-end-radius: 0;
7976
9908
  }
7977
9909
 
7978
- [data-scope="slider"][data-part="marker"][data-state="at-value"]::before,
7979
- [data-scope="slider"][data-part="marker"][data-state="under-value"]::before {
7980
- background: var(--bs-color-primary);
9910
+ /* The arrow rides the menu machine's trigger anatomy (as-child hosting
9911
+ reads data-scope="menu"), dressed as a button by the button
9912
+ stylesheet's trigger register — hence the variant seal here, keeping
9913
+ these joins above that register regardless of stylesheet order. */
9914
+ [data-scope="split-button"][data-part="root"] > [data-scope="menu"][data-part="trigger"][data-variant] {
9915
+ border-start-start-radius: 0;
9916
+ border-end-start-radius: 0;
9917
+ margin-inline-start: -1px;
9918
+ border-inline-start: 1px solid var(--bs-color-border);
7981
9919
  }
7982
9920
 
7983
- [data-scope="slider"][data-part="marker"][data-disabled] {
7984
- color: var(--bs-color-text-disabled);
9921
+ /* Focus fuses like the border: a raised member keeps its halo and its
9922
+ deepened edge clear of the neighbor, and an open menu reads as one
9923
+ control at rest. */
9924
+ [data-scope="split-button"][data-part="root"] > [data-scope="button"][data-part="root"]:hover,
9925
+ [data-scope="split-button"][data-part="root"] > [data-scope="button"][data-part="root"]:focus-visible,
9926
+ [data-scope="split-button"][data-part="root"] > [data-scope="menu"][data-part="trigger"][data-variant]:hover,
9927
+ [data-scope="split-button"][data-part="root"] > [data-scope="menu"][data-part="trigger"][data-variant]:focus-visible,
9928
+ [data-scope="split-button"][data-part="root"] > [data-scope="menu"][data-part="trigger"][data-variant][data-state="open"] {
9929
+ z-index: 1;
9930
+ }
9931
+
9932
+ /* The popup keeps the menu parts and the menu stylesheet; only the
9933
+ danger rows ride this scope's knowledge of the callers — the
9934
+ menubar's register, restated so this family stands alone. */
9935
+ [data-scope="menu"][data-part="item"][data-danger] {
9936
+ color: var(--bs-color-danger);
7985
9937
  }
7986
9938
  `;
7987
9939
  //#endregion
@@ -8120,6 +10072,77 @@ const splitterCss = `
8120
10072
  }
8121
10073
  `;
8122
10074
  //#endregion
10075
+ //#region src/styles/components/stack.ts
10076
+ const stackCss = `
10077
+ /* The spacing primitive: the wrapper points --bs-stack-gap at a step of
10078
+ the space ramp, and the flex gap carries it — the distance is one
10079
+ named token, never an ad-hoc margin between siblings. */
10080
+ [data-scope="stack"][data-part="root"] {
10081
+ display: flex;
10082
+ flex-direction: column;
10083
+ gap: var(--bs-stack-gap, var(--bs-space-3));
10084
+ }
10085
+
10086
+ [data-scope="stack"][data-part="root"][data-direction="row"] {
10087
+ flex-direction: row;
10088
+ }
10089
+ `;
10090
+ //#endregion
10091
+ //#region src/styles/components/stat.ts
10092
+ const statCss = `
10093
+ /* One figure on the page: the label whispers what it is, the value
10094
+ states it plainly in tabular figures, the delta reads the direction
10095
+ in the fixed semantic pigments. */
10096
+ [data-scope="stat"][data-part="root"] {
10097
+ display: flex;
10098
+ flex-direction: column;
10099
+ gap: var(--bs-space-1);
10100
+ }
10101
+
10102
+ [data-scope="stat"][data-part="label"] {
10103
+ margin: 0;
10104
+ color: var(--bs-color-text-secondary);
10105
+ font-size: var(--bs-font-size-sm);
10106
+ font-weight: var(--bs-font-weight-medium);
10107
+ letter-spacing: var(--bs-tracking-label);
10108
+ }
10109
+
10110
+ /* The value carries the ink: large, semibold, tabular so figures
10111
+ align across a row of stats. */
10112
+ [data-scope="stat"][data-part="value"] {
10113
+ color: var(--bs-color-text-primary);
10114
+ font-size: var(--bs-font-size-3xl);
10115
+ font-weight: var(--bs-font-weight-semibold);
10116
+ font-variant-numeric: tabular-nums;
10117
+ line-height: var(--bs-line-height-tight);
10118
+ }
10119
+
10120
+ [data-scope="stat"][data-part="delta"] {
10121
+ display: inline-flex;
10122
+ align-items: center;
10123
+ gap: var(--bs-space-1);
10124
+ color: var(--bs-color-text-tertiary);
10125
+ font-size: var(--bs-font-size-sm);
10126
+ font-weight: var(--bs-font-weight-medium);
10127
+ font-variant-numeric: tabular-nums;
10128
+ }
10129
+
10130
+ [data-scope="stat"][data-part="delta"][data-direction="up"] {
10131
+ color: var(--bs-color-success);
10132
+ }
10133
+
10134
+ [data-scope="stat"][data-part="delta"][data-direction="down"] {
10135
+ color: var(--bs-color-danger);
10136
+ }
10137
+
10138
+ [data-scope="stat"][data-part="description"] {
10139
+ margin: 0;
10140
+ color: var(--bs-color-text-tertiary);
10141
+ font-size: var(--bs-font-size-sm);
10142
+ line-height: var(--bs-line-height-relaxed);
10143
+ }
10144
+ `;
10145
+ //#endregion
8123
10146
  //#region src/styles/components/steps.ts
8124
10147
  const stepsCss = `
8125
10148
  [data-scope="steps"][data-part="root"] {
@@ -8191,7 +10214,7 @@ const stepsCss = `
8191
10214
  place-items: center;
8192
10215
  inline-size: var(--bs-control-height-sm);
8193
10216
  block-size: var(--bs-control-height-sm);
8194
- border-radius: calc(var(--bs-radius-lg) * 100);
10217
+ border-radius: var(--bs-radius-full);
8195
10218
  background: var(--bs-color-surface-2);
8196
10219
  box-shadow: inset 0 0 0 1px var(--bs-color-border);
8197
10220
  color: var(--bs-color-text-secondary);
@@ -8363,10 +10386,12 @@ const switchCss = labelCss("switch") + `
8363
10386
  }
8364
10387
 
8365
10388
  /* The track geometry: thumb travel is derived here so the knob lands
8366
- flush against the far edge instead of drifting off-token. */
10389
+ flush against the far edge instead of drifting off-token. The thumb
10390
+ rides the small part size — a switch sits beside controls, not
10391
+ among them, and 24px of track reads quiet at every density. */
8367
10392
  [data-scope="switch"][data-part="control"] {
8368
10393
  --bs-switch-track: calc(var(--bs-switch-thumb) * 2 + var(--bs-switch-inset) * 2);
8369
- --bs-switch-thumb: var(--bs-part-size-md);
10394
+ --bs-switch-thumb: var(--bs-part-size-sm);
8370
10395
  --bs-switch-inset: var(--bs-space-1);
8371
10396
  display: inline-flex;
8372
10397
  align-items: center;
@@ -9820,6 +11845,171 @@ const tourCss = popupContentCss("tour", "0") + `
9820
11845
  }
9821
11846
  `;
9822
11847
  //#endregion
11848
+ //#region src/styles/components/transfer.ts
11849
+ const transferCss = `
11850
+ /* Two ledgers and a crossing: the panels carry the popup chrome's
11851
+ paper-and-hairline at rest weight, the buttons column rides between. */
11852
+ [data-scope="transfer"][data-part="root"] {
11853
+ display: flex;
11854
+ align-items: center;
11855
+ gap: var(--bs-space-3);
11856
+ }
11857
+
11858
+ [data-scope="transfer"][data-part="panel"] {
11859
+ display: flex;
11860
+ flex-direction: column;
11861
+ inline-size: 14rem;
11862
+ border: 1px solid var(--bs-color-border);
11863
+ border-radius: var(--bs-radius-md);
11864
+ background: var(--bs-color-surface-1);
11865
+ }
11866
+
11867
+ [data-scope="transfer"][data-part="head"] {
11868
+ display: flex;
11869
+ align-items: center;
11870
+ justify-content: space-between;
11871
+ gap: var(--bs-space-2);
11872
+ padding: var(--bs-space-2) var(--bs-space-3);
11873
+ border-block-end: 1px solid var(--bs-color-border);
11874
+ }
11875
+
11876
+ [data-scope="transfer"][data-part="title"] {
11877
+ color: var(--bs-color-text-secondary);
11878
+ font-size: var(--bs-font-size-sm);
11879
+ font-weight: var(--bs-font-weight-semibold);
11880
+ letter-spacing: var(--bs-tracking-label);
11881
+ }
11882
+
11883
+ [data-scope="transfer"][data-part="count"] {
11884
+ min-inline-size: var(--bs-control-height-sm);
11885
+ padding-inline: var(--bs-space-1);
11886
+ border-radius: var(--bs-radius-sm);
11887
+ background: var(--bs-color-surface-inset);
11888
+ color: var(--bs-color-text-tertiary);
11889
+ font-size: var(--bs-font-size-xs, 0.75rem);
11890
+ line-height: var(--bs-control-height-sm);
11891
+ text-align: center;
11892
+ font-variant-numeric: tabular-nums;
11893
+ }
11894
+
11895
+ [data-scope="transfer"][data-part="search"] {
11896
+ padding: var(--bs-space-2);
11897
+ border-block-end: 1px solid var(--bs-color-border);
11898
+ }
11899
+
11900
+ [data-scope="transfer"][data-part="list"] {
11901
+ display: flex;
11902
+ flex-direction: column;
11903
+ padding: var(--bs-space-1);
11904
+ max-block-size: 16rem;
11905
+ overflow-block: auto;
11906
+ }
11907
+
11908
+ /* Rows are the checkbox's own seal and label — the panel only gives
11909
+ them a clickable span and a hover wash. */
11910
+ [data-scope="transfer"] [data-scope="checkbox"][data-part="root"] {
11911
+ flex: 1;
11912
+ gap: var(--bs-space-2);
11913
+ padding: var(--bs-space-1) var(--bs-space-2);
11914
+ border-radius: var(--bs-radius-sm);
11915
+ transition: background-color var(--bs-duration-fast) var(--bs-ease-out);
11916
+ }
11917
+
11918
+ [data-scope="transfer"] [data-scope="checkbox"][data-part="root"]:hover {
11919
+ background: var(--bs-color-surface-inset);
11920
+ }
11921
+
11922
+ [data-scope="transfer"] [data-part="label"] {
11923
+ flex: 1;
11924
+ font-size: var(--bs-font-size-sm);
11925
+ color: var(--bs-color-text-primary);
11926
+ cursor: inherit;
11927
+ }
11928
+
11929
+ [data-scope="transfer"][data-part="empty"] {
11930
+ padding: var(--bs-space-4);
11931
+ color: var(--bs-color-text-tertiary);
11932
+ font-size: var(--bs-font-size-sm);
11933
+ text-align: center;
11934
+ }
11935
+
11936
+ [data-scope="transfer"][data-part="operations"] {
11937
+ display: flex;
11938
+ flex-direction: column;
11939
+ gap: var(--bs-space-2);
11940
+ }
11941
+ `;
11942
+ //#endregion
11943
+ //#region src/styles/components/tree-select.ts
11944
+ const treeSelectCss = positionerCss("tree-select") + popupContentCss("tree-select", "0rem") + `
11945
+ /* The control reads as a field — the input recipe, with the chosen
11946
+ label as its ink and a quiet chevron at the inline end. */
11947
+ [data-scope="tree-select"][data-part="control"] {
11948
+ box-sizing: border-box;
11949
+ display: flex;
11950
+ align-items: center;
11951
+ justify-content: space-between;
11952
+ gap: var(--bs-space-2);
11953
+ inline-size: 100%;
11954
+ block-size: var(--bs-control-height-md);
11955
+ padding-inline: var(--bs-padding-md);
11956
+ border: 1px solid var(--bs-color-border);
11957
+ border-radius: var(--bs-radius-sm);
11958
+ background: var(--bs-color-surface-2);
11959
+ color: var(--bs-color-text-primary);
11960
+ font: inherit;
11961
+ font-size: var(--bs-font-size-md);
11962
+ text-align: start;
11963
+ cursor: pointer;
11964
+ transition: border-color var(--bs-duration-fast) var(--bs-ease-out);
11965
+ }
11966
+
11967
+ [data-scope="tree-select"][data-part="control"][data-placeholder] {
11968
+ color: var(--bs-color-text-tertiary);
11969
+ }
11970
+
11971
+ [data-scope="tree-select"][data-part="control"]:hover:not(:focus):not(:disabled) {
11972
+ border-color: var(--bs-color-border-strong);
11973
+ }
11974
+
11975
+ [data-scope="tree-select"][data-part="control"]:focus-visible {
11976
+ outline: none;
11977
+ border-color: var(--bs-color-primary);
11978
+ box-shadow: var(--bs-focus-ring);
11979
+ }
11980
+
11981
+ [data-scope="tree-select"][data-part="control"]:disabled {
11982
+ border-color: var(--bs-color-border);
11983
+ background: var(--bs-color-surface-inset);
11984
+ color: var(--bs-color-text-disabled);
11985
+ cursor: not-allowed;
11986
+ }
11987
+
11988
+ [data-scope="tree-select"][data-part="chevron"] {
11989
+ display: grid;
11990
+ place-items: center;
11991
+ color: var(--bs-color-text-tertiary);
11992
+ }
11993
+
11994
+ [data-scope="tree-select"][data-part="chevron"] svg {
11995
+ inline-size: var(--bs-space-4);
11996
+ block-size: var(--bs-space-4);
11997
+ transition: transform var(--bs-duration-fast) var(--bs-ease-out);
11998
+ }
11999
+
12000
+ [data-scope="tree-select"][data-part="control"][data-open] [data-part="chevron"] svg {
12001
+ transform: rotate(90deg);
12002
+ }
12003
+
12004
+ /* Inside the shared popup vessel, the tree keeps its own height and
12005
+ scrolls past a small grove. */
12006
+ [data-scope="tree-select"][data-part="content"] {
12007
+ max-block-size: 16rem;
12008
+ overflow-block: auto;
12009
+ padding: var(--bs-space-2);
12010
+ }
12011
+ `;
12012
+ //#endregion
9823
12013
  //#region src/styles/components/index.ts
9824
12014
  /** Component style registry — every component contributes its stylesheet
9825
12015
  * here, keyed by the name wrappers pass to `injectComponentStyle`. */
@@ -9835,9 +12025,11 @@ const componentStyles = {
9835
12025
  breadcrumb: breadcrumbCss,
9836
12026
  button: buttonCss,
9837
12027
  calendar: calendarCss + datePickerCss.replaceAll("data-scope=\"date-picker\"", "data-scope=\"calendar\""),
12028
+ "cascade-select": cascadeSelectCss,
9838
12029
  card: cardCss,
9839
12030
  carousel: carouselCss,
9840
12031
  checkbox: checkboxCss,
12032
+ "checkbox-group": checkboxGroupCss,
9841
12033
  chip: chipCss,
9842
12034
  clipboard: clipboardCss,
9843
12035
  collapsible: collapsibleCss,
@@ -9845,6 +12037,7 @@ const componentStyles = {
9845
12037
  combobox: comboboxCss,
9846
12038
  "date-input": dateInputCss,
9847
12039
  "date-picker": datePickerCss,
12040
+ descriptions: descriptionsCss,
9848
12041
  dialog: dialogCss,
9849
12042
  drawer: drawerCss,
9850
12043
  editable: editableCss,
@@ -9853,9 +12046,11 @@ const componentStyles = {
9853
12046
  fieldset: fieldsetCss,
9854
12047
  "file-upload": fileUploadCss,
9855
12048
  "floating-panel": floatingPanelCss,
12049
+ form: formCss,
9856
12050
  highlight: highlightCss,
9857
12051
  "hover-card": hoverCardCss,
9858
12052
  "image-cropper": imageCropperCss,
12053
+ input: fieldControlCss.replaceAll("data-scope=\"field\"", "data-scope=\"input\"").replaceAll("data-part=\"input\"", "data-part=\"root\""),
9859
12054
  "json-tree-view": jsonTreeViewCss,
9860
12055
  kbd: kbdCss,
9861
12056
  link: linkCss,
@@ -9863,13 +12058,13 @@ const componentStyles = {
9863
12058
  marquee: marqueeCss,
9864
12059
  menu: menuCss,
9865
12060
  meter: meterCss,
9866
- typography: typographyCss,
9867
12061
  "navigation-menu": navigationMenuCss,
9868
12062
  "number-input": numberInputCss,
9869
12063
  "page-header": pageHeaderCss,
9870
12064
  pagination: paginationCss,
9871
12065
  "password-input": passwordInputCss,
9872
12066
  "pin-input": pinInputCss,
12067
+ popconfirm: popconfirmCss,
9873
12068
  popover: popoverCss,
9874
12069
  progress: progressCss,
9875
12070
  "qr-code": qrCodeCss,
@@ -9890,6 +12085,7 @@ const componentStyles = {
9890
12085
  switch: switchCss,
9891
12086
  tabs: tabsCss,
9892
12087
  "tags-input": tagsInputCss,
12088
+ textarea: fieldControlCss.replaceAll("data-scope=\"field\"", "data-scope=\"textarea\"").replaceAll("data-part=\"textarea\"", "data-part=\"root\""),
9893
12089
  timeline: timelineCss,
9894
12090
  timer: timerCss,
9895
12091
  table: tableCss,
@@ -9897,16 +12093,111 @@ const componentStyles = {
9897
12093
  toc: tocCss,
9898
12094
  toggle: toggleCss,
9899
12095
  "toggle-group": toggleGroupCss,
12096
+ transfer: transferCss,
9900
12097
  tooltip: tooltipCss,
9901
12098
  tour: tourCss,
9902
- "tree-view": treeViewCss
12099
+ "tree-select": treeSelectCss,
12100
+ "tree-view": treeViewCss,
12101
+ typography: `
12102
+ /* The typographic voices, named so prose can ask for one: display and
12103
+ heading ride the song-serif, the rest ride the hei. Nothing here is
12104
+ decorative — hierarchy is size, weight, and space. */
12105
+ [data-scope="typography"][data-part="display"] {
12106
+ margin: 0;
12107
+ color: var(--bs-color-text-primary);
12108
+ font-family: var(--bs-font-serif);
12109
+ font-size: var(--bs-font-size-4xl);
12110
+ font-weight: var(--bs-font-weight-semibold);
12111
+ line-height: var(--bs-line-height-tight);
12112
+ }
12113
+
12114
+ [data-scope="typography"][data-part="heading"] {
12115
+ margin: 0;
12116
+ color: var(--bs-color-text-primary);
12117
+ font-family: var(--bs-font-serif);
12118
+ font-size: var(--bs-font-size-2xl);
12119
+ font-weight: var(--bs-font-weight-semibold);
12120
+ line-height: var(--bs-line-height-snug);
12121
+ }
12122
+
12123
+ [data-scope="typography"][data-part="lead"] {
12124
+ margin: 0;
12125
+ color: var(--bs-color-text-secondary);
12126
+ font-size: var(--bs-font-size-lg);
12127
+ line-height: var(--bs-line-height-relaxed);
12128
+ }
12129
+
12130
+ [data-scope="typography"][data-part="body"] {
12131
+ margin: 0;
12132
+ color: var(--bs-color-text-secondary);
12133
+ font-size: var(--bs-font-size-base);
12134
+ line-height: var(--bs-line-height-relaxed);
12135
+ }
12136
+
12137
+ [data-scope="typography"][data-part="muted"] {
12138
+ margin: 0;
12139
+ color: var(--bs-color-text-tertiary);
12140
+ font-size: var(--bs-font-size-sm);
12141
+ line-height: var(--bs-line-height-relaxed);
12142
+ }
12143
+
12144
+ [data-scope="typography"][data-part="label"] {
12145
+ margin: 0;
12146
+ color: var(--bs-color-text-secondary);
12147
+ font-size: var(--bs-font-size-sm);
12148
+ font-weight: var(--bs-font-weight-medium);
12149
+ letter-spacing: var(--bs-tracking-label);
12150
+ }
12151
+ `,
12152
+ container: containerCss,
12153
+ stack: stackCss,
12154
+ grid: gridCss,
12155
+ "aspect-ratio": aspectRatioCss,
12156
+ masonry: masonryCss,
12157
+ icon: iconCss,
12158
+ ellipsis: ellipsisCss,
12159
+ image: imageCss,
12160
+ "image-viewer": imageViewerCss,
12161
+ list: listCss,
12162
+ comment: commentCss,
12163
+ watermark: `
12164
+ /* The paper bears its seal beneath the content: the wrapper holds the
12165
+ flow, the marks repeat their tile across the whole sheet and never
12166
+ take a pointer — the page beneath stays live. */
12167
+ [data-scope="watermark"][data-part="root"] {
12168
+ position: relative;
12169
+ }
12170
+
12171
+ [data-scope="watermark"][data-part="content"] {
12172
+ position: relative;
12173
+ }
12174
+
12175
+ [data-scope="watermark"][data-part="marks"] {
12176
+ position: absolute;
12177
+ inset: 0;
12178
+ pointer-events: none;
12179
+ background-repeat: repeat;
12180
+ }
12181
+ `,
12182
+ "progress-group": progressGroupCss,
12183
+ "back-top": backTopCss,
12184
+ affix: affixCss,
12185
+ command: commandCss,
12186
+ mentions: mentionsCss,
12187
+ menubar: menubarCss,
12188
+ "input-group": inputGroupCss,
12189
+ "dynamic-input": dynamicInputCss,
12190
+ "button-group": buttonGroupCss,
12191
+ "split-button": splitButtonCss,
12192
+ "float-button": floatButtonCss,
12193
+ layout: layoutCss
9903
12194
  };
9904
12195
  //#endregion
9905
12196
  //#region src/styles.ts
9906
12197
  /** The complete token layer (`@layer bs.tokens`): themes, density, contrast,
9907
12198
  * typography, motion, z-index, plus the document-level base guard and the
9908
12199
  * ink-ripple motion grammar. */
9909
- const tokensCss = styles_default + baseCss + inkRippleCss;
12200
+ const tokensCss = tokensStyles + baseCss + inkRippleCss;
9910
12201
  let injected = false;
9911
12202
  /** True when an SSR integration already delivered the whole layer (the
9912
12203
  * Nuxt module ships it as one build-time stylesheet and plants this