@sableui/react 0.9.1 → 0.10.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 (212) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +95 -0
  3. package/CONTRIBUTING.md +2 -2
  4. package/DESIGN.md +55 -16
  5. package/README.md +12 -0
  6. package/dist/{_contract-C2M1SjBf.d.ts → _contract-CkqXDVww.d.ts} +25 -2
  7. package/dist/blocks/danger-zone-block.js +3 -3
  8. package/dist/blocks/danger-zone-block.meta.js +1 -1
  9. package/dist/blocks/faq-block.js +3 -3
  10. package/dist/blocks/goal-progress-block.js +1 -1
  11. package/dist/blocks/notification-list-block.js +1 -1
  12. package/dist/blocks/pricing-table-block.js +12 -12
  13. package/dist/blocks/product-card-block.js +3 -3
  14. package/dist/blocks/product-card-block.meta.js +3 -3
  15. package/dist/blocks/profile-card-block.js +1 -1
  16. package/dist/blocks/profile-form-block.js +3 -3
  17. package/dist/blocks/revenue-breakdown-block.js +5 -5
  18. package/dist/blocks/stat-row-block.js +3 -3
  19. package/dist/blocks/task-list-block.js +2 -2
  20. package/dist/blocks/team-members-block.js +1 -1
  21. package/dist/blocks/wallet-block.js +4 -4
  22. package/dist/blocks/wallet-block.meta.js +3 -3
  23. package/dist/components/accordion/accordion.meta.js +2 -2
  24. package/dist/components/app-header/app-header.meta.js +1 -1
  25. package/dist/components/app-shell/app-shell.meta.js +2 -2
  26. package/dist/components/area-chart/area-chart.meta.js +3 -3
  27. package/dist/components/badge/badge.js +13 -4
  28. package/dist/components/badge/badge.meta.js +24 -13
  29. package/dist/components/badge/badge.variants.js +37 -23
  30. package/dist/components/banner/banner.meta.js +8 -8
  31. package/dist/components/banner/banner.variants.js +3 -3
  32. package/dist/components/bar-chart/bar-chart.meta.js +4 -4
  33. package/dist/components/breadcrumbs/breadcrumbs.meta.js +1 -1
  34. package/dist/components/button/button.meta.js +3 -3
  35. package/dist/components/card/card.js +16 -7
  36. package/dist/components/card/card.meta.js +4 -4
  37. package/dist/components/checkbox-card/checkbox-card.meta.js +10 -10
  38. package/dist/components/checkbox-card/checkbox-card.variants.js +15 -15
  39. package/dist/components/code-block/code-block.meta.js +1 -1
  40. package/dist/components/command-menu/command-menu.meta.js +1 -1
  41. package/dist/components/context-card/context-card.meta.js +2 -2
  42. package/dist/components/destructive-action-modal/destructive-action-modal.meta.js +1 -1
  43. package/dist/components/dialog/dialog.meta.js +2 -2
  44. package/dist/components/disclosure-picker/disclosure-picker.js +48 -2
  45. package/dist/components/disclosure-picker/disclosure-picker.meta.js +3 -2
  46. package/dist/components/empty-state/empty-state.meta.js +4 -4
  47. package/dist/components/gradient-art/gradient-art.meta.js +3 -3
  48. package/dist/components/label/label.meta.js +1 -1
  49. package/dist/components/line-chart/line-chart.meta.js +3 -3
  50. package/dist/components/meter/meter.js +1 -1
  51. package/dist/components/meter/meter.meta.js +2 -2
  52. package/dist/components/meter/meter.variants.js +1 -1
  53. package/dist/components/middle-truncate/middle-truncate.meta.js +4 -4
  54. package/dist/components/note/note.meta.js +3 -3
  55. package/dist/components/page-header/page-header.js +1 -1
  56. package/dist/components/pagination/pagination.meta.js +2 -2
  57. package/dist/components/phone/phone.meta.js +1 -1
  58. package/dist/components/pie-chart/pie-chart.meta.js +3 -3
  59. package/dist/components/pill/pill.js +4 -2
  60. package/dist/components/pill/pill.meta.js +19 -2
  61. package/dist/components/pill/pill.variants.js +23 -2
  62. package/dist/components/popover/popover.meta.js +2 -2
  63. package/dist/components/radar-chart/radar-chart.meta.js +3 -3
  64. package/dist/components/radial-chart/radial-chart.meta.js +3 -3
  65. package/dist/components/radio/radio.meta.js +2 -2
  66. package/dist/components/radio-card/radio-card.meta.js +6 -6
  67. package/dist/components/radio-card/radio-card.variants.js +15 -15
  68. package/dist/components/scroller/scroller.meta.js +1 -1
  69. package/dist/components/search-input/search-input.meta.js +1 -1
  70. package/dist/components/section-header/section-header.js +1 -1
  71. package/dist/components/segmented-bar/segmented-bar.js +1 -1
  72. package/dist/components/segmented-bar/segmented-bar.meta.js +6 -6
  73. package/dist/components/segmented-bar/segmented-bar.variants.js +4 -4
  74. package/dist/components/select/select.meta.js +2 -2
  75. package/dist/components/sheet/sheet.meta.js +1 -1
  76. package/dist/components/side-nav/side-nav.meta.js +1 -1
  77. package/dist/components/sidebar/sidebar.meta.js +1 -1
  78. package/dist/components/sparkline/sparkline.meta.js +4 -4
  79. package/dist/components/spinner/spinner.meta.js +1 -1
  80. package/dist/components/status-dot/status-dot.js +4 -1
  81. package/dist/components/status-dot/status-dot.meta.js +8 -16
  82. package/dist/components/status-dot/status-dot.variants.js +13 -4
  83. package/dist/components/table/table.meta.js +4 -4
  84. package/dist/components/tabs/tabs.meta.js +1 -1
  85. package/dist/components/textarea/textarea.meta.js +2 -2
  86. package/dist/components/toast/toast.meta.js +1 -1
  87. package/dist/components/tooltip/tooltip.meta.js +2 -2
  88. package/dist/file-hashes.json +120 -118
  89. package/dist/foundations/spacing.js +8 -8
  90. package/dist/index.d.ts +137 -72
  91. package/dist/index.js +12 -1
  92. package/dist/lib/chart/types.js +3 -3
  93. package/dist/lib/palette.js +103 -0
  94. package/dist/lib/selection-card.js +4 -4
  95. package/dist/palette.css +669 -0
  96. package/dist/patterns/auth-page.js +6 -6
  97. package/dist/patterns/auth-page.meta.js +3 -3
  98. package/dist/patterns/brand-landing-page.js +2 -2
  99. package/dist/patterns/chat-panel.js +2 -2
  100. package/dist/patterns/dashboard-page.js +71 -71
  101. package/dist/patterns/dashboard-page.meta.js +5 -5
  102. package/dist/patterns/feed-page.js +25 -25
  103. package/dist/patterns/feed-page.meta.js +3 -3
  104. package/dist/patterns/landing-page.js +1 -1
  105. package/dist/patterns/list-detail-page.js +17 -17
  106. package/dist/patterns/settings-page.js +17 -17
  107. package/dist/presets/index.d.ts +1 -1
  108. package/dist/presets/index.js +17 -2
  109. package/dist/styles.css +115 -50
  110. package/dist/{theme-provider-S0NVJs2Y.d.ts → theme-provider-CdSicAad.d.ts} +1 -1
  111. package/dist/themes/_contract.js +22 -8
  112. package/dist/themes/atelier/atelier.css +13 -13
  113. package/dist/themes/atelier/index.d.ts +1 -1
  114. package/dist/themes/ember/ember.css +13 -13
  115. package/dist/themes/ember/index.d.ts +1 -1
  116. package/dist/themes/index.d.ts +2 -2
  117. package/dist/themes/meridian/index.d.ts +1 -1
  118. package/dist/themes/meridian/meridian.css +13 -13
  119. package/dist/themes/poppy/index.d.ts +1 -1
  120. package/dist/themes/poppy/poppy.css +19 -16
  121. package/dist/themes/prism/index.d.ts +1 -1
  122. package/dist/themes/prism/prism.css +18 -15
  123. package/dist/themes/sable/index.d.ts +1 -1
  124. package/dist/themes/sable/sable.css +18 -22
  125. package/dist/themes/verdant/index.d.ts +1 -1
  126. package/dist/themes/verdant/verdant.css +15 -15
  127. package/llms/blocks/danger-zone-block.md +4 -4
  128. package/llms/blocks/faq-block.md +3 -3
  129. package/llms/blocks/goal-progress-block.md +1 -1
  130. package/llms/blocks/notification-list-block.md +1 -1
  131. package/llms/blocks/pricing-table-block.md +12 -12
  132. package/llms/blocks/product-card-block.md +6 -6
  133. package/llms/blocks/profile-card-block.md +1 -1
  134. package/llms/blocks/profile-form-block.md +3 -3
  135. package/llms/blocks/revenue-breakdown-block.md +5 -5
  136. package/llms/blocks/stat-row-block.md +3 -3
  137. package/llms/blocks/task-list-block.md +2 -2
  138. package/llms/blocks/team-members-block.md +1 -1
  139. package/llms/blocks/wallet-block.md +7 -7
  140. package/llms/components/accordion.md +2 -2
  141. package/llms/components/app-header.md +1 -1
  142. package/llms/components/app-shell.md +2 -2
  143. package/llms/components/area-chart.md +1 -1
  144. package/llms/components/badge.md +15 -7
  145. package/llms/components/banner.md +6 -6
  146. package/llms/components/bar-chart.md +2 -2
  147. package/llms/components/breadcrumbs.md +1 -1
  148. package/llms/components/button.md +3 -3
  149. package/llms/components/card.md +4 -4
  150. package/llms/components/checkbox-card.md +5 -5
  151. package/llms/components/code-block.md +1 -1
  152. package/llms/components/command-menu.md +1 -1
  153. package/llms/components/context-card.md +2 -2
  154. package/llms/components/destructive-action-modal.md +1 -1
  155. package/llms/components/dialog.md +2 -2
  156. package/llms/components/disclosure-picker.md +2 -1
  157. package/llms/components/empty-state.md +4 -4
  158. package/llms/components/gradient-art.md +3 -3
  159. package/llms/components/label.md +1 -1
  160. package/llms/components/line-chart.md +1 -1
  161. package/llms/components/meter.md +2 -2
  162. package/llms/components/middle-truncate.md +5 -5
  163. package/llms/components/note.md +3 -3
  164. package/llms/components/pagination.md +2 -2
  165. package/llms/components/phone.md +1 -1
  166. package/llms/components/pie-chart.md +1 -1
  167. package/llms/components/pill.md +12 -2
  168. package/llms/components/popover.md +2 -2
  169. package/llms/components/radar-chart.md +1 -1
  170. package/llms/components/radial-chart.md +1 -1
  171. package/llms/components/radio-card.md +4 -4
  172. package/llms/components/radio.md +2 -2
  173. package/llms/components/scroller.md +1 -1
  174. package/llms/components/search-input.md +1 -1
  175. package/llms/components/segmented-bar.md +7 -7
  176. package/llms/components/select.md +2 -2
  177. package/llms/components/sheet.md +1 -1
  178. package/llms/components/side-nav.md +1 -1
  179. package/llms/components/sidebar.md +1 -1
  180. package/llms/components/sparkline.md +2 -2
  181. package/llms/components/spinner.md +1 -1
  182. package/llms/components/status-dot.md +4 -4
  183. package/llms/components/table.md +8 -8
  184. package/llms/components/tabs.md +1 -1
  185. package/llms/components/textarea.md +2 -2
  186. package/llms/components/toast.md +1 -1
  187. package/llms/components/tooltip.md +2 -2
  188. package/llms/foundations/spacing.md +7 -7
  189. package/llms/layout/grid.md +1 -1
  190. package/llms/layout/page.md +1 -1
  191. package/llms/layout/region.md +1 -1
  192. package/llms/layout/split-pane.md +1 -1
  193. package/llms/patterns/auth-page.md +11 -11
  194. package/llms/patterns/brand-landing-page.md +2 -2
  195. package/llms/patterns/chat-panel.md +2 -2
  196. package/llms/patterns/dashboard-page.md +78 -78
  197. package/llms/patterns/feed-page.md +31 -31
  198. package/llms/patterns/landing-page.md +1 -1
  199. package/llms/patterns/list-detail-page.md +18 -18
  200. package/llms/patterns/settings-page.md +18 -18
  201. package/llms/reference/token-intent.md +25 -11
  202. package/llms/reference/utilities.md +1 -1
  203. package/llms/tasks/adding-a-component.md +4 -1
  204. package/llms/tasks/building-a-page.md +40 -0
  205. package/llms/tasks/converting-an-app.md +3 -1
  206. package/llms/tasks/using-presets.md +4 -0
  207. package/llms-full.txt +429 -347
  208. package/llms.txt +7 -7
  209. package/package.json +4 -4
  210. package/registry.json +369 -233
  211. package/scripts/lint-rules.mjs +26 -5
  212. package/skills/sable/SKILL.md +14 -3
package/registry.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://sable.design/registry.schema.json",
3
3
  "name": "@sableui/react",
4
- "version": "0.9.1",
4
+ "version": "0.10.0",
5
5
  "description": "Themeable React components, semantic tokens, layout patterns, presets, and package-owned agent skills. Default theme: Sable.",
6
6
  "engine": {
7
7
  "behavior": "@base-ui/react",
@@ -430,22 +430,22 @@
430
430
  ]
431
431
  },
432
432
  {
433
- "name": "tone-standard",
434
- "intent": "Domain tone: the baseline classification in a set. Part of the `tone-*` family, which exists so a product can classify content without inventing colours per feature.",
435
- "whenToUse": "The default category of a classified item — the one that means 'nothing special'.",
433
+ "name": "tone-3",
434
+ "intent": "Extra colour 3: the third, and usually the quietest, category in a classified set.",
435
+ "whenToUse": "A third category, or the baseline one that means 'nothing special'.",
436
436
  "insteadOf": "`muted`. A tone is *semantic classification* that a reader is meant to decode; `muted` is de-emphasis. If a legend would help the user, it is a tone."
437
437
  },
438
438
  {
439
- "name": "tone-collectible",
440
- "intent": "Domain tone: a scarce or owned item.",
441
- "whenToUse": "Marking an item as collectible/owned in a classified set.",
442
- "insteadOf": "`primary`. A tone classifies the *thing*; primary drives an *action*. A collectible badge is not a button."
439
+ "name": "tone-2",
440
+ "intent": "Extra colour 2: the second category in a classified set.",
441
+ "whenToUse": "A second category, distinct from tone-1, in a badge, dot, card or chart.",
442
+ "insteadOf": "`tone-1`. Distinct categories must stay visually distinct."
443
443
  },
444
444
  {
445
- "name": "tone-edition",
446
- "intent": "Domain tone: an edition or release.",
447
- "whenToUse": "Marking editioned content in a classified set.",
448
- "insteadOf": "`tone-collectible`. Distinct classifications must stay visually distinct."
445
+ "name": "tone-1",
446
+ "intent": "Extra colour 1, the lead of the three a theme defines beyond its status colours. The numbered tones exist so a product can classify content (categories, plans, series) without inventing colours per feature; the product decides what each one means.",
447
+ "whenToUse": "The most prominent category in a classified set, or the first series of a chart.",
448
+ "insteadOf": "`primary`. A tone classifies the *thing*; primary drives an *action*. A category badge is not a button."
449
449
  },
450
450
  {
451
451
  "name": "tone-info",
@@ -466,30 +466,30 @@
466
466
  "insteadOf": "`accent`. `accent` is UI state that follows the pointer or selection; `highlight` marks the *content* and persists independently of interaction."
467
467
  },
468
468
  {
469
- "name": "tone-standard-foreground",
470
- "intent": "Ink paired with `tone-standard`. Evaluate the rendered contrast together; never choose it independently.",
471
- "whenToUse": "Whenever the background is `tone-standard`. Use them as a pair — `bg-tone-standard text-tone-standard-foreground` — so a theme can retune the surface and the ink together.",
469
+ "name": "tone-3-foreground",
470
+ "intent": "Ink paired with `tone-3`. Evaluate the rendered contrast together; never choose it independently.",
471
+ "whenToUse": "Whenever the background is `tone-3`. Use them as a pair — `bg-tone-3 text-tone-3-foreground` — so a theme can retune the surface and the ink together.",
472
472
  "insteadOf": "`foreground`. That is the ink for `background` only; on any other surface it is a contrast gamble that happens to pass in the default theme and fails in another.",
473
473
  "pairsWith": [
474
- "tone-standard"
474
+ "tone-3"
475
475
  ]
476
476
  },
477
477
  {
478
- "name": "tone-collectible-foreground",
479
- "intent": "Ink paired with `tone-collectible`. Evaluate the rendered contrast together; never choose it independently.",
480
- "whenToUse": "Whenever the background is `tone-collectible`. Use them as a pair — `bg-tone-collectible text-tone-collectible-foreground` — so a theme can retune the surface and the ink together.",
478
+ "name": "tone-2-foreground",
479
+ "intent": "Ink paired with `tone-2`. Evaluate the rendered contrast together; never choose it independently.",
480
+ "whenToUse": "Whenever the background is `tone-2`. Use them as a pair — `bg-tone-2 text-tone-2-foreground` — so a theme can retune the surface and the ink together.",
481
481
  "insteadOf": "`foreground`. That is the ink for `background` only; on any other surface it is a contrast gamble that happens to pass in the default theme and fails in another.",
482
482
  "pairsWith": [
483
- "tone-collectible"
483
+ "tone-2"
484
484
  ]
485
485
  },
486
486
  {
487
- "name": "tone-edition-foreground",
488
- "intent": "Ink paired with `tone-edition`. Evaluate the rendered contrast together; never choose it independently.",
489
- "whenToUse": "Whenever the background is `tone-edition`. Use them as a pair — `bg-tone-edition text-tone-edition-foreground` — so a theme can retune the surface and the ink together.",
487
+ "name": "tone-1-foreground",
488
+ "intent": "Ink paired with `tone-1`. Evaluate the rendered contrast together; never choose it independently.",
489
+ "whenToUse": "Whenever the background is `tone-1`. Use them as a pair — `bg-tone-1 text-tone-1-foreground` — so a theme can retune the surface and the ink together.",
490
490
  "insteadOf": "`foreground`. That is the ink for `background` only; on any other surface it is a contrast gamble that happens to pass in the default theme and fails in another.",
491
491
  "pairsWith": [
492
- "tone-edition"
492
+ "tone-1"
493
493
  ]
494
494
  },
495
495
  {
@@ -849,7 +849,7 @@
849
849
  "layout": [
850
850
  {
851
851
  "name": "rhythm-unit",
852
- "intent": "The vertical rhythm base — two spacing steps (8px at the default scale). Every vertical measure is a multiple of it, and it follows the density control with the spacing scale.",
852
+ "intent": "The vertical rhythm base — one baseline step (8px at the default scale). Every vertical measure is a multiple of it, and it follows the type and the density control.",
853
853
  "whenToUse": "Through `Stack`/`Row`/`Grid` gap numbers, which are rhythm units rather than pixels. A gap of 2 is 16px at the default scale, and grows or tightens with density.",
854
854
  "insteadOf": "arbitrary vertical spacing. The rhythm is what makes unrelated sections of a page feel like one document."
855
855
  },
@@ -861,7 +861,7 @@
861
861
  },
862
862
  {
863
863
  "name": "grid-gutter",
864
- "intent": "The gap between grid columns.",
864
+ "intent": "The gap between grid columns and rows — 2 baseline steps on phones, 3 on desktop, snapped to the step.",
865
865
  "whenToUse": "`Grid`/`Columns` spacing."
866
866
  },
867
867
  {
@@ -1396,7 +1396,81 @@
1396
1396
  "whenToUse": "Every floating surface — popover, menu, select, tooltip, dialog, sheet, toast, a floating panel — as `border-border-floating` or `ring-border-floating`, together with `bg-popover` and a `shadow-md`+ shadow. Derived for every theme (a faint alpha of the foreground); a theme may tune it with `--theme-border-floating`.",
1397
1397
  "insteadOf": "`border`. A full hairline plus a shadow stacks into a heavy outline around anything that floats. `border` is for resting edges: inputs, cards, badges."
1398
1398
  }
1399
- ]
1399
+ ],
1400
+ "palette": {
1401
+ "about": "More colour for apps, generated from the active theme: neutral and eight hue families, plus a 50–950 scale under every tone. Each family keeps its hue and leans toward the brand (half the gap, at most 15°; --theme-palette-harmony scales it, 0 = own hues). Neutral is the theme's --background mixed toward its --foreground. Steps follow the mode: low steps sit near the page and high ones far from it in light and dark alike, so a pair needs no dark: variant. For decorative and categorical colour (tags, avatars, labels, legends, illustration); surfaces, text and actions take roles. Components never name a step.",
1402
+ "families": [
1403
+ "neutral",
1404
+ "red",
1405
+ "orange",
1406
+ "yellow",
1407
+ "green",
1408
+ "teal",
1409
+ "blue",
1410
+ "purple",
1411
+ "pink"
1412
+ ],
1413
+ "toneScales": [
1414
+ "tone-1",
1415
+ "tone-2",
1416
+ "tone-3",
1417
+ "tone-info",
1418
+ "tone-warning",
1419
+ "success",
1420
+ "destructive"
1421
+ ],
1422
+ "steps": [
1423
+ 50,
1424
+ 100,
1425
+ 200,
1426
+ 300,
1427
+ 400,
1428
+ 500,
1429
+ 600,
1430
+ 700,
1431
+ 800,
1432
+ 900,
1433
+ 950
1434
+ ],
1435
+ "utilities": "bg-blue-100, text-blue-800, border-neutral-200, bg-tone-1-100 — or var(--teal-500). Tailwind's other families (slate, gray, zinc, stone, amber, lime, emerald, cyan, sky, indigo, violet, fuchsia, rose) are switched off.",
1436
+ "pairs": {
1437
+ "tag / label": "bg-<family>-100 text-<family>-800 (held to 4.5:1 in every theme and mode)",
1438
+ "text on the page": "text-<family>-700",
1439
+ "solid fill": "bg-<family>-700 text-<family>-50",
1440
+ "hairline": "border-<family>-300"
1441
+ },
1442
+ "components": "Badge, Pill and StatusDot take a family as tone (tone=\"blue\"): a category with no meaning attached.",
1443
+ "slots": {
1444
+ "about": "data-palette=\"<family>\" on an element fills these from that family, so one set of classes paints any family: bg-palette-wash text-palette-text.",
1445
+ "names": {
1446
+ "palette-wash": 100,
1447
+ "palette-wash-strong": 200,
1448
+ "palette-edge": 300,
1449
+ "palette-dot": 500,
1450
+ "palette-solid": 700,
1451
+ "palette-text": 800,
1452
+ "palette-solid-ink": 50
1453
+ }
1454
+ },
1455
+ "scales": [
1456
+ "neutral",
1457
+ "red",
1458
+ "orange",
1459
+ "yellow",
1460
+ "green",
1461
+ "teal",
1462
+ "blue",
1463
+ "purple",
1464
+ "pink",
1465
+ "tone-1",
1466
+ "tone-2",
1467
+ "tone-3",
1468
+ "tone-info",
1469
+ "tone-warning",
1470
+ "success",
1471
+ "destructive"
1472
+ ]
1473
+ }
1400
1474
  },
1401
1475
  "enforcement": {
1402
1476
  "note": "The design-system hard rules are enforced in CI, not just documented. design-lint is a zero-dependency lexical linter over the package's own src/ (excludes themes/ and *.meta.ts).",
@@ -1698,7 +1772,8 @@
1698
1772
  "The 12-column grid (Grid/Columns) is used WITHIN a region, for genuinely multi-column content. Never use a column span as the page's reading width.",
1699
1773
  "App chrome is not on the grid: Sidebar and rails are fixed-width chrome outside the content grid.",
1700
1774
  "Measure ≠ width: cap text line-length (prose, in ch) independently of a region's layout width.",
1701
- "Every vertical measure is a multiple of --rhythm-unit (8px); Stack/Row gap numbers are rhythm units, not pixels."
1775
+ "One direction is flex (Stack, Row); content that must line up across rows AND columns is Grid/Columns; width is a Region. A card's single column stacks with Stack, not a one-track grid.",
1776
+ "Vertical space is set in lines: the baseline is one line of body text, split into steps (thirds by default, 8px at 15/24). Line heights round to the step, Stack/Row/Grid gap numbers are steps, and even Tailwind spacing steps (p-4 = two steps) stay on it. A rhythm to keep, not a grid to force: components are as tall as their content."
1702
1777
  ],
1703
1778
  "primitives": [
1704
1779
  {
@@ -1796,7 +1871,7 @@
1796
1871
  },
1797
1872
  {
1798
1873
  "label": "Dashboard region (capped at wide)",
1799
- "code": "<Region max=\"wide\"><Grid minItemWidth=\"16rem\"><Card><CardHeader><CardTitle>Revenue</CardTitle></CardHeader></Card><Card><CardHeader><CardTitle>Collects</CardTitle></CardHeader></Card></Grid></Region>",
1874
+ "code": "<Region max=\"wide\"><Grid minItemWidth=\"16rem\"><Card><CardHeader><CardTitle>Revenue</CardTitle></CardHeader></Card><Card><CardHeader><CardTitle>Orders</CardTitle></CardHeader></Card></Grid></Region>",
1800
1875
  "imports": "import { Card, CardHeader, CardTitle, Grid, Region } from \"@sableui/react\""
1801
1876
  },
1802
1877
  {
@@ -1865,7 +1940,7 @@
1865
1940
  "examples": [
1866
1941
  {
1867
1942
  "label": "Feed route",
1868
- "code": "<Page as=\"main\" max=\"feed\"><PageHeader title=\"Home\" description=\"Latest posts from artists you follow.\" /><Card><CardContent><p className=\"text-body-md\">First post.</p></CardContent></Card></Page>",
1943
+ "code": "<Page as=\"main\" max=\"feed\"><PageHeader title=\"Home\" description=\"Latest posts from people you follow.\" /><Card><CardContent><p className=\"text-body-md\">First post.</p></CardContent></Card></Page>",
1869
1944
  "imports": "import { Card, CardContent, Page, PageHeader } from \"@sableui/react\""
1870
1945
  }
1871
1946
  ]
@@ -1961,7 +2036,7 @@
1961
2036
  },
1962
2037
  {
1963
2038
  "label": "Responsive fixed columns",
1964
- "code": "<Grid cols={{ base: 1, md: 2, lg: 4 }}><Badge appearance=\"plain\" tone=\"standard\" dot /><Badge appearance=\"plain\" tone=\"warning\" dot /><Badge appearance=\"plain\" tone=\"neutral\" dot /><Badge appearance=\"plain\" tone=\"destructive\" dot /></Grid>",
2039
+ "code": "<Grid cols={{ base: 1, md: 2, lg: 4 }}><Badge appearance=\"plain\" tone=\"3\" dot /><Badge appearance=\"plain\" tone=\"warning\" dot /><Badge appearance=\"plain\" tone=\"neutral\" dot /><Badge appearance=\"plain\" tone=\"destructive\" dot /></Grid>",
1965
2040
  "imports": "import { Badge, Grid } from \"@sableui/react\""
1966
2041
  }
1967
2042
  ]
@@ -2456,7 +2531,7 @@
2456
2531
  "examples": [
2457
2532
  {
2458
2533
  "label": "Media + detail",
2459
- "code": "<SplitPane ratio=\"2/1\" primary={<AspectBox ratio={1}><img src={post.imageUrl} alt={post.title} className=\"size-full object-cover\" /></AspectBox>} secondary={<Stack gap={2}><h1 className=\"text-heading-1\">{post.title}</h1><p className=\"text-body-md text-muted-foreground\">{post.description}</p><Button>Collect</Button></Stack>} />",
2534
+ "code": "<SplitPane ratio=\"2/1\" primary={<AspectBox ratio={1}><img src={post.imageUrl} alt={post.title} className=\"size-full object-cover\" /></AspectBox>} secondary={<Stack gap={2}><h1 className=\"text-heading-1\">{post.title}</h1><p className=\"text-body-md text-muted-foreground\">{post.description}</p><Button>Add to cart</Button></Stack>} />",
2460
2535
  "imports": "import { AspectBox, Button, SplitPane, Stack } from \"@sableui/react\""
2461
2536
  }
2462
2537
  ]
@@ -2733,7 +2808,7 @@
2733
2808
  "examples": [
2734
2809
  {
2735
2810
  "label": "Single (default)",
2736
- "code": "<Accordion defaultValue={[\"a\"]}><AccordionItem value=\"a\"><AccordionTrigger>Is it permanent?</AccordionTrigger><AccordionPanel>Yes — stored on Arweave.</AccordionPanel></AccordionItem><AccordionItem value=\"b\"><AccordionTrigger>How much?</AccordionTrigger><AccordionPanel>A one-time mint fee.</AccordionPanel></AccordionItem></Accordion>",
2811
+ "code": "<Accordion defaultValue={[\"a\"]}><AccordionItem value=\"a\"><AccordionTrigger>Can I cancel anytime?</AccordionTrigger><AccordionPanel>Yes — from billing settings.</AccordionPanel></AccordionItem><AccordionItem value=\"b\"><AccordionTrigger>How much?</AccordionTrigger><AccordionPanel>$16 per seat each month.</AccordionPanel></AccordionItem></Accordion>",
2737
2812
  "imports": "import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger } from \"@sableui/react\""
2738
2813
  },
2739
2814
  {
@@ -2743,7 +2818,7 @@
2743
2818
  },
2744
2819
  {
2745
2820
  "label": "Custom indicator (plus-becomes-×)",
2746
- "code": "<Accordion defaultValue={[\"a\"]}><AccordionItem value=\"a\"><AccordionTrigger indicator={<Icon name=\"plus\" className=\"text-xs text-muted-foreground transition-transform [[data-panel-open]_&]:rotate-45\" />}>Is it permanent?</AccordionTrigger><AccordionPanel>Yes — stored on Arweave.</AccordionPanel></AccordionItem></Accordion>",
2821
+ "code": "<Accordion defaultValue={[\"a\"]}><AccordionItem value=\"a\"><AccordionTrigger indicator={<Icon name=\"plus\" className=\"text-xs text-muted-foreground transition-transform [[data-panel-open]_&]:rotate-45\" />}>Can I cancel anytime?</AccordionTrigger><AccordionPanel>Yes — from billing settings.</AccordionPanel></AccordionItem></Accordion>",
2747
2822
  "imports": "import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, Icon } from \"@sableui/react\""
2748
2823
  }
2749
2824
  ]
@@ -2803,7 +2878,7 @@
2803
2878
  {
2804
2879
  "name": "actions",
2805
2880
  "type": "ReactNode",
2806
- "description": "Trailing slot — wallet trigger, action buttons."
2881
+ "description": "Trailing slot — account menu, action buttons."
2807
2882
  }
2808
2883
  ],
2809
2884
  "tokensUsed": [
@@ -2979,7 +3054,7 @@
2979
3054
  "examples": [
2980
3055
  {
2981
3056
  "label": "Full frame",
2982
- "code": "<AppShell\n banners={<Banner tone=\"warning\">Network congested — mints may be slow.</Banner>}\n header={<AppHeader title=\"Studio\" />}\n sidebar={\n <Sidebar>\n <SidebarNav>\n <SidebarItem icon={<Icon name=\"home\" />} label=\"Home\" active render={<Link to=\"/\" />} />\n <SidebarItem icon={<Icon name=\"bell\" />} label=\"Notifications\" render={<Link to=\"/notifications\" />} />\n <SidebarItem icon={<Icon name=\"gear\" />} label=\"Settings\" render={<Link to=\"/settings\" />} />\n </SidebarNav>\n </Sidebar>\n }\n bottomNav={\n <BottomNav>\n <BottomNavItem icon={<Icon name=\"home\" />} label=\"Home\" active render={<Link to=\"/\" />} />\n <BottomNavItem icon={<Icon name=\"bell\" />} label=\"Alerts\" render={<Link to=\"/notifications\" />} />\n </BottomNav>\n }\n>\n <Region max=\"feed\">\n <PageHeader title=\"Home\" description=\"Latest posts from artists you follow.\" />\n </Region>\n</AppShell>",
3057
+ "code": "<AppShell\n banners={<Banner tone=\"warning\">Sync is delayed — changes may be slow to appear.</Banner>}\n header={<AppHeader title=\"Studio\" />}\n sidebar={\n <Sidebar>\n <SidebarNav>\n <SidebarItem icon={<Icon name=\"home\" />} label=\"Home\" active render={<Link to=\"/\" />} />\n <SidebarItem icon={<Icon name=\"bell\" />} label=\"Notifications\" render={<Link to=\"/notifications\" />} />\n <SidebarItem icon={<Icon name=\"gear\" />} label=\"Settings\" render={<Link to=\"/settings\" />} />\n </SidebarNav>\n </Sidebar>\n }\n bottomNav={\n <BottomNav>\n <BottomNavItem icon={<Icon name=\"home\" />} label=\"Home\" active render={<Link to=\"/\" />} />\n <BottomNavItem icon={<Icon name=\"bell\" />} label=\"Alerts\" render={<Link to=\"/notifications\" />} />\n </BottomNav>\n }\n>\n <Region max=\"feed\">\n <PageHeader title=\"Home\" description=\"Latest posts from people you follow.\" />\n </Region>\n</AppShell>",
2983
3058
  "imports": "import { AppHeader, AppShell, Banner, BottomNav, BottomNavItem, Icon, PageHeader, Region, Sidebar, SidebarItem, SidebarNav } from \"@sableui/react\"\n// <Link> = your router's link (react-router, next/link, @tanstack/router, …)"
2984
3059
  },
2985
3060
  {
@@ -3161,9 +3236,9 @@
3161
3236
  }
3162
3237
  ],
3163
3238
  "tokensUsed": [
3164
- "tone-edition",
3165
- "tone-collectible",
3166
- "tone-standard",
3239
+ "tone-1",
3240
+ "tone-2",
3241
+ "tone-3",
3167
3242
  "tone-info",
3168
3243
  "tone-warning",
3169
3244
  "primary",
@@ -3699,7 +3774,7 @@
3699
3774
  "group": "Badges & pills",
3700
3775
  "register": "either",
3701
3776
  "path": "components/badge",
3702
- "summary": "Compact static label, set on two axes: appearance (soft, outline, solid, overlay on media, plain text) and tone (neutral or a semantic tone). An optional leading icon, or a status dot (with pulse) for state.",
3777
+ "summary": "Compact static label, set on two axes: appearance (soft, outline, solid, overlay on media, plain text) and tone (neutral, a semantic tone, or a palette family like blue for a category with no meaning attached). An optional leading icon, or a status dot (with pulse) for state.",
3703
3778
  "keywords": [
3704
3779
  "badge",
3705
3780
  "tag",
@@ -3720,7 +3795,7 @@
3720
3795
  },
3721
3796
  {
3722
3797
  "guidance": "do",
3723
- "description": "Use tone variants when the badge carries meaning, such as success, warning, destructive, edition, or collectible."
3798
+ "description": "Use tone variants when the badge carries meaning, such as success, warning or destructive, or a decorative tone for a category or plan."
3724
3799
  },
3725
3800
  {
3726
3801
  "guidance": "do",
@@ -3779,9 +3854,9 @@
3779
3854
  },
3780
3855
  {
3781
3856
  "name": "tone",
3782
- "type": "\"neutral\" | \"standard\" | \"collectible\" | \"edition\" | \"info\" | \"success\" | \"warning\" | \"destructive\"",
3857
+ "type": "\"neutral\" | \"1\" | \"2\" | \"3\" | \"info\" | \"success\" | \"warning\" | \"destructive\" | \"red\" | \"orange\" | \"yellow\" | \"green\" | \"teal\" | \"blue\" | \"purple\" | \"pink\"",
3783
3858
  "default": "\"neutral\"",
3784
- "description": "What it means. Every appearance takes every tone."
3859
+ "description": "What it means. Every appearance takes every tone. The palette families (red … pink) are categories with no meaning attached — tags, labels, project colours — painted from the theme's generated palette, so they lean toward the brand and read in both modes."
3785
3860
  },
3786
3861
  {
3787
3862
  "name": "size",
@@ -3817,13 +3892,21 @@
3817
3892
  ],
3818
3893
  "tone": [
3819
3894
  "neutral",
3820
- "standard",
3821
- "collectible",
3822
- "edition",
3895
+ "1",
3896
+ "2",
3897
+ "3",
3823
3898
  "info",
3824
3899
  "success",
3825
3900
  "warning",
3826
- "destructive"
3901
+ "destructive",
3902
+ "red",
3903
+ "orange",
3904
+ "yellow",
3905
+ "green",
3906
+ "teal",
3907
+ "blue",
3908
+ "purple",
3909
+ "pink"
3827
3910
  ],
3828
3911
  "size": [
3829
3912
  "sm",
@@ -3831,6 +3914,13 @@
3831
3914
  ]
3832
3915
  },
3833
3916
  "tokensUsed": [
3917
+ "palette-wash",
3918
+ "palette-wash-strong",
3919
+ "palette-edge",
3920
+ "palette-dot",
3921
+ "palette-solid",
3922
+ "palette-text",
3923
+ "palette-solid-ink",
3834
3924
  "secondary",
3835
3925
  "secondary-foreground",
3836
3926
  "border",
@@ -3838,18 +3928,18 @@
3838
3928
  "background",
3839
3929
  "muted",
3840
3930
  "muted-foreground",
3841
- "tone-standard",
3842
- "tone-collectible",
3843
- "tone-edition",
3931
+ "tone-1",
3932
+ "tone-2",
3933
+ "tone-3",
3844
3934
  "tone-info",
3845
3935
  "success",
3846
3936
  "tone-warning",
3847
3937
  "destructive",
3848
3938
  "overlay",
3849
3939
  "overlay-foreground",
3850
- "tone-standard-foreground",
3851
- "tone-collectible-foreground",
3852
- "tone-edition-foreground",
3940
+ "tone-1-foreground",
3941
+ "tone-2-foreground",
3942
+ "tone-3-foreground",
3853
3943
  "tone-info-foreground",
3854
3944
  "success-foreground",
3855
3945
  "tone-warning-foreground",
@@ -3862,7 +3952,12 @@
3862
3952
  "examples": [
3863
3953
  {
3864
3954
  "label": "Tones",
3865
- "code": "<Row gap={1}><Badge tone=\"edition\">Edition</Badge><Badge tone=\"collectible\">Collectible</Badge><Badge tone=\"standard\">Standard</Badge></Row>",
3955
+ "code": "<Row gap={1}><Badge tone=\"1\">Business</Badge><Badge tone=\"2\">Studio</Badge><Badge tone=\"3\">Starter</Badge></Row>",
3956
+ "imports": "import { Badge, Row } from \"@sableui/react\""
3957
+ },
3958
+ {
3959
+ "label": "Palette colours (categories)",
3960
+ "code": "<Row gap={1}><Badge tone=\"blue\">Design</Badge><Badge tone=\"teal\">Research</Badge><Badge tone=\"orange\">Marketing</Badge><Badge tone=\"pink\" appearance=\"outline\">Ops</Badge><Badge tone=\"purple\" appearance=\"solid\">Launch</Badge></Row>",
3866
3961
  "imports": "import { Badge, Row } from \"@sableui/react\""
3867
3962
  },
3868
3963
  {
@@ -3887,7 +3982,7 @@
3887
3982
  },
3888
3983
  {
3889
3984
  "label": "Metadata row",
3890
- "code": "<Row gap={1}><Badge>12 editions</Badge><Badge appearance=\"outline\">Open edition</Badge><Badge tone=\"info\" icon={<Icon name=\"info\" />}>Curated</Badge></Row>",
3985
+ "code": "<Row gap={1}><Badge>12 projects</Badge><Badge appearance=\"outline\">Draft</Badge><Badge tone=\"info\" icon={<Icon name=\"info\" />}>Curated</Badge></Row>",
3891
3986
  "imports": "import { Badge, Icon, Row } from \"@sableui/react\""
3892
3987
  },
3893
3988
  {
@@ -3903,7 +3998,7 @@
3903
3998
  "group": "Status",
3904
3999
  "register": "page",
3905
4000
  "path": "components/banner",
3906
- "summary": "Full-width, page-level announcement bar. A tone (neutral, info, success, warning, edition, destructive) × an appearance (soft tint or loud solid fill), and an optional leading scope label. Centers its message, or — with a trailing action — switches to a leading-text + trailing-button layout. Optionally sticky and dismissible.",
4001
+ "summary": "Full-width, page-level announcement bar. A tone (neutral, info, success, warning, the decorative 1, destructive) × an appearance (soft tint or loud solid fill), and an optional leading scope label. Centers its message, or — with a trailing action — switches to a leading-text + trailing-button layout. Optionally sticky and dismissible.",
3907
4002
  "keywords": [
3908
4003
  "banner",
3909
4004
  "announcement",
@@ -3977,9 +4072,9 @@
3977
4072
  "props": [
3978
4073
  {
3979
4074
  "name": "tone",
3980
- "type": "\"neutral\" | \"info\" | \"success\" | \"warning\" | \"edition\" | \"destructive\"",
4075
+ "type": "\"neutral\" | \"info\" | \"success\" | \"warning\" | \"1\" | \"destructive\"",
3981
4076
  "default": "\"neutral\"",
3982
- "description": "Neutral surface or a tone that encodes the announcement's intent (info / success / warning / edition / destructive)."
4077
+ "description": "Neutral surface or a tone that encodes the announcement's intent (info / success / warning / the decorative 1 / destructive)."
3983
4078
  },
3984
4079
  {
3985
4080
  "name": "appearance",
@@ -4027,7 +4122,7 @@
4027
4122
  "info",
4028
4123
  "success",
4029
4124
  "warning",
4030
- "edition",
4125
+ "1",
4031
4126
  "destructive"
4032
4127
  ],
4033
4128
  "appearance": [
@@ -4046,8 +4141,8 @@
4046
4141
  "success-foreground",
4047
4142
  "tone-warning",
4048
4143
  "tone-warning-foreground",
4049
- "tone-edition",
4050
- "tone-edition-foreground",
4144
+ "tone-1",
4145
+ "tone-1-foreground",
4051
4146
  "destructive",
4052
4147
  "destructive-foreground"
4053
4148
  ],
@@ -4064,7 +4159,7 @@
4064
4159
  },
4065
4160
  {
4066
4161
  "label": "Solid (loud)",
4067
- "code": "<Banner tone=\"destructive\" appearance=\"solid\" live=\"assertive\">Blockchain unavailable. Collect and buy are temporarily disabled.</Banner>",
4162
+ "code": "<Banner tone=\"destructive\" appearance=\"solid\" live=\"assertive\">Payments are down. Checkout and upgrades are temporarily disabled.</Banner>",
4068
4163
  "imports": "import { Banner } from \"@sableui/react\""
4069
4164
  },
4070
4165
  {
@@ -4084,7 +4179,7 @@
4084
4179
  },
4085
4180
  {
4086
4181
  "label": "Recoverable warning",
4087
- "code": "<Banner tone=\"warning\" action={<Button variant=\"outline\" size=\"sm\">Reconnect</Button>}>Wallet connection paused.</Banner>",
4182
+ "code": "<Banner tone=\"warning\" action={<Button variant=\"outline\" size=\"sm\">Reconnect</Button>}>Calendar sync paused.</Banner>",
4088
4183
  "imports": "import { Banner, Button } from \"@sableui/react\""
4089
4184
  },
4090
4185
  {
@@ -4262,7 +4357,7 @@
4262
4357
  },
4263
4358
  {
4264
4359
  "name": "negativeTone",
4265
- "type": "\"edition\" | \"collectible\" | \"standard\" | \"info\" | \"warning\" | \"primary\" | \"neutral\" | \"success\" | \"destructive\"",
4360
+ "type": "\"1\" | \"2\" | \"3\" | \"info\" | \"warning\" | \"primary\" | \"neutral\" | \"success\" | \"destructive\"",
4266
4361
  "description": "Colour negative values with this tone."
4267
4362
  },
4268
4363
  {
@@ -4279,9 +4374,9 @@
4279
4374
  }
4280
4375
  ],
4281
4376
  "tokensUsed": [
4282
- "tone-edition",
4283
- "tone-collectible",
4284
- "tone-standard",
4377
+ "tone-1",
4378
+ "tone-2",
4379
+ "tone-3",
4285
4380
  "tone-info",
4286
4381
  "tone-warning",
4287
4382
  "primary",
@@ -4532,7 +4627,7 @@
4532
4627
  "examples": [
4533
4628
  {
4534
4629
  "label": "Three-level trail",
4535
- "code": "<Breadcrumbs><BreadcrumbItem><BreadcrumbLink href=\"/\">Home</BreadcrumbLink></BreadcrumbItem><BreadcrumbSeparator /><BreadcrumbItem><BreadcrumbLink href=\"/library\">Library</BreadcrumbLink></BreadcrumbItem><BreadcrumbSeparator /><BreadcrumbItem><BreadcrumbPage>Editions</BreadcrumbPage></BreadcrumbItem></Breadcrumbs>",
4630
+ "code": "<Breadcrumbs><BreadcrumbItem><BreadcrumbLink href=\"/\">Home</BreadcrumbLink></BreadcrumbItem><BreadcrumbSeparator /><BreadcrumbItem><BreadcrumbLink href=\"/library\">Library</BreadcrumbLink></BreadcrumbItem><BreadcrumbSeparator /><BreadcrumbItem><BreadcrumbPage>Reports</BreadcrumbPage></BreadcrumbItem></Breadcrumbs>",
4536
4631
  "imports": "import { BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, Breadcrumbs, BreadcrumbSeparator } from \"@sableui/react\""
4537
4632
  },
4538
4633
  {
@@ -5050,7 +5145,7 @@
5050
5145
  "examples": [
5051
5146
  {
5052
5147
  "label": "Primary",
5053
- "code": "<Button>Collect</Button>",
5148
+ "code": "<Button>Save changes</Button>",
5054
5149
  "imports": "import { Button } from \"@sableui/react\""
5055
5150
  },
5056
5151
  {
@@ -5085,7 +5180,7 @@
5085
5180
  },
5086
5181
  {
5087
5182
  "label": "Destructive with confirmation copy",
5088
- "code": "<Row gap={1}><Button variant=\"secondary\">Cancel</Button><Button variant=\"destructive\">Delete edition</Button></Row>",
5183
+ "code": "<Row gap={1}><Button variant=\"secondary\">Cancel</Button><Button variant=\"destructive\">Delete project</Button></Row>",
5089
5184
  "imports": "import { Button, Row } from \"@sableui/react\""
5090
5185
  },
5091
5186
  {
@@ -5130,7 +5225,7 @@
5130
5225
  },
5131
5226
  {
5132
5227
  "label": "Action in a solid Banner",
5133
- "code": "<Banner tone=\"warning\" appearance=\"solid\" action={<Button variant=\"on-color\" size=\"sm\">Reconnect</Button>}>Wallet connection paused.</Banner>",
5228
+ "code": "<Banner tone=\"warning\" appearance=\"solid\" action={<Button variant=\"on-color\" size=\"sm\">Reconnect</Button>}>Calendar sync paused.</Banner>",
5134
5229
  "imports": "import { Banner, Button } from \"@sableui/react\""
5135
5230
  }
5136
5231
  ]
@@ -5475,12 +5570,12 @@
5475
5570
  "examples": [
5476
5571
  {
5477
5572
  "label": "With parts",
5478
- "code": "<Card><CardHeader><CardTitle>Edition</CardTitle><CardDescription>Limited run of 100.</CardDescription></CardHeader><CardContent><p className=\"text-body-md\">Each collector receives a signed print and the on-chain edition.</p></CardContent></Card>",
5573
+ "code": "<Card><CardHeader><CardTitle>Studio plan</CardTitle><CardDescription>Up to 10 seats.</CardDescription></CardHeader><CardContent><p className=\"text-body-md\">Every seat gets unlimited projects and shared reports.</p></CardContent></Card>",
5479
5574
  "imports": "import { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@sableui/react\""
5480
5575
  },
5481
5576
  {
5482
5577
  "label": "With a header action",
5483
- "code": "<Card><CardHeader><CardTitle>Edition</CardTitle><CardAction><Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"More\"><Icon name=\"ellipsis\" /></Button></CardAction></CardHeader><CardContent><p className=\"text-body-md text-muted-foreground\">24 of 100 collected.</p></CardContent></Card>",
5578
+ "code": "<Card><CardHeader><CardTitle>Seats</CardTitle><CardAction><Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"More\"><Icon name=\"ellipsis\" /></Button></CardAction></CardHeader><CardContent><p className=\"text-body-md text-muted-foreground\">24 of 100 seats in use.</p></CardContent></Card>",
5484
5579
  "imports": "import { Button, Card, CardAction, CardContent, CardHeader, CardTitle, Icon } from \"@sableui/react\""
5485
5580
  },
5486
5581
  {
@@ -5490,7 +5585,7 @@
5490
5585
  },
5491
5586
  {
5492
5587
  "label": "Product card with actions",
5493
- "code": "<Card><CardHeader><CardTitle>Midnight Archive</CardTitle><CardDescription>Edition #24 of 100</CardDescription><CardAction><Badge tone=\"edition\">Edition</Badge></CardAction></CardHeader><CardContent><p className=\"text-body-sm text-muted-foreground\">A signed print and token-gated download for collectors.</p></CardContent><CardFooter><Button size=\"sm\">Collect</Button><Button size=\"sm\" variant=\"ghost\">Preview</Button></CardFooter></Card>",
5588
+ "code": "<Card><CardHeader><CardTitle>Midnight Archive</CardTitle><CardDescription>Template · 24 pages</CardDescription><CardAction><Badge tone=\"1\">New</Badge></CardAction></CardHeader><CardContent><p className=\"text-body-sm text-muted-foreground\">A project template with a brief, a timeline and a review board.</p></CardContent><CardFooter><Button size=\"sm\">Use template</Button><Button size=\"sm\" variant=\"ghost\">Preview</Button></CardFooter></Card>",
5494
5589
  "imports": "import { Badge, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from \"@sableui/react\""
5495
5590
  },
5496
5591
  {
@@ -5500,7 +5595,7 @@
5500
5595
  },
5501
5596
  {
5502
5597
  "label": "Metric card",
5503
- "code": "<Card density=\"compact\"><CardHeader><CardTitle>Revenue</CardTitle><CardDescription>Last 30 days</CardDescription></CardHeader><CardContent><p className=\"text-heading-3\">128.4 SOL</p><p className=\"text-body-sm text-success\">+12.8% from previous period</p></CardContent></Card>",
5598
+ "code": "<Card density=\"compact\"><CardHeader><CardTitle>Revenue</CardTitle><CardDescription>Last 30 days</CardDescription></CardHeader><CardContent><p className=\"text-heading-3\">$12,840</p><p className=\"text-body-sm text-success\">+12.8% from previous period</p></CardContent></Card>",
5504
5599
  "imports": "import { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@sableui/react\""
5505
5600
  },
5506
5601
  {
@@ -6150,7 +6245,7 @@
6150
6245
  },
6151
6246
  {
6152
6247
  "name": "tone",
6153
- "type": "\"neutral\" | \"standard\" | \"collectible\" | \"edition\" | \"info\" | \"warning\"",
6248
+ "type": "\"neutral\" | \"3\" | \"2\" | \"1\" | \"info\" | \"warning\"",
6154
6249
  "default": "\"neutral\"",
6155
6250
  "description": "Semantic colour the checked state carries across the border, checkbox box, and icon."
6156
6251
  },
@@ -6175,9 +6270,9 @@
6175
6270
  "variants": {
6176
6271
  "tone": [
6177
6272
  "neutral",
6178
- "standard",
6179
- "collectible",
6180
- "edition",
6273
+ "3",
6274
+ "2",
6275
+ "1",
6181
6276
  "info",
6182
6277
  "warning"
6183
6278
  ]
@@ -6191,12 +6286,12 @@
6191
6286
  "accent",
6192
6287
  "foreground",
6193
6288
  "muted-foreground",
6194
- "tone-standard",
6195
- "tone-standard-foreground",
6196
- "tone-collectible",
6197
- "tone-collectible-foreground",
6198
- "tone-edition",
6199
- "tone-edition-foreground",
6289
+ "tone-3",
6290
+ "tone-3-foreground",
6291
+ "tone-2",
6292
+ "tone-2-foreground",
6293
+ "tone-1",
6294
+ "tone-1-foreground",
6200
6295
  "tone-info",
6201
6296
  "tone-info-foreground",
6202
6297
  "tone-warning",
@@ -6221,12 +6316,12 @@
6221
6316
  },
6222
6317
  {
6223
6318
  "label": "A single independent card",
6224
- "code": "<CheckboxCard title=\"Email me a receipt\" description=\"Sent when the mint settles\" checked={receipt} onCheckedChange={setReceipt} />",
6319
+ "code": "<CheckboxCard title=\"Email me a receipt\" description=\"Sent when the payment settles\" checked={receipt} onCheckedChange={setReceipt} />",
6225
6320
  "imports": "import { CheckboxCard } from \"@sableui/react\""
6226
6321
  },
6227
6322
  {
6228
6323
  "label": "Toned with description",
6229
- "code": "<CheckboxCard tone=\"edition\" title=\"Editions\" description=\"Sell as numbered NFT editions\" defaultChecked />",
6324
+ "code": "<CheckboxCard tone=\"1\" title=\"Reports\" description=\"Weekly usage reports by email\" defaultChecked />",
6230
6325
  "imports": "import { CheckboxCard } from \"@sableui/react\""
6231
6326
  }
6232
6327
  ]
@@ -6337,7 +6432,7 @@
6337
6432
  },
6338
6433
  {
6339
6434
  "guidance": "do",
6340
- "description": "Wire highlighting once with CodeHighlightProvider and map token colors to semantic tone tokens (keywords → tone-edition, strings → success, comments → muted-foreground), so code follows the theme instead of a fixed palette."
6435
+ "description": "Wire highlighting once with CodeHighlightProvider and map token colors to semantic tone tokens (keywords → tone-1, strings → success, comments → muted-foreground), so code follows the theme instead of a fixed palette."
6341
6436
  },
6342
6437
  {
6343
6438
  "guidance": "dont",
@@ -7177,7 +7272,7 @@
7177
7272
  },
7178
7273
  {
7179
7274
  "label": "Grouped launcher",
7180
- "code": "<CommandMenu open={open} onOpenChange={setOpen}><CommandMenuGroup label=\"Create\"><CommandMenuItem icon=\"plus\" onSelect={() => createPost()}>New post</CommandMenuItem><CommandMenuItem icon=\"sparkles\" onSelect={() => createEdition()}>New edition</CommandMenuItem></CommandMenuGroup><CommandMenuGroup label=\"Navigation\"><CommandMenuItem icon=\"home\" onSelect={() => navigate(\"/\")}>Home</CommandMenuItem><CommandMenuItem icon=\"gear\" onSelect={() => navigate(\"/settings\")}>Settings</CommandMenuItem></CommandMenuGroup></CommandMenu>",
7275
+ "code": "<CommandMenu open={open} onOpenChange={setOpen}><CommandMenuGroup label=\"Create\"><CommandMenuItem icon=\"plus\" onSelect={() => createPost()}>New post</CommandMenuItem><CommandMenuItem icon=\"sparkles\" onSelect={() => createProject()}>New project</CommandMenuItem></CommandMenuGroup><CommandMenuGroup label=\"Navigation\"><CommandMenuItem icon=\"home\" onSelect={() => navigate(\"/\")}>Home</CommandMenuItem><CommandMenuItem icon=\"gear\" onSelect={() => navigate(\"/settings\")}>Settings</CommandMenuItem></CommandMenuGroup></CommandMenu>",
7181
7276
  "imports": "import { CommandMenu, CommandMenuGroup, CommandMenuItem } from \"@sableui/react\""
7182
7277
  },
7183
7278
  {
@@ -7213,7 +7308,7 @@
7213
7308
  "group": "Overlays",
7214
7309
  "register": "page",
7215
7310
  "path": "components/context-card",
7216
- "summary": "Hover/focus-triggered rich preview card over Base UI PreviewCard. Reveals contextual content (a profile, an edition summary) from an inline anchor; 20rem-wide surface with delayed open and collision-aware positioning.",
7311
+ "summary": "Hover/focus-triggered rich preview card over Base UI PreviewCard. Reveals contextual content (a profile, a project summary) from an inline anchor; 20rem-wide surface with delayed open and collision-aware positioning.",
7217
7312
  "keywords": [
7218
7313
  "context card",
7219
7314
  "rich tooltip",
@@ -7304,7 +7399,7 @@
7304
7399
  "examples": [
7305
7400
  {
7306
7401
  "label": "Profile preview",
7307
- "code": "<ContextCard><ContextCardTrigger render={<a href='/u/ada' className='text-title-sm underline-offset-2 hover:underline'>@ada</a>} /><ContextCardContent><Entity className='[--entity-px:0] [--entity-py:0]' leading={<Avatar fallback='AL' size='lg' />} title='Ada Lovelace' subtitle='@ada' /><p className='mt-3 text-body-sm text-muted-foreground'>Minting generative editions on Solana. Permanently on Arweave.</p></ContextCardContent></ContextCard>",
7402
+ "code": "<ContextCard><ContextCardTrigger render={<a href='/u/ada' className='text-title-sm underline-offset-2 hover:underline'>@ada</a>} /><ContextCardContent><Entity className='[--entity-px:0] [--entity-py:0]' leading={<Avatar fallback='AL' size='lg' />} title='Ada Lovelace' subtitle='@ada' /><p className='mt-3 text-body-sm text-muted-foreground'>Product designer. Building tools for small teams.</p></ContextCardContent></ContextCard>",
7308
7403
  "imports": "import { Avatar, ContextCard, ContextCardContent, ContextCardTrigger, Entity } from \"@sableui/react\""
7309
7404
  },
7310
7405
  {
@@ -8068,7 +8163,7 @@
8068
8163
  },
8069
8164
  {
8070
8165
  "label": "Body slot + type-to-confirm",
8071
- "code": "<DestructiveActionModal open={open} onOpenChange={setOpen} title='Delete this post?' confirmText='gold rush' confirmLabel='Delete forever' onConfirm={handleDelete}><Note tone='warning'>Collectors who minted this edition keep their copies.</Note></DestructiveActionModal>",
8166
+ "code": "<DestructiveActionModal open={open} onOpenChange={setOpen} title='Delete this post?' confirmText='gold rush' confirmLabel='Delete forever' onConfirm={handleDelete}><Note tone='warning'>People you shared this post with lose access to it.</Note></DestructiveActionModal>",
8072
8167
  "imports": "import { DestructiveActionModal, Note } from \"@sableui/react\""
8073
8168
  }
8074
8169
  ]
@@ -8316,7 +8411,7 @@
8316
8411
  },
8317
8412
  {
8318
8413
  "label": "Basic",
8319
- "code": "<Dialog><DialogTrigger render={<Button>Open</Button>} /><DialogContent><DialogHeader><DialogTitle>Edition details</DialogTitle><DialogDescription>A limited run of 100, stored permanently on Arweave.</DialogDescription></DialogHeader><DialogFooter><DialogClose render={<Button variant=\"secondary\">Close</Button>} /><DialogClose render={<Button>Collect</Button>} /></DialogFooter></DialogContent></Dialog>",
8414
+ "code": "<Dialog><DialogTrigger render={<Button>Open</Button>} /><DialogContent><DialogHeader><DialogTitle>Plan details</DialogTitle><DialogDescription>Up to 10 seats, billed monthly per seat.</DialogDescription></DialogHeader><DialogFooter><DialogClose render={<Button variant=\"secondary\">Close</Button>} /><DialogClose render={<Button>Upgrade</Button>} /></DialogFooter></DialogContent></Dialog>",
8320
8415
  "imports": "import { Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from \"@sableui/react\""
8321
8416
  },
8322
8417
  {
@@ -8326,7 +8421,7 @@
8326
8421
  },
8327
8422
  {
8328
8423
  "label": "Destructive confirmation",
8329
- "code": "<AlertDialog><AlertDialogTrigger render={<Button variant=\"destructive\">Delete edition</Button>} /><AlertDialogContent><AlertDialogHeader><AlertDialogTitle>Delete edition?</AlertDialogTitle><AlertDialogDescription>This removes the edition and cannot be undone.</AlertDialogDescription></AlertDialogHeader><AlertDialogFooter><AlertDialogClose render={<Button variant=\"secondary\">Cancel</Button>} /><Button variant=\"destructive\" onClick={deleteEdition}>Delete edition</Button></AlertDialogFooter></AlertDialogContent></AlertDialog>",
8424
+ "code": "<AlertDialog><AlertDialogTrigger render={<Button variant=\"destructive\">Delete project</Button>} /><AlertDialogContent><AlertDialogHeader><AlertDialogTitle>Delete project?</AlertDialogTitle><AlertDialogDescription>This removes the project and cannot be undone.</AlertDialogDescription></AlertDialogHeader><AlertDialogFooter><AlertDialogClose render={<Button variant=\"secondary\">Cancel</Button>} /><Button variant=\"destructive\" onClick={deleteProject}>Delete project</Button></AlertDialogFooter></AlertDialogContent></AlertDialog>",
8330
8425
  "imports": "import { AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, Button } from \"@sableui/react\""
8331
8426
  }
8332
8427
  ]
@@ -8485,8 +8580,8 @@
8485
8580
  },
8486
8581
  {
8487
8582
  "name": "DisclosurePickerOption",
8488
- "type": "{ value: string; children?: ReactNode; media?: ReactNode; note?: ReactNode; disabled?: boolean; textValue?: string }",
8489
- "description": "One option row. `media` is a leading visual (a ThemePreview tile); `note` a muted trailing annotation — options without one show a check when chosen. `disabled` skips it for the keyboard and refuses the pick; `textValue` is what type-ahead matches when the name is not plain text."
8583
+ "type": "{ value: string; children?: ReactNode; media?: ReactNode; note?: ReactNode; disabled?: boolean; textValue?: string; onRemove?: () => void; removeLabel?: string }",
8584
+ "description": "One option row. `media` is a leading visual (a ThemePreview tile); `note` a muted trailing annotation — options without one show a check when chosen. `disabled` skips it for the keyboard and refuses the pick; `textValue` is what type-ahead matches when the name is not plain text. `onRemove` makes the option removable — for entries the person added (an imported preset, a saved font): a trash glyph at the row's end on hover or focus (always on touch), and Delete or Backspace on the focused row; removing never picks, and focus moves to a neighbour first. `removeLabel` is the glyph's tooltip."
8490
8585
  },
8491
8586
  {
8492
8587
  "name": "DisclosurePickerEmpty",
@@ -8541,6 +8636,7 @@
8541
8636
  "Why a listbox and not a radio group: a radio group's checked state follows focus, so every arrow press would be a pick — wrong for a choice that opens a confirm, loads a font or resets settings. Base UI ships no standalone listbox (Select is popup-only, Combobox's inline list is focus-in-the-input, Composite is internal), so the keyboard model lives here.",
8542
8637
  "Opening from the keyboard (Enter, Space, ArrowDown) moves focus into the panel: the search field when there is one, otherwise the chosen option (or the first). A pointer open focuses the search field on a fine pointer only, so touch does not raise the on-screen keyboard.",
8543
8638
  "Escape closes the row without changing the value (clearing a query first) and returns focus to the trigger; a pick does the same. Focus is not pulled back when it has already moved on, e.g. to a confirm dialog the pick opened.",
8639
+ "A removable option (onRemove) announces aria-keyshortcuts=\"Delete\"; Delete or Backspace removes it and focus moves to the next option (or the previous). The trash glyph is a pointer target only (aria-hidden, not focusable): an option's children are presentational, so a nested button would be unreachable by assistive tech anyway.",
8544
8640
  "The panel animates with motion-disclosure, which is static under prefers-reduced-motion or MotionProvider off; the scroll that keeps the row in view is instant, never smooth."
8545
8641
  ],
8546
8642
  "examples": [
@@ -8587,7 +8683,7 @@
8587
8683
  "bestPractices": [
8588
8684
  {
8589
8685
  "guidance": "do",
8590
- "description": "Explain what is empty in plain language, such as \"No posts yet\" or \"No matching collectors\"."
8686
+ "description": "Explain what is empty in plain language, such as \"No posts yet\" or \"No matching customers\"."
8591
8687
  },
8592
8688
  {
8593
8689
  "guidance": "do",
@@ -8733,7 +8829,7 @@
8733
8829
  },
8734
8830
  {
8735
8831
  "label": "First-run setup",
8736
- "code": "<EmptyState surface=\"card\" icon={<Icon name=\"sparkles\" />} title=\"Start your collection\" description=\"Create a first edition, import an existing drop, or invite collaborators.\" action={<Row gap={1}><Button>Create edition</Button><Button variant=\"ghost\">Import</Button></Row>} />",
8832
+ "code": "<EmptyState surface=\"card\" icon={<Icon name=\"sparkles\" />} title=\"Start your first project\" description=\"Create a project, import one from a file, or invite collaborators.\" action={<Row gap={1}><Button>Create project</Button><Button variant=\"ghost\">Import</Button></Row>} />",
8737
8833
  "imports": "import { Button, EmptyState, Icon, Row } from \"@sableui/react\""
8738
8834
  },
8739
8835
  {
@@ -8743,12 +8839,12 @@
8743
8839
  },
8744
8840
  {
8745
8841
  "label": "Error card",
8746
- "code": "<EmptyState tone=\"destructive\" surface=\"card\" title=\"Could not load this edition\" description=\"Check your connection and try again.\" action={<Button variant=\"secondary\">Retry</Button>} />",
8842
+ "code": "<EmptyState tone=\"destructive\" surface=\"card\" title=\"Could not load this project\" description=\"Check your connection and try again.\" action={<Button variant=\"secondary\">Retry</Button>} />",
8747
8843
  "imports": "import { Button, EmptyState } from \"@sableui/react\""
8748
8844
  },
8749
8845
  {
8750
8846
  "label": "Empty table body",
8751
- "code": "<DataTable data={[]} columns={[{ key: \"name\", header: \"Collector\" }, { key: \"status\", header: \"Status\" }]} empty={<EmptyState title=\"No collectors yet\" description=\"Collectors appear here after the first mint.\" />} />",
8847
+ "code": "<DataTable data={[]} columns={[{ key: \"name\", header: \"Customer\" }, { key: \"status\", header: \"Status\" }]} empty={<EmptyState title=\"No customers yet\" description=\"Customers appear here after the first order.\" />} />",
8752
8848
  "imports": "import { DataTable, EmptyState } from \"@sableui/react\""
8753
8849
  }
8754
8850
  ]
@@ -9918,7 +10014,7 @@
9918
10014
  },
9919
10015
  {
9920
10016
  "name": "tones",
9921
- "type": "(\"info\" | \"standard\" | \"collectible\" | \"edition\" | \"warning\")[]",
10017
+ "type": "(\"info\" | \"3\" | \"2\" | \"1\" | \"warning\")[]",
9922
10018
  "description": "Paint these `--tone-*` colours instead of the preset's art palette. Leave unset to follow the preset's art direction."
9923
10019
  },
9924
10020
  {
@@ -9956,9 +10052,9 @@
9956
10052
  "art-grain",
9957
10053
  "primary",
9958
10054
  "tone-info",
9959
- "tone-standard",
9960
- "tone-collectible",
9961
- "tone-edition",
10055
+ "tone-3",
10056
+ "tone-2",
10057
+ "tone-1",
9962
10058
  "tone-warning"
9963
10059
  ],
9964
10060
  "motion": [
@@ -9999,7 +10095,7 @@
9999
10095
  },
10000
10096
  {
10001
10097
  "label": "Grain banner",
10002
- "code": "<GradientArt variant=\"grain\" seed=\"juno\" tones={[\"info\", \"edition\"]} className=\"h-20 rounded-md\" />",
10098
+ "code": "<GradientArt variant=\"grain\" seed=\"juno\" tones={[\"info\", \"1\"]} className=\"h-20 rounded-md\" />",
10003
10099
  "imports": "import { GradientArt } from \"@sableui/react\""
10004
10100
  },
10005
10101
  {
@@ -10492,7 +10588,7 @@
10492
10588
  },
10493
10589
  {
10494
10590
  "label": "With hint",
10495
- "code": "<Label htmlFor=\"royalty\" hint=\"A percentage of every resale, paid to you.\">Royalty</Label>",
10591
+ "code": "<Label htmlFor=\"discount\" hint=\"Taken off every order this code is used on.\">Discount</Label>",
10496
10592
  "imports": "import { Label } from \"@sableui/react\""
10497
10593
  }
10498
10594
  ]
@@ -10663,9 +10759,9 @@
10663
10759
  }
10664
10760
  ],
10665
10761
  "tokensUsed": [
10666
- "tone-edition",
10667
- "tone-collectible",
10668
- "tone-standard",
10762
+ "tone-1",
10763
+ "tone-2",
10764
+ "tone-3",
10669
10765
  "tone-info",
10670
10766
  "tone-warning",
10671
10767
  "primary",
@@ -12055,7 +12151,7 @@
12055
12151
  },
12056
12152
  {
12057
12153
  "name": "tone",
12058
- "type": "\"primary\" | \"standard\" | \"success\" | \"warning\" | \"destructive\"",
12154
+ "type": "\"primary\" | \"3\" | \"success\" | \"warning\" | \"destructive\"",
12059
12155
  "default": "\"primary\"",
12060
12156
  "description": "Fill colour, mapped to a semantic token. Choose it from your own thresholds."
12061
12157
  },
@@ -12123,7 +12219,7 @@
12123
12219
  "variants": {
12124
12220
  "tone": [
12125
12221
  "primary",
12126
- "standard",
12222
+ "3",
12127
12223
  "success",
12128
12224
  "warning",
12129
12225
  "destructive"
@@ -12267,18 +12363,18 @@
12267
12363
  ],
12268
12364
  "examples": [
12269
12365
  {
12270
- "label": "Solana address",
12271
- "code": "<MiddleTruncate text=\"5J7sX2QpHt9vK4mNbR8wYcZ3dF6gH1jL0pA8xQpVnTu\" />",
12366
+ "label": "API key",
12367
+ "code": "<MiddleTruncate text=\"demo_key_7f3a9c2e5b8d1f4a6c0e9b3d7a2f5c8e1b4\" />",
12272
12368
  "imports": "import { MiddleTruncate } from \"@sableui/react\""
12273
12369
  },
12274
12370
  {
12275
12371
  "label": "Custom start/end counts",
12276
- "code": "<MiddleTruncate text=\"0xAbC1234567890dEf1234567890aBcDeF12345678\" startChars={4} endChars={4} />",
12372
+ "code": "<MiddleTruncate text=\"9f2c4e7a1b3d5f60c8e2a4b6d8f0e1c3a5b7d9f2\" startChars={4} endChars={4} />",
12277
12373
  "imports": "import { MiddleTruncate } from \"@sableui/react\""
12278
12374
  },
12279
12375
  {
12280
12376
  "label": "Copyable value",
12281
- "code": "const { copy, copied } = useCopy(address)\n<Row gap={1}><MiddleTruncate text={address} /><Button variant=\"ghost\" size=\"icon-xs\" aria-label=\"Copy address\" onClick={copy}><Icon name={copied ? \"check\" : \"copy\"} /></Button></Row>",
12377
+ "code": "const { copy, copied } = useCopy(commitHash)\n<Row gap={1}><MiddleTruncate text={commitHash} /><Button variant=\"ghost\" size=\"icon-xs\" aria-label=\"Copy commit hash\" onClick={copy}><Icon name={copied ? \"check\" : \"copy\"} /></Button></Row>",
12282
12378
  "imports": "import { Button, Icon, MiddleTruncate, Row, useCopy } from \"@sableui/react\""
12283
12379
  }
12284
12380
  ]
@@ -12388,12 +12484,12 @@
12388
12484
  "examples": [
12389
12485
  {
12390
12486
  "label": "Info",
12391
- "code": "<Note tone=\"info\">Editions mint on Solana mainnet.</Note>",
12487
+ "code": "<Note tone=\"info\">Reports refresh every hour.</Note>",
12392
12488
  "imports": "import { Note } from \"@sableui/react\""
12393
12489
  },
12394
12490
  {
12395
12491
  "label": "With title",
12396
- "code": "<Note tone=\"warning\" title=\"Low balance\">Top up SOL before minting.</Note>",
12492
+ "code": "<Note tone=\"warning\" title=\"Low balance\">Add credits before your next export.</Note>",
12397
12493
  "imports": "import { Note } from \"@sableui/react\""
12398
12494
  },
12399
12495
  {
@@ -12403,7 +12499,7 @@
12403
12499
  },
12404
12500
  {
12405
12501
  "label": "Custom icon",
12406
- "code": "<Note tone=\"neutral\" icon=\"lock\">Pricing and supply cannot be changed after an edition is purchased.</Note>",
12502
+ "code": "<Note tone=\"neutral\" icon=\"lock\">Pricing cannot be changed after an order is placed.</Note>",
12407
12503
  "imports": "import { Note } from \"@sableui/react\""
12408
12504
  }
12409
12505
  ]
@@ -13170,7 +13266,7 @@
13170
13266
  },
13171
13267
  {
13172
13268
  "guidance": "do",
13173
- "description": "Use a specific label when multiple pagers appear on a page, such as \"Collector pages\"."
13269
+ "description": "Use a specific label when multiple pagers appear on a page, such as \"Customer pages\"."
13174
13270
  },
13175
13271
  {
13176
13272
  "guidance": "do",
@@ -13285,7 +13381,7 @@
13285
13381
  },
13286
13382
  {
13287
13383
  "label": "Table results footer",
13288
- "code": "<Stack gap={2}><DataTable data={rows} columns={columns} rowKey={(row) => row.id} /><Pagination label=\"Collector pages\" page={page} total={12} onPageChange={setPage} /></Stack>",
13384
+ "code": "<Stack gap={2}><DataTable data={rows} columns={columns} rowKey={(row) => row.id} /><Pagination label=\"Customer pages\" page={page} total={12} onPageChange={setPage} /></Stack>",
13289
13385
  "imports": "import { DataTable, Pagination, Stack } from \"@sableui/react\""
13290
13386
  },
13291
13387
  {
@@ -13622,7 +13718,7 @@
13622
13718
  "examples": [
13623
13719
  {
13624
13720
  "label": "Framing a mobile layout",
13625
- "code": "<Phone><div className=\"p-4\"><h2 className=\"text-title-lg\">Activity</h2><p className=\"text-body-sm text-muted-foreground\">3 new collects today.</p></div></Phone>",
13721
+ "code": "<Phone><div className=\"p-4\"><h2 className=\"text-title-lg\">Activity</h2><p className=\"text-body-sm text-muted-foreground\">3 new orders today.</p></div></Phone>",
13626
13722
  "imports": "import { Phone } from \"@sableui/react\""
13627
13723
  },
13628
13724
  {
@@ -13786,9 +13882,9 @@
13786
13882
  }
13787
13883
  ],
13788
13884
  "tokensUsed": [
13789
- "tone-edition",
13790
- "tone-collectible",
13791
- "tone-standard",
13885
+ "tone-1",
13886
+ "tone-2",
13887
+ "tone-3",
13792
13888
  "tone-info",
13793
13889
  "tone-warning",
13794
13890
  "primary",
@@ -13908,6 +14004,12 @@
13908
14004
  "default": "\"md\"",
13909
14005
  "description": "md sits on the density ladder's xs control height (28px); sm is 24px, for chips under or inside other content."
13910
14006
  },
14007
+ {
14008
+ "name": "tone",
14009
+ "type": "\"neutral\" | \"red\" | \"orange\" | \"yellow\" | \"green\" | \"teal\" | \"blue\" | \"purple\" | \"pink\"",
14010
+ "default": "\"neutral\"",
14011
+ "description": "neutral is the chip as described. A palette family colours it as a category, like the choices in a tag picker: a wash at rest, the solid family colour when selected. Painted from the theme's generated palette."
14012
+ },
13911
14013
  {
13912
14014
  "name": "icon",
13913
14015
  "type": "ReactNode",
@@ -13926,9 +14028,26 @@
13926
14028
  "selected": [
13927
14029
  "true",
13928
14030
  "false"
14031
+ ],
14032
+ "tone": [
14033
+ "neutral",
14034
+ "red",
14035
+ "orange",
14036
+ "yellow",
14037
+ "green",
14038
+ "teal",
14039
+ "blue",
14040
+ "purple",
14041
+ "pink"
13929
14042
  ]
13930
14043
  },
13931
14044
  "tokensUsed": [
14045
+ "palette-wash",
14046
+ "palette-wash-strong",
14047
+ "palette-edge",
14048
+ "palette-solid",
14049
+ "palette-text",
14050
+ "palette-solid-ink",
13932
14051
  "secondary",
13933
14052
  "secondary-foreground",
13934
14053
  "accent",
@@ -13945,9 +14064,14 @@
13945
14064
  "Selected state is exposed via aria-pressed."
13946
14065
  ],
13947
14066
  "examples": [
14067
+ {
14068
+ "label": "Tag picker",
14069
+ "code": "<Row gap={1}><Pill tone=\"blue\" selected onClick={() => {}}>Design</Pill><Pill tone=\"teal\" onClick={() => {}}>Research</Pill><Pill tone=\"orange\" onClick={() => {}}>Marketing</Pill></Row>",
14070
+ "imports": "import { Pill, Row } from \"@sableui/react\""
14071
+ },
13948
14072
  {
13949
14073
  "label": "Filter row",
13950
- "code": "<Row gap={1}><Pill selected onClick={() => {}}>All</Pill><Pill onClick={() => {}}>Editions</Pill><Pill onClick={() => {}}>Collectibles</Pill></Row>",
14074
+ "code": "<Row gap={1}><Pill selected onClick={() => {}}>All</Pill><Pill onClick={() => {}}>Projects</Pill><Pill onClick={() => {}}>Reports</Pill></Row>",
13951
14075
  "imports": "import { Pill, Row } from \"@sableui/react\""
13952
14076
  },
13953
14077
  {
@@ -14089,12 +14213,12 @@
14089
14213
  "examples": [
14090
14214
  {
14091
14215
  "label": "Basic",
14092
- "code": "<Popover><PopoverTrigger render={<Button variant=\"secondary\">Details</Button>} /><PopoverContent><PopoverTitle>Edition details</PopoverTitle><PopoverDescription>A limited run of 100, stored permanently on Arweave.</PopoverDescription></PopoverContent></Popover>",
14216
+ "code": "<Popover><PopoverTrigger render={<Button variant=\"secondary\">Details</Button>} /><PopoverContent><PopoverTitle>Plan details</PopoverTitle><PopoverDescription>Up to 10 seats, billed monthly per seat.</PopoverDescription></PopoverContent></Popover>",
14093
14217
  "imports": "import { Button, Popover, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger } from \"@sableui/react\""
14094
14218
  },
14095
14219
  {
14096
14220
  "label": "With close",
14097
- "code": "<Popover><PopoverTrigger render={<Button variant=\"secondary\">Open</Button>} /><PopoverContent><PopoverTitle>Heads up</PopoverTitle><PopoverDescription>Collecting mints a compressed NFT to your wallet.</PopoverDescription><PopoverClose render={<Button size=\"sm\">Got it</Button>} /></PopoverContent></Popover>",
14221
+ "code": "<Popover><PopoverTrigger render={<Button variant=\"secondary\">Open</Button>} /><PopoverContent><PopoverTitle>Heads up</PopoverTitle><PopoverDescription>Upgrading starts a new billing period today.</PopoverDescription><PopoverClose render={<Button size=\"sm\">Got it</Button>} /></PopoverContent></Popover>",
14098
14222
  "imports": "import { Button, Popover, PopoverClose, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger } from \"@sableui/react\""
14099
14223
  }
14100
14224
  ]
@@ -14429,9 +14553,9 @@
14429
14553
  }
14430
14554
  ],
14431
14555
  "tokensUsed": [
14432
- "tone-edition",
14433
- "tone-collectible",
14434
- "tone-standard",
14556
+ "tone-1",
14557
+ "tone-2",
14558
+ "tone-3",
14435
14559
  "tone-info",
14436
14560
  "tone-warning",
14437
14561
  "primary",
@@ -14646,9 +14770,9 @@
14646
14770
  }
14647
14771
  ],
14648
14772
  "tokensUsed": [
14649
- "tone-edition",
14650
- "tone-collectible",
14651
- "tone-standard",
14773
+ "tone-1",
14774
+ "tone-2",
14775
+ "tone-3",
14652
14776
  "tone-info",
14653
14777
  "tone-warning",
14654
14778
  "primary",
@@ -14787,12 +14911,12 @@
14787
14911
  "examples": [
14788
14912
  {
14789
14913
  "label": "Basic",
14790
- "code": "<Fieldset>\n\t<FieldsetLegend>Post type</FieldsetLegend>\n\t<FieldsetContent>\n\t\t<RadioGroup defaultValue=\"standard\">\n\t\t\t<Field label=\"Standard\" orientation=\"horizontal\"><Radio value=\"standard\" /></Field>\n\t\t\t<Field label=\"Edition\" orientation=\"horizontal\"><Radio value=\"edition\" /></Field>\n\t\t</RadioGroup>\n\t</FieldsetContent>\n</Fieldset>",
14914
+ "code": "<Fieldset>\n\t<FieldsetLegend>Post type</FieldsetLegend>\n\t<FieldsetContent>\n\t\t<RadioGroup defaultValue=\"standard\">\n\t\t\t<Field label=\"Standard\" orientation=\"horizontal\"><Radio value=\"standard\" /></Field>\n\t\t\t<Field label=\"Poll\" orientation=\"horizontal\"><Radio value=\"poll\" /></Field>\n\t\t</RadioGroup>\n\t</FieldsetContent>\n</Fieldset>",
14791
14915
  "imports": "import { Field, Fieldset, FieldsetContent, FieldsetLegend, Radio, RadioGroup } from \"@sableui/react\""
14792
14916
  },
14793
14917
  {
14794
14918
  "label": "With a disabled option",
14795
- "code": "<Fieldset>\n\t<FieldsetLegend>Post type</FieldsetLegend>\n\t<FieldsetContent>\n\t\t<RadioGroup defaultValue=\"standard\">\n\t\t\t<Field label=\"Standard\" orientation=\"horizontal\"><Radio value=\"standard\" /></Field>\n\t\t\t<Field label=\"Edition\" orientation=\"horizontal\" disabled><Radio value=\"edition\" /></Field>\n\t\t</RadioGroup>\n\t</FieldsetContent>\n</Fieldset>",
14919
+ "code": "<Fieldset>\n\t<FieldsetLegend>Post type</FieldsetLegend>\n\t<FieldsetContent>\n\t\t<RadioGroup defaultValue=\"standard\">\n\t\t\t<Field label=\"Standard\" orientation=\"horizontal\"><Radio value=\"standard\" /></Field>\n\t\t\t<Field label=\"Poll\" orientation=\"horizontal\" disabled><Radio value=\"poll\" /></Field>\n\t\t</RadioGroup>\n\t</FieldsetContent>\n</Fieldset>",
14796
14920
  "imports": "import { Field, Fieldset, FieldsetContent, FieldsetLegend, Radio, RadioGroup } from \"@sableui/react\""
14797
14921
  }
14798
14922
  ]
@@ -14878,7 +15002,7 @@
14878
15002
  },
14879
15003
  {
14880
15004
  "name": "tone",
14881
- "type": "\"neutral\" | \"standard\" | \"collectible\" | \"edition\" | \"info\" | \"warning\"",
15005
+ "type": "\"neutral\" | \"3\" | \"2\" | \"1\" | \"info\" | \"warning\"",
14882
15006
  "default": "\"neutral\"",
14883
15007
  "description": "Semantic colour the selected state carries across the border, radio, and icon glyph. Neutral uses the primary ring with a muted wash."
14884
15008
  },
@@ -14909,9 +15033,9 @@
14909
15033
  "variants": {
14910
15034
  "tone": [
14911
15035
  "neutral",
14912
- "standard",
14913
- "collectible",
14914
- "edition",
15036
+ "3",
15037
+ "2",
15038
+ "1",
14915
15039
  "info",
14916
15040
  "warning"
14917
15041
  ]
@@ -14924,9 +15048,9 @@
14924
15048
  "muted",
14925
15049
  "ring",
14926
15050
  "muted-foreground",
14927
- "tone-standard",
14928
- "tone-collectible",
14929
- "tone-edition",
15051
+ "tone-3",
15052
+ "tone-2",
15053
+ "tone-1",
14930
15054
  "tone-info",
14931
15055
  "tone-warning"
14932
15056
  ],
@@ -14940,7 +15064,7 @@
14940
15064
  "examples": [
14941
15065
  {
14942
15066
  "label": "Post type (tone + icon)",
14943
- "code": "<RadioGroup defaultValue=\"post\" className=\"grid sm:grid-cols-3\"><RadioCard value=\"post\" tone=\"standard\" icon={<Icon name=\"plus\" />} title=\"Standard\" description=\"Share without minting\" /><RadioCard value=\"collectible\" tone=\"collectible\" icon={<Icon name=\"gem\" />} title=\"Collectible\" description=\"Free to collect as NFT\" /><RadioCard value=\"edition\" tone=\"edition\" icon={<Icon name=\"image-stack\" />} title=\"Edition\" description=\"Sell as NFT editions\" /></RadioGroup>",
15067
+ "code": "<RadioGroup defaultValue=\"starter\" className=\"grid sm:grid-cols-3\"><RadioCard value=\"starter\" tone=\"3\" icon={<Icon name=\"plus\" />} title=\"Starter\" description=\"Free for one person\" /><RadioCard value=\"studio\" tone=\"2\" icon={<Icon name=\"gem\" />} title=\"Studio\" description=\"For small teams\" /><RadioCard value=\"business\" tone=\"1\" icon={<Icon name=\"image-stack\" />} title=\"Business\" description=\"SSO and audit log\" /></RadioGroup>",
14944
15068
  "imports": "import { Icon, RadioCard, RadioGroup } from \"@sableui/react\""
14945
15069
  },
14946
15070
  {
@@ -15147,7 +15271,7 @@
15147
15271
  "examples": [
15148
15272
  {
15149
15273
  "label": "Vertical with overflow",
15150
- "code": "<Scroller label=\"Editions\" className=\"h-64\"><ul className=\"space-y-2 p-4\">{items.map((item) => (<li key={item.id} className=\"text-body-sm\">{item.title}</li>))}</ul></Scroller>",
15274
+ "code": "<Scroller label=\"Projects\" className=\"h-64\"><ul className=\"space-y-2 p-4\">{items.map((item) => (<li key={item.id} className=\"text-body-sm\">{item.title}</li>))}</ul></Scroller>",
15151
15275
  "imports": "import { Scroller } from \"@sableui/react\""
15152
15276
  },
15153
15277
  {
@@ -15453,7 +15577,7 @@
15453
15577
  },
15454
15578
  {
15455
15579
  "label": "Uncontrolled, sized as a whole",
15456
- "code": "<Field label=\"Search posts\"><SearchInput defaultValue=\"editions\" className=\"max-w-80\" /></Field>",
15580
+ "code": "<Field label=\"Search posts\"><SearchInput defaultValue=\"roadmap\" className=\"max-w-80\" /></Field>",
15457
15581
  "imports": "import { Field, SearchInput } from \"@sableui/react\""
15458
15582
  },
15459
15583
  {
@@ -15742,9 +15866,9 @@
15742
15866
  "lg"
15743
15867
  ],
15744
15868
  "tone": [
15745
- "edition",
15746
- "collectible",
15747
- "standard",
15869
+ "1",
15870
+ "2",
15871
+ "3",
15748
15872
  "info",
15749
15873
  "primary",
15750
15874
  "neutral",
@@ -15754,9 +15878,9 @@
15754
15878
  ]
15755
15879
  },
15756
15880
  "tokensUsed": [
15757
- "tone-edition",
15758
- "tone-collectible",
15759
- "tone-standard",
15881
+ "tone-1",
15882
+ "tone-2",
15883
+ "tone-3",
15760
15884
  "tone-info",
15761
15885
  "primary",
15762
15886
  "muted-foreground",
@@ -15791,13 +15915,13 @@
15791
15915
  }
15792
15916
  ],
15793
15917
  "a11y": [
15794
- "Rendered role=img with an aria-label built from `label` and each segment's share (\"Revenue by type: Editions 42%, Collectibles 27%\"), so the whole breakdown is read in one stop.",
15918
+ "Rendered role=img with an aria-label built from `label` and each segment's share (\"Revenue by channel: Direct 42%, Partners 27%\"), so the whole breakdown is read in one stop.",
15795
15919
  "Colour alone does not identify a segment: always show a legend with each segment's label beside the bar."
15796
15920
  ],
15797
15921
  "examples": [
15798
15922
  {
15799
15923
  "label": "With a legend",
15800
- "code": "const sales = [\n\t{ label: \"Editions\", value: 42, tone: \"edition\" },\n\t{ label: \"Collectibles\", value: 27, tone: \"collectible\" },\n\t{ label: \"Standard\", value: 19, tone: \"standard\" },\n] as const\n\n<Stack gap={3}>\n\t<SegmentedBar label=\"Revenue by type\" segments={[...sales]} size=\"lg\" />\n\t<ul className=\"flex flex-col gap-1.5\">\n\t\t{sales.map((s) => (\n\t\t\t<li key={s.label} className=\"flex items-center gap-2 text-body-sm\">\n\t\t\t\t<StatusDot tone={s.tone} />\n\t\t\t\t<span className=\"flex-1 text-muted-foreground\">{s.label}</span>\n\t\t\t\t<span className=\"text-mono-caption\">{s.value}%</span>\n\t\t\t</li>\n\t\t))}\n\t</ul>\n</Stack>",
15924
+ "code": "const sales = [\n\t{ label: \"Direct\", value: 42, tone: \"1\" },\n\t{ label: \"Partners\", value: 27, tone: \"2\" },\n\t{ label: \"Referrals\", value: 19, tone: \"3\" },\n] as const\n\n<Stack gap={3}>\n\t<SegmentedBar label=\"Revenue by channel\" segments={[...sales]} size=\"lg\" />\n\t<ul className=\"flex flex-col gap-1.5\">\n\t\t{sales.map((s) => (\n\t\t\t<li key={s.label} className=\"flex items-center gap-2 text-body-sm\">\n\t\t\t\t<StatusDot tone={s.tone} />\n\t\t\t\t<span className=\"flex-1 text-muted-foreground\">{s.label}</span>\n\t\t\t\t<span className=\"text-mono-caption\">{s.value}%</span>\n\t\t\t</li>\n\t\t))}\n\t</ul>\n</Stack>",
15801
15925
  "imports": "import { SegmentedBar, Stack, StatusDot } from \"@sableui/react\""
15802
15926
  },
15803
15927
  {
@@ -15997,12 +16121,12 @@
15997
16121
  },
15998
16122
  {
15999
16123
  "label": "Inside Field",
16000
- "code": "<Field label=\"Default visibility\" description=\"Controls how new editions appear.\"><Select defaultValue=\"public\"><SelectTrigger placeholder=\"Choose visibility\" /><SelectContent><SelectItem value=\"public\">Public</SelectItem><SelectItem value=\"private\">Private</SelectItem><SelectItem value=\"unlisted\">Unlisted</SelectItem></SelectContent></Select></Field>",
16124
+ "code": "<Field label=\"Default visibility\" description=\"Controls how new projects appear.\"><Select defaultValue=\"public\"><SelectTrigger placeholder=\"Choose visibility\" /><SelectContent><SelectItem value=\"public\">Public</SelectItem><SelectItem value=\"private\">Private</SelectItem><SelectItem value=\"unlisted\">Unlisted</SelectItem></SelectContent></Select></Field>",
16001
16125
  "imports": "import { Field, Select, SelectContent, SelectItem, SelectTrigger } from \"@sableui/react\""
16002
16126
  },
16003
16127
  {
16004
16128
  "label": "Rich item labels",
16005
- "code": "<Select defaultValue=\"edition\" items={[{ value: \"edition\", label: \"Edition\" }, { value: \"collectible\", label: \"Collectible\" }]}><SelectTrigger /><SelectContent><SelectItem value=\"edition\" label=\"Edition\"><span className=\"flex items-center gap-2\"><Badge tone=\"edition\">Edition</Badge><span>Edition drop</span></span></SelectItem><SelectItem value=\"collectible\" label=\"Collectible\"><span className=\"flex items-center gap-2\"><Badge tone=\"collectible\">Collectible</Badge><span>Collectible</span></span></SelectItem></SelectContent></Select>",
16129
+ "code": "<Select defaultValue=\"business\" items={[{ value: \"business\", label: \"Business\" }, { value: \"studio\", label: \"Studio\" }]}><SelectTrigger /><SelectContent><SelectItem value=\"business\" label=\"Business\"><span className=\"flex items-center gap-2\"><Badge tone=\"1\">Business</Badge><span>Business plan</span></span></SelectItem><SelectItem value=\"studio\" label=\"Studio\"><span className=\"flex items-center gap-2\"><Badge tone=\"2\">Studio</Badge><span>Studio</span></span></SelectItem></SelectContent></Select>",
16006
16130
  "imports": "import { Badge, Select, SelectContent, SelectItem, SelectTrigger } from \"@sableui/react\""
16007
16131
  }
16008
16132
  ]
@@ -16467,7 +16591,7 @@
16467
16591
  },
16468
16592
  {
16469
16593
  "label": "Bottom side",
16470
- "code": "<Sheet><SheetTrigger render={<Button variant=\"secondary\">Details</Button>} /><SheetContent side=\"bottom\"><SheetHeader><SheetTitle>Edition details</SheetTitle></SheetHeader><SheetFooter><SheetClose render={<Button variant=\"secondary\">Close</Button>} /></SheetFooter></SheetContent></Sheet>",
16594
+ "code": "<Sheet><SheetTrigger render={<Button variant=\"secondary\">Details</Button>} /><SheetContent side=\"bottom\"><SheetHeader><SheetTitle>Order details</SheetTitle></SheetHeader><SheetFooter><SheetClose render={<Button variant=\"secondary\">Close</Button>} /></SheetFooter></SheetContent></Sheet>",
16471
16595
  "imports": "import { Button, Sheet, SheetClose, SheetContent, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from \"@sableui/react\""
16472
16596
  }
16473
16597
  ]
@@ -16669,7 +16793,7 @@
16669
16793
  "examples": [
16670
16794
  {
16671
16795
  "label": "Rail with nav",
16672
- "code": "<Sidebar><SidebarHeader><SidebarBrand mark={<Icon name=\"music\" variant=\"solid\" />} label=\"Undertow\" render={<Link to=\"/\" />} /></SidebarHeader><SidebarNav><SidebarItem icon={<Icon name=\"bell\" />} label=\"Notifications\" badge={<NotificationBadge count={3} />} render={<Link to=\"/notifications\" />} /></SidebarNav><SidebarFooter>{wallet}</SidebarFooter></Sidebar>",
16796
+ "code": "<Sidebar><SidebarHeader><SidebarBrand mark={<Icon name=\"music\" variant=\"solid\" />} label=\"Undertow\" render={<Link to=\"/\" />} /></SidebarHeader><SidebarNav><SidebarItem icon={<Icon name=\"bell\" />} label=\"Notifications\" badge={<NotificationBadge count={3} />} render={<Link to=\"/notifications\" />} /></SidebarNav><SidebarFooter>{account}</SidebarFooter></Sidebar>",
16673
16797
  "imports": "import { Icon, NotificationBadge, Sidebar, SidebarBrand, SidebarFooter, SidebarHeader, SidebarItem, SidebarNav } from \"@sableui/react\"\n// <Link> = your router's link (react-router, next/link, @tanstack/router, …)"
16674
16798
  },
16675
16799
  {
@@ -16821,7 +16945,7 @@
16821
16945
  },
16822
16946
  {
16823
16947
  "label": "Chrome-weight rail (size=lg)",
16824
- "code": "<SideNav size=\"lg\"><SideNavItem label=\"Profile\" active render={<Link to=\"/settings/profile\" />} /><SideNavItem label=\"Wallets\" render={<Link to=\"/settings/wallets\" />} /><SideNavItem label=\"Security\" render={<Link to=\"/settings/security\" />} /></SideNav>",
16948
+ "code": "<SideNav size=\"lg\"><SideNavItem label=\"Profile\" active render={<Link to=\"/settings/profile\" />} /><SideNavItem label=\"Billing\" render={<Link to=\"/settings/billing\" />} /><SideNavItem label=\"Security\" render={<Link to=\"/settings/security\" />} /></SideNav>",
16825
16949
  "imports": "import { SideNav, SideNavItem } from \"@sableui/react\"\n// <Link> = your router's link (react-router, next/link, @tanstack/router, …)"
16826
16950
  },
16827
16951
  {
@@ -17217,7 +17341,7 @@
17217
17341
  },
17218
17342
  {
17219
17343
  "name": "tone",
17220
- "type": "\"edition\" | \"collectible\" | \"standard\" | \"info\" | \"warning\" | \"primary\" | \"neutral\" | \"success\" | \"destructive\"",
17344
+ "type": "\"1\" | \"2\" | \"3\" | \"info\" | \"warning\" | \"primary\" | \"neutral\" | \"success\" | \"destructive\"",
17221
17345
  "default": "\"primary\"",
17222
17346
  "description": "Colour by role."
17223
17347
  },
@@ -17249,9 +17373,9 @@
17249
17373
  },
17250
17374
  "tokensUsed": [
17251
17375
  "primary",
17252
- "tone-edition",
17253
- "tone-collectible",
17254
- "tone-standard",
17376
+ "tone-1",
17377
+ "tone-2",
17378
+ "tone-3",
17255
17379
  "tone-info",
17256
17380
  "tone-warning",
17257
17381
  "muted-foreground",
@@ -17431,7 +17555,7 @@
17431
17555
  },
17432
17556
  {
17433
17557
  "label": "Inside a button (Button owns it — use loading)",
17434
- "code": "<Button loading>Collecting</Button>",
17558
+ "code": "<Button loading>Saving</Button>",
17435
17559
  "imports": "import { Button } from \"@sableui/react\""
17436
17560
  }
17437
17561
  ]
@@ -17614,9 +17738,9 @@
17614
17738
  "props": [
17615
17739
  {
17616
17740
  "name": "tone",
17617
- "type": "\"neutral\" | \"standard\" | \"success\" | \"warning\" | \"info\" | \"edition\" | \"collectible\" | \"destructive\"",
17741
+ "type": "\"neutral\" | \"1\" | \"2\" | \"3\" | \"success\" | \"warning\" | \"info\" | \"destructive\" | \"red\" | \"orange\" | \"yellow\" | \"green\" | \"teal\" | \"blue\" | \"purple\" | \"pink\"",
17618
17742
  "default": "\"neutral\"",
17619
- "description": "Semantic color of the dot."
17743
+ "description": "Semantic color of the dot, or a palette family (red … pink) for a category legend."
17620
17744
  },
17621
17745
  {
17622
17746
  "name": "size",
@@ -17634,13 +17758,21 @@
17634
17758
  "variants": {
17635
17759
  "tone": [
17636
17760
  "neutral",
17637
- "standard",
17761
+ "1",
17762
+ "2",
17763
+ "3",
17638
17764
  "success",
17639
17765
  "warning",
17640
17766
  "info",
17641
- "edition",
17642
- "collectible",
17643
- "destructive"
17767
+ "destructive",
17768
+ "red",
17769
+ "orange",
17770
+ "yellow",
17771
+ "green",
17772
+ "teal",
17773
+ "blue",
17774
+ "purple",
17775
+ "pink"
17644
17776
  ],
17645
17777
  "size": [
17646
17778
  "sm",
@@ -17649,13 +17781,14 @@
17649
17781
  ]
17650
17782
  },
17651
17783
  "tokensUsed": [
17784
+ "palette-dot",
17652
17785
  "muted-foreground",
17653
- "tone-standard",
17786
+ "tone-3",
17654
17787
  "success",
17655
17788
  "tone-warning",
17656
17789
  "tone-info",
17657
- "tone-edition",
17658
- "tone-collectible",
17790
+ "tone-1",
17791
+ "tone-2",
17659
17792
  "destructive"
17660
17793
  ],
17661
17794
  "a11y": [
@@ -17665,7 +17798,7 @@
17665
17798
  "examples": [
17666
17799
  {
17667
17800
  "label": "Tones",
17668
- "code": "<Row gap={2}><StatusDot tone=\"standard\" /><StatusDot tone=\"info\" /><StatusDot tone=\"warning\" /><StatusDot tone=\"destructive\" /></Row>",
17801
+ "code": "<Row gap={2}><StatusDot tone=\"3\" /><StatusDot tone=\"info\" /><StatusDot tone=\"warning\" /><StatusDot tone=\"destructive\" /></Row>",
17669
17802
  "imports": "import { Row, StatusDot } from \"@sableui/react\""
17670
17803
  },
17671
17804
  {
@@ -18304,8 +18437,8 @@
18304
18437
  ],
18305
18438
  "examples": [
18306
18439
  {
18307
- "label": "Collectors",
18308
- "code": "<Table>\n\t<TableCaption>Recent collectors</TableCaption>\n\t<TableHeader>\n\t\t<TableRow>\n\t\t\t<TableHead>Collector</TableHead>\n\t\t\t<TableHead>Edition</TableHead>\n\t\t\t<TableHead>Paid</TableHead>\n\t\t</TableRow>\n\t</TableHeader>\n\t<TableBody>\n\t\t<TableRow>\n\t\t\t<TableCell>@nova</TableCell>\n\t\t\t<TableCell>#012</TableCell>\n\t\t\t<TableCell>0.5 SOL</TableCell>\n\t\t</TableRow>\n\t</TableBody>\n</Table>",
18440
+ "label": "Orders",
18441
+ "code": "<Table>\n\t<TableCaption>Recent orders</TableCaption>\n\t<TableHeader>\n\t\t<TableRow>\n\t\t\t<TableHead>Customer</TableHead>\n\t\t\t<TableHead>Order</TableHead>\n\t\t\t<TableHead>Paid</TableHead>\n\t\t</TableRow>\n\t</TableHeader>\n\t<TableBody>\n\t\t<TableRow>\n\t\t\t<TableCell>@nova</TableCell>\n\t\t\t<TableCell>1042</TableCell>\n\t\t\t<TableCell>$48.00</TableCell>\n\t\t</TableRow>\n\t</TableBody>\n</Table>",
18309
18442
  "imports": "import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from \"@sableui/react\""
18310
18443
  },
18311
18444
  {
@@ -18315,12 +18448,12 @@
18315
18448
  },
18316
18449
  {
18317
18450
  "label": "Flush in a card",
18318
- "code": "<Card><CardHeader><CardTitle>Collectors</CardTitle></CardHeader><CardContent flush><Table><TableHeader><TableRow><TableHead>Collector</TableHead><TableHead>Paid</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>@nova</TableCell><TableCell>0.5 SOL</TableCell></TableRow></TableBody></Table></CardContent></Card>",
18451
+ "code": "<Card><CardHeader><CardTitle>Customers</CardTitle></CardHeader><CardContent flush><Table><TableHeader><TableRow><TableHead>Customer</TableHead><TableHead>Paid</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>@nova</TableCell><TableCell>$48.00</TableCell></TableRow></TableBody></Table></CardContent></Card>",
18319
18452
  "imports": "import { Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from \"@sableui/react\""
18320
18453
  },
18321
18454
  {
18322
18455
  "label": "Summary footer",
18323
- "code": "<Table><TableHeader><TableRow><TableHead>Item</TableHead><TableHead className=\"text-end\">Amount</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>Primary sale</TableCell><TableCell className=\"text-end\">42 SOL</TableCell></TableRow></TableBody><TableFooter><TableRow><TableCell>Total</TableCell><TableCell className=\"text-end\">42 SOL</TableCell></TableRow></TableFooter></Table>",
18456
+ "code": "<Table><TableHeader><TableRow><TableHead>Item</TableHead><TableHead className=\"text-end\">Amount</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>Studio plan</TableCell><TableCell className=\"text-end\">$192.00</TableCell></TableRow></TableBody><TableFooter><TableRow><TableCell>Total</TableCell><TableCell className=\"text-end\">$192.00</TableCell></TableRow></TableFooter></Table>",
18324
18457
  "imports": "import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from \"@sableui/react\""
18325
18458
  }
18326
18459
  ]
@@ -18448,7 +18581,7 @@
18448
18581
  },
18449
18582
  {
18450
18583
  "label": "With a disabled tab",
18451
- "code": "<Tabs defaultValue=\"overview\"><TabsList><TabsTrigger value=\"overview\">Overview</TabsTrigger><TabsTrigger value=\"holders\" disabled>Holders</TabsTrigger></TabsList><TabsPanel value=\"overview\">Overview content</TabsPanel></Tabs>",
18584
+ "code": "<Tabs defaultValue=\"overview\"><TabsList><TabsTrigger value=\"overview\">Overview</TabsTrigger><TabsTrigger value=\"billing\" disabled>Billing</TabsTrigger></TabsList><TabsPanel value=\"overview\">Overview content</TabsPanel></Tabs>",
18452
18585
  "imports": "import { Tabs, TabsList, TabsPanel, TabsTrigger } from \"@sableui/react\""
18453
18586
  }
18454
18587
  ]
@@ -18554,7 +18687,7 @@
18554
18687
  },
18555
18688
  {
18556
18689
  "label": "With rows",
18557
- "code": "<Textarea rows={6} placeholder=\"Tell the story behind this edition…\" />",
18690
+ "code": "<Textarea rows={6} placeholder=\"Tell your team what this project is for…\" />",
18558
18691
  "imports": "import { Textarea } from \"@sableui/react\""
18559
18692
  },
18560
18693
  {
@@ -18569,7 +18702,7 @@
18569
18702
  },
18570
18703
  {
18571
18704
  "label": "Disabled",
18572
- "code": "<Textarea disabled defaultValue=\"Captions lock once the edition is minted.\" />",
18705
+ "code": "<Textarea disabled defaultValue=\"Notes lock once the invoice is sent.\" />",
18573
18706
  "imports": "import { Textarea } from \"@sableui/react\""
18574
18707
  }
18575
18708
  ]
@@ -19243,7 +19376,7 @@
19243
19376
  },
19244
19377
  {
19245
19378
  "label": "Persistent loading, updated in place",
19246
- "code": "const id = toast.loading(\"Pinning to Arweave…\"); /* later */ toast.update(id, { title: \"Pinned\", type: \"success\", timeout: 4000 })",
19379
+ "code": "const id = toast.loading(\"Uploading report…\"); /* later */ toast.update(id, { title: \"Uploaded\", type: \"success\", timeout: 4000 })",
19247
19380
  "imports": "import { toast } from \"@sableui/react\""
19248
19381
  },
19249
19382
  {
@@ -19808,7 +19941,7 @@
19808
19941
  "examples": [
19809
19942
  {
19810
19943
  "label": "Basic",
19811
- "code": "<TooltipProvider><Tooltip><TooltipTrigger render={<Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Info\"><Icon name=\"info\" /></Button>} /><TooltipContent>Stored permanently on Arweave.</TooltipContent></Tooltip></TooltipProvider>",
19944
+ "code": "<TooltipProvider><Tooltip><TooltipTrigger render={<Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Info\"><Icon name=\"info\" /></Button>} /><TooltipContent>Saved to your workspace.</TooltipContent></Tooltip></TooltipProvider>",
19812
19945
  "imports": "import { Button, Icon, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@sableui/react\""
19813
19946
  },
19814
19947
  {
@@ -19818,7 +19951,7 @@
19818
19951
  },
19819
19952
  {
19820
19953
  "label": "Shorthand (SimpleTooltip)",
19821
- "code": "<SimpleTooltip content=\"Stored permanently on Arweave.\"><Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Info\"><Icon name=\"info\" /></Button></SimpleTooltip>",
19954
+ "code": "<SimpleTooltip content=\"Saved to your workspace.\"><Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Info\"><Icon name=\"info\" /></Button></SimpleTooltip>",
19822
19955
  "imports": "import { Button, Icon, SimpleTooltip } from \"@sableui/react\""
19823
19956
  }
19824
19957
  ]
@@ -20348,7 +20481,7 @@
20348
20481
  {
20349
20482
  "name": "AuthPage",
20350
20483
  "path": "patterns/auth-page.tsx",
20351
- "summary": "Split sign-in page: a form column (brand mark, heading, email + password, primary sign-in, passkey and wallet alternatives, sign-up switch, terms) beside a brand panel painted from the theme's tones with a featured release card. Below lg the panel drops away and the form is the whole screen.",
20484
+ "summary": "Split sign-in page: a form column (brand mark, heading, email + password, primary sign-in, passkey and email-link alternatives, sign-up switch, terms) beside a brand panel painted from the theme's tones with a customer story card. Below lg the panel drops away and the form is the whole screen.",
20352
20485
  "intent": "Reach for this for sign-in, sign-up, forgot-password, and any single-purpose one-column form route (invite accept, unsubscribe). Not for multi-section forms (use SettingsPage).",
20353
20486
  "notes": [
20354
20487
  "The form column is a `form`-capped Region (480px), so the controls keep the same measure whether the panel is present or not.",
@@ -20356,20 +20489,20 @@
20356
20489
  "The heading is `text-heading-1` in an h1 — it is the page outline, so it takes a heading tier, not a card title.",
20357
20490
  "Brand mark, form and terms are a three-part column: mark pinned to the top, form vertically centred in the remaining space, terms pinned to the bottom.",
20358
20491
  "Forgot password sits under the password field, right-aligned, before the submit button — so it comes right after the field in tab order. Field has no label-row action slot yet.",
20359
- "One primary action. Passkey and wallet are outline buttons under an `or` Separator; switching to sign-up is a quiet `link-underline` link.",
20492
+ "One primary action. Passkey and email link are outline buttons under an `or` Separator; switching to sign-up is a quiet `link-underline` link.",
20360
20493
  "Validation uses Field `validate` for product copy instead of the browser's generic required message. A rejected sign-in comes back through Form `errors` on the password field; the submit Button shows `loading` while the request is in flight.",
20361
20494
  "The brand panel artwork is a mesh of the theme's decorative tones over `--primary`, so it re-paints for every theme and mode. The relative-colour `oklch(from var(--tone-*) …)` lift needs an `inline-color` disable with a reason.",
20362
20495
  "The panel is `hidden lg:flex`: on a phone the form is the whole screen, with no decorative scroll above it."
20363
20496
  ],
20364
20497
  "a11y": [
20365
- "The route renders inside <main> with one h1; the brand panel is an <aside> labelled \"Featured release\".",
20498
+ "The route renders inside <main> with one h1; the brand panel is an <aside> labelled \"Customer story\".",
20366
20499
  "Field auto-wires labels, describedby, and invalid state onto the inputs; on submit, the first invalid field is focused.",
20367
20500
  "Autocomplete attributes (`email webauthn`, `current-password`) keep password managers and passkey autofill working.",
20368
20501
  "The Forgot password and Create one links use `hit-target` to meet the 44px touch floor without changing layout.",
20369
20502
  "The artwork swatches are aria-hidden decoration; the brand link's accessible name is the wordmark."
20370
20503
  ],
20371
20504
  "imports": "import { useState } from \"react\"\nimport { Avatar, Badge, Button, Card, CardContent, Entity, Field, Form, GradientArt, Icon, Input, Region, Separator, Stack } from \"@sableui/react\"",
20372
- "code": "export function AuthPage() {\n\tconst [pending, setPending] = useState(false)\n\tconst [errors, setErrors] = useState<Record<string, string>>({})\n\n\treturn (\n\t\t<main className=\"grid min-h-dvh bg-background lg:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)]\">\n\t\t\t<Region max=\"form\" safeArea={[\"top\", \"bottom\"]} className=\"flex flex-col\">\n\t\t\t\t{/* safeArea owns the region's own padding, so the breathing room sits one level in. */}\n\t\t\t\t<Stack gap={0} className=\"flex-1 py-8 sm:py-10\">\n\t\t\t\t\t<a href=\"/\" className=\"flex items-center gap-2.5 self-start rounded-sm motion-interactive motion-press\">\n\t\t\t\t\t\t{/* The same mark the app shell's sidebar and header carry. */}\n\t\t\t\t\t\t<Icon name=\"gem\" variant=\"solid\" className=\"text-2xl\" />\n\t\t\t\t\t\t<span className=\"text-title-lg\">Atelier</span>\n\t\t\t\t\t</a>\n\n\t\t\t\t\t<Stack gap={4} justify=\"center\" className=\"flex-1 py-12\">\n\t\t\t\t\t\t<Stack gap={1.5}>\n\t\t\t\t\t\t\t<h1 className=\"text-heading-1\">Welcome back</h1>\n\t\t\t\t\t\t\t<p className=\"text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\tSign in to collect new releases and manage your editions.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</Stack>\n\n\t\t\t\t\t\t<Form\n\t\t\t\t\t\t\tclassName=\"gap-5\"\n\t\t\t\t\t\t\terrors={errors}\n\t\t\t\t\t\t\tonFormSubmit={() => {\n\t\t\t\t\t\t\t\tsetPending(true)\n\t\t\t\t\t\t\t\t// Replace with your auth call; a rejected sign-in comes back as a field error.\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\tsetPending(false)\n\t\t\t\t\t\t\t\t\tsetErrors({ password: \"That email and password don’t match. Try again or reset your password.\" })\n\t\t\t\t\t\t\t\t}, 900)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Field label=\"Email\" name=\"email\" validate={(value) => (value ? null : \"Enter your email address.\")}>\n\t\t\t\t\t\t\t\t<Input type=\"email\" autoComplete=\"email webauthn\" placeholder=\"you@example.com\" />\n\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t<Stack gap={1.5}>\n\t\t\t\t\t\t\t\t<Field label=\"Password\" name=\"password\" validate={(value) => (value ? null : \"Enter your password.\")}>\n\t\t\t\t\t\t\t\t\t<Input type=\"password\" autoComplete=\"current-password\" />\n\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t<a href=\"/forgot-password\" className=\"hit-target self-end text-body-sm text-muted-foreground link-underline motion-interactive\">\n\t\t\t\t\t\t\t\t\tForgot password?\n\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t<Button type=\"submit\" size=\"cta\" loading={pending} loadingLabel=\"Signing in\" className=\"w-full\">\n\t\t\t\t\t\t\t\tSign in\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</Form>\n\n\t\t\t\t\t\t<Separator label=\"or\" />\n\n\t\t\t\t\t\t<Stack gap={1.5}>\n\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"cta\" className=\"w-full\" iconStart={<Icon name=\"key\" />}>\n\t\t\t\t\t\t\t\tSign in with a passkey\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"cta\" className=\"w-full\" iconStart={<Icon name=\"wallet\" />}>\n\t\t\t\t\t\t\t\tConnect a wallet\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</Stack>\n\n\t\t\t\t\t\t<p className=\"text-body-md text-muted-foreground\">\n\t\t\t\t\t\t\tNo account?{\" \"}\n\t\t\t\t\t\t\t<a href=\"/signup\" className=\"hit-target link-underline motion-interactive text-foreground\">\n\t\t\t\t\t\t\t\tCreate one\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</Stack>\n\n\t\t\t\t\t<p className=\"text-body-xs text-muted-foreground\">\n\t\t\t\t\t\tBy signing in you agree to the{\" \"}\n\t\t\t\t\t\t<a href=\"/terms\" className=\"link-underline motion-interactive\">\n\t\t\t\t\t\t\tTerms\n\t\t\t\t\t\t</a>{\" \"}\n\t\t\t\t\t\tand{\" \"}\n\t\t\t\t\t\t<a href=\"/privacy\" className=\"link-underline motion-interactive\">\n\t\t\t\t\t\t\tPrivacy Policy\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t.\n\t\t\t\t\t</p>\n\t\t\t\t</Stack>\n\t\t\t</Region>\n\n\t\t\t{/* The brand panel is decoration: on a phone the form is the whole screen. */}\n\t\t\t<aside aria-label=\"Featured release\" className=\"hidden p-3 lg:flex\">\n\t\t\t\t{/* Art painted from the theme's tones over its brand, so it follows every look. */}\n\t\t\t\t<GradientArt seed=\"Untitled (gold rush)\" className=\"flex flex-1 flex-col justify-end rounded-xl p-8\">\n\t\t\t\t\t<Card className=\"max-w-md\">\n\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t<Badge tone=\"edition\" className=\"self-start\">\n\t\t\t\t\t\t\t\t\tEdition of 50\n\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t<p className=\"text-title-lg\">\n\t\t\t\t\t\t\t\t\t“Every edition is a conversation with whoever holds it next.”\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\tclassName=\"p-0\"\n\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" fallback=\"MO\" />}\n\t\t\t\t\t\t\t\t\ttitle=\"Mara Okafor\"\n\t\t\t\t\t\t\t\t\tsubtitle=\"Untitled (gold rush), 2026\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t</Card>\n\t\t\t\t</GradientArt>\n\t\t\t</aside>\n\t\t</main>\n\t)\n}"
20505
+ "code": "export function AuthPage() {\n\tconst [pending, setPending] = useState(false)\n\tconst [errors, setErrors] = useState<Record<string, string>>({})\n\n\treturn (\n\t\t<main className=\"grid min-h-dvh bg-background lg:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)]\">\n\t\t\t<Region max=\"form\" safeArea={[\"top\", \"bottom\"]} className=\"flex flex-col\">\n\t\t\t\t{/* safeArea owns the region's own padding, so the breathing room sits one level in. */}\n\t\t\t\t<Stack gap={0} className=\"flex-1 py-8 sm:py-10\">\n\t\t\t\t\t<a href=\"/\" className=\"flex items-center gap-2.5 self-start rounded-sm motion-interactive motion-press\">\n\t\t\t\t\t\t{/* The same mark the app shell's sidebar and header carry. */}\n\t\t\t\t\t\t<Icon name=\"gem\" variant=\"solid\" className=\"text-2xl\" />\n\t\t\t\t\t\t<span className=\"text-title-lg\">Atelier</span>\n\t\t\t\t\t</a>\n\n\t\t\t\t\t<Stack gap={4} justify=\"center\" className=\"flex-1 py-12\">\n\t\t\t\t\t\t<Stack gap={1.5}>\n\t\t\t\t\t\t\t<h1 className=\"text-heading-1\">Welcome back</h1>\n\t\t\t\t\t\t\t<p className=\"text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\tSign in to pick up where you and your team left off.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</Stack>\n\n\t\t\t\t\t\t<Form\n\t\t\t\t\t\t\tclassName=\"gap-5\"\n\t\t\t\t\t\t\terrors={errors}\n\t\t\t\t\t\t\tonFormSubmit={() => {\n\t\t\t\t\t\t\t\tsetPending(true)\n\t\t\t\t\t\t\t\t// Replace with your auth call; a rejected sign-in comes back as a field error.\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\tsetPending(false)\n\t\t\t\t\t\t\t\t\tsetErrors({ password: \"That email and password don’t match. Try again or reset your password.\" })\n\t\t\t\t\t\t\t\t}, 900)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Field label=\"Email\" name=\"email\" validate={(value) => (value ? null : \"Enter your email address.\")}>\n\t\t\t\t\t\t\t\t<Input type=\"email\" autoComplete=\"email webauthn\" placeholder=\"you@example.com\" />\n\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t<Stack gap={1.5}>\n\t\t\t\t\t\t\t\t<Field label=\"Password\" name=\"password\" validate={(value) => (value ? null : \"Enter your password.\")}>\n\t\t\t\t\t\t\t\t\t<Input type=\"password\" autoComplete=\"current-password\" />\n\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t<a href=\"/forgot-password\" className=\"hit-target self-end text-body-sm text-muted-foreground link-underline motion-interactive\">\n\t\t\t\t\t\t\t\t\tForgot password?\n\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t<Button type=\"submit\" size=\"cta\" loading={pending} loadingLabel=\"Signing in\" className=\"w-full\">\n\t\t\t\t\t\t\t\tSign in\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</Form>\n\n\t\t\t\t\t\t<Separator label=\"or\" />\n\n\t\t\t\t\t\t<Stack gap={1.5}>\n\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"cta\" className=\"w-full\" iconStart={<Icon name=\"key\" />}>\n\t\t\t\t\t\t\t\tSign in with a passkey\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"cta\" className=\"w-full\" iconStart={<Icon name=\"mail\" />}>\n\t\t\t\t\t\t\t\tEmail me a sign-in link\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</Stack>\n\n\t\t\t\t\t\t<p className=\"text-body-md text-muted-foreground\">\n\t\t\t\t\t\t\tNo account?{\" \"}\n\t\t\t\t\t\t\t<a href=\"/signup\" className=\"hit-target link-underline motion-interactive text-foreground\">\n\t\t\t\t\t\t\t\tCreate one\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</Stack>\n\n\t\t\t\t\t<p className=\"text-body-xs text-muted-foreground\">\n\t\t\t\t\t\tBy signing in you agree to the{\" \"}\n\t\t\t\t\t\t<a href=\"/terms\" className=\"link-underline motion-interactive\">\n\t\t\t\t\t\t\tTerms\n\t\t\t\t\t\t</a>{\" \"}\n\t\t\t\t\t\tand{\" \"}\n\t\t\t\t\t\t<a href=\"/privacy\" className=\"link-underline motion-interactive\">\n\t\t\t\t\t\t\tPrivacy Policy\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t.\n\t\t\t\t\t</p>\n\t\t\t\t</Stack>\n\t\t\t</Region>\n\n\t\t\t{/* The brand panel is decoration: on a phone the form is the whole screen. */}\n\t\t\t<aside aria-label=\"Customer story\" className=\"hidden p-3 lg:flex\">\n\t\t\t\t{/* Art painted from the theme's tones over its brand, so it follows every look. */}\n\t\t\t\t<GradientArt seed=\"Untitled (gold rush)\" className=\"flex flex-1 flex-col justify-end rounded-xl p-8\">\n\t\t\t\t\t<Card className=\"max-w-md\">\n\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t<Badge tone=\"1\" className=\"self-start\">\n\t\t\t\t\t\t\t\t\tCustomer story\n\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t<p className=\"text-title-lg\">\n\t\t\t\t\t\t\t\t\t“We moved three teams over in a week, and nobody asked for the old tool back.”\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\tclassName=\"p-0\"\n\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" fallback=\"MO\" />}\n\t\t\t\t\t\t\t\t\ttitle=\"Mara Okafor\"\n\t\t\t\t\t\t\t\t\tsubtitle=\"Head of Design, Northwind\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t</Card>\n\t\t\t\t</GradientArt>\n\t\t\t</aside>\n\t\t</main>\n\t)\n}"
20373
20506
  },
20374
20507
  {
20375
20508
  "name": "BrandLandingPage",
@@ -20396,7 +20529,7 @@
20396
20529
  "Status dots and the status Badge are paired with visible text labels so color is never the only signal."
20397
20530
  ],
20398
20531
  "imports": "import { Avatar, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Grid, Icon, Input, Region, Reveal, Row, Separator, Stack, StatusDot, TextReveal } from \"@sableui/react\"",
20399
- "code": "const navItems = [\n\t{ label: \"Platform\", href: \"#platform\" },\n\t{ label: \"How it works\", href: \"#how-it-works\" },\n\t{ label: \"Customers\", href: \"#customers\" },\n\t{ label: \"Pricing\", href: \"/pricing\" },\n]\n\nconst spaces = [\n\t{ name: \"Waypoint\", value: \"98.4%\", label: \"clear routes\" },\n\t{ name: \"Fielddesk\", value: \"12\", label: \"open holds\" },\n\t{ name: \"Archive\", value: \"4.8k\", label: \"sealed records\" },\n]\n\nconst routes = [\n\t{ label: \"Intake\", state: \"Open\", tone: \"info\" as const },\n\t{ label: \"Review\", state: \"Live\", tone: \"edition\" as const },\n\t{ label: \"Archive\", state: \"Safe\", tone: \"success\" as const },\n]\n\n// Weekly approvals: past weeks recede, the current week carries the signal.\nconst bars = [\n\t{ week: \"w1\", height: \"38%\", current: false },\n\t{ week: \"w2\", height: \"56%\", current: false },\n\t{ week: \"w3\", height: \"44%\", current: false },\n\t{ week: \"w4\", height: \"68%\", current: false },\n\t{ week: \"w5\", height: \"52%\", current: false },\n\t{ week: \"w6\", height: \"78%\", current: false },\n\t{ week: \"w7\", height: \"60%\", current: false },\n\t{ week: \"w8\", height: \"72%\", current: false },\n\t{ week: \"w9\", height: \"88%\", current: true },\n]\n\nconst sceneMetrics = [\n\t{ label: \"Cleared\", value: \"1.8M\", detail: \"last 30 days\" },\n\t{ label: \"Review\", value: \"42s\", detail: \"median path\" },\n\t{ label: \"Spaces\", value: \"284\", detail: \"active teams\" },\n]\n\nconst values = [\n\t{\n\t\ticon: \"lock\" as const,\n\t\ttitle: \"Private by default\",\n\t\tbody: \"Roles, approvals, and handoff history stay with the people who need them. No shared inboxes, no forwarded screenshots.\",\n\t},\n\t{\n\t\ticon: \"bell\" as const,\n\t\ttitle: \"Signals before surprises\",\n\t\tbody: \"Cove watches every route and surfaces stalled reviews and expiring approvals while there is still time to act.\",\n\t},\n\t{\n\t\ticon: \"clock\" as const,\n\t\ttitle: \"A trail for every decision\",\n\t\tbody: \"Each approval keeps its context, reviewer, and timestamp, so an audit takes an afternoon instead of a quarter.\",\n\t},\n]\n\nconst steps = [\n\t{\n\t\tnumber: \"01\",\n\t\ttitle: \"Connect your spaces\",\n\t\tbody: \"Bring teams, vendors, and record stores into one workspace, with roles that mirror how you already work.\",\n\t},\n\t{\n\t\tnumber: \"02\",\n\t\ttitle: \"Route every approval\",\n\t\tbody: \"Requests follow the path you define, with reviewers, deadlines, and escalation built in from the start.\",\n\t},\n\t{\n\t\tnumber: \"03\",\n\t\ttitle: \"Seal the record\",\n\t\tbody: \"Approved work is archived with its full context and is ready for review the moment it closes.\",\n\t},\n]\n\nconst outcomes = [\n\t{ value: \"3.2×\", label: \"faster approvals in the first quarter\" },\n\t{ value: \"68%\", label: \"fewer follow-ups on handoffs\" },\n\t{ value: \"1 day\", label: \"to prepare a quarterly audit\" },\n]\n\nconst footerGroups = [\n\t{\n\t\ttitle: \"Platform\",\n\t\tlinks: [\n\t\t\t{ label: \"Overview\", href: \"#platform\" },\n\t\t\t{ label: \"Signals\", href: \"/signals\" },\n\t\t\t{ label: \"Approvals\", href: \"/approvals\" },\n\t\t\t{ label: \"Records\", href: \"/records\" },\n\t\t],\n\t},\n\t{\n\t\ttitle: \"Company\",\n\t\tlinks: [\n\t\t\t{ label: \"About\", href: \"/about\" },\n\t\t\t{ label: \"Security\", href: \"/security\" },\n\t\t\t{ label: \"Customers\", href: \"#customers\" },\n\t\t\t{ label: \"Careers\", href: \"/careers\" },\n\t\t],\n\t},\n\t{\n\t\ttitle: \"Resources\",\n\t\tlinks: [\n\t\t\t{ label: \"Guides\", href: \"/guides\" },\n\t\t\t{ label: \"Status\", href: \"/status\" },\n\t\t\t{ label: \"Changelog\", href: \"/changelog\" },\n\t\t\t{ label: \"Docs\", href: \"/docs\" },\n\t\t],\n\t},\n]\n\nconst legalLinks = [\n\t{ label: \"Privacy\", href: \"/privacy\" },\n\t{ label: \"Terms\", href: \"/terms\" },\n\t{ label: \"Contact\", href: \"/contact\" },\n]\n\nexport function BrandLandingPage() {\n\treturn (\n\t\t<div className=\"min-h-dvh overflow-hidden bg-background text-foreground\">\n\t\t\t<header className=\"relative z-10 pt-4\">\n\t\t\t\t<Region max=\"wide\">\n\t\t\t\t\t<nav\n\t\t\t\t\t\taria-label=\"Main\"\n\t\t\t\t\t\tclassName=\"flex min-h-15 items-center justify-between gap-3 rounded-full border border-border bg-card/90 px-3 py-2 shadow-sm backdrop-blur\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<a href=\"/\" className=\"flex items-center gap-2 rounded-full px-2 motion-interactive motion-press\">\n\t\t\t\t\t\t\t<span className=\"grid size-8 place-items-center rounded-full bg-primary text-primary-foreground\">\n\t\t\t\t\t\t\t\t<Icon name=\"sparkles\" className=\"text-sm\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span className=\"text-title-lg\">Cove</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t<div className=\"hidden lg:block\">\n\t\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t\t{navItems.map((item) => (\n\t\t\t\t\t\t\t\t\t<Button key={item.label} variant=\"ghost\" size=\"sm\" render={<a href={item.href} />}>\n\t\t\t\t\t\t\t\t\t\t{item.label}\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" className=\"max-sm:hidden\" render={<a href=\"/login\" />}>\n\t\t\t\t\t\t\t\tSign in\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button size=\"sm\" render={<a href=\"/demo\" />}>\n\t\t\t\t\t\t\t\tRequest access\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t</nav>\n\t\t\t\t</Region>\n\t\t\t</header>\n\n\t\t\t<main>\n\t\t\t\t<Region as=\"section\" max=\"wide\" className=\"pb-20 pt-14 sm:pt-20 lg:pb-28\">\n\t\t\t\t\t<div className=\"grid items-center gap-12 lg:grid-cols-[1fr_1.1fr] lg:gap-10\">\n\t\t\t\t\t\t<Stack gap={3} className=\"max-w-2xl\">\n\t\t\t\t\t\t\t<Badge appearance=\"outline\" icon={<Icon name=\"lock\" className=\"text-sm\" />}>\n\t\t\t\t\t\t\t\tPrivate operations, made legible\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t<TextReveal\n\t\t\t\t\t\t\t\tas=\"h1\"\n\t\t\t\t\t\t\t\ttext=\"A calmer command center for high-trust work\"\n\t\t\t\t\t\t\t\tclassName=\"text-balance text-display-lg sm:text-display-2xl\"\n\t\t\t\t\t\t\t\tonce\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<p className=\"max-w-(--measure-prose) text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\tCove gives teams a polished front door and a serious operating\n\t\t\t\t\t\t\t\tsurface: approvals, signals, records, and handoffs in one\n\t\t\t\t\t\t\t\tunderstandable flow.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<Row gap={2} wrap className=\"pt-3\">\n\t\t\t\t\t\t\t\t<Button size=\"cta\" render={<a href=\"/demo\" />}>\n\t\t\t\t\t\t\t\t\tBook a walkthrough\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t<Button size=\"cta\" variant=\"outline\" render={<a href=\"#platform\" />}>\n\t\t\t\t\t\t\t\t\tExplore the platform\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t</Stack>\n\n\t\t\t\t\t\t{/* The product scene is a dark island: `dark` re-scopes every\n\t\t\t\t\t\t semantic token, so its surfaces, muted ink and status tones\n\t\t\t\t\t\t are the theme's own dark values in either page mode. It is a\n\t\t\t\t\t\t picture of the product, so it stays out of the tab order and\n\t\t\t\t\t\t the accessibility tree; the hero copy carries its meaning. */}\n\t\t\t\t\t\t<div className=\"dark\" aria-hidden inert>\n\t\t\t\t\t\t\t<div className=\"overflow-hidden rounded-xl border border-border-floating bg-popover p-3 text-popover-foreground shadow-xl\">\n\t\t\t\t\t\t\t\t{/* Nested surfaces take concentric corners: each level's radius is\n\t\t\t\t\t\t\t\t the shell's xl minus every inset between it and the shell edge. */}\n\t\t\t\t\t\t\t\t<div className=\"rounded-concentric-xl/3 border border-border bg-card\">\n\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between border-b border-border px-4 py-3\">\n\t\t\t\t\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t\t\t\t\t<StatusDot tone=\"warning\" />\n\t\t\t\t\t\t\t\t\t\t\t<StatusDot tone=\"edition\" />\n\t\t\t\t\t\t\t\t\t\t\t<StatusDot tone=\"success\" />\n\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-label-sm text-muted-foreground\">Live workspace</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<div className=\"grid gap-3 p-3 md:grid-cols-[0.7fr_1.3fr]\">\n\t\t\t\t\t\t\t\t\t\t<div className=\"space-y-3 max-md:hidden\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"rounded-concentric-xl/6 border border-border bg-background p-4\">\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-label-sm text-muted-foreground\">Spaces</p>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"mt-4 space-y-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{spaces.map((space) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div key={space.name} className=\"rounded-concentric-xl/10 bg-card p-3\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Row justify=\"between\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-title-sm\">{space.name}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-mono-sm\">{space.value}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-1 text-body-xs text-muted-foreground\">{space.label}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"rounded-concentric-xl/6 border border-border bg-background p-4\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Row justify=\"between\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-title-sm\">Route status</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Badge tone=\"success\" size=\"sm\" dot>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tHealthy\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"mt-4 space-y-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{routes.map((route) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Row key={route.label} justify=\"between\" className=\"rounded-full bg-card px-3 py-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<StatusDot tone={route.tone} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-body-sm\">{route.label}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-label-sm text-muted-foreground\">{route.state}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t\t\t<div className=\"rounded-concentric-xl/6 border border-border bg-background p-4\">\n\t\t\t\t\t\t\t\t\t\t\t<Row justify=\"between\" className=\"border-b border-border pb-4\">\n\t\t\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-label-sm text-muted-foreground\">Signal map</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-1 text-heading-4 sm:text-heading-3\">Every approval path in view</p>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"secondary\" size=\"sm\">Review</Button>\n\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"mt-5 grid gap-4 sm:mt-6 sm:gap-5\">\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"grid grid-cols-9 items-end gap-1.5 rounded-concentric-xl/10 bg-card p-4 sm:gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{bars.map((bar) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div key={bar.week} className=\"flex h-36 items-end sm:h-56\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={`w-full rounded-full ${bar.current ? \"bg-success\" : \"bg-secondary\"}`}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstyle={{ height: bar.height }}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"grid grid-cols-3 gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{sceneMetrics.map((metric) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div key={metric.label} className=\"rounded-concentric-xl/10 border border-border bg-card p-3\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"truncate text-label-sm text-muted-foreground\">{metric.label}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-2 text-heading-4 sm:text-heading-3\">{metric.value}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-1 text-body-xs text-muted-foreground max-sm:hidden\">{metric.detail}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\n\t\t\t\t<section id=\"platform\" aria-labelledby=\"brand-platform-title\" className=\"border-y border-border bg-muted/40 py-20 lg:py-28\">\n\t\t\t\t\t<Region max=\"wide\">\n\t\t\t\t\t\t<Stack gap={6}>\n\t\t\t\t\t\t\t<Stack gap={2} className=\"max-w-2xl\">\n\t\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">Why Cove</p>\n\t\t\t\t\t\t\t\t<h2 id=\"brand-platform-title\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\tOne private workspace for the decisions that matter.\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t<p className=\"max-w-(--measure-prose) text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tMost operations work lives in threads, spreadsheets, and memory.\n\t\t\t\t\t\t\t\t\tCove gives it a home: every request routed, every signal visible,\n\t\t\t\t\t\t\t\t\tevery outcome on the record.\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t<Reveal>\n\t\t\t\t\t\t\t\t<Grid minItemWidth=\"calc(var(--spacing) * 64)\" gap={2}>\n\t\t\t\t\t\t\t\t\t{values.map((value) => (\n\t\t\t\t\t\t\t\t\t\t<Card key={value.title} render={<article />} className=\"h-full\">\n\t\t\t\t\t\t\t\t\t\t\t<CardContent className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"mb-4 grid size-11 place-items-center rounded-md border border-border sable-well\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name={value.icon} className=\"text-xl\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t<h3 className=\"text-title-lg\">{value.title}</h3>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">{value.body}</p>\n\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t</Reveal>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t</Region>\n\t\t\t\t</section>\n\n\t\t\t\t<Region as=\"section\" id=\"how-it-works\" aria-labelledby=\"brand-steps-title\" max=\"wide\" className=\"py-20 lg:py-28\">\n\t\t\t\t\t<div className=\"grid gap-12 lg:grid-cols-[1fr_2fr] lg:gap-16\">\n\t\t\t\t\t\t<Stack gap={2} className=\"max-w-md\">\n\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">How it works</p>\n\t\t\t\t\t\t\t<h2 id=\"brand-steps-title\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\tFrom request to sealed record.\n\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t<p className=\"text-body-md text-muted-foreground\">\n\t\t\t\t\t\t\t\tTeams are usually live within a week. Cove adapts to the routes\n\t\t\t\t\t\t\t\tyou already run instead of asking you to start over.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<div className=\"pt-3\">\n\t\t\t\t\t\t\t\t<Button variant=\"outline\" iconEnd={<Icon name=\"arrow-right\" />} render={<a href=\"/guides/rollout\" />}>\n\t\t\t\t\t\t\t\t\tRead the rollout guide\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t<ol className=\"grid gap-10 sm:grid-cols-3 sm:gap-6\">\n\t\t\t\t\t\t\t{steps.map((step) => (\n\t\t\t\t\t\t\t\t<li key={step.number} className=\"flex flex-col gap-2 border-t border-border-strong pt-6\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-mono-sm text-muted-foreground\">{step.number}</span>\n\t\t\t\t\t\t\t\t\t<h3 className=\"mt-3 text-title-lg sm:mt-6\">{step.title}</h3>\n\t\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">{step.body}</p>\n\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</ol>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\n\t\t\t\t<Region as=\"section\" id=\"customers\" aria-label=\"Customer story\" max=\"wide\" className=\"pb-20 lg:pb-28\">\n\t\t\t\t\t<Card render={<figure />}>\n\t\t\t\t\t\t<div className=\"grid lg:grid-cols-[1.5fr_1fr]\">\n\t\t\t\t\t\t\t<CardContent className=\"flex flex-col justify-between gap-10 sm:p-10\">\n\t\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">Customer story</p>\n\t\t\t\t\t\t\t\t\t<blockquote className=\"text-balance text-heading-3 sm:text-heading-2\">\n\t\t\t\t\t\t\t\t\t\t“We used to chase approvals across four tools. Now every request\n\t\t\t\t\t\t\t\t\t\thas an owner, a deadline, and a record, and our auditors stopped\n\t\t\t\t\t\t\t\t\t\tasking for screenshots.”\n\t\t\t\t\t\t\t\t\t</blockquote>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t<figcaption className=\"flex items-center gap-3\">\n\t\t\t\t\t\t\t\t\t<Avatar variant=\"art\" size=\"lg\" fallback=\"MO\" />\n\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t<p className=\"text-title-sm\">Maya Okafor</p>\n\t\t\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">Head of operations, Fielddesk</p>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</figcaption>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t<div className=\"border-border max-lg:border-t lg:border-s\">\n\t\t\t\t\t\t\t\t<CardContent className=\"flex h-full flex-col justify-center gap-6 sm:p-10\">\n\t\t\t\t\t\t\t\t\t{outcomes.map((outcome, index) => (\n\t\t\t\t\t\t\t\t\t\t<Stack key={outcome.label} gap={3}>\n\t\t\t\t\t\t\t\t\t\t\t{index > 0 && <Separator />}\n\t\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-heading-1 sm:text-display-lg\">{outcome.value}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-1 text-body-sm text-muted-foreground\">{outcome.label}</p>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Card>\n\t\t\t\t</Region>\n\n\t\t\t\t<Region as=\"section\" aria-labelledby=\"brand-cta-title\" max=\"wide\" className=\"pb-20 lg:pb-28\">\n\t\t\t\t\t<div className=\"dark\">\n\t\t\t\t\t\t<div className=\"rounded-xl border border-border bg-card px-6 py-12 text-card-foreground sm:px-12 sm:py-16\">\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-8 lg:flex-row lg:items-end lg:justify-between\">\n\t\t\t\t\t\t\t\t<Stack gap={3} className=\"max-w-2xl\">\n\t\t\t\t\t\t\t\t\t<h2 id=\"brand-cta-title\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\t\tSee your own approval paths in Cove.\n\t\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t\t<p className=\"text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tA 30-minute walkthrough with your routes, your roles, and your\n\t\t\t\t\t\t\t\t\t\trecords. No slides.\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t<Row gap={2} wrap>\n\t\t\t\t\t\t\t\t\t<Button size=\"cta\" render={<a href=\"/demo\" />}>\n\t\t\t\t\t\t\t\t\t\tBook a walkthrough\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t<Button size=\"cta\" variant=\"ghost\" render={<a href=\"/pricing\" />}>\n\t\t\t\t\t\t\t\t\t\tView pricing\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\t\t\t</main>\n\n\t\t\t<footer className=\"border-t border-border bg-muted/35\">\n\t\t\t\t<Region max=\"wide\" className=\"py-12 sm:py-16\">\n\t\t\t\t\t<div className=\"grid gap-10 lg:grid-cols-[1fr_1.35fr_1fr] lg:gap-8\">\n\t\t\t\t\t\t<Stack gap={4} className=\"max-w-sm\">\n\t\t\t\t\t\t\t<a href=\"/\" className=\"flex w-fit items-center gap-2 rounded-full motion-interactive motion-press\">\n\t\t\t\t\t\t\t\t<span className=\"grid size-9 place-items-center rounded-full bg-primary text-primary-foreground\">\n\t\t\t\t\t\t\t\t\t<Icon name=\"sparkles\" className=\"text-sm\" />\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<span className=\"text-title-lg\">Cove</span>\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\tA calm operating surface for teams that need trust, speed, and\n\t\t\t\t\t\t\t\tcontext without the noise.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<Badge appearance=\"plain\" tone=\"success\" dot>\n\t\t\t\t\t\t\t\tAll systems normal\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t</Stack>\n\n\t\t\t\t\t\t<nav aria-label=\"Footer\" className=\"grid grid-cols-3 gap-6\">\n\t\t\t\t\t\t\t{footerGroups.map((group) => (\n\t\t\t\t\t\t\t\t<div key={group.title}>\n\t\t\t\t\t\t\t\t\t<h2 className=\"text-label-md\">{group.title}</h2>\n\t\t\t\t\t\t\t\t\t<ul className=\"mt-3 space-y-1\">\n\t\t\t\t\t\t\t\t\t\t{group.links.map((link) => (\n\t\t\t\t\t\t\t\t\t\t\t<li key={link.label}>\n\t\t\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\t\t\thref={link.href}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"flex min-h-9 items-center text-body-sm text-muted-foreground motion-interactive hover:text-foreground\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</nav>\n\n\t\t\t\t\t\t<Card className=\"h-fit\">\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t{/* CardTitle carries the type; the h2 inside it keeps the outline. */}\n\t\t\t\t\t\t\t\t<CardTitle>\n\t\t\t\t\t\t\t\t\t<h2>The Cove digest</h2>\n\t\t\t\t\t\t\t\t</CardTitle>\n\t\t\t\t\t\t\t\t<CardDescription>\n\t\t\t\t\t\t\t\t\tOne note a month on trusted workflows and product releases.\n\t\t\t\t\t\t\t\t</CardDescription>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t<form action=\"/newsletter\" className=\"grid gap-2 sm:grid-cols-[1fr_auto]\">\n\t\t\t\t\t\t\t\t\t<label htmlFor=\"brand-footer-email\" className=\"sr-only\">\n\t\t\t\t\t\t\t\t\t\tWork email\n\t\t\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\tid=\"brand-footer-email\"\n\t\t\t\t\t\t\t\t\t\tname=\"email\"\n\t\t\t\t\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\t\t\t\t\tautoComplete=\"email\"\n\t\t\t\t\t\t\t\t\t\tplaceholder=\"you@company.com\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Button type=\"submit\" className=\"w-full sm:w-auto\">\n\t\t\t\t\t\t\t\t\t\tSubscribe\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</form>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className=\"mt-12 flex flex-col gap-3 border-t border-border pt-6 text-caption text-muted-foreground sm:flex-row sm:items-center sm:justify-between\">\n\t\t\t\t\t\t<p>© 2026 Cove. All rights reserved.</p>\n\t\t\t\t\t\t<Row gap={4} wrap>\n\t\t\t\t\t\t\t{legalLinks.map((link) => (\n\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\tkey={link.label}\n\t\t\t\t\t\t\t\t\thref={link.href}\n\t\t\t\t\t\t\t\t\tclassName=\"flex min-h-9 min-w-9 items-center motion-interactive hover:text-foreground\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\t\t\t</footer>\n\t\t</div>\n\t)\n}"
20532
+ "code": "const navItems = [\n\t{ label: \"Platform\", href: \"#platform\" },\n\t{ label: \"How it works\", href: \"#how-it-works\" },\n\t{ label: \"Customers\", href: \"#customers\" },\n\t{ label: \"Pricing\", href: \"/pricing\" },\n]\n\nconst spaces = [\n\t{ name: \"Waypoint\", value: \"98.4%\", label: \"clear routes\" },\n\t{ name: \"Fielddesk\", value: \"12\", label: \"open holds\" },\n\t{ name: \"Archive\", value: \"4.8k\", label: \"sealed records\" },\n]\n\nconst routes = [\n\t{ label: \"Intake\", state: \"Open\", tone: \"info\" as const },\n\t{ label: \"Review\", state: \"Live\", tone: \"1\" as const },\n\t{ label: \"Archive\", state: \"Safe\", tone: \"success\" as const },\n]\n\n// Weekly approvals: past weeks recede, the current week carries the signal.\nconst bars = [\n\t{ week: \"w1\", height: \"38%\", current: false },\n\t{ week: \"w2\", height: \"56%\", current: false },\n\t{ week: \"w3\", height: \"44%\", current: false },\n\t{ week: \"w4\", height: \"68%\", current: false },\n\t{ week: \"w5\", height: \"52%\", current: false },\n\t{ week: \"w6\", height: \"78%\", current: false },\n\t{ week: \"w7\", height: \"60%\", current: false },\n\t{ week: \"w8\", height: \"72%\", current: false },\n\t{ week: \"w9\", height: \"88%\", current: true },\n]\n\nconst sceneMetrics = [\n\t{ label: \"Cleared\", value: \"1.8M\", detail: \"last 30 days\" },\n\t{ label: \"Review\", value: \"42s\", detail: \"median path\" },\n\t{ label: \"Spaces\", value: \"284\", detail: \"active teams\" },\n]\n\nconst values = [\n\t{\n\t\ticon: \"lock\" as const,\n\t\ttitle: \"Private by default\",\n\t\tbody: \"Roles, approvals, and handoff history stay with the people who need them. No shared inboxes, no forwarded screenshots.\",\n\t},\n\t{\n\t\ticon: \"bell\" as const,\n\t\ttitle: \"Signals before surprises\",\n\t\tbody: \"Cove watches every route and surfaces stalled reviews and expiring approvals while there is still time to act.\",\n\t},\n\t{\n\t\ticon: \"clock\" as const,\n\t\ttitle: \"A trail for every decision\",\n\t\tbody: \"Each approval keeps its context, reviewer, and timestamp, so an audit takes an afternoon instead of a quarter.\",\n\t},\n]\n\nconst steps = [\n\t{\n\t\tnumber: \"01\",\n\t\ttitle: \"Connect your spaces\",\n\t\tbody: \"Bring teams, vendors, and record stores into one workspace, with roles that mirror how you already work.\",\n\t},\n\t{\n\t\tnumber: \"02\",\n\t\ttitle: \"Route every approval\",\n\t\tbody: \"Requests follow the path you define, with reviewers, deadlines, and escalation built in from the start.\",\n\t},\n\t{\n\t\tnumber: \"03\",\n\t\ttitle: \"Seal the record\",\n\t\tbody: \"Approved work is archived with its full context and is ready for review the moment it closes.\",\n\t},\n]\n\nconst outcomes = [\n\t{ value: \"3.2×\", label: \"faster approvals in the first quarter\" },\n\t{ value: \"68%\", label: \"fewer follow-ups on handoffs\" },\n\t{ value: \"1 day\", label: \"to prepare a quarterly audit\" },\n]\n\nconst footerGroups = [\n\t{\n\t\ttitle: \"Platform\",\n\t\tlinks: [\n\t\t\t{ label: \"Overview\", href: \"#platform\" },\n\t\t\t{ label: \"Signals\", href: \"/signals\" },\n\t\t\t{ label: \"Approvals\", href: \"/approvals\" },\n\t\t\t{ label: \"Records\", href: \"/records\" },\n\t\t],\n\t},\n\t{\n\t\ttitle: \"Company\",\n\t\tlinks: [\n\t\t\t{ label: \"About\", href: \"/about\" },\n\t\t\t{ label: \"Security\", href: \"/security\" },\n\t\t\t{ label: \"Customers\", href: \"#customers\" },\n\t\t\t{ label: \"Careers\", href: \"/careers\" },\n\t\t],\n\t},\n\t{\n\t\ttitle: \"Resources\",\n\t\tlinks: [\n\t\t\t{ label: \"Guides\", href: \"/guides\" },\n\t\t\t{ label: \"Status\", href: \"/status\" },\n\t\t\t{ label: \"Changelog\", href: \"/changelog\" },\n\t\t\t{ label: \"Docs\", href: \"/docs\" },\n\t\t],\n\t},\n]\n\nconst legalLinks = [\n\t{ label: \"Privacy\", href: \"/privacy\" },\n\t{ label: \"Terms\", href: \"/terms\" },\n\t{ label: \"Contact\", href: \"/contact\" },\n]\n\nexport function BrandLandingPage() {\n\treturn (\n\t\t<div className=\"min-h-dvh overflow-hidden bg-background text-foreground\">\n\t\t\t<header className=\"relative z-10 pt-4\">\n\t\t\t\t<Region max=\"wide\">\n\t\t\t\t\t<nav\n\t\t\t\t\t\taria-label=\"Main\"\n\t\t\t\t\t\tclassName=\"flex min-h-15 items-center justify-between gap-3 rounded-full border border-border bg-card/90 px-3 py-2 shadow-sm backdrop-blur\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<a href=\"/\" className=\"flex items-center gap-2 rounded-full px-2 motion-interactive motion-press\">\n\t\t\t\t\t\t\t<span className=\"grid size-8 place-items-center rounded-full bg-primary text-primary-foreground\">\n\t\t\t\t\t\t\t\t<Icon name=\"sparkles\" className=\"text-sm\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span className=\"text-title-lg\">Cove</span>\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t<div className=\"hidden lg:block\">\n\t\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t\t{navItems.map((item) => (\n\t\t\t\t\t\t\t\t\t<Button key={item.label} variant=\"ghost\" size=\"sm\" render={<a href={item.href} />}>\n\t\t\t\t\t\t\t\t\t\t{item.label}\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" className=\"max-sm:hidden\" render={<a href=\"/login\" />}>\n\t\t\t\t\t\t\t\tSign in\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button size=\"sm\" render={<a href=\"/demo\" />}>\n\t\t\t\t\t\t\t\tRequest access\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t</nav>\n\t\t\t\t</Region>\n\t\t\t</header>\n\n\t\t\t<main>\n\t\t\t\t<Region as=\"section\" max=\"wide\" className=\"pb-20 pt-14 sm:pt-20 lg:pb-28\">\n\t\t\t\t\t<div className=\"grid items-center gap-12 lg:grid-cols-[1fr_1.1fr] lg:gap-10\">\n\t\t\t\t\t\t<Stack gap={3} className=\"max-w-2xl\">\n\t\t\t\t\t\t\t<Badge appearance=\"outline\" icon={<Icon name=\"lock\" className=\"text-sm\" />}>\n\t\t\t\t\t\t\t\tPrivate operations, made legible\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t<TextReveal\n\t\t\t\t\t\t\t\tas=\"h1\"\n\t\t\t\t\t\t\t\ttext=\"A calmer command center for high-trust work\"\n\t\t\t\t\t\t\t\tclassName=\"text-balance text-display-lg sm:text-display-2xl\"\n\t\t\t\t\t\t\t\tonce\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<p className=\"max-w-(--measure-prose) text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\tCove gives teams a polished front door and a serious operating\n\t\t\t\t\t\t\t\tsurface: approvals, signals, records, and handoffs in one\n\t\t\t\t\t\t\t\tunderstandable flow.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<Row gap={2} wrap className=\"pt-3\">\n\t\t\t\t\t\t\t\t<Button size=\"cta\" render={<a href=\"/demo\" />}>\n\t\t\t\t\t\t\t\t\tBook a walkthrough\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t<Button size=\"cta\" variant=\"outline\" render={<a href=\"#platform\" />}>\n\t\t\t\t\t\t\t\t\tExplore the platform\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t</Stack>\n\n\t\t\t\t\t\t{/* The product scene is a dark island: `dark` re-scopes every\n\t\t\t\t\t\t semantic token, so its surfaces, muted ink and status tones\n\t\t\t\t\t\t are the theme's own dark values in either page mode. It is a\n\t\t\t\t\t\t picture of the product, so it stays out of the tab order and\n\t\t\t\t\t\t the accessibility tree; the hero copy carries its meaning. */}\n\t\t\t\t\t\t<div className=\"dark\" aria-hidden inert>\n\t\t\t\t\t\t\t<div className=\"overflow-hidden rounded-xl border border-border-floating bg-popover p-3 text-popover-foreground shadow-xl\">\n\t\t\t\t\t\t\t\t{/* Nested surfaces take concentric corners: each level's radius is\n\t\t\t\t\t\t\t\t the shell's xl minus every inset between it and the shell edge. */}\n\t\t\t\t\t\t\t\t<div className=\"rounded-concentric-xl/3 border border-border bg-card\">\n\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between border-b border-border px-4 py-3\">\n\t\t\t\t\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t\t\t\t\t<StatusDot tone=\"warning\" />\n\t\t\t\t\t\t\t\t\t\t\t<StatusDot tone=\"1\" />\n\t\t\t\t\t\t\t\t\t\t\t<StatusDot tone=\"success\" />\n\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-label-sm text-muted-foreground\">Live workspace</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<div className=\"grid gap-3 p-3 md:grid-cols-[0.7fr_1.3fr]\">\n\t\t\t\t\t\t\t\t\t\t<div className=\"space-y-3 max-md:hidden\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"rounded-concentric-xl/6 border border-border bg-background p-4\">\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-label-sm text-muted-foreground\">Spaces</p>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"mt-4 space-y-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{spaces.map((space) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div key={space.name} className=\"rounded-concentric-xl/10 bg-card p-3\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Row justify=\"between\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-title-sm\">{space.name}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-mono-sm\">{space.value}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-1 text-body-xs text-muted-foreground\">{space.label}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"rounded-concentric-xl/6 border border-border bg-background p-4\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Row justify=\"between\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-title-sm\">Route status</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Badge tone=\"success\" size=\"sm\" dot>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tHealthy\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"mt-4 space-y-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{routes.map((route) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Row key={route.label} justify=\"between\" className=\"rounded-full bg-card px-3 py-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<StatusDot tone={route.tone} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-body-sm\">{route.label}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-label-sm text-muted-foreground\">{route.state}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t\t\t<div className=\"rounded-concentric-xl/6 border border-border bg-background p-4\">\n\t\t\t\t\t\t\t\t\t\t\t<Row justify=\"between\" className=\"border-b border-border pb-4\">\n\t\t\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-label-sm text-muted-foreground\">Signal map</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-1 text-heading-4 sm:text-heading-3\">Every approval path in view</p>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"secondary\" size=\"sm\">Review</Button>\n\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"mt-5 grid gap-4 sm:mt-6 sm:gap-5\">\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"grid grid-cols-9 items-end gap-1.5 rounded-concentric-xl/10 bg-card p-4 sm:gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{bars.map((bar) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div key={bar.week} className=\"flex h-36 items-end sm:h-56\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={`w-full rounded-full ${bar.current ? \"bg-success\" : \"bg-secondary\"}`}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstyle={{ height: bar.height }}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"grid grid-cols-3 gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{sceneMetrics.map((metric) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div key={metric.label} className=\"rounded-concentric-xl/10 border border-border bg-card p-3\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"truncate text-label-sm text-muted-foreground\">{metric.label}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-2 text-heading-4 sm:text-heading-3\">{metric.value}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-1 text-body-xs text-muted-foreground max-sm:hidden\">{metric.detail}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\n\t\t\t\t<section id=\"platform\" aria-labelledby=\"brand-platform-title\" className=\"border-y border-border bg-muted/40 py-20 lg:py-28\">\n\t\t\t\t\t<Region max=\"wide\">\n\t\t\t\t\t\t<Stack gap={6}>\n\t\t\t\t\t\t\t<Stack gap={2} className=\"max-w-2xl\">\n\t\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">Why Cove</p>\n\t\t\t\t\t\t\t\t<h2 id=\"brand-platform-title\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\tOne private workspace for the decisions that matter.\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t<p className=\"max-w-(--measure-prose) text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tMost operations work lives in threads, spreadsheets, and memory.\n\t\t\t\t\t\t\t\t\tCove gives it a home: every request routed, every signal visible,\n\t\t\t\t\t\t\t\t\tevery outcome on the record.\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t<Reveal>\n\t\t\t\t\t\t\t\t<Grid minItemWidth=\"calc(var(--spacing) * 64)\" gap={2}>\n\t\t\t\t\t\t\t\t\t{values.map((value) => (\n\t\t\t\t\t\t\t\t\t\t<Card key={value.title} render={<article />} className=\"h-full\">\n\t\t\t\t\t\t\t\t\t\t\t<CardContent className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"mb-4 grid size-11 place-items-center rounded-md border border-border sable-well\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name={value.icon} className=\"text-xl\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t<h3 className=\"text-title-lg\">{value.title}</h3>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">{value.body}</p>\n\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t</Reveal>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t</Region>\n\t\t\t\t</section>\n\n\t\t\t\t<Region as=\"section\" id=\"how-it-works\" aria-labelledby=\"brand-steps-title\" max=\"wide\" className=\"py-20 lg:py-28\">\n\t\t\t\t\t<div className=\"grid gap-12 lg:grid-cols-[1fr_2fr] lg:gap-16\">\n\t\t\t\t\t\t<Stack gap={2} className=\"max-w-md\">\n\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">How it works</p>\n\t\t\t\t\t\t\t<h2 id=\"brand-steps-title\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\tFrom request to sealed record.\n\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t<p className=\"text-body-md text-muted-foreground\">\n\t\t\t\t\t\t\t\tTeams are usually live within a week. Cove adapts to the routes\n\t\t\t\t\t\t\t\tyou already run instead of asking you to start over.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<div className=\"pt-3\">\n\t\t\t\t\t\t\t\t<Button variant=\"outline\" iconEnd={<Icon name=\"arrow-right\" />} render={<a href=\"/guides/rollout\" />}>\n\t\t\t\t\t\t\t\t\tRead the rollout guide\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t<ol className=\"grid gap-10 sm:grid-cols-3 sm:gap-6\">\n\t\t\t\t\t\t\t{steps.map((step) => (\n\t\t\t\t\t\t\t\t<li key={step.number} className=\"flex flex-col gap-2 border-t border-border-strong pt-6\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-mono-sm text-muted-foreground\">{step.number}</span>\n\t\t\t\t\t\t\t\t\t<h3 className=\"mt-3 text-title-lg sm:mt-6\">{step.title}</h3>\n\t\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">{step.body}</p>\n\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</ol>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\n\t\t\t\t<Region as=\"section\" id=\"customers\" aria-label=\"Customer story\" max=\"wide\" className=\"pb-20 lg:pb-28\">\n\t\t\t\t\t<Card render={<figure />}>\n\t\t\t\t\t\t<div className=\"grid lg:grid-cols-[1.5fr_1fr]\">\n\t\t\t\t\t\t\t<CardContent className=\"flex flex-col justify-between gap-10 sm:p-10\">\n\t\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">Customer story</p>\n\t\t\t\t\t\t\t\t\t<blockquote className=\"text-balance text-heading-3 sm:text-heading-2\">\n\t\t\t\t\t\t\t\t\t\t“We used to chase approvals across four tools. Now every request\n\t\t\t\t\t\t\t\t\t\thas an owner, a deadline, and a record, and our auditors stopped\n\t\t\t\t\t\t\t\t\t\tasking for screenshots.”\n\t\t\t\t\t\t\t\t\t</blockquote>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t<figcaption className=\"flex items-center gap-3\">\n\t\t\t\t\t\t\t\t\t<Avatar variant=\"art\" size=\"lg\" fallback=\"MO\" />\n\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t<p className=\"text-title-sm\">Maya Okafor</p>\n\t\t\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">Head of operations, Fielddesk</p>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</figcaption>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t<div className=\"border-border max-lg:border-t lg:border-s\">\n\t\t\t\t\t\t\t\t<CardContent className=\"flex h-full flex-col justify-center gap-6 sm:p-10\">\n\t\t\t\t\t\t\t\t\t{outcomes.map((outcome, index) => (\n\t\t\t\t\t\t\t\t\t\t<Stack key={outcome.label} gap={3}>\n\t\t\t\t\t\t\t\t\t\t\t{index > 0 && <Separator />}\n\t\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-heading-1 sm:text-display-lg\">{outcome.value}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-1 text-body-sm text-muted-foreground\">{outcome.label}</p>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Card>\n\t\t\t\t</Region>\n\n\t\t\t\t<Region as=\"section\" aria-labelledby=\"brand-cta-title\" max=\"wide\" className=\"pb-20 lg:pb-28\">\n\t\t\t\t\t<div className=\"dark\">\n\t\t\t\t\t\t<div className=\"rounded-xl border border-border bg-card px-6 py-12 text-card-foreground sm:px-12 sm:py-16\">\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-8 lg:flex-row lg:items-end lg:justify-between\">\n\t\t\t\t\t\t\t\t<Stack gap={3} className=\"max-w-2xl\">\n\t\t\t\t\t\t\t\t\t<h2 id=\"brand-cta-title\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\t\tSee your own approval paths in Cove.\n\t\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t\t<p className=\"text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tA 30-minute walkthrough with your routes, your roles, and your\n\t\t\t\t\t\t\t\t\t\trecords. No slides.\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t<Row gap={2} wrap>\n\t\t\t\t\t\t\t\t\t<Button size=\"cta\" render={<a href=\"/demo\" />}>\n\t\t\t\t\t\t\t\t\t\tBook a walkthrough\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t<Button size=\"cta\" variant=\"ghost\" render={<a href=\"/pricing\" />}>\n\t\t\t\t\t\t\t\t\t\tView pricing\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\t\t\t</main>\n\n\t\t\t<footer className=\"border-t border-border bg-muted/35\">\n\t\t\t\t<Region max=\"wide\" className=\"py-12 sm:py-16\">\n\t\t\t\t\t<div className=\"grid gap-10 lg:grid-cols-[1fr_1.35fr_1fr] lg:gap-8\">\n\t\t\t\t\t\t<Stack gap={4} className=\"max-w-sm\">\n\t\t\t\t\t\t\t<a href=\"/\" className=\"flex w-fit items-center gap-2 rounded-full motion-interactive motion-press\">\n\t\t\t\t\t\t\t\t<span className=\"grid size-9 place-items-center rounded-full bg-primary text-primary-foreground\">\n\t\t\t\t\t\t\t\t\t<Icon name=\"sparkles\" className=\"text-sm\" />\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<span className=\"text-title-lg\">Cove</span>\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\tA calm operating surface for teams that need trust, speed, and\n\t\t\t\t\t\t\t\tcontext without the noise.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<Badge appearance=\"plain\" tone=\"success\" dot>\n\t\t\t\t\t\t\t\tAll systems normal\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t</Stack>\n\n\t\t\t\t\t\t<nav aria-label=\"Footer\" className=\"grid grid-cols-3 gap-6\">\n\t\t\t\t\t\t\t{footerGroups.map((group) => (\n\t\t\t\t\t\t\t\t<div key={group.title}>\n\t\t\t\t\t\t\t\t\t<h2 className=\"text-label-md\">{group.title}</h2>\n\t\t\t\t\t\t\t\t\t<ul className=\"mt-3 space-y-1\">\n\t\t\t\t\t\t\t\t\t\t{group.links.map((link) => (\n\t\t\t\t\t\t\t\t\t\t\t<li key={link.label}>\n\t\t\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\t\t\thref={link.href}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"flex min-h-9 items-center text-body-sm text-muted-foreground motion-interactive hover:text-foreground\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</nav>\n\n\t\t\t\t\t\t<Card className=\"h-fit\">\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t{/* CardTitle carries the type; the h2 inside it keeps the outline. */}\n\t\t\t\t\t\t\t\t<CardTitle>\n\t\t\t\t\t\t\t\t\t<h2>The Cove digest</h2>\n\t\t\t\t\t\t\t\t</CardTitle>\n\t\t\t\t\t\t\t\t<CardDescription>\n\t\t\t\t\t\t\t\t\tOne note a month on trusted workflows and product releases.\n\t\t\t\t\t\t\t\t</CardDescription>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t<form action=\"/newsletter\" className=\"grid gap-2 sm:grid-cols-[1fr_auto]\">\n\t\t\t\t\t\t\t\t\t<label htmlFor=\"brand-footer-email\" className=\"sr-only\">\n\t\t\t\t\t\t\t\t\t\tWork email\n\t\t\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\t\tid=\"brand-footer-email\"\n\t\t\t\t\t\t\t\t\t\tname=\"email\"\n\t\t\t\t\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\t\t\t\t\tautoComplete=\"email\"\n\t\t\t\t\t\t\t\t\t\tplaceholder=\"you@company.com\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Button type=\"submit\" className=\"w-full sm:w-auto\">\n\t\t\t\t\t\t\t\t\t\tSubscribe\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</form>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className=\"mt-12 flex flex-col gap-3 border-t border-border pt-6 text-caption text-muted-foreground sm:flex-row sm:items-center sm:justify-between\">\n\t\t\t\t\t\t<p>© 2026 Cove. All rights reserved.</p>\n\t\t\t\t\t\t<Row gap={4} wrap>\n\t\t\t\t\t\t\t{legalLinks.map((link) => (\n\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\tkey={link.label}\n\t\t\t\t\t\t\t\t\thref={link.href}\n\t\t\t\t\t\t\t\t\tclassName=\"flex min-h-9 min-w-9 items-center motion-interactive hover:text-foreground\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\t\t\t</footer>\n\t\t</div>\n\t)\n}"
20400
20533
  },
20401
20534
  {
20402
20535
  "name": "ChatPanel",
@@ -20426,20 +20559,20 @@
20426
20559
  "The composer is a real <form> whose Enter handling skips IME composition, and every icon-only control in the panel carries an accessible label — the answer actions also carry a tooltip, and the stop control is named for what it stops (`stopLabel=\"Stop answering\"`)."
20427
20560
  ],
20428
20561
  "imports": "import { Attachment, Avatar, Bubble, Button, Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle, ChatComposer, CodeBlock, Icon, Marker, MenuRadioGroup, MenuRadioItem, Message, MessageScroller, Page, SettingsMenu, SettingsRow, SimpleTooltip, StreamingText, Toolbar, ToolbarButton } from \"@sableui/react\"",
20429
- "code": "/**\n * The request is in flight. In an app this is your send state — from `useChat`,\n * a fetch, whatever owns the turn — and it is deliberately ONE flag driving two\n * places: the composer's `busy` and the answer's `streaming`. They are the same\n * wait reported at both ends of the panel, and letting them drift is how you get\n * a composer that still says \"sending\" under an answer that already finished.\n */\nconst pending = true\n\n/** The answer so far. Append tokens to this; `StreamingText` assumes it only grows. */\nconst streamed =\n\t\"Yes, appends are already safe: your layout effect measures before paint. The jump is on the prepend path, which reads scrollHeight after the new rows\"\n\n/**\n * The opening exchange. `step` is what the assistant did before answering — a\n * doc it read, a tool it ran — and it rides in that message's header, so it\n * reads as part of the turn rather than as an event between turns.\n */\nconst turns: { id: string; mine: boolean; step?: string; body: string }[] = [\n\t{\n\t\tid: \"t_01\",\n\t\tmine: true,\n\t\tbody: \"Our chat scroll jumps every time older messages load. What are we doing wrong?\",\n\t},\n\t{\n\t\tid: \"t_02\",\n\t\tmine: false,\n\t\tstep: \"Read message-scroller.md\",\n\t\tbody: \"Almost always the anchor. If you preserve the scroll position by measuring the container, prepending history moves everything under the reader.\",\n\t},\n\t{\n\t\tid: \"t_03\",\n\t\tmine: false,\n\t\tbody: \"Anchor on the oldest rendered message instead and add back however far it moved. Appends leave it alone, so the same rule covers both.\",\n\t},\n]\n\nexport function ChatPanel() {\n\t// One identity for every assistant turn. A glyph rather than initials: \"AI\"\n\t// in a circle reads as a person called A. I. The glyph is sized to the\n\t// circle (the fallback's own size is tuned for two letters, and a lone icon\n\t// at that size reads as a speck); `alt` names it for a screen reader, which\n\t// otherwise gets nothing from a fallback-only avatar.\n\tconst assistant = (\n\t\t<Avatar variant=\"art\"\n\t\t\tsize=\"sm\"\n\t\t\talt=\"Assistant\"\n\t\t\tfallback={<Icon name=\"sparkles\" className=\"text-base\" />}\n\t\t/>\n\t)\n\n\treturn (\n\t\t<Page as=\"main\" max=\"feed\" gap={0} className=\"py-6\">\n\t\t\t{/* h-dvh minus the page's own padding (py-6 = 12 spacing units, in\n\t\t\t spacing units so density moves both together): the panel owns the\n\t\t\t viewport, and the transcript — not the page — is the thing that\n\t\t\t scrolls. The panel is a Card; its parts inset by the card's own\n\t\t\t --card-p, so header, transcript and composer share one edge. */}\n\t\t\t<Card\n\t\t\t\trender={<section aria-labelledby=\"chat-title\" />}\n\t\t\t\tclassName=\"flex h-[calc(100dvh_-_--spacing(12))] flex-col overflow-hidden\"\n\t\t\t>\n\t\t\t\t<CardHeader className=\"border-b border-border pb-(--card-p)\">\n\t\t\t\t\t{/* CardTitle carries the type; the h1 inside it is the page heading. */}\n\t\t\t\t\t<CardTitle>\n\t\t\t\t\t\t<h1 id=\"chat-title\">Scroll behaviour</h1>\n\t\t\t\t\t</CardTitle>\n\t\t\t\t\t<CardDescription>Assistant · answers from your team's docs</CardDescription>\n\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t{/* Labelled, not a bare plus: the composer already has a plus\n\t\t\t\t\t\t (attach), and two identical glyphs a panel apart that do\n\t\t\t\t\t\t different things is a guessing game. */}\n\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\">\n\t\t\t\t\t\t\t<Icon name=\"edit\" />\n\t\t\t\t\t\t\tNew chat\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</CardAction>\n\t\t\t\t</CardHeader>\n\n\t\t\t\t{/* flex-1 min-h-0 is the whole layout: it bounds the scroll region\n\t\t\t\t against the panel instead of letting the transcript grow the page. */}\n\t\t\t\t<MessageScroller className=\"min-h-0 flex-1 p-(--card-p)\">\n\t\t\t\t\t<Marker variant=\"separator\">Today</Marker>\n\n\t\t\t\t\t{turns.map((turn, i) => {\n\t\t\t\t\t\t// Derived, not hand-set: a turn continues a run when the one\n\t\t\t\t\t\t// before it has the same author. A continuation drops its\n\t\t\t\t\t\t// avatar (Message keeps the column) AND its tail (Bubble keeps\n\t\t\t\t\t\t// the ear's space) — together, that makes the run one block.\n\t\t\t\t\t\tconst continues = i > 0 && turns[i - 1].mine === turn.mine\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<Message\n\t\t\t\t\t\t\t\tkey={turn.id}\n\t\t\t\t\t\t\t\tmessageId={turn.id}\n\t\t\t\t\t\t\t\talign={turn.mine ? \"end\" : \"start\"}\n\t\t\t\t\t\t\t\tgrouped={continues}\n\t\t\t\t\t\t\t\tavatar={turn.mine ? undefined : assistant}\n\t\t\t\t\t\t\t\theader={\n\t\t\t\t\t\t\t\t\tturn.step ? (\n\t\t\t\t\t\t\t\t\t\t<Marker variant=\"note\" icon=\"book\" className=\"justify-start\">\n\t\t\t\t\t\t\t\t\t\t\t{turn.step}\n\t\t\t\t\t\t\t\t\t\t</Marker>\n\t\t\t\t\t\t\t\t\t) : undefined\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Bubble\n\t\t\t\t\t\t\t\t\tvariant={turn.mine ? \"primary\" : \"muted\"}\n\t\t\t\t\t\t\t\t\talign={turn.mine ? \"end\" : \"start\"}\n\t\t\t\t\t\t\t\t\ttail={!continues}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{turn.body}\n\t\t\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t\t\t</Message>\n\t\t\t\t\t\t)\n\t\t\t\t\t})}\n\n\t\t\t\t\t{/* The same run, continued: long-form output with no bubble,\n\t\t\t\t\t because a tinted box around a code block reads as a quote\n\t\t\t\t\t rather than as a turn. Its footer carries what the reader does\n\t\t\t\t\t WITH a settled answer, as a Toolbar: one tab stop, arrow keys\n\t\t\t\t\t between the actions. */}\n\t\t\t\t\t<Message\n\t\t\t\t\t\tmessageId=\"t_04\"\n\t\t\t\t\t\tgrouped\n\t\t\t\t\t\tavatar={assistant}\n\t\t\t\t\t\tfooter={\n\t\t\t\t\t\t\t<Toolbar aria-label=\"Answer actions\">\n\t\t\t\t\t\t\t\t<SimpleTooltip content=\"Copy answer\">\n\t\t\t\t\t\t\t\t\t<ToolbarButton size=\"icon-sm\" aria-label=\"Copy answer\">\n\t\t\t\t\t\t\t\t\t\t<Icon name=\"copy\" />\n\t\t\t\t\t\t\t\t\t</ToolbarButton>\n\t\t\t\t\t\t\t\t</SimpleTooltip>\n\t\t\t\t\t\t\t\t<SimpleTooltip content=\"Regenerate\">\n\t\t\t\t\t\t\t\t\t<ToolbarButton size=\"icon-sm\" aria-label=\"Regenerate answer\">\n\t\t\t\t\t\t\t\t\t\t<Icon name=\"refresh\" />\n\t\t\t\t\t\t\t\t\t</ToolbarButton>\n\t\t\t\t\t\t\t\t</SimpleTooltip>\n\t\t\t\t\t\t\t</Toolbar>\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t{/* max-w-full: a plain Bubble sizes to its content with no\n\t\t\t\t\t\t measure cap, and a code line is wider than a phone. Bounded\n\t\t\t\t\t\t by the column, the CodeBlock scrolls inside itself instead of\n\t\t\t\t\t\t pushing the whole transcript sideways. */}\n\t\t\t\t\t\t<Bubble variant=\"plain\" className=\"max-w-full\">\n\t\t\t\t\t\t\t<p className=\"text-body-md text-foreground\">\n\t\t\t\t\t\t\t\tHere is the shape of it:\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<CodeBlock\n\t\t\t\t\t\t\t\tcode={\n\t\t\t\t\t\t\t\t\t\"const shift = anchor.offsetTop - lastTop\\nif (shift !== 0) viewport.scrollTop += shift\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfilename=\"preserve.ts\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t</Message>\n\n\t\t\t\t\t{/* The reader's follow-up is one turn: the question and the file\n\t\t\t\t\t it refers to stack in a single Message, so they share a side, an\n\t\t\t\t\t edge and a timestamp rather than reading as two sends. */}\n\t\t\t\t\t<Message messageId=\"t_05\" align=\"end\">\n\t\t\t\t\t\t<Bubble variant=\"primary\" align=\"end\">\n\t\t\t\t\t\t\tHere's ours. Does the same fix cover new messages arriving?\n\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t\t<Attachment\n\t\t\t\t\t\t\tname=\"scroller.tsx\"\n\t\t\t\t\t\t\tmeta=\"4.2 KB · TSX\"\n\t\t\t\t\t\t\tonOpen={() => undefined}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</Message>\n\n\t\t\t\t\t{/* The answer arriving. A `Marker` with a spinner — or ThinkingOrb —\n\t\t\t\t\t is what sits here BEFORE the first token; once tokens land it is\n\t\t\t\t\t replaced rather than kept, because two indicators for one wait\n\t\t\t\t\t read as two things happening. What stays is the step it took,\n\t\t\t\t\t as a finished note in the header.\n\n\t\t\t\t\t No bubble, same as the long-form answer above: a tinted box\n\t\t\t\t\t around a growing paragraph makes the reflow look like the box\n\t\t\t\t\t breathing. And no answer actions yet — there is nothing to copy\n\t\t\t\t\t or regenerate until it settles; Stop lives in the composer.\n\n\t\t\t\t\t MessageScroller needs no help with this — it already re-follows on\n\t\t\t\t\t \"a message grew\" (ResizeObserver), not just on \"a message arrived\",\n\t\t\t\t\t so the newest token stays pinned while the reader can still scroll\n\t\t\t\t\t up without being yanked back. */}\n\t\t\t\t\t<Message\n\t\t\t\t\t\tmessageId=\"t_06\"\n\t\t\t\t\t\tavatar={assistant}\n\t\t\t\t\t\theader={\n\t\t\t\t\t\t\t<Marker variant=\"note\" icon=\"file\" className=\"justify-start\">\n\t\t\t\t\t\t\t\tRead scroller.tsx\n\t\t\t\t\t\t\t</Marker>\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Bubble variant=\"plain\">\n\t\t\t\t\t\t\t<StreamingText text={streamed} streaming={pending} />\n\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t</Message>\n\t\t\t\t</MessageScroller>\n\n\t\t\t\t<CardFooter className=\"border-t border-border pt-(--card-p)\">\n\t\t\t\t\t<ChatComposer\n\t\t\t\t\t\tclassName=\"flex-1\"\n\t\t\t\t\t\tplaceholder=\"Ask a follow-up…\"\n\t\t\t\t\t\t// Same flag as the answer's `streaming`, which is what turns Send\n\t\t\t\t\t\t// into Stop. A composer that stays sendable mid-answer invites a\n\t\t\t\t\t\t// second request on top of the one still running.\n\t\t\t\t\t\tbusy={pending}\n\t\t\t\t\t\tonStop={() => undefined}\n\t\t\t\t\t\tstopLabel=\"Stop answering\"\n\t\t\t\t\t\tonAttach={() => undefined}\n\t\t\t\t\t\tfooter=\"Enter to send · Shift+Enter for a new line\"\n\t\t\t\t\t\t// Model and effort sit in `actions`, at the send end of the row:\n\t\t\t\t\t\t// they qualify the message about to be sent, so they belong beside\n\t\t\t\t\t\t// the button that sends it rather than next to the attach control.\n\t\t\t\t\t\tactions={\n\t\t\t\t\t\t\t<SettingsMenu\n\t\t\t\t\t\t\t\tvariant=\"subtle\"\n\t\t\t\t\t\t\t\tsize=\"xs\"\n\t\t\t\t\t\t\t\tlabel=\"Change model and effort\"\n\t\t\t\t\t\t\t\tsummary={\n\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-foreground\">5.6 Sol</span>{\" \"}\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-muted-foreground\">High</span>\n\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<SettingsRow label=\"Model\" value=\"5.6 Sol\">\n\t\t\t\t\t\t\t\t\t<MenuRadioGroup defaultValue=\"sol\">\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"sol\">5.6 Sol</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"lite\">5.6 Lite</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t</MenuRadioGroup>\n\t\t\t\t\t\t\t\t</SettingsRow>\n\t\t\t\t\t\t\t\t<SettingsRow label=\"Effort\" value=\"High\">\n\t\t\t\t\t\t\t\t\t<MenuRadioGroup defaultValue=\"high\">\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"low\">Low</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"standard\">Standard</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"high\">High</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t</MenuRadioGroup>\n\t\t\t\t\t\t\t\t</SettingsRow>\n\t\t\t\t\t\t\t\t<SettingsRow label=\"Speed\" value=\"Standard\">\n\t\t\t\t\t\t\t\t\t<MenuRadioGroup defaultValue=\"standard\">\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"standard\">Standard</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"priority\">Priority</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t</MenuRadioGroup>\n\t\t\t\t\t\t\t\t</SettingsRow>\n\t\t\t\t\t\t\t</SettingsMenu>\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t</CardFooter>\n\t\t\t</Card>\n\t\t</Page>\n\t)\n}"
20562
+ "code": "/**\n * The request is in flight. In an app this is your send state — from `useChat`,\n * a fetch, whatever owns the turn — and it is deliberately ONE flag driving two\n * places: the composer's `busy` and the answer's `streaming`. They are the same\n * wait reported at both ends of the panel, and letting them drift is how you get\n * a composer that still says \"sending\" under an answer that already finished.\n */\nconst pending = true\n\n/** The answer so far. Append tokens to this; `StreamingText` assumes it only grows. */\nconst streamed =\n\t\"Yes, appends are already safe: your layout effect measures before paint. The jump is on the prepend path, which reads scrollHeight after the new rows\"\n\n/**\n * The opening exchange. `step` is what the assistant did before answering — a\n * doc it read, a tool it ran — and it rides in that message's header, so it\n * reads as part of the turn rather than as an event between turns.\n */\nconst turns: { id: string; mine: boolean; step?: string; body: string }[] = [\n\t{\n\t\tid: \"t_01\",\n\t\tmine: true,\n\t\tbody: \"Our chat scroll jumps every time older messages load. What are we doing wrong?\",\n\t},\n\t{\n\t\tid: \"t_02\",\n\t\tmine: false,\n\t\tstep: \"Read message-scroller.md\",\n\t\tbody: \"Almost always the anchor. If you preserve the scroll position by measuring the container, prepending history moves everything under the reader.\",\n\t},\n\t{\n\t\tid: \"t_03\",\n\t\tmine: false,\n\t\tbody: \"Anchor on the oldest rendered message instead and add back however far it moved. Appends leave it alone, so the same rule covers both.\",\n\t},\n]\n\nexport function ChatPanel() {\n\t// One identity for every assistant turn. A glyph rather than initials: \"AI\"\n\t// in a circle reads as a person called A. I. The glyph is sized to the\n\t// circle (the fallback's own size is tuned for two letters, and a lone icon\n\t// at that size reads as a speck); `alt` names it for a screen reader, which\n\t// otherwise gets nothing from a fallback-only avatar.\n\tconst assistant = (\n\t\t<Avatar variant=\"art\"\n\t\t\tsize=\"sm\"\n\t\t\talt=\"Assistant\"\n\t\t\tfallback={<Icon name=\"sparkles\" className=\"text-base\" />}\n\t\t/>\n\t)\n\n\treturn (\n\t\t<Page as=\"main\" max=\"feed\" gap={0} className=\"py-6\">\n\t\t\t{/* h-dvh minus the page's own padding (py-6 = 12 spacing units, in\n\t\t\t spacing units so density moves both together): the panel owns the\n\t\t\t viewport, and the transcript — not the page — is the thing that\n\t\t\t scrolls. The panel is a Card; its parts inset by the card's own\n\t\t\t --card-p, so header, transcript and composer share one edge. */}\n\t\t\t<Card\n\t\t\t\trender={<section aria-labelledby=\"chat-title\" />}\n\t\t\t\tclassName=\"flex h-[calc(100dvh_-_--spacing(12))] flex-col overflow-hidden\"\n\t\t\t>\n\t\t\t\t<CardHeader className=\"pb-(--card-p) shadow-[inset_0_-1px_0_var(--border)]\">\n\t\t\t\t\t{/* CardTitle carries the type; the h1 inside it is the page heading. */}\n\t\t\t\t\t<CardTitle>\n\t\t\t\t\t\t<h1 id=\"chat-title\">Scroll behaviour</h1>\n\t\t\t\t\t</CardTitle>\n\t\t\t\t\t<CardDescription>Assistant · answers from your team's docs</CardDescription>\n\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t{/* Labelled, not a bare plus: the composer already has a plus\n\t\t\t\t\t\t (attach), and two identical glyphs a panel apart that do\n\t\t\t\t\t\t different things is a guessing game. */}\n\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\">\n\t\t\t\t\t\t\t<Icon name=\"edit\" />\n\t\t\t\t\t\t\tNew chat\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</CardAction>\n\t\t\t\t</CardHeader>\n\n\t\t\t\t{/* flex-1 min-h-0 is the whole layout: it bounds the scroll region\n\t\t\t\t against the panel instead of letting the transcript grow the page. */}\n\t\t\t\t<MessageScroller className=\"min-h-0 flex-1 p-(--card-p)\">\n\t\t\t\t\t<Marker variant=\"separator\">Today</Marker>\n\n\t\t\t\t\t{turns.map((turn, i) => {\n\t\t\t\t\t\t// Derived, not hand-set: a turn continues a run when the one\n\t\t\t\t\t\t// before it has the same author. A continuation drops its\n\t\t\t\t\t\t// avatar (Message keeps the column) AND its tail (Bubble keeps\n\t\t\t\t\t\t// the ear's space) — together, that makes the run one block.\n\t\t\t\t\t\tconst continues = i > 0 && turns[i - 1].mine === turn.mine\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<Message\n\t\t\t\t\t\t\t\tkey={turn.id}\n\t\t\t\t\t\t\t\tmessageId={turn.id}\n\t\t\t\t\t\t\t\talign={turn.mine ? \"end\" : \"start\"}\n\t\t\t\t\t\t\t\tgrouped={continues}\n\t\t\t\t\t\t\t\tavatar={turn.mine ? undefined : assistant}\n\t\t\t\t\t\t\t\theader={\n\t\t\t\t\t\t\t\t\tturn.step ? (\n\t\t\t\t\t\t\t\t\t\t<Marker variant=\"note\" icon=\"book\" className=\"justify-start\">\n\t\t\t\t\t\t\t\t\t\t\t{turn.step}\n\t\t\t\t\t\t\t\t\t\t</Marker>\n\t\t\t\t\t\t\t\t\t) : undefined\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Bubble\n\t\t\t\t\t\t\t\t\tvariant={turn.mine ? \"primary\" : \"muted\"}\n\t\t\t\t\t\t\t\t\talign={turn.mine ? \"end\" : \"start\"}\n\t\t\t\t\t\t\t\t\ttail={!continues}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{turn.body}\n\t\t\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t\t\t</Message>\n\t\t\t\t\t\t)\n\t\t\t\t\t})}\n\n\t\t\t\t\t{/* The same run, continued: long-form output with no bubble,\n\t\t\t\t\t because a tinted box around a code block reads as a quote\n\t\t\t\t\t rather than as a turn. Its footer carries what the reader does\n\t\t\t\t\t WITH a settled answer, as a Toolbar: one tab stop, arrow keys\n\t\t\t\t\t between the actions. */}\n\t\t\t\t\t<Message\n\t\t\t\t\t\tmessageId=\"t_04\"\n\t\t\t\t\t\tgrouped\n\t\t\t\t\t\tavatar={assistant}\n\t\t\t\t\t\tfooter={\n\t\t\t\t\t\t\t<Toolbar aria-label=\"Answer actions\">\n\t\t\t\t\t\t\t\t<SimpleTooltip content=\"Copy answer\">\n\t\t\t\t\t\t\t\t\t<ToolbarButton size=\"icon-sm\" aria-label=\"Copy answer\">\n\t\t\t\t\t\t\t\t\t\t<Icon name=\"copy\" />\n\t\t\t\t\t\t\t\t\t</ToolbarButton>\n\t\t\t\t\t\t\t\t</SimpleTooltip>\n\t\t\t\t\t\t\t\t<SimpleTooltip content=\"Regenerate\">\n\t\t\t\t\t\t\t\t\t<ToolbarButton size=\"icon-sm\" aria-label=\"Regenerate answer\">\n\t\t\t\t\t\t\t\t\t\t<Icon name=\"refresh\" />\n\t\t\t\t\t\t\t\t\t</ToolbarButton>\n\t\t\t\t\t\t\t\t</SimpleTooltip>\n\t\t\t\t\t\t\t</Toolbar>\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t{/* max-w-full: a plain Bubble sizes to its content with no\n\t\t\t\t\t\t measure cap, and a code line is wider than a phone. Bounded\n\t\t\t\t\t\t by the column, the CodeBlock scrolls inside itself instead of\n\t\t\t\t\t\t pushing the whole transcript sideways. */}\n\t\t\t\t\t\t<Bubble variant=\"plain\" className=\"max-w-full\">\n\t\t\t\t\t\t\t<p className=\"text-body-md text-foreground\">\n\t\t\t\t\t\t\t\tHere is the shape of it:\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<CodeBlock\n\t\t\t\t\t\t\t\tcode={\n\t\t\t\t\t\t\t\t\t\"const shift = anchor.offsetTop - lastTop\\nif (shift !== 0) viewport.scrollTop += shift\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfilename=\"preserve.ts\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t</Message>\n\n\t\t\t\t\t{/* The reader's follow-up is one turn: the question and the file\n\t\t\t\t\t it refers to stack in a single Message, so they share a side, an\n\t\t\t\t\t edge and a timestamp rather than reading as two sends. */}\n\t\t\t\t\t<Message messageId=\"t_05\" align=\"end\">\n\t\t\t\t\t\t<Bubble variant=\"primary\" align=\"end\">\n\t\t\t\t\t\t\tHere's ours. Does the same fix cover new messages arriving?\n\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t\t<Attachment\n\t\t\t\t\t\t\tname=\"scroller.tsx\"\n\t\t\t\t\t\t\tmeta=\"4.2 KB · TSX\"\n\t\t\t\t\t\t\tonOpen={() => undefined}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</Message>\n\n\t\t\t\t\t{/* The answer arriving. A `Marker` with a spinner — or ThinkingOrb —\n\t\t\t\t\t is what sits here BEFORE the first token; once tokens land it is\n\t\t\t\t\t replaced rather than kept, because two indicators for one wait\n\t\t\t\t\t read as two things happening. What stays is the step it took,\n\t\t\t\t\t as a finished note in the header.\n\n\t\t\t\t\t No bubble, same as the long-form answer above: a tinted box\n\t\t\t\t\t around a growing paragraph makes the reflow look like the box\n\t\t\t\t\t breathing. And no answer actions yet — there is nothing to copy\n\t\t\t\t\t or regenerate until it settles; Stop lives in the composer.\n\n\t\t\t\t\t MessageScroller needs no help with this — it already re-follows on\n\t\t\t\t\t \"a message grew\" (ResizeObserver), not just on \"a message arrived\",\n\t\t\t\t\t so the newest token stays pinned while the reader can still scroll\n\t\t\t\t\t up without being yanked back. */}\n\t\t\t\t\t<Message\n\t\t\t\t\t\tmessageId=\"t_06\"\n\t\t\t\t\t\tavatar={assistant}\n\t\t\t\t\t\theader={\n\t\t\t\t\t\t\t<Marker variant=\"note\" icon=\"file\" className=\"justify-start\">\n\t\t\t\t\t\t\t\tRead scroller.tsx\n\t\t\t\t\t\t\t</Marker>\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Bubble variant=\"plain\">\n\t\t\t\t\t\t\t<StreamingText text={streamed} streaming={pending} />\n\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t</Message>\n\t\t\t\t</MessageScroller>\n\n\t\t\t\t<CardFooter className=\"pt-(--card-p) shadow-[inset_0_1px_0_var(--border)]\">\n\t\t\t\t\t<ChatComposer\n\t\t\t\t\t\tclassName=\"flex-1\"\n\t\t\t\t\t\tplaceholder=\"Ask a follow-up…\"\n\t\t\t\t\t\t// Same flag as the answer's `streaming`, which is what turns Send\n\t\t\t\t\t\t// into Stop. A composer that stays sendable mid-answer invites a\n\t\t\t\t\t\t// second request on top of the one still running.\n\t\t\t\t\t\tbusy={pending}\n\t\t\t\t\t\tonStop={() => undefined}\n\t\t\t\t\t\tstopLabel=\"Stop answering\"\n\t\t\t\t\t\tonAttach={() => undefined}\n\t\t\t\t\t\tfooter=\"Enter to send · Shift+Enter for a new line\"\n\t\t\t\t\t\t// Model and effort sit in `actions`, at the send end of the row:\n\t\t\t\t\t\t// they qualify the message about to be sent, so they belong beside\n\t\t\t\t\t\t// the button that sends it rather than next to the attach control.\n\t\t\t\t\t\tactions={\n\t\t\t\t\t\t\t<SettingsMenu\n\t\t\t\t\t\t\t\tvariant=\"subtle\"\n\t\t\t\t\t\t\t\tsize=\"xs\"\n\t\t\t\t\t\t\t\tlabel=\"Change model and effort\"\n\t\t\t\t\t\t\t\tsummary={\n\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-foreground\">5.6 Sol</span>{\" \"}\n\t\t\t\t\t\t\t\t\t\t<span className=\"text-muted-foreground\">High</span>\n\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<SettingsRow label=\"Model\" value=\"5.6 Sol\">\n\t\t\t\t\t\t\t\t\t<MenuRadioGroup defaultValue=\"sol\">\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"sol\">5.6 Sol</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"lite\">5.6 Lite</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t</MenuRadioGroup>\n\t\t\t\t\t\t\t\t</SettingsRow>\n\t\t\t\t\t\t\t\t<SettingsRow label=\"Effort\" value=\"High\">\n\t\t\t\t\t\t\t\t\t<MenuRadioGroup defaultValue=\"high\">\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"low\">Low</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"standard\">Standard</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"high\">High</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t</MenuRadioGroup>\n\t\t\t\t\t\t\t\t</SettingsRow>\n\t\t\t\t\t\t\t\t<SettingsRow label=\"Speed\" value=\"Standard\">\n\t\t\t\t\t\t\t\t\t<MenuRadioGroup defaultValue=\"standard\">\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"standard\">Standard</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t\t<MenuRadioItem value=\"priority\">Priority</MenuRadioItem>\n\t\t\t\t\t\t\t\t\t</MenuRadioGroup>\n\t\t\t\t\t\t\t\t</SettingsRow>\n\t\t\t\t\t\t\t</SettingsMenu>\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t</CardFooter>\n\t\t\t</Card>\n\t\t</Page>\n\t)\n}"
20430
20563
  },
20431
20564
  {
20432
20565
  "name": "DashboardPage",
20433
20566
  "path": "patterns/dashboard-page.tsx",
20434
- "summary": "App-shell creator dashboard: a branded Sidebar with an account footer (AppHeader + BottomNav below lg), a KPI row, an edge-to-edge editions table with sell-through Meters, and supporting payout, collector, release and activity cards — the default shape for an operator or analytics home route.",
20435
- "intent": "Reach for this when a route's job is monitoring or managing many records (sales, orders, editions, posts) with a few headline numbers. Not for reading-focused routes (use FeedPage) or single-record routes (use ListDetailPage's detail side).",
20567
+ "summary": "App-shell admin dashboard: a branded Sidebar with an account footer (AppHeader + BottomNav below lg), a KPI row, an edge-to-edge accounts table with seat-usage Meters, and supporting payout, top-customer, release and activity cards — the default shape for an operator or analytics home route.",
20568
+ "intent": "Reach for this when a route's job is monitoring or managing many records (sales, orders, accounts, posts) with a few headline numbers. Not for reading-focused routes (use FeedPage) or single-record routes (use ListDetailPage's detail side).",
20436
20569
  "notes": [
20437
20570
  "Region cap is `wide` (1280px): dashboards hold tables and multi-column grids, never a reading column.",
20438
- "Hierarchy runs headline → records → context: KPI row, then the primary table beside its two closest companions (payout, top collectors), then the lower-priority lists (upcoming, activity). Every row of the grid is filled so the route ends on content, not empty canvas.",
20571
+ "Hierarchy runs headline → records → context: KPI row, then the primary table beside its two closest companions (payout, top customers), then the lower-priority lists (upcoming, activity). Every row of the grid is filled so the route ends on content, not empty canvas.",
20439
20572
  "Stat cards: CardHeader carries the label (CardDescription) and the value; KPI values are `text-mono-xl` — the metric mono tier, tabular figures — never a heading tier. The trend Badge sits with its comparison note in CardContent, so the value keeps the full card width when the row folds to 2×2 on a phone.",
20440
20573
  "Stat cards use an explicit cols map (2 → 4) — a 4-stat row must fold to 2×2, never strand an orphan the way auto-fit can.",
20441
20574
  "The table runs flush to the card edges with `<CardContent flush>`, which hands the card's padding to the table (`--table-edge-px`), so the first and last columns line up with the CardHeader at any card density — never hand-set pl-6/pr-6. Numeric columns align end in mono; Table scrolls horizontally inside the card on narrow screens.",
20442
- "Sold / supply is a `Meter` (how full a known range is), not `Progress` (task progress). Status is `Badge appearance=\"plain\" dot`, with the app's own states mapped to tones once at the top of the file.",
20575
+ "Seats used / seats is a `Meter` (how full a known range is), not `Progress` (task progress). Status is `Badge appearance=\"plain\" dot`, with the app's own states mapped to tones once at the top of the file.",
20443
20576
  "Lists are Entity rows in CardList, inset to the card padding and divided by hairlines — never CardContent with hand-tuned px. A card that shares a grid row with a taller one becomes `flex flex-col` and pins its CardFooter with `mt-auto`.",
20444
20577
  "Brand and account live in the Sidebar: SidebarHeader holds the mark + wordmark, SidebarFooter holds the signed-in Entity with an OverflowMenu. Sidebar rebinds the page ink tokens to its own, so the Entity and OverflowMenu read correctly even when a theme paints the rail dark on a light page.",
20445
20578
  "The page has exactly one heading-1 (PageHeader); card titles are Title tier, not headings.",
@@ -20450,23 +20583,23 @@
20450
20583
  "AppShell provides the skip link and <main id=\"main-content\"> landmark.",
20451
20584
  "The active SidebarItem and BottomNavItem carry aria-current via `active`.",
20452
20585
  "DataTable headers are real <th>; sortable columns are announced.",
20453
- "Each sell-through Meter has an aria-label naming the edition and aria-valuetext reading \"184 of 250 sold\".",
20586
+ "Each seat-usage Meter has an aria-label naming the account and aria-valuetext reading \"184 of 250 seats used\".",
20454
20587
  "Avatars beside a visible name use an empty alt so the name is not read twice."
20455
20588
  ],
20456
20589
  "imports": "import { AppHeader, AppShell, Avatar, Badge, BottomNav, BottomNavItem, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, DataTable, Entity, Grid, Icon, MenuItem, MenuSeparator, Meter, NotificationBadge, OverflowMenu, Page, PageHeader, RelativeTime, Row, Sidebar, SidebarBrand, SidebarFooter, SidebarHeader, SidebarItem, SidebarNav, Stack } from \"@sableui/react\"",
20457
- "code": "// Edition status → Badge tone. Sable has no status vocabulary of its own;\n// an app maps ITS states onto tones once, here, and every badge follows.\nconst STATUS_TONE = { live: \"success\", scheduled: \"info\", \"sold-out\": \"edition\", closed: \"neutral\" } as const\nconst STATUS_LABEL = { live: \"Live\", scheduled: \"Scheduled\", \"sold-out\": \"Sold out\", closed: \"Closed\" } as const\ntype EditionStatus = keyof typeof STATUS_TONE\n\nconst MIN = 60 * 1000\nconst HOUR = 60 * MIN\nconst DAY = 24 * HOUR\n\nconst usd = new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency: \"USD\", maximumFractionDigits: 0 })\nconst usdCents = new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency: \"USD\" })\nconst shortDate = (offset: number) =>\n\tnew Date(Date.now() + offset).toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" })\n\nconst stats = [\n\t{ label: \"Revenue\", value: \"$18,240\", delta: \"+8.2%\", up: true, note: \"vs. $16,857\" },\n\t{ label: \"Collects\", value: \"1,284\", delta: \"+3.1%\", up: true, note: \"182 first-time\" },\n\t{ label: \"Collectors\", value: \"312\", delta: \"+12.4%\", up: true, note: \"Best month yet\" },\n\t{ label: \"Sell-through\", value: \"64%\", delta: \"−1.8%\", up: false, note: \"6 open editions\" },\n]\n\nconst editions = [\n\t{ id: \"ed_01\", title: \"Gold Rush\", status: \"live\" as const, sold: 184, supply: 250, price: 48, released: -6 * DAY },\n\t{ id: \"ed_02\", title: \"Quiet Harbor\", status: \"sold-out\" as const, sold: 100, supply: 100, price: 120, released: -19 * DAY },\n\t{ id: \"ed_03\", title: \"Dusk, no. 4\", status: \"live\" as const, sold: 61, supply: 150, price: 64, released: -2 * DAY },\n\t{ id: \"ed_04\", title: \"Tidewater\", status: \"live\" as const, sold: 212, supply: 300, price: 36, released: -27 * DAY },\n\t{ id: \"ed_05\", title: \"Salt Study\", status: \"closed\" as const, sold: 38, supply: 75, price: 90, released: -44 * DAY },\n\t{ id: \"ed_06\", title: \"Paper Moon\", status: \"sold-out\" as const, sold: 50, supply: 50, price: 220, released: -58 * DAY },\n\t{ id: \"ed_07\", title: \"Night Signal\", status: \"scheduled\" as const, sold: 0, supply: 200, price: 56, released: 3 * DAY },\n\t{ id: \"ed_08\", title: \"Low Tide, Blue\", status: \"closed\" as const, sold: 72, supply: 120, price: 42, released: -71 * DAY },\n\t{ id: \"ed_09\", title: \"Ember Field\", status: \"sold-out\" as const, sold: 80, supply: 80, price: 140, released: -86 * DAY },\n]\nconst editionCount = 24\n\nconst payout = {\n\tamount: 8214.6,\n\tdate: 5 * DAY,\n\tlines: [\n\t\t{ label: \"Sales\", value: 9127.33 },\n\t\t{ label: \"Platform fee (10%)\", value: -912.73 },\n\t\t{ label: \"Deposits to\", value: \"Checking ••4821\" },\n\t],\n}\n\nconst collectors = [\n\t{ id: \"co_01\", name: \"Nina Simone\", handle: \"@nina\", initials: \"NS\", pieces: 14, spent: 1460 },\n\t{ id: \"co_02\", name: \"Grace Hopper\", handle: \"@grace\", initials: \"GH\", pieces: 11, spent: 1128 },\n\t{ id: \"co_03\", name: \"Frida Kahlo\", handle: \"@frida\", initials: \"FK\", pieces: 9, spent: 942 },\n\t{ id: \"co_04\", name: \"Mary Shelley\", handle: \"@mary\", initials: \"MS\", pieces: 6, spent: 588 },\n]\n\nconst upcoming = [\n\t{ id: \"up_01\", title: \"Night Signal\", detail: \"Edition of 200 · $56\", at: 3 * DAY },\n\t{ id: \"up_02\", title: \"Tidewater, second printing\", detail: \"Edition of 100 · $40\", at: 12 * DAY },\n\t{ id: \"up_03\", title: \"Dusk, no. 5\", detail: \"Edition of 150 · $64\", at: 26 * DAY },\n\t{ id: \"up_04\", title: \"Quiet Harbor, artist proofs\", detail: \"Edition of 10 · $480\", at: 34 * DAY },\n]\n\nconst activity = [\n\t{ id: \"ac_01\", who: \"Nina Simone\", initials: \"NS\", what: \"collected Gold Rush · 185 of 250\", when: 2 * MIN },\n\t{ id: \"ac_02\", who: \"Grace Hopper\", initials: \"GH\", what: \"collected Dusk, no. 4 · 61 of 150\", when: 26 * MIN },\n\t{ id: \"ac_03\", who: \"Frida Kahlo\", initials: \"FK\", what: \"tipped $20 on Tidewater\", when: 1 * HOUR },\n\t{ id: \"ac_04\", who: \"Mary Shelley\", initials: \"MS\", what: \"started following you\", when: 3 * HOUR },\n\t{ id: \"ac_05\", who: \"Nina Simone\", initials: \"NS\", what: \"set a reminder for Night Signal\", when: 6 * HOUR },\n]\n\nexport function DashboardPage() {\n\treturn (\n\t\t<AppShell\n\t\t\t// Sidebar hides below lg, so the same destinations ride in the shell's\n\t\t\t// mobile slots — without them the route has no navigation under 1024px.\n\t\t\theader={\n\t\t\t\t<AppHeader\n\t\t\t\t\ttitle={\n\t\t\t\t\t\t<Row gap={1} align=\"center\">\n\t\t\t\t\t\t\t<Icon name=\"gem\" variant=\"solid\" className=\"text-xl\" />\n\t\t\t\t\t\t\t<span className=\"text-title-sm\">Atelier</span>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t}\n\t\t\t\t\tactions={<Avatar variant=\"art\" size=\"sm\" fallback=\"AL\" alt=\"Ada Lovelace\" />}\n\t\t\t\t/>\n\t\t\t}\n\t\t\tbottomNav={\n\t\t\t\t<BottomNav aria-label=\"Primary\">\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"grid\" />} label=\"Overview\" showLabel active render={<a href=\"/\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"image-stack\" />} label=\"Editions\" showLabel render={<a href=\"/editions\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"user\" />} label=\"Collectors\" showLabel render={<a href=\"/collectors\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"bell\" />} label=\"Activity\" showLabel render={<a href=\"/activity\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"gear\" />} label=\"Settings\" showLabel render={<a href=\"/settings\" />} />\n\t\t\t\t</BottomNav>\n\t\t\t}\n\t\t\tsidebar={\n\t\t\t\t<Sidebar>\n\t\t\t\t\t<SidebarHeader>\n\t\t\t\t\t\t<SidebarBrand mark={<Icon name=\"gem\" variant=\"solid\" />} label=\"Atelier\" render={<a href=\"/\" />} />\n\t\t\t\t\t</SidebarHeader>\n\t\t\t\t\t<SidebarNav>\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"grid\" />} label=\"Overview\" active render={<a href=\"/\" />} />\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"image-stack\" />} label=\"Editions\" render={<a href=\"/editions\" />} />\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"user\" />} label=\"Collectors\" render={<a href=\"/collectors\" />} />\n\t\t\t\t\t\t<SidebarItem\n\t\t\t\t\t\t\ticon={<Icon name=\"bell\" />}\n\t\t\t\t\t\t\tlabel=\"Activity\"\n\t\t\t\t\t\t\tbadge={<NotificationBadge count={4} tone=\"neutral\" />}\n\t\t\t\t\t\t\trender={<a href=\"/activity\" />}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"gear\" />} label=\"Settings\" render={<a href=\"/settings\" />} />\n\t\t\t\t\t</SidebarNav>\n\t\t\t\t\t<SidebarFooter>\n\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\tclassName=\"[--entity-py:0]\"\n\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback=\"AL\" alt=\"\" />}\n\t\t\t\t\t\t\ttitle=\"Ada Lovelace\"\n\t\t\t\t\t\t\tsubtitle=\"@ada\"\n\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t<OverflowMenu label=\"Account\" size=\"icon-sm\">\n\t\t\t\t\t\t\t\t\t<MenuItem render={<a href=\"/@ada\" />}>View profile</MenuItem>\n\t\t\t\t\t\t\t\t\t<MenuItem render={<a href=\"/settings\" />}>Settings</MenuItem>\n\t\t\t\t\t\t\t\t\t<MenuSeparator />\n\t\t\t\t\t\t\t\t\t<MenuItem>Sign out</MenuItem>\n\t\t\t\t\t\t\t\t</OverflowMenu>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SidebarFooter>\n\t\t\t\t</Sidebar>\n\t\t\t}\n\t\t>\n\t\t\t<Page max=\"wide\" gap={4} className=\"py-6\">\n\t\t\t\t<PageHeader\n\t\t\t\t\ttitle=\"Overview\"\n\t\t\t\t\tdescription=\"Sales and collector activity for the last 30 days.\"\n\t\t\t\t\tactions={\n\t\t\t\t\t\t<Row gap={2}>\n\t\t\t\t\t\t\t<Button variant=\"outline\">\n\t\t\t\t\t\t\t\t<Icon name=\"download\" />\n\t\t\t\t\t\t\t\tExport\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button>\n\t\t\t\t\t\t\t\t<Icon name=\"plus\" />\n\t\t\t\t\t\t\t\tNew edition\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t}\n\t\t\t\t/>\n\t\t\t\t<Grid cols={{ base: 2, xl: 4 }} gap={3}>\n\t\t\t\t\t{stats.map((stat) => (\n\t\t\t\t\t\t<Card key={stat.label}>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardDescription>{stat.label}</CardDescription>\n\t\t\t\t\t\t\t\t<CardTitle className=\"text-mono-xl\">{stat.value}</CardTitle>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t<Row gap={1} wrap>\n\t\t\t\t\t\t\t\t\t<Badge\n\t\t\t\t\t\t\t\t\t\ttone={stat.up ? \"success\" : \"warning\"}\n\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\ticon={<Icon name={stat.up ? \"arrow-up\" : \"arrow-down\"} />}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{stat.delta}\n\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t<span className=\"text-caption text-muted-foreground\">{stat.note}</span>\n\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t))}\n\t\t\t\t</Grid>\n\t\t\t\t<Grid cols={{ base: 1, xl: 3 }} gap={3}>\n\t\t\t\t\t<Card className=\"flex flex-col xl:col-span-2\">\n\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t<CardTitle>Editions</CardTitle>\n\t\t\t\t\t\t\t<CardDescription>Sales across your releases, newest first.</CardDescription>\n\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t<CardContent flush className=\"mt-4 flex-1\">\n\t\t\t\t\t\t\t<DataTable\n\t\t\t\t\t\t\t\tdata={editions}\n\t\t\t\t\t\t\t\trowKey={(row) => row.id}\n\t\t\t\t\t\t\t\tdefaultSort={{ key: \"released\", direction: \"desc\" }}\n\t\t\t\t\t\t\t\tcolumns={[\n\t\t\t\t\t\t\t\t\t{ key: \"title\", header: \"Edition\", sortable: true, className: \"text-label-md whitespace-nowrap\" },\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tkey: \"status\",\n\t\t\t\t\t\t\t\t\t\theader: \"Status\",\n\t\t\t\t\t\t\t\t\t\tcell: (row) => (\n\t\t\t\t\t\t\t\t\t\t\t<Badge appearance=\"plain\" tone={STATUS_TONE[row.status as EditionStatus]} dot>\n\t\t\t\t\t\t\t\t\t\t\t\t{STATUS_LABEL[row.status as EditionStatus]}\n\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tkey: \"sold\",\n\t\t\t\t\t\t\t\t\t\theader: \"Sold\",\n\t\t\t\t\t\t\t\t\t\tsortable: true,\n\t\t\t\t\t\t\t\t\t\tsortValue: (row) => row.sold / row.supply,\n\t\t\t\t\t\t\t\t\t\tcell: (row) => (\n\t\t\t\t\t\t\t\t\t\t\t<Row gap={3} align=\"center\" className=\"min-w-40\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Meter\n\t\t\t\t\t\t\t\t\t\t\t\t\tvalue={row.sold}\n\t\t\t\t\t\t\t\t\t\t\t\t\tmax={row.supply}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\ttone={row.sold === row.supply ? \"success\" : \"primary\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\taria-label={`${row.title} sold`}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgetAriaValueText={(_, v) => `${v} of ${row.supply} sold`}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"flex-1\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-mono-caption whitespace-nowrap text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{row.sold}/{row.supply}\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tkey: \"revenue\",\n\t\t\t\t\t\t\t\t\t\theader: \"Revenue\",\n\t\t\t\t\t\t\t\t\t\talign: \"end\",\n\t\t\t\t\t\t\t\t\t\tsortable: true,\n\t\t\t\t\t\t\t\t\t\tsortValue: (row) => row.sold * row.price,\n\t\t\t\t\t\t\t\t\t\tcell: (row) =>\n\t\t\t\t\t\t\t\t\t\t\trow.sold > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-mono-sm\">{usd.format(row.sold * row.price)}</span>\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-mono-sm text-muted-foreground\">—</span>\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tkey: \"released\",\n\t\t\t\t\t\t\t\t\t\theader: \"Release\",\n\t\t\t\t\t\t\t\t\t\talign: \"end\",\n\t\t\t\t\t\t\t\t\t\tsortable: true,\n\t\t\t\t\t\t\t\t\t\tcell: (row) => (\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-body-sm whitespace-nowrap text-muted-foreground\">{shortDate(row.released)}</span>\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t<CardFooter className=\"justify-between\">\n\t\t\t\t\t\t\t<span className=\"text-caption text-muted-foreground\">\n\t\t\t\t\t\t\t\tShowing {editions.length} of {editionCount} editions\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" bleed=\"end\" render={<a href=\"/editions\" />}>\n\t\t\t\t\t\t\t\tView all editions\n\t\t\t\t\t\t\t\t<Icon name=\"arrow-right\" />\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t</Card>\n\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardDescription>Next payout</CardDescription>\n\t\t\t\t\t\t\t\t<CardTitle className=\"text-mono-xl\">{usdCents.format(payout.amount)}</CardTitle>\n\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t<Badge tone=\"info\" size=\"sm\" icon={<Icon name=\"calendar\" />}>\n\t\t\t\t\t\t\t\t\t\t{shortDate(payout.date)}\n\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t<dl className=\"sable-list-dl text-body-sm\">\n\t\t\t\t\t\t\t\t\t{payout.lines.map((line) => (\n\t\t\t\t\t\t\t\t\t\t<div key={line.label} className=\"contents\">\n\t\t\t\t\t\t\t\t\t\t\t<dt>{line.label}</dt>\n\t\t\t\t\t\t\t\t\t\t\t<dd className={typeof line.value === \"number\" ? \"text-end text-mono-sm\" : \"text-end\"}>\n\t\t\t\t\t\t\t\t\t\t\t\t{typeof line.value === \"number\" ? usdCents.format(line.value) : line.value}\n\t\t\t\t\t\t\t\t\t\t\t</dd>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</dl>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardTitle>Top collectors</CardTitle>\n\t\t\t\t\t\t\t\t<CardDescription>By spend this month.</CardDescription>\n\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" render={<a href=\"/collectors\" />}>\n\t\t\t\t\t\t\t\t\t\tView all\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t{collectors.map((person) => (\n\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\tkey={person.id}\n\t\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback={person.initials} alt=\"\" />}\n\t\t\t\t\t\t\t\t\t\ttitle={person.name}\n\t\t\t\t\t\t\t\t\t\tsubtitle={`${person.handle} · ${person.pieces} pieces`}\n\t\t\t\t\t\t\t\t\t\taction={<span className=\"text-mono-sm\">{usd.format(person.spent)}</span>}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</Stack>\n\t\t\t\t</Grid>\n\t\t\t\t<Grid cols={{ base: 1, xl: 3 }} gap={3}>\n\t\t\t\t\t<Card className=\"flex flex-col\">\n\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t<CardTitle>Upcoming releases</CardTitle>\n\t\t\t\t\t\t\t<CardDescription>Scheduled to open to collectors.</CardDescription>\n\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t{upcoming.map((release) => (\n\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\tkey={release.id}\n\t\t\t\t\t\t\t\t\ttitle={release.title}\n\t\t\t\t\t\t\t\t\tsubtitle={release.detail}\n\t\t\t\t\t\t\t\t\taction={<RelativeTime date={Date.now() + release.at} className=\"text-caption text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t<CardFooter className=\"mt-auto\">\n\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\" render={<a href=\"/editions/new\" />}>\n\t\t\t\t\t\t\t\t<Icon name=\"calendar\" />\n\t\t\t\t\t\t\t\tSchedule a release\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t</Card>\n\t\t\t\t\t<Card className=\"xl:col-span-2\">\n\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t<CardTitle>Activity</CardTitle>\n\t\t\t\t\t\t\t<CardDescription>Collects, tips, and follows.</CardDescription>\n\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" render={<a href=\"/activity\" />}>\n\t\t\t\t\t\t\t\t\tView all\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t{activity.map((item) => (\n\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\tkey={item.id}\n\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback={item.initials} alt=\"\" />}\n\t\t\t\t\t\t\t\t\ttitle={item.who}\n\t\t\t\t\t\t\t\t\tsubtitle={item.what}\n\t\t\t\t\t\t\t\t\taction={<RelativeTime date={Date.now() - item.when} className=\"text-caption text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</CardList>\n\t\t\t\t\t</Card>\n\t\t\t\t</Grid>\n\t\t\t</Page>\n\t\t</AppShell>\n\t)\n}"
20590
+ "code": "// Account status → Badge tone. Sable has no status vocabulary of its own;\n// an app maps ITS states onto tones once, here, and every badge follows.\nconst STATUS_TONE = { active: \"success\", onboarding: \"info\", full: \"1\", cancelled: \"neutral\" } as const\nconst STATUS_LABEL = { active: \"Active\", onboarding: \"Onboarding\", full: \"Seats full\", cancelled: \"Cancelled\" } as const\ntype AccountStatus = keyof typeof STATUS_TONE\n\nconst MIN = 60 * 1000\nconst HOUR = 60 * MIN\nconst DAY = 24 * HOUR\n\nconst usd = new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency: \"USD\", maximumFractionDigits: 0 })\nconst usdCents = new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency: \"USD\" })\nconst shortDate = (offset: number) =>\n\tnew Date(Date.now() + offset).toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" })\n\nconst stats = [\n\t{ label: \"Revenue\", value: \"$18,240\", delta: \"+8.2%\", up: true, note: \"vs. $16,857\" },\n\t{ label: \"Active seats\", value: \"1,284\", delta: \"+3.1%\", up: true, note: \"182 added\" },\n\t{ label: \"Accounts\", value: \"312\", delta: \"+12.4%\", up: true, note: \"Best month yet\" },\n\t{ label: \"Seat usage\", value: \"64%\", delta: \"−1.8%\", up: false, note: \"6 near their limit\" },\n]\n\nconst accounts = [\n\t{ id: \"ac_01\", title: \"Northwind\", status: \"active\" as const, used: 184, seats: 250, price: 16, started: -6 * DAY },\n\t{ id: \"ac_02\", title: \"Harbor Labs\", status: \"full\" as const, used: 100, seats: 100, price: 20, started: -19 * DAY },\n\t{ id: \"ac_03\", title: \"Brightline\", status: \"active\" as const, used: 61, seats: 150, price: 16, started: -2 * DAY },\n\t{ id: \"ac_04\", title: \"Tidewater Co.\", status: \"active\" as const, used: 212, seats: 300, price: 12, started: -27 * DAY },\n\t{ id: \"ac_05\", title: \"Saltworks\", status: \"cancelled\" as const, used: 38, seats: 75, price: 20, started: -44 * DAY },\n\t{ id: \"ac_06\", title: \"Paper Moon\", status: \"full\" as const, used: 50, seats: 50, price: 24, started: -58 * DAY },\n\t{ id: \"ac_07\", title: \"Signal Studio\", status: \"onboarding\" as const, used: 0, seats: 200, price: 16, started: 3 * DAY },\n\t{ id: \"ac_08\", title: \"Bluewater\", status: \"cancelled\" as const, used: 72, seats: 120, price: 12, started: -71 * DAY },\n\t{ id: \"ac_09\", title: \"Ember & Field\", status: \"full\" as const, used: 80, seats: 80, price: 20, started: -86 * DAY },\n]\nconst accountCount = 312\n\nconst payout = {\n\tamount: 8862.64,\n\tdate: 5 * DAY,\n\tlines: [\n\t\t{ label: \"Subscriptions\", value: 9127.33 },\n\t\t{ label: \"Processing (2.9%)\", value: -264.69 },\n\t\t{ label: \"Deposits to\", value: \"Checking ••4821\" },\n\t],\n}\n\nconst customers = [\n\t{ id: \"cu_01\", name: \"Northwind\", handle: \"northwind.io\", initials: \"NW\", seats: 184, spent: 2944 },\n\t{ id: \"cu_02\", name: \"Tidewater Co.\", handle: \"tidewater.co\", initials: \"TC\", seats: 212, spent: 2544 },\n\t{ id: \"cu_03\", name: \"Harbor Labs\", handle: \"harborlabs.com\", initials: \"HL\", seats: 100, spent: 2000 },\n\t{ id: \"cu_04\", name: \"Ember & Field\", handle: \"emberfield.com\", initials: \"EF\", seats: 80, spent: 1600 },\n]\n\nconst upcoming = [\n\t{ id: \"up_01\", title: \"Version 2.4\", detail: \"Shared reports · All plans\", at: 3 * DAY },\n\t{ id: \"up_02\", title: \"Audit log export\", detail: \"Business plan · Beta\", at: 12 * DAY },\n\t{ id: \"up_03\", title: \"Version 2.5\", detail: \"Offline mode · All plans\", at: 26 * DAY },\n\t{ id: \"up_04\", title: \"Usage-based billing\", detail: \"Studio and Business · Beta\", at: 34 * DAY },\n]\n\nconst activity = [\n\t{ id: \"ev_01\", who: \"Nina Simone\", initials: \"NS\", what: \"added 12 seats to Northwind · 184 of 250\", when: 2 * MIN },\n\t{ id: \"ev_02\", who: \"Grace Hopper\", initials: \"GH\", what: \"upgraded Brightline to Studio\", when: 26 * MIN },\n\t{ id: \"ev_03\", who: \"Frida Kahlo\", initials: \"FK\", what: \"paid invoice INV-0042 for Tidewater Co.\", when: 1 * HOUR },\n\t{ id: \"ev_04\", who: \"Mary Shelley\", initials: \"MS\", what: \"joined the Harbor Labs workspace\", when: 3 * HOUR },\n\t{ id: \"ev_05\", who: \"Nina Simone\", initials: \"NS\", what: \"booked onboarding for Signal Studio\", when: 6 * HOUR },\n]\n\nexport function DashboardPage() {\n\treturn (\n\t\t<AppShell\n\t\t\t// Sidebar hides below lg, so the same destinations ride in the shell's\n\t\t\t// mobile slots — without them the route has no navigation under 1024px.\n\t\t\theader={\n\t\t\t\t<AppHeader\n\t\t\t\t\ttitle={\n\t\t\t\t\t\t<Row gap={1} align=\"center\">\n\t\t\t\t\t\t\t<Icon name=\"gem\" variant=\"solid\" className=\"text-xl\" />\n\t\t\t\t\t\t\t<span className=\"text-title-sm\">Atelier</span>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t}\n\t\t\t\t\tactions={<Avatar variant=\"art\" size=\"sm\" fallback=\"AL\" alt=\"Ada Lovelace\" />}\n\t\t\t\t/>\n\t\t\t}\n\t\t\tbottomNav={\n\t\t\t\t<BottomNav aria-label=\"Primary\">\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"grid\" />} label=\"Overview\" showLabel active render={<a href=\"/\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"folder\" />} label=\"Accounts\" showLabel render={<a href=\"/accounts\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"user\" />} label=\"Customers\" showLabel render={<a href=\"/customers\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"bell\" />} label=\"Activity\" showLabel render={<a href=\"/activity\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"gear\" />} label=\"Settings\" showLabel render={<a href=\"/settings\" />} />\n\t\t\t\t</BottomNav>\n\t\t\t}\n\t\t\tsidebar={\n\t\t\t\t<Sidebar>\n\t\t\t\t\t<SidebarHeader>\n\t\t\t\t\t\t<SidebarBrand mark={<Icon name=\"gem\" variant=\"solid\" />} label=\"Atelier\" render={<a href=\"/\" />} />\n\t\t\t\t\t</SidebarHeader>\n\t\t\t\t\t<SidebarNav>\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"grid\" />} label=\"Overview\" active render={<a href=\"/\" />} />\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"folder\" />} label=\"Accounts\" render={<a href=\"/accounts\" />} />\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"user\" />} label=\"Customers\" render={<a href=\"/customers\" />} />\n\t\t\t\t\t\t<SidebarItem\n\t\t\t\t\t\t\ticon={<Icon name=\"bell\" />}\n\t\t\t\t\t\t\tlabel=\"Activity\"\n\t\t\t\t\t\t\tbadge={<NotificationBadge count={4} tone=\"neutral\" />}\n\t\t\t\t\t\t\trender={<a href=\"/activity\" />}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"gear\" />} label=\"Settings\" render={<a href=\"/settings\" />} />\n\t\t\t\t\t</SidebarNav>\n\t\t\t\t\t<SidebarFooter>\n\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\tclassName=\"[--entity-py:0]\"\n\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback=\"AL\" alt=\"\" />}\n\t\t\t\t\t\t\ttitle=\"Ada Lovelace\"\n\t\t\t\t\t\t\tsubtitle=\"@ada\"\n\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t<OverflowMenu label=\"Account\" size=\"icon-sm\">\n\t\t\t\t\t\t\t\t\t<MenuItem render={<a href=\"/@ada\" />}>View profile</MenuItem>\n\t\t\t\t\t\t\t\t\t<MenuItem render={<a href=\"/settings\" />}>Settings</MenuItem>\n\t\t\t\t\t\t\t\t\t<MenuSeparator />\n\t\t\t\t\t\t\t\t\t<MenuItem>Sign out</MenuItem>\n\t\t\t\t\t\t\t\t</OverflowMenu>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SidebarFooter>\n\t\t\t\t</Sidebar>\n\t\t\t}\n\t\t>\n\t\t\t<Page max=\"wide\" gap={4} className=\"py-6\">\n\t\t\t\t<PageHeader\n\t\t\t\t\ttitle=\"Overview\"\n\t\t\t\t\tdescription=\"Revenue and account activity for the last 30 days.\"\n\t\t\t\t\tactions={\n\t\t\t\t\t\t<Row gap={2}>\n\t\t\t\t\t\t\t<Button variant=\"outline\">\n\t\t\t\t\t\t\t\t<Icon name=\"download\" />\n\t\t\t\t\t\t\t\tExport\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button>\n\t\t\t\t\t\t\t\t<Icon name=\"plus\" />\n\t\t\t\t\t\t\t\tNew account\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t}\n\t\t\t\t/>\n\t\t\t\t<Grid cols={{ base: 2, xl: 4 }} gap={3}>\n\t\t\t\t\t{stats.map((stat) => (\n\t\t\t\t\t\t<Card key={stat.label}>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardDescription>{stat.label}</CardDescription>\n\t\t\t\t\t\t\t\t<CardTitle className=\"text-mono-xl\">{stat.value}</CardTitle>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t<Row gap={1} wrap>\n\t\t\t\t\t\t\t\t\t<Badge\n\t\t\t\t\t\t\t\t\t\ttone={stat.up ? \"success\" : \"warning\"}\n\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\ticon={<Icon name={stat.up ? \"arrow-up\" : \"arrow-down\"} />}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{stat.delta}\n\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t<span className=\"text-caption text-muted-foreground\">{stat.note}</span>\n\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t))}\n\t\t\t\t</Grid>\n\t\t\t\t<Grid cols={{ base: 1, xl: 3 }} gap={3}>\n\t\t\t\t\t<Card className=\"flex flex-col xl:col-span-2\">\n\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t<CardTitle>Accounts</CardTitle>\n\t\t\t\t\t\t\t<CardDescription>Seats across your customers, newest first.</CardDescription>\n\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t<CardContent flush className=\"mt-4 flex-1\">\n\t\t\t\t\t\t\t<DataTable\n\t\t\t\t\t\t\t\tdata={accounts}\n\t\t\t\t\t\t\t\trowKey={(row) => row.id}\n\t\t\t\t\t\t\t\tdefaultSort={{ key: \"started\", direction: \"desc\" }}\n\t\t\t\t\t\t\t\tcolumns={[\n\t\t\t\t\t\t\t\t\t{ key: \"title\", header: \"Account\", sortable: true, className: \"text-label-md whitespace-nowrap\" },\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tkey: \"status\",\n\t\t\t\t\t\t\t\t\t\theader: \"Status\",\n\t\t\t\t\t\t\t\t\t\tcell: (row) => (\n\t\t\t\t\t\t\t\t\t\t\t<Badge appearance=\"plain\" tone={STATUS_TONE[row.status as AccountStatus]} dot>\n\t\t\t\t\t\t\t\t\t\t\t\t{STATUS_LABEL[row.status as AccountStatus]}\n\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tkey: \"used\",\n\t\t\t\t\t\t\t\t\t\theader: \"Seats\",\n\t\t\t\t\t\t\t\t\t\tsortable: true,\n\t\t\t\t\t\t\t\t\t\tsortValue: (row) => row.used / row.seats,\n\t\t\t\t\t\t\t\t\t\tcell: (row) => (\n\t\t\t\t\t\t\t\t\t\t\t<Row gap={3} align=\"center\" className=\"min-w-40\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Meter\n\t\t\t\t\t\t\t\t\t\t\t\t\tvalue={row.used}\n\t\t\t\t\t\t\t\t\t\t\t\t\tmax={row.seats}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\ttone={row.used === row.seats ? \"success\" : \"primary\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\taria-label={`${row.title} seats used`}\n\t\t\t\t\t\t\t\t\t\t\t\t\tgetAriaValueText={(_, v) => `${v} of ${row.seats} seats used`}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"flex-1\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-mono-caption whitespace-nowrap text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{row.used}/{row.seats}\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tkey: \"revenue\",\n\t\t\t\t\t\t\t\t\t\theader: \"Revenue\",\n\t\t\t\t\t\t\t\t\t\talign: \"end\",\n\t\t\t\t\t\t\t\t\t\tsortable: true,\n\t\t\t\t\t\t\t\t\t\tsortValue: (row) => row.used * row.price,\n\t\t\t\t\t\t\t\t\t\tcell: (row) =>\n\t\t\t\t\t\t\t\t\t\t\trow.used > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-mono-sm\">{usd.format(row.used * row.price)}</span>\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-mono-sm text-muted-foreground\">—</span>\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tkey: \"started\",\n\t\t\t\t\t\t\t\t\t\theader: \"Started\",\n\t\t\t\t\t\t\t\t\t\talign: \"end\",\n\t\t\t\t\t\t\t\t\t\tsortable: true,\n\t\t\t\t\t\t\t\t\t\tcell: (row) => (\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-body-sm whitespace-nowrap text-muted-foreground\">{shortDate(row.started)}</span>\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t<CardFooter className=\"justify-between\">\n\t\t\t\t\t\t\t<span className=\"text-caption text-muted-foreground\">\n\t\t\t\t\t\t\t\tShowing {accounts.length} of {accountCount} accounts\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" bleed=\"end\" render={<a href=\"/accounts\" />}>\n\t\t\t\t\t\t\t\tView all accounts\n\t\t\t\t\t\t\t\t<Icon name=\"arrow-right\" />\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t</Card>\n\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardDescription>Next payout</CardDescription>\n\t\t\t\t\t\t\t\t<CardTitle className=\"text-mono-xl\">{usdCents.format(payout.amount)}</CardTitle>\n\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t<Badge tone=\"info\" size=\"sm\" icon={<Icon name=\"calendar\" />}>\n\t\t\t\t\t\t\t\t\t\t{shortDate(payout.date)}\n\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t<dl className=\"sable-list-dl text-body-sm\">\n\t\t\t\t\t\t\t\t\t{payout.lines.map((line) => (\n\t\t\t\t\t\t\t\t\t\t<div key={line.label} className=\"contents\">\n\t\t\t\t\t\t\t\t\t\t\t<dt>{line.label}</dt>\n\t\t\t\t\t\t\t\t\t\t\t<dd className={typeof line.value === \"number\" ? \"text-end text-mono-sm\" : \"text-end\"}>\n\t\t\t\t\t\t\t\t\t\t\t\t{typeof line.value === \"number\" ? usdCents.format(line.value) : line.value}\n\t\t\t\t\t\t\t\t\t\t\t</dd>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</dl>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardTitle>Top customers</CardTitle>\n\t\t\t\t\t\t\t\t<CardDescription>By spend this month.</CardDescription>\n\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" render={<a href=\"/customers\" />}>\n\t\t\t\t\t\t\t\t\t\tView all\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t{customers.map((person) => (\n\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\tkey={person.id}\n\t\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback={person.initials} alt=\"\" />}\n\t\t\t\t\t\t\t\t\t\ttitle={person.name}\n\t\t\t\t\t\t\t\t\t\tsubtitle={`${person.handle} · ${person.seats} seats`}\n\t\t\t\t\t\t\t\t\t\taction={<span className=\"text-mono-sm\">{usd.format(person.spent)}</span>}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</Stack>\n\t\t\t\t</Grid>\n\t\t\t\t<Grid cols={{ base: 1, xl: 3 }} gap={3}>\n\t\t\t\t\t<Card className=\"flex flex-col\">\n\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t<CardTitle>Upcoming releases</CardTitle>\n\t\t\t\t\t\t\t<CardDescription>Scheduled to ship to customers.</CardDescription>\n\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t{upcoming.map((release) => (\n\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\tkey={release.id}\n\t\t\t\t\t\t\t\t\ttitle={release.title}\n\t\t\t\t\t\t\t\t\tsubtitle={release.detail}\n\t\t\t\t\t\t\t\t\taction={<RelativeTime date={Date.now() + release.at} className=\"text-caption text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t<CardFooter className=\"mt-auto\">\n\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\" render={<a href=\"/releases/new\" />}>\n\t\t\t\t\t\t\t\t<Icon name=\"calendar\" />\n\t\t\t\t\t\t\t\tSchedule a release\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t</Card>\n\t\t\t\t\t<Card className=\"xl:col-span-2\">\n\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t<CardTitle>Activity</CardTitle>\n\t\t\t\t\t\t\t<CardDescription>Seats, upgrades, and invoices.</CardDescription>\n\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" render={<a href=\"/activity\" />}>\n\t\t\t\t\t\t\t\t\tView all\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t{activity.map((item) => (\n\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\tkey={item.id}\n\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback={item.initials} alt=\"\" />}\n\t\t\t\t\t\t\t\t\ttitle={item.who}\n\t\t\t\t\t\t\t\t\tsubtitle={item.what}\n\t\t\t\t\t\t\t\t\taction={<RelativeTime date={Date.now() - item.when} className=\"text-caption text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</CardList>\n\t\t\t\t\t</Card>\n\t\t\t\t</Grid>\n\t\t\t</Page>\n\t\t</AppShell>\n\t)\n}"
20458
20591
  },
20459
20592
  {
20460
20593
  "name": "FeedPage",
20461
20594
  "path": "patterns/feed-page.tsx",
20462
- "summary": "Social reading feed with a side rail: a feed-width main column (composer, For you / Following Tabs, one Card per post, an edition drop with price, supply Meter and Collect, a Skeleton for the next page) beside a sticky rail of who-to-follow and upcoming drops that drops away below lg.",
20595
+ "summary": "Social reading feed with a side rail: a feed-width main column (composer, For you / Following Tabs, one Card per post, an event post with price, capacity Meter and Register, a Skeleton for the next page) beside a sticky rail of who-to-follow and upcoming events that drops away below lg.",
20463
20596
  "intent": "Reach for this for timelines, activity streams, and any reverse-chronological reading surface where people also act on what they read. Not for record management (DashboardPage) or side-by-side browsing (ListDetailPage).",
20464
20597
  "notes": [
20465
20598
  "The main column is capped at `--region-feed` (640px) inside a `wide` page, so posts keep a reading measure while the rail fills the desktop width. The rail is a fixed `w-80` aside, `sticky` at lg and `hidden` below it — its content is supplementary, so nothing is lost on mobile.",
20466
20599
  "Feed scope is `Tabs` (underline, stretched to the column with `flex`): peer views of the same feed. The composer sits above them because posting applies to both.",
20467
20600
  "The composer is a Card holding `Textarea autoResize` plus ghost icon Buttons and a secondary `Post` — not ChatComposer, which submits on Enter and is for conversations.",
20468
20601
  "Each post is a Card: an Entity byline in a CardList (with an OverflowMenu for post actions), body copy, then media in an AspectBox at the asset's own ratio, inset with `rounded-concentric-lg/6` so its corners follow the card's in every theme.",
20469
- "An edition drop adds a `Badge appearance=\"overlay\"` on the artwork, title and price, the one primary Button on the page (`Collect`), and a `Meter` for supply — collected against a known maximum is a level, not task progress.",
20602
+ "An event post adds a `Badge appearance=\"overlay\"` on the image, title and price, the one primary Button on the page (`Register`), and a `Meter` for capacity — seats taken against a known maximum is a level, not task progress.",
20470
20603
  "Actions are ghost sm Buttons (icon + count) in a Row pulled out by the button's own padding (`-ms-3`) so the first icon sits on the text edge.",
20471
20604
  "Loading the next page shows a Skeleton post in the shape the post will land in, above the explicit load-more Button in its `loading` state — predictable for keyboard and AT users, unlike infinite scroll."
20472
20605
  ],
@@ -20474,11 +20607,11 @@
20474
20607
  "Each post is an <article>, so AT users can jump between posts; the rail is a labelled <aside> landmark.",
20475
20608
  "Rail card titles render as h2 (CardTitle `render={<h2 />}`) under the page's one h1.",
20476
20609
  "Icon-only buttons and every Follow / Remind button carry aria-labels naming their target; counts stay in the accessible name.",
20477
- "The supply Meter is role=meter with a getAriaValueText reading (\"62 of 100 collected\").",
20610
+ "The capacity Meter is role=meter with a getAriaValueText reading (\"28 of 40 seats taken\").",
20478
20611
  "The Skeleton card is aria-busy; the loading Button keeps its label (\"Loading posts\")."
20479
20612
  ],
20480
20613
  "imports": "import { AspectBox, Avatar, Badge, Button, Card, CardContent, CardHeader, CardList, CardTitle, Entity, Icon, MenuItem, MenuSeparator, Meter, MeterLabel, MeterTrack, MeterValue, OverflowMenu, Page, PageHeader, Row, Skeleton, Stack, Tabs, TabsList, TabsPanel, TabsTrigger, Textarea } from \"@sableui/react\"",
20481
- "code": "type Post = {\n\tid: string\n\tauthor: string\n\thandle: string\n\tinitials: string\n\ttime: string\n\tfollowing: boolean\n\tbody: string\n\tmedia?: { src: string; alt: string }\n\tdrop?: { title: string; price: string; collected: number; supply: number }\n\tlikes: number\n\tcomments: number\n}\n\nconst posts: Post[] = [\n\t{\n\t\tid: \"p_01\",\n\t\tauthor: \"Ada Lovelace\",\n\t\thandle: \"@ada\",\n\t\tinitials: \"AL\",\n\t\ttime: \"2h\",\n\t\tfollowing: true,\n\t\tbody: \"Gold Rush is live. 100 signed prints, each paired with the on-chain original. Thank you for waiting.\",\n\t\tmedia: { src: \"/media/edition-cover.svg\", alt: \"Gold Rush, a gold-leaf study in overlapping arcs\" },\n\t\tdrop: { title: \"Gold Rush\", price: \"$120\", collected: 62, supply: 100 },\n\t\tlikes: 128,\n\t\tcomments: 24,\n\t},\n\t{\n\t\tid: \"p_02\",\n\t\tauthor: \"Grace Hopper\",\n\t\thandle: \"@grace\",\n\t\tinitials: \"GH\",\n\t\ttime: \"5h\",\n\t\tfollowing: true,\n\t\tbody: \"Process shot from this week. Compilers and copper ink — the plotter ran for eleven hours on this one.\",\n\t\tmedia: { src: \"/media/process-shot.svg\", alt: \"Work-in-progress plotter drawing in copper ink\" },\n\t\tlikes: 86,\n\t\tcomments: 9,\n\t},\n\t{\n\t\tid: \"p_03\",\n\t\tauthor: \"Nina Simone\",\n\t\thandle: \"@nina\",\n\t\tinitials: \"NS\",\n\t\ttime: \"8h\",\n\t\tfollowing: false,\n\t\tbody: \"Collectors of Dusk, no. 3 — your unlockable score is now in your library. Headphones on for this one.\",\n\t\tlikes: 214,\n\t\tcomments: 31,\n\t},\n]\n\nconst suggestions = [\n\t{ id: \"u_01\", name: \"Mary Shelley\", handle: \"@mary\", initials: \"MS\" },\n\t{ id: \"u_02\", name: \"Nina Simone\", handle: \"@nina\", initials: \"NS\" },\n]\n\nconst upcoming = [\n\t{ id: \"d_01\", title: \"Quiet Harbor\", artist: \"Mary Shelley\", initials: \"MS\", date: \"Sat, Oct 12\" },\n\t{ id: \"d_02\", title: \"Dusk, no. 4\", artist: \"Nina Simone\", initials: \"NS\", date: \"Tue, Oct 15\" },\n]\n\nconst renderPost = (post: Post) => (\n\t<article key={post.id}>\n\t\t<Card>\n\t\t\t<CardList>\n\t\t\t\t<Entity\n\t\t\t\t\tleading={<Avatar variant=\"art\" fallback={post.initials} alt={post.author} />}\n\t\t\t\t\ttitle={post.author}\n\t\t\t\t\tsubtitle={`${post.handle} · ${post.time}`}\n\t\t\t\t\taction={\n\t\t\t\t\t\t<OverflowMenu label={`Post options for ${post.author}`} size=\"icon-sm\">\n\t\t\t\t\t\t\t<MenuItem>Copy link</MenuItem>\n\t\t\t\t\t\t\t<MenuItem>Mute {post.handle}</MenuItem>\n\t\t\t\t\t\t\t<MenuSeparator />\n\t\t\t\t\t\t\t<MenuItem variant=\"destructive\">Report post</MenuItem>\n\t\t\t\t\t\t</OverflowMenu>\n\t\t\t\t\t}\n\t\t\t\t/>\n\t\t\t</CardList>\n\t\t\t<CardContent className=\"flex flex-col gap-4 pt-0\">\n\t\t\t\t<p className=\"text-body-md text-foreground\">{post.body}</p>\n\t\t\t\t{post.media ? (\n\t\t\t\t\t<AspectBox ratio={16 / 9} className=\"overflow-hidden rounded-concentric-lg/6 bg-muted\">\n\t\t\t\t\t\t<img src={post.media.src} alt={post.media.alt} className=\"size-full object-cover\" />\n\t\t\t\t\t\t{post.drop ? (\n\t\t\t\t\t\t\t<div className=\"absolute start-3 top-3\">\n\t\t\t\t\t\t\t\t<Badge appearance=\"overlay\" size=\"sm\" dot>\n\t\t\t\t\t\t\t\t\tLive drop\n\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</AspectBox>\n\t\t\t\t) : null}\n\t\t\t\t{post.drop ? (\n\t\t\t\t\t<Stack gap={1.5}>\n\t\t\t\t\t\t<Row gap={2} align=\"center\" justify=\"between\">\n\t\t\t\t\t\t\t<div className=\"min-w-0\">\n\t\t\t\t\t\t\t\t<p className=\"text-title-md text-foreground\">{post.drop.title}</p>\n\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t{`${post.drop.price} · Edition of ${post.drop.supply}`}\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<Button>{`Collect for ${post.drop.price}`}</Button>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t<Meter\n\t\t\t\t\t\t\tvalue={post.drop.collected}\n\t\t\t\t\t\t\tmax={post.drop.supply}\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tgetAriaValueText={(_, value) => `${value} of ${post.drop?.supply} collected`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Row gap={2} align=\"center\" justify=\"between\" className=\"mb-2\">\n\t\t\t\t\t\t\t\t<MeterLabel>{`${post.drop.collected} collected`}</MeterLabel>\n\t\t\t\t\t\t\t\t<MeterValue>{(_, value) => `${(post.drop?.supply ?? 0) - value} left`}</MeterValue>\n\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t<MeterTrack />\n\t\t\t\t\t\t</Meter>\n\t\t\t\t\t</Stack>\n\t\t\t\t) : null}\n\t\t\t\t{/* bleed=\"start\" pulls the first button out by exactly its own\n\t\t\t\t padding, so its icon lands on the text edge above it. */}\n\t\t\t\t<Row gap={0.5}>\n\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" bleed=\"start\" aria-label={`Like, ${post.likes} likes`}>\n\t\t\t\t\t\t<Icon name=\"heart\" />\n\t\t\t\t\t\t{post.likes}\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" aria-label={`Comment, ${post.comments} comments`}>\n\t\t\t\t\t\t<Icon name=\"comment\" />\n\t\t\t\t\t\t{post.comments}\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" aria-label=\"Share\">\n\t\t\t\t\t\t<Icon name=\"share\" />\n\t\t\t\t\t</Button>\n\t\t\t\t</Row>\n\t\t\t</CardContent>\n\t\t</Card>\n\t</article>\n)\n\nexport function FeedPage() {\n\treturn (\n\t\t<Page as=\"main\" max=\"wide\" gap={4} className=\"py-6\">\n\t\t\t<Row gap={4} align=\"start\" justify=\"center\">\n\t\t\t\t<Stack gap={3} className=\"min-w-0 max-w-(--region-feed) flex-1\">\n\t\t\t\t\t<PageHeader title=\"Home\" />\n\t\t\t\t\t<Card>\n\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t<Row gap={1.5} align=\"start\">\n\t\t\t\t\t\t\t\t<Avatar variant=\"art\" fallback=\"AL\" alt=\"Ada Lovelace\" />\n\t\t\t\t\t\t\t\t<Stack gap={1.5} className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t\t\t<Textarea\n\t\t\t\t\t\t\t\t\t\tautoResize\n\t\t\t\t\t\t\t\t\t\trows={2}\n\t\t\t\t\t\t\t\t\t\taria-label=\"New post\"\n\t\t\t\t\t\t\t\t\t\tplaceholder=\"Share a process shot or an upcoming drop\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Row gap={1} align=\"center\" justify=\"between\">\n\t\t\t\t\t\t\t\t\t\t<Row gap={0.5}>\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Add images\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"image-stack\" />\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Attach an edition\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"gem\" />\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t<Button variant=\"secondary\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\tPost\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t</Card>\n\t\t\t\t\t<Tabs defaultValue=\"for-you\">\n\t\t\t\t\t\t<TabsList className=\"flex\">\n\t\t\t\t\t\t\t<TabsTrigger value=\"for-you\">For you</TabsTrigger>\n\t\t\t\t\t\t\t<TabsTrigger value=\"following\">Following</TabsTrigger>\n\t\t\t\t\t\t</TabsList>\n\t\t\t\t\t\t<TabsPanel value=\"for-you\">\n\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t{posts.map(renderPost)}\n\t\t\t\t\t\t\t\t{/* The next page, in the shape it will land in, while it fetches. */}\n\t\t\t\t\t\t\t\t<Card aria-busy=\"true\">\n\t\t\t\t\t\t\t\t\t<CardContent className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t\t\t\t\t<Row gap={1.5} align=\"center\">\n\t\t\t\t\t\t\t\t\t\t\t<Skeleton className=\"size-10 rounded-full\" />\n\t\t\t\t\t\t\t\t\t\t\t<Stack gap={1}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Skeleton className=\"h-3 w-32\" />\n\t\t\t\t\t\t\t\t\t\t\t\t<Skeleton className=\"h-3 w-20\" />\n\t\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t<Skeleton className=\"h-3 w-full\" />\n\t\t\t\t\t\t\t\t\t\t<Skeleton className=\"h-3 w-2/3\" />\n\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t<Button variant=\"secondary\" loading loadingLabel=\"Loading posts\">\n\t\t\t\t\t\t\t\t\tLoad more posts\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t</TabsPanel>\n\t\t\t\t\t\t<TabsPanel value=\"following\">\n\t\t\t\t\t\t\t<Stack gap={3}>{posts.filter((post) => post.following).map(renderPost)}</Stack>\n\t\t\t\t\t\t</TabsPanel>\n\t\t\t\t\t</Tabs>\n\t\t\t\t</Stack>\n\t\t\t\t<aside aria-label=\"Suggestions\" className=\"sticky top-6 hidden w-80 shrink-0 lg:block\">\n\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Who to follow</CardTitle>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t{suggestions.map((person) => (\n\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\tkey={person.id}\n\t\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" fallback={person.initials} alt={person.name} />}\n\t\t\t\t\t\t\t\t\t\ttitle={person.name}\n\t\t\t\t\t\t\t\t\t\tsubtitle={person.handle}\n\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"secondary\" size=\"sm\" aria-label={`Follow ${person.name}`}>\n\t\t\t\t\t\t\t\t\t\t\t\tFollow\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Upcoming drops</CardTitle>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t{upcoming.map((drop) => (\n\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\tkey={drop.id}\n\t\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" fallback={drop.initials} alt={drop.artist} />}\n\t\t\t\t\t\t\t\t\t\ttitle={drop.title}\n\t\t\t\t\t\t\t\t\t\tsubtitle={`${drop.artist} · ${drop.date}`}\n\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"icon-sm\" aria-label={`Remind me about ${drop.title}`}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"bell\" />\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</Stack>\n\t\t\t\t</aside>\n\t\t\t</Row>\n\t\t</Page>\n\t)\n}"
20614
+ "code": "type Post = {\n\tid: string\n\tauthor: string\n\thandle: string\n\tinitials: string\n\ttime: string\n\tfollowing: boolean\n\tbody: string\n\tmedia?: { src: string; alt: string }\n\tevent?: { title: string; price: string; registered: number; capacity: number }\n\tlikes: number\n\tcomments: number\n}\n\nconst posts: Post[] = [\n\t{\n\t\tid: \"p_01\",\n\t\tauthor: \"Ada Lovelace\",\n\t\thandle: \"@ada\",\n\t\tinitials: \"AL\",\n\t\ttime: \"2h\",\n\t\tfollowing: true,\n\t\tbody: \"Registration for the spring workshop is open. Two days of plotter drawing in a small group. See you there.\",\n\t\tmedia: { src: \"/media/edition-cover.svg\", alt: \"Workshop poster, a gold-leaf study in overlapping arcs\" },\n\t\tevent: { title: \"Spring plotter workshop\", price: \"$120\", registered: 28, capacity: 40 },\n\t\tlikes: 128,\n\t\tcomments: 24,\n\t},\n\t{\n\t\tid: \"p_02\",\n\t\tauthor: \"Grace Hopper\",\n\t\thandle: \"@grace\",\n\t\tinitials: \"GH\",\n\t\ttime: \"5h\",\n\t\tfollowing: true,\n\t\tbody: \"Process shot from this week. Compilers and copper ink — the plotter ran for eleven hours on this one.\",\n\t\tmedia: { src: \"/media/process-shot.svg\", alt: \"Work-in-progress plotter drawing in copper ink\" },\n\t\tlikes: 86,\n\t\tcomments: 9,\n\t},\n\t{\n\t\tid: \"p_03\",\n\t\tauthor: \"Nina Simone\",\n\t\thandle: \"@nina\",\n\t\tinitials: \"NS\",\n\t\ttime: \"8h\",\n\t\tfollowing: false,\n\t\tbody: \"Thanks to everyone at the Dusk listening session — the full recording is now in your library. Headphones on.\",\n\t\tlikes: 214,\n\t\tcomments: 31,\n\t},\n]\n\nconst suggestions = [\n\t{ id: \"u_01\", name: \"Mary Shelley\", handle: \"@mary\", initials: \"MS\" },\n\t{ id: \"u_02\", name: \"Nina Simone\", handle: \"@nina\", initials: \"NS\" },\n]\n\nconst upcoming = [\n\t{ id: \"e_01\", title: \"Quiet Harbor talk\", host: \"Mary Shelley\", initials: \"MS\", date: \"Sat, Oct 12\" },\n\t{ id: \"e_02\", title: \"Dusk, live\", host: \"Nina Simone\", initials: \"NS\", date: \"Tue, Oct 15\" },\n]\n\nconst renderPost = (post: Post) => (\n\t<article key={post.id}>\n\t\t<Card>\n\t\t\t<CardList>\n\t\t\t\t<Entity\n\t\t\t\t\tleading={<Avatar variant=\"art\" fallback={post.initials} alt={post.author} />}\n\t\t\t\t\ttitle={post.author}\n\t\t\t\t\tsubtitle={`${post.handle} · ${post.time}`}\n\t\t\t\t\taction={\n\t\t\t\t\t\t<OverflowMenu label={`Post options for ${post.author}`} size=\"icon-sm\">\n\t\t\t\t\t\t\t<MenuItem>Copy link</MenuItem>\n\t\t\t\t\t\t\t<MenuItem>Mute {post.handle}</MenuItem>\n\t\t\t\t\t\t\t<MenuSeparator />\n\t\t\t\t\t\t\t<MenuItem variant=\"destructive\">Report post</MenuItem>\n\t\t\t\t\t\t</OverflowMenu>\n\t\t\t\t\t}\n\t\t\t\t/>\n\t\t\t</CardList>\n\t\t\t<CardContent className=\"flex flex-col gap-4 pt-0\">\n\t\t\t\t<p className=\"text-body-md text-foreground\">{post.body}</p>\n\t\t\t\t{post.media ? (\n\t\t\t\t\t<AspectBox ratio={16 / 9} className=\"overflow-hidden rounded-concentric-lg/6 bg-muted\">\n\t\t\t\t\t\t<img src={post.media.src} alt={post.media.alt} className=\"size-full object-cover\" />\n\t\t\t\t\t\t{post.event ? (\n\t\t\t\t\t\t\t<div className=\"absolute start-3 top-3\">\n\t\t\t\t\t\t\t\t<Badge appearance=\"overlay\" size=\"sm\" dot>\n\t\t\t\t\t\t\t\t\tOpen now\n\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</AspectBox>\n\t\t\t\t) : null}\n\t\t\t\t{post.event ? (\n\t\t\t\t\t<Stack gap={1.5}>\n\t\t\t\t\t\t<Row gap={2} align=\"center\" justify=\"between\">\n\t\t\t\t\t\t\t<div className=\"min-w-0\">\n\t\t\t\t\t\t\t\t<p className=\"text-title-md text-foreground\">{post.event.title}</p>\n\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t{`${post.event.price} · ${post.event.capacity} seats`}\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<Button>{`Register for ${post.event.price}`}</Button>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t<Meter\n\t\t\t\t\t\t\tvalue={post.event.registered}\n\t\t\t\t\t\t\tmax={post.event.capacity}\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tgetAriaValueText={(_, value) => `${value} of ${post.event?.capacity} seats taken`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Row gap={2} align=\"center\" justify=\"between\" className=\"mb-2\">\n\t\t\t\t\t\t\t\t<MeterLabel>{`${post.event.registered} registered`}</MeterLabel>\n\t\t\t\t\t\t\t\t<MeterValue>{(_, value) => `${(post.event?.capacity ?? 0) - value} left`}</MeterValue>\n\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t<MeterTrack />\n\t\t\t\t\t\t</Meter>\n\t\t\t\t\t</Stack>\n\t\t\t\t) : null}\n\t\t\t\t{/* bleed=\"start\" pulls the first button out by exactly its own\n\t\t\t\t padding, so its icon lands on the text edge above it. */}\n\t\t\t\t<Row gap={0.5}>\n\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" bleed=\"start\" aria-label={`Like, ${post.likes} likes`}>\n\t\t\t\t\t\t<Icon name=\"heart\" />\n\t\t\t\t\t\t{post.likes}\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" aria-label={`Comment, ${post.comments} comments`}>\n\t\t\t\t\t\t<Icon name=\"comment\" />\n\t\t\t\t\t\t{post.comments}\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" aria-label=\"Share\">\n\t\t\t\t\t\t<Icon name=\"share\" />\n\t\t\t\t\t</Button>\n\t\t\t\t</Row>\n\t\t\t</CardContent>\n\t\t</Card>\n\t</article>\n)\n\nexport function FeedPage() {\n\treturn (\n\t\t<Page as=\"main\" max=\"wide\" gap={4} className=\"py-6\">\n\t\t\t<Row gap={4} align=\"start\" justify=\"center\">\n\t\t\t\t<Stack gap={3} className=\"min-w-0 max-w-(--region-feed) flex-1\">\n\t\t\t\t\t<PageHeader title=\"Home\" />\n\t\t\t\t\t<Card>\n\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t<Row gap={1.5} align=\"start\">\n\t\t\t\t\t\t\t\t<Avatar variant=\"art\" fallback=\"AL\" alt=\"Ada Lovelace\" />\n\t\t\t\t\t\t\t\t<Stack gap={1.5} className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t\t\t<Textarea\n\t\t\t\t\t\t\t\t\t\tautoResize\n\t\t\t\t\t\t\t\t\t\trows={2}\n\t\t\t\t\t\t\t\t\t\taria-label=\"New post\"\n\t\t\t\t\t\t\t\t\t\tplaceholder=\"Share a process shot or an upcoming event\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Row gap={1} align=\"center\" justify=\"between\">\n\t\t\t\t\t\t\t\t\t\t<Row gap={0.5}>\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Add images\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"image-stack\" />\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Add an event\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"calendar\" />\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t<Button variant=\"secondary\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\tPost\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t</Card>\n\t\t\t\t\t<Tabs defaultValue=\"for-you\">\n\t\t\t\t\t\t<TabsList className=\"flex\">\n\t\t\t\t\t\t\t<TabsTrigger value=\"for-you\">For you</TabsTrigger>\n\t\t\t\t\t\t\t<TabsTrigger value=\"following\">Following</TabsTrigger>\n\t\t\t\t\t\t</TabsList>\n\t\t\t\t\t\t<TabsPanel value=\"for-you\">\n\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t{posts.map(renderPost)}\n\t\t\t\t\t\t\t\t{/* The next page, in the shape it will land in, while it fetches. */}\n\t\t\t\t\t\t\t\t<Card aria-busy=\"true\">\n\t\t\t\t\t\t\t\t\t<CardContent className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t\t\t\t\t<Row gap={1.5} align=\"center\">\n\t\t\t\t\t\t\t\t\t\t\t<Skeleton className=\"size-10 rounded-full\" />\n\t\t\t\t\t\t\t\t\t\t\t<Stack gap={1}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Skeleton className=\"h-3 w-32\" />\n\t\t\t\t\t\t\t\t\t\t\t\t<Skeleton className=\"h-3 w-20\" />\n\t\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t<Skeleton className=\"h-3 w-full\" />\n\t\t\t\t\t\t\t\t\t\t<Skeleton className=\"h-3 w-2/3\" />\n\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t<Button variant=\"secondary\" loading loadingLabel=\"Loading posts\">\n\t\t\t\t\t\t\t\t\tLoad more posts\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t</TabsPanel>\n\t\t\t\t\t\t<TabsPanel value=\"following\">\n\t\t\t\t\t\t\t<Stack gap={3}>{posts.filter((post) => post.following).map(renderPost)}</Stack>\n\t\t\t\t\t\t</TabsPanel>\n\t\t\t\t\t</Tabs>\n\t\t\t\t</Stack>\n\t\t\t\t<aside aria-label=\"Suggestions\" className=\"sticky top-6 hidden w-80 shrink-0 lg:block\">\n\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Who to follow</CardTitle>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t{suggestions.map((person) => (\n\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\tkey={person.id}\n\t\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" fallback={person.initials} alt={person.name} />}\n\t\t\t\t\t\t\t\t\t\ttitle={person.name}\n\t\t\t\t\t\t\t\t\t\tsubtitle={person.handle}\n\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"secondary\" size=\"sm\" aria-label={`Follow ${person.name}`}>\n\t\t\t\t\t\t\t\t\t\t\t\tFollow\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Upcoming events</CardTitle>\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t{upcoming.map((event) => (\n\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\tkey={event.id}\n\t\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" fallback={event.initials} alt={event.host} />}\n\t\t\t\t\t\t\t\t\t\ttitle={event.title}\n\t\t\t\t\t\t\t\t\t\tsubtitle={`${event.host} · ${event.date}`}\n\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"icon-sm\" aria-label={`Remind me about ${event.title}`}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"bell\" />\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</Stack>\n\t\t\t\t</aside>\n\t\t\t</Row>\n\t\t</Page>\n\t)\n}"
20482
20615
  },
20483
20616
  {
20484
20617
  "name": "LandingPage",
@@ -20503,7 +20636,7 @@
20503
20636
  "CTA buttons are real links with visible text, with no icon-only CTAs. Footer links keep a 36px minimum row height for touch."
20504
20637
  ],
20505
20638
  "imports": "import { Avatar, Badge, Browser, Bubble, Button, Card, CardAction, CardContent, CardDescription, CardHeader, CardList, CardTitle, ChatComposer, Code, CodeBlock, Entity, Grid, Icon, Message, Meter, MeterLabel, MeterTrack, MeterValue, PageHeader, Region, Reveal, Row, Stack, Tabs, TabsList, TabsPanel, TabsTrigger } from \"@sableui/react\"",
20506
- "code": "const navLinks = [\n\t{ label: \"Docs\", href: \"/docs\" },\n\t{ label: \"Components\", href: \"/components\" },\n\t{ label: \"Patterns\", href: \"/patterns\" },\n\t{ label: \"Themes\", href: \"/themes\" },\n]\n\nconst deploys = [\n\t{\n\t\tinitials: \"MO\",\n\t\tname: \"Maya Ortiz\",\n\t\ttitle: \"Add usage-based billing\",\n\t\tmeta: \"main · a3f9c21 · 2m ago\",\n\t\tstatus: \"Building\",\n\t\ttone: \"info\" as const,\n\t\tpulse: true,\n\t},\n\t{\n\t\tinitials: \"SR\",\n\t\tname: \"Sam Reyes\",\n\t\ttitle: \"Fix search ranking for long queries\",\n\t\tmeta: \"main · 81be04d · 18m ago\",\n\t\tstatus: \"Ready\",\n\t\ttone: \"success\" as const,\n\t\tpulse: false,\n\t},\n\t{\n\t\tinitials: \"JL\",\n\t\tname: \"Jun Lee\",\n\t\ttitle: \"Rotate webhook signing keys\",\n\t\tmeta: \"main · 5c7e9aa · 1h ago\",\n\t\tstatus: \"Ready\",\n\t\ttone: \"success\" as const,\n\t\tpulse: false,\n\t},\n]\n\nconst features = [\n\t{\n\t\ticon: \"component\" as const,\n\t\ttitle: \"Behavior built in\",\n\t\tbody: \"Focus management, keyboard navigation, and ARIA come from Base UI primitives under every control.\",\n\t},\n\t{\n\t\ticon: \"eyedropper\" as const,\n\t\ttitle: \"Semantic tokens end to end\",\n\t\tbody: \"Re-theme the whole product, light and dark, from one preset without touching a component.\",\n\t},\n\t{\n\t\ticon: \"layout\" as const,\n\t\ttitle: \"Pages, not just parts\",\n\t\tbody: \"Landing, dashboard, settings, auth, and list-detail shells to copy and adapt.\",\n\t},\n\t{\n\t\ticon: \"book\" as const,\n\t\ttitle: \"A contract agents can read\",\n\t\tbody: \"registry.json, llms.txt, and a CLI hand agents the exact props, examples, and rules.\",\n\t},\n\t{\n\t\ticon: \"circle-check\" as const,\n\t\ttitle: \"Lint that knows the design\",\n\t\tbody: \"sable-lint flags raw colors, hand-built controls, and motion that ignores reduced motion.\",\n\t},\n\t{\n\t\ticon: \"play\" as const,\n\t\ttitle: \"Motion with an off switch\",\n\t\tbody: \"Named recipes run on one scale variable, so reduced motion stops every transition at once.\",\n\t},\n]\n\nconst steps = [\n\t{\n\t\ttitle: \"Install the package\",\n\t\tbody: \"One dependency. React 19 and Tailwind v4 are the only peers.\",\n\t},\n\t{\n\t\ttitle: \"Import the styles\",\n\t\tbody: \"Tailwind first, then Sable's tokens, then a source glob over the package.\",\n\t},\n\t{\n\t\ttitle: \"Compose your screens\",\n\t\tbody: \"Build from components and layout primitives. Your theme does the rest.\",\n\t},\n]\n\nconst stylesCode = `@import \"tailwindcss\";\n@import \"@sableui/react/styles.css\";\n@source \"../node_modules/@sableui/react/dist/**/*.{js,mjs}\";`\n\n// design-lint-disable-next-line self-import -- a consumer code sample displayed on the page, not an import\nconst composeCode = `import { Button, Card, CardContent, CardHeader, CardTitle } from \"@sableui/react\"\n\nexport function DeployCard() {\n return (\n <Card>\n <CardHeader>\n <CardTitle>Production</CardTitle>\n </CardHeader>\n <CardContent>\n <Button>Deploy</Button>\n </CardContent>\n </Card>\n )\n}`\n\nconst terminalCode = `$ npx sable search \"confirm before deleting\"\ncomponent DestructiveActionModal\ncomponent Dialog\n\n$ npx sable component DestructiveActionModal --props\nprops, a11y contract, composed examples\n\n$ npx sable lint src\n0 findings in 42 files`\n\nconst agentPoints = [\n\t\"Search ranks components, patterns, tokens, and docs for a plain-language need.\",\n\t\"Every component ships its props, accessibility contract, and composed examples.\",\n\t\"The same lint runs in the editor, in CI, and inside the agent's own loop.\",\n]\n\nconst footerGroups = [\n\t{\n\t\ttitle: \"Product\",\n\t\tlinks: [\n\t\t\t{ label: \"Components\", href: \"/components\" },\n\t\t\t{ label: \"Patterns\", href: \"/patterns\" },\n\t\t\t{ label: \"Themes\", href: \"/themes\" },\n\t\t\t{ label: \"Canvas\", href: \"/canvas\" },\n\t\t],\n\t},\n\t{\n\t\ttitle: \"Resources\",\n\t\tlinks: [\n\t\t\t{ label: \"Documentation\", href: \"/docs\" },\n\t\t\t{ label: \"Changelog\", href: \"/changelog\" },\n\t\t\t{ label: \"llms.txt\", href: \"/llms.txt\" },\n\t\t\t{ label: \"registry.json\", href: \"/registry.json\" },\n\t\t],\n\t},\n\t{\n\t\ttitle: \"Community\",\n\t\tlinks: [\n\t\t\t{ label: \"GitHub\", href: \"/github\" },\n\t\t\t{ label: \"npm\", href: \"/npm\" },\n\t\t\t{ label: \"Discussions\", href: \"/discussions\" },\n\t\t],\n\t},\n]\n\nexport function LandingPage() {\n\treturn (\n\t\t<div className=\"min-h-dvh bg-background text-foreground\">\n\t\t\t<header className=\"border-b border-border\">\n\t\t\t\t<Region as=\"nav\" aria-label=\"Main\" max=\"wide\" className=\"grid h-16 grid-cols-[1fr_auto_1fr] items-center\">\n\t\t\t\t\t<a href=\"/\" className=\"justify-self-start text-title-lg motion-interactive hover:text-muted-foreground\">\n\t\t\t\t\t\tSable\n\t\t\t\t\t</a>\n\t\t\t\t\t<div className=\"justify-self-center max-md:hidden\">\n\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t{navLinks.map((link) => (\n\t\t\t\t\t\t\t\t<Button key={link.label} variant=\"ghost\" size=\"sm\" render={<a href={link.href} />}>\n\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t</div>\n\t\t\t\t\t<Row gap={1} className=\"col-start-3 justify-self-end\">\n\t\t\t\t\t\t<div className=\"max-sm:hidden\">\n\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" render={<a href=\"/github\" />}>\n\t\t\t\t\t\t\t\tGitHub\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<Button size=\"sm\" render={<a href=\"/docs/getting-started\" />}>\n\t\t\t\t\t\t\tGet started\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</Row>\n\t\t\t\t</Region>\n\t\t\t</header>\n\n\t\t\t<main>\n\t\t\t\t<Region as=\"section\" max=\"wide\" className=\"pb-16 pt-16 sm:pt-24\">\n\t\t\t\t\t<Stack gap={8} align=\"center\">\n\t\t\t\t\t\t<Stack gap={4} align=\"center\" className=\"text-center\">\n\t\t\t\t\t\t\t<Badge appearance=\"outline\" size=\"sm\" icon={<Icon name=\"sparkles\" />}>\n\t\t\t\t\t\t\t\tOpen source · MIT licensed\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t<h1 className=\"max-w-4xl text-balance text-display-lg sm:text-display-2xl\">\n\t\t\t\t\t\t\t\tProduct UI your agents can't get wrong\n\t\t\t\t\t\t\t</h1>\n\t\t\t\t\t\t\t<p className=\"max-w-(--measure-prose) text-pretty text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\tSable is a themeable React component library with enforced tokens,\n\t\t\t\t\t\t\t\ttypography, and motion, documented for coding agents as carefully\n\t\t\t\t\t\t\t\tas for people.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<Row gap={2} justify=\"center\" wrap className=\"pt-2\">\n\t\t\t\t\t\t\t\t<Button size=\"cta\" render={<a href=\"/docs/getting-started\" />}>\n\t\t\t\t\t\t\t\t\tGet started\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t<Button size=\"cta\" variant=\"outline\" render={<a href=\"/components\" />}>\n\t\t\t\t\t\t\t\t\tBrowse components\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t<div className=\"w-full max-w-sm\">\n\t\t\t\t\t\t\t<CodeBlock prompt=\"$\" code=\"pnpm add @sableui/react\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Stack>\n\t\t\t\t</Region>\n\n\t\t\t\t<Region as=\"section\" aria-label=\"Product preview\" max=\"wide\" className=\"pb-20 sm:pb-28\">\n\t\t\t\t\t<figure>\n\t\t\t\t\t\t<Browser url=\"atlas.example/deploys\" className=\"shadow-xl\">\n\t\t\t\t\t\t\t{/* A picture of software, not live UI: inert keeps its controls\n\t\t\t\t\t\t\t out of the tab order and the accessibility tree. */}\n\t\t\t\t\t\t\t<div inert className=\"grid gap-4 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n\t\t\t\t\t\t\t\t<Stack gap={4} className=\"min-w-0\">\n\t\t\t\t\t\t\t\t\t<PageHeader\n\t\t\t\t\t\t\t\t\t\ttitleAs=\"h2\"\n\t\t\t\t\t\t\t\t\t\ttitle=\"Deployments\"\n\t\t\t\t\t\t\t\t\t\tdescription=\"atlas-web · production\"\n\t\t\t\t\t\t\t\t\t\tactions={\n\t\t\t\t\t\t\t\t\t\t\t<Row gap={2}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tView logs\n\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t<Button size=\"sm\" iconStart={<Icon name=\"upload\" />}>\n\t\t\t\t\t\t\t\t\t\t\t\t\tDeploy\n\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Grid cols={{ base: 2, md: 3 }} gap={3}>\n\t\t\t\t\t\t\t\t\t\t<Card density=\"compact\">\n\t\t\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardTitle>Uptime</CardTitle>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardDescription>Last 30 days</CardDescription>\n\t\t\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-heading-3\">99.98%</p>\n\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t\t<Card density=\"compact\">\n\t\t\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardTitle>p95 latency</CardTitle>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardDescription>Edge, all regions</CardDescription>\n\t\t\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-heading-3\">182 ms</p>\n\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t\t<Card density=\"compact\" className=\"max-md:hidden\">\n\t\t\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardTitle>Build minutes</CardTitle>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardDescription>Team plan</CardDescription>\n\t\t\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t\t\t\t<Meter value={64} tone=\"standard\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Row justify=\"between\" wrap className=\"mb-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<MeterLabel>Used</MeterLabel>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<MeterValue>{(_, value) => `${(value * 30).toLocaleString(\"en-US\")} of 3,000`}</MeterValue>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<MeterTrack />\n\t\t\t\t\t\t\t\t\t\t\t\t</Meter>\n\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t<CardTitle>Recent deploys</CardTitle>\n\t\t\t\t\t\t\t\t\t\t\t<CardDescription>Production branch</CardDescription>\n\t\t\t\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t\t\t\t<Badge appearance=\"plain\" tone=\"success\" dot>\n\t\t\t\t\t\t\t\t\t\t\t\t\tHealthy\n\t\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t\t\t\t{deploys.map((deploy) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={deploy.meta}\n\t\t\t\t\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback={deploy.initials} alt={deploy.name} />}\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={deploy.title}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsubtitle={deploy.meta}\n\t\t\t\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Badge appearance=\"plain\" tone={deploy.tone} dot pulse={deploy.pulse}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{deploy.status}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t<Card className=\"max-lg:hidden\">\n\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t<CardTitle>Release assistant</CardTitle>\n\t\t\t\t\t\t\t\t\t\t<CardDescription>Summaries from today's deploys</CardDescription>\n\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t<CardContent className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t\t\t\t\t<Message align=\"end\">\n\t\t\t\t\t\t\t\t\t\t\t<Bubble variant=\"primary\" align=\"end\">\n\t\t\t\t\t\t\t\t\t\t\t\tWhat shipped to production today?\n\t\t\t\t\t\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t\t\t\t\t\t</Message>\n\t\t\t\t\t\t\t\t\t\t<Message>\n\t\t\t\t\t\t\t\t\t\t\t<Bubble>\n\t\t\t\t\t\t\t\t\t\t\t\tThree deploys. Search ranking is fixed, webhook keys are\n\t\t\t\t\t\t\t\t\t\t\t\trotated, and usage-based billing is building now.\n\t\t\t\t\t\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t\t\t\t\t\t</Message>\n\t\t\t\t\t\t\t\t\t\t<ChatComposer placeholder=\"Ask about a deploy\" />\n\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</Browser>\n\t\t\t\t\t\t<figcaption className=\"mt-4 text-center text-body-sm text-muted-foreground\">\n\t\t\t\t\t\t\tEvery surface in this preview is a Sable component, styled by the active theme.\n\t\t\t\t\t\t</figcaption>\n\t\t\t\t\t</figure>\n\t\t\t\t</Region>\n\n\t\t\t\t<section aria-labelledby=\"landing-features\" className=\"border-y border-border bg-muted/40 py-20 sm:py-28\">\n\t\t\t\t\t<Region max=\"wide\">\n\t\t\t\t\t\t<Stack gap={10}>\n\t\t\t\t\t\t\t<Stack gap={3} className=\"max-w-2xl\">\n\t\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">Why Sable</p>\n\t\t\t\t\t\t\t\t<h2 id=\"landing-features\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\tA design system with the rules written down\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t<p className=\"text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tThe decisions a senior designer would make are encoded once, so every\n\t\t\t\t\t\t\t\t\tscreen your team or your agent builds starts from them.\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t<Reveal>\n\t\t\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 2, lg: 3 }} gap={4}>\n\t\t\t\t\t\t\t\t\t{features.map((feature) => (\n\t\t\t\t\t\t\t\t\t\t<Card key={feature.title} render={<article />} className=\"h-full\">\n\t\t\t\t\t\t\t\t\t\t\t<CardContent className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"grid size-10 place-items-center rounded-md sable-well\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name={feature.icon} className=\"text-xl\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t<h3 className=\"pt-2 text-title-lg\">{feature.title}</h3>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">{feature.body}</p>\n\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t</Reveal>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t</Region>\n\t\t\t\t</section>\n\n\t\t\t\t<Region as=\"section\" aria-labelledby=\"landing-setup\" max=\"wide\" className=\"py-20 sm:py-28\">\n\t\t\t\t\t<div className=\"grid items-start gap-10 lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:gap-16\">\n\t\t\t\t\t\t<Stack gap={8}>\n\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">Setup</p>\n\t\t\t\t\t\t\t\t<h2 id=\"landing-setup\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\tFrom install to first screen in three steps\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t<ol className=\"flex flex-col gap-6\">\n\t\t\t\t\t\t\t\t{steps.map((step, index) => (\n\t\t\t\t\t\t\t\t\t<li key={step.title} className=\"grid grid-cols-[auto_1fr] gap-4\">\n\t\t\t\t\t\t\t\t\t\t<span className=\"pt-0.5 text-mono-md text-muted-foreground\">0{index + 1}</span>\n\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t<h3 className=\"text-title-lg\">{step.title}</h3>\n\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-1 text-body-md text-muted-foreground\">{step.body}</p>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</ol>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t<Reveal className=\"min-w-0\">\n\t\t\t\t\t\t\t<Tabs defaultValue=\"compose\">\n\t\t\t\t\t\t\t\t<TabsList>\n\t\t\t\t\t\t\t\t\t<TabsTrigger value=\"install\">Install</TabsTrigger>\n\t\t\t\t\t\t\t\t\t<TabsTrigger value=\"styles\">Styles</TabsTrigger>\n\t\t\t\t\t\t\t\t\t<TabsTrigger value=\"compose\">Compose</TabsTrigger>\n\t\t\t\t\t\t\t\t</TabsList>\n\t\t\t\t\t\t\t\t<TabsPanel value=\"install\" className=\"pt-4\">\n\t\t\t\t\t\t\t\t\t<CodeBlock prompt=\"$\" code=\"pnpm add @sableui/react\" />\n\t\t\t\t\t\t\t\t</TabsPanel>\n\t\t\t\t\t\t\t\t<TabsPanel value=\"styles\" className=\"pt-4\">\n\t\t\t\t\t\t\t\t\t<CodeBlock filename=\"app.css\" code={stylesCode} />\n\t\t\t\t\t\t\t\t</TabsPanel>\n\t\t\t\t\t\t\t\t<TabsPanel value=\"compose\" className=\"pt-4\">\n\t\t\t\t\t\t\t\t\t<CodeBlock filename=\"deploy-card.tsx\" code={composeCode} />\n\t\t\t\t\t\t\t\t</TabsPanel>\n\t\t\t\t\t\t\t</Tabs>\n\t\t\t\t\t\t</Reveal>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\n\t\t\t\t<section aria-labelledby=\"landing-agents\" className=\"border-t border-border py-20 sm:py-28\">\n\t\t\t\t\t<Region max=\"wide\">\n\t\t\t\t\t\t<div className=\"grid items-center gap-10 lg:grid-cols-2 lg:gap-16\">\n\t\t\t\t\t\t\t<Stack gap={5}>\n\t\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">Built for agents</p>\n\t\t\t\t\t\t\t\t\t<h2 id=\"landing-agents\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\t\tYour agent reads the same contract you do\n\t\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t\t<p className=\"text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tPoint Claude Code, Codex, or Cursor at the installed package. The{\" \"}\n\t\t\t\t\t\t\t\t\t\t<Code>sable</Code> CLI answers what to use, how to use it, and whether\n\t\t\t\t\t\t\t\t\t\tthe result holds up.\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t<ul className=\"sable-list\">\n\t\t\t\t\t\t\t\t\t{agentPoints.map((point) => (\n\t\t\t\t\t\t\t\t\t\t<li key={point}>{point}</li>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t<Reveal className=\"min-w-0\">\n\t\t\t\t\t\t\t\t<CodeBlock filename=\"Terminal\" code={terminalCode} highlight={false} />\n\t\t\t\t\t\t\t</Reveal>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Region>\n\t\t\t\t</section>\n\n\t\t\t\t<Region as=\"section\" aria-labelledby=\"landing-cta\" max=\"wide\" className=\"pb-20 sm:pb-28\">\n\t\t\t\t\t<Reveal>\n\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t<Stack gap={4} align=\"center\" className=\"py-8 text-center sm:py-12\">\n\t\t\t\t\t\t\t\t\t<h2 id=\"landing-cta\" className=\"max-w-2xl text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\t\tBuild the product, not another component library\n\t\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t\t<p className=\"max-w-(--measure-prose) text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tInstall Sable, pick a preset, and ship the first screen today.\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t<Row gap={2} justify=\"center\" wrap className=\"pt-2\">\n\t\t\t\t\t\t\t\t\t\t<Button size=\"cta\" render={<a href=\"/docs/getting-started\" />}>\n\t\t\t\t\t\t\t\t\t\t\tGet started\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t<Button size=\"cta\" variant=\"outline\" render={<a href=\"/github\" />}>\n\t\t\t\t\t\t\t\t\t\t\tStar on GitHub\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</Reveal>\n\t\t\t\t</Region>\n\t\t\t</main>\n\n\t\t\t<footer className=\"border-t border-border\">\n\t\t\t\t<Region max=\"wide\" className=\"py-12\">\n\t\t\t\t\t<div className=\"grid gap-10 md:grid-cols-[minmax(0,2fr)_minmax(0,3fr)]\">\n\t\t\t\t\t\t<Stack gap={3} className=\"max-w-xs\">\n\t\t\t\t\t\t\t<a href=\"/\" className=\"w-fit text-title-lg motion-interactive hover:text-muted-foreground\">\n\t\t\t\t\t\t\t\tSable\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\tReact components, patterns, and tokens for products built with AI agents.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t<nav aria-label=\"Footer\" className=\"grid grid-cols-2 gap-8 sm:grid-cols-3\">\n\t\t\t\t\t\t\t{footerGroups.map((group) => (\n\t\t\t\t\t\t\t\t<div key={group.title}>\n\t\t\t\t\t\t\t\t\t<h2 className=\"text-label-md text-muted-foreground\">{group.title}</h2>\n\t\t\t\t\t\t\t\t\t<ul className=\"mt-3 flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t\t\t{group.links.map((link) => (\n\t\t\t\t\t\t\t\t\t\t\t<li key={link.label}>\n\t\t\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\t\t\thref={link.href}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"flex min-h-9 items-center text-body-sm motion-interactive hover:text-muted-foreground\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</nav>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"mt-12 border-t border-border pt-6\">\n\t\t\t\t\t\t<p className=\"text-caption text-muted-foreground\">© 2026 Sable. Released under the MIT license.</p>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\t\t\t</footer>\n\t\t</div>\n\t)\n}"
20639
+ "code": "const navLinks = [\n\t{ label: \"Docs\", href: \"/docs\" },\n\t{ label: \"Components\", href: \"/components\" },\n\t{ label: \"Patterns\", href: \"/patterns\" },\n\t{ label: \"Themes\", href: \"/themes\" },\n]\n\nconst deploys = [\n\t{\n\t\tinitials: \"MO\",\n\t\tname: \"Maya Ortiz\",\n\t\ttitle: \"Add usage-based billing\",\n\t\tmeta: \"main · a3f9c21 · 2m ago\",\n\t\tstatus: \"Building\",\n\t\ttone: \"info\" as const,\n\t\tpulse: true,\n\t},\n\t{\n\t\tinitials: \"SR\",\n\t\tname: \"Sam Reyes\",\n\t\ttitle: \"Fix search ranking for long queries\",\n\t\tmeta: \"main · 81be04d · 18m ago\",\n\t\tstatus: \"Ready\",\n\t\ttone: \"success\" as const,\n\t\tpulse: false,\n\t},\n\t{\n\t\tinitials: \"JL\",\n\t\tname: \"Jun Lee\",\n\t\ttitle: \"Rotate webhook signing keys\",\n\t\tmeta: \"main · 5c7e9aa · 1h ago\",\n\t\tstatus: \"Ready\",\n\t\ttone: \"success\" as const,\n\t\tpulse: false,\n\t},\n]\n\nconst features = [\n\t{\n\t\ticon: \"component\" as const,\n\t\ttitle: \"Behavior built in\",\n\t\tbody: \"Focus management, keyboard navigation, and ARIA come from Base UI primitives under every control.\",\n\t},\n\t{\n\t\ticon: \"eyedropper\" as const,\n\t\ttitle: \"Semantic tokens end to end\",\n\t\tbody: \"Re-theme the whole product, light and dark, from one preset without touching a component.\",\n\t},\n\t{\n\t\ticon: \"layout\" as const,\n\t\ttitle: \"Pages, not just parts\",\n\t\tbody: \"Landing, dashboard, settings, auth, and list-detail shells to copy and adapt.\",\n\t},\n\t{\n\t\ticon: \"book\" as const,\n\t\ttitle: \"A contract agents can read\",\n\t\tbody: \"registry.json, llms.txt, and a CLI hand agents the exact props, examples, and rules.\",\n\t},\n\t{\n\t\ticon: \"circle-check\" as const,\n\t\ttitle: \"Lint that knows the design\",\n\t\tbody: \"sable-lint flags raw colors, hand-built controls, and motion that ignores reduced motion.\",\n\t},\n\t{\n\t\ticon: \"play\" as const,\n\t\ttitle: \"Motion with an off switch\",\n\t\tbody: \"Named recipes run on one scale variable, so reduced motion stops every transition at once.\",\n\t},\n]\n\nconst steps = [\n\t{\n\t\ttitle: \"Install the package\",\n\t\tbody: \"One dependency. React 19 and Tailwind v4 are the only peers.\",\n\t},\n\t{\n\t\ttitle: \"Import the styles\",\n\t\tbody: \"Tailwind first, then Sable's tokens, then a source glob over the package.\",\n\t},\n\t{\n\t\ttitle: \"Compose your screens\",\n\t\tbody: \"Build from components and layout primitives. Your theme does the rest.\",\n\t},\n]\n\nconst stylesCode = `@import \"tailwindcss\";\n@import \"@sableui/react/styles.css\";\n@source \"../node_modules/@sableui/react/dist/**/*.{js,mjs}\";`\n\n// design-lint-disable-next-line self-import -- a consumer code sample displayed on the page, not an import\nconst composeCode = `import { Button, Card, CardContent, CardHeader, CardTitle } from \"@sableui/react\"\n\nexport function DeployCard() {\n return (\n <Card>\n <CardHeader>\n <CardTitle>Production</CardTitle>\n </CardHeader>\n <CardContent>\n <Button>Deploy</Button>\n </CardContent>\n </Card>\n )\n}`\n\nconst terminalCode = `$ npx sable search \"confirm before deleting\"\ncomponent DestructiveActionModal\ncomponent Dialog\n\n$ npx sable component DestructiveActionModal --props\nprops, a11y contract, composed examples\n\n$ npx sable lint src\n0 findings in 42 files`\n\nconst agentPoints = [\n\t\"Search ranks components, patterns, tokens, and docs for a plain-language need.\",\n\t\"Every component ships its props, accessibility contract, and composed examples.\",\n\t\"The same lint runs in the editor, in CI, and inside the agent's own loop.\",\n]\n\nconst footerGroups = [\n\t{\n\t\ttitle: \"Product\",\n\t\tlinks: [\n\t\t\t{ label: \"Components\", href: \"/components\" },\n\t\t\t{ label: \"Patterns\", href: \"/patterns\" },\n\t\t\t{ label: \"Themes\", href: \"/themes\" },\n\t\t\t{ label: \"Canvas\", href: \"/canvas\" },\n\t\t],\n\t},\n\t{\n\t\ttitle: \"Resources\",\n\t\tlinks: [\n\t\t\t{ label: \"Documentation\", href: \"/docs\" },\n\t\t\t{ label: \"Changelog\", href: \"/changelog\" },\n\t\t\t{ label: \"llms.txt\", href: \"/llms.txt\" },\n\t\t\t{ label: \"registry.json\", href: \"/registry.json\" },\n\t\t],\n\t},\n\t{\n\t\ttitle: \"Community\",\n\t\tlinks: [\n\t\t\t{ label: \"GitHub\", href: \"/github\" },\n\t\t\t{ label: \"npm\", href: \"/npm\" },\n\t\t\t{ label: \"Discussions\", href: \"/discussions\" },\n\t\t],\n\t},\n]\n\nexport function LandingPage() {\n\treturn (\n\t\t<div className=\"min-h-dvh bg-background text-foreground\">\n\t\t\t<header className=\"border-b border-border\">\n\t\t\t\t<Region as=\"nav\" aria-label=\"Main\" max=\"wide\" className=\"grid h-16 grid-cols-[1fr_auto_1fr] items-center\">\n\t\t\t\t\t<a href=\"/\" className=\"justify-self-start text-title-lg motion-interactive hover:text-muted-foreground\">\n\t\t\t\t\t\tSable\n\t\t\t\t\t</a>\n\t\t\t\t\t<div className=\"justify-self-center max-md:hidden\">\n\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t{navLinks.map((link) => (\n\t\t\t\t\t\t\t\t<Button key={link.label} variant=\"ghost\" size=\"sm\" render={<a href={link.href} />}>\n\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t</div>\n\t\t\t\t\t<Row gap={1} className=\"col-start-3 justify-self-end\">\n\t\t\t\t\t\t<div className=\"max-sm:hidden\">\n\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" render={<a href=\"/github\" />}>\n\t\t\t\t\t\t\t\tGitHub\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<Button size=\"sm\" render={<a href=\"/docs/getting-started\" />}>\n\t\t\t\t\t\t\tGet started\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</Row>\n\t\t\t\t</Region>\n\t\t\t</header>\n\n\t\t\t<main>\n\t\t\t\t<Region as=\"section\" max=\"wide\" className=\"pb-16 pt-16 sm:pt-24\">\n\t\t\t\t\t<Stack gap={8} align=\"center\">\n\t\t\t\t\t\t<Stack gap={4} align=\"center\" className=\"text-center\">\n\t\t\t\t\t\t\t<Badge appearance=\"outline\" size=\"sm\" icon={<Icon name=\"sparkles\" />}>\n\t\t\t\t\t\t\t\tOpen source · MIT licensed\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t<h1 className=\"max-w-4xl text-balance text-display-lg sm:text-display-2xl\">\n\t\t\t\t\t\t\t\tProduct UI your agents can't get wrong\n\t\t\t\t\t\t\t</h1>\n\t\t\t\t\t\t\t<p className=\"max-w-(--measure-prose) text-pretty text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\tSable is a themeable React component library with enforced tokens,\n\t\t\t\t\t\t\t\ttypography, and motion, documented for coding agents as carefully\n\t\t\t\t\t\t\t\tas for people.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t<Row gap={2} justify=\"center\" wrap className=\"pt-2\">\n\t\t\t\t\t\t\t\t<Button size=\"cta\" render={<a href=\"/docs/getting-started\" />}>\n\t\t\t\t\t\t\t\t\tGet started\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t<Button size=\"cta\" variant=\"outline\" render={<a href=\"/components\" />}>\n\t\t\t\t\t\t\t\t\tBrowse components\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t<div className=\"w-full max-w-sm\">\n\t\t\t\t\t\t\t<CodeBlock prompt=\"$\" code=\"pnpm add @sableui/react\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Stack>\n\t\t\t\t</Region>\n\n\t\t\t\t<Region as=\"section\" aria-label=\"Product preview\" max=\"wide\" className=\"pb-20 sm:pb-28\">\n\t\t\t\t\t<figure>\n\t\t\t\t\t\t<Browser url=\"atlas.example/deploys\" className=\"shadow-xl\">\n\t\t\t\t\t\t\t{/* A picture of software, not live UI: inert keeps its controls\n\t\t\t\t\t\t\t out of the tab order and the accessibility tree. */}\n\t\t\t\t\t\t\t<div inert className=\"grid gap-4 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n\t\t\t\t\t\t\t\t<Stack gap={4} className=\"min-w-0\">\n\t\t\t\t\t\t\t\t\t<PageHeader\n\t\t\t\t\t\t\t\t\t\ttitleAs=\"h2\"\n\t\t\t\t\t\t\t\t\t\ttitle=\"Deployments\"\n\t\t\t\t\t\t\t\t\t\tdescription=\"atlas-web · production\"\n\t\t\t\t\t\t\t\t\t\tactions={\n\t\t\t\t\t\t\t\t\t\t\t<Row gap={2}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tView logs\n\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t<Button size=\"sm\" iconStart={<Icon name=\"upload\" />}>\n\t\t\t\t\t\t\t\t\t\t\t\t\tDeploy\n\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Grid cols={{ base: 2, md: 3 }} gap={3}>\n\t\t\t\t\t\t\t\t\t\t<Card density=\"compact\">\n\t\t\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardTitle>Uptime</CardTitle>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardDescription>Last 30 days</CardDescription>\n\t\t\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-heading-3\">99.98%</p>\n\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t\t<Card density=\"compact\">\n\t\t\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardTitle>p95 latency</CardTitle>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardDescription>Edge, all regions</CardDescription>\n\t\t\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-heading-3\">182 ms</p>\n\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t\t<Card density=\"compact\" className=\"max-md:hidden\">\n\t\t\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardTitle>Build minutes</CardTitle>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardDescription>Team plan</CardDescription>\n\t\t\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t\t\t\t<Meter value={64} tone=\"3\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Row justify=\"between\" wrap className=\"mb-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<MeterLabel>Used</MeterLabel>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<MeterValue>{(_, value) => `${(value * 30).toLocaleString(\"en-US\")} of 3,000`}</MeterValue>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<MeterTrack />\n\t\t\t\t\t\t\t\t\t\t\t\t</Meter>\n\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t<CardTitle>Recent deploys</CardTitle>\n\t\t\t\t\t\t\t\t\t\t\t<CardDescription>Production branch</CardDescription>\n\t\t\t\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t\t\t\t<Badge appearance=\"plain\" tone=\"success\" dot>\n\t\t\t\t\t\t\t\t\t\t\t\t\tHealthy\n\t\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t\t\t\t{deploys.map((deploy) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={deploy.meta}\n\t\t\t\t\t\t\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback={deploy.initials} alt={deploy.name} />}\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={deploy.title}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsubtitle={deploy.meta}\n\t\t\t\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Badge appearance=\"plain\" tone={deploy.tone} dot pulse={deploy.pulse}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{deploy.status}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t<Card className=\"max-lg:hidden\">\n\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t<CardTitle>Release assistant</CardTitle>\n\t\t\t\t\t\t\t\t\t\t<CardDescription>Summaries from today's deploys</CardDescription>\n\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t<CardContent className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t\t\t\t\t<Message align=\"end\">\n\t\t\t\t\t\t\t\t\t\t\t<Bubble variant=\"primary\" align=\"end\">\n\t\t\t\t\t\t\t\t\t\t\t\tWhat shipped to production today?\n\t\t\t\t\t\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t\t\t\t\t\t</Message>\n\t\t\t\t\t\t\t\t\t\t<Message>\n\t\t\t\t\t\t\t\t\t\t\t<Bubble>\n\t\t\t\t\t\t\t\t\t\t\t\tThree deploys. Search ranking is fixed, webhook keys are\n\t\t\t\t\t\t\t\t\t\t\t\trotated, and usage-based billing is building now.\n\t\t\t\t\t\t\t\t\t\t\t</Bubble>\n\t\t\t\t\t\t\t\t\t\t</Message>\n\t\t\t\t\t\t\t\t\t\t<ChatComposer placeholder=\"Ask about a deploy\" />\n\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</Browser>\n\t\t\t\t\t\t<figcaption className=\"mt-4 text-center text-body-sm text-muted-foreground\">\n\t\t\t\t\t\t\tEvery surface in this preview is a Sable component, styled by the active theme.\n\t\t\t\t\t\t</figcaption>\n\t\t\t\t\t</figure>\n\t\t\t\t</Region>\n\n\t\t\t\t<section aria-labelledby=\"landing-features\" className=\"border-y border-border bg-muted/40 py-20 sm:py-28\">\n\t\t\t\t\t<Region max=\"wide\">\n\t\t\t\t\t\t<Stack gap={10}>\n\t\t\t\t\t\t\t<Stack gap={3} className=\"max-w-2xl\">\n\t\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">Why Sable</p>\n\t\t\t\t\t\t\t\t<h2 id=\"landing-features\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\tA design system with the rules written down\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t<p className=\"text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\t\tThe decisions a senior designer would make are encoded once, so every\n\t\t\t\t\t\t\t\t\tscreen your team or your agent builds starts from them.\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t<Reveal>\n\t\t\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 2, lg: 3 }} gap={4}>\n\t\t\t\t\t\t\t\t\t{features.map((feature) => (\n\t\t\t\t\t\t\t\t\t\t<Card key={feature.title} render={<article />} className=\"h-full\">\n\t\t\t\t\t\t\t\t\t\t\t<CardContent className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"grid size-10 place-items-center rounded-md sable-well\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name={feature.icon} className=\"text-xl\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t<h3 className=\"pt-2 text-title-lg\">{feature.title}</h3>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">{feature.body}</p>\n\t\t\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t</Reveal>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t</Region>\n\t\t\t\t</section>\n\n\t\t\t\t<Region as=\"section\" aria-labelledby=\"landing-setup\" max=\"wide\" className=\"py-20 sm:py-28\">\n\t\t\t\t\t<div className=\"grid items-start gap-10 lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:gap-16\">\n\t\t\t\t\t\t<Stack gap={8}>\n\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">Setup</p>\n\t\t\t\t\t\t\t\t<h2 id=\"landing-setup\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\tFrom install to first screen in three steps\n\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t<ol className=\"flex flex-col gap-6\">\n\t\t\t\t\t\t\t\t{steps.map((step, index) => (\n\t\t\t\t\t\t\t\t\t<li key={step.title} className=\"grid grid-cols-[auto_1fr] gap-4\">\n\t\t\t\t\t\t\t\t\t\t<span className=\"pt-0.5 text-mono-md text-muted-foreground\">0{index + 1}</span>\n\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t<h3 className=\"text-title-lg\">{step.title}</h3>\n\t\t\t\t\t\t\t\t\t\t\t<p className=\"mt-1 text-body-md text-muted-foreground\">{step.body}</p>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</ol>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t<Reveal className=\"min-w-0\">\n\t\t\t\t\t\t\t<Tabs defaultValue=\"compose\">\n\t\t\t\t\t\t\t\t<TabsList>\n\t\t\t\t\t\t\t\t\t<TabsTrigger value=\"install\">Install</TabsTrigger>\n\t\t\t\t\t\t\t\t\t<TabsTrigger value=\"styles\">Styles</TabsTrigger>\n\t\t\t\t\t\t\t\t\t<TabsTrigger value=\"compose\">Compose</TabsTrigger>\n\t\t\t\t\t\t\t\t</TabsList>\n\t\t\t\t\t\t\t\t<TabsPanel value=\"install\" className=\"pt-4\">\n\t\t\t\t\t\t\t\t\t<CodeBlock prompt=\"$\" code=\"pnpm add @sableui/react\" />\n\t\t\t\t\t\t\t\t</TabsPanel>\n\t\t\t\t\t\t\t\t<TabsPanel value=\"styles\" className=\"pt-4\">\n\t\t\t\t\t\t\t\t\t<CodeBlock filename=\"app.css\" code={stylesCode} />\n\t\t\t\t\t\t\t\t</TabsPanel>\n\t\t\t\t\t\t\t\t<TabsPanel value=\"compose\" className=\"pt-4\">\n\t\t\t\t\t\t\t\t\t<CodeBlock filename=\"deploy-card.tsx\" code={composeCode} />\n\t\t\t\t\t\t\t\t</TabsPanel>\n\t\t\t\t\t\t\t</Tabs>\n\t\t\t\t\t\t</Reveal>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\n\t\t\t\t<section aria-labelledby=\"landing-agents\" className=\"border-t border-border py-20 sm:py-28\">\n\t\t\t\t\t<Region max=\"wide\">\n\t\t\t\t\t\t<div className=\"grid items-center gap-10 lg:grid-cols-2 lg:gap-16\">\n\t\t\t\t\t\t\t<Stack gap={5}>\n\t\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t\t<p className=\"text-overline text-muted-foreground\">Built for agents</p>\n\t\t\t\t\t\t\t\t\t<h2 id=\"landing-agents\" className=\"text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\t\tYour agent reads the same contract you do\n\t\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t\t<p className=\"text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tPoint Claude Code, Codex, or Cursor at the installed package. The{\" \"}\n\t\t\t\t\t\t\t\t\t\t<Code>sable</Code> CLI answers what to use, how to use it, and whether\n\t\t\t\t\t\t\t\t\t\tthe result holds up.\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t<ul className=\"sable-list\">\n\t\t\t\t\t\t\t\t\t{agentPoints.map((point) => (\n\t\t\t\t\t\t\t\t\t\t<li key={point}>{point}</li>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t<Reveal className=\"min-w-0\">\n\t\t\t\t\t\t\t\t<CodeBlock filename=\"Terminal\" code={terminalCode} highlight={false} />\n\t\t\t\t\t\t\t</Reveal>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Region>\n\t\t\t\t</section>\n\n\t\t\t\t<Region as=\"section\" aria-labelledby=\"landing-cta\" max=\"wide\" className=\"pb-20 sm:pb-28\">\n\t\t\t\t\t<Reveal>\n\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t<Stack gap={4} align=\"center\" className=\"py-8 text-center sm:py-12\">\n\t\t\t\t\t\t\t\t\t<h2 id=\"landing-cta\" className=\"max-w-2xl text-balance text-heading-1\">\n\t\t\t\t\t\t\t\t\t\tBuild the product, not another component library\n\t\t\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t\t\t<p className=\"max-w-(--measure-prose) text-body-lg text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\tInstall Sable, pick a preset, and ship the first screen today.\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t<Row gap={2} justify=\"center\" wrap className=\"pt-2\">\n\t\t\t\t\t\t\t\t\t\t<Button size=\"cta\" render={<a href=\"/docs/getting-started\" />}>\n\t\t\t\t\t\t\t\t\t\t\tGet started\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t<Button size=\"cta\" variant=\"outline\" render={<a href=\"/github\" />}>\n\t\t\t\t\t\t\t\t\t\t\tStar on GitHub\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</Reveal>\n\t\t\t\t</Region>\n\t\t\t</main>\n\n\t\t\t<footer className=\"border-t border-border\">\n\t\t\t\t<Region max=\"wide\" className=\"py-12\">\n\t\t\t\t\t<div className=\"grid gap-10 md:grid-cols-[minmax(0,2fr)_minmax(0,3fr)]\">\n\t\t\t\t\t\t<Stack gap={3} className=\"max-w-xs\">\n\t\t\t\t\t\t\t<a href=\"/\" className=\"w-fit text-title-lg motion-interactive hover:text-muted-foreground\">\n\t\t\t\t\t\t\t\tSable\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">\n\t\t\t\t\t\t\t\tReact components, patterns, and tokens for products built with AI agents.\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t<nav aria-label=\"Footer\" className=\"grid grid-cols-2 gap-8 sm:grid-cols-3\">\n\t\t\t\t\t\t\t{footerGroups.map((group) => (\n\t\t\t\t\t\t\t\t<div key={group.title}>\n\t\t\t\t\t\t\t\t\t<h2 className=\"text-label-md text-muted-foreground\">{group.title}</h2>\n\t\t\t\t\t\t\t\t\t<ul className=\"mt-3 flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t\t\t{group.links.map((link) => (\n\t\t\t\t\t\t\t\t\t\t\t<li key={link.label}>\n\t\t\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\t\t\thref={link.href}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"flex min-h-9 items-center text-body-sm motion-interactive hover:text-muted-foreground\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</nav>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"mt-12 border-t border-border pt-6\">\n\t\t\t\t\t\t<p className=\"text-caption text-muted-foreground\">© 2026 Sable. Released under the MIT license.</p>\n\t\t\t\t\t</div>\n\t\t\t\t</Region>\n\t\t\t</footer>\n\t\t</div>\n\t)\n}"
20507
20640
  },
20508
20641
  {
20509
20642
  "name": "ListDetailPage",
@@ -20526,7 +20659,7 @@
20526
20659
  "On a phone, drilling in moves focus to the back button; going back returns focus to the row that was open. After a remove, focus lands on the newly selected row; after Clear search, on the search field."
20527
20660
  ],
20528
20661
  "imports": "import { useState, useEffect, useRef } from \"react\"\nimport { Avatar, Badge, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, Col, Columns, DestructiveActionModal, EmptyState, Entity, Field, Icon, Page, PageHeader, Row, SearchInput, Select, SelectContent, SelectItem, SelectTrigger, Stack, StatusDot } from \"@sableui/react\"",
20529
- "code": "type Role = \"owner\" | \"admin\" | \"editor\" | \"viewer\"\n\nconst roles: { value: Role; label: string }[] = [\n\t{ value: \"owner\", label: \"Owner\" },\n\t{ value: \"admin\", label: \"Admin\" },\n\t{ value: \"editor\", label: \"Editor\" },\n\t{ value: \"viewer\", label: \"Viewer\" },\n]\n\nconst roleLabel: Record<Role, string> = {\n\towner: \"Owner\",\n\tadmin: \"Admin\",\n\teditor: \"Editor\",\n\tviewer: \"Viewer\",\n}\n\nconst roleSummary: Record<Role, string> = {\n\towner: \"Full access, including payouts and billing. Transfer ownership in studio settings.\",\n\tadmin: \"Manages members, editions and payouts.\",\n\teditor: \"Drafts, prices and releases editions.\",\n\tviewer: \"Sees editions and sales, but can't change them.\",\n}\n\ntype Activity = { id: string; icon: string; text: string; when: string }\n\ntype Member = {\n\tid: string\n\tname: string\n\temail: string\n\tinitials: string\n\trole: Role\n\tpending: boolean\n\thandle?: string\n\tjoined?: string\n\tlastActive?: string\n\teditions?: number\n\tinvitedBy?: string\n\tsent?: string\n\texpires?: string\n\tresent?: boolean\n\tactivity: Activity[]\n}\n\nconst seats = 10\n\nconst initialMembers: Member[] = [\n\t{\n\t\tid: \"u_01\", name: \"Ada Lovelace\", handle: \"@ada\", email: \"ada@atelier.art\", initials: \"AL\", role: \"owner\", pending: false,\n\t\tjoined: \"Mar 4, 2024\", lastActive: \"Online now\", editions: 12,\n\t\tactivity: [\n\t\t\t{ id: \"a1\", icon: \"gem\", text: \"Released Analytical Engine No. 3 — edition of 50\", when: \"2 hours ago\" },\n\t\t\t{ id: \"a2\", icon: \"mail\", text: \"Invited louise@bourgeois.studio as Editor\", when: \"2 days ago\" },\n\t\t],\n\t},\n\t{\n\t\tid: \"u_02\", name: \"Grace Hopper\", handle: \"@grace\", email: \"grace@atelier.art\", initials: \"GH\", role: \"admin\", pending: false,\n\t\tjoined: \"Mar 9, 2024\", lastActive: \"20 minutes ago\", editions: 4,\n\t\tactivity: [\n\t\t\t{ id: \"a1\", icon: \"user\", text: \"Changed Frida Kahlo's role to Viewer\", when: \"Yesterday\" },\n\t\t\t{ id: \"a2\", icon: \"edit\", text: \"Updated pricing on First Compiler — edition of 10\", when: \"3 days ago\" },\n\t\t\t{ id: \"a3\", icon: \"gem\", text: \"Released First Compiler — edition of 10\", when: \"Sep 28\" },\n\t\t],\n\t},\n\t{\n\t\tid: \"u_03\", name: \"Mary Shelley\", handle: \"@mary\", email: \"mary@atelier.art\", initials: \"MS\", role: \"editor\", pending: false,\n\t\tjoined: \"Jun 18, 2024\", lastActive: \"3 hours ago\", editions: 7,\n\t\tactivity: [\n\t\t\t{ id: \"a1\", icon: \"calendar\", text: \"Scheduled Frankenstein Studies for Oct 14\", when: \"3 hours ago\" },\n\t\t\t{ id: \"a2\", icon: \"gem\", text: \"Released The Last Man — edition of 25\", when: \"Sep 21\" },\n\t\t],\n\t},\n\t{\n\t\tid: \"u_04\", name: \"Nina Simone\", handle: \"@nina\", email: \"nina@atelier.art\", initials: \"NS\", role: \"editor\", pending: false,\n\t\tjoined: \"Jan 12, 2025\", lastActive: \"Yesterday\", editions: 3,\n\t\tactivity: [\n\t\t\t{ id: \"a1\", icon: \"upload\", text: \"Uploaded artwork for Little Girl Blue\", when: \"Yesterday\" },\n\t\t\t{ id: \"a2\", icon: \"gem\", text: \"Released Feeling Good — edition of 100\", when: \"Aug 30\" },\n\t\t],\n\t},\n\t{\n\t\tid: \"u_05\", name: \"Frida Kahlo\", handle: \"@frida\", email: \"frida@atelier.art\", initials: \"FK\", role: \"viewer\", pending: false,\n\t\tjoined: \"Jul 2, 2025\", lastActive: \"4 days ago\", editions: 0,\n\t\tactivity: [{ id: \"a1\", icon: \"comment\", text: \"Commented on the Las Dos Fridas draft\", when: \"4 days ago\" }],\n\t},\n\t{\n\t\tid: \"i_01\", name: \"louise@bourgeois.studio\", email: \"louise@bourgeois.studio\", initials: \"\", role: \"editor\", pending: true,\n\t\tinvitedBy: \"Ada Lovelace\", sent: \"2 days ago\", expires: \"In 5 days\",\n\t\tactivity: [],\n\t},\n]\n\nexport function ListDetailPage() {\n\tconst [members, setMembers] = useState(initialMembers)\n\tconst [selectedId, setSelectedId] = useState<string | null>(\"u_02\")\n\tconst [query, setQuery] = useState(\"\")\n\t// Below lg the list and the detail share one column: selecting drills in.\n\tconst [showDetail, setShowDetail] = useState(false)\n\tconst [confirming, setConfirming] = useState(false)\n\tconst listRef = useRef<HTMLDivElement>(null)\n\tconst backRef = useRef<HTMLButtonElement>(null)\n\tconst searchRef = useRef<HTMLInputElement>(null)\n\n\tconst needle = query.trim().toLowerCase()\n\tconst visible = members.filter((m) =>\n\t\t[m.name, m.email, m.handle ?? \"\", roleLabel[m.role]].some((v) => v.toLowerCase().includes(needle)),\n\t)\n\tconst selected = members.find((m) => m.id === selectedId) ?? null\n\tconst active = members.filter((m) => !m.pending).length\n\tconst pending = members.length - active\n\n\t// Drilling in on a phone moves focus to the detail's back control; the\n\t// control is display:none at lg, so desktop focus stays on the list.\n\tuseEffect(() => {\n\t\tif (showDetail && backRef.current?.offsetParent) backRef.current.focus()\n\t}, [showDetail, selectedId])\n\n\tfunction select(id: string) {\n\t\tsetSelectedId(id)\n\t\tsetShowDetail(true)\n\t}\n\n\tfunction focusRow(id: string | undefined) {\n\t\trequestAnimationFrame(() => {\n\t\t\tlistRef.current?.querySelector<HTMLButtonElement>(`[data-member-id=\"${id}\"] button`)?.focus()\n\t\t})\n\t}\n\n\tfunction backToList() {\n\t\tsetShowDetail(false)\n\t\tfocusRow(selectedId ?? undefined)\n\t}\n\n\tfunction setRole(id: string, role: Role) {\n\t\tsetMembers((list) => list.map((m) => (m.id === id ? { ...m, role } : m)))\n\t}\n\n\tfunction resend(id: string) {\n\t\tsetMembers((list) =>\n\t\t\tlist.map((m) => (m.id === id ? { ...m, sent: \"Just now\", expires: \"In 7 days\", resent: true } : m)),\n\t\t)\n\t}\n\n\tfunction remove(id: string) {\n\t\tconst index = members.findIndex((m) => m.id === id)\n\t\tconst rest = members.filter((m) => m.id !== id)\n\t\tsetMembers(rest)\n\t\tconst next = rest[Math.min(index, rest.length - 1)]?.id\n\t\tsetSelectedId(next ?? null)\n\t\tsetShowDetail(false)\n\t\t// The control that opened the confirmation is gone; land on the next row.\n\t\tfocusRow(next)\n\t}\n\n\treturn (\n\t\t<Page as=\"main\" max=\"wide\" gap={4} className=\"py-6\">\n\t\t\t<PageHeader\n\t\t\t\ttitle=\"Members\"\n\t\t\t\tdescription={`${active} members · ${pending} pending ${pending === 1 ? \"invite\" : \"invites\"} · ${members.length} of ${seats} seats used`}\n\t\t\t\tactions={<Button iconStart={<Icon name=\"plus\" />}>Invite member</Button>}\n\t\t\t/>\n\t\t\t<Columns count={{ base: 1, lg: 12 }}>\n\t\t\t\t<Col span={{ base: 1, lg: 5 }} className={showDetail ? \"max-lg:hidden\" : undefined}>\n\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t<SearchInput\n\t\t\t\t\t\t\tref={searchRef}\n\t\t\t\t\t\t\tvalue={query}\n\t\t\t\t\t\t\tonValueChange={setQuery}\n\t\t\t\t\t\t\tplaceholder=\"Search name, email or role\"\n\t\t\t\t\t\t\taria-label=\"Search members\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{/* sable-surface derives the rows' selected step from the card (12% ink),\n\t\t\t\t\t\t so a selected row stays distinct from its muted avatar. */}\n\t\t\t\t\t\t<Card ref={listRef} className=\"sable-surface\">\n\t\t\t\t\t\t\t{visible.length > 0 ? (\n\t\t\t\t\t\t\t\t<CardList aria-label=\"Members\">\n\t\t\t\t\t\t\t\t\t{visible.map((m) => {\n\t\t\t\t\t\t\t\t\t\tconst isSelected = m.id === selectedId\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\t\t\tkey={m.id}\n\t\t\t\t\t\t\t\t\t\t\t\tdata-member-id={m.id}\n\t\t\t\t\t\t\t\t\t\t\t\tinteractive\n\t\t\t\t\t\t\t\t\t\t\t\tselected={isSelected}\n\t\t\t\t\t\t\t\t\t\t\t\tonSelect={() => select(m.id)}\n\t\t\t\t\t\t\t\t\t\t\t\tleading={\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Avatar variant=\"art\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tseed={m.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfallback={m.pending ? <Icon name=\"mail\" className=\"size-5\" /> : m.initials}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstatus={m.lastActive === \"Online now\" ? <StatusDot tone=\"success\" /> : undefined}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// The row's title already names it: keep the initials out of the row's name.\n\t\t\t\t\t\t\t\t\t\t\t\t\t\taria-hidden\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\ttitle={m.name}\n\t\t\t\t\t\t\t\t\t\t\t\tsubtitle={\n\t\t\t\t\t\t\t\t\t\t\t\t\tm.pending\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t? `${roleLabel[m.role]} · invite sent ${m.sent?.toLowerCase()}`\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t: `${roleLabel[m.role]} · ${m.handle}`\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\taction={m.pending ? <Badge tone=\"info\">Invited</Badge> : undefined}\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t<EmptyState\n\t\t\t\t\t\t\t\t\t\ticon={<Icon name=\"search\" />}\n\t\t\t\t\t\t\t\t\t\ttitle=\"No members found\"\n\t\t\t\t\t\t\t\t\t\tdescription={`Nobody matches “${query.trim()}”. Try a name, email or role.`}\n\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"secondary\" size=\"sm\" onClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\tsetQuery(\"\")\n\t\t\t\t\t\t\t\t\t\t\t\tsearchRef.current?.focus()\n\t\t\t\t\t\t\t\t\t\t\t}}>\n\t\t\t\t\t\t\t\t\t\t\t\tClear search\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</Stack>\n\t\t\t\t</Col>\n\t\t\t\t<Col span={{ base: 1, lg: 7 }} className={showDetail ? undefined : \"max-lg:hidden\"}>\n\t\t\t\t\t<Stack role=\"region\" aria-label=\"Member details\" gap={3} className=\"lg:sticky lg:top-6\">\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tref={backRef}\n\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tbleed=\"start\"\n\t\t\t\t\t\t\ticonStart={<Icon name=\"chevron-left\" />}\n\t\t\t\t\t\t\tonClick={backToList}\n\t\t\t\t\t\t\tclassName=\"self-start lg:hidden\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tAll members\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t{selected === null ? (\n\t\t\t\t\t\t\t<EmptyState\n\t\t\t\t\t\t\t\tsurface=\"card\"\n\t\t\t\t\t\t\t\ticon={<Icon name=\"user\" />}\n\t\t\t\t\t\t\t\ttitle=\"No member selected\"\n\t\t\t\t\t\t\t\tdescription=\"Choose someone from the list to see their role and activity.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t<Row gap={2}>\n\t\t\t\t\t\t\t\t\t\t\t<Avatar variant=\"art\"\n\t\t\t\t\t\t\t\t\t\t\t\tsize=\"lg\"\n\t\t\t\t\t\t\t\t\t\t\t\tfallback={selected.pending ? <Icon name=\"mail\" className=\"size-6\" /> : selected.initials}\n\t\t\t\t\t\t\t\t\t\t\t\tstatus={selected.lastActive === \"Online now\" ? <StatusDot tone=\"success\" /> : undefined}\n\t\t\t\t\t\t\t\t\t\t\t\talt={selected.pending ? \"Pending invite\" : selected.name}\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t<Stack gap={0} className=\"min-w-0\">\n\t\t\t\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />} className=\"truncate\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{selected.name}\n\t\t\t\t\t\t\t\t\t\t\t\t</CardTitle>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardDescription className=\"truncate\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{selected.pending ? \"Hasn't joined yet\" : `${selected.handle} · ${selected.email}`}\n\t\t\t\t\t\t\t\t\t\t\t\t</CardDescription>\n\t\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t{selected.pending ? (\n\t\t\t\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t\t\t\t<Badge tone=\"info\">Invited</Badge>\n\t\t\t\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t\t<Stack gap={5}>\n\t\t\t\t\t\t\t\t\t\t\t<Field\n\t\t\t\t\t\t\t\t\t\t\t\tlabel={selected.pending ? \"Role on joining\" : \"Role\"}\n\t\t\t\t\t\t\t\t\t\t\t\tdescription={roleSummary[selected.role]}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<Select\n\t\t\t\t\t\t\t\t\t\t\t\t\titems={roles}\n\t\t\t\t\t\t\t\t\t\t\t\t\tvalue={selected.role}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonValueChange={(value) => setRole(selected.id, value as Role)}\n\t\t\t\t\t\t\t\t\t\t\t\t\tdisabled={selected.role === \"owner\"}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectTrigger className=\"max-w-72\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{roles\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.filter((r) => r.value !== \"owner\" || selected.role === \"owner\")\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.map((r) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectItem key={r.value} value={r.value}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{r.label}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t\t\t\t\t\t</Select>\n\t\t\t\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"@container\">\n\t\t\t\t\t\t\t\t\t\t\t\t{selected.pending ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<dl className=\"sable-list-dl sable-list-dl-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Sent</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.sent}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Expires</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.expires}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Invited by</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.invitedBy}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</dl>\n\t\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<dl className=\"sable-list-dl sable-list-dl-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Email</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.email}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Joined</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.joined}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Last active</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd className={selected.lastActive === \"Online now\" ? \"text-success\" : undefined}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{selected.lastActive}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Editions released</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.editions}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</dl>\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t{selected.role !== \"owner\" ? (\n\t\t\t\t\t\t\t\t\t\t<CardFooter>\n\t\t\t\t\t\t\t\t\t\t\t<Row gap={2} wrap>\n\t\t\t\t\t\t\t\t\t\t\t\t{selected.pending ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ticonStart={<Icon name={selected.resent ? \"check\" : \"send\"} />}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdisabled={selected.resent}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => resend(selected.id)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{selected.resent ? \"Invite resent\" : \"Resend invite\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\" onClick={() => setConfirming(true)}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{selected.pending ? \"Revoke invite\" : \"Remove from studio\"}\n\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t{selected.activity.length > 0 ? (\n\t\t\t\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t<CardTitle render={<h3 />}>\n\t\t\t\t\t\t\t\t\t\t\t\tRecent activity\n\t\t\t\t\t\t\t\t\t\t\t</CardTitle>\n\t\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t\t\t\t{selected.activity.map((a) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={a.id}\n\t\t\t\t\t\t\t\t\t\t\t\t\tleading={<Icon name={a.icon} className=\"text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={a.text}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsubtitle={a.when}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t<DestructiveActionModal\n\t\t\t\t\t\t\t\t\topen={confirming}\n\t\t\t\t\t\t\t\t\tonOpenChange={setConfirming}\n\t\t\t\t\t\t\t\t\ttitle={selected.pending ? \"Revoke this invite?\" : `Remove ${selected.name}?`}\n\t\t\t\t\t\t\t\t\tdescription={\n\t\t\t\t\t\t\t\t\t\tselected.pending\n\t\t\t\t\t\t\t\t\t\t\t? `${selected.email} won't be able to join with the link they were sent.`\n\t\t\t\t\t\t\t\t\t\t\t: \"They lose access to the studio right away. Editions they released stay on sale.\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tconfirmLabel={selected.pending ? \"Revoke invite\" : \"Remove member\"}\n\t\t\t\t\t\t\t\t\tonConfirm={() => remove(selected.id)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</Stack>\n\t\t\t\t</Col>\n\t\t\t</Columns>\n\t\t</Page>\n\t)\n}"
20662
+ "code": "type Role = \"owner\" | \"admin\" | \"editor\" | \"viewer\"\n\nconst roles: { value: Role; label: string }[] = [\n\t{ value: \"owner\", label: \"Owner\" },\n\t{ value: \"admin\", label: \"Admin\" },\n\t{ value: \"editor\", label: \"Editor\" },\n\t{ value: \"viewer\", label: \"Viewer\" },\n]\n\nconst roleLabel: Record<Role, string> = {\n\towner: \"Owner\",\n\tadmin: \"Admin\",\n\teditor: \"Editor\",\n\tviewer: \"Viewer\",\n}\n\nconst roleSummary: Record<Role, string> = {\n\towner: \"Full access, including payouts and billing. Transfer ownership in studio settings.\",\n\tadmin: \"Manages members, projects and billing.\",\n\teditor: \"Drafts, edits and publishes projects.\",\n\tviewer: \"Sees projects and reports, but can't change them.\",\n}\n\ntype Activity = { id: string; icon: string; text: string; when: string }\n\ntype Member = {\n\tid: string\n\tname: string\n\temail: string\n\tinitials: string\n\trole: Role\n\tpending: boolean\n\thandle?: string\n\tjoined?: string\n\tlastActive?: string\n\tprojects?: number\n\tinvitedBy?: string\n\tsent?: string\n\texpires?: string\n\tresent?: boolean\n\tactivity: Activity[]\n}\n\nconst seats = 10\n\nconst initialMembers: Member[] = [\n\t{\n\t\tid: \"u_01\", name: \"Ada Lovelace\", handle: \"@ada\", email: \"ada@atelier.art\", initials: \"AL\", role: \"owner\", pending: false,\n\t\tjoined: \"Mar 4, 2024\", lastActive: \"Online now\", projects: 12,\n\t\tactivity: [\n\t\t\t{ id: \"a1\", icon: \"file\", text: \"Published the Analytical Engine No. 3 roadmap\", when: \"2 hours ago\" },\n\t\t\t{ id: \"a2\", icon: \"mail\", text: \"Invited louise@bourgeois.studio as Editor\", when: \"2 days ago\" },\n\t\t],\n\t},\n\t{\n\t\tid: \"u_02\", name: \"Grace Hopper\", handle: \"@grace\", email: \"grace@atelier.art\", initials: \"GH\", role: \"admin\", pending: false,\n\t\tjoined: \"Mar 9, 2024\", lastActive: \"20 minutes ago\", projects: 4,\n\t\tactivity: [\n\t\t\t{ id: \"a1\", icon: \"user\", text: \"Changed Frida Kahlo's role to Viewer\", when: \"Yesterday\" },\n\t\t\t{ id: \"a2\", icon: \"edit\", text: \"Updated the budget on First Compiler\", when: \"3 days ago\" },\n\t\t\t{ id: \"a3\", icon: \"file\", text: \"Created the First Compiler project\", when: \"Sep 28\" },\n\t\t],\n\t},\n\t{\n\t\tid: \"u_03\", name: \"Mary Shelley\", handle: \"@mary\", email: \"mary@atelier.art\", initials: \"MS\", role: \"editor\", pending: false,\n\t\tjoined: \"Jun 18, 2024\", lastActive: \"3 hours ago\", projects: 7,\n\t\tactivity: [\n\t\t\t{ id: \"a1\", icon: \"calendar\", text: \"Scheduled Frankenstein Studies for Oct 14\", when: \"3 hours ago\" },\n\t\t\t{ id: \"a2\", icon: \"file\", text: \"Published The Last Man report\", when: \"Sep 21\" },\n\t\t],\n\t},\n\t{\n\t\tid: \"u_04\", name: \"Nina Simone\", handle: \"@nina\", email: \"nina@atelier.art\", initials: \"NS\", role: \"editor\", pending: false,\n\t\tjoined: \"Jan 12, 2025\", lastActive: \"Yesterday\", projects: 3,\n\t\tactivity: [\n\t\t\t{ id: \"a1\", icon: \"upload\", text: \"Uploaded files for Little Girl Blue\", when: \"Yesterday\" },\n\t\t\t{ id: \"a2\", icon: \"file\", text: \"Published the Feeling Good brief\", when: \"Aug 30\" },\n\t\t],\n\t},\n\t{\n\t\tid: \"u_05\", name: \"Frida Kahlo\", handle: \"@frida\", email: \"frida@atelier.art\", initials: \"FK\", role: \"viewer\", pending: false,\n\t\tjoined: \"Jul 2, 2025\", lastActive: \"4 days ago\", projects: 0,\n\t\tactivity: [{ id: \"a1\", icon: \"comment\", text: \"Commented on the Las Dos Fridas draft\", when: \"4 days ago\" }],\n\t},\n\t{\n\t\tid: \"i_01\", name: \"louise@bourgeois.studio\", email: \"louise@bourgeois.studio\", initials: \"\", role: \"editor\", pending: true,\n\t\tinvitedBy: \"Ada Lovelace\", sent: \"2 days ago\", expires: \"In 5 days\",\n\t\tactivity: [],\n\t},\n]\n\nexport function ListDetailPage() {\n\tconst [members, setMembers] = useState(initialMembers)\n\tconst [selectedId, setSelectedId] = useState<string | null>(\"u_02\")\n\tconst [query, setQuery] = useState(\"\")\n\t// Below lg the list and the detail share one column: selecting drills in.\n\tconst [showDetail, setShowDetail] = useState(false)\n\tconst [confirming, setConfirming] = useState(false)\n\tconst listRef = useRef<HTMLDivElement>(null)\n\tconst backRef = useRef<HTMLButtonElement>(null)\n\tconst searchRef = useRef<HTMLInputElement>(null)\n\n\tconst needle = query.trim().toLowerCase()\n\tconst visible = members.filter((m) =>\n\t\t[m.name, m.email, m.handle ?? \"\", roleLabel[m.role]].some((v) => v.toLowerCase().includes(needle)),\n\t)\n\tconst selected = members.find((m) => m.id === selectedId) ?? null\n\tconst active = members.filter((m) => !m.pending).length\n\tconst pending = members.length - active\n\n\t// Drilling in on a phone moves focus to the detail's back control; the\n\t// control is display:none at lg, so desktop focus stays on the list.\n\tuseEffect(() => {\n\t\tif (showDetail && backRef.current?.offsetParent) backRef.current.focus()\n\t}, [showDetail, selectedId])\n\n\tfunction select(id: string) {\n\t\tsetSelectedId(id)\n\t\tsetShowDetail(true)\n\t}\n\n\tfunction focusRow(id: string | undefined) {\n\t\trequestAnimationFrame(() => {\n\t\t\tlistRef.current?.querySelector<HTMLButtonElement>(`[data-member-id=\"${id}\"] button`)?.focus()\n\t\t})\n\t}\n\n\tfunction backToList() {\n\t\tsetShowDetail(false)\n\t\tfocusRow(selectedId ?? undefined)\n\t}\n\n\tfunction setRole(id: string, role: Role) {\n\t\tsetMembers((list) => list.map((m) => (m.id === id ? { ...m, role } : m)))\n\t}\n\n\tfunction resend(id: string) {\n\t\tsetMembers((list) =>\n\t\t\tlist.map((m) => (m.id === id ? { ...m, sent: \"Just now\", expires: \"In 7 days\", resent: true } : m)),\n\t\t)\n\t}\n\n\tfunction remove(id: string) {\n\t\tconst index = members.findIndex((m) => m.id === id)\n\t\tconst rest = members.filter((m) => m.id !== id)\n\t\tsetMembers(rest)\n\t\tconst next = rest[Math.min(index, rest.length - 1)]?.id\n\t\tsetSelectedId(next ?? null)\n\t\tsetShowDetail(false)\n\t\t// The control that opened the confirmation is gone; land on the next row.\n\t\tfocusRow(next)\n\t}\n\n\treturn (\n\t\t<Page as=\"main\" max=\"wide\" gap={4} className=\"py-6\">\n\t\t\t<PageHeader\n\t\t\t\ttitle=\"Members\"\n\t\t\t\tdescription={`${active} members · ${pending} pending ${pending === 1 ? \"invite\" : \"invites\"} · ${members.length} of ${seats} seats used`}\n\t\t\t\tactions={<Button iconStart={<Icon name=\"plus\" />}>Invite member</Button>}\n\t\t\t/>\n\t\t\t<Columns count={{ base: 1, lg: 12 }}>\n\t\t\t\t<Col span={{ base: 1, lg: 5 }} className={showDetail ? \"max-lg:hidden\" : undefined}>\n\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t<SearchInput\n\t\t\t\t\t\t\tref={searchRef}\n\t\t\t\t\t\t\tvalue={query}\n\t\t\t\t\t\t\tonValueChange={setQuery}\n\t\t\t\t\t\t\tplaceholder=\"Search name, email or role\"\n\t\t\t\t\t\t\taria-label=\"Search members\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{/* sable-surface derives the rows' selected step from the card (12% ink),\n\t\t\t\t\t\t so a selected row stays distinct from its muted avatar. */}\n\t\t\t\t\t\t<Card ref={listRef} className=\"sable-surface\">\n\t\t\t\t\t\t\t{visible.length > 0 ? (\n\t\t\t\t\t\t\t\t<CardList aria-label=\"Members\">\n\t\t\t\t\t\t\t\t\t{visible.map((m) => {\n\t\t\t\t\t\t\t\t\t\tconst isSelected = m.id === selectedId\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\t\t\tkey={m.id}\n\t\t\t\t\t\t\t\t\t\t\t\tdata-member-id={m.id}\n\t\t\t\t\t\t\t\t\t\t\t\tinteractive\n\t\t\t\t\t\t\t\t\t\t\t\tselected={isSelected}\n\t\t\t\t\t\t\t\t\t\t\t\tonSelect={() => select(m.id)}\n\t\t\t\t\t\t\t\t\t\t\t\tleading={\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Avatar variant=\"art\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tseed={m.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfallback={m.pending ? <Icon name=\"mail\" className=\"size-5\" /> : m.initials}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstatus={m.lastActive === \"Online now\" ? <StatusDot tone=\"success\" /> : undefined}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// The row's title already names it: keep the initials out of the row's name.\n\t\t\t\t\t\t\t\t\t\t\t\t\t\taria-hidden\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\ttitle={m.name}\n\t\t\t\t\t\t\t\t\t\t\t\tsubtitle={\n\t\t\t\t\t\t\t\t\t\t\t\t\tm.pending\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t? `${roleLabel[m.role]} · invite sent ${m.sent?.toLowerCase()}`\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t: `${roleLabel[m.role]} · ${m.handle}`\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\taction={m.pending ? <Badge tone=\"info\">Invited</Badge> : undefined}\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t<EmptyState\n\t\t\t\t\t\t\t\t\t\ticon={<Icon name=\"search\" />}\n\t\t\t\t\t\t\t\t\t\ttitle=\"No members found\"\n\t\t\t\t\t\t\t\t\t\tdescription={`Nobody matches “${query.trim()}”. Try a name, email or role.`}\n\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"secondary\" size=\"sm\" onClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\tsetQuery(\"\")\n\t\t\t\t\t\t\t\t\t\t\t\tsearchRef.current?.focus()\n\t\t\t\t\t\t\t\t\t\t\t}}>\n\t\t\t\t\t\t\t\t\t\t\t\tClear search\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</Stack>\n\t\t\t\t</Col>\n\t\t\t\t<Col span={{ base: 1, lg: 7 }} className={showDetail ? undefined : \"max-lg:hidden\"}>\n\t\t\t\t\t<Stack role=\"region\" aria-label=\"Member details\" gap={3} className=\"lg:sticky lg:top-6\">\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tref={backRef}\n\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tbleed=\"start\"\n\t\t\t\t\t\t\ticonStart={<Icon name=\"chevron-left\" />}\n\t\t\t\t\t\t\tonClick={backToList}\n\t\t\t\t\t\t\tclassName=\"self-start lg:hidden\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tAll members\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t{selected === null ? (\n\t\t\t\t\t\t\t<EmptyState\n\t\t\t\t\t\t\t\tsurface=\"card\"\n\t\t\t\t\t\t\t\ticon={<Icon name=\"user\" />}\n\t\t\t\t\t\t\t\ttitle=\"No member selected\"\n\t\t\t\t\t\t\t\tdescription=\"Choose someone from the list to see their role and activity.\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t<Row gap={2}>\n\t\t\t\t\t\t\t\t\t\t\t<Avatar variant=\"art\"\n\t\t\t\t\t\t\t\t\t\t\t\tsize=\"lg\"\n\t\t\t\t\t\t\t\t\t\t\t\tfallback={selected.pending ? <Icon name=\"mail\" className=\"size-6\" /> : selected.initials}\n\t\t\t\t\t\t\t\t\t\t\t\tstatus={selected.lastActive === \"Online now\" ? <StatusDot tone=\"success\" /> : undefined}\n\t\t\t\t\t\t\t\t\t\t\t\talt={selected.pending ? \"Pending invite\" : selected.name}\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t<Stack gap={0} className=\"min-w-0\">\n\t\t\t\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />} className=\"truncate\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{selected.name}\n\t\t\t\t\t\t\t\t\t\t\t\t</CardTitle>\n\t\t\t\t\t\t\t\t\t\t\t\t<CardDescription className=\"truncate\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{selected.pending ? \"Hasn't joined yet\" : `${selected.handle} · ${selected.email}`}\n\t\t\t\t\t\t\t\t\t\t\t\t</CardDescription>\n\t\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t{selected.pending ? (\n\t\t\t\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t\t\t\t<Badge tone=\"info\">Invited</Badge>\n\t\t\t\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t\t<Stack gap={5}>\n\t\t\t\t\t\t\t\t\t\t\t<Field\n\t\t\t\t\t\t\t\t\t\t\t\tlabel={selected.pending ? \"Role on joining\" : \"Role\"}\n\t\t\t\t\t\t\t\t\t\t\t\tdescription={roleSummary[selected.role]}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<Select\n\t\t\t\t\t\t\t\t\t\t\t\t\titems={roles}\n\t\t\t\t\t\t\t\t\t\t\t\t\tvalue={selected.role}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonValueChange={(value) => setRole(selected.id, value as Role)}\n\t\t\t\t\t\t\t\t\t\t\t\t\tdisabled={selected.role === \"owner\"}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectTrigger className=\"max-w-72\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{roles\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.filter((r) => r.value !== \"owner\" || selected.role === \"owner\")\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.map((r) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectItem key={r.value} value={r.value}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{r.label}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t\t\t\t\t\t</Select>\n\t\t\t\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"@container\">\n\t\t\t\t\t\t\t\t\t\t\t\t{selected.pending ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<dl className=\"sable-list-dl sable-list-dl-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Sent</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.sent}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Expires</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.expires}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Invited by</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.invitedBy}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</dl>\n\t\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<dl className=\"sable-list-dl sable-list-dl-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Email</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.email}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Joined</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.joined}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Last active</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd className={selected.lastActive === \"Online now\" ? \"text-success\" : undefined}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{selected.lastActive}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dt>Projects owned</dt>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dd>{selected.projects}</dd>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</dl>\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t{selected.role !== \"owner\" ? (\n\t\t\t\t\t\t\t\t\t\t<CardFooter>\n\t\t\t\t\t\t\t\t\t\t\t<Row gap={2} wrap>\n\t\t\t\t\t\t\t\t\t\t\t\t{selected.pending ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ticonStart={<Icon name={selected.resent ? \"check\" : \"send\"} />}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdisabled={selected.resent}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => resend(selected.id)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{selected.resent ? \"Invite resent\" : \"Resend invite\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\" onClick={() => setConfirming(true)}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{selected.pending ? \"Revoke invite\" : \"Remove from studio\"}\n\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t{selected.activity.length > 0 ? (\n\t\t\t\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t\t\t<CardTitle render={<h3 />}>\n\t\t\t\t\t\t\t\t\t\t\t\tRecent activity\n\t\t\t\t\t\t\t\t\t\t\t</CardTitle>\n\t\t\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t\t\t\t{selected.activity.map((a) => (\n\t\t\t\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={a.id}\n\t\t\t\t\t\t\t\t\t\t\t\t\tleading={<Icon name={a.icon} className=\"text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={a.text}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsubtitle={a.when}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t<DestructiveActionModal\n\t\t\t\t\t\t\t\t\topen={confirming}\n\t\t\t\t\t\t\t\t\tonOpenChange={setConfirming}\n\t\t\t\t\t\t\t\t\ttitle={selected.pending ? \"Revoke this invite?\" : `Remove ${selected.name}?`}\n\t\t\t\t\t\t\t\t\tdescription={\n\t\t\t\t\t\t\t\t\t\tselected.pending\n\t\t\t\t\t\t\t\t\t\t\t? `${selected.email} won't be able to join with the link they were sent.`\n\t\t\t\t\t\t\t\t\t\t\t: \"They lose access to the workspace right away. Projects they own stay with the team.\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tconfirmLabel={selected.pending ? \"Revoke invite\" : \"Remove member\"}\n\t\t\t\t\t\t\t\t\tonConfirm={() => remove(selected.id)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</Stack>\n\t\t\t\t</Col>\n\t\t\t</Columns>\n\t\t</Page>\n\t)\n}"
20530
20663
  },
20531
20664
  {
20532
20665
  "name": "SettingsPage",
@@ -20552,7 +20685,7 @@
20552
20685
  "DestructiveActionModal is an alertdialog: focus opens on Cancel, the backdrop does not dismiss, and the confirm stays disabled until the handle is typed."
20553
20686
  ],
20554
20687
  "imports": "import { useState } from \"react\"\nimport { AppHeader, AppShell, Avatar, BottomNav, BottomNavItem, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, Col, Columns, DestructiveActionModal, Entity, Field, Form, Grid, Icon, Input, MenuItem, MenuSeparator, NotificationBadge, OverflowMenu, Page, PageHeader, Row, Select, SelectContent, SelectItem, SelectTrigger, Sidebar, SidebarBrand, SidebarFooter, SidebarHeader, SidebarItem, SidebarNav, SideNav, SideNavItem, Stack, Switch, Textarea } from \"@sableui/react\"",
20555
- "code": "const sections = [\n\t{ id: \"profile\", label: \"Profile\" },\n\t{ id: \"notifications\", label: \"Notifications\" },\n\t{ id: \"payouts\", label: \"Payouts\" },\n\t{ id: \"account\", label: \"Account\" },\n]\n\nconst timezones = [\n\t{ value: \"europe-london\", label: \"London (GMT+1)\" },\n\t{ value: \"europe-paris\", label: \"Paris (GMT+2)\" },\n\t{ value: \"america-new-york\", label: \"New York (GMT−4)\" },\n\t{ value: \"america-los-angeles\", label: \"Los Angeles (GMT−7)\" },\n\t{ value: \"asia-tokyo\", label: \"Tokyo (GMT+9)\" },\n]\n\nconst notifications = [\n\t{ name: \"notifyCollects\", title: \"New collects\", description: \"When someone collects your work\", on: true },\n\t{ name: \"notifySoldOut\", title: \"Sold out\", description: \"When an edition sells its last copy\", on: true },\n\t{ name: \"notifyFollowers\", title: \"New followers\", description: \"When a collector follows you\", on: false },\n\t{ name: \"notifyDigest\", title: \"Weekly digest\", description: \"Sales and collectors, every Monday\", on: true },\n\t{ name: \"notifyProduct\", title: \"Product updates\", description: \"New Atelier features\", on: false },\n]\n\nconst payouts = [\n\t{ label: \"Next payout\", value: \"$1,284.60\", note: \"Friday, Oct 16\" },\n\t{ label: \"Pending\", value: \"$412.00\", note: \"Clears in 7 days\" },\n\t{ label: \"Paid this year\", value: \"$18,920.00\", note: \"Across 214 collects\" },\n]\n\nexport function SettingsPage() {\n\tconst [activeSection, setActiveSection] = useState(\"profile\")\n\tconst [deleteOpen, setDeleteOpen] = useState(false)\n\n\treturn (\n\t\t<AppShell\n\t\t\t// Sidebar hides below lg, so the same destinations ride in the shell's\n\t\t\t// mobile slots — without them the route has no navigation under 1024px.\n\t\t\theader={\n\t\t\t\t<AppHeader\n\t\t\t\t\ttitle={\n\t\t\t\t\t\t<Row gap={1} align=\"center\">\n\t\t\t\t\t\t\t<Icon name=\"gem\" variant=\"solid\" className=\"text-xl\" />\n\t\t\t\t\t\t\t<span className=\"text-title-sm\">Atelier</span>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t}\n\t\t\t\t\tactions={<Avatar variant=\"art\" size=\"sm\" fallback=\"AL\" alt=\"Ada Lovelace\" />}\n\t\t\t\t/>\n\t\t\t}\n\t\t\tbottomNav={\n\t\t\t\t<BottomNav aria-label=\"Primary\">\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"grid\" />} label=\"Overview\" showLabel render={<a href=\"/\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"image-stack\" />} label=\"Editions\" showLabel render={<a href=\"/editions\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"user\" />} label=\"Collectors\" showLabel render={<a href=\"/collectors\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"bell\" />} label=\"Activity\" showLabel render={<a href=\"/activity\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"gear\" />} label=\"Settings\" showLabel active render={<a href=\"/settings\" />} />\n\t\t\t\t</BottomNav>\n\t\t\t}\n\t\t\tsidebar={\n\t\t\t\t<Sidebar>\n\t\t\t\t\t<SidebarHeader>\n\t\t\t\t\t\t<SidebarBrand mark={<Icon name=\"gem\" variant=\"solid\" />} label=\"Atelier\" render={<a href=\"/\" />} />\n\t\t\t\t\t</SidebarHeader>\n\t\t\t\t\t<SidebarNav>\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"grid\" />} label=\"Overview\" render={<a href=\"/\" />} />\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"image-stack\" />} label=\"Editions\" render={<a href=\"/editions\" />} />\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"user\" />} label=\"Collectors\" render={<a href=\"/collectors\" />} />\n\t\t\t\t\t\t<SidebarItem\n\t\t\t\t\t\t\ticon={<Icon name=\"bell\" />}\n\t\t\t\t\t\t\tlabel=\"Activity\"\n\t\t\t\t\t\t\tbadge={<NotificationBadge count={4} tone=\"neutral\" />}\n\t\t\t\t\t\t\trender={<a href=\"/activity\" />}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"gear\" />} label=\"Settings\" active render={<a href=\"/settings\" />} />\n\t\t\t\t\t</SidebarNav>\n\t\t\t\t\t<SidebarFooter>\n\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\tclassName=\"[--entity-py:0]\"\n\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback=\"AL\" alt=\"\" />}\n\t\t\t\t\t\t\ttitle=\"Ada Lovelace\"\n\t\t\t\t\t\t\tsubtitle=\"@ada\"\n\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t<OverflowMenu label=\"Account\" size=\"icon-sm\">\n\t\t\t\t\t\t\t\t\t<MenuItem render={<a href=\"/@ada\" />}>View profile</MenuItem>\n\t\t\t\t\t\t\t\t\t<MenuItem render={<a href=\"/settings\" />}>Settings</MenuItem>\n\t\t\t\t\t\t\t\t\t<MenuSeparator />\n\t\t\t\t\t\t\t\t\t<MenuItem>Sign out</MenuItem>\n\t\t\t\t\t\t\t\t</OverflowMenu>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SidebarFooter>\n\t\t\t\t</Sidebar>\n\t\t\t}\n\t\t>\n\t\t\t{/* No `as=\"main\"`: AppShell already renders <main id=\"main-content\">. */}\n\t\t\t<Page max=\"wide\" gap={4} className=\"py-6\">\n\t\t\t\t<PageHeader\n\t\t\t\t\ttitle=\"Settings\"\n\t\t\t\t\tdescription=\"Manage your profile, notifications, and payouts.\"\n\t\t\t\t/>\n\t\t\t\t<Columns gap={4}>\n\t\t\t\t\t{/* The section rail is a desktop aid; on mobile the sections simply stack. */}\n\t\t\t\t\t<Col span={{ base: 12, lg: 3 }} className=\"hidden lg:block\">\n\t\t\t\t\t\t<SideNav aria-label=\"Settings sections\" className=\"sticky top-6\">\n\t\t\t\t\t\t\t{sections.map((section) => (\n\t\t\t\t\t\t\t\t<SideNavItem\n\t\t\t\t\t\t\t\t\tkey={section.id}\n\t\t\t\t\t\t\t\t\tlabel={section.label}\n\t\t\t\t\t\t\t\t\tactive={activeSection === section.id}\n\t\t\t\t\t\t\t\t\tonClick={() => setActiveSection(section.id)}\n\t\t\t\t\t\t\t\t\trender={<a href={`#${section.id}`} />}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</SideNav>\n\t\t\t\t\t</Col>\n\t\t\t\t\t<Col span={{ base: 12, lg: 9 }}>\n\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t<Card id=\"profile\" className=\"scroll-mt-6\">\n\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Profile</CardTitle>\n\t\t\t\t\t\t\t\t\t<CardDescription>How collectors see you on Atelier.</CardDescription>\n\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t{/* The one explicit save on the page: profile text is drafted, then\n\t\t\t\t\t\t\t\t committed. Toggles below apply the moment they change. */}\n\t\t\t\t\t\t\t\t<Form onSubmit={(event) => event.preventDefault()} className=\"gap-0\">\n\t\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t\t\t\t<Row gap={2}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Avatar variant=\"art\" size=\"lg\" fallback=\"AL\" alt=\"Ada Lovelace\" />\n\t\t\t\t\t\t\t\t\t\t\t\t<Stack gap={0.5} align=\"start\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Button type=\"button\" variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tUpload photo\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Button type=\"button\" variant=\"ghost\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tRemove\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-caption text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tSquare PNG or JPG, at least 400 px.\n\t\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 2 }} gap={3}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Field label=\"Display name\" name=\"displayName\" required>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Input defaultValue=\"Ada Lovelace\" autoComplete=\"name\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t\t\t\t\t<Field label=\"Handle\" name=\"handle\" description=\"atelier.com/@ada\" required>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Input defaultValue=\"ada\" autoComplete=\"username\" spellCheck={false} />\n\t\t\t\t\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t\t\t\t\t<Field label=\"Bio\" name=\"bio\" description=\"Shown on your profile and edition pages.\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Textarea\n\t\t\t\t\t\t\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\t\t\t\t\t\t\tdefaultValue=\"Generative artist working in plotted ink and code. New editions of 25 to 100 every month.\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 2 }} gap={3}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Field label=\"Timezone\" name=\"timezone\" description=\"Release times use this zone.\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Select defaultValue=\"europe-london\" items={timezones}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectTrigger />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{timezones.map((zone) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectItem key={zone.value} value={zone.value}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{zone.label}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Select>\n\t\t\t\t\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t<CardFooter className=\"justify-end\">\n\t\t\t\t\t\t\t\t\t\t<Button type=\"submit\">Save profile</Button>\n\t\t\t\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t\t\t\t</Form>\n\t\t\t\t\t\t\t</Card>\n\n\t\t\t\t\t\t\t<Card id=\"notifications\" className=\"scroll-mt-6\">\n\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Notifications</CardTitle>\n\t\t\t\t\t\t\t\t\t<CardDescription>\n\t\t\t\t\t\t\t\t\t\tEmail updates about your editions. Changes save automatically.\n\t\t\t\t\t\t\t\t\t</CardDescription>\n\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t\t{notifications.map((item) => (\n\t\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\t\tkey={item.name}\n\t\t\t\t\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\t\t\t\t\tsubtitle={item.description}\n\t\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t\t<Switch\n\t\t\t\t\t\t\t\t\t\t\t\t\tname={item.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\tdefaultChecked={item.on}\n\t\t\t\t\t\t\t\t\t\t\t\t\taria-label={item.title}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t\t</Card>\n\n\t\t\t\t\t\t\t<Card id=\"payouts\" className=\"scroll-mt-6\">\n\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Payouts</CardTitle>\n\t\t\t\t\t\t\t\t\t<CardDescription>Paid in USD every Friday to Chase checking ••4821.</CardDescription>\n\t\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\" render={<a href=\"/settings/payouts\" />}>\n\t\t\t\t\t\t\t\t\t\t\tManage payouts\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 3 }} gap={3}>\n\t\t\t\t\t\t\t\t\t\t{payouts.map((stat) => (\n\t\t\t\t\t\t\t\t\t\t\t<Stack key={stat.label} gap={0.5}>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">{stat.label}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-mono-lg text-foreground\">{stat.value}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-caption text-muted-foreground\">{stat.note}</p>\n\t\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t</Card>\n\n\t\t\t\t\t\t\t<Card id=\"account\" className=\"scroll-mt-6\">\n\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Account</CardTitle>\n\t\t\t\t\t\t\t\t\t<CardDescription>Sign-in details and your data.</CardDescription>\n\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\tleading={<Icon name=\"mail\" className=\"text-lg text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t\t\ttitle=\"Email\"\n\t\t\t\t\t\t\t\t\t\tsubtitle=\"ada@atelier.art\"\n\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\t\tChange\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\tleading={<Icon name=\"download\" className=\"text-lg text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t\t\ttitle=\"Export data\"\n\t\t\t\t\t\t\t\t\t\tsubtitle=\"Editions, sales, and collectors\"\n\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\t\tExport\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t\t</Card>\n\n\t\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Delete account</CardTitle>\n\t\t\t\t\t\t\t\t\t<CardDescription>\n\t\t\t\t\t\t\t\t\t\tPermanently remove your profile, editions, and sales history. Collectors keep\n\t\t\t\t\t\t\t\t\t\tthe editions they own.\n\t\t\t\t\t\t\t\t\t</CardDescription>\n\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t<CardFooter className=\"justify-end\">\n\t\t\t\t\t\t\t\t\t<Button variant=\"destructive\" onClick={() => setDeleteOpen(true)}>\n\t\t\t\t\t\t\t\t\t\tDelete account\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t</Col>\n\t\t\t\t</Columns>\n\t\t\t</Page>\n\t\t\t<DestructiveActionModal\n\t\t\t\topen={deleteOpen}\n\t\t\t\tonOpenChange={setDeleteOpen}\n\t\t\t\ttitle=\"Delete your account?\"\n\t\t\t\tdescription=\"This permanently removes your profile, 12 editions, and sales history. Pending payouts are still sent. This can't be undone.\"\n\t\t\t\tconfirmLabel=\"Delete account\"\n\t\t\t\tconfirmingLabel=\"Deleting…\"\n\t\t\t\tconfirmText=\"ada\"\n\t\t\t\tonConfirm={() => setDeleteOpen(false)}\n\t\t\t/>\n\t\t</AppShell>\n\t)\n}"
20688
+ "code": "const sections = [\n\t{ id: \"profile\", label: \"Profile\" },\n\t{ id: \"notifications\", label: \"Notifications\" },\n\t{ id: \"payouts\", label: \"Payouts\" },\n\t{ id: \"account\", label: \"Account\" },\n]\n\nconst timezones = [\n\t{ value: \"europe-london\", label: \"London (GMT+1)\" },\n\t{ value: \"europe-paris\", label: \"Paris (GMT+2)\" },\n\t{ value: \"america-new-york\", label: \"New York (GMT−4)\" },\n\t{ value: \"america-los-angeles\", label: \"Los Angeles (GMT−7)\" },\n\t{ value: \"asia-tokyo\", label: \"Tokyo (GMT+9)\" },\n]\n\nconst notifications = [\n\t{ name: \"notifyComments\", title: \"New comments\", description: \"When someone comments on your work\", on: true },\n\t{ name: \"notifyInvoicePaid\", title: \"Invoice paid\", description: \"When a customer pays an invoice\", on: true },\n\t{ name: \"notifyMembers\", title: \"New members\", description: \"When someone joins your workspace\", on: false },\n\t{ name: \"notifyDigest\", title: \"Weekly digest\", description: \"Activity and usage, every Monday\", on: true },\n\t{ name: \"notifyProduct\", title: \"Product updates\", description: \"New Atelier features\", on: false },\n]\n\nconst payouts = [\n\t{ label: \"Next payout\", value: \"$1,284.60\", note: \"Friday, Oct 16\" },\n\t{ label: \"Pending\", value: \"$412.00\", note: \"Clears in 7 days\" },\n\t{ label: \"Paid this year\", value: \"$18,920.00\", note: \"Across 214 invoices\" },\n]\n\nexport function SettingsPage() {\n\tconst [activeSection, setActiveSection] = useState(\"profile\")\n\tconst [deleteOpen, setDeleteOpen] = useState(false)\n\n\treturn (\n\t\t<AppShell\n\t\t\t// Sidebar hides below lg, so the same destinations ride in the shell's\n\t\t\t// mobile slots — without them the route has no navigation under 1024px.\n\t\t\theader={\n\t\t\t\t<AppHeader\n\t\t\t\t\ttitle={\n\t\t\t\t\t\t<Row gap={1} align=\"center\">\n\t\t\t\t\t\t\t<Icon name=\"gem\" variant=\"solid\" className=\"text-xl\" />\n\t\t\t\t\t\t\t<span className=\"text-title-sm\">Atelier</span>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t}\n\t\t\t\t\tactions={<Avatar variant=\"art\" size=\"sm\" fallback=\"AL\" alt=\"Ada Lovelace\" />}\n\t\t\t\t/>\n\t\t\t}\n\t\t\tbottomNav={\n\t\t\t\t<BottomNav aria-label=\"Primary\">\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"grid\" />} label=\"Overview\" showLabel render={<a href=\"/\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"folder\" />} label=\"Accounts\" showLabel render={<a href=\"/accounts\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"user\" />} label=\"Customers\" showLabel render={<a href=\"/customers\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"bell\" />} label=\"Activity\" showLabel render={<a href=\"/activity\" />} />\n\t\t\t\t\t<BottomNavItem icon={<Icon name=\"gear\" />} label=\"Settings\" showLabel active render={<a href=\"/settings\" />} />\n\t\t\t\t</BottomNav>\n\t\t\t}\n\t\t\tsidebar={\n\t\t\t\t<Sidebar>\n\t\t\t\t\t<SidebarHeader>\n\t\t\t\t\t\t<SidebarBrand mark={<Icon name=\"gem\" variant=\"solid\" />} label=\"Atelier\" render={<a href=\"/\" />} />\n\t\t\t\t\t</SidebarHeader>\n\t\t\t\t\t<SidebarNav>\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"grid\" />} label=\"Overview\" render={<a href=\"/\" />} />\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"folder\" />} label=\"Accounts\" render={<a href=\"/accounts\" />} />\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"user\" />} label=\"Customers\" render={<a href=\"/customers\" />} />\n\t\t\t\t\t\t<SidebarItem\n\t\t\t\t\t\t\ticon={<Icon name=\"bell\" />}\n\t\t\t\t\t\t\tlabel=\"Activity\"\n\t\t\t\t\t\t\tbadge={<NotificationBadge count={4} tone=\"neutral\" />}\n\t\t\t\t\t\t\trender={<a href=\"/activity\" />}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SidebarItem icon={<Icon name=\"gear\" />} label=\"Settings\" active render={<a href=\"/settings\" />} />\n\t\t\t\t\t</SidebarNav>\n\t\t\t\t\t<SidebarFooter>\n\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\tclassName=\"[--entity-py:0]\"\n\t\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback=\"AL\" alt=\"\" />}\n\t\t\t\t\t\t\ttitle=\"Ada Lovelace\"\n\t\t\t\t\t\t\tsubtitle=\"@ada\"\n\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t<OverflowMenu label=\"Account\" size=\"icon-sm\">\n\t\t\t\t\t\t\t\t\t<MenuItem render={<a href=\"/@ada\" />}>View profile</MenuItem>\n\t\t\t\t\t\t\t\t\t<MenuItem render={<a href=\"/settings\" />}>Settings</MenuItem>\n\t\t\t\t\t\t\t\t\t<MenuSeparator />\n\t\t\t\t\t\t\t\t\t<MenuItem>Sign out</MenuItem>\n\t\t\t\t\t\t\t\t</OverflowMenu>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SidebarFooter>\n\t\t\t\t</Sidebar>\n\t\t\t}\n\t\t>\n\t\t\t{/* No `as=\"main\"`: AppShell already renders <main id=\"main-content\">. */}\n\t\t\t<Page max=\"wide\" gap={4} className=\"py-6\">\n\t\t\t\t<PageHeader\n\t\t\t\t\ttitle=\"Settings\"\n\t\t\t\t\tdescription=\"Manage your profile, notifications, and payouts.\"\n\t\t\t\t/>\n\t\t\t\t<Columns gap={4}>\n\t\t\t\t\t{/* The section rail is a desktop aid; on mobile the sections simply stack. */}\n\t\t\t\t\t<Col span={{ base: 12, lg: 3 }} className=\"hidden lg:block\">\n\t\t\t\t\t\t<SideNav aria-label=\"Settings sections\" className=\"sticky top-6\">\n\t\t\t\t\t\t\t{sections.map((section) => (\n\t\t\t\t\t\t\t\t<SideNavItem\n\t\t\t\t\t\t\t\t\tkey={section.id}\n\t\t\t\t\t\t\t\t\tlabel={section.label}\n\t\t\t\t\t\t\t\t\tactive={activeSection === section.id}\n\t\t\t\t\t\t\t\t\tonClick={() => setActiveSection(section.id)}\n\t\t\t\t\t\t\t\t\trender={<a href={`#${section.id}`} />}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</SideNav>\n\t\t\t\t\t</Col>\n\t\t\t\t\t<Col span={{ base: 12, lg: 9 }}>\n\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t<Card id=\"profile\" className=\"scroll-mt-6\">\n\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Profile</CardTitle>\n\t\t\t\t\t\t\t\t\t<CardDescription>How your team sees you on Atelier.</CardDescription>\n\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t{/* The one explicit save on the page: profile text is drafted, then\n\t\t\t\t\t\t\t\t committed. Toggles below apply the moment they change. */}\n\t\t\t\t\t\t\t\t<Form onSubmit={(event) => event.preventDefault()} className=\"gap-0\">\n\t\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t\t\t\t\t\t<Row gap={2}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Avatar variant=\"art\" size=\"lg\" fallback=\"AL\" alt=\"Ada Lovelace\" />\n\t\t\t\t\t\t\t\t\t\t\t\t<Stack gap={0.5} align=\"start\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Button type=\"button\" variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tUpload photo\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Button type=\"button\" variant=\"ghost\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tRemove\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-caption text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tSquare PNG or JPG, at least 400 px.\n\t\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 2 }} gap={3}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Field label=\"Display name\" name=\"displayName\" required>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Input defaultValue=\"Ada Lovelace\" autoComplete=\"name\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t\t\t\t\t<Field label=\"Handle\" name=\"handle\" description=\"atelier.com/@ada\" required>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Input defaultValue=\"ada\" autoComplete=\"username\" spellCheck={false} />\n\t\t\t\t\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t\t\t\t\t<Field label=\"Bio\" name=\"bio\" description=\"Shown on your profile and in comments.\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Textarea\n\t\t\t\t\t\t\t\t\t\t\t\t\trows={3}\n\t\t\t\t\t\t\t\t\t\t\t\t\tdefaultValue=\"Product designer working on tools for small teams. Writing about process every month.\"\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 2 }} gap={3}>\n\t\t\t\t\t\t\t\t\t\t\t\t<Field label=\"Timezone\" name=\"timezone\" description=\"Due dates use this zone.\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Select defaultValue=\"europe-london\" items={timezones}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectTrigger />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{timezones.map((zone) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<SelectItem key={zone.value} value={zone.value}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{zone.label}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Select>\n\t\t\t\t\t\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t\t\t<CardFooter className=\"justify-end\">\n\t\t\t\t\t\t\t\t\t\t<Button type=\"submit\">Save profile</Button>\n\t\t\t\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t\t\t\t</Form>\n\t\t\t\t\t\t\t</Card>\n\n\t\t\t\t\t\t\t<Card id=\"notifications\" className=\"scroll-mt-6\">\n\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Notifications</CardTitle>\n\t\t\t\t\t\t\t\t\t<CardDescription>\n\t\t\t\t\t\t\t\t\t\tEmail updates about your projects. Changes save automatically.\n\t\t\t\t\t\t\t\t\t</CardDescription>\n\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t\t{notifications.map((item) => (\n\t\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\t\tkey={item.name}\n\t\t\t\t\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\t\t\t\t\tsubtitle={item.description}\n\t\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t\t<Switch\n\t\t\t\t\t\t\t\t\t\t\t\t\tname={item.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\tdefaultChecked={item.on}\n\t\t\t\t\t\t\t\t\t\t\t\t\taria-label={item.title}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t\t</Card>\n\n\t\t\t\t\t\t\t<Card id=\"payouts\" className=\"scroll-mt-6\">\n\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Payouts</CardTitle>\n\t\t\t\t\t\t\t\t\t<CardDescription>Paid in USD every Friday to Chase checking ••4821.</CardDescription>\n\t\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\" render={<a href=\"/settings/payouts\" />}>\n\t\t\t\t\t\t\t\t\t\t\tManage payouts\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 3 }} gap={3}>\n\t\t\t\t\t\t\t\t\t\t{payouts.map((stat) => (\n\t\t\t\t\t\t\t\t\t\t\t<Stack key={stat.label} gap={0.5}>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-body-sm text-muted-foreground\">{stat.label}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-mono-lg text-foreground\">{stat.value}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-caption text-muted-foreground\">{stat.note}</p>\n\t\t\t\t\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t</Card>\n\n\t\t\t\t\t\t\t<Card id=\"account\" className=\"scroll-mt-6\">\n\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Account</CardTitle>\n\t\t\t\t\t\t\t\t\t<CardDescription>Sign-in details and your data.</CardDescription>\n\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t<CardList>\n\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\tleading={<Icon name=\"mail\" className=\"text-lg text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t\t\ttitle=\"Email\"\n\t\t\t\t\t\t\t\t\t\tsubtitle=\"ada@atelier.art\"\n\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\t\tChange\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\t\t\t\tleading={<Icon name=\"download\" className=\"text-lg text-muted-foreground\" />}\n\t\t\t\t\t\t\t\t\t\ttitle=\"Export data\"\n\t\t\t\t\t\t\t\t\t\tsubtitle=\"Projects, invoices, and members\"\n\t\t\t\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\t\tExport\n\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</CardList>\n\t\t\t\t\t\t\t</Card>\n\n\t\t\t\t\t\t\t<Card>\n\t\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t\t<CardTitle render={<h2 />}>Delete account</CardTitle>\n\t\t\t\t\t\t\t\t\t<CardDescription>\n\t\t\t\t\t\t\t\t\t\tPermanently remove your profile, projects, and billing history. Files you shared\n\t\t\t\t\t\t\t\t\t\tstay with your team.\n\t\t\t\t\t\t\t\t\t</CardDescription>\n\t\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t\t<CardFooter className=\"justify-end\">\n\t\t\t\t\t\t\t\t\t<Button variant=\"destructive\" onClick={() => setDeleteOpen(true)}>\n\t\t\t\t\t\t\t\t\t\tDelete account\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t\t\t</Card>\n\t\t\t\t\t\t</Stack>\n\t\t\t\t\t</Col>\n\t\t\t\t</Columns>\n\t\t\t</Page>\n\t\t\t<DestructiveActionModal\n\t\t\t\topen={deleteOpen}\n\t\t\t\tonOpenChange={setDeleteOpen}\n\t\t\t\ttitle=\"Delete your account?\"\n\t\t\t\tdescription=\"This permanently removes your profile, 12 projects, and billing history. Your final invoice is still sent. This can't be undone.\"\n\t\t\t\tconfirmLabel=\"Delete account\"\n\t\t\t\tconfirmingLabel=\"Deleting…\"\n\t\t\t\tconfirmText=\"ada\"\n\t\t\t\tonConfirm={() => setDeleteOpen(false)}\n\t\t\t/>\n\t\t</AppShell>\n\t)\n}"
20556
20689
  }
20557
20690
  ]
20558
20691
  },
@@ -20661,14 +20794,14 @@
20661
20794
  "notes": [
20662
20795
  "Copied from the SettingsPage page shell's Delete account section.",
20663
20796
  "The button never acts directly: it opens the modal, whose confirm stays disabled until the handle is typed.",
20664
- "The description says what is lost AND what is kept (\"Collectors keep the editions they own\"), which is what people want to know before they decide."
20797
+ "The description says what is lost AND what is kept (\"Files you shared stay with your team\"), which is what people want to know before they decide."
20665
20798
  ],
20666
20799
  "a11y": [
20667
20800
  "DestructiveActionModal is an alertdialog: focus opens on Cancel and the backdrop does not dismiss it.",
20668
20801
  "The CardTitle is an h2, so the section is reachable by heading."
20669
20802
  ],
20670
20803
  "imports": "import { useState } from \"react\"\nimport { Button, Card, CardDescription, CardFooter, CardHeader, CardTitle, cn, DestructiveActionModal } from \"@sableui/react\"",
20671
- "code": "const account = { handle: \"ada\", editions: 12 }\n\nexport interface DangerZoneBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\t/** Called once the person has typed the handle and confirmed. */\n\tonDelete?: () => void\n}\n\nexport function DangerZoneBlock({ className, framed = true, onDelete }: DangerZoneBlockProps) {\n\tconst [open, setOpen] = useState(false)\n\treturn (\n\t\t<>\n\t\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-2xl\", className)}>\n\t\t\t\t<CardHeader>\n\t\t\t\t\t<CardTitle render={<h2 />}>Delete account</CardTitle>\n\t\t\t\t\t<CardDescription>\n\t\t\t\t\t\tPermanently remove your profile, editions, and sales history. Collectors keep the editions they own.\n\t\t\t\t\t</CardDescription>\n\t\t\t\t</CardHeader>\n\t\t\t\t<CardFooter actions className=\"justify-end\">\n\t\t\t\t\t<Button variant=\"destructive\" onClick={() => setOpen(true)}>\n\t\t\t\t\t\tDelete account\n\t\t\t\t\t</Button>\n\t\t\t\t</CardFooter>\n\t\t\t</Card>\n\t\t\t<DestructiveActionModal\n\t\t\t\topen={open}\n\t\t\t\tonOpenChange={setOpen}\n\t\t\t\ttitle=\"Delete your account?\"\n\t\t\t\tdescription={`This permanently removes your profile, ${account.editions} editions, and sales history. Pending payouts are still sent. This can't be undone.`}\n\t\t\t\tconfirmLabel=\"Delete account\"\n\t\t\t\tconfirmingLabel=\"Deleting…\"\n\t\t\t\tconfirmText={account.handle}\n\t\t\t\tonConfirm={() => {\n\t\t\t\t\tsetOpen(false)\n\t\t\t\t\tonDelete?.()\n\t\t\t\t}}\n\t\t\t/>\n\t\t</>\n\t)\n}"
20804
+ "code": "const account = { handle: \"ada\", projects: 12 }\n\nexport interface DangerZoneBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\t/** Called once the person has typed the handle and confirmed. */\n\tonDelete?: () => void\n}\n\nexport function DangerZoneBlock({ className, framed = true, onDelete }: DangerZoneBlockProps) {\n\tconst [open, setOpen] = useState(false)\n\treturn (\n\t\t<>\n\t\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-2xl\", className)}>\n\t\t\t\t<CardHeader>\n\t\t\t\t\t<CardTitle render={<h2 />}>Delete account</CardTitle>\n\t\t\t\t\t<CardDescription>\n\t\t\t\t\t\tPermanently remove your profile, projects, and billing history. Files you shared stay with your team.\n\t\t\t\t\t</CardDescription>\n\t\t\t\t</CardHeader>\n\t\t\t\t<CardFooter actions className=\"justify-end\">\n\t\t\t\t\t<Button variant=\"destructive\" onClick={() => setOpen(true)}>\n\t\t\t\t\t\tDelete account\n\t\t\t\t\t</Button>\n\t\t\t\t</CardFooter>\n\t\t\t</Card>\n\t\t\t<DestructiveActionModal\n\t\t\t\topen={open}\n\t\t\t\tonOpenChange={setOpen}\n\t\t\t\ttitle=\"Delete your account?\"\n\t\t\t\tdescription={`This permanently removes your profile, ${account.projects} projects, and billing history. Your final invoice is still sent. This can't be undone.`}\n\t\t\t\tconfirmLabel=\"Delete account\"\n\t\t\t\tconfirmingLabel=\"Deleting…\"\n\t\t\t\tconfirmText={account.handle}\n\t\t\t\tonConfirm={() => {\n\t\t\t\t\tsetOpen(false)\n\t\t\t\t\tonDelete?.()\n\t\t\t\t}}\n\t\t\t/>\n\t\t</>\n\t)\n}"
20672
20805
  },
20673
20806
  {
20674
20807
  "name": "DeviceComparisonBlock",
@@ -20733,7 +20866,7 @@
20733
20866
  "The contact link is a real <a> inside the lede sentence."
20734
20867
  ],
20735
20868
  "imports": "import { useId } from \"react\"\nimport { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, cn, Region, Stack } from \"@sableui/react\"",
20736
- "code": "const intro = {\n\ttitle: \"Questions, answered\",\n\tlede: \"Can't find what you need?\",\n\tcontact: { label: \"Write to us\", href: \"/contact\" },\n}\n\nconst questions = [\n\t{ q: \"Is there a free plan?\", a: \"Yes. Starter is free forever for three live editions, with no fees on your first $1,000 in sales.\" },\n\t{ q: \"Can I change plans later?\", a: \"Any time. Upgrades apply immediately and are prorated; downgrades take effect at the end of the billing period.\" },\n\t{ q: \"Who owns the work I sell?\", a: \"You do. Collectors own the editions they buy, and you keep the rights to the work itself.\" },\n\t{ q: \"How do payouts work?\", a: \"Sales clear after seven days and are paid out every Friday to the account you connect in settings.\" },\n]\n\nexport interface FaqBlockProps {\n\tclassName?: string\n}\n\nexport function FaqBlock({ className }: FaqBlockProps) {\n\tconst titleId = useId()\n\treturn (\n\t\t<Region as=\"section\" aria-labelledby={titleId} max=\"content\" className={cn(\"w-full py-16\", className)}>\n\t\t\t<div className=\"grid gap-10 md:grid-cols-[1fr_2fr]\">\n\t\t\t\t<Stack gap={2}>\n\t\t\t\t\t<h2 id={titleId} className=\"text-balance text-heading-2\">\n\t\t\t\t\t\t{intro.title}\n\t\t\t\t\t</h2>\n\t\t\t\t\t<p className=\"text-body-md text-muted-foreground\">\n\t\t\t\t\t\t{intro.lede}{\" \"}\n\t\t\t\t\t\t<a href={intro.contact.href} className=\"text-foreground underline underline-offset-4 motion-interactive hover:text-muted-foreground\">\n\t\t\t\t\t\t\t{intro.contact.label}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t.\n\t\t\t\t\t</p>\n\t\t\t\t</Stack>\n\t\t\t\t<Accordion>\n\t\t\t\t\t{questions.map((item) => (\n\t\t\t\t\t\t<AccordionItem key={item.q} value={item.q}>\n\t\t\t\t\t\t\t<AccordionTrigger>{item.q}</AccordionTrigger>\n\t\t\t\t\t\t\t<AccordionPanel>{item.a}</AccordionPanel>\n\t\t\t\t\t\t</AccordionItem>\n\t\t\t\t\t))}\n\t\t\t\t</Accordion>\n\t\t\t</div>\n\t\t</Region>\n\t)\n}"
20869
+ "code": "const intro = {\n\ttitle: \"Questions, answered\",\n\tlede: \"Can't find what you need?\",\n\tcontact: { label: \"Write to us\", href: \"/contact\" },\n}\n\nconst questions = [\n\t{ q: \"Is there a free plan?\", a: \"Yes. Starter is free forever for three active projects, with no card needed to sign up.\" },\n\t{ q: \"Can I change plans later?\", a: \"Any time. Upgrades apply immediately and are prorated; downgrades take effect at the end of the billing period.\" },\n\t{ q: \"Who owns the data I add?\", a: \"You do. You can export everything in your workspace at any time, and we never sell it.\" },\n\t{ q: \"How does billing work?\", a: \"Plans are billed per seat at the start of each period, to the card or account you add in settings.\" },\n]\n\nexport interface FaqBlockProps {\n\tclassName?: string\n}\n\nexport function FaqBlock({ className }: FaqBlockProps) {\n\tconst titleId = useId()\n\treturn (\n\t\t<Region as=\"section\" aria-labelledby={titleId} max=\"content\" className={cn(\"w-full py-16\", className)}>\n\t\t\t<div className=\"grid gap-10 md:grid-cols-[1fr_2fr]\">\n\t\t\t\t<Stack gap={2}>\n\t\t\t\t\t<h2 id={titleId} className=\"text-balance text-heading-2\">\n\t\t\t\t\t\t{intro.title}\n\t\t\t\t\t</h2>\n\t\t\t\t\t<p className=\"text-body-md text-muted-foreground\">\n\t\t\t\t\t\t{intro.lede}{\" \"}\n\t\t\t\t\t\t<a href={intro.contact.href} className=\"text-foreground underline underline-offset-4 motion-interactive hover:text-muted-foreground\">\n\t\t\t\t\t\t\t{intro.contact.label}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t.\n\t\t\t\t\t</p>\n\t\t\t\t</Stack>\n\t\t\t\t<Accordion>\n\t\t\t\t\t{questions.map((item) => (\n\t\t\t\t\t\t<AccordionItem key={item.q} value={item.q}>\n\t\t\t\t\t\t\t<AccordionTrigger>{item.q}</AccordionTrigger>\n\t\t\t\t\t\t\t<AccordionPanel>{item.a}</AccordionPanel>\n\t\t\t\t\t\t</AccordionItem>\n\t\t\t\t\t))}\n\t\t\t\t</Accordion>\n\t\t\t</div>\n\t\t</Region>\n\t)\n}"
20737
20870
  },
20738
20871
  {
20739
20872
  "name": "FeatureGridBlock",
@@ -20799,7 +20932,7 @@
20799
20932
  "Arrowing through the rings announces each goal; the legend names every ring's colour."
20800
20933
  ],
20801
20934
  "imports": "import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, cn, RadialChart } from \"@sableui/react\"",
20802
- "code": "const goals = [\n\t{ goal: \"Releases\", progress: 82 },\n\t{ goal: \"Collectors\", progress: 64 },\n\t{ goal: \"Revenue\", progress: 91 },\n]\n\nexport interface GoalProgressBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n}\n\nexport function GoalProgressBlock({ className, framed = true }: GoalProgressBlockProps) {\n\tconst behind = goals.filter((g) => g.progress < 70)\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-sm\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle render={<h2 />}>Quarter goals</CardTitle>\n\t\t\t\t<CardDescription>Progress toward each target, in percent.</CardDescription>\n\t\t\t</CardHeader>\n\t\t\t<CardContent>\n\t\t\t\t<RadialChart label=\"Progress toward quarter goals\" data={goals} index=\"goal\" valueKey=\"progress\" max={100} className=\"h-64\" valueFormatter={(v) => `${v}%`} />\n\t\t\t</CardContent>\n\t\t\t<CardFooter className=\"text-body-sm text-muted-foreground\">\n\t\t\t\t{behind.length ? `${behind.map((g) => g.goal).join(\" and \")} behind pace.` : \"Every goal on pace.\"}\n\t\t\t</CardFooter>\n\t\t</Card>\n\t)\n}"
20935
+ "code": "const goals = [\n\t{ goal: \"Releases\", progress: 82 },\n\t{ goal: \"Customers\", progress: 64 },\n\t{ goal: \"Revenue\", progress: 91 },\n]\n\nexport interface GoalProgressBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n}\n\nexport function GoalProgressBlock({ className, framed = true }: GoalProgressBlockProps) {\n\tconst behind = goals.filter((g) => g.progress < 70)\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-sm\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle render={<h2 />}>Quarter goals</CardTitle>\n\t\t\t\t<CardDescription>Progress toward each target, in percent.</CardDescription>\n\t\t\t</CardHeader>\n\t\t\t<CardContent>\n\t\t\t\t<RadialChart label=\"Progress toward quarter goals\" data={goals} index=\"goal\" valueKey=\"progress\" max={100} className=\"h-64\" valueFormatter={(v) => `${v}%`} />\n\t\t\t</CardContent>\n\t\t\t<CardFooter className=\"text-body-sm text-muted-foreground\">\n\t\t\t\t{behind.length ? `${behind.map((g) => g.goal).join(\" and \")} behind pace.` : \"Every goal on pace.\"}\n\t\t\t</CardFooter>\n\t\t</Card>\n\t)\n}"
20803
20936
  },
20804
20937
  {
20805
20938
  "name": "HeroBlock",
@@ -20971,7 +21104,7 @@
20971
21104
  "Each Avatar's alt is the actor's name."
20972
21105
  ],
20973
21106
  "imports": "import { Avatar, Card, CardList, cn, Entity, StatusDot } from \"@sableui/react\"",
20974
- "code": "const notifications = [\n\t{ initials: \"MO\", name: \"Mara Okafor\", what: \"collected your edition\", when: \"2m\", unread: true },\n\t{ initials: \"SR\", name: \"Sam Rivera\", what: \"left a comment on Dusk\", when: \"1h\", unread: false },\n]\n\nexport interface NotificationListBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a Popover or Sheet that already frames it. */\n\tframed?: boolean\n}\n\nexport function NotificationListBlock({ className, framed = true }: NotificationListBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs overflow-hidden\", className)}>\n\t\t\t<CardList>\n\t\t\t\t{notifications.map((n) => (\n\t\t\t\t\t<Entity\n\t\t\t\t\t\tkey={n.name}\n\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback={n.initials} alt={n.name} />}\n\t\t\t\t\t\ttitle={\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t{n.name}\n\t\t\t\t\t\t\t\t{n.unread ? <span className=\"sr-only\"> (unread)</span> : null}\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsubtitle={n.what}\n\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t<span className=\"flex items-center gap-1.5 text-caption text-muted-foreground\">\n\t\t\t\t\t\t\t\t{n.unread ? <StatusDot tone=\"info\" /> : null}\n\t\t\t\t\t\t\t\t{n.when}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</CardList>\n\t\t</Card>\n\t)\n}"
21107
+ "code": "const notifications = [\n\t{ initials: \"MO\", name: \"Mara Okafor\", what: \"invited you to Q3 planning\", when: \"2m\", unread: true },\n\t{ initials: \"SR\", name: \"Sam Rivera\", what: \"left a comment on Dusk\", when: \"1h\", unread: false },\n]\n\nexport interface NotificationListBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a Popover or Sheet that already frames it. */\n\tframed?: boolean\n}\n\nexport function NotificationListBlock({ className, framed = true }: NotificationListBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs overflow-hidden\", className)}>\n\t\t\t<CardList>\n\t\t\t\t{notifications.map((n) => (\n\t\t\t\t\t<Entity\n\t\t\t\t\t\tkey={n.name}\n\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback={n.initials} alt={n.name} />}\n\t\t\t\t\t\ttitle={\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t{n.name}\n\t\t\t\t\t\t\t\t{n.unread ? <span className=\"sr-only\"> (unread)</span> : null}\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsubtitle={n.what}\n\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t<span className=\"flex items-center gap-1.5 text-caption text-muted-foreground\">\n\t\t\t\t\t\t\t\t{n.unread ? <StatusDot tone=\"info\" /> : null}\n\t\t\t\t\t\t\t\t{n.when}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</CardList>\n\t\t</Card>\n\t)\n}"
20975
21108
  },
20976
21109
  {
20977
21110
  "name": "NotificationSettingsBlock",
@@ -21057,13 +21190,13 @@
21057
21190
  "The ToggleGroup is labelled \"Billing period\"; prices update in place and stay in the reading order of each card."
21058
21191
  ],
21059
21192
  "imports": "import { useState, useId } from \"react\"\nimport { Badge, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, cn, Grid, Icon, Region, Stack, Toggle, ToggleGroup } from \"@sableui/react\"",
21060
- "code": "const tiers = [\n\t{\n\t\tid: \"starter\",\n\t\tname: \"Starter\",\n\t\tblurb: \"For one person shipping their first editions.\",\n\t\tmonthly: 0,\n\t\tyearly: 0,\n\t\tcta: \"Start for free\",\n\t\tfeatured: false,\n\t\tfeatures: [\"3 live editions\", \"Collector profiles\", \"Community support\"],\n\t},\n\t{\n\t\tid: \"studio\",\n\t\tname: \"Studio\",\n\t\tblurb: \"For working artists with a steady release schedule.\",\n\t\tmonthly: 20,\n\t\tyearly: 16,\n\t\tcta: \"Start free trial\",\n\t\tfeatured: true,\n\t\tfeatures: [\"Unlimited editions\", \"Scheduled drops\", \"Sales analytics\", \"Priority support\"],\n\t},\n\t{\n\t\tid: \"collective\",\n\t\tname: \"Collective\",\n\t\tblurb: \"For galleries and teams releasing together.\",\n\t\tmonthly: 48,\n\t\tyearly: 40,\n\t\tcta: \"Talk to sales\",\n\t\tfeatured: false,\n\t\tfeatures: [\"Everything in Studio\", \"Up to 10 seats\", \"Shared payouts\", \"Roles and approvals\"],\n\t},\n]\n\ntype Period = \"monthly\" | \"yearly\"\n\nexport interface PricingTableBlockProps {\n\tclassName?: string\n\t/** Called with the chosen tier and the billing period showing. */\n\tonChoose?: (tier: string, period: Period) => void\n}\n\nexport function PricingTableBlock({ className, onChoose }: PricingTableBlockProps) {\n\tconst [period, setPeriod] = useState<Period>(\"yearly\")\n\tconst titleId = useId()\n\treturn (\n\t\t<Region as=\"section\" aria-labelledby={titleId} max=\"wide\" className={cn(\"w-full py-16\", className)}>\n\t\t\t<Stack gap={10}>\n\t\t\t\t<Stack gap={4} align=\"center\" className=\"text-center\">\n\t\t\t\t\t<h2 id={titleId} className=\"text-balance text-heading-1\">\n\t\t\t\t\t\tPricing that grows with your practice\n\t\t\t\t\t</h2>\n\t\t\t\t\t<p className=\"max-w-(--measure-prose) text-body-lg text-muted-foreground\">\n\t\t\t\t\t\tEvery plan includes unlimited collectors and no fees on your first $1,000 in sales.\n\t\t\t\t\t</p>\n\t\t\t\t\t<ToggleGroup value={[period]} onValueChange={(v: unknown[]) => v[0] && setPeriod(v[0] as Period)} aria-label=\"Billing period\">\n\t\t\t\t\t\t<Toggle value=\"monthly\">Monthly</Toggle>\n\t\t\t\t\t\t<Toggle value=\"yearly\">Yearly · save 20%</Toggle>\n\t\t\t\t\t</ToggleGroup>\n\t\t\t\t</Stack>\n\t\t\t\t<Grid cols={{ base: 1, md: 3 }} gap={4} className=\"items-stretch\">\n\t\t\t\t\t{tiers.map((tier) => (\n\t\t\t\t\t\t<Card\n\t\t\t\t\t\t\tkey={tier.id}\n\t\t\t\t\t\t\trender={<article />}\n\t\t\t\t\t\t\tclassName={cn(\"flex flex-col\", tier.featured && \"border-primary ring-1 ring-primary\")}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardTitle render={<h3 />}>{tier.name}</CardTitle>\n\t\t\t\t\t\t\t\t<CardDescription>{tier.blurb}</CardDescription>\n\t\t\t\t\t\t\t\t{tier.featured ? (\n\t\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t\t<Badge tone=\"edition\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\tMost popular\n\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardContent className=\"flex flex-1 flex-col gap-5\">\n\t\t\t\t\t\t\t\t<div className=\"flex items-baseline gap-1.5\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-heading-2\">${tier[period]}</span>\n\t\t\t\t\t\t\t\t\t<span className=\"text-body-sm text-muted-foreground\">per seat / month</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<ul className=\"flex flex-col gap-2 text-body-sm\">\n\t\t\t\t\t\t\t\t\t{tier.features.map((feature) => (\n\t\t\t\t\t\t\t\t\t\t<li key={feature} className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"check\" className=\"text-success\" />\n\t\t\t\t\t\t\t\t\t\t\t{feature}\n\t\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t<CardFooter>\n\t\t\t\t\t\t\t\t<Button className=\"w-full\" variant={tier.featured ? \"primary\" : \"outline\"} onClick={() => onChoose?.(tier.id, period)}>\n\t\t\t\t\t\t\t\t\t{tier.cta}\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t))}\n\t\t\t\t</Grid>\n\t\t\t</Stack>\n\t\t</Region>\n\t)\n}"
21193
+ "code": "const tiers = [\n\t{\n\t\tid: \"starter\",\n\t\tname: \"Starter\",\n\t\tblurb: \"For one person getting a first project going.\",\n\t\tmonthly: 0,\n\t\tyearly: 0,\n\t\tcta: \"Start for free\",\n\t\tfeatured: false,\n\t\tfeatures: [\"3 active projects\", \"Shared docs\", \"Community support\"],\n\t},\n\t{\n\t\tid: \"studio\",\n\t\tname: \"Studio\",\n\t\tblurb: \"For small teams shipping on a steady schedule.\",\n\t\tmonthly: 20,\n\t\tyearly: 16,\n\t\tcta: \"Start free trial\",\n\t\tfeatured: true,\n\t\tfeatures: [\"Unlimited projects\", \"Scheduled reports\", \"Usage analytics\", \"Priority support\"],\n\t},\n\t{\n\t\tid: \"business\",\n\t\tname: \"Business\",\n\t\tblurb: \"For companies running several teams at once.\",\n\t\tmonthly: 48,\n\t\tyearly: 40,\n\t\tcta: \"Talk to sales\",\n\t\tfeatured: false,\n\t\tfeatures: [\"Everything in Studio\", \"Up to 10 seats\", \"SSO and audit log\", \"Roles and approvals\"],\n\t},\n]\n\ntype Period = \"monthly\" | \"yearly\"\n\nexport interface PricingTableBlockProps {\n\tclassName?: string\n\t/** Called with the chosen tier and the billing period showing. */\n\tonChoose?: (tier: string, period: Period) => void\n}\n\nexport function PricingTableBlock({ className, onChoose }: PricingTableBlockProps) {\n\tconst [period, setPeriod] = useState<Period>(\"yearly\")\n\tconst titleId = useId()\n\treturn (\n\t\t<Region as=\"section\" aria-labelledby={titleId} max=\"wide\" className={cn(\"w-full py-16\", className)}>\n\t\t\t<Stack gap={10}>\n\t\t\t\t<Stack gap={4} align=\"center\" className=\"text-center\">\n\t\t\t\t\t<h2 id={titleId} className=\"text-balance text-heading-1\">\n\t\t\t\t\t\tPricing that grows with your team\n\t\t\t\t\t</h2>\n\t\t\t\t\t<p className=\"max-w-(--measure-prose) text-body-lg text-muted-foreground\">\n\t\t\t\t\t\tEvery plan includes unlimited guests, and you can switch plans at any time.\n\t\t\t\t\t</p>\n\t\t\t\t\t<ToggleGroup value={[period]} onValueChange={(v: unknown[]) => v[0] && setPeriod(v[0] as Period)} aria-label=\"Billing period\">\n\t\t\t\t\t\t<Toggle value=\"monthly\">Monthly</Toggle>\n\t\t\t\t\t\t<Toggle value=\"yearly\">Yearly · save 20%</Toggle>\n\t\t\t\t\t</ToggleGroup>\n\t\t\t\t</Stack>\n\t\t\t\t<Grid cols={{ base: 1, md: 3 }} gap={4} className=\"items-stretch\">\n\t\t\t\t\t{tiers.map((tier) => (\n\t\t\t\t\t\t<Card\n\t\t\t\t\t\t\tkey={tier.id}\n\t\t\t\t\t\t\trender={<article />}\n\t\t\t\t\t\t\tclassName={cn(\"flex flex-col\", tier.featured && \"outline-primary ring-1 ring-primary\")}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t\t\t<CardTitle render={<h3 />}>{tier.name}</CardTitle>\n\t\t\t\t\t\t\t\t<CardDescription>{tier.blurb}</CardDescription>\n\t\t\t\t\t\t\t\t{tier.featured ? (\n\t\t\t\t\t\t\t\t\t<CardAction>\n\t\t\t\t\t\t\t\t\t\t<Badge tone=\"1\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\t\tMost popular\n\t\t\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t\t</CardAction>\n\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t</CardHeader>\n\t\t\t\t\t\t\t<CardContent className=\"flex flex-1 flex-col gap-5\">\n\t\t\t\t\t\t\t\t<div className=\"flex items-baseline gap-1.5\">\n\t\t\t\t\t\t\t\t\t<span className=\"text-heading-2\">${tier[period]}</span>\n\t\t\t\t\t\t\t\t\t<span className=\"text-body-sm text-muted-foreground\">per seat / month</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<ul className=\"flex flex-col gap-2 text-body-sm\">\n\t\t\t\t\t\t\t\t\t{tier.features.map((feature) => (\n\t\t\t\t\t\t\t\t\t\t<li key={feature} className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"check\" className=\"text-success\" />\n\t\t\t\t\t\t\t\t\t\t\t{feature}\n\t\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t\t<CardFooter>\n\t\t\t\t\t\t\t\t<Button className=\"w-full\" variant={tier.featured ? \"primary\" : \"outline\"} onClick={() => onChoose?.(tier.id, period)}>\n\t\t\t\t\t\t\t\t\t{tier.cta}\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</CardFooter>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t))}\n\t\t\t\t</Grid>\n\t\t\t</Stack>\n\t\t</Region>\n\t)\n}"
21061
21194
  },
21062
21195
  {
21063
21196
  "name": "ProductCardBlock",
21064
21197
  "path": "blocks/product-card-block.tsx",
21065
- "summary": "A product card: edge-to-edge artwork, title and maker, an edition Badge, and the price beside a single buy action.",
21066
- "intent": "Reach for this for a product, edition or listing in a grid or a featured slot. For a row in a dense list, use an Entity with the image as `leading` instead.",
21198
+ "summary": "A product card: edge-to-edge product image, title and maker, a status Badge, and the price beside a single buy action.",
21199
+ "intent": "Reach for this for a product or listing in a grid or a featured slot. For a row in a dense list, use an Entity with the image as `leading` instead.",
21067
21200
  "family": "Commerce",
21068
21201
  "kind": "card",
21069
21202
  "width": {
@@ -21071,7 +21204,7 @@
21071
21204
  "max": "20rem"
21072
21205
  },
21073
21206
  "notes": [
21074
- "The artwork runs to the card's edges; the card clips it to its own corners (overflow-hidden).",
21207
+ "The image runs to the card's edges; the card clips it to its own corners (overflow-hidden).",
21075
21208
  "Prices are `text-mono-lg` — the metric mono tier with tabular figures — never a heading tier.",
21076
21209
  "The placeholder art is painted from the palette's decorative tones so it follows the theme; swap it for the product image with real alt text."
21077
21210
  ],
@@ -21080,7 +21213,7 @@
21080
21213
  "The card is not a link: it holds its own button. To make the whole card navigate, move the action to the detail page and render the Card as an <a>."
21081
21214
  ],
21082
21215
  "imports": "import { AspectBox, Badge, Button, Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle, cn, MediaImage } from \"@sableui/react\"",
21083
- "code": "// `image` is the product shot URL; left empty, it paints art seeded by the title.\nconst product = { title: \"Untitled (gold rush)\", maker: \"Mara Okafor\", image: \"\", edition: \"12 of 50\", price: \"0.42 SOL\" }\n\n\nexport interface ProductCardBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\t/** The buy action. */\n\tonCollect?: () => void\n}\n\nexport function ProductCardBlock({ className, framed = true, onCollect }: ProductCardBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs overflow-hidden\", className)}>\n\t\t\t<AspectBox ratio={4 / 3}>\n\t\t\t\t<MediaImage src={product.image} seed={product.title} />\n\t\t\t</AspectBox>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle>{product.title}</CardTitle>\n\t\t\t\t<CardDescription>{product.maker}</CardDescription>\n\t\t\t\t<CardAction>\n\t\t\t\t\t<Badge tone=\"edition\">{product.edition}</Badge>\n\t\t\t\t</CardAction>\n\t\t\t</CardHeader>\n\t\t\t<CardFooter className=\"justify-between pt-4\">\n\t\t\t\t<span className=\"text-mono-lg\">{product.price}</span>\n\t\t\t\t<Button onClick={onCollect}>Collect</Button>\n\t\t\t</CardFooter>\n\t\t</Card>\n\t)\n}"
21216
+ "code": "// `image` is the product shot URL; left empty, it paints art seeded by the title.\nconst product = { title: \"Linen throw blanket\", maker: \"Northfield Home\", image: \"\", badge: \"New\", price: \"$64.00\" }\n\n\nexport interface ProductCardBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\t/** The buy action. */\n\tonCollect?: () => void\n}\n\nexport function ProductCardBlock({ className, framed = true, onCollect }: ProductCardBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs overflow-hidden\", className)}>\n\t\t\t<AspectBox ratio={4 / 3}>\n\t\t\t\t<MediaImage src={product.image} seed={product.title} />\n\t\t\t</AspectBox>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle>{product.title}</CardTitle>\n\t\t\t\t<CardDescription>{product.maker}</CardDescription>\n\t\t\t\t<CardAction>\n\t\t\t\t\t<Badge tone=\"1\">{product.badge}</Badge>\n\t\t\t\t</CardAction>\n\t\t\t</CardHeader>\n\t\t\t<CardFooter className=\"justify-between pt-4\">\n\t\t\t\t<span className=\"text-mono-lg\">{product.price}</span>\n\t\t\t\t<Button onClick={onCollect}>Add to cart</Button>\n\t\t\t</CardFooter>\n\t\t</Card>\n\t)\n}"
21084
21217
  },
21085
21218
  {
21086
21219
  "name": "ProfileCardBlock",
@@ -21103,7 +21236,7 @@
21103
21236
  "The banner is decorative (aria-hidden); the Avatar's alt is the person's name."
21104
21237
  ],
21105
21238
  "imports": "import { useState } from \"react\"\nimport { Avatar, Button, Card, CardContent, CardDescription, CardTitle, cn, GradientArt, Icon } from \"@sableui/react\"",
21106
- "code": "const profile = {\n\tname: \"Juno Avery\",\n\tinitials: \"JA\",\n\thandle: \"@juno · Sound and light\",\n\tbio: \"Field recordings, slowed down. New edition every full moon.\",\n\tstats: [\n\t\t{ value: \"128\", label: \"posts\" },\n\t\t{ value: \"4.2k\", label: \"followers\" },\n\t],\n}\n\n\nexport interface ProfileCardBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\t/** Called with the new follow state. */\n\tonFollowChange?: (following: boolean) => void\n\tonShare?: () => void\n}\n\nexport function ProfileCardBlock({ className, framed = true, onFollowChange, onShare }: ProfileCardBlockProps) {\n\tconst [following, setFollowing] = useState(false)\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs overflow-hidden\", className)}>\n\t\t\t{/* Placeholder banner painted from the theme, so it follows every look. Replace with the person's cover image. */}\n\t\t\t<GradientArt aria-hidden seed={profile.handle} className=\"h-20\" />\n\t\t\t{/* pt-0: the avatar rises into the banner above, so the content\n\t\t\t starts at the banner's edge rather than a full pad below it. */}\n\t\t\t<CardContent className=\"flex flex-col gap-3 pt-0\">\n\t\t\t\t<div className=\"flex items-start justify-between\">\n\t\t\t\t\t<Avatar variant=\"art\" size=\"lg\" fallback={profile.initials} alt={profile.name} className=\"-mt-6 ring-4 ring-card\" />\n\t\t\t\t\t<div className=\"flex gap-1.5 pt-3\">\n\t\t\t\t\t\t<Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Share profile\" onClick={onShare}>\n\t\t\t\t\t\t\t<Icon name=\"share\" />\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tvariant={following ? \"secondary\" : \"primary\"}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tsetFollowing(!following)\n\t\t\t\t\t\t\t\tonFollowChange?.(!following)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{following ? \"Following\" : \"Follow\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex flex-col gap-0.5\">\n\t\t\t\t\t<CardTitle>{profile.name}</CardTitle>\n\t\t\t\t\t<CardDescription>{profile.handle}</CardDescription>\n\t\t\t\t</div>\n\t\t\t\t<p className=\"text-body-sm\">{profile.bio}</p>\n\t\t\t\t<div className=\"flex gap-4 text-body-sm\">\n\t\t\t\t\t{profile.stats.map((s) => (\n\t\t\t\t\t\t<span key={s.label}>\n\t\t\t\t\t\t\t<span className=\"text-label-lg\">{s.value}</span> <span className=\"text-muted-foreground\">{s.label}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t)\n}"
21239
+ "code": "const profile = {\n\tname: \"Juno Avery\",\n\tinitials: \"JA\",\n\thandle: \"@juno · Sound and light\",\n\tbio: \"Field recordings, slowed down. New track every full moon.\",\n\tstats: [\n\t\t{ value: \"128\", label: \"posts\" },\n\t\t{ value: \"4.2k\", label: \"followers\" },\n\t],\n}\n\n\nexport interface ProfileCardBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\t/** Called with the new follow state. */\n\tonFollowChange?: (following: boolean) => void\n\tonShare?: () => void\n}\n\nexport function ProfileCardBlock({ className, framed = true, onFollowChange, onShare }: ProfileCardBlockProps) {\n\tconst [following, setFollowing] = useState(false)\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs overflow-hidden\", className)}>\n\t\t\t{/* Placeholder banner painted from the theme, so it follows every look. Replace with the person's cover image. */}\n\t\t\t<GradientArt aria-hidden seed={profile.handle} className=\"h-20\" />\n\t\t\t{/* pt-0: the avatar rises into the banner above, so the content\n\t\t\t starts at the banner's edge rather than a full pad below it. */}\n\t\t\t<CardContent className=\"flex flex-col gap-3 pt-0\">\n\t\t\t\t<div className=\"flex items-start justify-between\">\n\t\t\t\t\t<Avatar variant=\"art\" size=\"lg\" fallback={profile.initials} alt={profile.name} className=\"-mt-6 ring-4 ring-card\" />\n\t\t\t\t\t<div className=\"flex gap-1.5 pt-3\">\n\t\t\t\t\t\t<Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Share profile\" onClick={onShare}>\n\t\t\t\t\t\t\t<Icon name=\"share\" />\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tvariant={following ? \"secondary\" : \"primary\"}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tsetFollowing(!following)\n\t\t\t\t\t\t\t\tonFollowChange?.(!following)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{following ? \"Following\" : \"Follow\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex flex-col gap-0.5\">\n\t\t\t\t\t<CardTitle>{profile.name}</CardTitle>\n\t\t\t\t\t<CardDescription>{profile.handle}</CardDescription>\n\t\t\t\t</div>\n\t\t\t\t<p className=\"text-body-sm\">{profile.bio}</p>\n\t\t\t\t<div className=\"flex gap-4 text-body-sm\">\n\t\t\t\t\t{profile.stats.map((s) => (\n\t\t\t\t\t\t<span key={s.label}>\n\t\t\t\t\t\t\t<span className=\"text-label-lg\">{s.value}</span> <span className=\"text-muted-foreground\">{s.label}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t)\n}"
21107
21240
  },
21108
21241
  {
21109
21242
  "name": "ProfileFormBlock",
@@ -21126,7 +21259,7 @@
21126
21259
  "The photo actions are type=\"button\", so neither one submits the form."
21127
21260
  ],
21128
21261
  "imports": "import { Avatar, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, cn, Field, Form, Grid, Input, Row, Select, SelectContent, SelectItem, SelectTrigger, Stack, Textarea } from \"@sableui/react\"",
21129
- "code": "const profile = {\n\tname: \"Ada Lovelace\",\n\tinitials: \"AL\",\n\thandle: \"ada\",\n\tbio: \"Generative artist working in plotted ink and code. New editions of 25 to 100 every month.\",\n\ttimezone: \"europe-london\",\n}\n\nconst timezones = [\n\t{ value: \"europe-london\", label: \"London (GMT+1)\" },\n\t{ value: \"europe-paris\", label: \"Paris (GMT+2)\" },\n\t{ value: \"america-new-york\", label: \"New York (GMT−4)\" },\n\t{ value: \"america-los-angeles\", label: \"Los Angeles (GMT−7)\" },\n\t{ value: \"asia-tokyo\", label: \"Tokyo (GMT+9)\" },\n]\n\nexport interface ProfileFormBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\t/** Called with the form's fields on Save profile. */\n\tonSave?: (fields: FormData) => void\n}\n\nexport function ProfileFormBlock({ className, framed = true, onSave }: ProfileFormBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-2xl\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle render={<h2 />}>Profile</CardTitle>\n\t\t\t\t<CardDescription>How collectors see you.</CardDescription>\n\t\t\t</CardHeader>\n\t\t\t{/* The one explicit save: profile text is drafted, then committed. */}\n\t\t\t<Form\n\t\t\t\tclassName=\"gap-0\"\n\t\t\t\tonSubmit={(event) => {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\tonSave?.(new FormData(event.currentTarget))\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t<Row gap={2}>\n\t\t\t\t\t\t\t<Avatar variant=\"art\" size=\"lg\" fallback={profile.initials} alt={profile.name} />\n\t\t\t\t\t\t\t<Stack gap={0.5} align=\"start\">\n\t\t\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t\t\t<Button type=\"button\" variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\tUpload photo\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t<Button type=\"button\" variant=\"ghost\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\tRemove\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t<p className=\"text-caption text-muted-foreground\">Square PNG or JPG, at least 400 px.</p>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 2 }} gap={3}>\n\t\t\t\t\t\t\t<Field label=\"Display name\" name=\"displayName\" required>\n\t\t\t\t\t\t\t\t<Input defaultValue={profile.name} autoComplete=\"name\" />\n\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t<Field label=\"Handle\" name=\"handle\" description={`atelier.com/@${profile.handle}`} required>\n\t\t\t\t\t\t\t\t<Input defaultValue={profile.handle} autoComplete=\"username\" spellCheck={false} />\n\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t<Field label=\"Bio\" name=\"bio\" description=\"Shown on your profile and edition pages.\">\n\t\t\t\t\t\t\t<Textarea rows={3} defaultValue={profile.bio} />\n\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 2 }} gap={3}>\n\t\t\t\t\t\t\t<Field label=\"Timezone\" name=\"timezone\" description=\"Release times use this zone.\">\n\t\t\t\t\t\t\t\t<Select defaultValue={profile.timezone} items={timezones}>\n\t\t\t\t\t\t\t\t\t<SelectTrigger />\n\t\t\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t\t\t{timezones.map((zone) => (\n\t\t\t\t\t\t\t\t\t\t\t<SelectItem key={zone.value} value={zone.value}>\n\t\t\t\t\t\t\t\t\t\t\t\t{zone.label}\n\t\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t\t</Select>\n\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t</Grid>\n\t\t\t\t\t</Stack>\n\t\t\t\t</CardContent>\n\t\t\t\t<CardFooter actions className=\"justify-end\">\n\t\t\t\t\t<Button type=\"submit\">Save profile</Button>\n\t\t\t\t</CardFooter>\n\t\t\t</Form>\n\t\t</Card>\n\t)\n}"
21262
+ "code": "const profile = {\n\tname: \"Ada Lovelace\",\n\tinitials: \"AL\",\n\thandle: \"ada\",\n\tbio: \"Product designer working on tools for small teams. Writing about process every month.\",\n\ttimezone: \"europe-london\",\n}\n\nconst timezones = [\n\t{ value: \"europe-london\", label: \"London (GMT+1)\" },\n\t{ value: \"europe-paris\", label: \"Paris (GMT+2)\" },\n\t{ value: \"america-new-york\", label: \"New York (GMT−4)\" },\n\t{ value: \"america-los-angeles\", label: \"Los Angeles (GMT−7)\" },\n\t{ value: \"asia-tokyo\", label: \"Tokyo (GMT+9)\" },\n]\n\nexport interface ProfileFormBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\t/** Called with the form's fields on Save profile. */\n\tonSave?: (fields: FormData) => void\n}\n\nexport function ProfileFormBlock({ className, framed = true, onSave }: ProfileFormBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-2xl\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle render={<h2 />}>Profile</CardTitle>\n\t\t\t\t<CardDescription>How your team sees you.</CardDescription>\n\t\t\t</CardHeader>\n\t\t\t{/* The one explicit save: profile text is drafted, then committed. */}\n\t\t\t<Form\n\t\t\t\tclassName=\"gap-0\"\n\t\t\t\tonSubmit={(event) => {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\tonSave?.(new FormData(event.currentTarget))\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<Stack gap={3}>\n\t\t\t\t\t\t<Row gap={2}>\n\t\t\t\t\t\t\t<Avatar variant=\"art\" size=\"lg\" fallback={profile.initials} alt={profile.name} />\n\t\t\t\t\t\t\t<Stack gap={0.5} align=\"start\">\n\t\t\t\t\t\t\t\t<Row gap={1}>\n\t\t\t\t\t\t\t\t\t<Button type=\"button\" variant=\"outline\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\tUpload photo\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t<Button type=\"button\" variant=\"ghost\" size=\"sm\">\n\t\t\t\t\t\t\t\t\t\tRemove\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t\t\t<p className=\"text-caption text-muted-foreground\">Square PNG or JPG, at least 400 px.</p>\n\t\t\t\t\t\t\t</Stack>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 2 }} gap={3}>\n\t\t\t\t\t\t\t<Field label=\"Display name\" name=\"displayName\" required>\n\t\t\t\t\t\t\t\t<Input defaultValue={profile.name} autoComplete=\"name\" />\n\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t\t<Field label=\"Handle\" name=\"handle\" description={`atelier.com/@${profile.handle}`} required>\n\t\t\t\t\t\t\t\t<Input defaultValue={profile.handle} autoComplete=\"username\" spellCheck={false} />\n\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t</Grid>\n\t\t\t\t\t\t<Field label=\"Bio\" name=\"bio\" description=\"Shown on your profile and in comments.\">\n\t\t\t\t\t\t\t<Textarea rows={3} defaultValue={profile.bio} />\n\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t<Grid cols={{ base: 1, sm: 2 }} gap={3}>\n\t\t\t\t\t\t\t<Field label=\"Timezone\" name=\"timezone\" description=\"Release times use this zone.\">\n\t\t\t\t\t\t\t\t<Select defaultValue={profile.timezone} items={timezones}>\n\t\t\t\t\t\t\t\t\t<SelectTrigger />\n\t\t\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t\t\t{timezones.map((zone) => (\n\t\t\t\t\t\t\t\t\t\t\t<SelectItem key={zone.value} value={zone.value}>\n\t\t\t\t\t\t\t\t\t\t\t\t{zone.label}\n\t\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t\t</Select>\n\t\t\t\t\t\t\t</Field>\n\t\t\t\t\t\t</Grid>\n\t\t\t\t\t</Stack>\n\t\t\t\t</CardContent>\n\t\t\t\t<CardFooter actions className=\"justify-end\">\n\t\t\t\t\t<Button type=\"submit\">Save profile</Button>\n\t\t\t\t</CardFooter>\n\t\t\t</Form>\n\t\t</Card>\n\t)\n}"
21130
21263
  },
21131
21264
  {
21132
21265
  "name": "RevenueBreakdownBlock",
@@ -21149,7 +21282,7 @@
21149
21282
  "The trend Badge states the change as text (+8.2%), not by colour."
21150
21283
  ],
21151
21284
  "imports": "import { Badge, Card, CardAction, CardContent, CardDescription, CardHeader, cn, Grid, SegmentedBar, StatusDot } from \"@sableui/react\"",
21152
- "code": "const revenue = { label: \"Revenue this month\", total: \"$12,480\", change: \"+8.2%\" }\n\nconst segments = [\n\t{ label: \"Editions\", value: 42, tone: \"edition\" },\n\t{ label: \"Collectibles\", value: 27, tone: \"collectible\" },\n\t{ label: \"Standard\", value: 19, tone: \"standard\" },\n\t{ label: \"Commissions\", value: 12, tone: \"info\" },\n] as const\n\nexport interface RevenueBreakdownBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n}\n\nexport function RevenueBreakdownBlock({ className, framed = true }: RevenueBreakdownBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardDescription>{revenue.label}</CardDescription>\n\t\t\t\t<CardAction>\n\t\t\t\t\t<Badge tone=\"success\" size=\"sm\">\n\t\t\t\t\t\t{revenue.change}\n\t\t\t\t\t</Badge>\n\t\t\t\t</CardAction>\n\t\t\t</CardHeader>\n\t\t\t<CardContent className=\"flex flex-col gap-4 pt-2\">\n\t\t\t\t<span className=\"text-mono-xl\">{revenue.total}</span>\n\t\t\t\t<SegmentedBar label=\"Revenue by type\" segments={[...segments]} size=\"lg\" />\n\t\t\t\t{/* Two columns while each entry fits whole, one when they would not:\n\t\t\t\t sized in em, so a larger type or a narrower card drops to one.\n\t\t\t\t aria-hidden: the bar already reads every share aloud. */}\n\t\t\t\t<Grid aria-hidden minItemWidth=\"9.5em\" gap=\"calc(var(--spacing) * 1.5) calc(var(--spacing) * 4)\">\n\t\t\t\t\t{segments.map((s) => (\n\t\t\t\t\t\t<div key={s.label} className=\"flex items-center gap-2 text-body-sm\">\n\t\t\t\t\t\t\t<StatusDot tone={s.tone} />\n\t\t\t\t\t\t\t<span className=\"flex-1 text-muted-foreground\">{s.label}</span>\n\t\t\t\t\t\t\t<span className=\"text-mono-caption\">{s.value}%</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</Grid>\n\t\t\t</CardContent>\n\t\t</Card>\n\t)\n}"
21285
+ "code": "const revenue = { label: \"Revenue this month\", total: \"$12,480\", change: \"+8.2%\" }\n\nconst segments = [\n\t{ label: \"Direct\", value: 42, tone: \"1\" },\n\t{ label: \"Partners\", value: 27, tone: \"2\" },\n\t{ label: \"Referrals\", value: 19, tone: \"3\" },\n\t{ label: \"Resellers\", value: 12, tone: \"info\" },\n] as const\n\nexport interface RevenueBreakdownBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n}\n\nexport function RevenueBreakdownBlock({ className, framed = true }: RevenueBreakdownBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardDescription>{revenue.label}</CardDescription>\n\t\t\t\t<CardAction>\n\t\t\t\t\t<Badge tone=\"success\" size=\"sm\">\n\t\t\t\t\t\t{revenue.change}\n\t\t\t\t\t</Badge>\n\t\t\t\t</CardAction>\n\t\t\t</CardHeader>\n\t\t\t<CardContent className=\"flex flex-col gap-4 pt-2\">\n\t\t\t\t<span className=\"text-mono-xl\">{revenue.total}</span>\n\t\t\t\t<SegmentedBar label=\"Revenue by channel\" segments={[...segments]} size=\"lg\" />\n\t\t\t\t{/* Two columns while each entry fits whole, one when they would not:\n\t\t\t\t sized in em, so a larger type or a narrower card drops to one.\n\t\t\t\t aria-hidden: the bar already reads every share aloud. */}\n\t\t\t\t<Grid aria-hidden minItemWidth=\"9.5em\" gap=\"calc(var(--spacing) * 1.5) calc(var(--spacing) * 4)\">\n\t\t\t\t\t{segments.map((s) => (\n\t\t\t\t\t\t<div key={s.label} className=\"flex items-center gap-2 text-body-sm\">\n\t\t\t\t\t\t\t<StatusDot tone={s.tone} />\n\t\t\t\t\t\t\t<span className=\"flex-1 text-muted-foreground\">{s.label}</span>\n\t\t\t\t\t\t\t<span className=\"text-mono-caption\">{s.value}%</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</Grid>\n\t\t\t</CardContent>\n\t\t</Card>\n\t)\n}"
21153
21286
  },
21154
21287
  {
21155
21288
  "name": "SignInBlock",
@@ -21234,7 +21367,7 @@
21234
21367
  "Each card's label is read before its value, in source order."
21235
21368
  ],
21236
21369
  "imports": "import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle, cn, Grid, Icon, Row } from \"@sableui/react\"",
21237
- "code": "const stats = [\n\t{ label: \"Revenue\", value: \"$18,240\", delta: \"+8.2%\", up: true, note: \"vs. $16,857\" },\n\t{ label: \"Collects\", value: \"1,284\", delta: \"+3.1%\", up: true, note: \"182 first-time\" },\n\t{ label: \"Collectors\", value: \"312\", delta: \"+12.4%\", up: true, note: \"Best month yet\" },\n\t{ label: \"Sell-through\", value: \"64%\", delta: \"−1.8%\", up: false, note: \"6 open editions\" },\n]\n\nexport interface StatRowBlockProps {\n\tclassName?: string\n}\n\nexport function StatRowBlock({ className }: StatRowBlockProps) {\n\treturn (\n\t\t<Grid cols={{ base: 2, xl: 4 }} gap={3} className={cn(\"w-full\", className)}>\n\t\t\t{stats.map((stat) => (\n\t\t\t\t<Card key={stat.label}>\n\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t<CardDescription>{stat.label}</CardDescription>\n\t\t\t\t\t\t<CardTitle className=\"text-mono-xl\">{stat.value}</CardTitle>\n\t\t\t\t\t</CardHeader>\n\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t<Row gap={1} wrap>\n\t\t\t\t\t\t\t<Badge tone={stat.up ? \"success\" : \"warning\"} size=\"sm\" icon={<Icon name={stat.up ? \"arrow-up\" : \"arrow-down\"} />}>\n\t\t\t\t\t\t\t\t{stat.delta}\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t<span className=\"text-caption text-muted-foreground\">{stat.note}</span>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t</CardContent>\n\t\t\t\t</Card>\n\t\t\t))}\n\t\t</Grid>\n\t)\n}"
21370
+ "code": "const stats = [\n\t{ label: \"Revenue\", value: \"$18,240\", delta: \"+8.2%\", up: true, note: \"vs. $16,857\" },\n\t{ label: \"Orders\", value: \"1,284\", delta: \"+3.1%\", up: true, note: \"182 first-time\" },\n\t{ label: \"New customers\", value: \"312\", delta: \"+12.4%\", up: true, note: \"Best month yet\" },\n\t{ label: \"Conversion\", value: \"3.4%\", delta: \"−0.2%\", up: false, note: \"6 open trials\" },\n]\n\nexport interface StatRowBlockProps {\n\tclassName?: string\n}\n\nexport function StatRowBlock({ className }: StatRowBlockProps) {\n\treturn (\n\t\t<Grid cols={{ base: 2, xl: 4 }} gap={3} className={cn(\"w-full\", className)}>\n\t\t\t{stats.map((stat) => (\n\t\t\t\t<Card key={stat.label}>\n\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t<CardDescription>{stat.label}</CardDescription>\n\t\t\t\t\t\t<CardTitle className=\"text-mono-xl\">{stat.value}</CardTitle>\n\t\t\t\t\t</CardHeader>\n\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t<Row gap={1} wrap>\n\t\t\t\t\t\t\t<Badge tone={stat.up ? \"success\" : \"warning\"} size=\"sm\" icon={<Icon name={stat.up ? \"arrow-up\" : \"arrow-down\"} />}>\n\t\t\t\t\t\t\t\t{stat.delta}\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t<span className=\"text-caption text-muted-foreground\">{stat.note}</span>\n\t\t\t\t\t\t</Row>\n\t\t\t\t\t</CardContent>\n\t\t\t\t</Card>\n\t\t\t))}\n\t\t</Grid>\n\t)\n}"
21238
21371
  },
21239
21372
  {
21240
21373
  "name": "StepsBlock",
@@ -21276,7 +21409,7 @@
21276
21409
  "Category Badges carry text; the tone only groups them visually."
21277
21410
  ],
21278
21411
  "imports": "import { useState, useId } from \"react\"\nimport { Badge, Button, Card, CardAction, CardContent, CardFooter, CardHeader, CardTitle, Checkbox, cn, Icon, Label } from \"@sableui/react\"",
21279
- "code": "const tasks = [\n\t{ title: \"Pick the cover type\", tag: \"Design\", tone: \"collectible\", done: true },\n\t{ title: \"Wire up checkout\", tag: \"Engineering\", tone: \"info\", done: false },\n\t{ title: \"Draft launch post\", tag: \"Launch\", tone: \"edition\", done: false },\n] as const\n\nexport interface TaskListBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\tonAddTask?: () => void\n}\n\nexport function TaskListBlock({ className, framed = true, onAddTask }: TaskListBlockProps) {\n\tconst [done, setDone] = useState<boolean[]>(tasks.map((t) => t.done))\n\tconst id = useId()\n\tconst left = done.filter((d) => !d).length\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle>This week</CardTitle>\n\t\t\t\t<CardAction>\n\t\t\t\t\t<span className=\"text-caption text-muted-foreground\">{left} left</span>\n\t\t\t\t</CardAction>\n\t\t\t</CardHeader>\n\t\t\t<CardContent>\n\t\t\t\t<ul className=\"flex flex-col gap-1\">\n\t\t\t\t\t{tasks.map((task, i) => (\n\t\t\t\t\t\t<li key={task.title} className=\"flex items-center gap-3 py-1.5\">\n\t\t\t\t\t\t\t<Checkbox\n\t\t\t\t\t\t\t\tid={`${id}-${i}`}\n\t\t\t\t\t\t\t\tchecked={done[i]}\n\t\t\t\t\t\t\t\tonCheckedChange={(v: boolean) => setDone(done.map((d, j) => (j === i ? v : d)))}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<Label\n\t\t\t\t\t\t\t\thtmlFor={`${id}-${i}`}\n\t\t\t\t\t\t\t\tclassName={cn(\"min-w-0 flex-1 motion-interactive\", done[i] && \"text-muted-foreground line-through\")}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{task.title}\n\t\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t\t<Badge tone={task.tone} size=\"sm\">\n\t\t\t\t\t\t\t\t{task.tag}\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t))}\n\t\t\t\t</ul>\n\t\t\t</CardContent>\n\t\t\t<CardFooter>\n\t\t\t\t<Button variant=\"ghost\" size=\"sm\" bleed=\"start\" iconStart={<Icon name=\"plus\" />} onClick={onAddTask}>\n\t\t\t\t\tAdd task\n\t\t\t\t</Button>\n\t\t\t</CardFooter>\n\t\t</Card>\n\t)\n}"
21412
+ "code": "const tasks = [\n\t{ title: \"Pick the cover type\", tag: \"Design\", tone: \"2\", done: true },\n\t{ title: \"Wire up checkout\", tag: \"Engineering\", tone: \"info\", done: false },\n\t{ title: \"Draft launch post\", tag: \"Launch\", tone: \"1\", done: false },\n] as const\n\nexport interface TaskListBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\tonAddTask?: () => void\n}\n\nexport function TaskListBlock({ className, framed = true, onAddTask }: TaskListBlockProps) {\n\tconst [done, setDone] = useState<boolean[]>(tasks.map((t) => t.done))\n\tconst id = useId()\n\tconst left = done.filter((d) => !d).length\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle>This week</CardTitle>\n\t\t\t\t<CardAction>\n\t\t\t\t\t<span className=\"text-caption text-muted-foreground\">{left} left</span>\n\t\t\t\t</CardAction>\n\t\t\t</CardHeader>\n\t\t\t<CardContent>\n\t\t\t\t<ul className=\"flex flex-col gap-1\">\n\t\t\t\t\t{tasks.map((task, i) => (\n\t\t\t\t\t\t<li key={task.title} className=\"flex items-center gap-3 py-1.5\">\n\t\t\t\t\t\t\t<Checkbox\n\t\t\t\t\t\t\t\tid={`${id}-${i}`}\n\t\t\t\t\t\t\t\tchecked={done[i]}\n\t\t\t\t\t\t\t\tonCheckedChange={(v: boolean) => setDone(done.map((d, j) => (j === i ? v : d)))}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<Label\n\t\t\t\t\t\t\t\thtmlFor={`${id}-${i}`}\n\t\t\t\t\t\t\t\tclassName={cn(\"min-w-0 flex-1 motion-interactive\", done[i] && \"text-muted-foreground line-through\")}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{task.title}\n\t\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t\t<Badge tone={task.tone} size=\"sm\">\n\t\t\t\t\t\t\t\t{task.tag}\n\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t))}\n\t\t\t\t</ul>\n\t\t\t</CardContent>\n\t\t\t<CardFooter>\n\t\t\t\t<Button variant=\"ghost\" size=\"sm\" bleed=\"start\" iconStart={<Icon name=\"plus\" />} onClick={onAddTask}>\n\t\t\t\t\tAdd task\n\t\t\t\t</Button>\n\t\t\t</CardFooter>\n\t\t</Card>\n\t)\n}"
21280
21413
  },
21281
21414
  {
21282
21415
  "name": "TeamMembersBlock",
@@ -21299,7 +21432,7 @@
21299
21432
  "Roles are text in a Badge; tone only distinguishes the owner."
21300
21433
  ],
21301
21434
  "imports": "import { Avatar, Badge, Button, Card, CardAction, CardDescription, CardHeader, CardList, CardTitle, cn, Entity, Icon, MenuItem, MenuSeparator, OverflowMenu } from \"@sableui/react\"",
21302
- "code": "const members = [\n\t{ id: \"u1\", name: \"Ada Lovelace\", initials: \"AL\", email: \"ada@studio.com\", role: \"Owner\" },\n\t{ id: \"u2\", name: \"Mara Okafor\", initials: \"MO\", email: \"mara@studio.com\", role: \"Admin\" },\n\t{ id: \"u3\", name: \"Sam Rivera\", initials: \"SR\", email: \"sam@studio.com\", role: \"Member\" },\n\t{ id: \"u4\", name: \"jun@studio.com\", initials: \"J\", email: \"Invited 2 days ago\", role: \"Pending\" },\n]\n\nexport interface TeamMembersBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\tonInvite?: () => void\n\t/** Called with the member's id and the action chosen from their menu. */\n\tonMemberAction?: (id: string, action: \"make-admin\" | \"make-member\" | \"resend\" | \"remove\") => void\n}\n\nexport function TeamMembersBlock({ className, framed = true, onInvite, onMemberAction }: TeamMembersBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-2xl\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle render={<h2 />}>Members</CardTitle>\n\t\t\t\t<CardDescription>{members.length} people in this workspace.</CardDescription>\n\t\t\t\t<CardAction>\n\t\t\t\t\t<Button size=\"sm\" iconStart={<Icon name=\"plus\" />} onClick={onInvite}>\n\t\t\t\t\t\tInvite\n\t\t\t\t\t</Button>\n\t\t\t\t</CardAction>\n\t\t\t</CardHeader>\n\t\t\t<CardList>\n\t\t\t\t{members.map((m) => (\n\t\t\t\t\t<Entity\n\t\t\t\t\t\tkey={m.id}\n\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback={m.initials} alt={m.name} />}\n\t\t\t\t\t\ttitle={m.name}\n\t\t\t\t\t\tsubtitle={m.email}\n\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t<Badge appearance={m.role === \"Pending\" ? \"outline\" : \"plain\"} tone={m.role === \"Owner\" ? \"edition\" : \"neutral\"} size=\"sm\">\n\t\t\t\t\t\t\t\t\t{m.role}\n\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t{/* The owner is not managed from here; a placeholder keeps the column aligned. */}\n\t\t\t\t\t\t\t\t{m.role === \"Owner\" ? (\n\t\t\t\t\t\t\t\t\t<span aria-hidden className=\"size-(--control-sm)\" />\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t<OverflowMenu label={`Manage ${m.name}`} size=\"icon-sm\">\n\t\t\t\t\t\t\t\t\t\t{m.role === \"Pending\" ? (\n\t\t\t\t\t\t\t\t\t\t\t<MenuItem onClick={() => onMemberAction?.(m.id, \"resend\")}>Resend invite</MenuItem>\n\t\t\t\t\t\t\t\t\t\t) : m.role === \"Admin\" ? (\n\t\t\t\t\t\t\t\t\t\t\t<MenuItem onClick={() => onMemberAction?.(m.id, \"make-member\")}>Make member</MenuItem>\n\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t<MenuItem onClick={() => onMemberAction?.(m.id, \"make-admin\")}>Make admin</MenuItem>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t<MenuSeparator />\n\t\t\t\t\t\t\t\t\t\t<MenuItem variant=\"destructive\" onClick={() => onMemberAction?.(m.id, \"remove\")}>\n\t\t\t\t\t\t\t\t\t\t\t{m.role === \"Pending\" ? \"Cancel invite\" : \"Remove from workspace\"}\n\t\t\t\t\t\t\t\t\t\t</MenuItem>\n\t\t\t\t\t\t\t\t\t</OverflowMenu>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</CardList>\n\t\t</Card>\n\t)\n}"
21435
+ "code": "const members = [\n\t{ id: \"u1\", name: \"Ada Lovelace\", initials: \"AL\", email: \"ada@studio.com\", role: \"Owner\" },\n\t{ id: \"u2\", name: \"Mara Okafor\", initials: \"MO\", email: \"mara@studio.com\", role: \"Admin\" },\n\t{ id: \"u3\", name: \"Sam Rivera\", initials: \"SR\", email: \"sam@studio.com\", role: \"Member\" },\n\t{ id: \"u4\", name: \"jun@studio.com\", initials: \"J\", email: \"Invited 2 days ago\", role: \"Pending\" },\n]\n\nexport interface TeamMembersBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a muted section that already frames it. */\n\tframed?: boolean\n\tonInvite?: () => void\n\t/** Called with the member's id and the action chosen from their menu. */\n\tonMemberAction?: (id: string, action: \"make-admin\" | \"make-member\" | \"resend\" | \"remove\") => void\n}\n\nexport function TeamMembersBlock({ className, framed = true, onInvite, onMemberAction }: TeamMembersBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-2xl\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardTitle render={<h2 />}>Members</CardTitle>\n\t\t\t\t<CardDescription>{members.length} people in this workspace.</CardDescription>\n\t\t\t\t<CardAction>\n\t\t\t\t\t<Button size=\"sm\" iconStart={<Icon name=\"plus\" />} onClick={onInvite}>\n\t\t\t\t\t\tInvite\n\t\t\t\t\t</Button>\n\t\t\t\t</CardAction>\n\t\t\t</CardHeader>\n\t\t\t<CardList>\n\t\t\t\t{members.map((m) => (\n\t\t\t\t\t<Entity\n\t\t\t\t\t\tkey={m.id}\n\t\t\t\t\t\tleading={<Avatar variant=\"art\" size=\"sm\" fallback={m.initials} alt={m.name} />}\n\t\t\t\t\t\ttitle={m.name}\n\t\t\t\t\t\tsubtitle={m.email}\n\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t<Badge appearance={m.role === \"Pending\" ? \"outline\" : \"plain\"} tone={m.role === \"Owner\" ? \"1\" : \"neutral\"} size=\"sm\">\n\t\t\t\t\t\t\t\t\t{m.role}\n\t\t\t\t\t\t\t\t</Badge>\n\t\t\t\t\t\t\t\t{/* The owner is not managed from here; a placeholder keeps the column aligned. */}\n\t\t\t\t\t\t\t\t{m.role === \"Owner\" ? (\n\t\t\t\t\t\t\t\t\t<span aria-hidden className=\"size-(--control-sm)\" />\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t<OverflowMenu label={`Manage ${m.name}`} size=\"icon-sm\">\n\t\t\t\t\t\t\t\t\t\t{m.role === \"Pending\" ? (\n\t\t\t\t\t\t\t\t\t\t\t<MenuItem onClick={() => onMemberAction?.(m.id, \"resend\")}>Resend invite</MenuItem>\n\t\t\t\t\t\t\t\t\t\t) : m.role === \"Admin\" ? (\n\t\t\t\t\t\t\t\t\t\t\t<MenuItem onClick={() => onMemberAction?.(m.id, \"make-member\")}>Make member</MenuItem>\n\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t<MenuItem onClick={() => onMemberAction?.(m.id, \"make-admin\")}>Make admin</MenuItem>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t<MenuSeparator />\n\t\t\t\t\t\t\t\t\t\t<MenuItem variant=\"destructive\" onClick={() => onMemberAction?.(m.id, \"remove\")}>\n\t\t\t\t\t\t\t\t\t\t\t{m.role === \"Pending\" ? \"Cancel invite\" : \"Remove from workspace\"}\n\t\t\t\t\t\t\t\t\t\t</MenuItem>\n\t\t\t\t\t\t\t\t\t</OverflowMenu>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</CardList>\n\t\t</Card>\n\t)\n}"
21303
21436
  },
21304
21437
  {
21305
21438
  "name": "TestimonialBlock",
@@ -21416,7 +21549,7 @@
21416
21549
  "name": "WalletBlock",
21417
21550
  "path": "blocks/wallet-block.tsx",
21418
21551
  "summary": "A balance card: the balance in the metric mono tier, Send and Receive as an equal pair, and recent activity as Entity rows with signed amounts.",
21419
- "intent": "Reach for this for a wallet, account balance or credits summary with its latest movements. For a full transaction history with filters, use a DataTable.",
21552
+ "intent": "Reach for this for an account balance, store credit or credits summary with its latest movements. For a full transaction history with filters, use a DataTable.",
21420
21553
  "family": "Commerce",
21421
21554
  "kind": "card",
21422
21555
  "width": {
@@ -21426,14 +21559,14 @@
21426
21559
  "notes": [
21427
21560
  "The balance is CardTitle at `text-mono-xl` — tabular figures, never a heading tier — under its label as CardDescription.",
21428
21561
  "Send is primary and Receive secondary, side by side at equal width: two directions of one action, with sending the riskier and more frequent.",
21429
- "Incoming amounts are `text-success`; outgoing stay muted rather than red, because a withdrawal is not an error."
21562
+ "Incoming amounts are `text-success`; outgoing stay muted rather than red, because a payout or refund is not an error."
21430
21563
  ],
21431
21564
  "a11y": [
21432
21565
  "Each activity row's leading swatch is aria-hidden; the title and detail carry the meaning.",
21433
- "Amounts keep their sign in text (+0.80 / -1.50), so the direction does not depend on colour."
21566
+ "Amounts keep their sign in text (+49.00 / -850.00), so the direction does not depend on colour."
21434
21567
  ],
21435
21568
  "imports": "import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, cn, Entity, Icon } from \"@sableui/react\"",
21436
- "code": "const balance = \"14.62 SOL\"\n\nconst activity = [\n\t{ title: \"Sold “Dusk, no. 4”\", detail: \"Edition · 2h ago\", amount: \"+0.80\", tone: \"edition\" },\n\t{ title: \"Royalty\", detail: \"Collectible · Yesterday\", amount: \"+0.12\", tone: \"collectible\" },\n\t{ title: \"Withdrawal\", detail: \"To Phantom · Mon\", amount: \"-1.50\", tone: \"standard\" },\n]\n\nexport interface WalletBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a Sheet or muted section that already frames it. */\n\tframed?: boolean\n\tonSend?: () => void\n\tonReceive?: () => void\n}\n\nexport function WalletBlock({ className, framed = true, onSend, onReceive }: WalletBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardDescription>Balance</CardDescription>\n\t\t\t\t<CardTitle className=\"text-mono-xl\">{balance}</CardTitle>\n\t\t\t</CardHeader>\n\t\t\t<CardContent className=\"flex flex-col gap-3\">\n\t\t\t\t<div className=\"grid grid-cols-2 gap-2\">\n\t\t\t\t\t<Button iconStart={<Icon name=\"arrow-up\" />} onClick={onSend}>\n\t\t\t\t\t\tSend\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button variant=\"secondary\" iconStart={<Icon name=\"arrow-down\" />} onClick={onReceive}>\n\t\t\t\t\t\tReceive\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t\t{activity.map((item) => (\n\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\tkey={item.title}\n\t\t\t\t\t\t\tclassName=\"px-0\"\n\t\t\t\t\t\t\tleading={\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\taria-hidden\n\t\t\t\t\t\t\t\t\tclassName=\"block size-8 rounded-full\"\n\t\t\t\t\t\t\t\t\t// design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look\n\t\t\t\t\t\t\t\t\tstyle={{ background: `oklch(from var(--tone-${item.tone}) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h)` }}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\tsubtitle={item.detail}\n\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t<span className={cn(\"text-mono-md\", item.amount.startsWith(\"+\") ? \"text-success\" : \"text-muted-foreground\")}>\n\t\t\t\t\t\t\t\t\t{item.amount}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t)\n}"
21569
+ "code": "const balance = \"$2,418.50\"\n\nconst activity = [\n\t{ title: \"Subscription\", detail: \"Team plan · 2h ago\", amount: \"+49.00\", tone: \"1\" },\n\t{ title: \"Refund\", detail: \"Order 1042 · Yesterday\", amount: \"-24.00\", tone: \"2\" },\n\t{ title: \"Payout\", detail: \"To checking · Mon\", amount: \"-850.00\", tone: \"3\" },\n]\n\nexport interface WalletBlockProps {\n\tclassName?: string\n\t/** false drops the card's surface and border — for a Sheet or muted section that already frames it. */\n\tframed?: boolean\n\tonSend?: () => void\n\tonReceive?: () => void\n}\n\nexport function WalletBlock({ className, framed = true, onSend, onReceive }: WalletBlockProps) {\n\treturn (\n\t\t<Card variant={framed ? \"default\" : \"flat\"} className={cn(\"w-full max-w-xs\", className)}>\n\t\t\t<CardHeader>\n\t\t\t\t<CardDescription>Balance</CardDescription>\n\t\t\t\t<CardTitle className=\"text-mono-xl\">{balance}</CardTitle>\n\t\t\t</CardHeader>\n\t\t\t<CardContent className=\"flex flex-col gap-3\">\n\t\t\t\t<div className=\"grid grid-cols-2 gap-2\">\n\t\t\t\t\t<Button iconStart={<Icon name=\"arrow-up\" />} onClick={onSend}>\n\t\t\t\t\t\tSend\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button variant=\"secondary\" iconStart={<Icon name=\"arrow-down\" />} onClick={onReceive}>\n\t\t\t\t\t\tReceive\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t\t{activity.map((item) => (\n\t\t\t\t\t\t<Entity\n\t\t\t\t\t\t\tkey={item.title}\n\t\t\t\t\t\t\tclassName=\"px-0\"\n\t\t\t\t\t\t\tleading={\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\taria-hidden\n\t\t\t\t\t\t\t\t\tclassName=\"block size-8 rounded-full\"\n\t\t\t\t\t\t\t\t\t// design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look\n\t\t\t\t\t\t\t\t\tstyle={{ background: `oklch(from var(--tone-${item.tone}) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h)` }}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\tsubtitle={item.detail}\n\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\t<span className={cn(\"text-mono-md\", item.amount.startsWith(\"+\") ? \"text-success\" : \"text-muted-foreground\")}>\n\t\t\t\t\t\t\t\t\t{item.amount}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t)\n}"
21437
21570
  }
21438
21571
  ]
21439
21572
  },
@@ -21620,8 +21753,8 @@
21620
21753
  {
21621
21754
  "slug": "spacing",
21622
21755
  "title": "Spacing & sizing",
21623
- "summary": "One 4px scale governs every gap, inset and size, and space is assigned by role. Controls read a height ladder that a density scope can retune all at once.",
21624
- "headline": "4px base · 8px rhythm · 3 roles · 1 control ladder",
21756
+ "summary": "One scale, derived from the type: the baseline is one line of body text, split into steps every gap, inset and line height lands on. Space is assigned by role. Controls read a height ladder that a density scope can retune all at once.",
21757
+ "headline": "Baseline from the type · 8px steps · 3 roles · 1 control ladder",
21625
21758
  "retrieve": "sable docs spacing",
21626
21759
  "vocabularies": [
21627
21760
  {
@@ -21634,13 +21767,13 @@
21634
21767
  "use": "Padding inside a surface. p-3 for a dense card, px-3 py-2 for a row."
21635
21768
  },
21636
21769
  {
21637
- "name": "Stack, within a group gap-1.5 – gap-2",
21638
- "value": "6–8px",
21770
+ "name": "Stack, within a group gap-2",
21771
+ "value": "1 step · 8px",
21639
21772
  "use": "Label to field, title to description."
21640
21773
  },
21641
21774
  {
21642
21775
  "name": "Stack, between groups gap-4 – gap-6",
21643
- "value": "16–24px",
21776
+ "value": "2–3 steps · 16–24px",
21644
21777
  "use": "Field to field, card to card."
21645
21778
  },
21646
21779
  {
@@ -21709,12 +21842,12 @@
21709
21842
  ],
21710
21843
  "rules": [
21711
21844
  {
21712
- "rule": "Every margin, padding and gap is a step on the 4px scale.",
21713
- "why": "Off-scale values break the rhythm that lets adjacent components line up without coordination."
21845
+ "rule": "Every margin, padding and gap is a step on the scale, and vertical space between blocks is whole baseline steps: an even Tailwind step (2, 4, 6, 8, 12) or a Stack/Row/Grid gap number.",
21846
+ "why": "The baseline is one line of body text split into steps; line heights round to it. An odd step (3, 5) is half a baseline step. It is a rhythm to keep, not a grid to force: components stay as tall as their content."
21714
21847
  },
21715
21848
  {
21716
21849
  "rule": "Padding, gaps and insets in a component sit on the spacing scale: a step (p-4, gap-2) or --spacing(n) inside an arbitrary value (p-[var(--card-p,--spacing(6))]).",
21717
- "why": "The density (Character) control works by retuning --spacing, so anything on the scale moves with it. A literal p-[1.5rem] or padding: \"12px\" stays put while everything around it tightens. Keep fixed rem/px for what must not scale: a 1px hairline, a ring width.",
21850
+ "why": "--spacing derives from the baseline step and the density (Character) control moves the leading it comes from, so anything on the scale moves with it. A literal p-[1.5rem] or padding: \"12px\" stays put while everything around it tightens. Keep fixed rem/px for what must not scale: a 1px hairline, a ring width.",
21718
21851
  "enforcedBy": "design-lint: fixed-spacing (library source)"
21719
21852
  },
21720
21853
  {
@@ -22947,7 +23080,7 @@
22947
23080
  "intent": "Label/value pairs on a native `<dl>`: muted label-tier `<dt>`s and body-tier `<dd>`s on a baseline-aligned two-column grid; long values wrap inside their column. Colour a status value with a `text-*` utility on its `<dd>`.",
22948
23081
  "whenToUse": "Read-only details: an order summary, a record's metadata, a settings summary, profile stats.",
22949
23082
  "insteadOf": "A grid of `<div>`s (screen readers lose the term/value pairing), or Table for a single record. For editable values use Form and Field.",
22950
- "example": "<dl className=\"sable-list-dl\"><dt>Network</dt><dd>Solana</dd><dt>Status</dt><dd className=\"text-success\">Active</dd></dl>",
23083
+ "example": "<dl className=\"sable-list-dl\"><dt>Plan</dt><dd>Studio</dd><dt>Status</dt><dd className=\"text-success\">Active</dd></dl>",
22951
23084
  "keywords": [
22952
23085
  "description list",
22953
23086
  "definition list",
@@ -24633,6 +24766,8 @@
24633
24766
  "disabled",
24634
24767
  "media",
24635
24768
  "note",
24769
+ "onRemove",
24770
+ "removeLabel",
24636
24771
  "textValue",
24637
24772
  "value"
24638
24773
  ],
@@ -25243,7 +25378,8 @@
25243
25378
  "appearance",
25244
25379
  "icon",
25245
25380
  "selected",
25246
- "size"
25381
+ "size",
25382
+ "tone"
25247
25383
  ],
25248
25384
  "Popover": [
25249
25385
  "actionsRef",
@@ -26527,7 +26663,7 @@
26527
26663
  "examples": [
26528
26664
  {
26529
26665
  "label": "Basic",
26530
- "code": "<TooltipProvider><Tooltip><TooltipTrigger render={<Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Info\"><Icon name=\"info\" /></Button>} /><TooltipContent>Stored permanently on Arweave.</TooltipContent></Tooltip></TooltipProvider>",
26666
+ "code": "<TooltipProvider><Tooltip><TooltipTrigger render={<Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Info\"><Icon name=\"info\" /></Button>} /><TooltipContent>Saved to your workspace.</TooltipContent></Tooltip></TooltipProvider>",
26531
26667
  "imports": "import { Button, Icon, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@sableui/react\""
26532
26668
  }
26533
26669
  ]