@sableui/react 0.1.1 → 0.3.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 (137) hide show
  1. package/AGENTS.md +3 -1
  2. package/CHANGELOG.md +120 -0
  3. package/CONTRIBUTING.md +8 -0
  4. package/README.md +1 -0
  5. package/bin/sable-lint.mjs +0 -0
  6. package/bin/sable.mjs +68 -26
  7. package/dist/{_contract-CYScOdf5.d.ts → _contract-o3tUc8Ig.d.ts} +1 -1
  8. package/dist/blocks/index.d.ts +396 -0
  9. package/dist/blocks/index.js +1997 -0
  10. package/dist/chunk-6LCIBMMG.js +4908 -0
  11. package/dist/{chunk-J23ELQQZ.js → chunk-CKNM7KSB.js} +1664 -4270
  12. package/dist/chunk-K6LII3OE.js +5093 -0
  13. package/dist/{chunk-RWP4XBSK.js → chunk-KFZ43RKU.js} +1 -1
  14. package/dist/{chunk-PAOZBZME.js → chunk-NGBDP3BM.js} +1 -1
  15. package/dist/{chunk-5Z5KLOAU.js → chunk-TH563WLW.js} +14 -1
  16. package/dist/{chunk-JI7WJP2L.js → chunk-X276MF2W.js} +5 -0
  17. package/dist/foundations/index.js +1 -1
  18. package/dist/icon/index.js +2 -2
  19. package/dist/icon-sets/reicon.js +1 -1
  20. package/dist/index.d.ts +624 -133
  21. package/dist/index.js +767 -4964
  22. package/dist/ios/index.d.ts +14 -2
  23. package/dist/ios/index.js +10 -9
  24. package/dist/ios/preview.d.ts +11 -1
  25. package/dist/ios/preview.js +14 -9
  26. package/dist/ios/styles.css +26 -6
  27. package/dist/{meta-6dUzgC6M.d.ts → meta-CcE_SeFu.d.ts} +37 -1
  28. package/dist/patterns/index.d.ts +1 -1
  29. package/dist/patterns/index.js +1684 -471
  30. package/dist/presets/index.d.ts +1 -1
  31. package/dist/presets/index.js +1 -1
  32. package/dist/styles.css +238 -40
  33. package/dist/{theme-provider-BWcizhAt.d.ts → theme-provider-BE-KqUG-.d.ts} +1 -1
  34. package/dist/themes/atelier/atelier.css +3 -0
  35. package/dist/themes/atelier/index.d.ts +1 -1
  36. package/dist/themes/base/base.css +34 -4
  37. package/dist/themes/base/index.d.ts +1 -1
  38. package/dist/themes/ember/ember.css +3 -0
  39. package/dist/themes/ember/index.d.ts +1 -1
  40. package/dist/themes/index.d.ts +2 -2
  41. package/dist/themes/index.js +2 -2
  42. package/dist/themes/meridian/index.d.ts +1 -1
  43. package/dist/themes/meridian/meridian.css +10 -5
  44. package/dist/themes/poppy/index.d.ts +1 -1
  45. package/dist/themes/poppy/poppy.css +16 -0
  46. package/dist/themes/prism/index.d.ts +1 -1
  47. package/dist/themes/prism/prism.css +4 -1
  48. package/dist/themes/verdant/index.d.ts +1 -1
  49. package/dist/themes/verdant/verdant.css +7 -2
  50. package/llms/blocks/api-keys-block.md +79 -0
  51. package/llms/blocks/chat-thread-block.md +51 -0
  52. package/llms/blocks/command-palette-block.md +48 -0
  53. package/llms/blocks/cta-band-block.md +62 -0
  54. package/llms/blocks/danger-zone-block.md +69 -0
  55. package/llms/blocks/device-comparison-block.md +91 -0
  56. package/llms/blocks/event-card-block.md +84 -0
  57. package/llms/blocks/faq-block.md +72 -0
  58. package/llms/blocks/feature-grid-block.md +75 -0
  59. package/llms/blocks/feedback-form-block.md +66 -0
  60. package/llms/blocks/goal-progress-block.md +54 -0
  61. package/llms/blocks/hero-block.md +65 -0
  62. package/llms/blocks/invite-team-block.md +61 -0
  63. package/llms/blocks/invoice-history-block.md +84 -0
  64. package/llms/blocks/kpi-sparkline-block.md +57 -0
  65. package/llms/blocks/media-player-block.md +100 -0
  66. package/llms/blocks/newsletter-block.md +64 -0
  67. package/llms/blocks/not-found-block.md +56 -0
  68. package/llms/blocks/notification-list-block.md +63 -0
  69. package/llms/blocks/notification-settings-block.md +54 -0
  70. package/llms/blocks/order-tracking-block.md +62 -0
  71. package/llms/blocks/pricing-card-block.md +82 -0
  72. package/llms/blocks/pricing-table-block.md +129 -0
  73. package/llms/blocks/product-card-block.md +69 -0
  74. package/llms/blocks/profile-card-block.md +103 -0
  75. package/llms/blocks/profile-form-block.md +114 -0
  76. package/llms/blocks/revenue-breakdown-block.md +70 -0
  77. package/llms/blocks/sign-in-block.md +64 -0
  78. package/llms/blocks/sign-up-block.md +78 -0
  79. package/llms/blocks/site-footer-block.md +96 -0
  80. package/llms/blocks/stat-row-block.md +59 -0
  81. package/llms/blocks/steps-block.md +73 -0
  82. package/llms/blocks/task-list-block.md +81 -0
  83. package/llms/blocks/team-members-block.md +91 -0
  84. package/llms/blocks/testimonial-block.md +78 -0
  85. package/llms/blocks/traffic-sources-block.md +63 -0
  86. package/llms/blocks/traffic-trend-block.md +95 -0
  87. package/llms/blocks/upload-list-block.md +66 -0
  88. package/llms/blocks/verify-code-block.md +59 -0
  89. package/llms/blocks/wallet-block.md +84 -0
  90. package/llms/components/area-chart.md +112 -0
  91. package/llms/components/bar-chart.md +131 -0
  92. package/llms/components/bubble.md +5 -5
  93. package/llms/components/button.md +1 -1
  94. package/llms/components/card.md +2 -2
  95. package/llms/components/checkbox-card.md +1 -1
  96. package/llms/components/context-card.md +2 -2
  97. package/llms/components/context-menu.md +1 -1
  98. package/llms/components/date-tile.md +79 -0
  99. package/llms/components/dialog.md +1 -1
  100. package/llms/components/empty-state.md +4 -4
  101. package/llms/components/entity.md +5 -3
  102. package/llms/components/fieldset.md +1 -1
  103. package/llms/components/kbd.md +18 -6
  104. package/llms/components/line-chart.md +101 -0
  105. package/llms/components/menu.md +1 -1
  106. package/llms/components/{dots-menu.md → overflow-menu.md} +9 -9
  107. package/llms/components/pie-chart.md +103 -0
  108. package/llms/components/pill.md +1 -1
  109. package/llms/components/radar-chart.md +106 -0
  110. package/llms/components/radial-chart.md +102 -0
  111. package/llms/components/segmented-bar.md +102 -0
  112. package/llms/components/settings-menu.md +2 -2
  113. package/llms/components/side-nav.md +1 -1
  114. package/llms/components/skeleton.md +1 -1
  115. package/llms/components/sparkline.md +80 -0
  116. package/llms/components/table.md +1 -1
  117. package/llms/components/theme-selector.md +1 -1
  118. package/llms/foundations/shape.md +1 -1
  119. package/llms/patterns/auth-page.md +132 -42
  120. package/llms/patterns/brand-landing-page.md +271 -175
  121. package/llms/patterns/chat-panel.md +120 -39
  122. package/llms/patterns/dashboard-page.md +276 -65
  123. package/llms/patterns/feed-page.md +245 -64
  124. package/llms/patterns/landing-page.md +473 -75
  125. package/llms/patterns/list-detail-page.md +377 -68
  126. package/llms/patterns/settings-page.md +268 -67
  127. package/llms/reference/token-intent.md +4 -2
  128. package/llms/reference/utilities.md +20 -5
  129. package/llms/tasks/building-an-ios-prototype.md +4 -1
  130. package/llms/tasks/choosing-a-component.md +2 -2
  131. package/llms-full.txt +6428 -892
  132. package/llms.txt +67 -16
  133. package/package.json +5 -1
  134. package/registry.json +3217 -552
  135. package/targets/ios.json +88 -18
  136. package/llms/components/description.md +0 -96
  137. package/dist/{chunk-L6Y46C62.js → chunk-MXJX3NZI.js} +1 -1
@@ -1,103 +1,200 @@
1
- import { Card, CardHeader, CardTitle, CardDescription, CardContent, Form, Field, Input, Button, Separator, Badge, TextReveal, StatusDot, CardAction, MessageScroller, Marker, Message, Bubble, Avatar, CodeBlock, BubbleReactions, BubbleReaction, Attachment, StreamingText, CardFooter, ChatComposer, SettingsMenu, SettingsRow, MenuRadioGroup, MenuRadioItem, AppShell, PageHeader, DataTable, RelativeTime, CardList, Entity, Sidebar, SidebarHeader, SidebarNav, SidebarItem, BottomNav, BottomNavItem, AppHeader, Reveal, SearchInput, Description, DescriptionItem, Fieldset, FieldsetLegend, FieldsetDescription, FieldsetContent, Textarea, Select, SelectTrigger, SelectContent, SelectItem, FieldsetFooter, Switch } from '../chunk-J23ELQQZ.js';
2
- import { Region, Stack, Row, Page, Grid, AspectBox, Columns, Col } from '../chunk-3IWZYBGH.js';
1
+ import { TextReveal, Reveal, MessageScroller, Marker, Toolbar, ToolbarButton, StreamingText, ChatComposer, SettingsMenu, SettingsRow, AppShell, PageHeader, DataTable, Meter, RelativeTime, Sidebar, SidebarHeader, SidebarNav, SidebarItem, NotificationBadge, SidebarFooter, BottomNav, BottomNavItem, AppHeader, Tabs, TabsList, TabsTrigger, TabsPanel, Skeleton, Browser, MeterLabel, MeterValue, MeterTrack, SearchInput, EmptyState, SideNav, SideNavItem } from '../chunk-6LCIBMMG.js';
2
+ import { Form, Field, Input, Button, Separator, Card, CardContent, Badge, Entity, Avatar, StatusDot, CardHeader, CardTitle, CardDescription, CardAction, Message, Bubble, CodeBlock, SimpleTooltip, Attachment, CardFooter, MenuRadioGroup, MenuRadioItem, CardList, OverflowMenu, MenuItem, MenuSeparator, Textarea, Code, Select, SelectTrigger, SelectContent, SelectItem, DestructiveActionModal, Switch } from '../chunk-CKNM7KSB.js';
3
+ import { Region, Stack, Row, Grid, Page, Columns, Col, AspectBox } from '../chunk-3IWZYBGH.js';
3
4
  import '../chunk-SRQWQUTW.js';
4
- import { Icon } from '../chunk-PAOZBZME.js';
5
- import '../chunk-5Z5KLOAU.js';
6
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { Icon } from '../chunk-NGBDP3BM.js';
6
+ import '../chunk-TH563WLW.js';
7
+ import { useState, useRef, useEffect } from 'react';
8
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
9
 
10
+ var artwork = [
11
+ // design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
12
+ "radial-gradient(in oklab at 18% 22%, oklch(from var(--tone-info) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h), transparent 58%)",
13
+ // design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
14
+ "radial-gradient(in oklab at 82% 18%, oklch(from var(--tone-collectible) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h), transparent 55%)",
15
+ // design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
16
+ "radial-gradient(in oklab at 70% 88%, oklch(from var(--tone-edition) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h), transparent 60%)",
17
+ // design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
18
+ "radial-gradient(in oklab at 12% 92%, oklch(from var(--tone-standard) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h), transparent 55%)",
19
+ "var(--primary)"
20
+ ].join(", ");
8
21
  function AuthPage() {
9
- return /* @__PURE__ */ jsx("main", { className: "grid min-h-dvh place-items-center bg-background", children: /* @__PURE__ */ jsx(Region, { max: "form", safeArea: ["top", "bottom"], children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
10
- /* @__PURE__ */ jsxs(Card, { children: [
11
- /* @__PURE__ */ jsxs(CardHeader, { children: [
12
- /* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h1", { children: "Sign in" }) }),
13
- /* @__PURE__ */ jsx(CardDescription, { children: "Welcome back \u2014 pick up where you left off." })
22
+ const [pending2, setPending] = useState(false);
23
+ const [errors, setErrors] = useState({});
24
+ return /* @__PURE__ */ jsxs("main", { className: "grid min-h-dvh bg-background lg:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)]", children: [
25
+ /* @__PURE__ */ jsx(Region, { max: "form", safeArea: ["top", "bottom"], className: "flex flex-col", children: /* @__PURE__ */ jsxs(Stack, { gap: 0, className: "flex-1 py-8 sm:py-10", children: [
26
+ /* @__PURE__ */ jsxs("a", { href: "/", className: "flex items-center gap-2.5 self-start rounded-sm motion-interactive motion-press", children: [
27
+ /* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-2xl" }),
28
+ /* @__PURE__ */ jsx("span", { className: "text-title-lg", children: "Atelier" })
14
29
  ] }),
15
- /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Form, { onSubmit: (event) => event.preventDefault(), children: [
16
- /* @__PURE__ */ jsx(Field, { label: "Email", required: true, children: /* @__PURE__ */ jsx(Input, { type: "email", name: "email", autoComplete: "email", placeholder: "you@example.com" }) }),
17
- /* @__PURE__ */ jsx(Field, { label: "Password", required: true, children: /* @__PURE__ */ jsx(Input, { type: "password", name: "password", autoComplete: "current-password" }) }),
18
- /* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full", children: "Sign in" }),
30
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, justify: "center", className: "flex-1 py-12", children: [
31
+ /* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
32
+ /* @__PURE__ */ jsx("h1", { className: "text-heading-1", children: "Welcome back" }),
33
+ /* @__PURE__ */ jsx("p", { className: "text-body-lg text-muted-foreground", children: "Sign in to collect new releases and manage your editions." })
34
+ ] }),
35
+ /* @__PURE__ */ jsxs(
36
+ Form,
37
+ {
38
+ className: "gap-5",
39
+ errors,
40
+ onFormSubmit: () => {
41
+ setPending(true);
42
+ setTimeout(() => {
43
+ setPending(false);
44
+ setErrors({ password: "That email and password don\u2019t match. Try again or reset your password." });
45
+ }, 900);
46
+ },
47
+ children: [
48
+ /* @__PURE__ */ jsx(Field, { label: "Email", name: "email", validate: (value) => value ? null : "Enter your email address.", children: /* @__PURE__ */ jsx(Input, { type: "email", autoComplete: "email webauthn", placeholder: "you@example.com" }) }),
49
+ /* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
50
+ /* @__PURE__ */ jsx(Field, { label: "Password", name: "password", validate: (value) => value ? null : "Enter your password.", children: /* @__PURE__ */ jsx(Input, { type: "password", autoComplete: "current-password" }) }),
51
+ /* @__PURE__ */ jsx("a", { href: "/forgot-password", className: "hit-target self-end text-body-sm text-muted-foreground link-underline motion-interactive", children: "Forgot password?" })
52
+ ] }),
53
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "cta", loading: pending2, loadingLabel: "Signing in", className: "w-full", children: "Sign in" })
54
+ ]
55
+ }
56
+ ),
19
57
  /* @__PURE__ */ jsx(Separator, { label: "or" }),
20
- /* @__PURE__ */ jsx(Button, { variant: "outline", className: "w-full", children: "Continue with wallet" })
21
- ] }) })
22
- ] }),
23
- /* @__PURE__ */ jsxs("p", { className: "text-center text-body-sm text-muted-foreground", children: [
24
- "No account?",
25
- " ",
26
- /* @__PURE__ */ jsx("a", { href: "/signup", className: "link-underline motion-interactive text-foreground", children: "Create one" })
27
- ] })
28
- ] }) }) });
58
+ /* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
59
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "cta", className: "w-full", iconStart: /* @__PURE__ */ jsx(Icon, { name: "key" }), children: "Sign in with a passkey" }),
60
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "cta", className: "w-full", iconStart: /* @__PURE__ */ jsx(Icon, { name: "wallet" }), children: "Connect a wallet" })
61
+ ] }),
62
+ /* @__PURE__ */ jsxs("p", { className: "text-body-md text-muted-foreground", children: [
63
+ "No account?",
64
+ " ",
65
+ /* @__PURE__ */ jsx("a", { href: "/signup", className: "hit-target link-underline motion-interactive text-foreground", children: "Create one" })
66
+ ] })
67
+ ] }),
68
+ /* @__PURE__ */ jsxs("p", { className: "text-body-xs text-muted-foreground", children: [
69
+ "By signing in you agree to the",
70
+ " ",
71
+ /* @__PURE__ */ jsx("a", { href: "/terms", className: "link-underline motion-interactive", children: "Terms" }),
72
+ " ",
73
+ "and",
74
+ " ",
75
+ /* @__PURE__ */ jsx("a", { href: "/privacy", className: "link-underline motion-interactive", children: "Privacy Policy" }),
76
+ "."
77
+ ] })
78
+ ] }) }),
79
+ /* @__PURE__ */ jsx("aside", { "aria-label": "Featured release", className: "hidden p-3 lg:flex", children: /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col justify-end rounded-xl p-8", style: { background: artwork }, children: /* @__PURE__ */ jsx(Card, { className: "max-w-md", children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
80
+ /* @__PURE__ */ jsx(Badge, { tone: "edition", className: "self-start", children: "Edition of 50" }),
81
+ /* @__PURE__ */ jsx("p", { className: "text-title-lg", children: "\u201CEvery edition is a conversation with whoever holds it next.\u201D" }),
82
+ /* @__PURE__ */ jsx(
83
+ Entity,
84
+ {
85
+ className: "p-0",
86
+ leading: /* @__PURE__ */ jsx(Avatar, { fallback: "MO" }),
87
+ title: "Mara Okafor",
88
+ subtitle: "Untitled (gold rush), 2026"
89
+ }
90
+ )
91
+ ] }) }) }) }) })
92
+ ] });
29
93
  }
30
94
 
31
95
  // src/patterns/auth-page.meta.ts
32
96
  var authPageMeta = {
33
97
  name: "AuthPage",
34
- summary: "Centered auth card: a form-width region vertically centered in the viewport, one Card holding the Form, an alternate-action divider, and a quiet switch-to-sign-up link beneath it.",
98
+ 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.",
35
99
  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).",
36
100
  notes: [
37
- "Region cap is `form` (480px) \u2014 the narrowest named cap; auth never needs more.",
38
- "The card is the only surface on the page: no app chrome, no sidebar \u2014 minimal exit paths.",
39
- "min-h-dvh + grid place-items-center centers the card without magic offsets.",
40
- "Field handles label/error wiring; the submit button is full-width \u2014 the primary (and only) action.",
41
- "Secondary actions (here, switch to sign-up; add forgot password the same way) are quiet `link-underline` links, never competing buttons."
101
+ "The form column is a `form`-capped Region (480px), so the controls keep the same measure whether the panel is present or not.",
102
+ "No card around the form: the page itself is the surface, and the brand panel carries the identity. A card on a blank page reads as a template.",
103
+ "The heading is `text-heading-1` in an h1 \u2014 it is the page outline, so it takes a heading tier, not a card title.",
104
+ "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.",
105
+ "Forgot password sits under the password field, right-aligned, before the submit button \u2014 so it comes right after the field in tab order. Field has no label-row action slot yet.",
106
+ "One primary action. Passkey and wallet are outline buttons under an `or` Separator; switching to sign-up is a quiet `link-underline` link.",
107
+ "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.",
108
+ "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-*) \u2026)` lift needs an `inline-color` disable with a reason.",
109
+ "The panel is `hidden lg:flex`: on a phone the form is the whole screen, with no decorative scroll above it."
42
110
  ],
43
111
  a11y: [
44
- "The route renders inside <main> with the page h1 in the card header (an h1 inside CardTitle, which carries the type).",
45
- "Field auto-wires labels, describedby, and invalid state onto the inputs.",
46
- "Autocomplete attributes (email, current-password) keep password managers working."
112
+ 'The route renders inside <main> with one h1; the brand panel is an <aside> labelled "Featured release".',
113
+ "Field auto-wires labels, describedby, and invalid state onto the inputs; on submit, the first invalid field is focused.",
114
+ "Autocomplete attributes (`email webauthn`, `current-password`) keep password managers and passkey autofill working.",
115
+ "The Forgot password and Create one links use `hit-target` to meet the 44px touch floor without changing layout.",
116
+ "The artwork swatches are aria-hidden decoration; the brand link's accessible name is the wordmark."
47
117
  ]
48
118
  };
49
- var navItems = ["Platform", "Signals", "Security", "Company"];
50
- var metrics = [
51
- { label: "Checks cleared", value: "1.8M", detail: "in the last 30 days" },
52
- { label: "Review time", value: "42s", detail: "median approval path" },
53
- { label: "Spaces online", value: "284", detail: "across active teams" }
119
+ var navItems = [
120
+ { label: "Platform", href: "#platform" },
121
+ { label: "How it works", href: "#how-it-works" },
122
+ { label: "Customers", href: "#customers" },
123
+ { label: "Pricing", href: "/pricing" }
54
124
  ];
55
- var workflow = [
125
+ var spaces = [
126
+ { name: "Waypoint", value: "98.4%", label: "clear routes" },
127
+ { name: "Fielddesk", value: "12", label: "open holds" },
128
+ { name: "Archive", value: "4.8k", label: "sealed records" }
129
+ ];
130
+ var routes = [
56
131
  { label: "Intake", state: "Open", tone: "info" },
57
132
  { label: "Review", state: "Live", tone: "edition" },
58
133
  { label: "Archive", state: "Safe", tone: "success" }
59
134
  ];
60
135
  var bars = [
61
- { height: "38%", tone: "bg-tone-info" },
62
- { height: "56%", tone: "bg-tone-edition" },
63
- { height: "44%", tone: "bg-tone-standard" },
64
- { height: "68%", tone: "bg-success" },
65
- { height: "52%", tone: "bg-tone-info" },
66
- { height: "78%", tone: "bg-highlight" },
67
- { height: "60%", tone: "bg-tone-edition" },
68
- { height: "86%", tone: "bg-success" },
69
- { height: "64%", tone: "bg-tone-standard" }
136
+ { week: "w1", height: "38%", current: false },
137
+ { week: "w2", height: "56%", current: false },
138
+ { week: "w3", height: "44%", current: false },
139
+ { week: "w4", height: "68%", current: false },
140
+ { week: "w5", height: "52%", current: false },
141
+ { week: "w6", height: "78%", current: false },
142
+ { week: "w7", height: "60%", current: false },
143
+ { week: "w8", height: "72%", current: false },
144
+ { week: "w9", height: "88%", current: true }
70
145
  ];
71
- var features = [
146
+ var sceneMetrics = [
147
+ { label: "Cleared", value: "1.8M", detail: "last 30 days" },
148
+ { label: "Review", value: "42s", detail: "median path" },
149
+ { label: "Spaces", value: "284", detail: "active teams" }
150
+ ];
151
+ var values = [
72
152
  {
73
153
  icon: "lock",
74
154
  title: "Private by default",
75
- body: "Workspace roles, approval trails, and handoff history stay clear without turning the interface into compliance fog."
155
+ body: "Roles, approvals, and handoff history stay with the people who need them. No shared inboxes, no forwarded screenshots."
76
156
  },
77
157
  {
78
- icon: "layout",
79
- title: "Brand and product together",
80
- body: "Editorial sections and dense console views share the same semantic tokens, radius, and motion contract."
158
+ icon: "bell",
159
+ title: "Signals before surprises",
160
+ body: "Cove watches every route and surfaces stalled reviews and expiring approvals while there is still time to act."
81
161
  },
82
162
  {
83
- icon: "circle-check",
84
- title: "Calm confidence",
85
- body: "Large type, tactile controls, and focused status tones make complex work feel understandable on first pass."
163
+ icon: "clock",
164
+ title: "A trail for every decision",
165
+ body: "Each approval keeps its context, reviewer, and timestamp, so an audit takes an afternoon instead of a quarter."
86
166
  }
87
167
  ];
88
- var reviews = [
89
- { name: "Waypoint", value: "98.4%", label: "clear routes" },
90
- { name: "Fielddesk", value: "12", label: "open holds" },
91
- { name: "Archive", value: "4.8k", label: "sealed records" }
168
+ var steps = [
169
+ {
170
+ number: "01",
171
+ title: "Connect your spaces",
172
+ body: "Bring teams, vendors, and record stores into one workspace, with roles that mirror how you already work."
173
+ },
174
+ {
175
+ number: "02",
176
+ title: "Route every approval",
177
+ body: "Requests follow the path you define, with reviewers, deadlines, and escalation built in from the start."
178
+ },
179
+ {
180
+ number: "03",
181
+ title: "Seal the record",
182
+ body: "Approved work is archived with its full context and is ready for review the moment it closes."
183
+ }
184
+ ];
185
+ var outcomes = [
186
+ { value: "3.2\xD7", label: "faster approvals in the first quarter" },
187
+ { value: "68%", label: "fewer follow-ups on handoffs" },
188
+ { value: "1 day", label: "to prepare a quarterly audit" }
92
189
  ];
93
190
  var footerGroups = [
94
191
  {
95
192
  title: "Platform",
96
193
  links: [
97
194
  { label: "Overview", href: "#platform" },
98
- { label: "Signals", href: "#signals" },
99
- { label: "Approvals", href: "#approvals" },
100
- { label: "Records", href: "#records" }
195
+ { label: "Signals", href: "/signals" },
196
+ { label: "Approvals", href: "/approvals" },
197
+ { label: "Records", href: "/records" }
101
198
  ]
102
199
  },
103
200
  {
@@ -105,7 +202,7 @@ var footerGroups = [
105
202
  links: [
106
203
  { label: "About", href: "/about" },
107
204
  { label: "Security", href: "/security" },
108
- { label: "Customers", href: "/customers" },
205
+ { label: "Customers", href: "#customers" },
109
206
  { label: "Careers", href: "/careers" }
110
207
  ]
111
208
  },
@@ -119,10 +216,10 @@ var footerGroups = [
119
216
  ]
120
217
  }
121
218
  ];
122
- var footerSignals = [
123
- { label: "Uptime", value: "99.98%" },
124
- { label: "Regions", value: "12" },
125
- { label: "Response", value: "42s" }
219
+ var legalLinks = [
220
+ { label: "Privacy", href: "/privacy" },
221
+ { label: "Terms", href: "/terms" },
222
+ { label: "Contact", href: "/contact" }
126
223
  ];
127
224
  function BrandLandingPage() {
128
225
  return /* @__PURE__ */ jsxs("div", { className: "min-h-dvh overflow-hidden bg-background text-foreground", children: [
@@ -136,7 +233,7 @@ function BrandLandingPage() {
136
233
  /* @__PURE__ */ jsx("span", { className: "grid size-8 place-items-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-sm" }) }),
137
234
  /* @__PURE__ */ jsx("span", { className: "text-title-lg", children: "Cove" })
138
235
  ] }),
139
- /* @__PURE__ */ jsx("div", { className: "hidden lg:block", children: /* @__PURE__ */ jsx(Row, { gap: 1, children: navItems.map((item) => /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: `#${item.toLowerCase()}` }), children: item }, item)) }) }),
236
+ /* @__PURE__ */ jsx("div", { className: "hidden lg:block", children: /* @__PURE__ */ jsx(Row, { gap: 1, children: navItems.map((item) => /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: item.href }), children: item.label }, item.label)) }) }),
140
237
  /* @__PURE__ */ jsxs(Row, { gap: 1, children: [
141
238
  /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "max-sm:hidden", render: /* @__PURE__ */ jsx("a", { href: "/login" }), children: "Sign in" }),
142
239
  /* @__PURE__ */ jsx(Button, { size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/demo" }), children: "Request access" })
@@ -145,118 +242,155 @@ function BrandLandingPage() {
145
242
  }
146
243
  ) }) }),
147
244
  /* @__PURE__ */ jsxs("main", { children: [
148
- /* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "pb-16 pt-14 sm:pt-20 lg:pb-24", children: /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-10 lg:grid-cols-[0.92fr_1.08fr]", children: [
149
- /* @__PURE__ */ jsxs(Stack, { gap: 4, className: "max-w-3xl", children: [
245
+ /* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "pb-20 pt-14 sm:pt-20 lg:pb-28", children: /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-12 lg:grid-cols-[1fr_1.1fr] lg:gap-10", children: [
246
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-2xl", children: [
150
247
  /* @__PURE__ */ jsx(Badge, { appearance: "outline", icon: /* @__PURE__ */ jsx(Icon, { name: "lock", className: "text-sm" }), children: "Private operations, made legible" }),
151
248
  /* @__PURE__ */ jsx(
152
249
  TextReveal,
153
250
  {
154
251
  as: "h1",
155
- text: "A calmer command center for high-trust digital work",
156
- className: "max-w-4xl text-balance text-display-xl sm:text-display-3xl",
252
+ text: "A calmer command center for high-trust work",
253
+ className: "text-balance text-display-lg sm:text-display-2xl",
157
254
  once: true
158
255
  }
159
256
  ),
160
257
  /* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-lg text-muted-foreground", children: "Cove gives teams a polished front door and a serious operating surface: approvals, signals, records, and handoffs in one understandable flow." }),
161
- /* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, children: [
258
+ /* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, className: "pt-3", children: [
162
259
  /* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/demo" }), children: "Book a walkthrough" }),
163
- /* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "#platform" }), children: "Explore platform" })
260
+ /* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "#platform" }), children: "Explore the platform" })
164
261
  ] })
165
262
  ] }),
166
- /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-xl border border-border-floating bg-overlay p-3 text-overlay-foreground shadow-xl", children: /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/3 border border-overlay-foreground/10 bg-overlay-foreground/8", children: [
167
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-overlay-foreground/10 px-4 py-3", children: [
263
+ /* @__PURE__ */ jsx("div", { className: "dark", "aria-hidden": true, inert: true, children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-xl border border-border-floating bg-popover p-3 text-popover-foreground shadow-xl", children: /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/3 border border-border bg-card", children: [
264
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-4 py-3", children: [
168
265
  /* @__PURE__ */ jsxs(Row, { gap: 1, children: [
169
266
  /* @__PURE__ */ jsx(StatusDot, { tone: "warning" }),
170
267
  /* @__PURE__ */ jsx(StatusDot, { tone: "edition" }),
171
268
  /* @__PURE__ */ jsx(StatusDot, { tone: "success" })
172
269
  ] }),
173
- /* @__PURE__ */ jsx("span", { className: "text-label-sm text-overlay-foreground/70", children: "Live workspace" })
270
+ /* @__PURE__ */ jsx("span", { className: "text-label-sm text-muted-foreground", children: "Live workspace" })
174
271
  ] }),
175
272
  /* @__PURE__ */ jsxs("div", { className: "grid gap-3 p-3 md:grid-cols-[0.7fr_1.3fr]", children: [
176
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
177
- /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-overlay-foreground/10 bg-overlay-foreground/8 p-4", children: [
178
- /* @__PURE__ */ jsx("p", { className: "text-label-sm text-overlay-foreground/60", children: "Spaces" }),
179
- /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: reviews.map((review) => /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/10 bg-overlay p-3", children: [
273
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 max-md:hidden", children: [
274
+ /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-border bg-background p-4", children: [
275
+ /* @__PURE__ */ jsx("p", { className: "text-label-sm text-muted-foreground", children: "Spaces" }),
276
+ /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: spaces.map((space) => /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/10 bg-card p-3", children: [
180
277
  /* @__PURE__ */ jsxs(Row, { justify: "between", children: [
181
- /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: review.name }),
182
- /* @__PURE__ */ jsx("span", { className: "text-mono-sm text-overlay-foreground", children: review.value })
278
+ /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: space.name }),
279
+ /* @__PURE__ */ jsx("span", { className: "text-mono-sm", children: space.value })
183
280
  ] }),
184
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-body-xs text-overlay-foreground/60", children: review.label })
185
- ] }, review.name)) })
281
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-body-xs text-muted-foreground", children: space.label })
282
+ ] }, space.name)) })
186
283
  ] }),
187
- /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-overlay-foreground/10 bg-overlay-foreground/8 p-4", children: [
284
+ /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-border bg-background p-4", children: [
188
285
  /* @__PURE__ */ jsxs(Row, { justify: "between", children: [
189
286
  /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Route status" }),
190
- /* @__PURE__ */ jsx(Badge, { tone: "success", size: "sm", children: "Healthy" })
287
+ /* @__PURE__ */ jsx(Badge, { tone: "success", size: "sm", dot: true, children: "Healthy" })
191
288
  ] }),
192
- /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: workflow.map((item) => /* @__PURE__ */ jsxs(Row, { justify: "between", className: "rounded-full bg-overlay px-3 py-2", children: [
289
+ /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: routes.map((route) => /* @__PURE__ */ jsxs(Row, { justify: "between", className: "rounded-full bg-card px-3 py-2", children: [
193
290
  /* @__PURE__ */ jsxs(Row, { gap: 1, children: [
194
- /* @__PURE__ */ jsx(StatusDot, { tone: item.tone }),
195
- /* @__PURE__ */ jsx("span", { className: "text-body-sm", children: item.label })
291
+ /* @__PURE__ */ jsx(StatusDot, { tone: route.tone }),
292
+ /* @__PURE__ */ jsx("span", { className: "text-body-sm", children: route.label })
196
293
  ] }),
197
- /* @__PURE__ */ jsx("span", { className: "text-label-sm text-overlay-foreground/60", children: item.state })
198
- ] }, item.label)) })
294
+ /* @__PURE__ */ jsx("span", { className: "text-label-sm text-muted-foreground", children: route.state })
295
+ ] }, route.label)) })
199
296
  ] })
200
297
  ] }),
201
- /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-overlay-foreground/10 bg-overlay-foreground/8 p-4", children: [
202
- /* @__PURE__ */ jsxs(Row, { justify: "between", className: "border-b border-overlay-foreground/10 pb-4", children: [
298
+ /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-border bg-background p-4", children: [
299
+ /* @__PURE__ */ jsxs(Row, { justify: "between", className: "border-b border-border pb-4", children: [
203
300
  /* @__PURE__ */ jsxs("div", { children: [
204
- /* @__PURE__ */ jsx("p", { className: "text-label-sm text-overlay-foreground/60", children: "Signal map" }),
205
- /* @__PURE__ */ jsx("h2", { className: "mt-1 text-heading-3", children: "Every approval path in view" })
301
+ /* @__PURE__ */ jsx("p", { className: "text-label-sm text-muted-foreground", children: "Signal map" }),
302
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-heading-4 sm:text-heading-3", children: "Every approval path in view" })
206
303
  ] }),
207
304
  /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", children: "Review" })
208
305
  ] }),
209
- /* @__PURE__ */ jsxs("div", { className: "mt-6 grid min-h-72 grid-rows-[1fr_auto] gap-5", children: [
210
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-9 items-end gap-2 rounded-concentric-xl/10 bg-overlay p-4", children: bars.map((bar, index) => /* @__PURE__ */ jsx("div", { className: "flex h-56 items-end", children: /* @__PURE__ */ jsx(
306
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 grid gap-4 sm:mt-6 sm:gap-5", children: [
307
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-9 items-end gap-1.5 rounded-concentric-xl/10 bg-card p-4 sm:gap-2", children: bars.map((bar) => /* @__PURE__ */ jsx("div", { className: "flex h-36 items-end sm:h-56", children: /* @__PURE__ */ jsx(
211
308
  "div",
212
309
  {
213
- className: `w-full rounded-full ${bar.tone}`,
310
+ className: `w-full rounded-full ${bar.current ? "bg-success" : "bg-secondary"}`,
214
311
  style: { height: bar.height }
215
312
  }
216
- ) }, `${bar.tone}-${index}`)) }),
217
- /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-3", children: metrics.map((metric) => /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/10 border border-overlay-foreground/10 bg-overlay p-3", children: [
218
- /* @__PURE__ */ jsx("p", { className: "text-label-sm text-overlay-foreground/60", children: metric.label }),
219
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-heading-3", children: metric.value }),
220
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-body-xs text-overlay-foreground/60", children: metric.detail })
313
+ ) }, bar.week)) }),
314
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: sceneMetrics.map((metric) => /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/10 border border-border bg-card p-3", children: [
315
+ /* @__PURE__ */ jsx("p", { className: "truncate text-label-sm text-muted-foreground", children: metric.label }),
316
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-heading-4 sm:text-heading-3", children: metric.value }),
317
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-body-xs text-muted-foreground max-sm:hidden", children: metric.detail })
221
318
  ] }, metric.label)) })
222
319
  ] })
223
320
  ] })
224
321
  ] })
225
322
  ] }) }) })
226
323
  ] }) }),
227
- /* @__PURE__ */ jsx("section", { id: "platform", className: "border-y border-border bg-muted/40 py-14", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-5 lg:grid-cols-[0.8fr_1.2fr]", children: [
228
- /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
229
- /* @__PURE__ */ jsx(Badge, { children: "Design mood" }),
230
- /* @__PURE__ */ jsx("h2", { className: "text-heading-1", children: "Marketing warmth, product discipline." }),
231
- /* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-md text-muted-foreground", children: "The page uses a pill nav, tactile surfaces, soft status tones, and slower ease-out movement. It stays broad in color so it does not collapse into purple-brand sameness." })
324
+ /* @__PURE__ */ jsx("section", { id: "platform", "aria-labelledby": "brand-platform-title", className: "border-y border-border bg-muted/40 py-20 lg:py-28", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
325
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, className: "max-w-2xl", children: [
326
+ /* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Why Cove" }),
327
+ /* @__PURE__ */ jsx("h2", { id: "brand-platform-title", className: "text-balance text-heading-1", children: "One private workspace for the decisions that matter." }),
328
+ /* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-lg text-muted-foreground", children: "Most operations work lives in threads, spreadsheets, and memory. Cove gives it a home: every request routed, every signal visible, every outcome on the record." })
329
+ ] }),
330
+ /* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx(Grid, { minItemWidth: "calc(var(--spacing) * 64)", gap: 2, children: values.map((value) => /* @__PURE__ */ jsx(Card, { render: /* @__PURE__ */ jsx("article", {}), className: "h-full", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-2", children: [
331
+ /* @__PURE__ */ jsx("span", { className: "mb-4 grid size-11 place-items-center rounded-md border border-border sable-well", children: /* @__PURE__ */ jsx(Icon, { name: value.icon, className: "text-xl" }) }),
332
+ /* @__PURE__ */ jsx("h3", { className: "text-title-lg", children: value.title }),
333
+ /* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: value.body })
334
+ ] }) }, value.title)) }) })
335
+ ] }) }) }),
336
+ /* @__PURE__ */ jsx(Region, { as: "section", id: "how-it-works", "aria-labelledby": "brand-steps-title", max: "wide", className: "py-20 lg:py-28", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-12 lg:grid-cols-[1fr_2fr] lg:gap-16", children: [
337
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, className: "max-w-md", children: [
338
+ /* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "How it works" }),
339
+ /* @__PURE__ */ jsx("h2", { id: "brand-steps-title", className: "text-balance text-heading-1", children: "From request to sealed record." }),
340
+ /* @__PURE__ */ jsx("p", { className: "text-body-md text-muted-foreground", children: "Teams are usually live within a week. Cove adapts to the routes you already run instead of asking you to start over." }),
341
+ /* @__PURE__ */ jsx("div", { className: "pt-3", children: /* @__PURE__ */ jsx(Button, { variant: "outline", iconEnd: /* @__PURE__ */ jsx(Icon, { name: "arrow-right" }), render: /* @__PURE__ */ jsx("a", { href: "/guides/rollout" }), children: "Read the rollout guide" }) })
342
+ ] }),
343
+ /* @__PURE__ */ jsx("ol", { className: "grid gap-10 sm:grid-cols-3 sm:gap-6", children: steps.map((step) => /* @__PURE__ */ jsxs("li", { className: "flex flex-col gap-2 border-t border-border-strong pt-6", children: [
344
+ /* @__PURE__ */ jsx("span", { className: "text-mono-sm text-muted-foreground", children: step.number }),
345
+ /* @__PURE__ */ jsx("h3", { className: "mt-3 text-title-lg sm:mt-6", children: step.title }),
346
+ /* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: step.body })
347
+ ] }, step.number)) })
348
+ ] }) }),
349
+ /* @__PURE__ */ jsx(Region, { as: "section", id: "customers", "aria-label": "Customer story", max: "wide", className: "pb-20 lg:pb-28", children: /* @__PURE__ */ jsx(Card, { render: /* @__PURE__ */ jsx("figure", {}), children: /* @__PURE__ */ jsxs("div", { className: "grid lg:grid-cols-[1.5fr_1fr]", children: [
350
+ /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col justify-between gap-10 sm:p-10", children: [
351
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
352
+ /* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Customer story" }),
353
+ /* @__PURE__ */ jsx("blockquote", { className: "text-balance text-heading-3 sm:text-heading-2", children: "\u201CWe used to chase approvals across four tools. Now every request has an owner, a deadline, and a record, and our auditors stopped asking for screenshots.\u201D" })
354
+ ] }),
355
+ /* @__PURE__ */ jsxs("figcaption", { className: "flex items-center gap-3", children: [
356
+ /* @__PURE__ */ jsx(Avatar, { size: "lg", fallback: "MO" }),
357
+ /* @__PURE__ */ jsxs("div", { children: [
358
+ /* @__PURE__ */ jsx("p", { className: "text-title-sm", children: "Maya Okafor" }),
359
+ /* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: "Head of operations, Fielddesk" })
360
+ ] })
361
+ ] })
232
362
  ] }),
233
- /* @__PURE__ */ jsx("div", { className: "grid gap-3 md:grid-cols-3", children: features.map((feature) => /* @__PURE__ */ jsx(Card, { render: /* @__PURE__ */ jsx("article", {}), className: "h-full", children: /* @__PURE__ */ jsxs(CardContent, { children: [
234
- /* @__PURE__ */ jsx("span", { className: "grid size-10 place-items-center rounded-md bg-highlight text-highlight-foreground", children: /* @__PURE__ */ jsx(Icon, { name: feature.icon, className: "text-xl" }) }),
235
- /* @__PURE__ */ jsx("h3", { className: "mt-5 text-title-lg", children: feature.title }),
236
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-body-sm text-muted-foreground", children: feature.body })
237
- ] }) }, feature.title)) })
363
+ /* @__PURE__ */ jsx("div", { className: "border-border max-lg:border-t lg:border-s", children: /* @__PURE__ */ jsx(CardContent, { className: "flex h-full flex-col justify-center gap-6 sm:p-10", children: outcomes.map((outcome, index) => /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
364
+ index > 0 && /* @__PURE__ */ jsx(Separator, {}),
365
+ /* @__PURE__ */ jsxs("div", { children: [
366
+ /* @__PURE__ */ jsx("p", { className: "text-heading-1 sm:text-display-lg", children: outcome.value }),
367
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-body-sm text-muted-foreground", children: outcome.label })
368
+ ] })
369
+ ] }, outcome.label)) }) })
238
370
  ] }) }) }),
239
- /* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "py-14", children: /* @__PURE__ */ jsx("div", { className: "grid gap-3 md:grid-cols-3", children: metrics.map((metric) => /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { children: [
240
- /* @__PURE__ */ jsx("p", { className: "text-label-md text-muted-foreground", children: metric.label }),
241
- /* @__PURE__ */ jsx("p", { className: "mt-3 text-display-lg", children: metric.value }),
242
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-body-sm text-muted-foreground", children: metric.detail })
243
- ] }) }, metric.label)) }) })
371
+ /* @__PURE__ */ jsx(Region, { as: "section", "aria-labelledby": "brand-cta-title", max: "wide", className: "pb-20 lg:pb-28", children: /* @__PURE__ */ jsx("div", { className: "dark", children: /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-border bg-card px-6 py-12 text-card-foreground sm:px-12 sm:py-16", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-8 lg:flex-row lg:items-end lg:justify-between", children: [
372
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-2xl", children: [
373
+ /* @__PURE__ */ jsx("h2", { id: "brand-cta-title", className: "text-balance text-heading-1", children: "See your own approval paths in Cove." }),
374
+ /* @__PURE__ */ jsx("p", { className: "text-body-lg text-muted-foreground", children: "A 30-minute walkthrough with your routes, your roles, and your records. No slides." })
375
+ ] }),
376
+ /* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, children: [
377
+ /* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/demo" }), children: "Book a walkthrough" }),
378
+ /* @__PURE__ */ jsx(Button, { size: "cta", variant: "ghost", render: /* @__PURE__ */ jsx("a", { href: "/pricing" }), children: "View pricing" })
379
+ ] })
380
+ ] }) }) }) })
244
381
  ] }),
245
- /* @__PURE__ */ jsx("footer", { className: "border-t border-border bg-muted/35", children: /* @__PURE__ */ jsxs(Region, { max: "wide", className: "py-10 sm:py-12", children: [
246
- /* @__PURE__ */ jsxs("div", { className: "grid gap-8 lg:grid-cols-[1fr_1.35fr_1fr]", children: [
247
- /* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-md", children: [
382
+ /* @__PURE__ */ jsx("footer", { className: "border-t border-border bg-muted/35", children: /* @__PURE__ */ jsxs(Region, { max: "wide", className: "py-12 sm:py-16", children: [
383
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-10 lg:grid-cols-[1fr_1.35fr_1fr] lg:gap-8", children: [
384
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, className: "max-w-sm", children: [
248
385
  /* @__PURE__ */ jsxs("a", { href: "/", className: "flex w-fit items-center gap-2 rounded-full motion-interactive motion-press", children: [
249
386
  /* @__PURE__ */ jsx("span", { className: "grid size-9 place-items-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-sm" }) }),
250
387
  /* @__PURE__ */ jsx("span", { className: "text-title-lg", children: "Cove" })
251
388
  ] }),
252
389
  /* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: "A calm operating surface for teams that need trust, speed, and context without the noise." }),
253
- /* @__PURE__ */ jsxs(Row, { gap: 1, wrap: true, children: [
254
- /* @__PURE__ */ jsx(Badge, { size: "sm", icon: /* @__PURE__ */ jsx(Icon, { name: "lock" }), children: "Private" }),
255
- /* @__PURE__ */ jsx(Badge, { tone: "success", size: "sm", icon: /* @__PURE__ */ jsx(Icon, { name: "circle-check" }), children: "Live" })
256
- ] })
390
+ /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: "success", dot: true, children: "All systems normal" })
257
391
  ] }),
258
- /* @__PURE__ */ jsx("nav", { "aria-label": "Footer", className: "grid gap-6 sm:grid-cols-3", children: footerGroups.map((group) => /* @__PURE__ */ jsxs("div", { children: [
259
- /* @__PURE__ */ jsx("h2", { className: "text-label-md text-muted-foreground", children: group.title }),
392
+ /* @__PURE__ */ jsx("nav", { "aria-label": "Footer", className: "grid grid-cols-3 gap-6", children: footerGroups.map((group) => /* @__PURE__ */ jsxs("div", { children: [
393
+ /* @__PURE__ */ jsx("h2", { className: "text-label-md", children: group.title }),
260
394
  /* @__PURE__ */ jsx("ul", { className: "mt-3 space-y-1", children: group.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
261
395
  "a",
262
396
  {
@@ -266,62 +400,38 @@ function BrandLandingPage() {
266
400
  }
267
401
  ) }, link.label)) })
268
402
  ] }, group.title)) }),
269
- /* @__PURE__ */ jsxs(Card, { children: [
403
+ /* @__PURE__ */ jsxs(Card, { className: "h-fit", children: [
270
404
  /* @__PURE__ */ jsxs(CardHeader, { children: [
271
- /* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h2", { children: "Signal digest" }) }),
272
- /* @__PURE__ */ jsx(CardDescription, { children: "One weekly note on trusted workflows and product releases." }),
273
- /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx("span", { className: "grid size-9 place-items-center rounded-full bg-highlight text-highlight-foreground", children: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-sm" }) }) })
405
+ /* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h2", { children: "The Cove digest" }) }),
406
+ /* @__PURE__ */ jsx(CardDescription, { children: "One note a month on trusted workflows and product releases." })
274
407
  ] }),
275
- /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4", children: [
276
- /* @__PURE__ */ jsxs("form", { action: "/newsletter", className: "grid gap-2 sm:grid-cols-[1fr_auto]", children: [
277
- /* @__PURE__ */ jsx("label", { htmlFor: "brand-footer-email", className: "sr-only", children: "Work email" }),
278
- /* @__PURE__ */ jsx(
279
- Input,
280
- {
281
- id: "brand-footer-email",
282
- name: "email",
283
- type: "email",
284
- autoComplete: "email",
285
- placeholder: "you@company.com"
286
- }
287
- ),
288
- /* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full sm:w-auto", children: "Join" })
289
- ] }),
290
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: footerSignals.map((signal) => /* @__PURE__ */ jsxs("div", { className: "rounded-md bg-muted p-3", children: [
291
- /* @__PURE__ */ jsx("p", { className: "text-mono-sm", children: signal.value }),
292
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-caption text-muted-foreground", children: signal.label })
293
- ] }, signal.label)) })
294
- ] })
408
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("form", { action: "/newsletter", className: "grid gap-2 sm:grid-cols-[1fr_auto]", children: [
409
+ /* @__PURE__ */ jsx("label", { htmlFor: "brand-footer-email", className: "sr-only", children: "Work email" }),
410
+ /* @__PURE__ */ jsx(
411
+ Input,
412
+ {
413
+ id: "brand-footer-email",
414
+ name: "email",
415
+ type: "email",
416
+ autoComplete: "email",
417
+ placeholder: "you@company.com"
418
+ }
419
+ ),
420
+ /* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full sm:w-auto", children: "Subscribe" })
421
+ ] }) })
295
422
  ] })
296
423
  ] }),
297
- /* @__PURE__ */ jsxs("div", { className: "mt-10 flex flex-col gap-3 border-t border-border pt-5 text-caption text-muted-foreground sm:flex-row sm:items-center sm:justify-between", children: [
424
+ /* @__PURE__ */ jsxs("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", children: [
298
425
  /* @__PURE__ */ jsx("p", { children: "\xA9 2026 Cove. All rights reserved." }),
299
- /* @__PURE__ */ jsxs(Row, { gap: 3, wrap: true, children: [
300
- /* @__PURE__ */ jsx(
301
- "a",
302
- {
303
- href: "/privacy",
304
- className: "flex min-h-9 min-w-9 items-center motion-interactive hover:text-foreground",
305
- children: "Privacy"
306
- }
307
- ),
308
- /* @__PURE__ */ jsx(
309
- "a",
310
- {
311
- href: "/terms",
312
- className: "flex min-h-9 min-w-9 items-center motion-interactive hover:text-foreground",
313
- children: "Terms"
314
- }
315
- ),
316
- /* @__PURE__ */ jsx(
317
- "a",
318
- {
319
- href: "/contact",
320
- className: "flex min-h-9 min-w-9 items-center motion-interactive hover:text-foreground",
321
- children: "Contact"
322
- }
323
- )
324
- ] })
426
+ /* @__PURE__ */ jsx(Row, { gap: 4, wrap: true, children: legalLinks.map((link) => /* @__PURE__ */ jsx(
427
+ "a",
428
+ {
429
+ href: link.href,
430
+ className: "flex min-h-9 min-w-9 items-center motion-interactive hover:text-foreground",
431
+ children: link.label
432
+ },
433
+ link.label
434
+ )) })
325
435
  ] })
326
436
  ] }) })
327
437
  ] });
@@ -330,26 +440,30 @@ function BrandLandingPage() {
330
440
  // src/patterns/brand-landing-page.meta.ts
331
441
  var brandLandingPageMeta = {
332
442
  name: "BrandLandingPage",
333
- summary: "High-fidelity brand/product landing page: pill navigation, editorial hero type, a dark product scene, calm status tones, CTA pair, feature cards, metrics, and a complete branded footer.",
443
+ summary: "High-fidelity brand/product landing page: pill navigation, editorial hero type, a dark product scene, a value section, a how-it-works sequence, a customer story with outcomes, a closing CTA band, and a complete branded footer.",
334
444
  intent: "Use this for a polished product marketing route that still needs to show a credible app surface. It is warmer and more tactile than the canonical LandingPage, but avoids trend-driven neon and generic hype cues.",
335
445
  notes: [
336
- "One display headline owns the first viewport; supporting copy stays in body-lg at prose measure.",
446
+ "The story runs promise \u2192 product \u2192 value \u2192 process \u2192 proof \u2192 ask: hero, product scene, 'Why Cove' value cards, a three-step how-it-works list, a customer story with outcome figures, then one closing CTA. Each section adds new evidence; no number is repeated from the product scene.",
447
+ "One display headline owns the first viewport; supporting copy stays in body-lg at prose measure. Section headings are heading-1 under a `text-overline` eyebrow.",
337
448
  "The nav spends one compact pill surface, with a clear brand signal and two primary paths: sign in and request access.",
338
- "The product scene uses overlay tokens for a stable dark media surface, then semantic tones for status and chart signals.",
339
- "The footer closes the page with brand reassurance, a three-column sitemap, a digest signup, operational mini-metrics, and legal links.",
340
- "Every surface outside the product scene is a real Card (feature, metric and digest cards); status marks are StatusDot. The scene's nested panels take `rounded-concentric-xl/<inset>` from the shell's radius, so the corners stay parallel at any radius preset.",
341
- "Radius and shadow are theme-controlled. In Cove the same rounded-lg/xl classes read larger and softer; in other themes the structure still holds.",
342
- "Color is intentionally broad: porcelain/plum/lavender/blue/mint/saffron/coral through semantic roles, not a one-note purple gradient."
449
+ "The product scene and the closing CTA band are dark islands: a `dark` class on the wrapper re-scopes every semantic token, so surfaces, muted ink, Badge tones and Buttons take the theme's own dark values in either page mode. Never fake a dark panel with overlay ink at reduced opacity \u2014 status tones tuned for a light page vanish on it.",
450
+ "The scene is a picture of the product: `aria-hidden` + `inert` keep its sample button and figures out of the tab order and the outline. On narrow screens it drops the side column and keeps the signal map, so it scales instead of stacking into a second page.",
451
+ "The chart keeps past weeks on the quiet `secondary` fill and gives the current week the one accent, so the signal reads at a glance.",
452
+ "Value cards are real Cards with a neutral `sable-well` icon tile; the customer story is a Card rendered as a figure, with outcome figures split by Separator. The scene's nested panels take `rounded-concentric-xl/<inset>` from the shell's radius, so the corners stay parallel at any radius preset.",
453
+ "The footer closes the page with brand reassurance, a plain status Badge, a three-column sitemap (three columns at every width), a digest signup, and legal links.",
454
+ "All figures, names and the testimonial are sample content; replace them with real, verifiable claims."
343
455
  ],
344
456
  a11y: [
345
457
  "The brand nav is a labeled nav landmark with real links and visible text.",
458
+ "Each content section is a labelled region (by its h2, or aria-label for the customer story); the decorative product scene is hidden from assistive tech and inert.",
459
+ "The how-it-works steps are an ordered list; the testimonial is a figure with a blockquote and a figcaption naming the speaker.",
346
460
  "The footer is a real footer landmark with a labeled footer nav, visible link text, and a labeled email field.",
347
- "TextReveal preserves the full headline as one accessible label and honors reduced-motion policy.",
348
- "Status dots are paired with visible text labels so color is never the only signal."
461
+ "TextReveal preserves the full headline as one accessible label and honors reduced-motion policy; Reveal shows the value cards immediately when motion is off.",
462
+ "Status dots and the status Badge are paired with visible text labels so color is never the only signal."
349
463
  ]
350
464
  };
351
465
  var pending = true;
352
- var streamed = "Anchor on the oldest rendered message and add back however far it moved. Appends";
466
+ var streamed = "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";
353
467
  var turns = [
354
468
  {
355
469
  id: "t_01",
@@ -359,6 +473,7 @@ var turns = [
359
473
  {
360
474
  id: "t_02",
361
475
  mine: false,
476
+ step: "Read message-scroller.md",
362
477
  body: "Almost always the anchor. If you preserve the scroll position by measuring the container, prepending history moves everything under the reader."
363
478
  },
364
479
  {
@@ -368,6 +483,14 @@ var turns = [
368
483
  }
369
484
  ];
370
485
  function ChatPanel() {
486
+ const assistant = /* @__PURE__ */ jsx(
487
+ Avatar,
488
+ {
489
+ size: "sm",
490
+ alt: "Assistant",
491
+ fallback: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-base" })
492
+ }
493
+ );
371
494
  return /* @__PURE__ */ jsx(Page, { as: "main", max: "feed", gap: 0, className: "py-6", children: /* @__PURE__ */ jsxs(
372
495
  Card,
373
496
  {
@@ -377,45 +500,78 @@ function ChatPanel() {
377
500
  /* @__PURE__ */ jsxs(CardHeader, { className: "border-b border-border pb-(--card-p)", children: [
378
501
  /* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h1", { id: "chat-title", children: "Scroll behaviour" }) }),
379
502
  /* @__PURE__ */ jsx(CardDescription, { children: "Assistant \xB7 answers from your team's docs" }),
380
- /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon-sm", "aria-label": "Start a new chat", children: /* @__PURE__ */ jsx(Icon, { name: "plus" }) }) })
503
+ /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", children: [
504
+ /* @__PURE__ */ jsx(Icon, { name: "edit" }),
505
+ "New chat"
506
+ ] }) })
381
507
  ] }),
382
508
  /* @__PURE__ */ jsxs(MessageScroller, { className: "min-h-0 flex-1 p-(--card-p)", children: [
383
509
  /* @__PURE__ */ jsx(Marker, { variant: "separator", children: "Today" }),
384
- turns.map((turn, i) => /* @__PURE__ */ jsx(
510
+ turns.map((turn, i) => {
511
+ const continues = i > 0 && turns[i - 1].mine === turn.mine;
512
+ return /* @__PURE__ */ jsx(
513
+ Message,
514
+ {
515
+ messageId: turn.id,
516
+ align: turn.mine ? "end" : "start",
517
+ grouped: continues,
518
+ avatar: turn.mine ? void 0 : assistant,
519
+ header: turn.step ? /* @__PURE__ */ jsx(Marker, { variant: "note", icon: "book", className: "justify-start", children: turn.step }) : void 0,
520
+ children: /* @__PURE__ */ jsx(
521
+ Bubble,
522
+ {
523
+ variant: turn.mine ? "primary" : "muted",
524
+ align: turn.mine ? "end" : "start",
525
+ tail: !continues,
526
+ children: turn.body
527
+ }
528
+ )
529
+ },
530
+ turn.id
531
+ );
532
+ }),
533
+ /* @__PURE__ */ jsx(
385
534
  Message,
386
535
  {
387
- messageId: turn.id,
388
- align: turn.mine ? "end" : "start",
389
- grouped: i > 0 && turns[i - 1].mine === turn.mine,
390
- avatar: turn.mine ? void 0 : /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AI" }),
391
- children: /* @__PURE__ */ jsx(
392
- Bubble,
393
- {
394
- variant: turn.mine ? "primary" : "muted",
395
- align: turn.mine ? "end" : "start",
396
- tail: i === 0 || turns[i - 1].mine !== turn.mine,
397
- children: turn.body
398
- }
399
- )
400
- },
401
- turn.id
402
- )),
403
- /* @__PURE__ */ jsx(Marker, { variant: "note", icon: "sparkles", children: "Read message-scroller.md" }),
404
- /* @__PURE__ */ jsxs(Message, { messageId: "t_04", avatar: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AI" }), children: [
405
- /* @__PURE__ */ jsxs(Bubble, { variant: "plain", children: [
406
- /* @__PURE__ */ jsx("p", { className: "text-body-md text-foreground", children: "Here is the shape of it:" }),
407
- /* @__PURE__ */ jsx(
408
- CodeBlock,
409
- {
410
- code: "const shift = anchor.offsetTop - lastTop\nif (shift !== 0) viewport.scrollTop += shift",
411
- filename: "preserve.ts"
412
- }
413
- )
414
- ] }),
415
- /* @__PURE__ */ jsx(BubbleReactions, { children: /* @__PURE__ */ jsx(BubbleReaction, { emoji: "\u{1F3AF}", count: 2, active: true }) })
536
+ messageId: "t_04",
537
+ grouped: true,
538
+ avatar: assistant,
539
+ footer: /* @__PURE__ */ jsxs(Toolbar, { "aria-label": "Answer actions", children: [
540
+ /* @__PURE__ */ jsx(SimpleTooltip, { content: "Copy answer", children: /* @__PURE__ */ jsx(ToolbarButton, { size: "icon-sm", "aria-label": "Copy answer", children: /* @__PURE__ */ jsx(Icon, { name: "copy" }) }) }),
541
+ /* @__PURE__ */ jsx(SimpleTooltip, { content: "Regenerate", children: /* @__PURE__ */ jsx(ToolbarButton, { size: "icon-sm", "aria-label": "Regenerate answer", children: /* @__PURE__ */ jsx(Icon, { name: "refresh" }) }) })
542
+ ] }),
543
+ children: /* @__PURE__ */ jsxs(Bubble, { variant: "plain", className: "max-w-full", children: [
544
+ /* @__PURE__ */ jsx("p", { className: "text-body-md text-foreground", children: "Here is the shape of it:" }),
545
+ /* @__PURE__ */ jsx(
546
+ CodeBlock,
547
+ {
548
+ code: "const shift = anchor.offsetTop - lastTop\nif (shift !== 0) viewport.scrollTop += shift",
549
+ filename: "preserve.ts"
550
+ }
551
+ )
552
+ ] })
553
+ }
554
+ ),
555
+ /* @__PURE__ */ jsxs(Message, { messageId: "t_05", align: "end", children: [
556
+ /* @__PURE__ */ jsx(Bubble, { variant: "primary", align: "end", children: "Here's ours. Does the same fix cover new messages arriving?" }),
557
+ /* @__PURE__ */ jsx(
558
+ Attachment,
559
+ {
560
+ name: "scroller.tsx",
561
+ meta: "4.2 KB \xB7 TSX",
562
+ onOpen: () => void 0
563
+ }
564
+ )
416
565
  ] }),
417
- /* @__PURE__ */ jsx(Message, { messageId: "t_05", align: "end", children: /* @__PURE__ */ jsx(Attachment, { name: "scroller.tsx", meta: "4.2 KB \xB7 TSX" }) }),
418
- /* @__PURE__ */ jsx(Message, { messageId: "t_06", avatar: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AI" }), children: /* @__PURE__ */ jsx(Bubble, { variant: "plain", children: /* @__PURE__ */ jsx(StreamingText, { text: streamed, streaming: pending }) }) })
566
+ /* @__PURE__ */ jsx(
567
+ Message,
568
+ {
569
+ messageId: "t_06",
570
+ avatar: assistant,
571
+ header: /* @__PURE__ */ jsx(Marker, { variant: "note", icon: "file", className: "justify-start", children: "Read scroller.tsx" }),
572
+ children: /* @__PURE__ */ jsx(Bubble, { variant: "plain", children: /* @__PURE__ */ jsx(StreamingText, { text: streamed, streaming: pending }) })
573
+ }
574
+ )
419
575
  ] }),
420
576
  /* @__PURE__ */ jsx(CardFooter, { className: "border-t border-border pt-(--card-p)", children: /* @__PURE__ */ jsx(
421
577
  ChatComposer,
@@ -424,6 +580,7 @@ function ChatPanel() {
424
580
  placeholder: "Ask a follow-up\u2026",
425
581
  busy: pending,
426
582
  onStop: () => void 0,
583
+ stopLabel: "Stop answering",
427
584
  onAttach: () => void 0,
428
585
  footer: "Enter to send \xB7 Shift+Enter for a new line",
429
586
  actions: /* @__PURE__ */ jsxs(
@@ -473,7 +630,11 @@ var chatPanelMeta = {
473
630
  "Region cap is `feed` (640px) \u2014 a conversation is a reading column, and the Bubble measure cap is tuned to the same assumption.",
474
631
  "The composer sits outside the scroll region, in a bordered CardFooter, so it stays put while the transcript moves.",
475
632
  "Grouping is derived from the data (`turns[i - 1].mine === turn.mine`) rather than hand-set, and the tail is dropped on continuations \u2014 that pairing is what makes a run read as one block.",
476
- 'The assistant\'s long-form answer uses `variant="plain"`. A tinted box around a code block reads as a quote rather than as a turn.',
633
+ 'What the assistant did before answering \u2014 a doc it read, a tool it ran \u2014 is a `Marker variant="note"` in that message\'s `header`, so it reads as part of the turn rather than as an event between turns.',
634
+ 'The assistant\'s long-form answer uses `variant="plain"` and continues the same grouped run. A tinted box around a code block reads as a quote rather than as a turn. A plain Bubble sizes to its content with no measure cap, so it takes `max-w-full` here: bounded by the column, a wide code line scrolls inside the CodeBlock instead of pushing the transcript sideways on a phone.',
635
+ "A settled answer carries its actions (copy, regenerate) in the Message `footer` as a `Toolbar` of icon `ToolbarButton`s with tooltips \u2014 one tab stop, arrow keys between them. The answer still streaming has none: there is nothing to copy or regenerate until it settles, and Stop lives in the composer.",
636
+ "One turn, one Message: the reader's question and the file it refers to stack inside a single end-aligned Message rather than being sent as two.",
637
+ 'Assistant turns share one Avatar with a glyph fallback and `alt="Assistant"` \u2014 initials like "AI" read as a person, and a fallback-only avatar is otherwise unnamed.',
477
638
  "The answer in flight is a `StreamingText` in a `plain` Bubble, and ONE `pending` flag drives both it and the composer's `busy`. They are the same wait reported at the two ends of the panel; letting them drift gives you a composer still saying 'sending' under an answer that already finished.",
478
639
  "A spinner `Marker` (or `ThinkingOrb`) belongs BEFORE the first token and is replaced once tokens land, not kept alongside. Two indicators for one wait read as two things happening \u2014 which is also why `StreamingText` takes `caret={false}` when something adjacent already reports it.",
479
640
  'MessageScroller needs no configuring for this: it re-follows on "a message grew" (ResizeObserver) as well as "a message arrived", so the newest token stays pinned while a reader who has scrolled up is left alone.',
@@ -482,40 +643,77 @@ var chatPanelMeta = {
482
643
  a11y: [
483
644
  "The panel header's title is the page's <h1> (an h1 inside CardTitle, which carries the type); the transcript is a scroll region with native overflow, so keyboard scrolling works unchanged.",
484
645
  "The answer in flight carries `aria-busy` rather than sitting in a live region. That is deliberate: a live region around streamed text announces every token, which is unusable. Announce once when the turn settles, from whatever owns the turn \u2014 the incomplete state is still exposed, just not narrated.",
485
- "The composer is a real <form> whose Enter handling skips IME composition, and every icon-only control in the panel carries an accessible label."
646
+ 'The composer is a real <form> whose Enter handling skips IME composition, and every icon-only control in the panel carries an accessible label \u2014 the answer actions also carry a tooltip, and the stop control is named for what it stops (`stopLabel="Stop answering"`).'
486
647
  ]
487
648
  };
488
- var STATUS_TONE = { ready: "standard", building: "warning", queued: "neutral", failed: "destructive" };
489
- var statusTone = (status) => STATUS_TONE[status];
649
+ var STATUS_TONE = { live: "success", scheduled: "info", "sold-out": "edition", closed: "neutral" };
650
+ var STATUS_LABEL = { live: "Live", scheduled: "Scheduled", "sold-out": "Sold out", closed: "Closed" };
490
651
  var MIN = 60 * 1e3;
491
652
  var HOUR = 60 * MIN;
653
+ var DAY = 24 * HOUR;
654
+ var usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 });
655
+ var usdCents = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
656
+ var shortDate = (offset) => new Date(Date.now() + offset).toLocaleDateString("en-US", { month: "short", day: "numeric" });
492
657
  var stats = [
493
- { label: "Revenue", value: "$12,480", delta: "+8.2%", up: true, note: "vs. previous 30 days" },
494
- { label: "Collects", value: "1,284", delta: "+3.1%", up: true, note: "182 from new collectors" },
495
- { label: "New collectors", value: "312", delta: "+12.4%", up: true, note: "best month this year" },
496
- { label: "Sell-through", value: "64%", delta: "\u22121.8%", up: false, note: "across 48 active editions" }
658
+ { label: "Revenue", value: "$18,240", delta: "+8.2%", up: true, note: "vs. $16,857" },
659
+ { label: "Collects", value: "1,284", delta: "+3.1%", up: true, note: "182 first-time" },
660
+ { label: "Collectors", value: "312", delta: "+12.4%", up: true, note: "Best month yet" },
661
+ { label: "Sell-through", value: "64%", delta: "\u22121.8%", up: false, note: "6 open editions" }
662
+ ];
663
+ var editions = [
664
+ { id: "ed_01", title: "Gold Rush", status: "live", sold: 184, supply: 250, price: 48, released: -6 * DAY },
665
+ { id: "ed_02", title: "Quiet Harbor", status: "sold-out", sold: 100, supply: 100, price: 120, released: -19 * DAY },
666
+ { id: "ed_03", title: "Dusk, no. 4", status: "live", sold: 61, supply: 150, price: 64, released: -2 * DAY },
667
+ { id: "ed_04", title: "Tidewater", status: "live", sold: 212, supply: 300, price: 36, released: -27 * DAY },
668
+ { id: "ed_05", title: "Salt Study", status: "closed", sold: 38, supply: 75, price: 90, released: -44 * DAY },
669
+ { id: "ed_06", title: "Paper Moon", status: "sold-out", sold: 50, supply: 50, price: 220, released: -58 * DAY },
670
+ { id: "ed_07", title: "Night Signal", status: "scheduled", sold: 0, supply: 200, price: 56, released: 3 * DAY },
671
+ { id: "ed_08", title: "Low Tide, Blue", status: "closed", sold: 72, supply: 120, price: 42, released: -71 * DAY },
672
+ { id: "ed_09", title: "Ember Field", status: "sold-out", sold: 80, supply: 80, price: 140, released: -86 * DAY }
497
673
  ];
498
- var deployments = [
499
- { id: "dp_01", project: "storefront", commit: "a41f2c9", status: "ready", duration: "42s", updated: 2 * MIN },
500
- { id: "dp_02", project: "api", commit: "7c03bd1", status: "building", duration: "1m 08s", updated: 9 * MIN },
501
- { id: "dp_03", project: "docs", commit: "e992a70", status: "queued", duration: "\u2014", updated: 12 * MIN },
502
- { id: "dp_04", project: "worker", commit: "51b8e04", status: "failed", duration: "18s", updated: 1 * HOUR },
503
- { id: "dp_05", project: "storefront", commit: "90cc41a", status: "ready", duration: "39s", updated: 3 * HOUR },
504
- { id: "dp_06", project: "renderer", commit: "b7714dd", status: "ready", duration: "2m 31s", updated: 5 * HOUR },
505
- { id: "dp_07", project: "api", commit: "f30a9e2", status: "ready", duration: "1m 02s", updated: 8 * HOUR }
674
+ var editionCount = 24;
675
+ var payout = {
676
+ amount: 8214.6,
677
+ date: 5 * DAY,
678
+ lines: [
679
+ { label: "Sales", value: 9127.33 },
680
+ { label: "Platform fee (10%)", value: -912.73 },
681
+ { label: "Deposits to", value: "Checking \u2022\u20224821" }
682
+ ]
683
+ };
684
+ var collectors = [
685
+ { id: "co_01", name: "Nina Simone", handle: "@nina", initials: "NS", pieces: 14, spent: 1460 },
686
+ { id: "co_02", name: "Grace Hopper", handle: "@grace", initials: "GH", pieces: 11, spent: 1128 },
687
+ { id: "co_03", name: "Frida Kahlo", handle: "@frida", initials: "FK", pieces: 9, spent: 942 },
688
+ { id: "co_04", name: "Mary Shelley", handle: "@mary", initials: "MS", pieces: 6, spent: 588 }
689
+ ];
690
+ var upcoming = [
691
+ { id: "up_01", title: "Night Signal", detail: "Edition of 200 \xB7 $56", at: 3 * DAY },
692
+ { id: "up_02", title: "Tidewater, second printing", detail: "Edition of 100 \xB7 $40", at: 12 * DAY },
693
+ { id: "up_03", title: "Dusk, no. 5", detail: "Edition of 150 \xB7 $64", at: 26 * DAY },
694
+ { id: "up_04", title: "Quiet Harbor, artist proofs", detail: "Edition of 10 \xB7 $480", at: 34 * DAY }
506
695
  ];
507
696
  var activity = [
508
- { id: "ac_01", who: "Nina Simone", initials: "NS", what: "collected Gold Rush #42", when: 2 * MIN },
509
- { id: "ac_02", who: "Grace Hopper", initials: "GH", what: "minted a 100-run edition", when: 26 * MIN },
510
- { id: "ac_03", who: "Ada Lovelace", initials: "AL", what: "followed you", when: 1 * HOUR },
511
- { id: "ac_04", who: "Mary Shelley", initials: "MS", what: "collected Quiet Harbor", when: 3 * HOUR },
512
- { id: "ac_05", who: "Frida Kahlo", initials: "FK", what: "tipped 0.2 SOL on Process", when: 6 * HOUR }
697
+ { id: "ac_01", who: "Nina Simone", initials: "NS", what: "collected Gold Rush \xB7 185 of 250", when: 2 * MIN },
698
+ { id: "ac_02", who: "Grace Hopper", initials: "GH", what: "collected Dusk, no. 4 \xB7 61 of 150", when: 26 * MIN },
699
+ { id: "ac_03", who: "Frida Kahlo", initials: "FK", what: "tipped $20 on Tidewater", when: 1 * HOUR },
700
+ { id: "ac_04", who: "Mary Shelley", initials: "MS", what: "started following you", when: 3 * HOUR },
701
+ { id: "ac_05", who: "Nina Simone", initials: "NS", what: "set a reminder for Night Signal", when: 6 * HOUR }
513
702
  ];
514
703
  function DashboardPage() {
515
704
  return /* @__PURE__ */ jsx(
516
705
  AppShell,
517
706
  {
518
- header: /* @__PURE__ */ jsx(AppHeader, { title: /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" }) }),
707
+ header: /* @__PURE__ */ jsx(
708
+ AppHeader,
709
+ {
710
+ title: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", children: [
711
+ /* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
712
+ /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
713
+ ] }),
714
+ actions: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AL", alt: "Ada Lovelace" })
715
+ }
716
+ ),
519
717
  bottomNav: /* @__PURE__ */ jsxs(BottomNav, { "aria-label": "Primary", children: [
520
718
  /* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", showLabel: true, active: true, render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
521
719
  /* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
@@ -524,75 +722,216 @@ function DashboardPage() {
524
722
  /* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
525
723
  ] }),
526
724
  sidebar: /* @__PURE__ */ jsxs(Sidebar, { children: [
527
- /* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" }) }),
725
+ /* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", className: "px-3", children: [
726
+ /* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
727
+ /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
728
+ ] }) }),
528
729
  /* @__PURE__ */ jsxs(SidebarNav, { children: [
529
730
  /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", active: true, render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
530
731
  /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
531
732
  /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "user" }), label: "Collectors", render: /* @__PURE__ */ jsx("a", { href: "/collectors" }) }),
532
- /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "bell" }), label: "Activity", render: /* @__PURE__ */ jsx("a", { href: "/activity" }) }),
733
+ /* @__PURE__ */ jsx(
734
+ SidebarItem,
735
+ {
736
+ icon: /* @__PURE__ */ jsx(Icon, { name: "bell" }),
737
+ label: "Activity",
738
+ badge: /* @__PURE__ */ jsx(NotificationBadge, { count: 4, tone: "neutral" }),
739
+ render: /* @__PURE__ */ jsx("a", { href: "/activity" })
740
+ }
741
+ ),
533
742
  /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
534
- ] })
743
+ ] }),
744
+ /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(
745
+ Entity,
746
+ {
747
+ className: "[--entity-py:0]",
748
+ leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AL", alt: "" }),
749
+ title: "Ada Lovelace",
750
+ subtitle: "@ada",
751
+ action: /* @__PURE__ */ jsxs(OverflowMenu, { label: "Account", size: "icon-sm", children: [
752
+ /* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/@ada" }), children: "View profile" }),
753
+ /* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/settings" }), children: "Settings" }),
754
+ /* @__PURE__ */ jsx(MenuSeparator, {}),
755
+ /* @__PURE__ */ jsx(MenuItem, { children: "Sign out" })
756
+ ] })
757
+ }
758
+ ) })
535
759
  ] }),
536
760
  children: /* @__PURE__ */ jsxs(Page, { max: "wide", gap: 4, className: "py-6", children: [
537
761
  /* @__PURE__ */ jsx(
538
762
  PageHeader,
539
763
  {
540
764
  title: "Overview",
541
- description: "Sales, collects, and edition activity for the last 30 days.",
765
+ description: "Sales and collector activity for the last 30 days.",
542
766
  actions: /* @__PURE__ */ jsxs(Row, { gap: 2, children: [
543
- /* @__PURE__ */ jsx(Button, { variant: "outline", children: "Export" }),
544
- /* @__PURE__ */ jsx(Button, { children: "New edition" })
767
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", children: [
768
+ /* @__PURE__ */ jsx(Icon, { name: "download" }),
769
+ "Export"
770
+ ] }),
771
+ /* @__PURE__ */ jsxs(Button, { children: [
772
+ /* @__PURE__ */ jsx(Icon, { name: "plus" }),
773
+ "New edition"
774
+ ] })
545
775
  ] })
546
776
  }
547
777
  ),
548
- /* @__PURE__ */ jsx(Grid, { cols: { base: 1, sm: 2, xl: 4 }, gap: 3, children: stats.map((stat) => /* @__PURE__ */ jsxs(Card, { children: [
778
+ /* @__PURE__ */ jsx(Grid, { cols: { base: 2, xl: 4 }, gap: 3, children: stats.map((stat) => /* @__PURE__ */ jsxs(Card, { children: [
549
779
  /* @__PURE__ */ jsxs(CardHeader, { children: [
550
780
  /* @__PURE__ */ jsx(CardDescription, { children: stat.label }),
551
- /* @__PURE__ */ jsx(CardTitle, { className: "text-mono-xl", children: stat.value }),
552
- /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsxs(Badge, { tone: stat.up ? "success" : "neutral", size: "sm", children: [
553
- /* @__PURE__ */ jsx(Icon, { name: stat.up ? "chevron-up" : "chevron-down" }),
554
- stat.delta
555
- ] }) })
781
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-mono-xl", children: stat.value })
556
782
  ] }),
557
- /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", { className: "text-caption text-muted-foreground", children: stat.note }) })
783
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Row, { gap: 1, wrap: true, children: [
784
+ /* @__PURE__ */ jsx(
785
+ Badge,
786
+ {
787
+ tone: stat.up ? "success" : "warning",
788
+ size: "sm",
789
+ icon: /* @__PURE__ */ jsx(Icon, { name: stat.up ? "arrow-up" : "arrow-down" }),
790
+ children: stat.delta
791
+ }
792
+ ),
793
+ /* @__PURE__ */ jsx("span", { className: "text-caption text-muted-foreground", children: stat.note })
794
+ ] }) })
558
795
  ] }, stat.label)) }),
559
796
  /* @__PURE__ */ jsxs(Grid, { cols: { base: 1, xl: 3 }, gap: 3, children: [
560
- /* @__PURE__ */ jsxs(Card, { className: "xl:col-span-2", children: [
797
+ /* @__PURE__ */ jsxs(Card, { className: "flex flex-col xl:col-span-2", children: [
561
798
  /* @__PURE__ */ jsxs(CardHeader, { children: [
562
- /* @__PURE__ */ jsx(CardTitle, { children: "Recent deployments" }),
563
- /* @__PURE__ */ jsx(CardDescription, { children: "Builds across every project, newest first." }),
564
- /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/deployments" }), children: "View all" }) })
799
+ /* @__PURE__ */ jsx(CardTitle, { children: "Editions" }),
800
+ /* @__PURE__ */ jsx(CardDescription, { children: "Sales across your releases, newest first." })
565
801
  ] }),
566
- /* @__PURE__ */ jsx(CardContent, { flush: true, children: /* @__PURE__ */ jsx(
802
+ /* @__PURE__ */ jsx(CardContent, { flush: true, className: "mt-4 flex-1", children: /* @__PURE__ */ jsx(
567
803
  DataTable,
568
804
  {
569
- data: deployments,
805
+ data: editions,
570
806
  rowKey: (row) => row.id,
807
+ defaultSort: { key: "released", direction: "desc" },
571
808
  columns: [
572
- { key: "project", header: "Project", sortable: true, className: "text-label-md" },
573
- { key: "commit", header: "Commit", cell: (row) => /* @__PURE__ */ jsx("span", { className: "text-mono-caption text-muted-foreground", children: row.commit }) },
574
- { key: "status", header: "Status", cell: (row) => /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: statusTone(row.status), dot: true, children: row.status }) },
575
- { key: "duration", header: "Duration", align: "end", cell: (row) => /* @__PURE__ */ jsx("span", { className: "text-mono-caption", children: row.duration }) },
576
- { key: "updated", header: "Updated", align: "end", cell: (row) => /* @__PURE__ */ jsx(RelativeTime, { date: Date.now() - row.updated, className: "text-body-xs text-muted-foreground" }) }
809
+ { key: "title", header: "Edition", sortable: true, className: "text-label-md whitespace-nowrap" },
810
+ {
811
+ key: "status",
812
+ header: "Status",
813
+ cell: (row) => /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: STATUS_TONE[row.status], dot: true, children: STATUS_LABEL[row.status] })
814
+ },
815
+ {
816
+ key: "sold",
817
+ header: "Sold",
818
+ sortable: true,
819
+ sortValue: (row) => row.sold / row.supply,
820
+ cell: (row) => /* @__PURE__ */ jsxs(Row, { gap: 3, align: "center", className: "min-w-40", children: [
821
+ /* @__PURE__ */ jsx(
822
+ Meter,
823
+ {
824
+ value: row.sold,
825
+ max: row.supply,
826
+ size: "sm",
827
+ tone: row.sold === row.supply ? "success" : "primary",
828
+ "aria-label": `${row.title} sold`,
829
+ getAriaValueText: (_, v) => `${v} of ${row.supply} sold`,
830
+ className: "flex-1"
831
+ }
832
+ ),
833
+ /* @__PURE__ */ jsxs("span", { className: "text-mono-caption whitespace-nowrap text-muted-foreground", children: [
834
+ row.sold,
835
+ "/",
836
+ row.supply
837
+ ] })
838
+ ] })
839
+ },
840
+ {
841
+ key: "revenue",
842
+ header: "Revenue",
843
+ align: "end",
844
+ sortable: true,
845
+ sortValue: (row) => row.sold * row.price,
846
+ cell: (row) => row.sold > 0 ? /* @__PURE__ */ jsx("span", { className: "text-mono-sm", children: usd.format(row.sold * row.price) }) : /* @__PURE__ */ jsx("span", { className: "text-mono-sm text-muted-foreground", children: "\u2014" })
847
+ },
848
+ {
849
+ key: "released",
850
+ header: "Release",
851
+ align: "end",
852
+ sortable: true,
853
+ cell: (row) => /* @__PURE__ */ jsx("span", { className: "text-body-sm whitespace-nowrap text-muted-foreground", children: shortDate(row.released) })
854
+ }
577
855
  ]
578
856
  }
579
- ) })
857
+ ) }),
858
+ /* @__PURE__ */ jsxs(CardFooter, { className: "justify-between", children: [
859
+ /* @__PURE__ */ jsxs("span", { className: "text-caption text-muted-foreground", children: [
860
+ "Showing ",
861
+ editions.length,
862
+ " of ",
863
+ editionCount,
864
+ " editions"
865
+ ] }),
866
+ /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/editions" }), children: [
867
+ "View all editions",
868
+ /* @__PURE__ */ jsx(Icon, { name: "arrow-right" })
869
+ ] })
870
+ ] })
580
871
  ] }),
581
- /* @__PURE__ */ jsxs(Card, { children: [
872
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
873
+ /* @__PURE__ */ jsxs(Card, { children: [
874
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
875
+ /* @__PURE__ */ jsx(CardDescription, { children: "Next payout" }),
876
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-mono-xl", children: usdCents.format(payout.amount) }),
877
+ /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, { tone: "info", size: "sm", icon: /* @__PURE__ */ jsx(Icon, { name: "calendar" }), children: shortDate(payout.date) }) })
878
+ ] }),
879
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("dl", { className: "sable-list-dl text-body-sm", children: payout.lines.map((line) => /* @__PURE__ */ jsxs("div", { className: "contents", children: [
880
+ /* @__PURE__ */ jsx("dt", { children: line.label }),
881
+ /* @__PURE__ */ jsx("dd", { className: typeof line.value === "number" ? "text-end text-mono-sm" : "text-end", children: typeof line.value === "number" ? usdCents.format(line.value) : line.value })
882
+ ] }, line.label)) }) })
883
+ ] }),
884
+ /* @__PURE__ */ jsxs(Card, { children: [
885
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
886
+ /* @__PURE__ */ jsx(CardTitle, { children: "Top collectors" }),
887
+ /* @__PURE__ */ jsx(CardDescription, { children: "By spend this month." }),
888
+ /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/collectors" }), children: "View all" }) })
889
+ ] }),
890
+ /* @__PURE__ */ jsx(CardList, { children: collectors.map((person) => /* @__PURE__ */ jsx(
891
+ Entity,
892
+ {
893
+ leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: person.initials, alt: "" }),
894
+ title: person.name,
895
+ subtitle: `${person.handle} \xB7 ${person.pieces} pieces`,
896
+ action: /* @__PURE__ */ jsx("span", { className: "text-mono-sm", children: usd.format(person.spent) })
897
+ },
898
+ person.id
899
+ )) })
900
+ ] })
901
+ ] })
902
+ ] }),
903
+ /* @__PURE__ */ jsxs(Grid, { cols: { base: 1, xl: 3 }, gap: 3, children: [
904
+ /* @__PURE__ */ jsxs(Card, { className: "flex flex-col", children: [
905
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
906
+ /* @__PURE__ */ jsx(CardTitle, { children: "Upcoming releases" }),
907
+ /* @__PURE__ */ jsx(CardDescription, { children: "Scheduled to open to collectors." })
908
+ ] }),
909
+ /* @__PURE__ */ jsx(CardList, { children: upcoming.map((release) => /* @__PURE__ */ jsx(
910
+ Entity,
911
+ {
912
+ title: release.title,
913
+ subtitle: release.detail,
914
+ action: /* @__PURE__ */ jsx(RelativeTime, { date: Date.now() + release.at, className: "text-caption text-muted-foreground" })
915
+ },
916
+ release.id
917
+ )) }),
918
+ /* @__PURE__ */ jsx(CardFooter, { className: "mt-auto", children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/editions/new" }), children: [
919
+ /* @__PURE__ */ jsx(Icon, { name: "calendar" }),
920
+ "Schedule a release"
921
+ ] }) })
922
+ ] }),
923
+ /* @__PURE__ */ jsxs(Card, { className: "xl:col-span-2", children: [
582
924
  /* @__PURE__ */ jsxs(CardHeader, { children: [
583
925
  /* @__PURE__ */ jsx(CardTitle, { children: "Activity" }),
584
- /* @__PURE__ */ jsx(CardDescription, { children: "Collects, mints, and follows." })
926
+ /* @__PURE__ */ jsx(CardDescription, { children: "Collects, tips, and follows." }),
927
+ /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/activity" }), children: "View all" }) })
585
928
  ] }),
586
929
  /* @__PURE__ */ jsx(CardList, { children: activity.map((item) => /* @__PURE__ */ jsx(
587
930
  Entity,
588
931
  {
589
- density: "compact",
590
- leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: item.initials, alt: item.who }),
591
- title: /* @__PURE__ */ jsxs("span", { className: "text-body-sm", children: [
592
- /* @__PURE__ */ jsx("span", { className: "text-label-md", children: item.who }),
593
- " ",
594
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: item.what })
595
- ] }),
932
+ leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: item.initials, alt: "" }),
933
+ title: item.who,
934
+ subtitle: item.what,
596
935
  action: /* @__PURE__ */ jsx(RelativeTime, { date: Date.now() - item.when, className: "text-caption text-muted-foreground" })
597
936
  },
598
937
  item.id
@@ -607,150 +946,561 @@ function DashboardPage() {
607
946
  // src/patterns/dashboard-page.meta.ts
608
947
  var dashboardPageMeta = {
609
948
  name: "DashboardPage",
610
- summary: "App-shell dashboard: sidebar chrome (AppHeader + BottomNav below lg), a wide content region, a stat-card row that breathes, and a data table \u2014 the default shape for operator/analytics home routes.",
611
- intent: "Reach for this when a route's job is monitoring or managing many records (metrics, deployments, orders, posts). Not for reading-focused routes (use FeedPage) or single-record routes (use ListDetailPage's detail side).",
949
+ 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 \u2014 the default shape for an operator or analytics home route.",
950
+ 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).",
612
951
  notes: [
613
952
  "Region cap is `wide` (1280px): dashboards hold tables and multi-column grids, never a reading column.",
614
- "Stat cards: CardHeader carries label (CardDescription) + value + trend badge (CardAction pins it top-right); KPI values are `text-mono-xl` \u2014 the metric mono tier, tabular figures \u2014 never a heading tier.",
615
- "Stat cards use an explicit cols map (1 \u2192 2 \u2192 4) \u2014 a 4-stat row must fold to 2\xD72, never strand an orphan the way auto-fit can.",
616
- "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 \u2014 never hand-set pl-6/pr-6. Numeric columns align end in mono.",
617
- "The Activity list is Entity rows in CardList, inset to the card padding and divided by hairlines \u2014 the same list shape as ListDetailPage, never CardContent with hand-tuned px.",
618
- "Believable density is part of the pattern: 7 rows, commit hashes in mono-caption, RelativeTime muted \u2014 never 3 rows of filler.",
953
+ "Hierarchy runs headline \u2192 records \u2192 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.",
954
+ "Stat cards: CardHeader carries the label (CardDescription) and the value; KPI values are `text-mono-xl` \u2014 the metric mono tier, tabular figures \u2014 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\xD72 on a phone.",
955
+ "Stat cards use an explicit cols map (2 \u2192 4) \u2014 a 4-stat row must fold to 2\xD72, never strand an orphan the way auto-fit can.",
956
+ "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 \u2014 never hand-set pl-6/pr-6. Numeric columns align end in mono; Table scrolls horizontally inside the card on narrow screens.",
957
+ '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.',
958
+ "Lists are Entity rows in CardList, inset to the card padding and divided by hairlines \u2014 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`.",
959
+ "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.",
619
960
  "The page has exactly one heading-1 (PageHeader); card titles are Title tier, not headings.",
620
961
  "Chrome (Sidebar) is outside the content grid; the Page region caps only content.",
621
- "Sidebar hides below lg, so the shell's header carries an AppHeader and its footer a BottomNav with the same destinations \u2014 never strand mobile users without navigation."
962
+ "Sidebar hides below lg, so the shell's header carries an AppHeader (brand + account) and its footer a BottomNav with the same destinations \u2014 never strand mobile users without navigation."
622
963
  ],
623
964
  a11y: [
624
965
  'AppShell provides the skip link and <main id="main-content"> landmark.',
625
966
  "The active SidebarItem and BottomNavItem carry aria-current via `active`.",
626
- "DataTable headers are real <th>; sortable columns are announced."
967
+ "DataTable headers are real <th>; sortable columns are announced.",
968
+ 'Each sell-through Meter has an aria-label naming the edition and aria-valuetext reading "184 of 250 sold".',
969
+ "Avatars beside a visible name use an empty alt so the name is not read twice."
627
970
  ]
628
971
  };
629
972
  var posts = [
630
973
  {
631
974
  id: "p_01",
632
975
  author: "Ada Lovelace",
633
- handle: "@ada \xB7 2h",
976
+ handle: "@ada",
634
977
  initials: "AL",
635
- body: "New edition drops Friday \u2014 100 signed prints, each with the on-chain original.",
636
- image: "/media/edition-cover.svg",
637
- alt: "Edition cover \u2014 Gold Rush series",
978
+ time: "2h",
979
+ following: true,
980
+ body: "Gold Rush is live. 100 signed prints, each paired with the on-chain original. Thank you for waiting.",
981
+ media: { src: "/media/edition-cover.svg", alt: "Gold Rush, a gold-leaf study in overlapping arcs" },
982
+ drop: { title: "Gold Rush", price: "$120", collected: 62, supply: 100 },
638
983
  likes: 128,
639
984
  comments: 24
640
985
  },
641
986
  {
642
987
  id: "p_02",
643
988
  author: "Grace Hopper",
644
- handle: "@grace \xB7 5h",
989
+ handle: "@grace",
645
990
  initials: "GH",
646
- body: "Process shot from this week's series. Compilers and copper ink.",
647
- image: "/media/process-shot.svg",
648
- alt: "Work-in-progress plotter drawing in copper ink",
991
+ time: "5h",
992
+ following: true,
993
+ body: "Process shot from this week. Compilers and copper ink \u2014 the plotter ran for eleven hours on this one.",
994
+ media: { src: "/media/process-shot.svg", alt: "Work-in-progress plotter drawing in copper ink" },
649
995
  likes: 86,
650
996
  comments: 9
997
+ },
998
+ {
999
+ id: "p_03",
1000
+ author: "Nina Simone",
1001
+ handle: "@nina",
1002
+ initials: "NS",
1003
+ time: "8h",
1004
+ following: false,
1005
+ body: "Collectors of Dusk, no. 3 \u2014 your unlockable score is now in your library. Headphones on for this one.",
1006
+ likes: 214,
1007
+ comments: 31
651
1008
  }
652
1009
  ];
653
- function FeedPage() {
654
- return /* @__PURE__ */ jsxs(Page, { as: "main", max: "feed", gap: 4, className: "py-6", children: [
655
- /* @__PURE__ */ jsx(PageHeader, { title: "Home", description: "Latest posts from artists you follow." }),
656
- /* @__PURE__ */ jsx(Stack, { gap: 3, children: posts.map((post) => /* @__PURE__ */ jsx("article", { children: /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
657
- /* @__PURE__ */ jsx(CardList, { children: /* @__PURE__ */ jsx(
658
- Entity,
1010
+ var suggestions = [
1011
+ { id: "u_01", name: "Mary Shelley", handle: "@mary", initials: "MS" },
1012
+ { id: "u_02", name: "Nina Simone", handle: "@nina", initials: "NS" }
1013
+ ];
1014
+ var upcoming2 = [
1015
+ { id: "d_01", title: "Quiet Harbor", artist: "Mary Shelley", initials: "MS", date: "Sat, Oct 12" },
1016
+ { id: "d_02", title: "Dusk, no. 4", artist: "Nina Simone", initials: "NS", date: "Tue, Oct 15" }
1017
+ ];
1018
+ var renderPost = (post) => /* @__PURE__ */ jsx("article", { children: /* @__PURE__ */ jsxs(Card, { children: [
1019
+ /* @__PURE__ */ jsx(CardList, { children: /* @__PURE__ */ jsx(
1020
+ Entity,
1021
+ {
1022
+ leading: /* @__PURE__ */ jsx(Avatar, { fallback: post.initials, alt: post.author }),
1023
+ title: post.author,
1024
+ subtitle: `${post.handle} \xB7 ${post.time}`,
1025
+ action: /* @__PURE__ */ jsxs(OverflowMenu, { label: `Post options for ${post.author}`, size: "icon-sm", children: [
1026
+ /* @__PURE__ */ jsx(MenuItem, { children: "Copy link" }),
1027
+ /* @__PURE__ */ jsxs(MenuItem, { children: [
1028
+ "Mute ",
1029
+ post.handle
1030
+ ] }),
1031
+ /* @__PURE__ */ jsx(MenuSeparator, {}),
1032
+ /* @__PURE__ */ jsx(MenuItem, { variant: "destructive", children: "Report post" })
1033
+ ] })
1034
+ }
1035
+ ) }),
1036
+ /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4 pt-0", children: [
1037
+ /* @__PURE__ */ jsx("p", { className: "text-body-md text-foreground", children: post.body }),
1038
+ post.media ? /* @__PURE__ */ jsxs(AspectBox, { ratio: 16 / 9, className: "overflow-hidden rounded-concentric-lg/6 bg-muted", children: [
1039
+ /* @__PURE__ */ jsx("img", { src: post.media.src, alt: post.media.alt, className: "size-full object-cover" }),
1040
+ post.drop ? /* @__PURE__ */ jsx("div", { className: "absolute start-3 top-3", children: /* @__PURE__ */ jsx(Badge, { appearance: "overlay", size: "sm", dot: true, children: "Live drop" }) }) : null
1041
+ ] }) : null,
1042
+ post.drop ? /* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
1043
+ /* @__PURE__ */ jsxs(Row, { gap: 2, align: "center", justify: "between", children: [
1044
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1045
+ /* @__PURE__ */ jsx("p", { className: "text-title-md text-foreground", children: post.drop.title }),
1046
+ /* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: `${post.drop.price} \xB7 Edition of ${post.drop.supply}` })
1047
+ ] }),
1048
+ /* @__PURE__ */ jsx(Button, { children: `Collect for ${post.drop.price}` })
1049
+ ] }),
1050
+ /* @__PURE__ */ jsxs(
1051
+ Meter,
659
1052
  {
660
- leading: /* @__PURE__ */ jsx(Avatar, { fallback: post.initials, alt: post.author }),
661
- title: post.author,
662
- subtitle: post.handle,
663
- action: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Post options", children: /* @__PURE__ */ jsx(Icon, { name: "ellipsis" }) })
1053
+ value: post.drop.collected,
1054
+ max: post.drop.supply,
1055
+ size: "sm",
1056
+ getAriaValueText: (_, value) => `${value} of ${post.drop?.supply} collected`,
1057
+ children: [
1058
+ /* @__PURE__ */ jsxs(Row, { gap: 2, align: "center", justify: "between", className: "mb-2", children: [
1059
+ /* @__PURE__ */ jsx(MeterLabel, { children: `${post.drop.collected} collected` }),
1060
+ /* @__PURE__ */ jsx(MeterValue, { children: (_, value) => `${(post.drop?.supply ?? 0) - value} left` })
1061
+ ] }),
1062
+ /* @__PURE__ */ jsx(MeterTrack, {})
1063
+ ]
664
1064
  }
665
- ) }),
666
- /* @__PURE__ */ jsx(AspectBox, { ratio: 16 / 9, children: /* @__PURE__ */ jsx("img", { src: post.image, alt: post.alt, className: "size-full object-cover" }) }),
667
- /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-2", children: [
668
- /* @__PURE__ */ jsx("p", { className: "text-body-md text-foreground", children: post.body }),
669
- /* @__PURE__ */ jsxs(Row, { gap: 1, className: "-ms-3", children: [
670
- /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", "aria-label": `Like \u2014 ${post.likes}`, children: [
671
- /* @__PURE__ */ jsx(Icon, { name: "heart" }),
672
- post.likes
673
- ] }),
674
- /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", "aria-label": `Comments \u2014 ${post.comments}`, children: [
675
- /* @__PURE__ */ jsx(Icon, { name: "comment" }),
676
- post.comments
677
- ] }),
678
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": "Share", children: /* @__PURE__ */ jsx(Icon, { name: "share" }) })
1065
+ )
1066
+ ] }) : null,
1067
+ /* @__PURE__ */ jsxs(Row, { gap: 0.5, className: "-ms-3", children: [
1068
+ /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", "aria-label": `Like, ${post.likes} likes`, children: [
1069
+ /* @__PURE__ */ jsx(Icon, { name: "heart" }),
1070
+ post.likes
1071
+ ] }),
1072
+ /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", "aria-label": `Comment, ${post.comments} comments`, children: [
1073
+ /* @__PURE__ */ jsx(Icon, { name: "comment" }),
1074
+ post.comments
1075
+ ] }),
1076
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": "Share", children: /* @__PURE__ */ jsx(Icon, { name: "share" }) })
1077
+ ] })
1078
+ ] })
1079
+ ] }) }, post.id);
1080
+ function FeedPage() {
1081
+ return /* @__PURE__ */ jsx(Page, { as: "main", max: "wide", gap: 4, className: "py-6", children: /* @__PURE__ */ jsxs(Row, { gap: 4, align: "start", justify: "center", children: [
1082
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, className: "min-w-0 max-w-(--region-feed) flex-1", children: [
1083
+ /* @__PURE__ */ jsx(PageHeader, { title: "Home" }),
1084
+ /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Row, { gap: 1.5, align: "start", children: [
1085
+ /* @__PURE__ */ jsx(Avatar, { fallback: "AL", alt: "Ada Lovelace" }),
1086
+ /* @__PURE__ */ jsxs(Stack, { gap: 1.5, className: "min-w-0 flex-1", children: [
1087
+ /* @__PURE__ */ jsx(
1088
+ Textarea,
1089
+ {
1090
+ autoResize: true,
1091
+ rows: 2,
1092
+ "aria-label": "New post",
1093
+ placeholder: "Share a process shot or an upcoming drop"
1094
+ }
1095
+ ),
1096
+ /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", justify: "between", children: [
1097
+ /* @__PURE__ */ jsxs(Row, { gap: 0.5, children: [
1098
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Add images", children: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }) }),
1099
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Attach an edition", children: /* @__PURE__ */ jsx(Icon, { name: "gem" }) })
1100
+ ] }),
1101
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", children: "Post" })
1102
+ ] })
679
1103
  ] })
1104
+ ] }) }) }),
1105
+ /* @__PURE__ */ jsxs(Tabs, { defaultValue: "for-you", children: [
1106
+ /* @__PURE__ */ jsxs(TabsList, { className: "flex", children: [
1107
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "for-you", children: "For you" }),
1108
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "following", children: "Following" })
1109
+ ] }),
1110
+ /* @__PURE__ */ jsx(TabsPanel, { value: "for-you", children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
1111
+ posts.map(renderPost),
1112
+ /* @__PURE__ */ jsx(Card, { "aria-busy": "true", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4", children: [
1113
+ /* @__PURE__ */ jsxs(Row, { gap: 1.5, align: "center", children: [
1114
+ /* @__PURE__ */ jsx(Skeleton, { className: "size-10 rounded-full" }),
1115
+ /* @__PURE__ */ jsxs(Stack, { gap: 1, children: [
1116
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-32" }),
1117
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-20" })
1118
+ ] })
1119
+ ] }),
1120
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-full" }),
1121
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-2/3" })
1122
+ ] }) }),
1123
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", loading: true, loadingLabel: "Loading posts", children: "Load more posts" })
1124
+ ] }) }),
1125
+ /* @__PURE__ */ jsx(TabsPanel, { value: "following", children: /* @__PURE__ */ jsx(Stack, { gap: 3, children: posts.filter((post) => post.following).map(renderPost) }) })
680
1126
  ] })
681
- ] }) }, post.id)) }),
682
- /* @__PURE__ */ jsx(Button, { variant: "secondary", loadingLabel: "Loading\u2026", children: "Load more posts" })
683
- ] });
1127
+ ] }),
1128
+ /* @__PURE__ */ jsx("aside", { "aria-label": "Suggestions", className: "sticky top-6 hidden w-80 shrink-0 lg:block", children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
1129
+ /* @__PURE__ */ jsxs(Card, { children: [
1130
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Who to follow" }) }),
1131
+ /* @__PURE__ */ jsx(CardList, { children: suggestions.map((person) => /* @__PURE__ */ jsx(
1132
+ Entity,
1133
+ {
1134
+ leading: /* @__PURE__ */ jsx(Avatar, { fallback: person.initials, alt: person.name }),
1135
+ title: person.name,
1136
+ subtitle: person.handle,
1137
+ action: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", "aria-label": `Follow ${person.name}`, children: "Follow" })
1138
+ },
1139
+ person.id
1140
+ )) })
1141
+ ] }),
1142
+ /* @__PURE__ */ jsxs(Card, { children: [
1143
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Upcoming drops" }) }),
1144
+ /* @__PURE__ */ jsx(CardList, { children: upcoming2.map((drop) => /* @__PURE__ */ jsx(
1145
+ Entity,
1146
+ {
1147
+ leading: /* @__PURE__ */ jsx(Avatar, { fallback: drop.initials, alt: drop.artist }),
1148
+ title: drop.title,
1149
+ subtitle: `${drop.artist} \xB7 ${drop.date}`,
1150
+ action: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": `Remind me about ${drop.title}`, children: /* @__PURE__ */ jsx(Icon, { name: "bell" }) })
1151
+ },
1152
+ drop.id
1153
+ )) })
1154
+ ] })
1155
+ ] }) })
1156
+ ] }) });
684
1157
  }
685
1158
 
686
1159
  // src/patterns/feed-page.meta.ts
687
1160
  var feedPageMeta = {
688
1161
  name: "FeedPage",
689
- summary: "Single-column reading feed: a feed-width region, one Card per post (Entity byline, zero-shift media, body copy, quiet action row), and explicit load-more pagination.",
690
- intent: "Reach for this for timelines, activity streams, and any reverse-chronological reading surface. Not for record management (DashboardPage) or side-by-side browsing (ListDetailPage).",
1162
+ 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.",
1163
+ 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).",
691
1164
  notes: [
692
- "Region cap is `feed` (640px) \u2014 a single reading column; media and copy share one measure.",
693
- "Post media sits in an AspectBox so late-loading images never shift the column.",
694
- "The byline is Entity (Avatar + name + handle) in a CardList \u2014 the same row primitive as everywhere else, inset to the card padding by CardList rather than by zeroing paddings on CardHeader and Entity.",
695
- "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: quiet chrome under content, not competing pills.",
696
- "Pagination is an explicit load-more Button (secondary; set `loading` while the next page fetches \u2014 `loadingLabel` names that state), not infinite scroll \u2014 predictable for keyboard and AT users."
1165
+ "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 \u2014 its content is supplementary, so nothing is lost on mobile.",
1166
+ "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.",
1167
+ "The composer is a Card holding `Textarea autoResize` plus ghost icon Buttons and a secondary `Post` \u2014 not ChatComposer, which submits on Enter and is for conversations.",
1168
+ "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.",
1169
+ '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 \u2014 collected against a known maximum is a level, not task progress.',
1170
+ "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.",
1171
+ "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 \u2014 predictable for keyboard and AT users, unlike infinite scroll."
697
1172
  ],
698
1173
  a11y: [
699
- "Each post is an <article>, so AT users can jump between posts.",
700
- "Icon-only action buttons carry aria-labels.",
701
- "Media images get meaningful alt text from the post, empty alt only if decorative."
1174
+ "Each post is an <article>, so AT users can jump between posts; the rail is a labelled <aside> landmark.",
1175
+ "Rail card titles render as h2 (CardTitle `render={<h2 />}`) under the page's one h1.",
1176
+ "Icon-only buttons and every Follow / Remind button carry aria-labels naming their target; counts stay in the accessible name.",
1177
+ 'The supply Meter is role=meter with a getAriaValueText reading ("62 of 100 collected").',
1178
+ 'The Skeleton card is aria-busy; the loading Button keeps its label ("Loading posts").'
702
1179
  ]
703
1180
  };
704
- var features2 = [
1181
+ var navLinks = [
1182
+ { label: "Docs", href: "/docs" },
1183
+ { label: "Components", href: "/components" },
1184
+ { label: "Patterns", href: "/patterns" },
1185
+ { label: "Themes", href: "/themes" }
1186
+ ];
1187
+ var deploys = [
1188
+ {
1189
+ initials: "MO",
1190
+ name: "Maya Ortiz",
1191
+ title: "Add usage-based billing",
1192
+ meta: "main \xB7 a3f9c21 \xB7 2m ago",
1193
+ status: "Building",
1194
+ tone: "info",
1195
+ pulse: true
1196
+ },
705
1197
  {
706
- icon: "sparkles",
707
- title: "Own the surface",
708
- body: "Semantic tokens end-to-end \u2014 re-theme the whole product without touching a component."
1198
+ initials: "SR",
1199
+ name: "Sam Reyes",
1200
+ title: "Fix search ranking for long queries",
1201
+ meta: "main \xB7 81be04d \xB7 18m ago",
1202
+ status: "Ready",
1203
+ tone: "success",
1204
+ pulse: false
709
1205
  },
1206
+ {
1207
+ initials: "JL",
1208
+ name: "Jun Lee",
1209
+ title: "Rotate webhook signing keys",
1210
+ meta: "main \xB7 5c7e9aa \xB7 1h ago",
1211
+ status: "Ready",
1212
+ tone: "success",
1213
+ pulse: false
1214
+ }
1215
+ ];
1216
+ var features = [
710
1217
  {
711
1218
  icon: "component",
712
- title: "Behavior included",
713
- body: "Focus, keyboard nav, and ARIA ride on Base UI primitives under every surface."
1219
+ title: "Behavior built in",
1220
+ body: "Focus management, keyboard navigation, and ARIA come from Base UI primitives under every control."
1221
+ },
1222
+ {
1223
+ icon: "eyedropper",
1224
+ title: "Semantic tokens end to end",
1225
+ body: "Re-theme the whole product, light and dark, from one preset without touching a component."
714
1226
  },
715
1227
  {
716
1228
  icon: "layout",
717
1229
  title: "Pages, not just parts",
718
- body: "Purpose-named regions, an 8px rhythm, and canonical page shells for the routes every product needs."
1230
+ body: "Landing, dashboard, settings, auth, and list-detail shells to copy and adapt."
1231
+ },
1232
+ {
1233
+ icon: "book",
1234
+ title: "A contract agents can read",
1235
+ body: "registry.json, llms.txt, and a CLI hand agents the exact props, examples, and rules."
1236
+ },
1237
+ {
1238
+ icon: "circle-check",
1239
+ title: "Lint that knows the design",
1240
+ body: "sable-lint flags raw colors, hand-built controls, and motion that ignores reduced motion."
1241
+ },
1242
+ {
1243
+ icon: "play",
1244
+ title: "Motion with an off switch",
1245
+ body: "Named recipes run on one scale variable, so reduced motion stops every transition at once."
1246
+ }
1247
+ ];
1248
+ var steps2 = [
1249
+ {
1250
+ title: "Install the package",
1251
+ body: "One dependency. React 19 and Tailwind v4 are the only peers."
1252
+ },
1253
+ {
1254
+ title: "Import the styles",
1255
+ body: "Tailwind first, then Sable's tokens, then a source glob over the package."
1256
+ },
1257
+ {
1258
+ title: "Compose your screens",
1259
+ body: "Build from components and layout primitives. Your theme does the rest."
1260
+ }
1261
+ ];
1262
+ var stylesCode = `@import "tailwindcss";
1263
+ @import "@sableui/react/styles.css";
1264
+ @source "../node_modules/@sableui/react/dist/**/*.{js,mjs}";`;
1265
+ var composeCode = `import { Button, Card, CardContent, CardHeader, CardTitle } from "@sableui/react"
1266
+
1267
+ export function DeployCard() {
1268
+ return (
1269
+ <Card>
1270
+ <CardHeader>
1271
+ <CardTitle>Production</CardTitle>
1272
+ </CardHeader>
1273
+ <CardContent>
1274
+ <Button>Deploy</Button>
1275
+ </CardContent>
1276
+ </Card>
1277
+ )
1278
+ }`;
1279
+ var terminalCode = `$ npx sable search "confirm before deleting"
1280
+ component DestructiveActionModal
1281
+ component Dialog
1282
+
1283
+ $ npx sable component DestructiveActionModal --props
1284
+ props, a11y contract, composed examples
1285
+
1286
+ $ npx sable lint src
1287
+ 0 findings in 42 files`;
1288
+ var agentPoints = [
1289
+ "Search ranks components, patterns, tokens, and docs for a plain-language need.",
1290
+ "Every component ships its props, accessibility contract, and composed examples.",
1291
+ "The same lint runs in the editor, in CI, and inside the agent's own loop."
1292
+ ];
1293
+ var footerGroups2 = [
1294
+ {
1295
+ title: "Product",
1296
+ links: [
1297
+ { label: "Components", href: "/components" },
1298
+ { label: "Patterns", href: "/patterns" },
1299
+ { label: "Themes", href: "/themes" },
1300
+ { label: "Canvas", href: "/canvas" }
1301
+ ]
1302
+ },
1303
+ {
1304
+ title: "Resources",
1305
+ links: [
1306
+ { label: "Documentation", href: "/docs" },
1307
+ { label: "Changelog", href: "/changelog" },
1308
+ { label: "llms.txt", href: "/llms.txt" },
1309
+ { label: "registry.json", href: "/registry.json" }
1310
+ ]
1311
+ },
1312
+ {
1313
+ title: "Community",
1314
+ links: [
1315
+ { label: "GitHub", href: "/github" },
1316
+ { label: "npm", href: "/npm" },
1317
+ { label: "Discussions", href: "/discussions" }
1318
+ ]
719
1319
  }
720
1320
  ];
721
1321
  function LandingPage() {
722
- return /* @__PURE__ */ jsxs("div", { className: "min-h-dvh bg-background", children: [
723
- /* @__PURE__ */ jsx("header", { children: /* @__PURE__ */ jsxs(Region, { as: "nav", "aria-label": "Main", className: "grid h-16 grid-cols-[1fr_auto_1fr] items-center", children: [
724
- /* @__PURE__ */ jsx("span", { className: "justify-self-start text-title-sm", children: "Sable" }),
725
- /* @__PURE__ */ jsxs(Row, { gap: 1, className: "justify-self-center max-sm:hidden", children: [
726
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/docs" }), children: "Docs" }),
727
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/patterns" }), children: "Patterns" }),
728
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/themes" }), children: "Themes" })
729
- ] }),
730
- /* @__PURE__ */ jsx(Button, { size: "sm", className: "justify-self-end", render: /* @__PURE__ */ jsx("a", { href: "/signup" }), children: "Get started" })
1322
+ return /* @__PURE__ */ jsxs("div", { className: "min-h-dvh bg-background text-foreground", children: [
1323
+ /* @__PURE__ */ jsx("header", { className: "border-b border-border", children: /* @__PURE__ */ jsxs(Region, { as: "nav", "aria-label": "Main", max: "wide", className: "grid h-16 grid-cols-[1fr_auto_1fr] items-center", children: [
1324
+ /* @__PURE__ */ jsx("a", { href: "/", className: "justify-self-start text-title-lg motion-interactive hover:text-muted-foreground", children: "Sable" }),
1325
+ /* @__PURE__ */ jsx("div", { className: "justify-self-center max-md:hidden", children: /* @__PURE__ */ jsx(Row, { gap: 1, children: navLinks.map((link) => /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: link.href }), children: link.label }, link.label)) }) }),
1326
+ /* @__PURE__ */ jsxs(Row, { gap: 1, className: "col-start-3 justify-self-end", children: [
1327
+ /* @__PURE__ */ jsx("div", { className: "max-sm:hidden", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/github" }), children: "GitHub" }) }),
1328
+ /* @__PURE__ */ jsx(Button, { size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/docs/getting-started" }), children: "Get started" })
1329
+ ] })
731
1330
  ] }) }),
732
1331
  /* @__PURE__ */ jsxs("main", { children: [
733
- /* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "py-24", children: /* @__PURE__ */ jsxs(Stack, { gap: 4, align: "center", className: "text-center", children: [
734
- /* @__PURE__ */ jsx(Badge, { appearance: "outline", size: "sm", children: "Now in beta" }),
735
- /* @__PURE__ */ jsx("h1", { className: "max-w-4xl text-balance text-display-2xl", children: "Ship product UI your agents can't get wrong" }),
736
- /* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-lg text-muted-foreground", children: "An owned, themeable component system with enforced tokens, typography, and motion \u2014 documented for machines as carefully as for people." }),
737
- /* @__PURE__ */ jsxs(Row, { gap: 2, justify: "center", children: [
738
- /* @__PURE__ */ jsx(Button, { size: "cta", children: "Get started" }),
739
- /* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", children: "Read the docs" })
740
- ] })
1332
+ /* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "pb-16 pt-16 sm:pt-24", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, align: "center", children: [
1333
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, align: "center", className: "text-center", children: [
1334
+ /* @__PURE__ */ jsx(Badge, { appearance: "outline", size: "sm", icon: /* @__PURE__ */ jsx(Icon, { name: "sparkles" }), children: "Open source \xB7 MIT licensed" }),
1335
+ /* @__PURE__ */ jsx("h1", { className: "max-w-4xl text-balance text-display-lg sm:text-display-2xl", children: "Product UI your agents can't get wrong" }),
1336
+ /* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-pretty text-body-lg text-muted-foreground", children: "Sable is a themeable React component library with enforced tokens, typography, and motion, documented for coding agents as carefully as for people." }),
1337
+ /* @__PURE__ */ jsxs(Row, { gap: 2, justify: "center", wrap: true, className: "pt-2", children: [
1338
+ /* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/docs/getting-started" }), children: "Get started" }),
1339
+ /* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "/components" }), children: "Browse components" })
1340
+ ] })
1341
+ ] }),
1342
+ /* @__PURE__ */ jsx("div", { className: "w-full max-w-sm", children: /* @__PURE__ */ jsx(CodeBlock, { prompt: "$", code: "pnpm add @sableui/react" }) })
741
1343
  ] }) }),
742
- /* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "pb-24", children: /* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx(Grid, { minItemWidth: "calc(var(--spacing) * 64)", children: features2.map((feature) => /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-2", children: [
743
- /* @__PURE__ */ jsx(Icon, { name: feature.icon, className: "text-xl text-muted-foreground" }),
744
- /* @__PURE__ */ jsx("h2", { className: "text-title-lg", children: feature.title }),
745
- /* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: feature.body })
746
- ] }) }, feature.title)) }) }) })
1344
+ /* @__PURE__ */ jsx(Region, { as: "section", "aria-label": "Product preview", max: "wide", className: "pb-20 sm:pb-28", children: /* @__PURE__ */ jsxs("figure", { children: [
1345
+ /* @__PURE__ */ jsx(Browser, { url: "atlas.example/deploys", className: "shadow-xl", children: /* @__PURE__ */ jsxs("div", { inert: true, className: "grid gap-4 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_20rem]", children: [
1346
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, className: "min-w-0", children: [
1347
+ /* @__PURE__ */ jsx(
1348
+ PageHeader,
1349
+ {
1350
+ titleAs: "h2",
1351
+ title: "Deployments",
1352
+ description: "atlas-web \xB7 production",
1353
+ actions: /* @__PURE__ */ jsxs(Row, { gap: 2, children: [
1354
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: "View logs" }),
1355
+ /* @__PURE__ */ jsx(Button, { size: "sm", iconStart: /* @__PURE__ */ jsx(Icon, { name: "upload" }), children: "Deploy" })
1356
+ ] })
1357
+ }
1358
+ ),
1359
+ /* @__PURE__ */ jsxs(Grid, { cols: { base: 2, md: 3 }, gap: 3, children: [
1360
+ /* @__PURE__ */ jsxs(Card, { density: "compact", children: [
1361
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
1362
+ /* @__PURE__ */ jsx(CardTitle, { children: "Uptime" }),
1363
+ /* @__PURE__ */ jsx(CardDescription, { children: "Last 30 days" })
1364
+ ] }),
1365
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", { className: "text-heading-3", children: "99.98%" }) })
1366
+ ] }),
1367
+ /* @__PURE__ */ jsxs(Card, { density: "compact", children: [
1368
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
1369
+ /* @__PURE__ */ jsx(CardTitle, { children: "p95 latency" }),
1370
+ /* @__PURE__ */ jsx(CardDescription, { children: "Edge, all regions" })
1371
+ ] }),
1372
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", { className: "text-heading-3", children: "182 ms" }) })
1373
+ ] }),
1374
+ /* @__PURE__ */ jsxs(Card, { density: "compact", className: "max-md:hidden", children: [
1375
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
1376
+ /* @__PURE__ */ jsx(CardTitle, { children: "Build minutes" }),
1377
+ /* @__PURE__ */ jsx(CardDescription, { children: "Team plan" })
1378
+ ] }),
1379
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Meter, { value: 64, tone: "standard", children: [
1380
+ /* @__PURE__ */ jsxs(Row, { justify: "between", wrap: true, className: "mb-2", children: [
1381
+ /* @__PURE__ */ jsx(MeterLabel, { children: "Used" }),
1382
+ /* @__PURE__ */ jsx(MeterValue, { children: (_, value) => `${(value * 30).toLocaleString("en-US")} of 3,000` })
1383
+ ] }),
1384
+ /* @__PURE__ */ jsx(MeterTrack, {})
1385
+ ] }) })
1386
+ ] })
1387
+ ] }),
1388
+ /* @__PURE__ */ jsxs(Card, { children: [
1389
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
1390
+ /* @__PURE__ */ jsx(CardTitle, { children: "Recent deploys" }),
1391
+ /* @__PURE__ */ jsx(CardDescription, { children: "Production branch" }),
1392
+ /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: "success", dot: true, children: "Healthy" }) })
1393
+ ] }),
1394
+ /* @__PURE__ */ jsx(CardList, { children: deploys.map((deploy) => /* @__PURE__ */ jsx(
1395
+ Entity,
1396
+ {
1397
+ leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: deploy.initials, alt: deploy.name }),
1398
+ title: deploy.title,
1399
+ subtitle: deploy.meta,
1400
+ action: /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: deploy.tone, dot: true, pulse: deploy.pulse, children: deploy.status })
1401
+ },
1402
+ deploy.meta
1403
+ )) })
1404
+ ] })
1405
+ ] }),
1406
+ /* @__PURE__ */ jsxs(Card, { className: "max-lg:hidden", children: [
1407
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
1408
+ /* @__PURE__ */ jsx(CardTitle, { children: "Release assistant" }),
1409
+ /* @__PURE__ */ jsx(CardDescription, { children: "Summaries from today's deploys" })
1410
+ ] }),
1411
+ /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4", children: [
1412
+ /* @__PURE__ */ jsx(Message, { align: "end", children: /* @__PURE__ */ jsx(Bubble, { variant: "primary", align: "end", children: "What shipped to production today?" }) }),
1413
+ /* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsx(Bubble, { children: "Three deploys. Search ranking is fixed, webhook keys are rotated, and usage-based billing is building now." }) }),
1414
+ /* @__PURE__ */ jsx(ChatComposer, { placeholder: "Ask about a deploy" })
1415
+ ] })
1416
+ ] })
1417
+ ] }) }),
1418
+ /* @__PURE__ */ jsx("figcaption", { className: "mt-4 text-center text-body-sm text-muted-foreground", children: "Every surface in this preview is a Sable component, styled by the active theme." })
1419
+ ] }) }),
1420
+ /* @__PURE__ */ jsx("section", { "aria-labelledby": "landing-features", className: "border-y border-border bg-muted/40 py-20 sm:py-28", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs(Stack, { gap: 10, children: [
1421
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-2xl", children: [
1422
+ /* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Why Sable" }),
1423
+ /* @__PURE__ */ jsx("h2", { id: "landing-features", className: "text-balance text-heading-1", children: "A design system with the rules written down" }),
1424
+ /* @__PURE__ */ jsx("p", { className: "text-body-lg text-muted-foreground", children: "The decisions a senior designer would make are encoded once, so every screen your team or your agent builds starts from them." })
1425
+ ] }),
1426
+ /* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx(Grid, { cols: { base: 1, sm: 2, lg: 3 }, gap: 4, children: features.map((feature) => /* @__PURE__ */ jsx(Card, { render: /* @__PURE__ */ jsx("article", {}), className: "h-full", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-3", children: [
1427
+ /* @__PURE__ */ jsx("span", { className: "grid size-10 place-items-center rounded-md sable-well", children: /* @__PURE__ */ jsx(Icon, { name: feature.icon, className: "text-xl" }) }),
1428
+ /* @__PURE__ */ jsx("h3", { className: "pt-2 text-title-lg", children: feature.title }),
1429
+ /* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: feature.body })
1430
+ ] }) }, feature.title)) }) })
1431
+ ] }) }) }),
1432
+ /* @__PURE__ */ jsx(Region, { as: "section", "aria-labelledby": "landing-setup", max: "wide", className: "py-20 sm:py-28", children: /* @__PURE__ */ jsxs("div", { className: "grid items-start gap-10 lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:gap-16", children: [
1433
+ /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
1434
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
1435
+ /* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Setup" }),
1436
+ /* @__PURE__ */ jsx("h2", { id: "landing-setup", className: "text-balance text-heading-1", children: "From install to first screen in three steps" })
1437
+ ] }),
1438
+ /* @__PURE__ */ jsx("ol", { className: "flex flex-col gap-6", children: steps2.map((step, index) => /* @__PURE__ */ jsxs("li", { className: "grid grid-cols-[auto_1fr] gap-4", children: [
1439
+ /* @__PURE__ */ jsxs("span", { className: "pt-0.5 text-mono-md text-muted-foreground", children: [
1440
+ "0",
1441
+ index + 1
1442
+ ] }),
1443
+ /* @__PURE__ */ jsxs("div", { children: [
1444
+ /* @__PURE__ */ jsx("h3", { className: "text-title-lg", children: step.title }),
1445
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-body-md text-muted-foreground", children: step.body })
1446
+ ] })
1447
+ ] }, step.title)) })
1448
+ ] }),
1449
+ /* @__PURE__ */ jsx(Reveal, { className: "min-w-0", children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "compose", children: [
1450
+ /* @__PURE__ */ jsxs(TabsList, { children: [
1451
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "install", children: "Install" }),
1452
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "styles", children: "Styles" }),
1453
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "compose", children: "Compose" })
1454
+ ] }),
1455
+ /* @__PURE__ */ jsx(TabsPanel, { value: "install", className: "pt-4", children: /* @__PURE__ */ jsx(CodeBlock, { prompt: "$", code: "pnpm add @sableui/react" }) }),
1456
+ /* @__PURE__ */ jsx(TabsPanel, { value: "styles", className: "pt-4", children: /* @__PURE__ */ jsx(CodeBlock, { filename: "app.css", code: stylesCode }) }),
1457
+ /* @__PURE__ */ jsx(TabsPanel, { value: "compose", className: "pt-4", children: /* @__PURE__ */ jsx(CodeBlock, { filename: "deploy-card.tsx", code: composeCode }) })
1458
+ ] }) })
1459
+ ] }) }),
1460
+ /* @__PURE__ */ jsx("section", { "aria-labelledby": "landing-agents", className: "border-t border-border py-20 sm:py-28", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-10 lg:grid-cols-2 lg:gap-16", children: [
1461
+ /* @__PURE__ */ jsxs(Stack, { gap: 5, children: [
1462
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
1463
+ /* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Built for agents" }),
1464
+ /* @__PURE__ */ jsx("h2", { id: "landing-agents", className: "text-balance text-heading-1", children: "Your agent reads the same contract you do" }),
1465
+ /* @__PURE__ */ jsxs("p", { className: "text-body-lg text-muted-foreground", children: [
1466
+ "Point Claude Code, Codex, or Cursor at the installed package. The",
1467
+ " ",
1468
+ /* @__PURE__ */ jsx(Code, { children: "sable" }),
1469
+ " CLI answers what to use, how to use it, and whether the result holds up."
1470
+ ] })
1471
+ ] }),
1472
+ /* @__PURE__ */ jsx("ul", { className: "sable-list", children: agentPoints.map((point) => /* @__PURE__ */ jsx("li", { children: point }, point)) })
1473
+ ] }),
1474
+ /* @__PURE__ */ jsx(Reveal, { className: "min-w-0", children: /* @__PURE__ */ jsx(CodeBlock, { filename: "Terminal", code: terminalCode, highlight: false }) })
1475
+ ] }) }) }),
1476
+ /* @__PURE__ */ jsx(Region, { as: "section", "aria-labelledby": "landing-cta", max: "wide", className: "pb-20 sm:pb-28", children: /* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 4, align: "center", className: "py-8 text-center sm:py-12", children: [
1477
+ /* @__PURE__ */ jsx("h2", { id: "landing-cta", className: "max-w-2xl text-balance text-heading-1", children: "Build the product, not another component library" }),
1478
+ /* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-lg text-muted-foreground", children: "Install Sable, pick a preset, and ship the first screen today." }),
1479
+ /* @__PURE__ */ jsxs(Row, { gap: 2, justify: "center", wrap: true, className: "pt-2", children: [
1480
+ /* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/docs/getting-started" }), children: "Get started" }),
1481
+ /* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "/github" }), children: "Star on GitHub" })
1482
+ ] })
1483
+ ] }) }) }) }) })
747
1484
  ] }),
748
- /* @__PURE__ */ jsx("footer", { className: "border-t border-border", children: /* @__PURE__ */ jsxs(Region, { className: "flex h-16 items-center justify-between", children: [
749
- /* @__PURE__ */ jsx("span", { className: "text-caption text-muted-foreground", children: "\xA9 2026 Sable" }),
750
- /* @__PURE__ */ jsxs(Row, { gap: 3, children: [
751
- /* @__PURE__ */ jsx("a", { href: "/github", className: "text-caption text-muted-foreground motion-interactive hover:text-foreground", children: "GitHub" }),
752
- /* @__PURE__ */ jsx("a", { href: "/changelog", className: "text-caption text-muted-foreground motion-interactive hover:text-foreground", children: "Changelog" })
753
- ] })
1485
+ /* @__PURE__ */ jsx("footer", { className: "border-t border-border", children: /* @__PURE__ */ jsxs(Region, { max: "wide", className: "py-12", children: [
1486
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-10 md:grid-cols-[minmax(0,2fr)_minmax(0,3fr)]", children: [
1487
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-xs", children: [
1488
+ /* @__PURE__ */ jsx("a", { href: "/", className: "w-fit text-title-lg motion-interactive hover:text-muted-foreground", children: "Sable" }),
1489
+ /* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: "React components, patterns, and tokens for products built with AI agents." })
1490
+ ] }),
1491
+ /* @__PURE__ */ jsx("nav", { "aria-label": "Footer", className: "grid grid-cols-2 gap-8 sm:grid-cols-3", children: footerGroups2.map((group) => /* @__PURE__ */ jsxs("div", { children: [
1492
+ /* @__PURE__ */ jsx("h2", { className: "text-label-md text-muted-foreground", children: group.title }),
1493
+ /* @__PURE__ */ jsx("ul", { className: "mt-3 flex flex-col gap-1", children: group.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
1494
+ "a",
1495
+ {
1496
+ href: link.href,
1497
+ className: "flex min-h-9 items-center text-body-sm motion-interactive hover:text-muted-foreground",
1498
+ children: link.label
1499
+ }
1500
+ ) }, link.label)) })
1501
+ ] }, group.title)) })
1502
+ ] }),
1503
+ /* @__PURE__ */ jsx("div", { className: "mt-12 border-t border-border pt-6", children: /* @__PURE__ */ jsx("p", { className: "text-caption text-muted-foreground", children: "\xA9 2026 Sable. Released under the MIT license." }) })
754
1504
  ] }) })
755
1505
  ] });
756
1506
  }
@@ -758,71 +1508,363 @@ function LandingPage() {
758
1508
  // src/patterns/landing-page.meta.ts
759
1509
  var landingPageMeta = {
760
1510
  name: "LandingPage",
761
- summary: "Marketing hero + feature grid: display-tier headline as the page's single loud moment, an eyebrow kicker, a two-action CTA row, and a three-up feature grid that reveals on scroll.",
762
- intent: "Reach for this for the marketing/landing route of a product \u2014 the one page where Display type and scroll reveals belong. Not for product UI (display type and one-per-view rules forbid it inside the app).",
1511
+ summary: "Product / open-source landing page: a centered hero with an eyebrow Badge, one display headline, lede, two CTAs and a copyable install command; a Browser-framed product preview composed from real components; a six-up feature grid; a setup section with tabbed code; an agent-workflow section with terminal output; a CTA card; and a link-column footer.",
1512
+ intent: "Reach for this for the marketing route of a developer product, library, or tool: the page where the product shows itself working and the reader installs or signs up. For a branded, editorial marketing site use BrandLandingPage. Not for product UI (display type and one-per-view rules forbid it inside the app).",
763
1513
  notes: [
764
- "Display type is the marketing voice: exactly one display headline per page, tight leading, weight 700+.",
765
- 'The eyebrow is a small outline Badge (`size="sm"`) \u2014 the badge\'s own size, never a type class forced onto its label; hierarchy above the headline is eyebrow \u2192 display \u2192 lede.',
766
- "The lede is body-lg capped at the prose measure \u2014 the headline may be wide, reading text may not.",
767
- "Two CTAs max: one primary, one quiet outline. More actions dilute the hero.",
768
- "Reveal wraps below-the-fold sections only; it no-ops under reduced motion via the motion policy.",
769
- "Feature cards are Cards in a Grid with minItemWidth (three-up that reflows on its own width), each icon + title + body-sm copy."
1514
+ "Display type is the marketing voice: exactly one display headline per page (`text-display-lg` on phones, `text-display-2xl` from `sm`). Every section below uses `text-heading-1` with a `text-overline` kicker.",
1515
+ 'The eyebrow is a small outline Badge (`size="sm"`): the badge\'s own size, never a type class forced onto its label. Hierarchy above the fold is eyebrow, display, lede, CTAs, install command.',
1516
+ "The lede is body-lg capped at the prose measure. The headline may be wide; reading text may not.",
1517
+ 'Two CTAs max per row: one primary, one quiet outline. The install line is a `CodeBlock` with `prompt="$"`, so it truncates on one line and copy takes only the command.',
1518
+ "Show the product instead of describing it. The preview is real components (PageHeader, metric Cards, a Meter, a CardList of Entity rows, Message and Bubble, ChatComposer) inside `Browser`, wrapped in an `inert` div so the picture's controls never take focus. Secondary columns hide on small screens rather than stacking into a long mock.",
1519
+ "Features are Cards in a fixed `Grid cols={{ base: 1, sm: 2, lg: 3 }}`, so six items always land as full rows (3 x 2, 2 x 3, 1 x 6) without an orphan.",
1520
+ "Multi-line code uses `CodeBlock` with a filename; switch related snippets with `Tabs`, defaulting to the most complete one. Terminal output passes `highlight={false}` so a code highlighter does not colour prose.",
1521
+ "Reveal wraps below-the-fold blocks only (features, code, CTA). It no-ops under reduced motion via the motion policy.",
1522
+ "Hide nav groups on narrow screens by wrapping them in a `max-md:hidden` div: a `Row` or `Button` sets its own display, which a `hidden` class on the same element does not reliably override."
770
1523
  ],
771
1524
  a11y: [
772
- "The hero headline is the page h1; feature titles are h2s restyled with the Title tier.",
1525
+ "The hero headline is the page h1; section titles are h2s and feature and step titles are h3s. Footer column labels are h2s inside the footer nav.",
1526
+ "The product preview is a `figure` with a visible figcaption. Its content is `inert`, which removes it from the tab order and the accessibility tree, so its headings and buttons do not pollute the outline.",
773
1527
  "Reveal renders content immediately (no animation) when prefers-reduced-motion is set.",
774
- "CTA buttons are real links or buttons with visible text \u2014 no icon-only CTAs."
1528
+ "CTA buttons are real links with visible text, with no icon-only CTAs. Footer links keep a 36px minimum row height for touch."
775
1529
  ]
776
1530
  };
777
- var members = [
778
- { id: "u_01", name: "Ada Lovelace", handle: "@ada", role: "Owner", initials: "AL", pending: false },
779
- { id: "u_02", name: "Grace Hopper", handle: "@grace", role: "Admin", initials: "GH", pending: false },
780
- { id: "u_03", name: "Mary Shelley", handle: "@mary", role: "Member", initials: "MS", pending: false },
781
- { id: "u_04", name: "Nina Simone", handle: "@nina", role: "Member", initials: "NS", pending: false },
782
- { id: "u_05", name: "Frida Kahlo", handle: "@frida", role: "Member", initials: "FK", pending: false },
783
- { id: "u_06", name: "hopper@navy.mil", handle: "invited 2 days ago", role: "Member", initials: "?", pending: true }
1531
+ var roles = [
1532
+ { value: "owner", label: "Owner" },
1533
+ { value: "admin", label: "Admin" },
1534
+ { value: "editor", label: "Editor" },
1535
+ { value: "viewer", label: "Viewer" }
1536
+ ];
1537
+ var roleLabel = {
1538
+ owner: "Owner",
1539
+ admin: "Admin",
1540
+ editor: "Editor",
1541
+ viewer: "Viewer"
1542
+ };
1543
+ var roleSummary = {
1544
+ owner: "Full access, including payouts and billing. Transfer ownership in studio settings.",
1545
+ admin: "Manages members, editions and payouts.",
1546
+ editor: "Drafts, prices and releases editions.",
1547
+ viewer: "Sees editions and sales, but can't change them."
1548
+ };
1549
+ var seats = 10;
1550
+ var initialMembers = [
1551
+ {
1552
+ id: "u_01",
1553
+ name: "Ada Lovelace",
1554
+ handle: "@ada",
1555
+ email: "ada@atelier.art",
1556
+ initials: "AL",
1557
+ role: "owner",
1558
+ pending: false,
1559
+ joined: "Mar 4, 2024",
1560
+ lastActive: "Online now",
1561
+ editions: 12,
1562
+ activity: [
1563
+ { id: "a1", icon: "gem", text: "Released Analytical Engine No. 3 \u2014 edition of 50", when: "2 hours ago" },
1564
+ { id: "a2", icon: "mail", text: "Invited louise@bourgeois.studio as Editor", when: "2 days ago" }
1565
+ ]
1566
+ },
1567
+ {
1568
+ id: "u_02",
1569
+ name: "Grace Hopper",
1570
+ handle: "@grace",
1571
+ email: "grace@atelier.art",
1572
+ initials: "GH",
1573
+ role: "admin",
1574
+ pending: false,
1575
+ joined: "Mar 9, 2024",
1576
+ lastActive: "20 minutes ago",
1577
+ editions: 4,
1578
+ activity: [
1579
+ { id: "a1", icon: "user", text: "Changed Frida Kahlo's role to Viewer", when: "Yesterday" },
1580
+ { id: "a2", icon: "edit", text: "Updated pricing on First Compiler \u2014 edition of 10", when: "3 days ago" },
1581
+ { id: "a3", icon: "gem", text: "Released First Compiler \u2014 edition of 10", when: "Sep 28" }
1582
+ ]
1583
+ },
1584
+ {
1585
+ id: "u_03",
1586
+ name: "Mary Shelley",
1587
+ handle: "@mary",
1588
+ email: "mary@atelier.art",
1589
+ initials: "MS",
1590
+ role: "editor",
1591
+ pending: false,
1592
+ joined: "Jun 18, 2024",
1593
+ lastActive: "3 hours ago",
1594
+ editions: 7,
1595
+ activity: [
1596
+ { id: "a1", icon: "calendar", text: "Scheduled Frankenstein Studies for Oct 14", when: "3 hours ago" },
1597
+ { id: "a2", icon: "gem", text: "Released The Last Man \u2014 edition of 25", when: "Sep 21" }
1598
+ ]
1599
+ },
1600
+ {
1601
+ id: "u_04",
1602
+ name: "Nina Simone",
1603
+ handle: "@nina",
1604
+ email: "nina@atelier.art",
1605
+ initials: "NS",
1606
+ role: "editor",
1607
+ pending: false,
1608
+ joined: "Jan 12, 2025",
1609
+ lastActive: "Yesterday",
1610
+ editions: 3,
1611
+ activity: [
1612
+ { id: "a1", icon: "upload", text: "Uploaded artwork for Little Girl Blue", when: "Yesterday" },
1613
+ { id: "a2", icon: "gem", text: "Released Feeling Good \u2014 edition of 100", when: "Aug 30" }
1614
+ ]
1615
+ },
1616
+ {
1617
+ id: "u_05",
1618
+ name: "Frida Kahlo",
1619
+ handle: "@frida",
1620
+ email: "frida@atelier.art",
1621
+ initials: "FK",
1622
+ role: "viewer",
1623
+ pending: false,
1624
+ joined: "Jul 2, 2025",
1625
+ lastActive: "4 days ago",
1626
+ editions: 0,
1627
+ activity: [{ id: "a1", icon: "comment", text: "Commented on the Las Dos Fridas draft", when: "4 days ago" }]
1628
+ },
1629
+ {
1630
+ id: "i_01",
1631
+ name: "louise@bourgeois.studio",
1632
+ email: "louise@bourgeois.studio",
1633
+ initials: "",
1634
+ role: "editor",
1635
+ pending: true,
1636
+ invitedBy: "Ada Lovelace",
1637
+ sent: "2 days ago",
1638
+ expires: "In 5 days",
1639
+ activity: []
1640
+ }
784
1641
  ];
785
1642
  function ListDetailPage() {
1643
+ const [members, setMembers] = useState(initialMembers);
1644
+ const [selectedId, setSelectedId] = useState("u_02");
1645
+ const [query, setQuery] = useState("");
1646
+ const [showDetail, setShowDetail] = useState(false);
1647
+ const [confirming, setConfirming] = useState(false);
1648
+ const listRef = useRef(null);
1649
+ const backRef = useRef(null);
1650
+ const searchRef = useRef(null);
1651
+ const needle = query.trim().toLowerCase();
1652
+ const visible = members.filter(
1653
+ (m) => [m.name, m.email, m.handle ?? "", roleLabel[m.role]].some((v) => v.toLowerCase().includes(needle))
1654
+ );
1655
+ const selected = members.find((m) => m.id === selectedId) ?? null;
1656
+ const active = members.filter((m) => !m.pending).length;
1657
+ const pending2 = members.length - active;
1658
+ useEffect(() => {
1659
+ if (showDetail && backRef.current?.offsetParent) backRef.current.focus();
1660
+ }, [showDetail, selectedId]);
1661
+ function select(id) {
1662
+ setSelectedId(id);
1663
+ setShowDetail(true);
1664
+ }
1665
+ function focusRow(id) {
1666
+ requestAnimationFrame(() => {
1667
+ listRef.current?.querySelector(`[data-member-id="${id}"] button`)?.focus();
1668
+ });
1669
+ }
1670
+ function backToList() {
1671
+ setShowDetail(false);
1672
+ focusRow(selectedId ?? void 0);
1673
+ }
1674
+ function setRole(id, role) {
1675
+ setMembers((list) => list.map((m) => m.id === id ? { ...m, role } : m));
1676
+ }
1677
+ function resend(id) {
1678
+ setMembers(
1679
+ (list) => list.map((m) => m.id === id ? { ...m, sent: "Just now", expires: "In 7 days", resent: true } : m)
1680
+ );
1681
+ }
1682
+ function remove(id) {
1683
+ const index = members.findIndex((m) => m.id === id);
1684
+ const rest = members.filter((m) => m.id !== id);
1685
+ setMembers(rest);
1686
+ const next = rest[Math.min(index, rest.length - 1)]?.id;
1687
+ setSelectedId(next ?? null);
1688
+ setShowDetail(false);
1689
+ focusRow(next);
1690
+ }
786
1691
  return /* @__PURE__ */ jsxs(Page, { as: "main", max: "wide", gap: 4, className: "py-6", children: [
787
1692
  /* @__PURE__ */ jsx(
788
1693
  PageHeader,
789
1694
  {
790
1695
  title: "Members",
791
- description: "Everyone with access to this workspace.",
792
- actions: /* @__PURE__ */ jsx(Button, { children: "Invite member" })
1696
+ description: `${active} members \xB7 ${pending2} pending ${pending2 === 1 ? "invite" : "invites"} \xB7 ${members.length} of ${seats} seats used`,
1697
+ actions: /* @__PURE__ */ jsx(Button, { iconStart: /* @__PURE__ */ jsx(Icon, { name: "plus" }), children: "Invite member" })
793
1698
  }
794
1699
  ),
795
1700
  /* @__PURE__ */ jsxs(Columns, { count: { base: 1, lg: 12 }, children: [
796
- /* @__PURE__ */ jsx(Col, { span: { base: 1, lg: 8 }, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
797
- /* @__PURE__ */ jsx(SearchInput, { placeholder: "Search members", "aria-label": "Search members" }),
798
- /* @__PURE__ */ jsx(Card, { className: "overflow-hidden", children: /* @__PURE__ */ jsx(CardList, { children: members.map((member) => /* @__PURE__ */ jsx(
799
- Entity,
1701
+ /* @__PURE__ */ jsx(Col, { span: { base: 1, lg: 5 }, className: showDetail ? "max-lg:hidden" : void 0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
1702
+ /* @__PURE__ */ jsx(
1703
+ SearchInput,
800
1704
  {
801
- leading: /* @__PURE__ */ jsx(
802
- Avatar,
803
- {
804
- fallback: member.pending ? "\u2022" : member.initials,
805
- alt: member.pending ? "" : member.name
806
- }
807
- ),
808
- title: /* @__PURE__ */ jsx("span", { className: member.pending ? "text-muted-foreground" : void 0, children: member.name }),
809
- subtitle: member.handle,
810
- action: member.pending ? /* @__PURE__ */ jsx(Badge, { children: "Invited" }) : /* @__PURE__ */ jsx(Badge, { appearance: "outline", children: member.role })
811
- },
812
- member.id
813
- )) }) })
1705
+ ref: searchRef,
1706
+ value: query,
1707
+ onValueChange: setQuery,
1708
+ placeholder: "Search name, email or role",
1709
+ "aria-label": "Search members"
1710
+ }
1711
+ ),
1712
+ /* @__PURE__ */ jsx(Card, { ref: listRef, className: "sable-surface", children: visible.length > 0 ? /* @__PURE__ */ jsx(CardList, { "aria-label": "Members", children: visible.map((m) => {
1713
+ const isSelected = m.id === selectedId;
1714
+ return /* @__PURE__ */ jsx(
1715
+ Entity,
1716
+ {
1717
+ "data-member-id": m.id,
1718
+ interactive: true,
1719
+ selected: isSelected,
1720
+ onSelect: () => select(m.id),
1721
+ leading: /* @__PURE__ */ jsx(
1722
+ Avatar,
1723
+ {
1724
+ fallback: m.pending ? /* @__PURE__ */ jsx(Icon, { name: "mail", className: "size-5" }) : m.initials,
1725
+ status: m.lastActive === "Online now" ? /* @__PURE__ */ jsx(StatusDot, { tone: "success" }) : void 0,
1726
+ "aria-hidden": true
1727
+ }
1728
+ ),
1729
+ title: m.name,
1730
+ subtitle: m.pending ? `${roleLabel[m.role]} \xB7 invite sent ${m.sent?.toLowerCase()}` : `${roleLabel[m.role]} \xB7 ${m.handle}`,
1731
+ action: m.pending ? /* @__PURE__ */ jsx(Badge, { tone: "info", children: "Invited" }) : void 0
1732
+ },
1733
+ m.id
1734
+ );
1735
+ }) }) : /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(
1736
+ EmptyState,
1737
+ {
1738
+ icon: /* @__PURE__ */ jsx(Icon, { name: "search" }),
1739
+ title: "No members found",
1740
+ description: `Nobody matches \u201C${query.trim()}\u201D. Try a name, email or role.`,
1741
+ action: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: () => {
1742
+ setQuery("");
1743
+ searchRef.current?.focus();
1744
+ }, children: "Clear search" })
1745
+ }
1746
+ ) }) })
814
1747
  ] }) }),
815
- /* @__PURE__ */ jsx(Col, { span: { base: 1, lg: 4 }, children: /* @__PURE__ */ jsx("aside", { className: "sticky top-6", children: /* @__PURE__ */ jsxs(Card, { children: [
816
- /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: "Workspace" }) }),
817
- /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-3", children: [
818
- /* @__PURE__ */ jsxs(Description, { children: [
819
- /* @__PURE__ */ jsx(DescriptionItem, { term: "Members", detail: /* @__PURE__ */ jsx("span", { className: "text-mono-md", children: "5" }) }),
820
- /* @__PURE__ */ jsx(DescriptionItem, { term: "Pending invites", detail: /* @__PURE__ */ jsx("span", { className: "text-mono-md", children: "1" }) }),
821
- /* @__PURE__ */ jsx(DescriptionItem, { term: "Seats used", detail: /* @__PURE__ */ jsx("span", { className: "text-mono-md", children: "6 / 10" }) })
1748
+ /* @__PURE__ */ jsx(Col, { span: { base: 1, lg: 7 }, className: showDetail ? void 0 : "max-lg:hidden", children: /* @__PURE__ */ jsxs(Stack, { role: "region", "aria-label": "Member details", gap: 3, className: "lg:sticky lg:top-6", children: [
1749
+ /* @__PURE__ */ jsx(
1750
+ Button,
1751
+ {
1752
+ ref: backRef,
1753
+ variant: "ghost",
1754
+ size: "sm",
1755
+ iconStart: /* @__PURE__ */ jsx(Icon, { name: "chevron-left" }),
1756
+ onClick: backToList,
1757
+ className: "self-start lg:hidden",
1758
+ children: "All members"
1759
+ }
1760
+ ),
1761
+ selected === null ? /* @__PURE__ */ jsx(
1762
+ EmptyState,
1763
+ {
1764
+ surface: "card",
1765
+ icon: /* @__PURE__ */ jsx(Icon, { name: "user" }),
1766
+ title: "No member selected",
1767
+ description: "Choose someone from the list to see their role and activity."
1768
+ }
1769
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1770
+ /* @__PURE__ */ jsxs(Card, { children: [
1771
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
1772
+ /* @__PURE__ */ jsxs(Row, { gap: 2, children: [
1773
+ /* @__PURE__ */ jsx(
1774
+ Avatar,
1775
+ {
1776
+ size: "lg",
1777
+ fallback: selected.pending ? /* @__PURE__ */ jsx(Icon, { name: "mail", className: "size-6" }) : selected.initials,
1778
+ status: selected.lastActive === "Online now" ? /* @__PURE__ */ jsx(StatusDot, { tone: "success" }) : void 0,
1779
+ alt: selected.pending ? "Pending invite" : selected.name
1780
+ }
1781
+ ),
1782
+ /* @__PURE__ */ jsxs(Stack, { gap: 0, className: "min-w-0", children: [
1783
+ /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), className: "truncate", children: selected.name }),
1784
+ /* @__PURE__ */ jsx(CardDescription, { className: "truncate", children: selected.pending ? "Hasn't joined yet" : `${selected.handle} \xB7 ${selected.email}` })
1785
+ ] })
1786
+ ] }),
1787
+ selected.pending ? /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, { tone: "info", children: "Invited" }) }) : null
1788
+ ] }),
1789
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 5, children: [
1790
+ /* @__PURE__ */ jsx(
1791
+ Field,
1792
+ {
1793
+ label: selected.pending ? "Role on joining" : "Role",
1794
+ description: roleSummary[selected.role],
1795
+ children: /* @__PURE__ */ jsxs(
1796
+ Select,
1797
+ {
1798
+ items: roles,
1799
+ value: selected.role,
1800
+ onValueChange: (value) => setRole(selected.id, value),
1801
+ disabled: selected.role === "owner",
1802
+ children: [
1803
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "max-w-72" }),
1804
+ /* @__PURE__ */ jsx(SelectContent, { children: roles.filter((r) => r.value !== "owner" || selected.role === "owner").map((r) => /* @__PURE__ */ jsx(SelectItem, { value: r.value, children: r.label }, r.value)) })
1805
+ ]
1806
+ }
1807
+ )
1808
+ }
1809
+ ),
1810
+ /* @__PURE__ */ jsx("div", { className: "@container", children: selected.pending ? /* @__PURE__ */ jsxs("dl", { className: "sable-list-dl sable-list-dl-2", children: [
1811
+ /* @__PURE__ */ jsx("dt", { children: "Sent" }),
1812
+ /* @__PURE__ */ jsx("dd", { children: selected.sent }),
1813
+ /* @__PURE__ */ jsx("dt", { children: "Expires" }),
1814
+ /* @__PURE__ */ jsx("dd", { children: selected.expires }),
1815
+ /* @__PURE__ */ jsx("dt", { children: "Invited by" }),
1816
+ /* @__PURE__ */ jsx("dd", { children: selected.invitedBy })
1817
+ ] }) : /* @__PURE__ */ jsxs("dl", { className: "sable-list-dl sable-list-dl-2", children: [
1818
+ /* @__PURE__ */ jsx("dt", { children: "Email" }),
1819
+ /* @__PURE__ */ jsx("dd", { children: selected.email }),
1820
+ /* @__PURE__ */ jsx("dt", { children: "Joined" }),
1821
+ /* @__PURE__ */ jsx("dd", { children: selected.joined }),
1822
+ /* @__PURE__ */ jsx("dt", { children: "Last active" }),
1823
+ /* @__PURE__ */ jsx("dd", { className: selected.lastActive === "Online now" ? "text-success" : void 0, children: selected.lastActive }),
1824
+ /* @__PURE__ */ jsx("dt", { children: "Editions released" }),
1825
+ /* @__PURE__ */ jsx("dd", { children: selected.editions })
1826
+ ] }) })
1827
+ ] }) }),
1828
+ selected.role !== "owner" ? /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, children: [
1829
+ selected.pending ? /* @__PURE__ */ jsx(
1830
+ Button,
1831
+ {
1832
+ variant: "secondary",
1833
+ size: "sm",
1834
+ iconStart: /* @__PURE__ */ jsx(Icon, { name: selected.resent ? "check" : "send" }),
1835
+ disabled: selected.resent,
1836
+ onClick: () => resend(selected.id),
1837
+ children: selected.resent ? "Invite resent" : "Resend invite"
1838
+ }
1839
+ ) : null,
1840
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirming(true), children: selected.pending ? "Revoke invite" : "Remove from studio" })
1841
+ ] }) }) : null
822
1842
  ] }),
823
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "mt-1 self-start", children: "Manage plan" })
1843
+ selected.activity.length > 0 ? /* @__PURE__ */ jsxs(Card, { children: [
1844
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h3", {}), children: "Recent activity" }) }),
1845
+ /* @__PURE__ */ jsx(CardList, { children: selected.activity.map((a) => /* @__PURE__ */ jsx(
1846
+ Entity,
1847
+ {
1848
+ leading: /* @__PURE__ */ jsx(Icon, { name: a.icon, className: "text-muted-foreground" }),
1849
+ title: a.text,
1850
+ subtitle: a.when
1851
+ },
1852
+ a.id
1853
+ )) })
1854
+ ] }) : null,
1855
+ /* @__PURE__ */ jsx(
1856
+ DestructiveActionModal,
1857
+ {
1858
+ open: confirming,
1859
+ onOpenChange: setConfirming,
1860
+ title: selected.pending ? "Revoke this invite?" : `Remove ${selected.name}?`,
1861
+ description: selected.pending ? `${selected.email} won't be able to join with the link they were sent.` : "They lose access to the studio right away. Editions they released stay on sale.",
1862
+ confirmLabel: selected.pending ? "Revoke invite" : "Remove member",
1863
+ onConfirm: () => remove(selected.id)
1864
+ }
1865
+ )
824
1866
  ] })
825
- ] }) }) })
1867
+ ] }) })
826
1868
  ] })
827
1869
  ] });
828
1870
  }
@@ -830,25 +1872,65 @@ function ListDetailPage() {
830
1872
  // src/patterns/list-detail-page.meta.ts
831
1873
  var listDetailPageMeta = {
832
1874
  name: "ListDetailPage",
833
- summary: "Two-column list-detail: a wide region split 8/4, the record list as Entity rows in the main column, and a sticky supporting rail (detail, filters, or summary) beside it.",
834
- intent: "Reach for this when a route lists records AND keeps one selected/summarized in view (members + member detail, inbox + message, orders + order). For pure browsing with no rail, a single-column FeedPage or DashboardPage table is simpler.",
1875
+ summary: "Searchable list beside the selected record's detail: interactive Entity rows in a CardList (5 of 12 columns) and a sticky detail pane (7) with an editable field, a label/value list, record actions with a destructive confirmation, and recent activity. Below lg it becomes a drill-in.",
1876
+ intent: "Reach for this when a route lists records AND works on one at a time (members + member, inbox + message, orders + order). For pure browsing with no selection, a single-column FeedPage or a DashboardPage table is simpler.",
835
1877
  notes: [
836
- "Region cap is `wide`; the split is Columns 12 \u2192 Col 8 + Col 4, collapsing to stacked full-width columns below lg via breakpoint maps.",
837
- "List rows are Entity inside one bordered Card, in CardList: it insets each row to the card padding and divides them with hairlines \u2014 on a dark canvas rows need an anchoring surface, not a flat float.",
838
- "Pending records live in the same list, muted (muted title + `Invited` badge), instead of a separate section.",
839
- "The rail card holds label/value pairs as a Description list \u2014 muted terms, mono (tabular) values \u2014 plus one quiet action. Never hand-built `justify-between` rows.",
840
- "The rail is sticky (top on the 4px scale) so the summary stays in view while the list scrolls."
1878
+ "Region cap is `wide`; Columns 12 \u2192 Col 5 (list) + Col 7 (detail). Below lg the list and detail share one column as a drill-in: selecting a row shows the detail with an `All members` back button (lg:hidden), and the back button returns to the list. The panes swap with `max-lg:hidden`, so desktop always shows both.",
1879
+ "Rows are interactive Entity inside one Card + CardList. Selection is Entity `selected`; `sable-surface` on the list card makes its selected step 12% ink off the card, so a selected row stays distinct from its muted avatar.",
1880
+ "Search filters the list in place (name, email, role). No matches swap the CardList for an EmptyState with a Clear search action; the selected detail stays open.",
1881
+ "A pending invite lives in the same list: a mail glyph in the Avatar, an `Invited` Badge, and an invite-specific detail (role on joining, sent, expires, invited by; Resend and Revoke actions).",
1882
+ "The role Select edits the record in place; its Field description states what the role can do. The owner's role is read-only and the owner has no remove action.",
1883
+ 'Read-only facts are a native `<dl className="sable-list-dl sable-list-dl-2">` inside an `@container`, so pairs go two-up only when the pane is wide enough.',
1884
+ "Remove / revoke run through DestructiveActionModal; on confirm the next row is selected. Page-level counts (members, pending, seats) live in the PageHeader description and update with the list.",
1885
+ "The detail pane is sticky (lg:top-6) so it stays in view while a long list scrolls."
841
1886
  ],
842
1887
  a11y: [
843
- "Entity rows given `interactive` + `onSelect` (to open a member's detail) render as real buttons; keyboard focus follows list order. Rows here are presentational.",
844
- "The rail is an <aside> landmark; the list column is the main content."
1888
+ "Each row is a real button (Entity `interactive` + `onSelect`): Tab moves through rows, Enter/Space selects. The selected row is Entity `selected`, which paints the selected fill and sets aria-current; list avatars are aria-hidden so initials are not read before the name.",
1889
+ "The detail pane is a labelled region (`Member details`) whose title is a level-2 heading.",
1890
+ "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."
845
1891
  ]
846
1892
  };
1893
+ var sections = [
1894
+ { id: "profile", label: "Profile" },
1895
+ { id: "notifications", label: "Notifications" },
1896
+ { id: "payouts", label: "Payouts" },
1897
+ { id: "account", label: "Account" }
1898
+ ];
1899
+ var timezones = [
1900
+ { value: "europe-london", label: "London (GMT+1)" },
1901
+ { value: "europe-paris", label: "Paris (GMT+2)" },
1902
+ { value: "america-new-york", label: "New York (GMT\u22124)" },
1903
+ { value: "america-los-angeles", label: "Los Angeles (GMT\u22127)" },
1904
+ { value: "asia-tokyo", label: "Tokyo (GMT+9)" }
1905
+ ];
1906
+ var notifications = [
1907
+ { name: "notifyCollects", title: "New collects", description: "When someone collects your work", on: true },
1908
+ { name: "notifySoldOut", title: "Sold out", description: "When an edition sells its last copy", on: true },
1909
+ { name: "notifyFollowers", title: "New followers", description: "When a collector follows you", on: false },
1910
+ { name: "notifyDigest", title: "Weekly digest", description: "Sales and collectors, every Monday", on: true },
1911
+ { name: "notifyProduct", title: "Product updates", description: "New Atelier features", on: false }
1912
+ ];
1913
+ var payouts = [
1914
+ { label: "Next payout", value: "$1,284.60", note: "Friday, Oct 16" },
1915
+ { label: "Pending", value: "$412.00", note: "Clears in 7 days" },
1916
+ { label: "Paid this year", value: "$18,920.00", note: "Across 214 collects" }
1917
+ ];
847
1918
  function SettingsPage() {
848
- return /* @__PURE__ */ jsx(
1919
+ const [activeSection, setActiveSection] = useState("profile");
1920
+ const [deleteOpen, setDeleteOpen] = useState(false);
1921
+ return /* @__PURE__ */ jsxs(
849
1922
  AppShell,
850
1923
  {
851
- header: /* @__PURE__ */ jsx(AppHeader, { title: /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" }) }),
1924
+ header: /* @__PURE__ */ jsx(
1925
+ AppHeader,
1926
+ {
1927
+ title: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", children: [
1928
+ /* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
1929
+ /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
1930
+ ] }),
1931
+ actions: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AL", alt: "Ada Lovelace" })
1932
+ }
1933
+ ),
852
1934
  bottomNav: /* @__PURE__ */ jsxs(BottomNav, { "aria-label": "Primary", children: [
853
1935
  /* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
854
1936
  /* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
@@ -857,55 +1939,182 @@ function SettingsPage() {
857
1939
  /* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", showLabel: true, active: true, render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
858
1940
  ] }),
859
1941
  sidebar: /* @__PURE__ */ jsxs(Sidebar, { children: [
860
- /* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" }) }),
1942
+ /* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", className: "px-3", children: [
1943
+ /* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
1944
+ /* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
1945
+ ] }) }),
861
1946
  /* @__PURE__ */ jsxs(SidebarNav, { children: [
862
1947
  /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
863
1948
  /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
864
1949
  /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "user" }), label: "Collectors", render: /* @__PURE__ */ jsx("a", { href: "/collectors" }) }),
865
- /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "bell" }), label: "Activity", render: /* @__PURE__ */ jsx("a", { href: "/activity" }) }),
1950
+ /* @__PURE__ */ jsx(
1951
+ SidebarItem,
1952
+ {
1953
+ icon: /* @__PURE__ */ jsx(Icon, { name: "bell" }),
1954
+ label: "Activity",
1955
+ badge: /* @__PURE__ */ jsx(NotificationBadge, { count: 4, tone: "neutral" }),
1956
+ render: /* @__PURE__ */ jsx("a", { href: "/activity" })
1957
+ }
1958
+ ),
866
1959
  /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", active: true, render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
867
- ] })
868
- ] }),
869
- children: /* @__PURE__ */ jsxs(Page, { max: "content", gap: 4, className: "py-6", children: [
870
- /* @__PURE__ */ jsx(
871
- PageHeader,
1960
+ ] }),
1961
+ /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(
1962
+ Entity,
872
1963
  {
873
- title: "Settings",
874
- description: "Your profile, notifications, and account controls."
1964
+ className: "[--entity-py:0]",
1965
+ leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AL", alt: "" }),
1966
+ title: "Ada Lovelace",
1967
+ subtitle: "@ada",
1968
+ action: /* @__PURE__ */ jsxs(OverflowMenu, { label: "Account", size: "icon-sm", children: [
1969
+ /* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/@ada" }), children: "View profile" }),
1970
+ /* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/settings" }), children: "Settings" }),
1971
+ /* @__PURE__ */ jsx(MenuSeparator, {}),
1972
+ /* @__PURE__ */ jsx(MenuItem, { children: "Sign out" })
1973
+ ] })
875
1974
  }
876
- ),
877
- /* @__PURE__ */ jsx(Form, { onSubmit: (event) => event.preventDefault(), children: /* @__PURE__ */ jsxs(Fieldset, { children: [
878
- /* @__PURE__ */ jsx(FieldsetLegend, { children: "Profile" }),
879
- /* @__PURE__ */ jsx(FieldsetDescription, { children: "How you appear across the app." }),
880
- /* @__PURE__ */ jsxs(FieldsetContent, { children: [
881
- /* @__PURE__ */ jsx(Field, { label: "Display name", required: true, children: /* @__PURE__ */ jsx(Input, { name: "displayName", defaultValue: "ada" }) }),
882
- /* @__PURE__ */ jsx(Field, { label: "Bio", description: "Shown on your public profile.", children: /* @__PURE__ */ jsx(Textarea, { name: "bio", rows: 3, placeholder: "Tell collectors who you are" }) }),
883
- /* @__PURE__ */ jsx(Field, { label: "Timezone", children: /* @__PURE__ */ jsxs(Select, { defaultValue: "utc", name: "timezone", children: [
884
- /* @__PURE__ */ jsx(SelectTrigger, {}),
885
- /* @__PURE__ */ jsxs(SelectContent, { children: [
886
- /* @__PURE__ */ jsx(SelectItem, { value: "utc", children: "UTC" }),
887
- /* @__PURE__ */ jsx(SelectItem, { value: "est", children: "Eastern (UTC\u22125)" }),
888
- /* @__PURE__ */ jsx(SelectItem, { value: "pst", children: "Pacific (UTC\u22128)" })
1975
+ ) })
1976
+ ] }),
1977
+ children: [
1978
+ /* @__PURE__ */ jsxs(Page, { max: "wide", gap: 4, className: "py-6", children: [
1979
+ /* @__PURE__ */ jsx(
1980
+ PageHeader,
1981
+ {
1982
+ title: "Settings",
1983
+ description: "Manage your profile, notifications, and payouts."
1984
+ }
1985
+ ),
1986
+ /* @__PURE__ */ jsxs(Columns, { gap: 4, children: [
1987
+ /* @__PURE__ */ jsx(Col, { span: { base: 12, lg: 3 }, className: "hidden lg:block", children: /* @__PURE__ */ jsx(SideNav, { "aria-label": "Settings sections", className: "sticky top-6", children: sections.map((section) => /* @__PURE__ */ jsx(
1988
+ SideNavItem,
1989
+ {
1990
+ label: section.label,
1991
+ active: activeSection === section.id,
1992
+ onClick: () => setActiveSection(section.id),
1993
+ render: /* @__PURE__ */ jsx("a", { href: `#${section.id}` })
1994
+ },
1995
+ section.id
1996
+ )) }) }),
1997
+ /* @__PURE__ */ jsx(Col, { span: { base: 12, lg: 9 }, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
1998
+ /* @__PURE__ */ jsxs(Card, { id: "profile", className: "scroll-mt-6", children: [
1999
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2000
+ /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Profile" }),
2001
+ /* @__PURE__ */ jsx(CardDescription, { children: "How collectors see you on Atelier." })
2002
+ ] }),
2003
+ /* @__PURE__ */ jsxs(Form, { onSubmit: (event) => event.preventDefault(), className: "gap-0", children: [
2004
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
2005
+ /* @__PURE__ */ jsxs(Row, { gap: 2, children: [
2006
+ /* @__PURE__ */ jsx(Avatar, { size: "lg", fallback: "AL", alt: "Ada Lovelace" }),
2007
+ /* @__PURE__ */ jsxs(Stack, { gap: 0.5, align: "start", children: [
2008
+ /* @__PURE__ */ jsxs(Row, { gap: 1, children: [
2009
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", children: "Upload photo" }),
2010
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", children: "Remove" })
2011
+ ] }),
2012
+ /* @__PURE__ */ jsx("p", { className: "text-caption text-muted-foreground", children: "Square PNG or JPG, at least 400 px." })
2013
+ ] })
2014
+ ] }),
2015
+ /* @__PURE__ */ jsxs(Grid, { cols: { base: 1, sm: 2 }, gap: 3, children: [
2016
+ /* @__PURE__ */ jsx(Field, { label: "Display name", name: "displayName", required: true, children: /* @__PURE__ */ jsx(Input, { defaultValue: "Ada Lovelace", autoComplete: "name" }) }),
2017
+ /* @__PURE__ */ jsx(Field, { label: "Handle", name: "handle", description: "atelier.com/@ada", required: true, children: /* @__PURE__ */ jsx(Input, { defaultValue: "ada", autoComplete: "username", spellCheck: false }) })
2018
+ ] }),
2019
+ /* @__PURE__ */ jsx(Field, { label: "Bio", name: "bio", description: "Shown on your profile and edition pages.", children: /* @__PURE__ */ jsx(
2020
+ Textarea,
2021
+ {
2022
+ rows: 3,
2023
+ defaultValue: "Generative artist working in plotted ink and code. New editions of 25 to 100 every month."
2024
+ }
2025
+ ) }),
2026
+ /* @__PURE__ */ jsx(Grid, { cols: { base: 1, sm: 2 }, gap: 3, children: /* @__PURE__ */ jsx(Field, { label: "Timezone", name: "timezone", description: "Release times use this zone.", children: /* @__PURE__ */ jsxs(Select, { defaultValue: "europe-london", items: timezones, children: [
2027
+ /* @__PURE__ */ jsx(SelectTrigger, {}),
2028
+ /* @__PURE__ */ jsx(SelectContent, { children: timezones.map((zone) => /* @__PURE__ */ jsx(SelectItem, { value: zone.value, children: zone.label }, zone.value)) })
2029
+ ] }) }) })
2030
+ ] }) }),
2031
+ /* @__PURE__ */ jsx(CardFooter, { className: "justify-end", children: /* @__PURE__ */ jsx(Button, { type: "submit", children: "Save profile" }) })
2032
+ ] })
2033
+ ] }),
2034
+ /* @__PURE__ */ jsxs(Card, { id: "notifications", className: "scroll-mt-6", children: [
2035
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2036
+ /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Notifications" }),
2037
+ /* @__PURE__ */ jsx(CardDescription, { children: "Email updates about your editions. Changes save automatically." })
2038
+ ] }),
2039
+ /* @__PURE__ */ jsx(CardList, { children: notifications.map((item) => /* @__PURE__ */ jsx(
2040
+ Entity,
2041
+ {
2042
+ title: item.title,
2043
+ subtitle: item.description,
2044
+ action: /* @__PURE__ */ jsx(
2045
+ Switch,
2046
+ {
2047
+ name: item.name,
2048
+ defaultChecked: item.on,
2049
+ "aria-label": item.title
2050
+ }
2051
+ )
2052
+ },
2053
+ item.name
2054
+ )) })
2055
+ ] }),
2056
+ /* @__PURE__ */ jsxs(Card, { id: "payouts", className: "scroll-mt-6", children: [
2057
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2058
+ /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Payouts" }),
2059
+ /* @__PURE__ */ jsx(CardDescription, { children: "Paid in USD every Friday to Chase checking \u2022\u20224821." }),
2060
+ /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/settings/payouts" }), children: "Manage payouts" }) })
2061
+ ] }),
2062
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(Grid, { cols: { base: 1, sm: 3 }, gap: 3, children: payouts.map((stat) => /* @__PURE__ */ jsxs(Stack, { gap: 0.5, children: [
2063
+ /* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: stat.label }),
2064
+ /* @__PURE__ */ jsx("p", { className: "text-mono-lg text-foreground", children: stat.value }),
2065
+ /* @__PURE__ */ jsx("p", { className: "text-caption text-muted-foreground", children: stat.note })
2066
+ ] }, stat.label)) }) })
2067
+ ] }),
2068
+ /* @__PURE__ */ jsxs(Card, { id: "account", className: "scroll-mt-6", children: [
2069
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2070
+ /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Account" }),
2071
+ /* @__PURE__ */ jsx(CardDescription, { children: "Sign-in details and your data." })
2072
+ ] }),
2073
+ /* @__PURE__ */ jsxs(CardList, { children: [
2074
+ /* @__PURE__ */ jsx(
2075
+ Entity,
2076
+ {
2077
+ leading: /* @__PURE__ */ jsx(Icon, { name: "mail", className: "text-lg text-muted-foreground" }),
2078
+ title: "Email",
2079
+ subtitle: "ada@atelier.art",
2080
+ action: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: "Change" })
2081
+ }
2082
+ ),
2083
+ /* @__PURE__ */ jsx(
2084
+ Entity,
2085
+ {
2086
+ leading: /* @__PURE__ */ jsx(Icon, { name: "download", className: "text-lg text-muted-foreground" }),
2087
+ title: "Export data",
2088
+ subtitle: "Editions, sales, and collectors",
2089
+ action: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: "Export" })
2090
+ }
2091
+ )
2092
+ ] })
2093
+ ] }),
2094
+ /* @__PURE__ */ jsxs(Card, { children: [
2095
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2096
+ /* @__PURE__ */ jsx(CardTitle, { render: /* @__PURE__ */ jsx("h2", {}), children: "Delete account" }),
2097
+ /* @__PURE__ */ jsx(CardDescription, { children: "Permanently remove your profile, editions, and sales history. Collectors keep the editions they own." })
2098
+ ] }),
2099
+ /* @__PURE__ */ jsx(CardFooter, { className: "justify-end", children: /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => setDeleteOpen(true), children: "Delete account" }) })
889
2100
  ] })
890
2101
  ] }) })
891
- ] }),
892
- /* @__PURE__ */ jsx(FieldsetFooter, { children: /* @__PURE__ */ jsx(Button, { type: "submit", children: "Save profile" }) })
893
- ] }) }),
894
- /* @__PURE__ */ jsx(Form, { onSubmit: (event) => event.preventDefault(), children: /* @__PURE__ */ jsxs(Fieldset, { children: [
895
- /* @__PURE__ */ jsx(FieldsetLegend, { children: "Notifications" }),
896
- /* @__PURE__ */ jsx(FieldsetDescription, { children: "What we email you about." }),
897
- /* @__PURE__ */ jsxs(FieldsetContent, { children: [
898
- /* @__PURE__ */ jsx(Field, { label: "New collector alerts", orientation: "horizontal", children: /* @__PURE__ */ jsx(Switch, { name: "collectorAlerts", defaultChecked: true }) }),
899
- /* @__PURE__ */ jsx(Field, { label: "Weekly digest", orientation: "horizontal", children: /* @__PURE__ */ jsx(Switch, { name: "weeklyDigest" }) })
900
- ] }),
901
- /* @__PURE__ */ jsx(FieldsetFooter, { children: /* @__PURE__ */ jsx(Button, { type: "submit", children: "Save notifications" }) })
902
- ] }) }),
903
- /* @__PURE__ */ jsxs(Fieldset, { children: [
904
- /* @__PURE__ */ jsx(FieldsetLegend, { children: "Danger zone" }),
905
- /* @__PURE__ */ jsx(FieldsetDescription, { children: "Deleting your account removes your posts and collections permanently." }),
906
- /* @__PURE__ */ jsx(FieldsetFooter, { children: /* @__PURE__ */ jsx(Button, { variant: "destructive", children: "Delete account" }) })
907
- ] })
908
- ] })
2102
+ ] })
2103
+ ] }),
2104
+ /* @__PURE__ */ jsx(
2105
+ DestructiveActionModal,
2106
+ {
2107
+ open: deleteOpen,
2108
+ onOpenChange: setDeleteOpen,
2109
+ title: "Delete your account?",
2110
+ description: "This permanently removes your profile, 12 editions, and sales history. Pending payouts are still sent. This can't be undone.",
2111
+ confirmLabel: "Delete account",
2112
+ confirmingLabel: "Deleting\u2026",
2113
+ confirmText: "ada",
2114
+ onConfirm: () => setDeleteOpen(false)
2115
+ }
2116
+ )
2117
+ ]
909
2118
  }
910
2119
  );
911
2120
  }
@@ -913,21 +2122,25 @@ function SettingsPage() {
913
2122
  // src/patterns/settings-page.meta.ts
914
2123
  var settingsPageMeta = {
915
2124
  name: "SettingsPage",
916
- summary: "Stacked settings sections inside the app shell: a content-width region, one Fieldset card per concern (each with its own save), auto-wired Form/Field rows, and a destructive zone last.",
2125
+ summary: "Account settings inside the app shell: a sticky SideNav section rail beside a column of section Cards \u2014 a Profile form with the page's one explicit save, Card + CardList + Entity rows with Switches that apply instantly, a read-only payouts summary, account rows, and a delete card confirmed through DestructiveActionModal.",
917
2126
  intent: "Reach for this for account/profile/workspace settings and any 'edit a handful of grouped preferences' route. Not for a single short form (use AuthPage's centered form shape) or bulk record editing (use DashboardPage + DataTable).",
918
2127
  notes: [
919
- "Region cap is `content` (896px): settings panes hold labeled rows and the occasional 2-up field, never a full grid.",
920
- "One Fieldset per concern, each with its own footer save \u2014 per-section saves beat one page-wide submit for settings.",
921
- "Field wires label/description/error a11y automatically; never hand-roll <label> + <input> pairs here.",
922
- "The destructive section is visually last, isolated in its own Fieldset with a destructive-variant action.",
923
- 'Toggles use orientation="horizontal" Fields so the label sits beside the Switch.',
2128
+ "Region cap is `wide`, split on a 12-column track: a 3-column SideNav rail and a 9-column section column. The rail fills the space a lone content-width column would leave empty and gives the page its table of contents.",
2129
+ "The rail is `SideNav` (the documented settings sub-nav), sticky, with in-page anchor items; it hides below lg, where the sections simply stack and the shell's BottomNav carries app navigation.",
2130
+ "One save model, stated in the copy: drafted text (the Profile form) commits through its single footer save; toggles apply the moment they change, and the Notifications description says so. Never pair a Switch with a Save button.",
2131
+ "Settings rows are Card + CardList + Entity with the Switch in `action` \u2014 the row owns its title and one-line description; keep descriptions short, because Entity truncates its subtitle.",
2132
+ "Each section is a Card whose CardTitle holds an h2, so the page has a real heading outline under PageHeader's h1; Cards carry the anchor id and `scroll-mt-*` so anchor jumps clear the top edge.",
2133
+ "Field wires label/description/error a11y automatically; never hand-roll <label> + <input> pairs. Pair fields side by side with Grid cols={{ base: 1, sm: 2 }}.",
2134
+ "The destructive action is last and isolated in its own Card, and it never acts directly: it opens DestructiveActionModal with type-to-confirm on the handle.",
924
2135
  "Lives in the same AppShell as DashboardPage: Sidebar from lg up, AppHeader + BottomNav below it, with Settings marked active in both."
925
2136
  ],
926
2137
  a11y: [
927
2138
  'AppShell provides the skip link and <main id="main-content"> landmark; the active SidebarItem and BottomNavItem carry aria-current.',
928
- "Each section is a real <fieldset> with a <legend> (FieldsetLegend).",
2139
+ 'The section rail is a labelled <nav> ("Settings sections") distinct from the primary navigation; its active item carries aria-current.',
2140
+ "Every section has an h2, so screen-reader heading navigation mirrors the rail.",
2141
+ "Each Switch takes its row title as aria-label, since the Entity title is not a <label>.",
929
2142
  "Field auto-wires htmlFor/id, aria-describedby (description + error), aria-invalid, and required.",
930
- "The danger action is a real button labeled by its visible text \u2014 no icon-only destructive controls."
2143
+ "DestructiveActionModal is an alertdialog: focus opens on Cancel, the backdrop does not dismiss, and the confirm stays disabled until the handle is typed."
931
2144
  ]
932
2145
  };
933
2146