astryx-dracula 0.2.0 → 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 (83) hide show
  1. package/BRAND.md +30 -18
  2. package/README.md +22 -11
  3. package/USAGE.md +29 -10
  4. package/astryx-dracula.js +19 -13
  5. package/astryx-dracula.variants.d.ts +15 -0
  6. package/package.json +59 -17
  7. package/shared/auth-chrome-config.ts +18 -0
  8. package/shared/auth-chrome.tsx +25 -0
  9. package/shared/auth-copy.ts +22 -0
  10. package/shared/chaptered-doc-config.ts +92 -0
  11. package/shared/chaptered-doc.tsx +332 -0
  12. package/shared/chart-hues.ts +27 -0
  13. package/shared/chart-labels.tsx +44 -0
  14. package/shared/chart-legend.tsx +100 -0
  15. package/shared/chart-panel-style.ts +13 -0
  16. package/shared/data-bar.tsx +160 -0
  17. package/shared/gallery-image.ts +20 -0
  18. package/shared/login-demo.ts +7 -0
  19. package/shared/metric-delta.tsx +36 -0
  20. package/shared/order-desk.tsx +273 -0
  21. package/shared/revenue-chart.tsx +174 -0
  22. package/shared/scene-castle.tsx +322 -0
  23. package/shared/scene-frame.tsx +88 -0
  24. package/shared/scene-hues.ts +49 -0
  25. package/shared/scene-tile.tsx +128 -0
  26. package/shared/settings-data.ts +125 -0
  27. package/shared/settings-rows.tsx +179 -0
  28. package/shared/sparkline.tsx +94 -0
  29. package/shared/sso-icons.tsx +30 -0
  30. package/templates/ai-chat-landing.tsx +67 -32
  31. package/templates/ai-chat.tsx +41 -20
  32. package/templates/blank.tsx +5 -2
  33. package/templates/centered-hero.tsx +15 -124
  34. package/templates/classic-gallery.tsx +21 -65
  35. package/templates/contact-form.tsx +62 -40
  36. package/templates/dashboard-portfolio.tsx +146 -227
  37. package/templates/dashboard.tsx +123 -121
  38. package/templates/detail-page.tsx +101 -95
  39. package/templates/documentation-design.template.mjs +1 -1
  40. package/templates/documentation-design.tsx +85 -96
  41. package/templates/documentation-technical.template.mjs +1 -1
  42. package/templates/documentation-technical.tsx +33 -21
  43. package/templates/documentation.template.mjs +1 -1
  44. package/templates/documentation.tsx +54 -74
  45. package/templates/editor.tsx +119 -89
  46. package/templates/file-explorer.tsx +133 -84
  47. package/templates/form-two-column.tsx +62 -124
  48. package/templates/gallery-hero.tsx +17 -171
  49. package/templates/ide.tsx +27 -21
  50. package/templates/incident-console.tsx +105 -69
  51. package/templates/kanban-board.tsx +100 -41
  52. package/templates/library.template.mjs +2 -2
  53. package/templates/library.tsx +38 -41
  54. package/templates/login-card.tsx +59 -86
  55. package/templates/login-split.tsx +88 -128
  56. package/templates/login-sso.tsx +107 -68
  57. package/templates/login.tsx +108 -52
  58. package/templates/messaging-shell.tsx +172 -143
  59. package/templates/mixed-gallery.tsx +31 -130
  60. package/templates/payment-form.tsx +369 -289
  61. package/templates/product-detail.tsx +40 -57
  62. package/templates/product-gallery.tsx +31 -40
  63. package/templates/product-tour.template.mjs +6 -0
  64. package/templates/product-tour.tsx +916 -0
  65. package/templates/settings-dialog.tsx +145 -286
  66. package/templates/settings-sidebar.tsx +179 -267
  67. package/templates/settings.tsx +42 -13
  68. package/templates/shell-nav.tsx +32 -23
  69. package/templates/shell-side-nav.tsx +30 -19
  70. package/templates/shell-top-nav.tsx +81 -43
  71. package/templates/side-gallery.tsx +39 -93
  72. package/templates/table-grouped.tsx +63 -33
  73. package/templates/table-page-chart.tsx +24 -297
  74. package/templates/table-page-heatmap-status.tsx +157 -91
  75. package/templates/table-page-shoe-store-heatmap.tsx +19 -288
  76. package/templates/table-page.tsx +24 -6
  77. package/templates/table.tsx +24 -7
  78. package/templates/tech-report.template.mjs +6 -0
  79. package/templates/tech-report.tsx +1486 -0
  80. package/templates/theme-showcase.template.mjs +1 -1
  81. package/templates/theme-showcase.tsx +313 -249
  82. package/theme.css +76 -14
  83. package/tokens.css +425 -30
@@ -1,4 +1,22 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // L > (LH[divider] > H[a=center wrap] > (SI[fill] > Hd"Settings"[level=1 t=display-2]) + TY"Search") + (LP[w=260 p=2] > UL > LI*6) + (LC[p=6] > V[g=4] > (V[a=center] > TabList) + ((G[c={min:280} g=8] > (V[g=1] > Hd"Basic information"[level=2] + Tx"View and update your details"[t=body]) + (V[g=4] > TI"Username" + TI"Email address" + (H > B.primary"Save"))) + D)*3)
4
+
5
+ /**
6
+ * Settings — one scrolling page of account sections.
7
+ *
8
+ * Frame: Layout header (title + settings search) | optional 260px section nav |
9
+ * content column of three section grids, divided.
10
+ *
11
+ * Responsive contract:
12
+ * > 768px the section nav is a 260px LayoutPanel beside the content
13
+ * <= 768px the panel is dropped, the nav collapses to a centered TabList
14
+ * above the content, the header search wraps under the title, and
15
+ * every section grid falls to one column (280px floor)
16
+ *
17
+ * Seam rule: nav/content whitespace pairs use hasDivider=false (LayoutPanel
18
+ * beside LayoutContent); dialog chrome and inset panels keep their divider.
19
+ */
2
20
 
3
21
  import {useState} from 'react';
4
22
  import {useMediaQuery} from '@astryxdesign/core/hooks';
@@ -32,6 +50,12 @@ const NAV_ITEMS = [
32
50
  'API',
33
51
  ];
34
52
 
53
+ // WCAG 1.3.5 wants autocomplete on identity fields. TextInput forwards unknown
54
+ // props to the <input>, but its prop type omits input-only attributes, so the
55
+ // attribute is spread in through a widened record.
56
+ const inputAutoComplete = (value: string) =>
57
+ ({autoComplete: value}) as Record<string, string>;
58
+
35
59
  const SETTINGS_ITEMS: SearchableItem[] = [
36
60
  {id: '1', label: 'Username'},
37
61
  {id: '2', label: 'First name'},
@@ -69,17 +93,17 @@ export default function SettingsTemplate() {
69
93
  return (
70
94
  <Layout
71
95
  height="fill"
72
- contentWidth={1440}
96
+ contentWidth={1200}
73
97
  header={
74
- <LayoutHeader hasDivider>
75
- <HStack vAlign="center">
98
+ <LayoutHeader hasDivider padding={6}>
99
+ <HStack vAlign="center" wrap="wrap">
76
100
  <StackItem size="fill">
77
- <Heading level={1}>Settings</Heading>
101
+ <Heading level={1} type="display-2">Settings</Heading>
78
102
  </StackItem>
79
103
  <Typeahead
80
104
  label="Search"
81
105
  isLabelHidden
82
- placeholder="Search coven settings..."
106
+ placeholder="Search coven settings…"
83
107
  searchSource={settingsSearchSource}
84
108
  value={searchValue}
85
109
  onChange={setSearchValue}
@@ -106,7 +130,7 @@ export default function SettingsTemplate() {
106
130
  )
107
131
  }
108
132
  content={
109
- <LayoutContent padding={4}>
133
+ <LayoutContent padding={6}>
110
134
  <VStack gap={4}>
111
135
  {/* Mobile: the sidebar nav collapses to a horizontal, centered
112
136
  tab bar above the content. */}
@@ -126,9 +150,9 @@ export default function SettingsTemplate() {
126
150
  </TabList>
127
151
  </VStack>
128
152
  )}
129
- <Grid columns={{minWidth: 320}} gap={10}>
153
+ <Grid columns={{minWidth: 280}} gap={8}>
130
154
  <VStack gap={1}>
131
- <Heading level={3}>Basic information</Heading>
155
+ <Heading level={2}>Basic information</Heading>
132
156
  <Text type="body" color="secondary">
133
157
  View and update your crypt details and coven account information.
134
158
  </Text>
@@ -151,6 +175,8 @@ export default function SettingsTemplate() {
151
175
  />
152
176
  <TextInput
153
177
  label="Email address"
178
+ type="email"
179
+ {...inputAutoComplete('email')}
154
180
  value={email}
155
181
  onChange={setEmail}
156
182
  />
@@ -162,9 +188,9 @@ export default function SettingsTemplate() {
162
188
 
163
189
  <Divider />
164
190
 
165
- <Grid columns={{minWidth: 320}} gap={10}>
191
+ <Grid columns={{minWidth: 280}} gap={8}>
166
192
  <VStack gap={1}>
167
- <Heading level={3}>Change password</Heading>
193
+ <Heading level={2}>Change password</Heading>
168
194
  <Text type="body" color="secondary">
169
195
  Update your password to keep your coffin sealed.
170
196
  </Text>
@@ -173,18 +199,21 @@ export default function SettingsTemplate() {
173
199
  <TextInput
174
200
  label="Verify current password"
175
201
  type="password"
202
+ {...inputAutoComplete('current-password')}
176
203
  value={currentPw}
177
204
  onChange={setCurrentPw}
178
205
  />
179
206
  <TextInput
180
207
  label="New password"
181
208
  type="password"
209
+ {...inputAutoComplete('new-password')}
182
210
  value={newPw}
183
211
  onChange={setNewPw}
184
212
  />
185
213
  <TextInput
186
214
  label="Confirm password"
187
215
  type="password"
216
+ {...inputAutoComplete('new-password')}
188
217
  value={confirmPw}
189
218
  onChange={setConfirmPw}
190
219
  />
@@ -196,14 +225,14 @@ export default function SettingsTemplate() {
196
225
 
197
226
  <Divider />
198
227
 
199
- <Grid columns={{minWidth: 320}} gap={10}>
228
+ <Grid columns={{minWidth: 280}} gap={8}>
200
229
  <VStack gap={1}>
201
- <Heading level={3}>Advanced settings</Heading>
230
+ <Heading level={2}>Advanced settings</Heading>
202
231
  <Text type="body" color="secondary">
203
232
  Configure detailed coven preferences and warding options.
204
233
  </Text>
205
234
  </VStack>
206
- <VStack gap={5}>
235
+ <VStack gap={4}>
207
236
  <CheckboxInput
208
237
  label="Data Export Access"
209
238
  description="Allow export of personal data and backups."
@@ -1,4 +1,8 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // A[cp=0 @topNav=(TN) @sideNav=(SN > TL)] > L > (LH[divider] > H[g=2 wrap] > C.muted[p=0]*3) + (LC[p=6] > V[g=2] > (H[g=3 a=center] > C.muted[p=0] + C.muted[p=0])*3 + C.muted[p=0] + (H[g=3 a=center] > C.muted[p=0] + C.muted[p=0])*4 + C.muted[p=0] + (H[g=3 a=center] > C.muted[p=0] + C.muted[p=0])*5)
4
+ // Note: skeleton placeholders carry no Hd by design (skill skeleton-shell
5
+ // exemption) — routed pages supply the h1.
2
6
 
3
7
  import {Fragment, useState, useMemo, useEffect} from 'react';
4
8
  import {AppShell} from '@astryxdesign/core/AppShell';
@@ -75,9 +79,9 @@ const MENUS: {label: string; groups: MenuEntry[][]}[] = [
75
79
  ['New Window', '⇧⌘N'],
76
80
  ],
77
81
  [
78
- ['Open...', '⌘O'],
82
+ ['Open…', '⌘O'],
79
83
  ['Save', '⌘S'],
80
- ['Save As...', '⇧⌘S'],
84
+ ['Save As…', '⇧⌘S'],
81
85
  ],
82
86
  [['Close Editor', '⌘W']],
83
87
  ],
@@ -139,20 +143,20 @@ const MENUS: {label: string; groups: MenuEntry[][]}[] = [
139
143
  ];
140
144
 
141
145
  const CODE_LINES = [
142
- '38%',
143
- '62%',
144
- '54%',
145
- '0%',
146
- '46%',
147
- '70%',
148
- '58%',
149
- '34%',
150
- '0%',
151
- '50%',
152
- '66%',
153
- '42%',
154
- '60%',
155
- '28%',
146
+ {id: 'line-1', width: '38%'},
147
+ {id: 'line-2', width: '62%'},
148
+ {id: 'line-3', width: '54%'},
149
+ {id: 'line-4', width: '0%'},
150
+ {id: 'line-5', width: '46%'},
151
+ {id: 'line-6', width: '70%'},
152
+ {id: 'line-7', width: '58%'},
153
+ {id: 'line-8', width: '34%'},
154
+ {id: 'line-9', width: '0%'},
155
+ {id: 'line-10', width: '50%'},
156
+ {id: 'line-11', width: '66%'},
157
+ {id: 'line-12', width: '42%'},
158
+ {id: 'line-13', width: '60%'},
159
+ {id: 'line-14', width: '28%'},
156
160
  ];
157
161
 
158
162
  const EDITOR_TABS = ['AppShell.tsx', 'TopNav.tsx', 'theme.ts'];
@@ -201,8 +205,11 @@ export default function ShellNav() {
201
205
  button={{label: menu.label, variant: 'ghost', size: 'sm'}}
202
206
  hasChevron={false}
203
207
  menuWidth={MENU_WIDTH}>
208
+ {/* Keyed by the group's first label, not its position:
209
+ an index key remounts every group when the menu
210
+ data shifts. `gi` only decides the divider. */}
204
211
  {menu.groups.map((group, gi) => (
205
- <Fragment key={gi}>
212
+ <Fragment key={group[0][0]}>
206
213
  {gi > 0 && <Divider />}
207
214
  {group.map(([label, shortcut]) => (
208
215
  <DropdownMenuItem
@@ -247,7 +254,8 @@ export default function ShellNav() {
247
254
  height="fill"
248
255
  header={
249
256
  <LayoutHeader hasDivider padding={6}>
250
- <HStack gap={2}>
257
+ {/* Wraps rather than clipping: three 132px tabs need ~410px. */}
258
+ <HStack gap={2} wrap="wrap">
251
259
  {EDITOR_TABS.map(tab => (
252
260
  <Card
253
261
  key={tab}
@@ -262,18 +270,19 @@ export default function ShellNav() {
262
270
  }
263
271
  content={
264
272
  <LayoutContent padding={6}>
273
+ {/* Skeleton chrome only — the open file supplies the h1. */}
265
274
  <VStack gap={2}>
266
- {CODE_LINES.map((width, i) =>
267
- width === '0%' ? (
275
+ {CODE_LINES.map(line =>
276
+ line.width === '0%' ? (
268
277
  <Card
269
- key={i}
278
+ key={line.id}
270
279
  variant="muted"
271
280
  padding={0}
272
281
  width={1}
273
282
  height={14}
274
283
  />
275
284
  ) : (
276
- <HStack key={i} gap={3} vAlign="center">
285
+ <HStack key={line.id} gap={3} vAlign="center">
277
286
  <Card
278
287
  variant="muted"
279
288
  padding={0}
@@ -283,7 +292,7 @@ export default function ShellNav() {
283
292
  <Card
284
293
  variant="muted"
285
294
  padding={0}
286
- width={width}
295
+ width={line.width}
287
296
  height={14}
288
297
  />
289
298
  </HStack>
@@ -1,4 +1,8 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // A[cp=0 @sideNav=(SN > SNI"New chat" + SNI"Search" + SNI"Library" + D + (SNI"Personal" > SNI"Full-moon rite planning")*3)] > L > (LC[p=6] > V[g=5] > (H > C.muted[p=0])*4) + (LF > TI"Message Night Owl")
4
+ // Note: skeleton placeholders carry no Hd by design (skill skeleton-shell
5
+ // exemption) — routed pages supply the h1.
2
6
 
3
7
  import {useState} from 'react';
4
8
  import {AppShell} from '@astryxdesign/core/AppShell';
@@ -48,39 +52,43 @@ const WORKSPACES: Workspace[] = [
48
52
  icon: User,
49
53
  chats: [
50
54
  {
51
- label: 'Weekend trip planning',
55
+ label: 'Full-moon rite planning',
52
56
  status: 'success',
53
57
  statusLabel: 'Active',
54
58
  },
55
59
  {
56
- label: 'Recipe ideas for the week',
60
+ label: 'Potion recipes for the week',
57
61
  status: 'neutral',
58
62
  statusLabel: 'Idle',
59
63
  },
60
64
  {
61
- label: 'Book recommendations',
65
+ label: 'Grimoire recommendations',
62
66
  status: 'warning',
63
67
  statusLabel: 'Needs review',
64
68
  },
65
- {label: 'Home workout plan', status: 'neutral', statusLabel: 'Idle'},
69
+ {label: 'Crypt workout plan', status: 'neutral', statusLabel: 'Idle'},
66
70
  ],
67
71
  },
68
72
  {
69
- name: 'Acme Corp',
73
+ name: 'Night Watch',
70
74
  icon: Building2,
71
75
  chats: [
72
- {label: 'Q3 roadmap draft', status: 'accent', statusLabel: 'In progress'},
73
76
  {
74
- label: 'Customer onboarding flow',
77
+ label: 'Crypt expansion draft',
78
+ status: 'info',
79
+ statusLabel: 'In progress',
80
+ },
81
+ {
82
+ label: 'Neonate welcoming rite',
75
83
  status: 'success',
76
84
  statusLabel: 'Active',
77
85
  },
78
86
  {
79
- label: 'Pricing strategy review',
87
+ label: 'Tribute ledger review',
80
88
  status: 'warning',
81
89
  statusLabel: 'Needs review',
82
90
  },
83
- {label: 'Standup summary', status: 'neutral', statusLabel: 'Idle'},
91
+ {label: 'Night-watch summary', status: 'neutral', statusLabel: 'Idle'},
84
92
  ],
85
93
  },
86
94
  {
@@ -88,12 +96,12 @@ const WORKSPACES: Workspace[] = [
88
96
  icon: CodeXml,
89
97
  chats: [
90
98
  {
91
- label: 'StyleX migration notes',
92
- status: 'accent',
99
+ label: 'Coven theme migration notes',
100
+ status: 'info',
93
101
  statusLabel: 'In progress',
94
102
  },
95
103
  {
96
- label: 'Skeleton loading states',
104
+ label: 'Crypt loading states',
97
105
  status: 'success',
98
106
  statusLabel: 'Active',
99
107
  },
@@ -103,16 +111,18 @@ const WORKSPACES: Workspace[] = [
103
111
  },
104
112
  ];
105
113
 
106
- const SELECTED_CHAT = 'StyleX migration notes';
114
+ const SELECTED_CHAT = 'Coven theme migration notes';
107
115
  // Same-route hash: demo links stay focusable anchors without escaping the
108
116
  // template through the hash router (bare "#" would drop back to the home page).
109
117
  const SELF_HASH = '#/templates/shell-side-nav';
110
118
 
119
+ // `id` carries the React key: the bubbles are positional placeholders, and an
120
+ // index key would remount every row below an inserted one.
111
121
  const MESSAGES = [
112
- {role: 'assistant', width: '78%', height: 104},
113
- {role: 'user', width: '48%', height: 48},
114
- {role: 'assistant', width: '64%', height: 132},
115
- {role: 'user', width: '38%', height: 40},
122
+ {id: 'context', role: 'assistant', width: '78%', height: 104},
123
+ {id: 'follow-up', role: 'user', width: '48%', height: 48},
124
+ {id: 'answer', role: 'assistant', width: '64%', height: 132},
125
+ {id: 'reply', role: 'user', width: '38%', height: 40},
116
126
  ];
117
127
 
118
128
  function ConversationItem({
@@ -204,10 +214,11 @@ export default function ShellSideNav() {
204
214
  contentWidth={768}
205
215
  content={
206
216
  <LayoutContent padding={6}>
217
+ {/* Skeleton chrome only — the routed conversation supplies the h1. */}
207
218
  <VStack gap={5}>
208
- {MESSAGES.map((message, mi) => (
219
+ {MESSAGES.map(message => (
209
220
  <HStack
210
- key={mi}
221
+ key={message.id}
211
222
  hAlign={message.role === 'assistant' ? 'start' : 'end'}>
212
223
  <Card
213
224
  variant="muted"
@@ -1,4 +1,8 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // A[cp=6 @topNav=(TN > TNI"Shop" + TNI"Brands" + TNI"Sale" + TNI"Service")] > Ctr[axis=horizontal] > S[mw=1100 w=100% p=0] > V[g=10] > C.muted[p=0 w=100% h=360] + (V[g=4] > C.muted[p=0 w=200 h=24] + (G[c={min:160} g=4] > (V[g=2] > C.muted[p=0 w=100% h=120] + C.muted[p=0 w=60% h=14])*6))*3
4
+ // Note: skeleton placeholders carry no Hd by design (skill skeleton-shell
5
+ // exemption) — routed pages supply the h1.
2
6
 
3
7
  import type {CSSProperties} from 'react';
4
8
  import {AppShell} from '@astryxdesign/core/AppShell';
@@ -19,7 +23,9 @@ import {IconButton} from '@astryxdesign/core/IconButton';
19
23
  import {Button} from '@astryxdesign/core/Button';
20
24
  import {Badge} from '@astryxdesign/core/Badge';
21
25
  import {Card} from '@astryxdesign/core/Card';
26
+ import {Center} from '@astryxdesign/core/Center';
22
27
  import {Grid} from '@astryxdesign/core/Grid';
28
+ import {Section} from '@astryxdesign/core/Section';
23
29
  import {Stack, VStack} from '@astryxdesign/core/Stack';
24
30
  import {
25
31
  ShoppingBag,
@@ -42,7 +48,7 @@ import {
42
48
  } from 'lucide-react';
43
49
 
44
50
  // Cap + center the page body so wide screens show whitespace gutters.
45
- const contentMax: CSSProperties = {maxWidth: 1100, marginInline: 'auto'};
51
+ const CONTENT_MAX_WIDTH = 1100;
46
52
  // Same-route hash: demo links stay focusable anchors without escaping the
47
53
  // template through the hash router (bare "#" would drop back to the home page).
48
54
  const SELF_HASH = '#/templates/shell-top-nav';
@@ -50,51 +56,60 @@ const SELF_HASH = '#/templates/shell-top-nav';
50
56
  // Brands size to their own content (different widths); since both anchor to
51
57
  // the centered nav, switching between them resizes the panel — which reads
52
58
  // as flashing/jumping. Fixed item + featured widths make the panels
53
- // pixel-identical so the transition is seamless.
59
+ // pixel-identical so the transition is seamless. The pair needs roughly
60
+ // 800px of clear space, so narrower viewports fall back to natural-width
61
+ // single columns (see MegaItems) instead of painting past the edge.
54
62
  const megaItems: CSSProperties = {gridColumn: '1 / -1', width: 520};
55
63
  const megaFeatured: CSSProperties = {width: 240};
56
64
 
65
+ // Three identical shelves; the ids keep React keys stable if they reorder.
66
+ const SHELVES = ['new-in', 'featured', 'sale'];
67
+
57
68
  type MegaItem = {name: string; tagline: string; icon: IconType};
58
69
 
59
70
  // Shop and Brands each render 8 items — the mega menu's built-in 2-column grid
60
71
  // lays them out as 2 columns × 4 rows, alongside a featured card.
61
72
  const SHOP_ITEMS: MegaItem[] = [
62
- {name: 'New Arrivals', tagline: 'The latest drops', icon: Sparkles},
63
- {name: 'Womenswear', tagline: 'Dresses, knitwear & more', icon: SwatchBook},
64
- {name: 'Menswear', tagline: 'Shirts, tailoring & more', icon: Tag},
65
- {name: 'Home', tagline: 'Bedding, lighting & décor', icon: House},
73
+ {name: 'New Moon Arrivals', tagline: 'Fresh from the kiln', icon: Sparkles},
74
+ {
75
+ name: 'Gowns & Cloaks',
76
+ tagline: 'Dresses, knitwear & moonlit layers',
77
+ icon: SwatchBook,
78
+ },
79
+ {name: 'Doublets & Tailoring', tagline: 'Shirts, cloaks & more', icon: Tag},
80
+ {name: 'Keep & Crypt', tagline: 'Bedding, candlelight & décor', icon: House},
66
81
  {
67
- name: 'Beauty',
68
- tagline: 'Skincare, fragrance & makeup',
82
+ name: 'Potions',
83
+ tagline: 'Salves, scents & powders',
69
84
  icon: Smile,
70
85
  },
71
86
  {
72
- name: 'Accessories',
73
- tagline: 'Bags, hats & sunglasses',
87
+ name: 'Relics',
88
+ tagline: 'Satchels, hats & moonshades',
74
89
  icon: ShoppingBag,
75
90
  },
76
91
  {name: 'Sale', tagline: 'Up to 50% off', icon: BadgePercent},
77
- {name: 'Gift Cards', tagline: 'The perfect present', icon: Gift},
92
+ {name: 'Gift Tokens', tagline: 'The perfect tribute', icon: Gift},
78
93
  ];
79
94
 
80
95
  const BRAND_ITEMS: MegaItem[] = [
81
- {name: 'Aether', tagline: 'Performance essentials', icon: Sparkles},
82
- {name: 'Northwind', tagline: 'Outdoor & technical', icon: Cloud},
83
- {name: 'Loomwell', tagline: 'Everyday knitwear', icon: Zap},
84
- {name: 'Verdant', tagline: 'Sustainable basics', icon: Sun},
85
- {name: 'Studio Mara', tagline: 'Modern tailoring', icon: Star},
86
- {name: 'Atelier Kos', tagline: 'Limited ateliers', icon: Flame},
87
- {name: 'Rue & Co', tagline: 'City streetwear', icon: Globe},
88
- {name: 'Halden', tagline: 'Minimal staples', icon: Moon},
96
+ {name: 'Aether', tagline: 'Moonlit essentials', icon: Sparkles},
97
+ {name: 'Northcrypt', tagline: 'Cold-weather & travel', icon: Cloud},
98
+ {name: 'Loomwell', tagline: 'Everyday knitwear, coven-stitched', icon: Zap},
99
+ {name: 'Verdant', tagline: 'Earth-kept basics', icon: Sun},
100
+ {name: 'Studio Mara', tagline: 'Modern tailoring for the night', icon: Star},
101
+ {name: 'Atelier Kos', tagline: 'Small-batch ateliers', icon: Flame},
102
+ {name: 'Rue & Co', tagline: 'Old-town streetwear', icon: Globe},
103
+ {name: 'Halden', tagline: 'Spare, lasting staples', icon: Moon},
89
104
  ];
90
105
 
91
106
  const CATEGORY_TILES = [
92
- 'New Arrivals',
93
- 'Womenswear',
94
- 'Menswear',
95
- 'Home & Living',
96
- 'Beauty',
97
- 'Accessories',
107
+ 'New Moon Arrivals',
108
+ 'Gowns & Cloaks',
109
+ 'Doublets & Tailoring',
110
+ 'Keep & Crypt',
111
+ 'Potions',
112
+ 'Relics',
98
113
  ];
99
114
 
100
115
  // Wraps the 8 items in a fixed-width 2-column grid so every mega menu's item
@@ -102,9 +117,12 @@ const CATEGORY_TILES = [
102
117
  function MegaItems({items}: {items: MegaItem[]}) {
103
118
  // In the mobile drawer the fixed 520px panel would overflow the ~350px
104
119
  // drawer, and the 2-column grid overlaps item text there — so the drawer
105
- // gets a natural-width single column. Desktop keeps the lock.
120
+ // gets a natural-width single column. The same goes for viewports too
121
+ // narrow to seat the locked desktop panel, which would otherwise paint
122
+ // past the edge: below 1024px the popover keeps the natural-width column.
106
123
  const isDrawer = useTopNavRenderMode() === 'drawer';
107
- if (isDrawer) {
124
+ const isCompact = useMediaQuery('(max-width: 1024px)');
125
+ if (isDrawer || isCompact) {
108
126
  return (
109
127
  <VStack gap={1}>
110
128
  {items.map(item => (
@@ -144,8 +162,9 @@ function MegaFeatured(props: {
144
162
  linkHref: string;
145
163
  }) {
146
164
  const isDrawer = useTopNavRenderMode() === 'drawer';
165
+ const isCompact = useMediaQuery('(max-width: 1024px)');
147
166
  return (
148
- <Stack style={isDrawer ? undefined : megaFeatured}>
167
+ <Stack style={isDrawer || isCompact ? undefined : megaFeatured}>
149
168
  <TopNavMegaMenuFeaturedCard {...props} />
150
169
  </Stack>
151
170
  );
@@ -221,23 +240,42 @@ export default function ShellTopNav() {
221
240
  }
222
241
  />
223
242
  }>
224
- <VStack gap={10} style={contentMax}>
225
- <Card variant="muted" padding={0} width="100%" height={360} />
243
+ <Center axis="horizontal">
244
+ <Section
245
+ variant="transparent"
246
+ maxWidth={CONTENT_MAX_WIDTH}
247
+ width="100%"
248
+ padding={0}>
249
+ <VStack gap={10}>
250
+ {/* Skeleton shelves only — the routed collection supplies the h1. */}
251
+ <Card variant="muted" padding={0} width="100%" height={360} />
226
252
 
227
- {[0, 1, 2].map(section => (
228
- <VStack key={section} gap={4}>
229
- <Card variant="muted" padding={0} width={200} height={24} />
230
- <Grid columns={{minWidth: 160, repeat: 'fit'}} gap={4}>
231
- {CATEGORY_TILES.map(tile => (
232
- <VStack key={tile} gap={2}>
233
- <Card variant="muted" padding={0} width="100%" height={120} />
234
- <Card variant="muted" padding={0} width="60%" height={14} />
235
- </VStack>
236
- ))}
237
- </Grid>
253
+ {SHELVES.map(shelf => (
254
+ <VStack key={shelf} gap={4}>
255
+ <Card variant="muted" padding={0} width={200} height={24} />
256
+ <Grid columns={{minWidth: 160, repeat: 'fit'}} gap={4}>
257
+ {CATEGORY_TILES.map(tile => (
258
+ <VStack key={tile} gap={2}>
259
+ <Card
260
+ variant="muted"
261
+ padding={0}
262
+ width="100%"
263
+ height={120}
264
+ />
265
+ <Card
266
+ variant="muted"
267
+ padding={0}
268
+ width="60%"
269
+ height={14}
270
+ />
271
+ </VStack>
272
+ ))}
273
+ </Grid>
274
+ </VStack>
275
+ ))}
238
276
  </VStack>
239
- ))}
240
- </VStack>
277
+ </Section>
278
+ </Center>
241
279
  </AppShell>
242
280
  );
243
281
  }