@teamblind-chorus/ui 1.1.0 → 2.0.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.
Files changed (148) hide show
  1. package/README.md +3 -3
  2. package/agents/AGENTS.md +6 -6
  3. package/agents/DESIGN.md +245 -244
  4. package/agents/LOVABLE.md +40 -11
  5. package/agents/catalog.md +10 -8
  6. package/agents/components/avatar-rail/avatar-rail.md +2 -4
  7. package/agents/components/avatar-rail/avatar-rail.spec.json +27 -12
  8. package/agents/components/badge/role.md +7 -9
  9. package/agents/components/badge/role.spec.json +6 -6
  10. package/agents/components/badge/update.md +6 -8
  11. package/agents/components/badge/update.spec.json +5 -5
  12. package/agents/components/banner/banner.family.json +3 -1
  13. package/agents/components/banner/banner.md +66 -15
  14. package/agents/components/banner/banner.spec.json +37 -14
  15. package/agents/components/bottom-sheet/bottom-sheet.md +4 -6
  16. package/agents/components/bottom-sheet/bottom-sheet.spec.json +5 -5
  17. package/agents/components/bubble/bubble.md +8 -10
  18. package/agents/components/bubble/bubble.spec.json +11 -11
  19. package/agents/components/button/button.md +1 -1
  20. package/agents/components/button/check.md +9 -11
  21. package/agents/components/button/check.spec.json +25 -8
  22. package/agents/components/button/fab.md +7 -9
  23. package/agents/components/button/fab.spec.json +27 -10
  24. package/agents/components/button/group.spec.json +4 -4
  25. package/agents/components/button/icon.md +21 -23
  26. package/agents/components/button/icon.spec.json +29 -12
  27. package/agents/components/button/standard.md +40 -42
  28. package/agents/components/button/standard.spec.json +37 -20
  29. package/agents/components/button/text.md +21 -23
  30. package/agents/components/button/text.spec.json +30 -13
  31. package/agents/components/button/toggle.md +7 -9
  32. package/agents/components/button/toggle.spec.json +27 -10
  33. package/agents/components/button/toolbar.md +24 -26
  34. package/agents/components/button/toolbar.spec.json +10 -12
  35. package/agents/components/carousel/carousel.md +1 -1
  36. package/agents/components/carousel/post.md +15 -21
  37. package/agents/components/carousel/post.spec.json +17 -17
  38. package/agents/components/carousel/profile.md +9 -45
  39. package/agents/components/carousel/profile.spec.json +17 -17
  40. package/agents/components/chip/chip.md +1 -1
  41. package/agents/components/chip/filter.md +22 -24
  42. package/agents/components/chip/filter.spec.json +34 -11
  43. package/agents/components/chip/tag.md +22 -24
  44. package/agents/components/chip/tag.spec.json +36 -13
  45. package/agents/components/dialog/dialog.md +1 -3
  46. package/agents/components/dialog/dialog.spec.json +3 -3
  47. package/agents/components/directory-list/directory-list.md +1 -3
  48. package/agents/components/directory-list/directory-list.spec.json +2 -2
  49. package/agents/components/divider/divider.family.json +1 -1
  50. package/agents/components/divider/divider.md +12 -14
  51. package/agents/components/divider/divider.spec.json +8 -8
  52. package/agents/components/empty-state/empty-state.family.json +28 -0
  53. package/agents/components/empty-state/empty-state.md +69 -0
  54. package/agents/components/empty-state/empty-state.spec.json +87 -0
  55. package/agents/components/feed/ad.md +2 -4
  56. package/agents/components/feed/ad.spec.json +10 -10
  57. package/agents/components/feed/post.md +41 -43
  58. package/agents/components/feed/post.spec.json +35 -39
  59. package/agents/components/form-field/form-field.md +1 -1
  60. package/agents/components/form-field/input.md +32 -34
  61. package/agents/components/form-field/input.spec.json +39 -31
  62. package/agents/components/form-field/search.md +2 -4
  63. package/agents/components/form-field/search.spec.json +24 -16
  64. package/agents/components/form-field/select.md +18 -20
  65. package/agents/components/form-field/select.spec.json +36 -27
  66. package/agents/components/form-field/textarea.md +3 -5
  67. package/agents/components/form-field/textarea.spec.json +37 -29
  68. package/agents/components/header/main.md +4 -6
  69. package/agents/components/header/main.spec.json +3 -3
  70. package/agents/components/header/sub.md +6 -8
  71. package/agents/components/header/sub.spec.json +3 -3
  72. package/agents/components/list/accordion.md +34 -45
  73. package/agents/components/list/accordion.spec.json +26 -17
  74. package/agents/components/list/entry.md +59 -81
  75. package/agents/components/list/entry.spec.json +37 -21
  76. package/agents/components/list/list.md +2 -2
  77. package/agents/components/list/radio.md +13 -20
  78. package/agents/components/list/radio.spec.json +33 -18
  79. package/agents/components/list/standard.md +88 -64
  80. package/agents/components/list/standard.spec.json +52 -20
  81. package/agents/components/metadata/compact.md +4 -6
  82. package/agents/components/metadata/compact.spec.json +6 -6
  83. package/agents/components/metadata/metadata.md +1 -1
  84. package/agents/components/metadata/standard.md +12 -14
  85. package/agents/components/metadata/standard.spec.json +10 -10
  86. package/agents/components/nav-card/nav-card.md +25 -27
  87. package/agents/components/nav-card/nav-card.spec.json +25 -16
  88. package/agents/components/nav-list/nav-list.md +2 -8
  89. package/agents/components/nav-list/nav-list.spec.json +3 -3
  90. package/agents/components/navigation-bar/main.md +9 -11
  91. package/agents/components/navigation-bar/main.spec.json +6 -6
  92. package/agents/components/navigation-bar/search.md +6 -8
  93. package/agents/components/navigation-bar/search.spec.json +9 -9
  94. package/agents/components/navigation-bar/sub.md +9 -11
  95. package/agents/components/navigation-bar/sub.spec.json +7 -7
  96. package/agents/components/page-shell/page-shell.family.json +1 -1
  97. package/agents/components/page-shell/page-shell.md +33 -0
  98. package/agents/components/page-shell/page-shell.spec.json +85 -0
  99. package/agents/components/pagination/pagination.family.json +1 -1
  100. package/agents/components/pagination/pagination.md +3 -3
  101. package/agents/components/pagination/pagination.spec.json +5 -5
  102. package/agents/components/profile-header/profile-header.md +9 -11
  103. package/agents/components/profile-header/profile-header.spec.json +9 -9
  104. package/agents/components/progress/progress.family.json +1 -1
  105. package/agents/components/progress/progress.md +5 -5
  106. package/agents/components/progress/progress.spec.json +8 -8
  107. package/agents/components/side-sheet/side-sheet.md +11 -13
  108. package/agents/components/side-sheet/side-sheet.spec.json +3 -3
  109. package/agents/components/skeleton/skeleton.md +7 -9
  110. package/agents/components/skeleton/skeleton.spec.json +5 -5
  111. package/agents/components/spinner/spinner.family.json +27 -0
  112. package/agents/components/spinner/spinner.md +96 -0
  113. package/agents/components/spinner/spinner.spec.json +82 -0
  114. package/agents/components/status-tag/status-tag.md +7 -9
  115. package/agents/components/status-tag/status-tag.spec.json +5 -5
  116. package/agents/components/suggestion-list/suggestion-list.md +3 -7
  117. package/agents/components/suggestion-list/suggestion-list.spec.json +8 -12
  118. package/agents/components/switch/switch.md +12 -14
  119. package/agents/components/switch/switch.spec.json +23 -15
  120. package/agents/components/tab-bar/tab-bar.md +9 -11
  121. package/agents/components/tab-bar/tab-bar.spec.json +37 -23
  122. package/agents/components/tabs/rounded.md +6 -8
  123. package/agents/components/tabs/rounded.spec.json +34 -13
  124. package/agents/components/tabs/segmented.md +4 -6
  125. package/agents/components/tabs/segmented.spec.json +4 -8
  126. package/agents/components/tabs/underline.md +9 -11
  127. package/agents/components/tabs/underline.spec.json +31 -14
  128. package/agents/components/thumbnail/thumbnail.md +5 -7
  129. package/agents/components/thumbnail/thumbnail.spec.json +8 -8
  130. package/agents/components/toast/toast.md +5 -7
  131. package/agents/components/toast/toast.spec.json +3 -3
  132. package/agents/components/tooltip/tooltip.md +6 -8
  133. package/agents/components/tooltip/tooltip.spec.json +4 -4
  134. package/agents/manifest.json +8 -6
  135. package/agents/tokens.usage.json +71 -226
  136. package/agents/usage.json +12 -0
  137. package/dist/index.cjs +531 -262
  138. package/dist/index.cjs.map +1 -1
  139. package/dist/index.d.cts +57 -13
  140. package/dist/index.d.ts +57 -13
  141. package/dist/index.js +530 -263
  142. package/dist/index.js.map +1 -1
  143. package/dist/styles.css +560 -379
  144. package/eslint/rules.js +7 -7
  145. package/package.json +2 -3
  146. package/agents/anti-patterns.md +0 -533
  147. package/agents/compose.md +0 -240
  148. package/agents/images.md +0 -66
package/dist/index.js CHANGED
@@ -5,11 +5,11 @@ import { createPortal } from 'react-dom';
5
5
  // ../../schema/components/badge/update.spec.json
6
6
  var update_spec_default = {
7
7
  appearance: {
8
- background: "sys.color.brand",
9
- label: "sys.color.onBrand",
8
+ background: "sys.color.text.brand",
9
+ label: "sys.color.text.onFill",
10
10
  radius: "sys.radius.full",
11
11
  dotOutline: {
12
- color: "sys.color.surface",
12
+ color: "sys.color.surface.default",
13
13
  width: "sys.borderWidth.thin",
14
14
  rendering: "box-shadow",
15
15
  note: "Update Dot rungs paint a 2px `surface`-color outline as a `box-shadow` so the dot stays a discrete chip on any host (image, icon, row) without enlarging its bounding box. The outline carves the dot out of whatever sits beside it; without it the brand fill blends into surrounding fills with similar luminance."
@@ -28,7 +28,7 @@ var update_spec_default = {
28
28
  minWidth: "ref.space.200",
29
29
  paddingBlock: "0",
30
30
  paddingInline: "sys.layout.container.2xs",
31
- labelTypo: "sys.typo.caption"
31
+ labelTypo: "sys.typo.label.xs"
32
32
  },
33
33
  "dot-md": {
34
34
  minHeight: "ref.space.100",
@@ -52,15 +52,15 @@ var update_spec_default = {
52
52
  var role_spec_default = {
53
53
  appearances: {
54
54
  default: {
55
- background: "sys.color.primaryContainer",
56
- label: "sys.color.onPrimaryContainer",
55
+ background: "sys.color.background.selected",
56
+ label: "sys.color.text.link",
57
57
  radius: "sys.radius.full",
58
58
  default: true,
59
59
  note: "Tonal informational pair \u2014 pale primary container fill with the deep primary-container label. Theme-aware sys tokens, so no separate dark binding. Never the brand pair: brand is reserved for the Update sub's activity marker, and a brand-filled role mark would read as an alert, not an identity."
60
60
  },
61
61
  inverse: {
62
- background: "sys.color.inverseSurface",
63
- label: "sys.color.inverseOnSurface",
62
+ background: "sys.color.background.inverse",
63
+ label: "sys.color.text.inverse",
64
64
  radius: "sys.radius.full",
65
65
  note: "Inverse pair \u2014 the strongest mark in the row: near-black pill with light label (theme-aware; flips in dark mode). Reserved for the PRO mark on paid professional users. Same geometry as default; only the fill pair changes."
66
66
  }
@@ -71,7 +71,7 @@ var role_spec_default = {
71
71
  minWidth: "ref.space.200",
72
72
  paddingBlock: "ref.space.25",
73
73
  paddingInline: "ref.space.75",
74
- labelTypo: "sys.typo.caption",
74
+ labelTypo: "sys.typo.label.xs",
75
75
  labelLineHeight: "1.2",
76
76
  note: "Single 16-rung (`ref.space.200`) \u2014 sized to ride inline beside `caption`/`label` metadata text without stretching the row. 2 \xD7 6 padding (`ref.space.25` \xD7 `ref.space.75`); the reference steps bind raw where `sys.*` exposes no step, in lockstep with the Update sub's rungs. Label line-height is the structural `1.2` (same device as StatusTag): 10px \xD7 1.2 + 2 \xD7 2px padding = exactly 16px \u2014 `caption`'s 15px running-text line would push the pill to 19."
77
77
  }
@@ -106,13 +106,13 @@ function formatCount(value) {
106
106
  return String(Math.floor(value));
107
107
  }
108
108
  function sizingStyle(spec, size) {
109
- const s = spec.sizes[size] ?? spec.sizes.medium;
109
+ const s2 = spec.sizes[size] ?? spec.sizes.medium;
110
110
  return {
111
- "--badge-min-height": tokenToCss(s.minHeight),
112
- "--badge-min-width": tokenToCss(s.minWidth),
113
- "--badge-padding-block": tokenToCss(s.paddingBlock),
114
- "--badge-padding-inline": tokenToCss(s.paddingInline),
115
- ...s.labelTypo ? typoStyles(s.labelTypo) : null
111
+ "--badge-min-height": tokenToCss(s2.minHeight),
112
+ "--badge-min-width": tokenToCss(s2.minWidth),
113
+ "--badge-padding-block": tokenToCss(s2.paddingBlock),
114
+ "--badge-padding-inline": tokenToCss(s2.paddingInline),
115
+ ...s2.labelTypo ? typoStyles(s2.labelTypo) : null
116
116
  };
117
117
  }
118
118
  function appearanceStyle(spec) {
@@ -236,10 +236,12 @@ function useFullBleedGuard(ref, name) {
236
236
  function Banner({
237
237
  appearance = "default",
238
238
  outlined = false,
239
+ neutralBody = false,
239
240
  title,
240
241
  icon,
241
242
  thumbnail,
242
243
  action,
244
+ trailingAction,
243
245
  trailingIcon,
244
246
  children,
245
247
  className,
@@ -255,6 +257,7 @@ function Banner({
255
257
  "chorus-banner",
256
258
  `chorus-banner--${appearance}`,
257
259
  outlined && "chorus-banner--outlined",
260
+ neutralBody && "chorus-banner--neutral-body",
258
261
  className
259
262
  ),
260
263
  role: "note",
@@ -275,7 +278,7 @@ function Banner({
275
278
  }
276
279
  ) : null
277
280
  ] }),
278
- trailingIcon ? /* @__PURE__ */ jsx("span", { className: "chorus-banner__trailing-icon", "aria-hidden": "true", children: trailingIcon }) : null
281
+ trailingAction ? /* @__PURE__ */ jsx("span", { className: "chorus-banner__trailing-action", children: trailingAction }) : trailingIcon ? /* @__PURE__ */ jsx("span", { className: "chorus-banner__trailing-icon", "aria-hidden": "true", children: trailingIcon }) : null
279
282
  ]
280
283
  }
281
284
  );
@@ -324,27 +327,27 @@ var standard_spec_default = {
324
327
  },
325
328
  appearances: {
326
329
  primary: {
327
- background: "sys.color.primary",
330
+ background: "sys.color.background.primary",
328
331
  border: null,
329
- label: "sys.color.onPrimary"
332
+ label: "sys.color.text.onFill"
330
333
  },
331
334
  secondary: {
332
- background: "sys.color.secondaryContainer",
335
+ background: "sys.color.background.neutral",
333
336
  border: null,
334
- label: "sys.color.onSecondaryContainer"
337
+ label: "sys.color.text.default"
335
338
  },
336
339
  outlined: {
337
340
  background: "transparent",
338
341
  border: {
339
342
  width: "sys.borderWidth.hairline",
340
- color: "sys.color.primary"
343
+ color: "sys.color.text.link"
341
344
  },
342
- label: "sys.color.primary"
345
+ label: "sys.color.text.link"
343
346
  },
344
347
  tertiary: {
345
348
  background: "transparent",
346
349
  border: null,
347
- label: "sys.color.onSurfaceVariant"
350
+ label: "sys.color.text.subtle"
348
351
  }
349
352
  },
350
353
  states: {
@@ -366,22 +369,19 @@ var standard_spec_default = {
366
369
  opacity: "sys.state.focus"
367
370
  },
368
371
  ring: {
369
- outerWidth: "sys.borderWidth.thin",
370
- outerColor: "sys.color.focus",
371
- insetWidth: "sys.borderWidth.hairline",
372
- insetColor: "sys.color.focusInset"
372
+ color: "sys.color.border.focused"
373
373
  }}};
374
374
  function sizeStyle(size) {
375
- const s = standard_spec_default.sizes[size] ?? standard_spec_default.sizes[standard_spec_default.props.size.default];
375
+ const s2 = standard_spec_default.sizes[size] ?? standard_spec_default.sizes[standard_spec_default.props.size.default];
376
376
  return {
377
- "--button-standard-padding-block": tokenToCss(s.paddingBlock),
378
- "--button-standard-padding-inline": tokenToCss(s.paddingInline),
379
- "--button-standard-gap": tokenToCss(s.gap),
380
- "--button-standard-min-height": tokenToCss(s.minHeight),
381
- "--button-standard-min-width": tokenToCss(s.minWidth),
382
- "--button-standard-radius": tokenToCss(s.radius),
383
- "--button-standard-icon-size": tokenToCss(s.iconSize),
384
- ...typoStyles(s.labelTypo)
377
+ "--button-standard-padding-block": tokenToCss(s2.paddingBlock),
378
+ "--button-standard-padding-inline": tokenToCss(s2.paddingInline),
379
+ "--button-standard-gap": tokenToCss(s2.gap),
380
+ "--button-standard-min-height": tokenToCss(s2.minHeight),
381
+ "--button-standard-min-width": tokenToCss(s2.minWidth),
382
+ "--button-standard-radius": tokenToCss(s2.radius),
383
+ "--button-standard-icon-size": tokenToCss(s2.iconSize),
384
+ ...typoStyles(s2.labelTypo)
385
385
  };
386
386
  }
387
387
  function appearanceStyle2(appearance) {
@@ -399,10 +399,7 @@ function stateStyle() {
399
399
  "--button-standard-overlay-pressed": tokenToCss(standard_spec_default.states.pressed.overlay.opacity),
400
400
  "--button-standard-overlay-focus": tokenToCss(standard_spec_default.focusIndicator.overlay.opacity),
401
401
  "--button-standard-disabled-opacity": tokenToCss(standard_spec_default.states.disabled.containerOpacity),
402
- "--button-standard-focus-outer-width": tokenToCss(standard_spec_default.focusIndicator.ring.outerWidth),
403
- "--button-standard-focus-outer-color": tokenToCss(standard_spec_default.focusIndicator.ring.outerColor),
404
- "--button-standard-focus-inset-width": tokenToCss(standard_spec_default.focusIndicator.ring.insetWidth),
405
- "--button-standard-focus-inset-color": tokenToCss(standard_spec_default.focusIndicator.ring.insetColor)
402
+ "--button-standard-focus-outer-color": tokenToCss(standard_spec_default.focusIndicator.ring.color)
406
403
  };
407
404
  }
408
405
  var FORCEABLE_STATES = /* @__PURE__ */ new Set(["hovered", "pressed", "focused"]);
@@ -470,12 +467,12 @@ var fab_spec_default = {
470
467
  },
471
468
  appearances: {
472
469
  primary: {
473
- background: "sys.color.brand",
474
- label: "sys.color.onBrand"
470
+ background: "sys.color.text.brand",
471
+ label: "sys.color.text.onFill"
475
472
  },
476
473
  secondary: {
477
- background: "sys.color.surfaceContainerHigh",
478
- label: "sys.color.onSurface"
474
+ background: "sys.color.surface.default",
475
+ label: "sys.color.text.default"
479
476
  }
480
477
  },
481
478
  states: {
@@ -488,17 +485,13 @@ var fab_spec_default = {
488
485
  overlay: {
489
486
  opacity: "sys.state.pressed"
490
487
  }
491
- }
492
- },
488
+ }},
493
489
  focusIndicator: {
494
490
  overlay: {
495
491
  opacity: "sys.state.focus"
496
492
  },
497
493
  ring: {
498
- outerWidth: "sys.borderWidth.thin",
499
- outerColor: "sys.color.focus",
500
- insetWidth: "sys.borderWidth.hairline",
501
- insetColor: "sys.color.focusInset"
494
+ color: "sys.color.border.focused"
502
495
  }}};
503
496
  var FORCEABLE_STATES2 = /* @__PURE__ */ new Set(["hovered", "pressed", "focused"]);
504
497
  function appearanceStyle3(appearance) {
@@ -519,10 +512,7 @@ var sizingStyle2 = () => ({
519
512
  "--button-fab-overlay-hover": tokenToCss(fab_spec_default.states.hovered.overlay.opacity),
520
513
  "--button-fab-overlay-pressed": tokenToCss(fab_spec_default.states.pressed.overlay.opacity),
521
514
  "--button-fab-overlay-focus": tokenToCss(fab_spec_default.focusIndicator.overlay.opacity),
522
- "--button-fab-focus-outer-width": tokenToCss(fab_spec_default.focusIndicator.ring.outerWidth),
523
- "--button-fab-focus-outer-color": tokenToCss(fab_spec_default.focusIndicator.ring.outerColor),
524
- "--button-fab-focus-inset-width": tokenToCss(fab_spec_default.focusIndicator.ring.insetWidth),
525
- "--button-fab-focus-inset-color": tokenToCss(fab_spec_default.focusIndicator.ring.insetColor),
515
+ "--button-fab-focus-outer-color": tokenToCss(fab_spec_default.focusIndicator.ring.color),
526
516
  ...typoStyles(fab_spec_default.sizing.labelTypo)
527
517
  });
528
518
  function ButtonFab({
@@ -1560,16 +1550,16 @@ var filter_spec_default = {
1560
1550
  selectionStates: {
1561
1551
  unselected: {
1562
1552
  background: "transparent",
1563
- label: "sys.color.onSurface",
1553
+ label: "sys.color.text.default",
1564
1554
  border: {
1565
1555
  width: "sys.borderWidth.hairline",
1566
- color: "sys.color.outlineVariant"
1556
+ color: "sys.color.border.default"
1567
1557
  },
1568
1558
  note: "Transparent fill so the chip adopts whatever surface sits behind it (page, raised card, sheet) without pinning to a fixed neutral step."
1569
1559
  },
1570
1560
  selected: {
1571
- background: "sys.color.inverseSurface",
1572
- label: "sys.color.inverseOnSurface",
1561
+ background: "sys.color.background.inverse",
1562
+ label: "sys.color.text.inverse",
1573
1563
  border: null
1574
1564
  }
1575
1565
  },
@@ -1589,26 +1579,49 @@ var filter_spec_default = {
1589
1579
  opacity: "sys.state.pressed"
1590
1580
  }
1591
1581
  },
1582
+ focused: {
1583
+ overlay: {
1584
+ color: "label",
1585
+ opacity: "sys.state.focus"
1586
+ },
1587
+ focusRing: {
1588
+ composition: "outward",
1589
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
1590
+ innerCounterRing: {
1591
+ width: "sys.borderWidth.hairline",
1592
+ color: "sys.color.border.focused"
1593
+ },
1594
+ outerRing: {
1595
+ width: "sys.borderWidth.thin",
1596
+ color: "sys.color.border.focused"
1597
+ }
1598
+ },
1599
+ note: "Keyboard-focus (:focus-visible) visual. Mirrors the `focusIndicator` block (the external-reader contract); kept here so spec-only renderers see focus in the states map. Composes over the lifecycle state the chip is in; never via plain mouse click."
1600
+ },
1592
1601
  disabled: {
1593
1602
  overlay: null,
1594
- containerOpacity: "sys.state.disabled",
1603
+ background: "sys.color.background.disabled",
1604
+ label: "sys.color.text.disabled",
1605
+ border: {
1606
+ width: "sys.borderWidth.hairline",
1607
+ color: "sys.color.border.bold"
1608
+ },
1595
1609
  suppressFocusRing: true,
1596
- cursor: "not-allowed"
1610
+ cursor: "not-allowed",
1611
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border so the shape reads on any surface, plus disabled label. Overrides the rest/selected appearance."
1597
1612
  }
1598
1613
  },
1599
1614
  focusIndicator: {
1600
1615
  description: "Keyboard-focus visual \u2014 an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the chip is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
1601
1616
  composition: "outward",
1602
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
1617
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
1603
1618
  overlay: {
1604
1619
  color: "label",
1605
1620
  opacity: "sys.state.focus"
1606
1621
  },
1607
1622
  ring: {
1608
- outerWidth: "sys.borderWidth.thin",
1609
- outerColor: "sys.color.focus",
1610
- insetWidth: "sys.borderWidth.hairline",
1611
- insetColor: "sys.color.focusInset"
1623
+ width: "sys.borderWidth.hairline",
1624
+ color: "sys.color.border.focused"
1612
1625
  },
1613
1626
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
1614
1627
  },
@@ -1626,7 +1639,7 @@ var tag_spec_default = {
1626
1639
  name: "Chip",
1627
1640
  family: "chip",
1628
1641
  subcomponent: "tag",
1629
- description: "Informational chip. Square-cornered metadata label. Passive (or dismissable via trailing \xD7). No leading icon. Two appearances: `default` paints the translucent `sys.color.scrimSubtle` scrim (~8% inverse-tone overlay \u2014 black in light, white in dark) so the tag adopts whatever surface sits behind it; `accent` paints a tonal pale-primary container (`sys.color.primaryContainer`) with primary label for tags that need to pop against the surface.",
1642
+ description: "Informational chip. Square-cornered metadata label. Passive (or dismissable via trailing \xD7). No leading icon. Two appearances: `default` paints the translucent `sys.color.background.neutral` scrim (~8% inverse-tone overlay \u2014 black in light, white in dark) so the tag adopts whatever surface sits behind it; `accent` paints a tonal pale-primary container (`sys.color.background.selected`) with primary label for tags that need to pop against the surface.",
1630
1643
  element: "span",
1631
1644
  props: {
1632
1645
  variant: {
@@ -1667,15 +1680,15 @@ var tag_spec_default = {
1667
1680
  },
1668
1681
  appearances: {
1669
1682
  default: {
1670
- background: "sys.color.scrimSubtle",
1671
- label: "sys.color.onSurface",
1683
+ background: "sys.color.background.neutral",
1684
+ label: "sys.color.text.default",
1672
1685
  border: null,
1673
1686
  default: true,
1674
- note: "Background is the translucent inverse-tone scrim (`sys.color.scrimSubtle` \u2014 black 8% in light mode, white 8% in dark) so the tag harmonises with whatever surface sits behind it \u2014 body, raised card, BottomSheet \u2014 instead of pinning to a fixed neutral step. Same Banner-style fill used by Progress track, StatusTag neutral, and Skeleton; sys-color is theme-aware so a single token resolves correctly in both modes."
1687
+ note: "Background is the translucent inverse-tone scrim (`sys.color.background.neutral` \u2014 black 8% in light mode, white 8% in dark) so the tag harmonises with whatever surface sits behind it \u2014 body, raised card, BottomSheet \u2014 instead of pinning to a fixed neutral step. Same Banner-style fill used by Progress track, StatusTag neutral, and Skeleton; sys-color is theme-aware so a single token resolves correctly in both modes."
1675
1688
  },
1676
1689
  accent: {
1677
- background: "sys.color.primaryContainer",
1678
- label: "sys.color.primary",
1690
+ background: "sys.color.background.selected",
1691
+ label: "sys.color.text.link",
1679
1692
  border: null,
1680
1693
  note: "Tonal accent: pale primary container background with primary label. Sys-color tokens are theme-aware, so no separate dark binding is needed \u2014 the resolved tokens pick the right values per theme. Use for tags that should pop against the surface (e.g. Popular Tags in compose, highlighted hashtags) where the default overlay is too quiet."
1681
1694
  }
@@ -1696,26 +1709,49 @@ var tag_spec_default = {
1696
1709
  opacity: "sys.state.pressed"
1697
1710
  }
1698
1711
  },
1712
+ focused: {
1713
+ overlay: {
1714
+ color: "label",
1715
+ opacity: "sys.state.focus"
1716
+ },
1717
+ focusRing: {
1718
+ composition: "outward",
1719
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
1720
+ innerCounterRing: {
1721
+ width: "sys.borderWidth.hairline",
1722
+ color: "sys.color.border.focused"
1723
+ },
1724
+ outerRing: {
1725
+ width: "sys.borderWidth.thin",
1726
+ color: "sys.color.border.focused"
1727
+ }
1728
+ },
1729
+ note: "Keyboard-focus (:focus-visible) visual. Mirrors the `focusIndicator` block (the external-reader contract); kept here so spec-only renderers see focus in the states map. Composes over the lifecycle state the chip is in; never via plain mouse click."
1730
+ },
1699
1731
  disabled: {
1700
1732
  overlay: null,
1701
- containerOpacity: "sys.state.disabled",
1733
+ background: "sys.color.background.disabled",
1734
+ label: "sys.color.text.disabled",
1735
+ border: {
1736
+ width: "sys.borderWidth.hairline",
1737
+ color: "sys.color.border.bold"
1738
+ },
1702
1739
  suppressFocusRing: true,
1703
- cursor: "not-allowed"
1740
+ cursor: "not-allowed",
1741
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border so the shape reads on any surface, plus disabled label. Overrides the rest/selected appearance."
1704
1742
  }
1705
1743
  },
1706
1744
  focusIndicator: {
1707
1745
  description: "Keyboard-focus visual \u2014 an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the chip is in. Applies only when the chip is interactive (a dismiss trailingIcon is present). The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
1708
1746
  composition: "outward",
1709
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
1747
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
1710
1748
  overlay: {
1711
1749
  color: "label",
1712
1750
  opacity: "sys.state.focus"
1713
1751
  },
1714
1752
  ring: {
1715
- outerWidth: "sys.borderWidth.thin",
1716
- outerColor: "sys.color.focus",
1717
- insetWidth: "sys.borderWidth.hairline",
1718
- insetColor: "sys.color.focusInset"
1753
+ width: "sys.borderWidth.hairline",
1754
+ color: "sys.color.border.focused"
1719
1755
  },
1720
1756
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
1721
1757
  },
@@ -1792,16 +1828,16 @@ var toggle_spec_default = {
1792
1828
  },
1793
1829
  selectionStates: {
1794
1830
  unselected: {
1795
- background: "sys.color.primary",
1796
- label: "sys.color.onPrimary",
1831
+ background: "sys.color.background.primary",
1832
+ label: "sys.color.text.onFill",
1797
1833
  border: null
1798
1834
  },
1799
1835
  selected: {
1800
1836
  background: "transparent",
1801
- label: "sys.color.onSurface",
1837
+ label: "sys.color.text.default",
1802
1838
  border: {
1803
1839
  width: "sys.borderWidth.hairline",
1804
- color: "sys.color.outlineVariant"
1840
+ color: "sys.color.border.default"
1805
1841
  }
1806
1842
  }
1807
1843
  },
@@ -1821,6 +1857,25 @@ var toggle_spec_default = {
1821
1857
  opacity: "sys.state.pressed"
1822
1858
  }
1823
1859
  },
1860
+ focused: {
1861
+ overlay: {
1862
+ color: "label",
1863
+ opacity: "sys.state.focus"
1864
+ },
1865
+ focusRing: {
1866
+ composition: "outward",
1867
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
1868
+ innerCounterRing: {
1869
+ width: "sys.borderWidth.hairline",
1870
+ color: "sys.color.border.focused"
1871
+ },
1872
+ outerRing: {
1873
+ width: "sys.borderWidth.thin",
1874
+ color: "sys.color.border.focused"
1875
+ }
1876
+ },
1877
+ note: "Keyboard-focus (:focus-visible) visual. Mirrors the `focusIndicator` block (the external-reader contract); kept here so spec-only renderers see focus in the states map. Composes over the lifecycle state the button is in; never via plain mouse click."
1878
+ },
1824
1879
  disabled: {
1825
1880
  overlay: null,
1826
1881
  containerOpacity: "sys.state.disabled",
@@ -1831,21 +1886,19 @@ var toggle_spec_default = {
1831
1886
  focusIndicator: {
1832
1887
  description: "Keyboard-focus visual \u2014 an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the button is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
1833
1888
  composition: "outward",
1834
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
1889
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
1835
1890
  overlay: {
1836
1891
  color: "label",
1837
1892
  opacity: "sys.state.focus"
1838
1893
  },
1839
1894
  ring: {
1840
- outerWidth: "sys.borderWidth.thin",
1841
- outerColor: "sys.color.focus",
1842
- insetWidth: "sys.borderWidth.hairline",
1843
- insetColor: "sys.color.focusInset"
1895
+ width: "sys.borderWidth.hairline",
1896
+ color: "sys.color.border.focused"
1844
1897
  },
1845
1898
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
1846
1899
  },
1847
1900
  forbidden: [
1848
- "active state painted with sys.color.primary fill \u2014 active is transparent + hairline outline (the active state recedes, not asserts)",
1901
+ "active state painted with sys.color.background.primary fill \u2014 active is transparent + hairline outline (the active state recedes, not asserts)",
1849
1902
  "active state painted with any opaque surface fill (surface, surfaceContainer, surfaceContainerHigh) \u2014 the committed form is transparent so the host surface shows through; do not re-bind to a tier'd fill",
1850
1903
  "rest state without an explicit `active={false}` \u2014 toggle is a binary contract, never tristate",
1851
1904
  "manual width override that breaks the full-card stretch when used inside ProfileCarousel.followAction"
@@ -1892,10 +1945,7 @@ function sizingStyle3(spec) {
1892
1945
  "--chip-overlay-pressed": tokenToCss(spec.states.pressed.overlay.opacity),
1893
1946
  "--chip-overlay-focus": tokenToCss(spec.focusIndicator.overlay.opacity),
1894
1947
  "--chip-disabled-opacity": tokenToCss(spec.states.disabled.containerOpacity),
1895
- "--chip-focus-outer-width": tokenToCss(spec.focusIndicator.ring.outerWidth),
1896
- "--chip-focus-outer-color": tokenToCss(spec.focusIndicator.ring.outerColor),
1897
- "--chip-focus-inset-width": tokenToCss(spec.focusIndicator.ring.insetWidth),
1898
- "--chip-focus-inset-color": tokenToCss(spec.focusIndicator.ring.insetColor),
1948
+ "--chip-focus-outer-color": tokenToCss(spec.focusIndicator.ring.color),
1899
1949
  ...typoStyles(spec.sizing.labelTypo)
1900
1950
  };
1901
1951
  }
@@ -1971,14 +2021,14 @@ function ButtonToolbar({ appearance = "default", className, style, ...rest }) {
1971
2021
  let pairStyle = null;
1972
2022
  if (appearance === "accent") {
1973
2023
  pairStyle = {
1974
- "--chip-bg": "var(--sys-color-primary)",
1975
- "--chip-label": "var(--sys-color-onPrimary)",
2024
+ "--chip-bg": "var(--sys-color-background-primary)",
2025
+ "--chip-label": "var(--sys-color-text-onFill)",
1976
2026
  "--chip-border-color": "transparent"
1977
2027
  };
1978
2028
  } else if (appearance === "inverse") {
1979
2029
  pairStyle = {
1980
- "--chip-bg": "var(--sys-color-inverseSurface)",
1981
- "--chip-label": "var(--sys-color-inverseOnSurface)",
2030
+ "--chip-bg": "var(--sys-color-background-inverse)",
2031
+ "--chip-label": "var(--sys-color-text-inverse)",
1982
2032
  "--chip-border-color": "transparent"
1983
2033
  };
1984
2034
  }
@@ -2007,6 +2057,46 @@ var Button = forwardRef(function Button2({ variant, ...rest }, ref) {
2007
2057
  const Impl = variant && VARIANTS[variant] || ButtonStandard;
2008
2058
  return /* @__PURE__ */ jsx(Impl, { ref, ...rest });
2009
2059
  });
2060
+ var FOCUSABLE_SELECTOR = [
2061
+ "a[href]",
2062
+ "button",
2063
+ "input",
2064
+ "select",
2065
+ "textarea",
2066
+ '[tabindex]:not([tabindex="-1"])'
2067
+ ].join(",");
2068
+ function useFocusTrap(ref, active) {
2069
+ useEffect(() => {
2070
+ if (!active) return void 0;
2071
+ const onKey = (e) => {
2072
+ if (e.key !== "Tab") return;
2073
+ const container = ref.current;
2074
+ if (!container) return;
2075
+ const focusable = Array.from(
2076
+ container.querySelectorAll(FOCUSABLE_SELECTOR)
2077
+ ).filter((el) => !el.disabled && el.offsetParent !== null);
2078
+ if (focusable.length === 0) {
2079
+ e.preventDefault();
2080
+ container.focus({ preventScroll: true });
2081
+ return;
2082
+ }
2083
+ const first = focusable[0];
2084
+ const last = focusable[focusable.length - 1];
2085
+ const activeEl = document.activeElement;
2086
+ if (e.shiftKey) {
2087
+ if (activeEl === first || !container.contains(activeEl)) {
2088
+ e.preventDefault();
2089
+ last.focus({ preventScroll: true });
2090
+ }
2091
+ } else if (activeEl === last || !container.contains(activeEl)) {
2092
+ e.preventDefault();
2093
+ first.focus({ preventScroll: true });
2094
+ }
2095
+ };
2096
+ document.addEventListener("keydown", onKey);
2097
+ return () => document.removeEventListener("keydown", onKey);
2098
+ }, [ref, active]);
2099
+ }
2010
2100
  function useBodyScrollLock(locked) {
2011
2101
  useEffect(() => {
2012
2102
  if (!locked) return void 0;
@@ -2046,6 +2136,7 @@ function BottomSheet({
2046
2136
  const lastFocusedRef = useRef(null);
2047
2137
  const [overflowing, setOverflowing] = useState(false);
2048
2138
  useBodyScrollLock(open && !inline);
2139
+ useFocusTrap(cardRef, open && !inline);
2049
2140
  useEffect(() => {
2050
2141
  if (!open || inline) return void 0;
2051
2142
  const vv = typeof window !== "undefined" ? window.visualViewport : null;
@@ -2109,6 +2200,7 @@ function BottomSheet({
2109
2200
  className: "chorus-bottom-sheet__card",
2110
2201
  role: "dialog",
2111
2202
  "aria-modal": "true",
2203
+ tabIndex: -1,
2112
2204
  "aria-label": ariaLabel ?? title,
2113
2205
  onClick: (e) => e.stopPropagation(),
2114
2206
  ...rest,
@@ -2427,6 +2519,26 @@ function List({
2427
2519
  if (isRadio) onChange == null ? void 0 : onChange(item.value);
2428
2520
  (_a = item.onClick) == null ? void 0 : _a.call(item);
2429
2521
  };
2522
+ const rowMain = isEntry ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
2523
+ isRadio ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading", children: /* @__PURE__ */ jsx(RadioIndicator, { selected }) }) : item.thumbnail ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading chorus-list__leading--image", children: /* @__PURE__ */ jsx(Thumbnail, { size: 40, ...item.thumbnail }) }) : item.icon ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading chorus-list__leading--icon", "aria-hidden": "true", children: item.icon }) : null,
2524
+ /* @__PURE__ */ jsxs("span", { className: "chorus-list__label-col", children: [
2525
+ /* @__PURE__ */ jsxs("span", { className: "chorus-list__primary-row", children: [
2526
+ /* @__PURE__ */ jsx("span", { className: "chorus-list__label", children: item.label }),
2527
+ item.count != null ? /* @__PURE__ */ jsx("span", { className: "chorus-list__count", children: item.count }) : null
2528
+ ] }),
2529
+ item.supportingText ? /* @__PURE__ */ jsx("span", { className: "chorus-list__supporting", children: item.supportingText }) : null
2530
+ ] }),
2531
+ item.nav && !item.trailingIcon ? /* @__PURE__ */ jsx("span", { className: "chorus-list__trailing chorus-list__nav-chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16 }) }) : item.trailingIcon ? /* @__PURE__ */ jsx(
2532
+ "span",
2533
+ {
2534
+ className: "chorus-list__trailing",
2535
+ "data-nested-action": "",
2536
+ onClick: (e) => e.stopPropagation(),
2537
+ onKeyDown: (e) => e.stopPropagation(),
2538
+ children: item.trailingIcon
2539
+ }
2540
+ ) : null
2541
+ ] });
2430
2542
  return /* @__PURE__ */ jsx(
2431
2543
  "div",
2432
2544
  {
@@ -2461,26 +2573,26 @@ function List({
2461
2573
  description: item.description,
2462
2574
  trailing: item.trailingIcon
2463
2575
  }
2464
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
2465
- isRadio ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading", children: /* @__PURE__ */ jsx(RadioIndicator, { selected }) }) : item.thumbnail ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading chorus-list__leading--image", children: /* @__PURE__ */ jsx(Thumbnail, { size: 40, ...item.thumbnail }) }) : item.icon ? /* @__PURE__ */ jsx("span", { className: "chorus-list__leading chorus-list__leading--icon", "aria-hidden": "true", children: item.icon }) : null,
2466
- /* @__PURE__ */ jsxs("span", { className: "chorus-list__label-col", children: [
2467
- /* @__PURE__ */ jsxs("span", { className: "chorus-list__primary-row", children: [
2468
- /* @__PURE__ */ jsx("span", { className: "chorus-list__label", children: item.label }),
2469
- item.count != null ? /* @__PURE__ */ jsx("span", { className: "chorus-list__count", children: item.count }) : null
2470
- ] }),
2471
- item.supportingText ? /* @__PURE__ */ jsx("span", { className: "chorus-list__supporting", children: item.supportingText }) : null
2472
- ] }),
2473
- item.nav && !item.trailingIcon ? /* @__PURE__ */ jsx("span", { className: "chorus-list__trailing chorus-list__nav-chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 16 }) }) : item.trailingIcon ? /* @__PURE__ */ jsx(
2474
- "span",
2475
- {
2476
- className: "chorus-list__trailing",
2477
- "data-nested-action": "",
2478
- onClick: (e) => e.stopPropagation(),
2479
- onKeyDown: (e) => e.stopPropagation(),
2480
- children: item.trailingIcon
2481
- }
2482
- ) : null
2483
- ] })
2576
+ ) : item.banner ? (
2577
+ // Embedded-Banner row the text group stacks over a Banner
2578
+ // that spans the row's full content width, 8px (stack.xs)
2579
+ // below. The Banner is a nested action region: its own
2580
+ // controls never commit the row, and the row's hover/press
2581
+ // overlay is suppressed over it (see styles.css).
2582
+ /* @__PURE__ */ jsxs("span", { className: "chorus-list__stack", children: [
2583
+ /* @__PURE__ */ jsx("span", { className: "chorus-list__row-main", children: rowMain }),
2584
+ /* @__PURE__ */ jsx(
2585
+ "span",
2586
+ {
2587
+ className: "chorus-list__banner",
2588
+ "data-nested-action": "",
2589
+ onClick: (e) => e.stopPropagation(),
2590
+ onKeyDown: (e) => e.stopPropagation(),
2591
+ children: item.banner
2592
+ }
2593
+ )
2594
+ ] })
2595
+ ) : rowMain
2484
2596
  },
2485
2597
  item.value ?? idx
2486
2598
  );
@@ -2740,6 +2852,7 @@ function Dialog({
2740
2852
  const cardRef = useRef(null);
2741
2853
  const lastFocusedRef = useRef(null);
2742
2854
  useBodyScrollLock(open && !inline);
2855
+ useFocusTrap(cardRef, open && !inline);
2743
2856
  useEffect(() => {
2744
2857
  var _a;
2745
2858
  if (!open) return void 0;
@@ -2785,6 +2898,7 @@ function Dialog({
2785
2898
  className: joinClasses("chorus-dialog__card", image && "chorus-dialog__card--with-image"),
2786
2899
  role: "dialog",
2787
2900
  "aria-modal": "true",
2901
+ tabIndex: -1,
2788
2902
  "aria-label": ariaLabel ?? title,
2789
2903
  onClick: (e) => e.stopPropagation(),
2790
2904
  ...rest,
@@ -2818,6 +2932,80 @@ function Divider({
2818
2932
  }
2819
2933
  );
2820
2934
  }
2935
+
2936
+ // ../../schema/components/empty-state/empty-state.spec.json
2937
+ var empty_state_spec_default = {
2938
+ sizing: {
2939
+ illustrationSize: "ref.space.600",
2940
+ illustrationColor: "sys.color.text.subtle",
2941
+ illustrationGap: "sys.layout.stack.sm",
2942
+ headlineTypo: "sys.typo.heading.sm",
2943
+ headlineColor: "sys.color.text.default",
2944
+ bodyTypo: "sys.typo.body.sm",
2945
+ bodyColor: "sys.color.text.subtle",
2946
+ bodyGap: "sys.layout.stack.2xs",
2947
+ actionGap: "sys.layout.stack.md"
2948
+ }};
2949
+ var s = empty_state_spec_default.sizing;
2950
+ var CONTAINER_STYLE = {
2951
+ "--empty-state-illustration-gap": tokenToCss(s.illustrationGap),
2952
+ "--empty-state-body-gap": tokenToCss(s.bodyGap),
2953
+ "--empty-state-action-gap": tokenToCss(s.actionGap)
2954
+ };
2955
+ var ILLUSTRATION_STYLE = {
2956
+ "--empty-state-illustration-size": tokenToCss(s.illustrationSize),
2957
+ "--empty-state-illustration-color": tokenToCss(s.illustrationColor)
2958
+ };
2959
+ var HEADLINE_STYLE = {
2960
+ "--empty-state-headline-color": tokenToCss(s.headlineColor),
2961
+ ...typoStyles(s.headlineTypo)
2962
+ };
2963
+ var BODY_STYLE = {
2964
+ "--empty-state-body-color": tokenToCss(s.bodyColor),
2965
+ ...typoStyles(s.bodyTypo)
2966
+ };
2967
+ function EmptyState({
2968
+ illustration,
2969
+ headline,
2970
+ body,
2971
+ action,
2972
+ className,
2973
+ style,
2974
+ ...rest
2975
+ }) {
2976
+ return /* @__PURE__ */ jsxs(
2977
+ "div",
2978
+ {
2979
+ role: "status",
2980
+ className: joinClasses("chorus-empty-state", className),
2981
+ style: { ...CONTAINER_STYLE, ...style },
2982
+ ...rest,
2983
+ children: [
2984
+ illustration ? /* @__PURE__ */ jsx(
2985
+ "span",
2986
+ {
2987
+ className: "chorus-empty-state__illustration",
2988
+ "aria-hidden": "true",
2989
+ style: ILLUSTRATION_STYLE,
2990
+ children: illustration
2991
+ }
2992
+ ) : null,
2993
+ /* @__PURE__ */ jsx("p", { className: "chorus-empty-state__headline", style: HEADLINE_STYLE, children: headline }),
2994
+ body ? /* @__PURE__ */ jsx("p", { className: "chorus-empty-state__body", style: BODY_STYLE, children: body }) : null,
2995
+ action ? /* @__PURE__ */ jsx("div", { className: "chorus-empty-state__action", children: /* @__PURE__ */ jsx(
2996
+ Button,
2997
+ {
2998
+ appearance: "primary",
2999
+ onClick: action.onClick ?? (action.href ? () => {
3000
+ window.location.assign(action.href);
3001
+ } : void 0),
3002
+ children: action.label
3003
+ }
3004
+ ) }) : null
3005
+ ]
3006
+ }
3007
+ );
3008
+ }
2821
3009
  var TabsContext = createContext({
2822
3010
  variant: "underline",
2823
3011
  value: null,
@@ -2844,17 +3032,17 @@ var underline_spec_default = {
2844
3032
  slotGap: "sys.layout.inline.sm",
2845
3033
  indicatorHeight: "sys.borderWidth.thin",
2846
3034
  dividerWidth: "sys.borderWidth.hairline",
2847
- dividerColor: "sys.color.outlineVariant",
3035
+ dividerColor: "sys.color.border.default",
2848
3036
  labelTypo: "sys.typo.label.md",
2849
3037
  iconSize: "sys.icon.md",
2850
3038
  fadeWidth: "ref.space.600"
2851
3039
  },
2852
3040
  selectionStates: {
2853
3041
  unselected: {
2854
- label: "sys.color.outline"},
3042
+ label: "sys.color.text.subtle"},
2855
3043
  selected: {
2856
- label: "sys.color.onSurface",
2857
- indicator: "sys.color.onSurface"
3044
+ label: "sys.color.text.default",
3045
+ indicator: "sys.color.border.selected"
2858
3046
  }
2859
3047
  },
2860
3048
  states: {
@@ -2869,17 +3057,14 @@ var underline_spec_default = {
2869
3057
  }
2870
3058
  },
2871
3059
  disabled: {
2872
- containerOpacity: "sys.state.disabled"}
3060
+ }
2873
3061
  },
2874
3062
  focusIndicator: {
2875
3063
  overlay: {
2876
3064
  opacity: "sys.state.focus"
2877
3065
  },
2878
3066
  ring: {
2879
- outerWidth: "sys.borderWidth.thin",
2880
- outerColor: "sys.color.focus",
2881
- insetWidth: "sys.borderWidth.hairline",
2882
- insetColor: "sys.color.focusInset"}}};
3067
+ color: "sys.color.border.focused"}}};
2883
3068
  function useSlidingIndicator(containerRef, indicatorRef, value) {
2884
3069
  useLayoutEffect(() => {
2885
3070
  const container = containerRef.current;
@@ -2940,19 +3125,19 @@ function TabsUnderline({ className, style, children, ...rest }) {
2940
3125
  useScrollOverflow(ref);
2941
3126
  useSlidingIndicator(ref, indicatorRef, value);
2942
3127
  useFullBleedGuard(ref, "Tabs");
2943
- const s = underline_spec_default.sizing;
3128
+ const s2 = underline_spec_default.sizing;
2944
3129
  const composedStyle = {
2945
- "--tabs-container-padding-inline": tokenToCss(s.containerPaddingInline),
2946
- "--tabs-tab-min-height": tokenToCss(s.minHeight),
2947
- "--tabs-tab-padding-block": tokenToCss(s.paddingBlock),
2948
- "--tabs-tab-padding-inline": tokenToCss(s.paddingInline),
2949
- "--tabs-inter-tab-gap": tokenToCss(s.interTabGap),
2950
- "--tabs-slot-gap": tokenToCss(s.slotGap),
2951
- "--tabs-icon-size": tokenToCss(s.iconSize),
2952
- "--tabs-indicator-height": tokenToCss(s.indicatorHeight),
2953
- "--tabs-divider-width": tokenToCss(s.dividerWidth),
2954
- "--tabs-divider-color": tokenToCss(s.dividerColor),
2955
- "--tabs-fade-width": tokenToCss(s.fadeWidth),
3130
+ "--tabs-container-padding-inline": tokenToCss(s2.containerPaddingInline),
3131
+ "--tabs-tab-min-height": tokenToCss(s2.minHeight),
3132
+ "--tabs-tab-padding-block": tokenToCss(s2.paddingBlock),
3133
+ "--tabs-tab-padding-inline": tokenToCss(s2.paddingInline),
3134
+ "--tabs-inter-tab-gap": tokenToCss(s2.interTabGap),
3135
+ "--tabs-slot-gap": tokenToCss(s2.slotGap),
3136
+ "--tabs-icon-size": tokenToCss(s2.iconSize),
3137
+ "--tabs-indicator-height": tokenToCss(s2.indicatorHeight),
3138
+ "--tabs-divider-width": tokenToCss(s2.dividerWidth),
3139
+ "--tabs-divider-color": tokenToCss(s2.dividerColor),
3140
+ "--tabs-fade-width": tokenToCss(s2.fadeWidth),
2956
3141
  "--tabs-label-unselected": tokenToCss(underline_spec_default.selectionStates.unselected.label),
2957
3142
  "--tabs-label-selected": tokenToCss(underline_spec_default.selectionStates.selected.label),
2958
3143
  "--tabs-indicator-color": tokenToCss(underline_spec_default.selectionStates.selected.indicator),
@@ -2960,11 +3145,8 @@ function TabsUnderline({ className, style, children, ...rest }) {
2960
3145
  "--tabs-overlay-pressed": tokenToCss(underline_spec_default.states.pressed.overlay.opacity),
2961
3146
  "--tabs-overlay-focus": tokenToCss(underline_spec_default.focusIndicator.overlay.opacity),
2962
3147
  "--tabs-disabled-opacity": tokenToCss(underline_spec_default.states.disabled.containerOpacity),
2963
- "--tabs-focus-outer-width": tokenToCss(underline_spec_default.focusIndicator.ring.outerWidth),
2964
- "--tabs-focus-outer-color": tokenToCss(underline_spec_default.focusIndicator.ring.outerColor),
2965
- "--tabs-focus-inset-width": tokenToCss(underline_spec_default.focusIndicator.ring.insetWidth),
2966
- "--tabs-focus-inset-color": tokenToCss(underline_spec_default.focusIndicator.ring.insetColor),
2967
- ...typoStyles(s.labelTypo),
3148
+ "--tabs-focus-outer-color": tokenToCss(underline_spec_default.focusIndicator.ring.color),
3149
+ ...typoStyles(s2.labelTypo),
2968
3150
  ...style
2969
3151
  };
2970
3152
  return /* @__PURE__ */ jsxs(
@@ -3026,12 +3208,12 @@ function TabsSegmented({ className, style, children, ...rest }) {
3026
3208
  const ref = useRef(null);
3027
3209
  useScrollOverflow(ref);
3028
3210
  useFullBleedGuard(ref, "Tabs");
3029
- const s = segmented_spec_default.sizing;
3211
+ const s2 = segmented_spec_default.sizing;
3030
3212
  const composedStyle = {
3031
- "--tabs-container-padding-block": tokenToCss(s.containerPaddingBlock),
3032
- "--tabs-container-padding-inline": tokenToCss(s.containerPaddingInline),
3033
- "--tabs-inter-segment-gap": tokenToCss(s.interSegmentGap),
3034
- "--tabs-fade-width": tokenToCss(s.fadeWidth),
3213
+ "--tabs-container-padding-block": tokenToCss(s2.containerPaddingBlock),
3214
+ "--tabs-container-padding-inline": tokenToCss(s2.containerPaddingInline),
3215
+ "--tabs-inter-segment-gap": tokenToCss(s2.interSegmentGap),
3216
+ "--tabs-fade-width": tokenToCss(s2.fadeWidth),
3035
3217
  ...style
3036
3218
  };
3037
3219
  return /* @__PURE__ */ jsx(
@@ -3699,9 +3881,9 @@ function Card({ item, innerRef, index }) {
3699
3881
  }
3700
3882
  var MAX_CARDS2 = 5;
3701
3883
  var METRIC_KINDS = {
3702
- star: { Icon: StarFillIcon, tone: "var(--sys-color-icon-yellow)" },
3703
- pulse: { Icon: PulseFillIcon, tone: "var(--sys-color-success)" },
3704
- heart: { Icon: HeartFillIcon, tone: "var(--sys-color-icon-red)" }
3884
+ star: { Icon: StarFillIcon, tone: "var(--sys-color-icon-accent-yellow-default)" },
3885
+ pulse: { Icon: PulseFillIcon, tone: "var(--sys-color-icon-success)" },
3886
+ heart: { Icon: HeartFillIcon, tone: "var(--sys-color-icon-accent-red-default)" }
3705
3887
  };
3706
3888
  var PLACEHOLDER_IMAGE = "/placeholder.png";
3707
3889
  function ProfileCarousel({
@@ -4255,7 +4437,7 @@ var input_spec_default = {
4255
4437
  helper: {
4256
4438
  type: "node",
4257
4439
  optional: true,
4258
- description: "Assistive text rendered below the field box, left-aligned. **Optional on every appearance** \u2014 omit the prop to render the field without an assistive rung; the box and label keep their footprint. On the `error` appearance the helper re-tones to `sys.color.error` so the message reads as the error caption; an error field may still be shown without a helper, in which case only the field box re-tones. Mutually exclusive with `maxLength` \u2014 if both are given, the character count is shown and `helper` is ignored."
4440
+ description: "Assistive text rendered below the field box, left-aligned. **Optional on every appearance** \u2014 omit the prop to render the field without an assistive rung; the box and label keep their footprint. On the `error` appearance the helper re-tones to `sys.color.text.danger` so the message reads as the error caption; an error field may still be shown without a helper, in which case only the field box re-tones. Mutually exclusive with `maxLength` \u2014 if both are given, the character count is shown and `helper` is ignored."
4259
4441
  },
4260
4442
  maxLength: {
4261
4443
  type: "number",
@@ -4275,7 +4457,7 @@ var input_spec_default = {
4275
4457
  },
4276
4458
  label: {
4277
4459
  required: false,
4278
- description: "Visible label above the box. `sys.typo.label.md`, `sys.color.onSurface`. Associated with the input via `htmlFor`.",
4460
+ description: "Visible label above the box. `sys.typo.label.md`, `sys.color.text.default`. Associated with the input via `htmlFor`.",
4279
4461
  accepts: [
4280
4462
  "text"
4281
4463
  ]
@@ -4299,14 +4481,14 @@ var input_spec_default = {
4299
4481
  },
4300
4482
  helper: {
4301
4483
  required: false,
4302
- description: "Assistive text below the box, left-aligned. `sys.typo.body.sm`, `sys.color.onSurfaceVariant`; on the `error` appearance the colour re-tones to `sys.color.error` so the message reads as the error caption. Referenced by the input's `aria-describedby`. Not rendered when a `maxLength` count is present, and intentionally omittable on every appearance (including `error`) \u2014 pass nothing and the field renders without an assistive rung.",
4484
+ description: "Assistive text below the box, left-aligned. `sys.typo.body.sm`, `sys.color.text.subtle`; on the `error` appearance the colour re-tones to `sys.color.text.danger` so the message reads as the error caption. Referenced by the input's `aria-describedby`. Not rendered when a `maxLength` count is present, and intentionally omittable on every appearance (including `error`) \u2014 pass nothing and the field renders without an assistive rung.",
4303
4485
  accepts: [
4304
4486
  "text"
4305
4487
  ]
4306
4488
  },
4307
4489
  count: {
4308
4490
  required: false,
4309
- description: '`current/max` character count below the box, right-aligned, present when `maxLength` is set. `sys.typo.body.sm`, `sys.color.onSurfaceVariant`; the current-count number is `sys.typo.label.md` weight in `sys.color.onSurface`. Referenced by the input\'s `aria-describedby`; updates `aria-live="polite"`.',
4491
+ description: '`current/max` character count below the box, right-aligned, present when `maxLength` is set. `sys.typo.body.sm`, `sys.color.text.subtle`; the current-count number is `sys.typo.label.md` weight in `sys.color.text.default`. Referenced by the input\'s `aria-describedby`; updates `aria-live="polite"`.',
4310
4492
  accepts: [
4311
4493
  "text"
4312
4494
  ]
@@ -4319,7 +4501,7 @@ var input_spec_default = {
4319
4501
  slotGap: "sys.layout.inline.md",
4320
4502
  radius: "sys.radius.md",
4321
4503
  borderWidth: "sys.borderWidth.hairline",
4322
- activeStrokeWeight: "sys.borderWidth.thin",
4504
+ activeStrokeWeight: "sys.borderWidth.hairline",
4323
4505
  groupGap: "sys.layout.stack.xs",
4324
4506
  labelTypo: "sys.typo.label.md",
4325
4507
  helperTypo: "sys.typo.body.sm",
@@ -4329,28 +4511,28 @@ var input_spec_default = {
4329
4511
  iconSize: "sys.icon.md"
4330
4512
  },
4331
4513
  groupColors: {
4332
- label: "sys.color.onSurface",
4333
- helper: "sys.color.onSurfaceVariant",
4334
- helperError: "sys.color.error",
4335
- count: "sys.color.onSurfaceVariant",
4336
- countCurrent: "sys.color.onSurface"
4514
+ label: "sys.color.text.default",
4515
+ helper: "sys.color.text.subtle",
4516
+ helperError: "sys.color.text.danger",
4517
+ count: "sys.color.text.subtle",
4518
+ countCurrent: "sys.color.text.default"
4337
4519
  },
4338
4520
  appearances: {
4339
4521
  default: {
4340
4522
  background: "transparent",
4341
- text: "sys.color.onSurface",
4342
- placeholder: "sys.color.outline",
4343
- borderRest: "sys.color.outlineVariant",
4344
- borderHover: "sys.color.outline",
4345
- borderActive: "sys.color.onSurface"
4523
+ text: "sys.color.text.default",
4524
+ placeholder: "sys.color.border.boldest",
4525
+ borderRest: "sys.color.border.default",
4526
+ borderHover: "sys.color.border.boldest",
4527
+ borderActive: "sys.color.border.focused"
4346
4528
  },
4347
4529
  error: {
4348
- background: "sys.color.errorContainer",
4349
- text: "sys.color.onErrorContainer",
4350
- placeholder: "sys.color.onErrorContainer",
4351
- borderRest: "sys.color.error",
4352
- borderHover: "sys.color.error",
4353
- borderActive: "sys.color.error"
4530
+ background: "sys.color.background.danger",
4531
+ text: "sys.color.text.danger",
4532
+ placeholder: "sys.color.text.danger",
4533
+ borderRest: "sys.color.border.danger",
4534
+ borderHover: "sys.color.border.danger",
4535
+ borderActive: "sys.color.border.danger"
4354
4536
  }
4355
4537
  },
4356
4538
  states: {
@@ -4371,35 +4553,43 @@ var input_spec_default = {
4371
4553
  nestedActionScope: "The pressed overlay (and hover stroke) is suppressed while the pointer presses / hovers the trailing clear button \u2014 that '\xD7' is an independent nested action, so the small control owns the state and the large field does not also read as pressed. The visual-state boundary matches the action boundary."
4372
4554
  },
4373
4555
  active: {
4556
+ isFocusState: true,
4374
4557
  overlay: null,
4375
4558
  border: "borderActive",
4376
4559
  strokeWeight: "activeStrokeWeight",
4377
4560
  caret: "visible",
4378
4561
  showsClearWhenValue: true,
4379
- note: "The stroke steps from its rest `hairline` (1px) to `activeStrokeWeight` (2px) and re-tones to `borderActive` \u2014 but it is an inset `box-shadow`, not a `border`, so the box model is untouched: the field's footprint, caret, and text position are pixel-stable as it goes active. Nothing reflows. The clear button is shown only in this state (and only when the value is non-empty)."
4562
+ focusRing: {
4563
+ composition: "outward",
4564
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
4565
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.border.focused" },
4566
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.border.focused" }
4567
+ },
4568
+ note: "This IS the field's keyboard/input-focus state \u2014 `active` (caret visible, input engaged) and `:focus-visible` coincide for a text field, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. The stroke stays at `hairline` (1px) and re-tones to `borderActive` on active (no thickening) \u2014 but it is an inset `box-shadow`, not a `border`, so the box model is untouched: the field's footprint, caret, and text position are pixel-stable as it goes active. Nothing reflows. The clear button is shown only in this state (and only when the value is non-empty)."
4380
4569
  },
4381
4570
  disabled: {
4382
4571
  overlay: null,
4383
- background: "sys.color.surfaceContainerLow",
4384
- containerOpacity: "sys.state.disabled",
4572
+ background: "sys.color.background.disabled",
4573
+ text: "sys.color.text.disabled",
4574
+ placeholder: "sys.color.text.disabled",
4575
+ border: "sys.color.border.bold",
4385
4576
  suppressClear: true,
4386
4577
  suppressFocusRing: true,
4387
- cursor: "not-allowed"
4578
+ cursor: "not-allowed",
4579
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
4388
4580
  }
4389
4581
  },
4390
4582
  focusIndicator: {
4391
4583
  description: "Keyboard-focus visual \u2014 an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the field is in (most commonly `active` since focus implies the caret is in the box). The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
4392
4584
  composition: "outward",
4393
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
4585
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
4394
4586
  overlay: {
4395
4587
  color: "label",
4396
4588
  opacity: "sys.state.focus"
4397
4589
  },
4398
4590
  ring: {
4399
- outerWidth: "sys.borderWidth.thin",
4400
- outerColor: "sys.color.focus",
4401
- insetWidth: "sys.borderWidth.hairline",
4402
- insetColor: "sys.color.focusInset"
4591
+ width: "sys.borderWidth.hairline",
4592
+ color: "sys.color.border.focused"
4403
4593
  },
4404
4594
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
4405
4595
  },
@@ -4412,7 +4602,7 @@ var input_spec_default = {
4412
4602
  },
4413
4603
  forbidden: [
4414
4604
  "raw <input> styled with Tailwind / inline color \u2014 the input is wrapped in the chorus-field chrome that owns the stroke",
4415
- "active-state stroke painted with sys.color.primary / a container tier \u2014 the active stroke is sys.color.onSurface (default appearance) or sys.color.error (error appearance), never primary or a container tier",
4605
+ "active-state stroke painted with sys.color.background.primary / a container tier \u2014 the active stroke is sys.color.text.default (default appearance) or sys.color.text.danger (error appearance), never primary or a container tier",
4416
4606
  "stroke painted via `border:` \u2014 stroke is an inset box-shadow on the field",
4417
4607
  "helper text rendered outside the helperText slot"
4418
4608
  ]
@@ -4457,7 +4647,7 @@ var textarea_spec_default = {
4457
4647
  helper: {
4458
4648
  type: "node",
4459
4649
  optional: true,
4460
- description: "Assistive text rendered below the field box, left-aligned. Same rules as [input.helper](./input.md): mutually exclusive with `maxLength`, optional on every appearance, re-tones to `sys.color.error` on the error appearance."
4650
+ description: "Assistive text rendered below the field box, left-aligned. Same rules as [input.helper](./input.md): mutually exclusive with `maxLength`, optional on every appearance, re-tones to `sys.color.text.danger` on the error appearance."
4461
4651
  },
4462
4652
  maxLength: {
4463
4653
  type: "number",
@@ -4482,7 +4672,7 @@ var textarea_spec_default = {
4482
4672
  },
4483
4673
  label: {
4484
4674
  required: false,
4485
- description: "Visible label above the box. `sys.typo.label.md`, `sys.color.onSurface`. Associated with the textarea via `htmlFor`.",
4675
+ description: "Visible label above the box. `sys.typo.label.md`, `sys.color.text.default`. Associated with the textarea via `htmlFor`.",
4486
4676
  accepts: ["text"]
4487
4677
  },
4488
4678
  container: {
@@ -4513,7 +4703,7 @@ var textarea_spec_default = {
4513
4703
  slotGap: "sys.layout.inline.md",
4514
4704
  radius: "sys.radius.md",
4515
4705
  borderWidth: "sys.borderWidth.hairline",
4516
- activeStrokeWeight: "sys.borderWidth.thin",
4706
+ activeStrokeWeight: "sys.borderWidth.hairline",
4517
4707
  groupGap: "sys.layout.stack.xs",
4518
4708
  labelTypo: "sys.typo.label.md",
4519
4709
  helperTypo: "sys.typo.body.sm",
@@ -4525,28 +4715,28 @@ var textarea_spec_default = {
4525
4715
  resize: "vertical"
4526
4716
  },
4527
4717
  groupColors: {
4528
- label: "sys.color.onSurface",
4529
- helper: "sys.color.onSurfaceVariant",
4530
- helperError: "sys.color.error",
4531
- count: "sys.color.onSurfaceVariant",
4532
- countCurrent: "sys.color.onSurface"
4718
+ label: "sys.color.text.default",
4719
+ helper: "sys.color.text.subtle",
4720
+ helperError: "sys.color.text.danger",
4721
+ count: "sys.color.text.subtle",
4722
+ countCurrent: "sys.color.text.default"
4533
4723
  },
4534
4724
  appearances: {
4535
4725
  default: {
4536
4726
  background: "transparent",
4537
- text: "sys.color.onSurface",
4538
- placeholder: "sys.color.outline",
4539
- borderRest: "sys.color.outlineVariant",
4540
- borderHover: "sys.color.outline",
4541
- borderActive: "sys.color.onSurface"
4727
+ text: "sys.color.text.default",
4728
+ placeholder: "sys.color.border.boldest",
4729
+ borderRest: "sys.color.border.default",
4730
+ borderHover: "sys.color.border.boldest",
4731
+ borderActive: "sys.color.border.focused"
4542
4732
  },
4543
4733
  error: {
4544
- background: "sys.color.errorContainer",
4545
- text: "sys.color.onErrorContainer",
4546
- placeholder: "sys.color.onErrorContainer",
4547
- borderRest: "sys.color.error",
4548
- borderHover: "sys.color.error",
4549
- borderActive: "sys.color.error"
4734
+ background: "sys.color.background.danger",
4735
+ text: "sys.color.text.danger",
4736
+ placeholder: "sys.color.text.danger",
4737
+ borderRest: "sys.color.border.danger",
4738
+ borderHover: "sys.color.border.danger",
4739
+ borderActive: "sys.color.border.danger"
4550
4740
  }
4551
4741
  },
4552
4742
  states: {
@@ -4558,33 +4748,41 @@ var textarea_spec_default = {
4558
4748
  nestedActionScope: "The pressed overlay (and hover stroke) is suppressed while the pointer presses / hovers the trailing clear button \u2014 that '\xD7' is an independent nested action, so the small control owns the state and the large field does not also read as pressed. The visual-state boundary matches the action boundary."
4559
4749
  },
4560
4750
  active: {
4751
+ isFocusState: true,
4561
4752
  overlay: null,
4562
4753
  border: "borderActive",
4563
4754
  strokeWeight: "activeStrokeWeight",
4564
4755
  caret: "visible",
4565
- note: "Stroke steps from `hairline` (1px) to `activeStrokeWeight` (2px) as an inset box-shadow \u2014 same pixel-stable contract as input."
4756
+ focusRing: {
4757
+ composition: "outward",
4758
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
4759
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.border.focused" },
4760
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.border.focused" }
4761
+ },
4762
+ note: "This IS the field's keyboard/input-focus state \u2014 `active` (caret visible, input engaged) and `:focus-visible` coincide for a text field, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. Stroke stays at `hairline` (1px), re-toning to `borderActive` as an inset box-shadow (no thickening) \u2014 same pixel-stable contract as input."
4566
4763
  },
4567
4764
  disabled: {
4568
4765
  overlay: null,
4569
- background: "sys.color.surfaceContainerLow",
4570
- containerOpacity: "sys.state.disabled",
4766
+ background: "sys.color.background.disabled",
4767
+ text: "sys.color.text.disabled",
4768
+ placeholder: "sys.color.text.disabled",
4769
+ border: "sys.color.border.bold",
4571
4770
  suppressFocusRing: true,
4572
- cursor: "not-allowed"
4771
+ cursor: "not-allowed",
4772
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
4573
4773
  }
4574
4774
  },
4575
4775
  focusIndicator: {
4576
- description: "Same outward 3-layer ring as input.focusIndicator.",
4776
+ description: "Same outward single ring as input.focusIndicator.",
4577
4777
  composition: "outward",
4578
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
4778
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
4579
4779
  overlay: {
4580
4780
  color: "label",
4581
4781
  opacity: "sys.state.focus"
4582
4782
  },
4583
4783
  ring: {
4584
- outerWidth: "sys.borderWidth.thin",
4585
- outerColor: "sys.color.focus",
4586
- insetWidth: "sys.borderWidth.hairline",
4587
- insetColor: "sys.color.focusInset"
4784
+ width: "sys.borderWidth.hairline",
4785
+ color: "sys.color.border.focused"
4588
4786
  },
4589
4787
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
4590
4788
  },
@@ -4641,7 +4839,7 @@ var search_spec_default = {
4641
4839
  },
4642
4840
  leading: {
4643
4841
  required: true,
4644
- description: "The leading `SearchIcon` glyph pinned at the box's inner-left edge. Inherits the field's text colour (`sys.color.onSurface`); decorative \u2014 not a real button, has `aria-hidden`. 16px (`sys.icon.md`), matching the clear button's footprint so the two affixes balance.",
4842
+ description: "The leading `SearchIcon` glyph pinned at the box's inner-left edge. Inherits the field's text colour (`sys.color.text.default`); decorative \u2014 not a real button, has `aria-hidden`. 16px (`sys.icon.md`), matching the clear button's footprint so the two affixes balance.",
4645
4843
  intrinsic: true
4646
4844
  },
4647
4845
  input: {
@@ -4664,18 +4862,18 @@ var search_spec_default = {
4664
4862
  slotGap: "sys.layout.inline.md",
4665
4863
  radius: "sys.radius.full",
4666
4864
  borderWidth: "sys.borderWidth.hairline",
4667
- activeStrokeWeight: "sys.borderWidth.thin",
4865
+ activeStrokeWeight: "sys.borderWidth.hairline",
4668
4866
  textTypo: "sys.typo.body.md",
4669
4867
  iconSize: "sys.icon.md"
4670
4868
  },
4671
4869
  appearances: {
4672
4870
  default: {
4673
4871
  background: "transparent",
4674
- text: "sys.color.onSurface",
4675
- placeholder: "sys.color.outline",
4676
- borderRest: "sys.color.outlineVariant",
4677
- borderHover: "sys.color.outline",
4678
- borderActive: "sys.color.onSurface"
4872
+ text: "sys.color.text.default",
4873
+ placeholder: "sys.color.border.boldest",
4874
+ borderRest: "sys.color.border.default",
4875
+ borderHover: "sys.color.border.boldest",
4876
+ borderActive: "sys.color.border.focused"
4679
4877
  }
4680
4878
  },
4681
4879
  states: {
@@ -4696,35 +4894,43 @@ var search_spec_default = {
4696
4894
  nestedActionScope: "The pressed overlay (and hover stroke) is suppressed while the pointer presses / hovers the trailing clear button \u2014 that '\xD7' is an independent nested action, so the small control owns the state and the large field does not also read as pressed. The visual-state boundary matches the action boundary."
4697
4895
  },
4698
4896
  active: {
4897
+ isFocusState: true,
4699
4898
  overlay: null,
4700
4899
  border: "borderActive",
4701
4900
  strokeWeight: "activeStrokeWeight",
4702
4901
  caret: "visible",
4703
4902
  showsClearWhenValue: true,
4704
- note: "The stroke steps from its rest `hairline` (1px) to `activeStrokeWeight` (2px) and re-tones to `borderActive` \u2014 but it is an inset `box-shadow`, not a `border`, so the box model is untouched: the field's footprint, caret, and text position are pixel-stable as it goes active. Nothing reflows. The clear button is shown only in this state (and only when the value is non-empty)."
4903
+ focusRing: {
4904
+ composition: "outward",
4905
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
4906
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.border.focused" },
4907
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.border.focused" }
4908
+ },
4909
+ note: "This IS the field's keyboard/input-focus state \u2014 `active` (caret visible, input engaged) and `:focus-visible` coincide for a text field, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. The stroke stays at `hairline` (1px) and re-tones to `borderActive` on active (no thickening) \u2014 but it is an inset `box-shadow`, not a `border`, so the box model is untouched: the field's footprint, caret, and text position are pixel-stable as it goes active. Nothing reflows. The clear button is shown only in this state (and only when the value is non-empty)."
4705
4910
  },
4706
4911
  disabled: {
4707
4912
  overlay: null,
4708
- background: "sys.color.surfaceContainerLow",
4709
- containerOpacity: "sys.state.disabled",
4913
+ background: "sys.color.background.disabled",
4914
+ text: "sys.color.text.disabled",
4915
+ placeholder: "sys.color.text.disabled",
4916
+ border: "sys.color.border.bold",
4710
4917
  suppressClear: true,
4711
4918
  suppressFocusRing: true,
4712
- cursor: "not-allowed"
4919
+ cursor: "not-allowed",
4920
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
4713
4921
  }
4714
4922
  },
4715
4923
  focusIndicator: {
4716
4924
  description: "Keyboard-focus visual \u2014 an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the field is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
4717
4925
  composition: "outward",
4718
- compositionReason: "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
4926
+ compositionReason: "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
4719
4927
  overlay: {
4720
4928
  color: "label",
4721
4929
  opacity: "sys.state.focus"
4722
4930
  },
4723
4931
  ring: {
4724
- outerWidth: "sys.borderWidth.thin",
4725
- outerColor: "sys.color.focus",
4726
- insetWidth: "sys.borderWidth.hairline",
4727
- insetColor: "sys.color.focusInset"
4932
+ width: "sys.borderWidth.hairline",
4933
+ color: "sys.color.border.focused"
4728
4934
  },
4729
4935
  trigger: ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
4730
4936
  },
@@ -4780,7 +4986,7 @@ var select_spec_default = {
4780
4986
  leadingIcon: {
4781
4987
  type: "node",
4782
4988
  optional: true,
4783
- description: "Optional 16px (`sys.icon.md`) decorative glyph pinned at the inner-left edge of the field. Tracks the field's active text colour (`sys.color.onSurface` on the default appearance, `sys.color.onErrorContainer` on `error`) so the glyph reads as part of the typed content."
4989
+ description: "Optional 16px (`sys.icon.md`) decorative glyph pinned at the inner-left edge of the field. Tracks the field's active text colour (`sys.color.text.default` on the default appearance, `sys.color.text.danger` on `error`) so the glyph reads as part of the typed content."
4784
4990
  },
4785
4991
  onOpen: {
4786
4992
  type: "function",
@@ -4837,7 +5043,7 @@ var select_spec_default = {
4837
5043
  slotGap: "sys.layout.inline.md",
4838
5044
  radius: "sys.radius.md",
4839
5045
  borderWidth: "sys.borderWidth.hairline",
4840
- activeStrokeWeight: "sys.borderWidth.thin",
5046
+ activeStrokeWeight: "sys.borderWidth.hairline",
4841
5047
  groupGap: "sys.layout.stack.xs",
4842
5048
  labelTypo: "sys.typo.label.md",
4843
5049
  helperTypo: "sys.typo.body.sm",
@@ -4847,28 +5053,28 @@ var select_spec_default = {
4847
5053
  iconSize: "sys.icon.md"
4848
5054
  },
4849
5055
  groupColors: {
4850
- label: "sys.color.onSurface",
4851
- helper: "sys.color.onSurfaceVariant",
4852
- helperError: "sys.color.error",
4853
- count: "sys.color.onSurfaceVariant",
4854
- countCurrent: "sys.color.onSurface"
5056
+ label: "sys.color.text.default",
5057
+ helper: "sys.color.text.subtle",
5058
+ helperError: "sys.color.text.danger",
5059
+ count: "sys.color.text.subtle",
5060
+ countCurrent: "sys.color.text.default"
4855
5061
  },
4856
5062
  appearances: {
4857
5063
  default: {
4858
5064
  background: "transparent",
4859
- text: "sys.color.onSurface",
4860
- placeholder: "sys.color.outline",
4861
- borderRest: "sys.color.outlineVariant",
4862
- borderHover: "sys.color.outline",
4863
- borderActive: "sys.color.onSurface"
5065
+ text: "sys.color.text.default",
5066
+ placeholder: "sys.color.border.boldest",
5067
+ borderRest: "sys.color.border.default",
5068
+ borderHover: "sys.color.border.boldest",
5069
+ borderActive: "sys.color.border.focused"
4864
5070
  },
4865
5071
  error: {
4866
- background: "sys.color.errorContainer",
4867
- text: "sys.color.onErrorContainer",
4868
- placeholder: "sys.color.onErrorContainer",
4869
- borderRest: "sys.color.error",
4870
- borderHover: "sys.color.error",
4871
- borderActive: "sys.color.error"
5072
+ background: "sys.color.background.danger",
5073
+ text: "sys.color.text.danger",
5074
+ placeholder: "sys.color.text.danger",
5075
+ borderRest: "sys.color.border.danger",
5076
+ borderHover: "sys.color.border.danger",
5077
+ borderActive: "sys.color.border.danger"
4872
5078
  }
4873
5079
  },
4874
5080
  states: {
@@ -4888,20 +5094,31 @@ var select_spec_default = {
4888
5094
  }
4889
5095
  },
4890
5096
  active: {
5097
+ isFocusState: true,
4891
5098
  overlay: null,
4892
5099
  border: "borderActive",
4893
- strokeWeight: "activeStrokeWeight"
5100
+ strokeWeight: "activeStrokeWeight",
5101
+ focusRing: {
5102
+ composition: "outward",
5103
+ layer: "::after overlay \u2014 position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
5104
+ innerCounterRing: { width: "sys.borderWidth.hairline", color: "sys.color.border.focused" },
5105
+ outerRing: { width: "sys.borderWidth.thin", color: "sys.color.border.focused" }
5106
+ },
5107
+ note: "This IS the trigger's keyboard-focus / open state \u2014 `:focus-visible` and the engaged (open) state coincide for the select trigger, so there is no separate `focused` state; the focus ring described here (and in the parallel `focusIndicator` block) is the focus affordance. The stroke re-tones to `borderActive` at `activeStrokeWeight` (1px, = rest) as an inset box-shadow, pixel-stable (no reflow)."
4894
5108
  },
4895
5109
  disabled: {
4896
5110
  overlay: null,
4897
- background: "sys.color.surfaceContainerLow",
4898
- containerOpacity: "sys.state.disabled",
5111
+ background: "sys.color.background.disabled",
5112
+ text: "sys.color.text.disabled",
5113
+ placeholder: "sys.color.text.disabled",
5114
+ border: "sys.color.border.bold",
4899
5115
  suppressFocusRing: true,
4900
- cursor: "not-allowed"
5116
+ cursor: "not-allowed",
5117
+ note: "Explicit disabled (no opacity): neutral disabled fill + bold border + disabled text/placeholder."
4901
5118
  }
4902
5119
  },
4903
5120
  focusIndicator: {
4904
- description: "Same keyboard-focus indicator as Input \u2014 outward two-layer ring composed over the active stroke.",
5121
+ description: "Same keyboard-focus indicator as Input \u2014 outward single ring composed over the active stroke.",
4905
5122
  composition: "outward",
4906
5123
  compositionReason: "Action affordance with breathing room around it.",
4907
5124
  overlay: {
@@ -4909,10 +5126,8 @@ var select_spec_default = {
4909
5126
  opacity: "sys.state.focus"
4910
5127
  },
4911
5128
  ring: {
4912
- outerWidth: "sys.borderWidth.thin",
4913
- outerColor: "sys.color.focus",
4914
- insetWidth: "sys.borderWidth.hairline",
4915
- insetColor: "sys.color.focusInset"
5129
+ width: "sys.borderWidth.hairline",
5130
+ color: "sys.color.border.focused"
4916
5131
  },
4917
5132
  trigger: ":focus-visible"
4918
5133
  },
@@ -4987,17 +5202,17 @@ function FormFieldBox({
4987
5202
  "--field-group-gap": tokenToCss(spec.sizing.groupGap),
4988
5203
  "--field-bg": tokenToCss(app.background),
4989
5204
  "--field-bg-disabled": tokenToCss(spec.states.disabled.background),
4990
- "--field-text": tokenToCss(app.text),
4991
- "--field-placeholder": tokenToCss(app.placeholder),
4992
- "--field-border": tokenToCss(app.borderRest),
5205
+ // Disabled swaps text / placeholder / border to the explicit disabled
5206
+ // tokens (no opacity) — the field box paints them via these vars.
5207
+ "--field-text": tokenToCss(isDisabled ? spec.states.disabled.text : app.text),
5208
+ "--field-placeholder": tokenToCss(
5209
+ isDisabled ? spec.states.disabled.placeholder : app.placeholder
5210
+ ),
5211
+ "--field-border": tokenToCss(isDisabled ? spec.states.disabled.border : app.borderRest),
4993
5212
  "--field-border-hover": tokenToCss(app.borderHover),
4994
5213
  "--field-border-active": tokenToCss(app.borderActive),
4995
5214
  "--field-overlay-pressed": tokenToCss(spec.states.pressed.overlay.opacity),
4996
- "--field-disabled-opacity": tokenToCss(spec.states.disabled.containerOpacity),
4997
- "--field-focus-outer-width": tokenToCss(spec.focusIndicator.ring.outerWidth),
4998
- "--field-focus-outer-color": tokenToCss(spec.focusIndicator.ring.outerColor),
4999
- "--field-focus-inset-width": tokenToCss(spec.focusIndicator.ring.insetWidth),
5000
- "--field-focus-inset-color": tokenToCss(spec.focusIndicator.ring.insetColor),
5215
+ "--field-focus-outer-color": tokenToCss(spec.focusIndicator.ring.color),
5001
5216
  ...typoStyles(spec.sizing.textTypo)
5002
5217
  };
5003
5218
  const handleChange = (event) => {
@@ -5511,6 +5726,58 @@ function SkeletonGroup({
5511
5726
  }
5512
5727
  );
5513
5728
  }
5729
+
5730
+ // ../../schema/components/spinner/spinner.spec.json
5731
+ var spinner_spec_default = {
5732
+ sizes: {
5733
+ medium: {
5734
+ diameter: "sys.icon.lg",
5735
+ labelTypo: "sys.typo.body.sm",
5736
+ gap: "sys.layout.inline.sm"
5737
+ },
5738
+ small: {
5739
+ diameter: "sys.icon.md",
5740
+ labelTypo: "sys.typo.body.sm",
5741
+ gap: "sys.layout.inline.sm"
5742
+ }
5743
+ }};
5744
+ function sizingStyle4(spec, size) {
5745
+ const s2 = spec.sizes[size] ?? spec.sizes.medium;
5746
+ return {
5747
+ "--spinner-diameter": tokenToCss(s2.diameter),
5748
+ "--spinner-gap": tokenToCss(s2.gap)
5749
+ };
5750
+ }
5751
+ function Spinner({
5752
+ size = "medium",
5753
+ label,
5754
+ className,
5755
+ style,
5756
+ "aria-label": ariaLabel,
5757
+ ...rest
5758
+ }) {
5759
+ var _a;
5760
+ const labelTypo = ((_a = spinner_spec_default.sizes[size]) == null ? void 0 : _a.labelTypo) ?? spinner_spec_default.sizes.medium.labelTypo;
5761
+ const a11yLabel = label != null ? void 0 : ariaLabel ?? "Loading";
5762
+ return /* @__PURE__ */ jsxs(
5763
+ "span",
5764
+ {
5765
+ role: "status",
5766
+ "aria-label": a11yLabel,
5767
+ className: joinClasses(
5768
+ "chorus-spinner",
5769
+ `chorus-spinner--${size}`,
5770
+ className
5771
+ ),
5772
+ style: { ...sizingStyle4(spinner_spec_default, size), ...style },
5773
+ ...rest,
5774
+ children: [
5775
+ /* @__PURE__ */ jsx("span", { className: "chorus-spinner__arc", "aria-hidden": "true" }),
5776
+ label != null ? /* @__PURE__ */ jsx("span", { className: "chorus-spinner__label", style: typoStyles(labelTypo), children: label }) : null
5777
+ ]
5778
+ }
5779
+ );
5780
+ }
5514
5781
  function StatusTag({
5515
5782
  appearance = "neutral",
5516
5783
  children,
@@ -5537,7 +5804,7 @@ var sub_spec_default = {
5537
5804
  appearance: {
5538
5805
  containerFill: "transparent",
5539
5806
  labelTypo: "sys.typo.label.md",
5540
- labelColor: "sys.color.onSurfaceVariant",
5807
+ labelColor: "sys.color.text.subtle",
5541
5808
  paddingInline: "sys.layout.container.md",
5542
5809
  paddingBlockStart: "sys.layout.stack.lg",
5543
5810
  paddingBlockEnd: "sys.layout.stack.xs",
@@ -5896,6 +6163,6 @@ function Tooltip({
5896
6163
  );
5897
6164
  }
5898
6165
 
5899
- export { Accordion, Banner as Alert, NavigationBar as AppBar, Thumbnail as Avatar, AvatarRail, Badge, Banner, TabBar as BottomNav, BottomSheet, Bubble, Button, ButtonGroup, Carousel, SuggestionList as ChannelList, AvatarRail as ChannelRail, Chip, Dialog, DirectoryList, Divider, BottomSheet as Drawer, Feed, FeedAd, FeedGroup, FormField, FormFieldGroup, Header, Input, List, Metadata, NavCard, NavCardGroup, NavList, NavigationBar, PageShell, Pagination, PostCarousel, ProfileCarousel, ProfileHeader, Progress, SearchBar2 as SearchBar, Carousel as Section, Select, BottomSheet as Sheet, SideSheet as SideDrawer, SideSheet, SideSheetGroup, Skeleton, SkeletonGroup, StatusTag, SubHeader, SuggestionList, Switch, Tab, TabBar, Tabs, Textarea, Thumbnail, Toast, Tooltip };
6166
+ export { Accordion, Banner as Alert, NavigationBar as AppBar, Thumbnail as Avatar, AvatarRail, Badge, Banner, TabBar as BottomNav, BottomSheet, Bubble, Button, ButtonGroup, Carousel, SuggestionList as ChannelList, AvatarRail as ChannelRail, Chip, Dialog, DirectoryList, Divider, BottomSheet as Drawer, EmptyState, Feed, FeedAd, FeedGroup, FormField, FormFieldGroup, Header, Input, List, Metadata, NavCard, NavCardGroup, NavList, NavigationBar, PageShell, Pagination, PostCarousel, ProfileCarousel, ProfileHeader, Progress, SearchBar2 as SearchBar, Carousel as Section, Select, BottomSheet as Sheet, SideSheet as SideDrawer, SideSheet, SideSheetGroup, Skeleton, SkeletonGroup, Spinner, StatusTag, SubHeader, SuggestionList, Switch, Tab, TabBar, Tabs, Textarea, Thumbnail, Toast, Tooltip };
5900
6167
  //# sourceMappingURL=index.js.map
5901
6168
  //# sourceMappingURL=index.js.map