@bysages/core 0.0.1 → 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
@@ -7,7 +7,12 @@ const SCENE_DEFAULT_ACCENT = {
7
7
  civic: "zhusha",
8
8
  enterprise: "qinghua",
9
9
  studio: "celadon",
10
- tech: "ink"
10
+ tech: "ink",
11
+ cupertino: "ink",
12
+ expressive: "zhusha",
13
+ fluent: "qinghua",
14
+ material: "celadon",
15
+ sketch: "zhusha"
11
16
  };
12
17
  /** Each scene also pairs with a contrast tier — civic serves elders, so it
13
18
  * speaks at the loud tier by default. "auto" contrast resolves here;
@@ -16,7 +21,12 @@ const SCENE_DEFAULT_CONTRAST = {
16
21
  civic: "high",
17
22
  enterprise: "normal",
18
23
  studio: "normal",
19
- tech: "normal"
24
+ tech: "normal",
25
+ cupertino: "normal",
26
+ expressive: "normal",
27
+ fluent: "normal",
28
+ material: "normal",
29
+ sketch: "normal"
20
30
  };
21
31
  const STORAGE_KEY = "bs-theme";
22
32
  const DARK_QUERY = "(prefers-color-scheme: dark)";
@@ -206,19 +216,25 @@ function detachDynamicLight() {
206
216
  //#endregion
207
217
  //#region src/ink-ripple.ts
208
218
  /**
209
- * Ink ripple — the press feedback of the paper-and-ink register. Where
210
- * Material spreads a hard circle, ink bleeds: a soft wash of the element's
211
- * own pigment diluting outward from the press point, gone before it draws
212
- * 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.
213
226
  *
214
227
  * The bleed's CSS lives in the base stylesheet, keyed on
215
- * `data-motion~="ink-ripple"`; this module watches presses and marks the
216
- * 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
217
230
  * everywhere else in the system.
218
231
  *
219
232
  * @module
220
233
  */
221
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;
222
238
  let detachPress;
223
239
  /** Watch for presses anywhere inside `root` (default: the document) and
224
240
  * bleed ink from the press point of the nearest opt-in element. Safe to
@@ -226,9 +242,7 @@ let detachPress;
226
242
  function attachInkRipple(root) {
227
243
  detachInkRipple();
228
244
  if (typeof document === "undefined") return () => {};
229
- const onEnd = (event) => {
230
- if (event.target instanceof HTMLElement) delete event.target.dataset.ripple;
231
- };
245
+ const held = /* @__PURE__ */ new Map();
232
246
  const onDown = (event) => {
233
247
  if (!(event instanceof PointerEvent)) return;
234
248
  if (event.pointerType !== "touch" && event.button !== 0) return;
@@ -237,18 +251,44 @@ function attachInkRipple(root) {
237
251
  const host = target.closest(RIPPLE_SELECTOR);
238
252
  if (!(host instanceof HTMLElement)) return;
239
253
  const rect = host.getBoundingClientRect();
240
- host.style.setProperty("--bs-ripple-x", `${(event.clientX - rect.left).toFixed(1)}px`);
241
- 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`);
242
260
  delete host.dataset.ripple;
243
261
  host.offsetWidth;
244
- 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;
245
281
  };
246
282
  const scope = root ?? document;
247
283
  scope.addEventListener("pointerdown", onDown, { passive: true });
248
- 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);
249
287
  detachPress = () => {
250
288
  scope.removeEventListener("pointerdown", onDown);
251
- scope.removeEventListener("animationend", onEnd, true);
289
+ scope.removeEventListener("pointerup", onUp);
290
+ scope.removeEventListener("pointercancel", onUp);
291
+ scope.removeEventListener("animationend", onAnimationEnd, true);
252
292
  };
253
293
  return detachPress;
254
294
  }
@@ -266,6 +306,21 @@ const baseCss = `
266
306
  [hidden] {
267
307
  display: none !important;
268
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
+ }
269
324
  `;
270
325
  /** Press feedback that rides the motion attribute, not any one component —
271
326
  * anything with `data-motion~="ink-ripple"` bleeds from its press point. */
@@ -288,38 +343,60 @@ const inkRippleCss = `
288
343
  border-radius: 100%;
289
344
  background: radial-gradient(
290
345
  circle closest-side,
291
- var(--_ripple-pigment, color-mix(in oklab, var(--bs-color-primary) 30%, transparent)),
292
- transparent 80%
346
+ var(--bs-ripple-pigment) var(--bs-ripple-core, 0%),
347
+ transparent var(--bs-ripple-fade, 80%)
293
348
  );
294
349
  scale: 0;
295
350
  opacity: 0;
296
351
  pointer-events: none;
297
352
  }
298
353
 
299
- [data-motion~="ink-ripple"][data-ripple="run"]::after {
300
- 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;
301
357
  }
302
358
 
303
- /* Ink into water: the wash sweeps across the whole surface at full
304
- strength first, and only once it has covered the body does it start
305
- dissolving never fading while it still has ground to cover. */
306
- @keyframes bs-ink-ripple {
307
- 0% {
308
- scale: 0.15;
309
- 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);
310
380
  }
311
- 45% {
312
- scale: 1;
313
- 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);
314
385
  }
315
- 100% {
316
- scale: 1;
386
+ }
387
+
388
+ @keyframes bs-ripple-fade {
389
+ from {
390
+ opacity: var(--bs-ripple-opacity, 0.9);
391
+ }
392
+ to {
317
393
  opacity: 0;
318
394
  }
319
395
  }
320
396
 
321
397
  @media (prefers-reduced-motion: reduce) {
322
- [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 {
323
400
  animation-duration: 1ms;
324
401
  }
325
402
  }
@@ -349,6 +426,7 @@ const accordionCss = `
349
426
  /* Triggers are quiet rows on the paper — no chrome of their own; the
350
427
  hairline rule carries the structure instead. */
351
428
  [data-scope="accordion"][data-part="item-trigger"] {
429
+ box-sizing: border-box;
352
430
  display: flex;
353
431
  align-items: center;
354
432
  justify-content: space-between;
@@ -471,6 +549,15 @@ const accordionCss = `
471
549
  }
472
550
  `;
473
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
474
561
  //#region src/styles/components/ai.ts
475
562
  const aiCss = `
476
563
  /* The conversation column: messages stack with section-band air, the
@@ -752,6 +839,78 @@ const aiCss = `
752
839
  text-decoration-color: var(--bs-color-border-strong);
753
840
  }
754
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
+
755
914
  /* Actions row: a quiet line of shared ghost buttons. */
756
915
  [data-scope="ai"][data-part="actions"] {
757
916
  display: flex;
@@ -762,10 +921,13 @@ const aiCss = `
762
921
  /* The prompt: one vessel — the shared field bared to the paper and
763
922
  self-growing on the machine's autoresize. It starts a single line tall,
764
923
  the submit seal riding that line; as the text grows the seal settles
765
- 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. */
766
927
  [data-scope="ai"][data-part="prompt"] {
767
928
  display: flex;
768
- align-items: flex-end;
929
+ flex-direction: column;
930
+ align-items: stretch;
769
931
  gap: var(--bs-space-2);
770
932
  padding: var(--bs-space-2);
771
933
  border: 1px solid var(--bs-color-border);
@@ -781,6 +943,30 @@ const aiCss = `
781
943
  box-shadow: var(--bs-focus-ring);
782
944
  }
783
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
+
784
970
  [data-scope="ai"][data-part="prompt"] [data-scope="field"][data-part="root"] {
785
971
  flex: 1;
786
972
  min-inline-size: 0;
@@ -809,10 +995,31 @@ const aiCss = `
809
995
  resize: none;
810
996
  }
811
997
 
998
+ /* The tool row beneath the writing: the model and its switches — the
999
+ seal closes the row at its far end. */
812
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"] {
813
1011
  display: flex;
814
1012
  align-items: center;
1013
+ gap: var(--bs-space-2);
815
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;
816
1023
  }
817
1024
 
818
1025
  [data-scope="ai"][data-part="prompt"] [data-scope="field"][data-part="textarea"]:focus,
@@ -1162,6 +1369,25 @@ const angleSliderCss = labelCss("angle-slider") + `
1162
1369
  }
1163
1370
  `;
1164
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
1165
1391
  //#region src/styles/components/avatar.ts
1166
1392
  const avatarCss = `
1167
1393
  [data-scope="avatar"][data-part="root"] {
@@ -1176,7 +1402,7 @@ const avatarCss = `
1176
1402
  justify-content: center;
1177
1403
  inline-size: var(--bs-avatar-size);
1178
1404
  block-size: var(--bs-avatar-size);
1179
- border-radius: calc(var(--bs-radius-lg) * 100);
1405
+ border-radius: var(--bs-radius-full);
1180
1406
  background: var(--bs-color-surface-inset);
1181
1407
  color: var(--bs-color-text-secondary);
1182
1408
  font-size: calc(var(--bs-font-size-sm) * 1.125);
@@ -1226,6 +1452,57 @@ const avatarGroupCss = `
1226
1452
  }
1227
1453
  `;
1228
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
1229
1506
  //#region src/styles/components/badge.ts
1230
1507
  const badgeCss = `
1231
1508
  /* A small seal of state: flat pigment fill, tracked caps-height ink. The
@@ -1481,7 +1758,7 @@ const buttonCss = `
1481
1758
  the pigment it carries. Each variant declares its fill/ink defaults,
1482
1759
  semantic tones re-point the pigment, and hover/focus ride the same
1483
1760
  variables. Ink is the solemn default. */
1484
- [data-scope="button"][data-part="root"] {
1761
+ :is([data-scope="button"][data-part="root"], [data-scope][data-part="trigger"][data-variant]) {
1485
1762
  --_pigment: var(--bs-color-primary);
1486
1763
  /* The deep register of the pigment for the subtle wash — the 600-step
1487
1764
  tone itself fails 4.5:1 on its own wash, so washed fills read from
@@ -1491,6 +1768,10 @@ const buttonCss = `
1491
1768
  --_fill-hover: transparent;
1492
1769
  --_ink: var(--bs-color-text-secondary);
1493
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"] {
1494
1775
  display: inline-flex;
1495
1776
  align-items: center;
1496
1777
  justify-content: center;
@@ -1502,7 +1783,7 @@ const buttonCss = `
1502
1783
  systems use 15-16px at this height, 24px at large). */
1503
1784
  padding: 0 var(--bs-padding-lg);
1504
1785
  border: 1px solid var(--_edge);
1505
- border-radius: var(--bs-radius-sm);
1786
+ border-radius: var(--bs-radius-control, var(--bs-radius-sm));
1506
1787
  background: var(--_fill);
1507
1788
  color: var(--_ink);
1508
1789
  font: inherit;
@@ -1554,19 +1835,19 @@ const buttonCss = `
1554
1835
  block-size: 1rem;
1555
1836
  }
1556
1837
 
1557
- [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"] {
1558
1839
  --_pigment: var(--bs-color-danger);
1559
1840
  --_ink-strong: var(--bs-color-danger-subtle-text);
1560
1841
  }
1561
- [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"] {
1562
1843
  --_pigment: var(--bs-color-success);
1563
1844
  --_ink-strong: var(--bs-color-success-subtle-text);
1564
1845
  }
1565
- [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"] {
1566
1847
  --_pigment: var(--bs-color-warning);
1567
1848
  --_ink-strong: var(--bs-color-warning-subtle-text);
1568
1849
  }
1569
- [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"] {
1570
1851
  --_pigment: var(--bs-color-info);
1571
1852
  --_ink-strong: var(--bs-color-info-subtle-text);
1572
1853
  }
@@ -1574,7 +1855,7 @@ const buttonCss = `
1574
1855
  /* Solid: the flat pigment fill, no lit edge at rest. On hover the ink
1575
1856
  bleeds — the pigment casts a small shadow of its own color, on a slower
1576
1857
  transition than the fill (light needs time). */
1577
- [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"] {
1578
1859
  /* The solid body follows --bs-color-primary-fill: full pigment on
1579
1860
  paper, kneaded toward black at night, and the theme keeps the ink
1580
1861
  on it legible in both registers. */
@@ -1582,63 +1863,64 @@ const buttonCss = `
1582
1863
  --_fill-hover: var(--bs-color-primary-fill-hover);
1583
1864
  --_ink: var(--bs-color-primary-text);
1584
1865
  /* On a filled body the wash must read against the fill, not sink into
1585
- it — the ink that blooms on a fill is the fill's own ink. */
1586
- --_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);
1587
1869
  box-shadow: none;
1588
1870
  }
1589
1871
 
1590
1872
  /* The fixed pigments knead by the theme's measure — a mid-tone body at
1591
1873
  night carries neither deep nor pale text past 4.5:1, so its ink turns
1592
1874
  bright paper. */
1593
- [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"] {
1594
1876
  --_fill: color-mix(in oklab, var(--_pigment) calc(100% - var(--bs-ink-knead, 0%)), black);
1595
1877
  --_fill-hover: color-mix(in oklab, var(--_pigment) calc(85% - var(--bs-ink-knead, 0%)), black);
1596
1878
  --_ink: var(--bs-color-ink-on-fill);
1597
1879
  }
1598
- [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"] {
1599
1881
  --_fill: color-mix(in oklab, var(--_pigment) calc(100% - var(--bs-ink-knead, 0%)), black);
1600
1882
  --_fill-hover: color-mix(in oklab, var(--_pigment) calc(85% - var(--bs-ink-knead, 0%)), black);
1601
1883
  --_ink: var(--bs-color-ink-on-fill);
1602
1884
  }
1603
- [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"] {
1604
1886
  --_fill: color-mix(in oklab, var(--_pigment) calc(100% - var(--bs-ink-knead, 0%)), black);
1605
1887
  --_fill-hover: color-mix(in oklab, var(--_pigment) calc(85% - var(--bs-ink-knead, 0%)), black);
1606
1888
  --_ink: var(--bs-color-ink-on-fill);
1607
1889
  }
1608
- [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"] {
1609
1891
  --_fill: color-mix(in oklab, var(--_pigment) calc(100% - var(--bs-ink-knead, 0%)), black);
1610
1892
  --_fill-hover: color-mix(in oklab, var(--_pigment) calc(85% - var(--bs-ink-knead, 0%)), black);
1611
1893
  --_ink: var(--bs-color-ink-on-fill);
1612
1894
  }
1613
1895
 
1614
- [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) {
1615
1897
  box-shadow: var(--bs-light-x) calc(1px * var(--bs-light-reach) + var(--bs-light-y))
1616
1898
  calc(3px * var(--bs-light-reach)) 0 color-mix(in oklab, var(--_pigment) 28%, transparent);
1617
1899
  }
1618
1900
 
1619
1901
  /* Outline: paper on a hairline, the hairline deepening on hover. */
1620
- [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"] {
1621
1903
  --_fill: var(--bs-color-surface-2);
1622
1904
  --_ink: var(--bs-color-text-primary);
1623
1905
  --_edge: var(--bs-color-border);
1624
1906
  }
1625
1907
 
1626
1908
  /* Ghost: bare ink that borrows the subtle surface under the cursor. */
1627
- [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"] {
1628
1910
  --_fill-hover: var(--bs-color-surface-0);
1629
1911
  box-shadow: none;
1630
1912
  }
1631
1913
 
1632
1914
  /* Subtle: a wash of the pigment with its deep register on top. */
1633
- [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"] {
1634
1916
  --_fill: color-mix(in oklab, var(--_pigment) 12%, transparent);
1635
1917
  --_fill-hover: color-mix(in oklab, var(--_pigment) 20%, transparent);
1636
1918
  --_ink: var(--_ink-strong);
1637
1919
  box-shadow: none;
1638
1920
  }
1639
1921
 
1640
- [data-scope="button"][data-part="root"][data-variant="outline"]:hover:not(:disabled),
1641
- [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 {
1642
1924
  --_edge: var(--bs-color-border-strong);
1643
1925
  }
1644
1926
 
@@ -1665,67 +1947,178 @@ const buttonCss = `
1665
1947
  box-shadow: none;
1666
1948
  }
1667
1949
 
1668
- /* As-child hosting: an overlay trigger that hosts a Button overwrites the
1669
- button's anatomy (data-scope reads the overlay family, not "button"),
1670
- so the rules above never match. The variant seals survive, though —
1671
- the ghost register re-asserts itself here for every overlay trigger at
1672
- once. The bare [data-scope] keeps specificity above any family's base,
1673
- and the state branches above its open/focus/disabled rules. */
1674
- [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] {
1675
1959
  display: inline-flex;
1676
1960
  align-items: center;
1677
1961
  justify-content: center;
1678
1962
  gap: var(--bs-space-2);
1679
1963
  flex: none;
1680
- block-size: var(--bs-control-height-sm);
1681
- padding: 0 var(--bs-padding-md);
1682
- border: none;
1683
- border-radius: var(--bs-radius-sm);
1684
- background: transparent;
1685
- 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);
1686
1971
  font: inherit;
1687
- font-size: var(--bs-font-size-sm);
1972
+ font-size: var(--bs-font-size-md);
1688
1973
  font-weight: var(--bs-font-weight-medium);
1689
1974
  letter-spacing: var(--bs-tracking-label);
1690
1975
  white-space: nowrap;
1691
1976
  text-decoration: none;
1692
1977
  cursor: pointer;
1693
1978
  user-select: none;
1694
- box-shadow: none;
1979
+ box-shadow: var(--bs-shadow-xs);
1695
1980
  transition:
1696
1981
  background-color var(--bs-duration-fast) var(--bs-ease-out),
1982
+ border-color var(--bs-duration-fast) var(--bs-ease-out),
1697
1983
  color var(--bs-duration-fast) var(--bs-ease-out),
1698
1984
  box-shadow calc(var(--bs-duration-fast) * 1.5) var(--bs-ease-out);
1699
1985
  }
1700
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
+
1701
1998
  /* Icon-only: the silhouette is the seal — width equals height. */
1702
- [data-scope][data-part="trigger"][data-variant="ghost"][data-square="true"] {
1999
+ [data-scope][data-part="trigger"][data-variant][data-square="true"] {
1703
2000
  inline-size: var(--bs-control-height-sm);
1704
2001
  padding-inline: 0;
1705
2002
  }
1706
2003
 
1707
- [data-scope][data-part="trigger"][data-variant="ghost"]:hover {
1708
- 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);
1709
2014
  }
1710
2015
 
1711
2016
  /* Open keeps the focus look: Zag hands focus to the overlay itself, so
1712
2017
  :focus-visible alone would drop the halo the moment it opens. */
1713
- [data-scope][data-part="trigger"][data-variant="ghost"]:focus-visible,
1714
- [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"] {
1715
2020
  outline: none;
2021
+ border-color: var(--bs-color-primary);
1716
2022
  box-shadow: var(--bs-focus-ring);
1717
2023
  }
1718
2024
 
1719
- [data-scope][data-part="trigger"][data-variant="ghost"]:active {
2025
+ [data-scope][data-part="trigger"][data-variant]:active:not(:disabled) {
1720
2026
  box-shadow: none;
1721
2027
  }
1722
2028
 
1723
- [data-scope][data-part="trigger"][data-variant="ghost"][data-disabled] {
1724
- background: var(--bs-color-surface-inset);
1725
- color: var(--bs-color-text-disabled);
1726
- 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;
1727
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;
1728
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;
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); }
1729
2122
  `;
1730
2123
  //#endregion
1731
2124
  //#region src/styles/components/calendar.ts
@@ -1973,7 +2366,7 @@ const carouselCss = `
1973
2366
  block-size: calc(var(--bs-space-2) + 2px);
1974
2367
  padding: 0;
1975
2368
  border: none;
1976
- border-radius: calc(var(--bs-radius-lg) * 100);
2369
+ border-radius: var(--bs-radius-full);
1977
2370
  background: var(--bs-color-border-strong);
1978
2371
  cursor: pointer;
1979
2372
  transition: background-color var(--bs-duration-fast) var(--bs-ease-out);
@@ -2001,20 +2394,174 @@ const carouselCss = `
2001
2394
  }
2002
2395
  `;
2003
2396
  //#endregion
2004
- //#region src/styles/components/checkbox.ts
2005
- const checkboxCss = labelCss("checkbox") + `
2006
- [data-scope="checkbox"][data-part="root"] {
2007
- 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;
2008
2410
  align-items: center;
2411
+ justify-content: space-between;
2009
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);
2010
2419
  color: var(--bs-color-text-primary);
2011
- 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);
2012
2425
  }
2013
2426
 
2014
- /* A group of checkboxes is one field: the rows stack, and the invalid
2015
- * pigment bleeds onto every row inside. */
2016
- [data-scope="checkbox"][data-part="group"] {
2017
- display: flex;
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"] {
2564
+ display: flex;
2018
2565
  flex-direction: column;
2019
2566
  gap: var(--bs-space-2);
2020
2567
  }
@@ -2030,8 +2577,8 @@ const checkboxCss = labelCss("checkbox") + `
2030
2577
  align-items: center;
2031
2578
  justify-content: center;
2032
2579
  flex-shrink: 0;
2033
- inline-size: var(--bs-part-size-md);
2034
- block-size: var(--bs-part-size-md);
2580
+ inline-size: var(--bs-part-size-sm);
2581
+ block-size: var(--bs-part-size-sm);
2035
2582
  border: 1px solid var(--bs-color-border);
2036
2583
  border-radius: var(--bs-radius-sm);
2037
2584
  background: var(--bs-color-surface-2);
@@ -2091,8 +2638,8 @@ const checkboxCss = labelCss("checkbox") + `
2091
2638
  }
2092
2639
 
2093
2640
  [data-scope="checkbox"][data-part="indicator"] svg {
2094
- inline-size: calc(var(--bs-part-size-md) * 0.7);
2095
- 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);
2096
2643
  }
2097
2644
 
2098
2645
  [data-scope="checkbox"][data-part="root"]:has([data-disabled]) {
@@ -2104,6 +2651,24 @@ const checkboxCss = labelCss("checkbox") + `
2104
2651
  }
2105
2652
  `;
2106
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
2107
2672
  //#region src/styles/components/chip.ts
2108
2673
  const chipCss = `
2109
2674
  /* A counting coin: one small number, round as a seal impression. It sits
@@ -2314,6 +2879,7 @@ const collapsibleCss = `
2314
2879
  /* A collapsible stands alone, so its trigger is a full control: paper
2315
2880
  surface, one hairline, resting on xs. */
2316
2881
  [data-scope="collapsible"][data-part="trigger"] {
2882
+ box-sizing: border-box;
2317
2883
  display: flex;
2318
2884
  align-items: center;
2319
2885
  justify-content: space-between;
@@ -2453,7 +3019,6 @@ const colorPickerCss = labelCss("color-picker") + positionerCss("color-picker")
2453
3019
  display: flex;
2454
3020
  flex-direction: column;
2455
3021
  gap: var(--bs-space-2);
2456
- max-inline-size: 17rem;
2457
3022
  }
2458
3023
 
2459
3024
  [data-scope="color-picker"][data-part="root"][data-disabled] {
@@ -2522,7 +3087,7 @@ const colorPickerCss = labelCss("color-picker") + positionerCss("color-picker")
2522
3087
  [data-scope="color-picker"][data-part="swatch"] {
2523
3088
  position: absolute;
2524
3089
  inset: var(--bs-space-1);
2525
- border-radius: calc(var(--bs-radius-sm) - 2px);
3090
+ border-radius: var(--bs-radius-xs);
2526
3091
  box-shadow: inset 0 0 0 1px var(--bs-color-border);
2527
3092
  }
2528
3093
 
@@ -2532,7 +3097,7 @@ const colorPickerCss = labelCss("color-picker") + positionerCss("color-picker")
2532
3097
  [data-scope="color-picker"][data-part="transparency-grid"] {
2533
3098
  position: absolute;
2534
3099
  inset: var(--bs-space-1);
2535
- border-radius: calc(var(--bs-radius-sm) - 2px);
3100
+ border-radius: var(--bs-radius-xs);
2536
3101
  }
2537
3102
 
2538
3103
  /* The geometry vars live on the content: the popup teleports to body, out
@@ -2567,7 +3132,7 @@ const colorPickerCss = labelCss("color-picker") + positionerCss("color-picker")
2567
3132
  position: absolute;
2568
3133
  inline-size: var(--bs-color-picker-thumb);
2569
3134
  block-size: var(--bs-color-picker-thumb);
2570
- border-radius: 9999px;
3135
+ border-radius: var(--bs-radius-full);
2571
3136
  outline: none;
2572
3137
  transform: translate(-50%, -50%);
2573
3138
  box-shadow:
@@ -2599,7 +3164,7 @@ const colorPickerCss = labelCss("color-picker") + positionerCss("color-picker")
2599
3164
  position: absolute;
2600
3165
  inline-size: var(--bs-color-picker-thumb);
2601
3166
  block-size: var(--bs-color-picker-thumb);
2602
- border-radius: 9999px;
3167
+ border-radius: var(--bs-radius-full);
2603
3168
  outline: none;
2604
3169
  transform: translate(-50%, -50%);
2605
3170
  box-shadow:
@@ -3061,107 +3626,358 @@ const comboboxCss = labelCss("combobox") + positionerCss("combobox") + popupCont
3061
3626
  }
3062
3627
  `;
3063
3628
  //#endregion
3064
- //#region src/styles/components/date-input.ts
3065
- const dateInputCss = labelCss("date-input") + `
3066
- [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));
3067
3637
  display: flex;
3068
- flex-direction: column;
3069
- 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);
3070
3641
  }
3071
3642
 
3072
- [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%);
3073
3649
  display: flex;
3074
- align-items: center;
3075
- 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);
3076
3660
  }
3077
3661
 
3078
- /* The segment group IS the field — the same border + surface + focus halo
3079
- recipe as every input, no shadow. */
3080
- [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"] {
3081
3670
  box-sizing: border-box;
3082
- flex: 1;
3083
- display: flex;
3084
- align-items: center;
3085
- min-width: 0;
3086
- block-size: var(--bs-control-height-md);
3671
+ inline-size: 100%;
3672
+ block-size: var(--bs-control-height-lg);
3087
3673
  padding: 0 var(--bs-padding-md);
3088
- border: 1px solid var(--bs-color-border);
3089
- border-radius: var(--bs-radius-sm);
3090
- 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;
3091
3678
  color: var(--bs-color-text-primary);
3092
3679
  font: inherit;
3093
3680
  font-size: var(--bs-font-size-md);
3094
- cursor: text;
3095
- transition: border-color var(--bs-duration-fast) var(--bs-ease-out);
3096
3681
  }
3097
3682
 
3098
- [data-scope="date-input"][data-part="segment-group"]:hover:not([data-disabled], [data-readonly]) {
3099
- border-color: var(--bs-color-border-strong);
3683
+ [data-scope="command"][data-part="input"]::placeholder {
3684
+ color: var(--bs-color-text-tertiary);
3100
3685
  }
3101
3686
 
3102
- [data-scope="date-input"][data-part="segment-group"][data-focus] {
3103
- border-color: var(--bs-color-primary);
3104
- 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);
3105
3691
  }
3106
3692
 
3107
- [data-scope="date-input"][data-part="segment-group"][data-invalid] {
3108
- 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;
3109
3704
  }
3110
3705
 
3111
- [data-scope="date-input"][data-part="segment-group"][data-invalid][data-focus] {
3112
- box-shadow: var(--bs-focus-ring);
3706
+ [data-scope="command"][data-part="list"][hidden] {
3707
+ display: none;
3113
3708
  }
3114
3709
 
3115
- [data-scope="date-input"][data-part="segment-group"][data-disabled] {
3116
- border-color: var(--bs-color-border);
3117
- background: var(--bs-color-surface-inset);
3118
- color: var(--bs-color-text-disabled);
3119
- cursor: not-allowed;
3710
+ [data-scope="command"][data-part="group"] {
3711
+ display: flex;
3712
+ flex-direction: column;
3713
+ gap: var(--bs-space-1);
3120
3714
  }
3121
3715
 
3122
- [data-scope="date-input"][data-part="segment-group"][data-readonly] {
3123
- 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);
3124
3718
  }
3125
3719
 
3126
- [data-scope="date-input"][data-part="segment"] {
3127
- display: inline-flex;
3128
- align-items: center;
3129
- justify-content: center;
3130
- min-width: 2ch;
3131
- padding: 0 2px;
3132
- border-radius: var(--bs-radius-xs);
3133
- font-variant-numeric: tabular-nums;
3134
- caret-color: transparent;
3135
- 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;
3136
3729
  }
3137
3730
 
3138
- /* The focused segment takes the flat primary fill ink where the eye is. */
3139
- [data-scope="date-input"][data-part="segment"]:focus {
3140
- background: var(--bs-color-primary);
3141
- 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);
3142
3745
  }
3143
3746
 
3144
- [data-scope="date-input"][data-part="segment"][data-placeholder-shown] {
3145
- color: var(--bs-color-text-tertiary);
3747
+ [data-scope="command"][data-part="item"][data-highlighted] {
3748
+ background: var(--bs-color-surface-0);
3146
3749
  }
3147
3750
 
3148
- [data-scope="date-input"][data-part="segment"]:focus[data-placeholder-shown] {
3149
- 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;
3150
3754
  }
3151
3755
 
3152
- /* Separators are type, not controls — never take the ink. */
3153
- [data-scope="date-input"][data-part="segment"][data-type="literal"] {
3154
- min-width: 0;
3155
- padding: 0;
3156
- color: var(--bs-color-text-tertiary);
3157
- 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;
3158
3761
  }
3159
3762
 
3160
- /* The hidden input still hosts form autofillvisually gone, structurally
3161
- present. */
3162
- [data-scope="date-input"][data-part="hidden-input"] {
3163
- position: absolute;
3164
- inline-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;
3165
3981
  block-size: 1px;
3166
3982
  opacity: 0;
3167
3983
  pointer-events: none;
@@ -3431,6 +4247,10 @@ const datePickerCss = labelCss("date-picker") + positionerCss("date-picker") + p
3431
4247
  }
3432
4248
 
3433
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;
3434
4254
  inline-size: 100%;
3435
4255
  border-collapse: separate;
3436
4256
  border-spacing: 0;
@@ -3541,6 +4361,52 @@ const datePickerCss = labelCss("date-picker") + positionerCss("date-picker") + p
3541
4361
  }
3542
4362
  `;
3543
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
3544
4410
  //#region src/styles/components/dialog.ts
3545
4411
  const dialogCss = `
3546
4412
  [data-scope="dialog"][data-part="trigger"] {
@@ -3809,6 +4675,31 @@ const drawerCss = positionerCss("drawer") + `
3809
4675
  background: var(--bs-color-text-tertiary);
3810
4676
  }
3811
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
+
3812
4703
  /* The trigger is a seal-cut control: paper on a hairline, deepening on
3813
4704
  hover, the shadow letting go under the press. */
3814
4705
  [data-scope="drawer"][data-part="trigger"] {
@@ -3949,23 +4840,67 @@ const drawerCss = positionerCss("drawer") + `
3949
4840
  }
3950
4841
  `;
3951
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
3952
4871
  //#region src/styles/components/editable.ts
3953
4872
  const editableCss = labelCss("editable") + `
3954
4873
  [data-scope="editable"][data-part="root"] {
3955
- display: flex;
3956
- flex-direction: column;
4874
+ display: grid;
4875
+ grid-template-columns: 1fr auto;
4876
+ grid-template-areas:
4877
+ "label label"
4878
+ "area control";
3957
4879
  gap: var(--bs-space-2);
4880
+ align-items: center;
3958
4881
  inline-size: 100%;
3959
4882
  }
3960
4883
 
4884
+ [data-scope="editable"][data-part="label"] {
4885
+ grid-area: label;
4886
+ }
4887
+
3961
4888
  [data-scope="editable"][data-part="root"][data-disabled] {
3962
4889
  color: var(--bs-color-text-disabled);
3963
4890
  }
3964
4891
 
4892
+ /* The text and its triggers share one row: the field stretches, the
4893
+ seals sit at its right shoulder. */
3965
4894
  [data-scope="editable"][data-part="area"] {
4895
+ grid-area: area;
3966
4896
  position: relative;
3967
4897
  display: flex;
3968
4898
  align-items: center;
4899
+ min-inline-size: 0;
4900
+ }
4901
+
4902
+ [data-scope="editable"][data-part="control"] {
4903
+ grid-area: control;
3969
4904
  }
3970
4905
 
3971
4906
  /* Preview and input share one geometry so the swap never shifts the page;
@@ -4102,12 +5037,37 @@ const editableCss = labelCss("editable") + `
4102
5037
  }
4103
5038
  `;
4104
5039
  //#endregion
4105
- //#region src/styles/components/empty.ts
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
5064
+ //#region src/styles/components/empty.ts
4106
5065
  const emptyCss = `
4107
5066
  /* An empty state: the page holds its breath — a quiet mark, one line of
4108
5067
  ink, and room for the next action. Centered, generous with whitespace. */
4109
5068
  [data-scope="empty"][data-part="root"] {
4110
5069
  /* Full width is the component's own property, not the stage's stretch. */
5070
+ box-sizing: border-box;
4111
5071
  inline-size: 100%;
4112
5072
  display: flex;
4113
5073
  flex-direction: column;
@@ -4154,17 +5114,10 @@ const emptyCss = `
4154
5114
  `;
4155
5115
  //#endregion
4156
5116
  //#region src/styles/components/field.ts
4157
- const fieldCss = labelCss("field") + `
4158
- [data-scope="field"][data-part="root"] {
4159
- display: flex;
4160
- flex-direction: column;
4161
- align-items: start;
4162
- gap: var(--bs-space-2);
4163
- inline-size: 100%;
4164
- }
4165
-
4166
- /* The control itself: inputs rely on border + surface + focus halo, never
4167
- 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 = `
4168
5121
  [data-scope="field"][data-part="input"],
4169
5122
  [data-scope="field"][data-part="textarea"],
4170
5123
  [data-scope="field"][data-part="select"] {
@@ -4188,6 +5141,20 @@ const fieldCss = labelCss("field") + `
4188
5141
  block-size: var(--bs-control-height-md);
4189
5142
  }
4190
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
+
4191
5158
  [data-scope="field"][data-part="textarea"] {
4192
5159
  min-block-size: calc(var(--bs-control-height-md) * 2 + var(--bs-space-2));
4193
5160
  padding-block: var(--bs-space-2);
@@ -4228,7 +5195,16 @@ const fieldCss = labelCss("field") + `
4228
5195
  color: var(--bs-color-text-disabled);
4229
5196
  cursor: not-allowed;
4230
5197
  }
4231
-
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 + `
4232
5208
  /* The required mark whispers, never shouts: a quiet pigment point after
4233
5209
  the label. */
4234
5210
  [data-scope="field"][data-part="required-indicator"] {
@@ -4251,6 +5227,49 @@ const fieldCss = labelCss("field") + `
4251
5227
  [data-scope="field"][data-part="root"][data-disabled] {
4252
5228
  color: var(--bs-color-text-disabled);
4253
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
+ }
4254
5273
  `;
4255
5274
  //#endregion
4256
5275
  //#region src/styles/components/fieldset.ts
@@ -4313,7 +5332,6 @@ const fileUploadCss = labelCss("file-upload") + `
4313
5332
  align-items: flex-start;
4314
5333
  gap: var(--bs-space-3);
4315
5334
  inline-size: 100%;
4316
- max-inline-size: 24rem;
4317
5335
  }
4318
5336
 
4319
5337
  /* The choose-files button rides the control recipe: paper fill, one
@@ -4556,6 +5574,136 @@ const fileUploadCss = labelCss("file-upload") + `
4556
5574
  }
4557
5575
  `;
4558
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
4559
5707
  //#region src/styles/components/floating-panel.ts
4560
5708
  const floatingPanelCss = positionerCss("floating-panel") + popupContentCss("floating-panel", "20rem") + `
4561
5709
  [data-scope="floating-panel"][data-part="positioner"] {
@@ -4809,6 +5957,43 @@ const floatingPanelCss = positionerCss("floating-panel") + popupContentCss("floa
4809
5957
  }
4810
5958
  `;
4811
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
4812
5997
  //#region src/styles/components/highlight.ts
4813
5998
  const highlightCss = `
4814
5999
  /* Search hits are strokes of pigment on the page, not neon. The highlight
@@ -4865,6 +6050,136 @@ const hoverCardCss = popupContentCss("hover-card") + positionerCss("hover-card")
4865
6050
  }
4866
6051
  `;
4867
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
4868
6183
  //#region src/styles/components/image-cropper.ts
4869
6184
  const imageCropperCss = labelCss("image-cropper") + `
4870
6185
  [data-scope="image-cropper"][data-part="root"] {
@@ -5028,36 +6343,228 @@ const imageCropperCss = labelCss("image-cropper") + `
5028
6343
  }
5029
6344
  `;
5030
6345
  //#endregion
5031
- //#region src/styles/components/tree-view.ts
5032
- const treeViewCss = labelCss("tree-view") + `
5033
- [data-scope="tree-view"][data-part="root"] {
5034
- /* The indent system: depth comes from the machine as --depth per node;
5035
- every offset below derives from these three knobs. */
5036
- --bs-tree-indent: 1rem;
5037
- --bs-tree-icon: 1rem;
5038
- --bs-tree-row-block: var(--bs-space-2);
5039
- /* Full width is the component's own property, not the stage's stretch. */
5040
- inline-size: 100%;
5041
- display: flex;
5042
- flex-direction: column;
5043
- 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);
5044
6361
  }
5045
6362
 
5046
- [data-scope="tree-view"][data-part="tree"] {
5047
- display: flex;
5048
- flex-direction: column;
5049
- color: var(--bs-color-text-primary);
5050
- 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;
5051
6373
  }
5052
6374
 
5053
- /* Rows are quiet: no hairlines, no shadows — selection and hover are pure
5054
- light on the paper. */
5055
- [data-scope="tree-view"][data-part="branch-control"],
5056
- [data-scope="tree-view"][data-part="item"] {
6375
+ [data-scope="dialog"][data-part="content"].bs-image-viewer-content {
5057
6376
  position: relative;
5058
6377
  display: flex;
5059
- align-items: center;
5060
- gap: var(--bs-space-2);
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);
5061
6568
  /* No page reset may be assumed: 100% must count the row's own padding. */
5062
6569
  box-sizing: border-box;
5063
6570
  inline-size: 100%;
@@ -5291,6 +6798,154 @@ const kbdCss = `
5291
6798
  }
5292
6799
  `;
5293
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
5294
6949
  //#region src/styles/components/link.ts
5295
6950
  const linkCss = `
5296
6951
  /* A link is ink in the accent's voice: quiet at rest, deepening under
@@ -5327,6 +6982,87 @@ const linkCss = `
5327
6982
  }
5328
6983
  `;
5329
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
5330
7066
  //#region src/styles/components/listbox.ts
5331
7067
  const listboxCss = labelCss("listbox") + popupContentCss("listbox", "16rem") + `
5332
7068
  [data-scope="listbox"][data-part="root"] {
@@ -5595,6 +7331,116 @@ const marqueeCss = `
5595
7331
  }
5596
7332
  `;
5597
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
5598
7444
  //#region src/styles/components/menu.ts
5599
7445
  const menuCss = positionerCss("menu") + popupContentCss("menu", "12rem") + `
5600
7446
  [data-scope="menu"][data-part="content"] {
@@ -5796,6 +7642,66 @@ const menuCss = positionerCss("menu") + popupContentCss("menu", "12rem") + `
5796
7642
  }
5797
7643
  `;
5798
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
5799
7705
  //#region src/styles/components/meter.ts
5800
7706
  const meterCss = `
5801
7707
  /* A measure in the world, not a task in flight: how much of the toner
@@ -5839,70 +7745,17 @@ const meterCss = `
5839
7745
  overflow: hidden;
5840
7746
  block-size: var(--bs-space-1);
5841
7747
  border-radius: var(--bs-radius-sm);
5842
- background: var(--bs-color-surface-0);
5843
- }
5844
-
5845
- [data-scope="meter"][data-part="range"] {
5846
- /* The fill reads the root's declared share of the scale. */
5847
- display: block;
5848
- inline-size: var(--_percent, 0%);
5849
- block-size: 100%;
5850
- border-radius: inherit;
5851
- background: var(--_pigment);
5852
- transition: inline-size var(--bs-duration-slow) var(--bs-ease-out);
5853
- }
5854
- `;
5855
- //#endregion
5856
- //#region src/styles/components/typography.ts
5857
- const typographyCss = `
5858
- /* The typographic voices, named so prose can ask for one: display and
5859
- heading ride the song-serif, the rest ride the hei. Nothing here is
5860
- decorative — hierarchy is size, weight, and space. */
5861
- [data-scope="typography"][data-part="display"] {
5862
- margin: 0;
5863
- color: var(--bs-color-text-primary);
5864
- font-family: var(--bs-font-serif);
5865
- font-size: var(--bs-font-size-4xl);
5866
- font-weight: var(--bs-font-weight-semibold);
5867
- line-height: var(--bs-line-height-tight);
5868
- }
5869
-
5870
- [data-scope="typography"][data-part="heading"] {
5871
- margin: 0;
5872
- color: var(--bs-color-text-primary);
5873
- font-family: var(--bs-font-serif);
5874
- font-size: var(--bs-font-size-2xl);
5875
- font-weight: var(--bs-font-weight-semibold);
5876
- line-height: var(--bs-line-height-snug);
5877
- }
5878
-
5879
- [data-scope="typography"][data-part="lead"] {
5880
- margin: 0;
5881
- color: var(--bs-color-text-secondary);
5882
- font-size: var(--bs-font-size-lg);
5883
- line-height: var(--bs-line-height-relaxed);
5884
- }
5885
-
5886
- [data-scope="typography"][data-part="body"] {
5887
- margin: 0;
5888
- color: var(--bs-color-text-secondary);
5889
- font-size: var(--bs-font-size-base);
5890
- line-height: var(--bs-line-height-relaxed);
5891
- }
5892
-
5893
- [data-scope="typography"][data-part="muted"] {
5894
- margin: 0;
5895
- color: var(--bs-color-text-tertiary);
5896
- font-size: var(--bs-font-size-sm);
5897
- line-height: var(--bs-line-height-relaxed);
7748
+ background: var(--bs-color-surface-0);
5898
7749
  }
5899
7750
 
5900
- [data-scope="typography"][data-part="label"] {
5901
- margin: 0;
5902
- color: var(--bs-color-text-secondary);
5903
- font-size: var(--bs-font-size-sm);
5904
- font-weight: var(--bs-font-weight-medium);
5905
- 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);
5906
7759
  }
5907
7760
  `;
5908
7761
  //#endregion
@@ -6581,6 +8434,31 @@ const pinInputCss = labelCss("pin-input") + `
6581
8434
  }
6582
8435
  `;
6583
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
6584
8462
  //#region src/styles/components/popover.ts
6585
8463
  const popoverCss = positionerCss("popover") + popupContentCss("popover", "20rem") + `
6586
8464
  [data-scope="popover"][data-part="content"] {
@@ -6736,14 +8614,16 @@ const progressCss = labelCss("progress") + `
6736
8614
  text-align: end;
6737
8615
  }
6738
8616
 
6739
- /* The track is a hairline groove pressed into the paper — ink will run
6740
- 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. */
6741
8621
  [data-scope="progress"][data-part="track"] {
6742
8622
  grid-column: 1 / -1;
6743
8623
  overflow: hidden;
6744
8624
  block-size: var(--bs-space-1);
6745
8625
  border-radius: var(--bs-radius-sm);
6746
- background: var(--bs-color-surface-0);
8626
+ background: color-mix(in oklab, var(--bs-color-surface-0) 96%, black);
6747
8627
  }
6748
8628
 
6749
8629
  [data-scope="progress"][data-part="track"][data-orientation="vertical"] {
@@ -6810,7 +8690,7 @@ const progressCss = labelCss("progress") + `
6810
8690
  }
6811
8691
 
6812
8692
  [data-scope="progress"][data-part="circle-track"] {
6813
- stroke: var(--bs-color-surface-0);
8693
+ stroke: color-mix(in oklab, var(--bs-color-surface-0) 92%, black);
6814
8694
  }
6815
8695
 
6816
8696
  [data-scope="progress"][data-part="circle-range"] {
@@ -6839,6 +8719,83 @@ const progressCss = labelCss("progress") + `
6839
8719
  }
6840
8720
  `;
6841
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
6842
8799
  //#region src/styles/components/qr-code.ts
6843
8800
  const qrCodeCss = `
6844
8801
  [data-scope="qr-code"][data-part="root"] {
@@ -6960,8 +8917,8 @@ const radioGroupCss = labelCss("radio-group") + `
6960
8917
  align-items: center;
6961
8918
  justify-content: center;
6962
8919
  flex-shrink: 0;
6963
- inline-size: var(--bs-part-size-md);
6964
- block-size: var(--bs-part-size-md);
8920
+ inline-size: var(--bs-part-size-sm);
8921
+ block-size: var(--bs-part-size-sm);
6965
8922
  border: 1px solid var(--bs-color-border);
6966
8923
  border-radius: var(--bs-radius-full);
6967
8924
  background: var(--bs-color-surface-2);
@@ -6990,8 +8947,8 @@ const radioGroupCss = labelCss("radio-group") + `
6990
8947
 
6991
8948
  [data-scope="radio-group"][data-part="item-control"][data-state="checked"]::before {
6992
8949
  content: "";
6993
- inline-size: calc(var(--bs-part-size-md) * 0.3);
6994
- 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);
6995
8952
  border-radius: var(--bs-radius-full);
6996
8953
  background: var(--bs-color-primary-text);
6997
8954
  }
@@ -7020,8 +8977,8 @@ const radioGroupCss = labelCss("radio-group") + `
7020
8977
  the spring — position comes from the machine. */
7021
8978
  [data-scope="radio-group"][data-part="indicator"] {
7022
8979
  position: absolute;
7023
- inline-size: var(--bs-part-size-md);
7024
- block-size: var(--bs-part-size-md);
8980
+ inline-size: var(--bs-part-size-sm);
8981
+ block-size: var(--bs-part-size-sm);
7025
8982
  border-radius: var(--bs-radius-full);
7026
8983
  background: var(--bs-color-primary);
7027
8984
  transition:
@@ -7333,6 +9290,7 @@ const selectCss = labelCss("select") + positionerCss("select") + popupContentCss
7333
9290
  /* The control is the field: the recipe's border, surface and focus halo
7334
9291
  ride on it, with the trigger and its passengers sitting inside. */
7335
9292
  [data-scope="select"][data-part="control"] {
9293
+ box-sizing: border-box;
7336
9294
  display: flex;
7337
9295
  align-items: center;
7338
9296
  gap: var(--bs-space-1);
@@ -7715,104 +9673,6 @@ const skeletonCss = `
7715
9673
  }
7716
9674
  `;
7717
9675
  //#endregion
7718
- //#region src/styles/components/spinner.ts
7719
- const spinnerCss = `
7720
- /* A wheel of waiting: one arc of ink turning about its center. Quiet by
7721
- default — it reports progress without claiming attention. */
7722
- [data-scope="spinner"][data-part="root"] {
7723
- display: inline-flex;
7724
- flex: none;
7725
- color: var(--bs-color-text-tertiary);
7726
- animation: bs-spinner-turn 0.9s linear infinite;
7727
- }
7728
-
7729
- [data-scope="spinner"][data-part="root"][data-size="sm"] {
7730
- inline-size: var(--bs-part-size-sm);
7731
- block-size: var(--bs-part-size-sm);
7732
- }
7733
- [data-scope="spinner"][data-part="root"][data-size="md"] {
7734
- inline-size: var(--bs-part-size-md);
7735
- block-size: var(--bs-part-size-md);
7736
- }
7737
- [data-scope="spinner"][data-part="root"][data-size="lg"] {
7738
- inline-size: var(--bs-part-size-lg);
7739
- block-size: var(--bs-part-size-lg);
7740
- }
7741
-
7742
- [data-scope="spinner"][data-part="root"] svg {
7743
- inline-size: 100%;
7744
- block-size: 100%;
7745
- }
7746
-
7747
- @keyframes bs-spinner-turn {
7748
- to {
7749
- transform: rotate(1turn);
7750
- }
7751
- }
7752
-
7753
- @media (prefers-reduced-motion: reduce) {
7754
- [data-scope="spinner"][data-part="root"] {
7755
- animation-duration: 1ms;
7756
- animation-iteration-count: 1;
7757
- }
7758
- }
7759
- `;
7760
- //#endregion
7761
- //#region src/styles/components/stat.ts
7762
- const statCss = `
7763
- /* One figure on the page: the label whispers what it is, the value
7764
- states it plainly in tabular figures, the delta reads the direction
7765
- in the fixed semantic pigments. */
7766
- [data-scope="stat"][data-part="root"] {
7767
- display: flex;
7768
- flex-direction: column;
7769
- gap: var(--bs-space-1);
7770
- }
7771
-
7772
- [data-scope="stat"][data-part="label"] {
7773
- margin: 0;
7774
- color: var(--bs-color-text-secondary);
7775
- font-size: var(--bs-font-size-sm);
7776
- font-weight: var(--bs-font-weight-medium);
7777
- letter-spacing: var(--bs-tracking-label);
7778
- }
7779
-
7780
- /* The value carries the ink: large, semibold, tabular so figures
7781
- align across a row of stats. */
7782
- [data-scope="stat"][data-part="value"] {
7783
- color: var(--bs-color-text-primary);
7784
- font-size: var(--bs-font-size-3xl);
7785
- font-weight: var(--bs-font-weight-semibold);
7786
- font-variant-numeric: tabular-nums;
7787
- line-height: var(--bs-line-height-tight);
7788
- }
7789
-
7790
- [data-scope="stat"][data-part="delta"] {
7791
- display: inline-flex;
7792
- align-items: center;
7793
- gap: var(--bs-space-1);
7794
- color: var(--bs-color-text-tertiary);
7795
- font-size: var(--bs-font-size-sm);
7796
- font-weight: var(--bs-font-weight-medium);
7797
- font-variant-numeric: tabular-nums;
7798
- }
7799
-
7800
- [data-scope="stat"][data-part="delta"][data-direction="up"] {
7801
- color: var(--bs-color-success);
7802
- }
7803
-
7804
- [data-scope="stat"][data-part="delta"][data-direction="down"] {
7805
- color: var(--bs-color-danger);
7806
- }
7807
-
7808
- [data-scope="stat"][data-part="description"] {
7809
- margin: 0;
7810
- color: var(--bs-color-text-tertiary);
7811
- font-size: var(--bs-font-size-sm);
7812
- line-height: var(--bs-line-height-relaxed);
7813
- }
7814
- `;
7815
- //#endregion
7816
9676
  //#region src/styles/components/slider.ts
7817
9677
  const sliderCss = labelCss("slider") + `
7818
9678
  [data-scope="slider"][data-part="root"] {
@@ -7881,15 +9741,17 @@ const sliderCss = labelCss("slider") + `
7881
9741
  }
7882
9742
 
7883
9743
  /* The thumb is a paper seal riding the ink: surface fill, primary hairline,
7884
- focus turns the ring primary — never a background change. Its size is a
7885
- 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. */
7886
9748
  [data-scope="slider"][data-part="thumb"] {
7887
9749
  box-sizing: border-box;
7888
9750
  display: flex;
7889
9751
  align-items: center;
7890
9752
  justify-content: center;
7891
- inline-size: var(--bs-part-size-md);
7892
- 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);
7893
9755
  border: 1px solid var(--bs-color-primary);
7894
9756
  border-radius: var(--bs-radius-full);
7895
9757
  background: var(--bs-color-surface-2);
@@ -7971,18 +9833,107 @@ const sliderCss = labelCss("slider") + `
7971
9833
  translate: -50% 0;
7972
9834
  }
7973
9835
 
7974
- [data-scope="slider"][data-part="marker"][data-state="at-value"],
7975
- [data-scope="slider"][data-part="marker"][data-state="under-value"] {
7976
- 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;
7977
9908
  }
7978
9909
 
7979
- [data-scope="slider"][data-part="marker"][data-state="at-value"]::before,
7980
- [data-scope="slider"][data-part="marker"][data-state="under-value"]::before {
7981
- 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);
7982
9919
  }
7983
9920
 
7984
- [data-scope="slider"][data-part="marker"][data-disabled] {
7985
- 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);
7986
9937
  }
7987
9938
  `;
7988
9939
  //#endregion
@@ -8121,6 +10072,77 @@ const splitterCss = `
8121
10072
  }
8122
10073
  `;
8123
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
8124
10146
  //#region src/styles/components/steps.ts
8125
10147
  const stepsCss = `
8126
10148
  [data-scope="steps"][data-part="root"] {
@@ -8192,7 +10214,7 @@ const stepsCss = `
8192
10214
  place-items: center;
8193
10215
  inline-size: var(--bs-control-height-sm);
8194
10216
  block-size: var(--bs-control-height-sm);
8195
- border-radius: calc(var(--bs-radius-lg) * 100);
10217
+ border-radius: var(--bs-radius-full);
8196
10218
  background: var(--bs-color-surface-2);
8197
10219
  box-shadow: inset 0 0 0 1px var(--bs-color-border);
8198
10220
  color: var(--bs-color-text-secondary);
@@ -8364,10 +10386,12 @@ const switchCss = labelCss("switch") + `
8364
10386
  }
8365
10387
 
8366
10388
  /* The track geometry: thumb travel is derived here so the knob lands
8367
- 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. */
8368
10392
  [data-scope="switch"][data-part="control"] {
8369
10393
  --bs-switch-track: calc(var(--bs-switch-thumb) * 2 + var(--bs-switch-inset) * 2);
8370
- --bs-switch-thumb: var(--bs-part-size-md);
10394
+ --bs-switch-thumb: var(--bs-part-size-sm);
8371
10395
  --bs-switch-inset: var(--bs-space-1);
8372
10396
  display: inline-flex;
8373
10397
  align-items: center;
@@ -9821,6 +11845,171 @@ const tourCss = popupContentCss("tour", "0") + `
9821
11845
  }
9822
11846
  `;
9823
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
9824
12013
  //#region src/styles/components/index.ts
9825
12014
  /** Component style registry — every component contributes its stylesheet
9826
12015
  * here, keyed by the name wrappers pass to `injectComponentStyle`. */
@@ -9836,9 +12025,11 @@ const componentStyles = {
9836
12025
  breadcrumb: breadcrumbCss,
9837
12026
  button: buttonCss,
9838
12027
  calendar: calendarCss + datePickerCss.replaceAll("data-scope=\"date-picker\"", "data-scope=\"calendar\""),
12028
+ "cascade-select": cascadeSelectCss,
9839
12029
  card: cardCss,
9840
12030
  carousel: carouselCss,
9841
12031
  checkbox: checkboxCss,
12032
+ "checkbox-group": checkboxGroupCss,
9842
12033
  chip: chipCss,
9843
12034
  clipboard: clipboardCss,
9844
12035
  collapsible: collapsibleCss,
@@ -9846,6 +12037,7 @@ const componentStyles = {
9846
12037
  combobox: comboboxCss,
9847
12038
  "date-input": dateInputCss,
9848
12039
  "date-picker": datePickerCss,
12040
+ descriptions: descriptionsCss,
9849
12041
  dialog: dialogCss,
9850
12042
  drawer: drawerCss,
9851
12043
  editable: editableCss,
@@ -9854,9 +12046,11 @@ const componentStyles = {
9854
12046
  fieldset: fieldsetCss,
9855
12047
  "file-upload": fileUploadCss,
9856
12048
  "floating-panel": floatingPanelCss,
12049
+ form: formCss,
9857
12050
  highlight: highlightCss,
9858
12051
  "hover-card": hoverCardCss,
9859
12052
  "image-cropper": imageCropperCss,
12053
+ input: fieldControlCss.replaceAll("data-scope=\"field\"", "data-scope=\"input\"").replaceAll("data-part=\"input\"", "data-part=\"root\""),
9860
12054
  "json-tree-view": jsonTreeViewCss,
9861
12055
  kbd: kbdCss,
9862
12056
  link: linkCss,
@@ -9864,13 +12058,13 @@ const componentStyles = {
9864
12058
  marquee: marqueeCss,
9865
12059
  menu: menuCss,
9866
12060
  meter: meterCss,
9867
- typography: typographyCss,
9868
12061
  "navigation-menu": navigationMenuCss,
9869
12062
  "number-input": numberInputCss,
9870
12063
  "page-header": pageHeaderCss,
9871
12064
  pagination: paginationCss,
9872
12065
  "password-input": passwordInputCss,
9873
12066
  "pin-input": pinInputCss,
12067
+ popconfirm: popconfirmCss,
9874
12068
  popover: popoverCss,
9875
12069
  progress: progressCss,
9876
12070
  "qr-code": qrCodeCss,
@@ -9891,6 +12085,7 @@ const componentStyles = {
9891
12085
  switch: switchCss,
9892
12086
  tabs: tabsCss,
9893
12087
  "tags-input": tagsInputCss,
12088
+ textarea: fieldControlCss.replaceAll("data-scope=\"field\"", "data-scope=\"textarea\"").replaceAll("data-part=\"textarea\"", "data-part=\"root\""),
9894
12089
  timeline: timelineCss,
9895
12090
  timer: timerCss,
9896
12091
  table: tableCss,
@@ -9898,9 +12093,104 @@ const componentStyles = {
9898
12093
  toc: tocCss,
9899
12094
  toggle: toggleCss,
9900
12095
  "toggle-group": toggleGroupCss,
12096
+ transfer: transferCss,
9901
12097
  tooltip: tooltipCss,
9902
12098
  tour: tourCss,
9903
- "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
9904
12194
  };
9905
12195
  //#endregion
9906
12196
  //#region src/styles.ts