astryx-dracula 0.5.0 → 0.6.2

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 (129) hide show
  1. package/BRAND.md +3 -3
  2. package/README.md +7 -4
  3. package/USAGE.md +49 -49
  4. package/astryx-dracula.d.ts +2 -2
  5. package/astryx-dracula.js +25 -9
  6. package/astryx-dracula.variants.d.ts +2 -2
  7. package/astryx.integration.mjs +1 -1
  8. package/icons.mjs +21 -17
  9. package/icons.tsx +69 -62
  10. package/package.json +125 -120
  11. package/shared/auth-chrome-config.ts +6 -6
  12. package/shared/auth-chrome.tsx +143 -144
  13. package/shared/auth-copy.ts +14 -14
  14. package/shared/chaptered-doc-config.ts +48 -51
  15. package/shared/chaptered-doc.tsx +267 -289
  16. package/shared/chart-hues.ts +25 -10
  17. package/shared/chart-labels.tsx +31 -30
  18. package/shared/chart-legend.tsx +55 -56
  19. package/shared/chart-panel-style.ts +3 -3
  20. package/shared/data-bar.tsx +97 -102
  21. package/shared/format-money.ts +1 -1
  22. package/shared/gallery-image.ts +6 -6
  23. package/shared/login-demo.ts +1 -1
  24. package/shared/metric-card.tsx +101 -105
  25. package/shared/metric-delta.tsx +28 -27
  26. package/shared/order-desk.tsx +205 -216
  27. package/shared/revenue-chart.tsx +145 -165
  28. package/shared/scene-castle.tsx +206 -291
  29. package/shared/scene-frame.tsx +33 -32
  30. package/shared/scene-hues.ts +17 -17
  31. package/shared/scene-tile.tsx +88 -92
  32. package/shared/settings-data.ts +119 -125
  33. package/shared/settings-rows.tsx +122 -159
  34. package/shared/sparkline.tsx +75 -88
  35. package/shared/sso-icons.tsx +10 -22
  36. package/shared/stars.tsx +16 -25
  37. package/templates/ai-chat-landing.template.mjs +5 -4
  38. package/templates/ai-chat-landing.tsx +473 -460
  39. package/templates/ai-chat.template.mjs +5 -4
  40. package/templates/ai-chat.tsx +543 -612
  41. package/templates/blank.template.mjs +5 -4
  42. package/templates/blank.tsx +18 -14
  43. package/templates/centered-hero.template.mjs +5 -4
  44. package/templates/centered-hero.tsx +55 -68
  45. package/templates/classic-gallery.template.mjs +5 -4
  46. package/templates/classic-gallery.tsx +97 -104
  47. package/templates/contact-form.template.mjs +5 -4
  48. package/templates/contact-form.tsx +247 -282
  49. package/templates/dashboard-portfolio.template.mjs +5 -4
  50. package/templates/dashboard-portfolio.tsx +717 -548
  51. package/templates/dashboard.template.mjs +5 -4
  52. package/templates/dashboard.tsx +596 -567
  53. package/templates/detail-page.template.mjs +5 -4
  54. package/templates/detail-page.tsx +546 -603
  55. package/templates/documentation-design.template.mjs +5 -4
  56. package/templates/documentation-design.tsx +231 -243
  57. package/templates/documentation-technical.template.mjs +5 -4
  58. package/templates/documentation-technical.tsx +236 -239
  59. package/templates/documentation.template.mjs +5 -4
  60. package/templates/documentation.tsx +267 -279
  61. package/templates/editor.template.mjs +5 -4
  62. package/templates/editor.tsx +844 -877
  63. package/templates/file-explorer.template.mjs +5 -4
  64. package/templates/file-explorer.tsx +566 -590
  65. package/templates/form-two-column.template.mjs +5 -4
  66. package/templates/form-two-column.tsx +195 -210
  67. package/templates/gallery-hero.template.mjs +5 -4
  68. package/templates/gallery-hero.tsx +93 -114
  69. package/templates/ide.template.mjs +5 -4
  70. package/templates/ide.tsx +407 -453
  71. package/templates/incident-console.template.mjs +5 -4
  72. package/templates/incident-console.tsx +593 -575
  73. package/templates/kanban-board.template.mjs +5 -4
  74. package/templates/kanban-board.tsx +665 -683
  75. package/templates/library.template.mjs +5 -4
  76. package/templates/library.tsx +409 -430
  77. package/templates/login-card.template.mjs +5 -4
  78. package/templates/login-card.tsx +63 -72
  79. package/templates/login-split.template.mjs +5 -4
  80. package/templates/login-split.tsx +192 -193
  81. package/templates/login-sso.template.mjs +5 -4
  82. package/templates/login-sso.tsx +257 -258
  83. package/templates/login.template.mjs +5 -4
  84. package/templates/login.tsx +53 -49
  85. package/templates/messaging-shell.template.mjs +5 -4
  86. package/templates/messaging-shell.tsx +556 -574
  87. package/templates/mixed-gallery.template.mjs +5 -4
  88. package/templates/mixed-gallery.tsx +60 -58
  89. package/templates/payment-form.template.mjs +5 -4
  90. package/templates/payment-form.tsx +882 -991
  91. package/templates/product-detail.template.mjs +5 -4
  92. package/templates/product-detail.tsx +261 -272
  93. package/templates/product-gallery.template.mjs +5 -4
  94. package/templates/product-gallery.tsx +126 -137
  95. package/templates/product-tour.template.mjs +5 -4
  96. package/templates/product-tour.tsx +741 -773
  97. package/templates/settings-dialog.template.mjs +5 -4
  98. package/templates/settings-dialog.tsx +688 -717
  99. package/templates/settings-sidebar.template.mjs +5 -4
  100. package/templates/settings-sidebar.tsx +762 -807
  101. package/templates/settings.template.mjs +5 -4
  102. package/templates/settings.tsx +289 -304
  103. package/templates/shell-nav.template.mjs +5 -4
  104. package/templates/shell-nav.tsx +212 -219
  105. package/templates/shell-side-nav.template.mjs +5 -4
  106. package/templates/shell-side-nav.tsx +135 -157
  107. package/templates/shell-top-nav.template.mjs +5 -4
  108. package/templates/shell-top-nav.tsx +182 -203
  109. package/templates/side-gallery.template.mjs +5 -4
  110. package/templates/side-gallery.tsx +85 -90
  111. package/templates/table-grouped.template.mjs +5 -4
  112. package/templates/table-grouped.tsx +1000 -1053
  113. package/templates/table-page-chart.template.mjs +5 -4
  114. package/templates/table-page-chart.tsx +400 -398
  115. package/templates/table-page-heatmap-status.template.mjs +5 -4
  116. package/templates/table-page-heatmap-status.tsx +483 -512
  117. package/templates/table-page-shoe-store-heatmap.template.mjs +5 -4
  118. package/templates/table-page-shoe-store-heatmap.tsx +748 -746
  119. package/templates/table-page.template.mjs +5 -4
  120. package/templates/table-page.tsx +396 -405
  121. package/templates/table.template.mjs +5 -4
  122. package/templates/table.tsx +92 -93
  123. package/templates/tech-report.template.mjs +5 -4
  124. package/templates/tech-report.tsx +1316 -1304
  125. package/templates/theme-showcase.template.mjs +5 -4
  126. package/templates/theme-showcase.tsx +871 -951
  127. package/theme.css +35 -77
  128. package/theme.css.d.ts +9 -0
  129. package/tokens.css +373 -380
@@ -1,46 +1,40 @@
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
2
+ // XLE (canonical structure of the tree below; the CLI had no XLE validator
3
+ // after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
4
+ // human drift check — update it with the file, never alone):
5
+ // A.surface[cp=6 @topNav=(TN > (Hd{TopNavHeading}"Nocturne") + (TNI{TopNavMegaMenu}"Shop" > (TNI{TopNavMegaMenuItem}*4 + C{TopNavMegaMenuFeaturedCard}"The Midnight Edit") + TNI{TopNavMegaMenu}"Brands" > (TNI{TopNavMegaMenuItem}*4 + C{TopNavMegaMenuFeaturedCard}"Meet Studio Mara") + TNI"Sale" + TNI"Service") + (IB"Search products" + B.ghost"Sign in" + B.primary"Checkout"))] > Ctr[axis=horizontal] > S.transparent[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])*4))*3
4
6
  // Note: skeleton placeholders carry no Hd by design (skill skeleton-shell
5
7
  // exemption) — routed pages supply the h1.
6
8
 
7
- import {AppShell} from '@astryxdesign/core/AppShell';
8
- import {useMediaQuery} from '@astryxdesign/core/hooks';
9
+ import { AppShell } from "@astryxdesign/core/AppShell";
10
+ import { Badge } from "@astryxdesign/core/Badge";
11
+ import { Button } from "@astryxdesign/core/Button";
12
+ import { Card } from "@astryxdesign/core/Card";
13
+ import { Center } from "@astryxdesign/core/Center";
14
+ import { Grid } from "@astryxdesign/core/Grid";
15
+ import { useMediaQuery } from "@astryxdesign/core/hooks";
16
+ import type { IconType } from "@astryxdesign/core/Icon";
17
+ import { Icon } from "@astryxdesign/core/Icon";
18
+ import { IconButton } from "@astryxdesign/core/IconButton";
19
+ import { NavIcon } from "@astryxdesign/core/NavIcon";
20
+ import { Section } from "@astryxdesign/core/Section";
21
+ import { Stack, VStack } from "@astryxdesign/core/Stack";
9
22
  import {
10
- TopNav,
11
- TopNavHeading,
12
- TopNavItem,
13
- TopNavMegaMenu,
14
- TopNavMegaMenuItem,
15
- TopNavMegaMenuFeaturedCard,
16
- useTopNavRenderMode,
17
- } from '@astryxdesign/core/TopNav';
18
- import {NavIcon} from '@astryxdesign/core/NavIcon';
19
- import {Icon} from '@astryxdesign/core/Icon';
20
- import type {IconType} from '@astryxdesign/core/Icon';
21
- import {IconButton} from '@astryxdesign/core/IconButton';
22
- import {Button} from '@astryxdesign/core/Button';
23
- import {Badge} from '@astryxdesign/core/Badge';
24
- import {Card} from '@astryxdesign/core/Card';
25
- import {Center} from '@astryxdesign/core/Center';
26
- import {Grid} from '@astryxdesign/core/Grid';
27
- import {Section} from '@astryxdesign/core/Section';
28
- import {Stack, VStack} from '@astryxdesign/core/Stack';
29
- import {
30
- ShoppingBag,
31
- ShoppingCart,
32
- Search,
33
- Sparkles,
34
- SwatchBook,
35
- Tag,
36
- House,
37
- } from 'lucide-react';
23
+ TopNav,
24
+ TopNavHeading,
25
+ TopNavItem,
26
+ TopNavMegaMenu,
27
+ TopNavMegaMenuFeaturedCard,
28
+ TopNavMegaMenuItem,
29
+ useTopNavRenderMode,
30
+ } from "@astryxdesign/core/TopNav";
31
+ import { House, Search, ShoppingBag, ShoppingCart, Sparkles, SwatchBook, Tag } from "lucide-react";
38
32
 
39
33
  // Cap + center the page body so wide screens show whitespace gutters.
40
34
  const CONTENT_MAX_WIDTH = 1100;
41
35
  // Same-route hash: demo links stay focusable anchors without escaping the
42
36
  // template through the hash router (bare "#" would drop back to the home page).
43
- const SELF_HASH = '#/templates/shell-top-nav';
37
+ const SELF_HASH = "#/templates/shell-top-nav";
44
38
  // Lock both mega-menu panels to an identical size. Without fixed widths Shop
45
39
  // and Brands size to their own content (different widths); since both anchor
46
40
  // to the centered nav, switching between them resizes the panel — which reads
@@ -49,200 +43,185 @@ const SELF_HASH = '#/templates/shell-top-nav';
49
43
  // MegaItems) instead of painting past the edge.
50
44
 
51
45
  // Three identical shelves; the ids keep React keys stable if they reorder.
52
- const SHELVES = ['new-in', 'featured', 'sale'];
46
+ const SHELVES = ["new-in", "featured", "sale"];
53
47
 
54
- type MegaItem = {name: string; tagline: string; icon: IconType};
48
+ type MegaItem = { name: string; tagline: string; icon: IconType };
55
49
 
56
50
  // Shop and Brands each render 4 items — the mega menu's built-in 2-column
57
51
  // grid lays them out as 2 columns × 2 rows, alongside a featured card.
58
52
  const SHOP_ITEMS: MegaItem[] = [
59
- {name: 'New Moon Arrivals', tagline: 'Fresh from the kiln', icon: Sparkles},
60
- {
61
- name: 'Gowns & Cloaks',
62
- tagline: 'Dresses, knitwear & moonlit layers',
63
- icon: SwatchBook,
64
- },
65
- {name: 'Doublets & Tailoring', tagline: 'Shirts, cloaks & more', icon: Tag},
66
- {name: 'Keep & Crypt', tagline: 'Bedding, candlelight & décor', icon: House},
53
+ { name: "New Moon Arrivals", tagline: "Fresh from the kiln", icon: Sparkles },
54
+ {
55
+ name: "Gowns & Cloaks",
56
+ tagline: "Dresses, knitwear & moonlit layers",
57
+ icon: SwatchBook,
58
+ },
59
+ { name: "Doublets & Tailoring", tagline: "Shirts, cloaks & more", icon: Tag },
60
+ { name: "Keep & Crypt", tagline: "Bedding, candlelight & décor", icon: House },
67
61
  ];
68
62
 
69
63
  const BRAND_ITEMS: MegaItem[] = [
70
- {name: 'Aether', tagline: 'Moonlit essentials', icon: Sparkles},
71
- {name: 'Loomwell', tagline: 'Everyday knitwear, coven-stitched', icon: Tag},
72
- {name: 'Verdant', tagline: 'Earth-kept basics', icon: House},
73
- {name: 'Studio Mara', tagline: 'Modern tailoring for the night', icon: SwatchBook},
64
+ { name: "Aether", tagline: "Moonlit essentials", icon: Sparkles },
65
+ { name: "Loomwell", tagline: "Everyday knitwear, coven-stitched", icon: Tag },
66
+ { name: "Verdant", tagline: "Earth-kept basics", icon: House },
67
+ { name: "Studio Mara", tagline: "Modern tailoring for the night", icon: SwatchBook },
74
68
  ];
75
69
 
76
70
  // Shelf tiles mirror the Shop menu so the page previews what the menu links.
77
- const CATEGORY_TILES = SHOP_ITEMS.map(item => item.name);
71
+ const CATEGORY_TILES = SHOP_ITEMS.map((item) => item.name);
78
72
 
79
73
  // Both panels share one item renderer so the drawer's single column and the
80
74
  // desktop popover's fixed-width grid stay in lockstep. Fixed widths keep the
81
75
  // two panels pixel-identical (no resize flash when switching menus); the
82
76
  // drawer and narrow viewports fall back to a natural-width column so the
83
77
  // locked 520px panel never paints past the edge.
84
- function MegaItems({items}: {items: MegaItem[]}) {
85
- // In the mobile drawer the fixed 520px panel would overflow the ~350px
86
- // drawer, and the 2-column grid overlaps item text there — so the drawer
87
- // gets a natural-width single column. The same goes for viewports too
88
- // narrow to seat the locked desktop panel, which would otherwise paint
89
- // past the edge: below 1024px the popover keeps the natural-width column.
90
- const isDrawer = useTopNavRenderMode() === 'drawer';
91
- const isCompact = useMediaQuery('(max-width: 1024px)');
92
- if (isDrawer || isCompact) {
93
- return (
94
- <VStack gap={1}>
95
- {items.map(item => (
96
- <TopNavMegaMenuItem
97
- key={item.name}
98
- title={item.name}
99
- description={item.tagline}
100
- icon={<Icon icon={item.icon} size="md" color="secondary" />}
101
- href={SELF_HASH}
102
- />
103
- ))}
104
- </VStack>
105
- );
106
- }
107
- return (
108
- <Stack style={{gridColumn: '1 / -1', width: 520}}>
109
- <Grid columns={2} gap={2}>
110
- {items.map(item => (
111
- <TopNavMegaMenuItem
112
- key={item.name}
113
- title={item.name}
114
- description={item.tagline}
115
- icon={<Icon icon={item.icon} size="md" color="secondary" />}
116
- href={SELF_HASH}
117
- />
118
- ))}
119
- </Grid>
120
- </Stack>
121
- );
78
+ function MegaItems({ items }: { items: MegaItem[] }) {
79
+ // In the mobile drawer the fixed 520px panel would overflow the ~350px
80
+ // drawer, and the 2-column grid overlaps item text there — so the drawer
81
+ // gets a natural-width single column. The same goes for viewports too
82
+ // narrow to seat the locked desktop panel, which would otherwise paint
83
+ // past the edge: below 1024px the popover keeps the natural-width column.
84
+ const isDrawer = useTopNavRenderMode() === "drawer";
85
+ const isCompact = useMediaQuery("(max-width: 1024px)");
86
+ if (isDrawer || isCompact) {
87
+ return (
88
+ <VStack gap={1}>
89
+ {items.map((item) => (
90
+ <TopNavMegaMenuItem
91
+ key={item.name}
92
+ title={item.name}
93
+ description={item.tagline}
94
+ icon={<Icon icon={item.icon} size="md" color="secondary" />}
95
+ href={SELF_HASH}
96
+ />
97
+ ))}
98
+ </VStack>
99
+ );
100
+ }
101
+ return (
102
+ <Stack style={{ gridColumn: "1 / -1", width: 520 }}>
103
+ <Grid columns={2} gap={2}>
104
+ {items.map((item) => (
105
+ <TopNavMegaMenuItem
106
+ key={item.name}
107
+ title={item.name}
108
+ description={item.tagline}
109
+ icon={<Icon icon={item.icon} size="md" color="secondary" />}
110
+ href={SELF_HASH}
111
+ />
112
+ ))}
113
+ </Grid>
114
+ </Stack>
115
+ );
122
116
  }
123
117
 
124
118
  // Pins the featured card to a fixed width so both panels match exactly.
125
119
  function MegaFeatured(props: {
126
- title: string;
127
- description: string;
128
- linkLabel: string;
129
- linkHref: string;
120
+ title: string;
121
+ description: string;
122
+ linkLabel: string;
123
+ linkHref: string;
130
124
  }) {
131
- const isDrawer = useTopNavRenderMode() === 'drawer';
132
- const isCompact = useMediaQuery('(max-width: 1024px)');
133
- return (
134
- <Stack style={isDrawer || isCompact ? undefined : {width: 240}}>
135
- <TopNavMegaMenuFeaturedCard {...props} />
136
- </Stack>
137
- );
125
+ const isDrawer = useTopNavRenderMode() === "drawer";
126
+ const isCompact = useMediaQuery("(max-width: 1024px)");
127
+ return (
128
+ <Stack style={isDrawer || isCompact ? undefined : { width: 240 }}>
129
+ <TopNavMegaMenuFeaturedCard {...props} />
130
+ </Stack>
131
+ );
138
132
  }
139
133
 
140
134
  export default function ShellTopNav() {
141
- // Below ~640px the mobile bar (heading + actions + toggle) overflows 390px
142
- // viewports, pushing the nav toggle off-screen. Dropping the text Sign in
143
- // button there restores room for search, checkout, and the toggle.
144
- const isCompact = useMediaQuery('(max-width: 640px)');
145
- return (
146
- <AppShell
147
- variant="surface"
148
- contentPadding={6}
149
- topNav={
150
- <TopNav
151
- label="Nocturne storefront navigation"
152
- heading={
153
- <TopNavHeading
154
- heading="Nocturne"
155
- logo={
156
- <NavIcon icon={<Icon icon={ShoppingBag} size="sm" />} />
157
- }
158
- />
159
- }
160
- centerContent={
161
- <>
162
- <TopNavMegaMenu
163
- label="Shop"
164
- items={<MegaItems items={SHOP_ITEMS} />}
165
- featured={
166
- <MegaFeatured
167
- title="The Midnight Edit"
168
- description="Layered staples in moonlit purples."
169
- linkLabel="Shop the edit"
170
- linkHref={SELF_HASH}
171
- />
172
- }
173
- />
174
- <TopNavMegaMenu
175
- label="Brands"
176
- items={<MegaItems items={BRAND_ITEMS} />}
177
- featured={
178
- <MegaFeatured
179
- title="Meet Studio Mara"
180
- description="Modern tailoring, made to last."
181
- linkLabel="Discover the label"
182
- linkHref={SELF_HASH}
183
- />
184
- }
185
- />
186
- <TopNavItem label="Sale" href={SELF_HASH} />
187
- <TopNavItem label="Service" href={SELF_HASH} />
188
- </>
189
- }
190
- endContent={
191
- <>
192
- <IconButton
193
- label="Search products"
194
- tooltip="Search"
195
- variant="ghost"
196
- icon={<Icon icon={Search} size="sm" />}
197
- />
198
- {!isCompact && <Button label="Sign in" variant="ghost" />}
199
- <Button
200
- label="Checkout"
201
- variant="primary"
202
- tooltip="Cart, 3 items"
203
- icon={<Icon icon={ShoppingCart} size="sm" />}
204
- endContent={<Badge label={3} />}
205
- />
206
- </>
207
- }
208
- />
209
- }>
210
- <Center axis="horizontal">
211
- <Section
212
- variant="transparent"
213
- maxWidth={CONTENT_MAX_WIDTH}
214
- width="100%"
215
- padding={0}>
216
- <VStack gap={10}>
217
- {/* Skeleton shelves only — the routed collection supplies the h1. */}
218
- <Card variant="muted" padding={0} width="100%" height={360} />
135
+ // Below ~640px the mobile bar (heading + actions + toggle) overflows 390px
136
+ // viewports, pushing the nav toggle off-screen. Dropping the text Sign in
137
+ // button there restores room for search, checkout, and the toggle.
138
+ const isCompact = useMediaQuery("(max-width: 640px)");
139
+ return (
140
+ <AppShell
141
+ variant="surface"
142
+ contentPadding={6}
143
+ topNav={
144
+ <TopNav
145
+ label="Nocturne storefront navigation"
146
+ heading={
147
+ <TopNavHeading
148
+ heading="Nocturne"
149
+ logo={<NavIcon icon={<Icon icon={ShoppingBag} size="sm" />} />}
150
+ />
151
+ }
152
+ centerContent={
153
+ <>
154
+ <TopNavMegaMenu
155
+ label="Shop"
156
+ items={<MegaItems items={SHOP_ITEMS} />}
157
+ featured={
158
+ <MegaFeatured
159
+ title="The Midnight Edit"
160
+ description="Layered staples in moonlit purples."
161
+ linkLabel="Shop the edit"
162
+ linkHref={SELF_HASH}
163
+ />
164
+ }
165
+ />
166
+ <TopNavMegaMenu
167
+ label="Brands"
168
+ items={<MegaItems items={BRAND_ITEMS} />}
169
+ featured={
170
+ <MegaFeatured
171
+ title="Meet Studio Mara"
172
+ description="Modern tailoring, made to last."
173
+ linkLabel="Discover the label"
174
+ linkHref={SELF_HASH}
175
+ />
176
+ }
177
+ />
178
+ <TopNavItem label="Sale" href={SELF_HASH} />
179
+ <TopNavItem label="Service" href={SELF_HASH} />
180
+ </>
181
+ }
182
+ endContent={
183
+ <>
184
+ <IconButton
185
+ label="Search products"
186
+ tooltip="Search"
187
+ variant="ghost"
188
+ icon={<Icon icon={Search} size="sm" />}
189
+ />
190
+ {!isCompact && <Button label="Sign in" variant="ghost" />}
191
+ <Button
192
+ label="Checkout"
193
+ variant="primary"
194
+ tooltip="Cart, 3 items"
195
+ icon={<Icon icon={ShoppingCart} size="sm" />}
196
+ endContent={<Badge label={3} />}
197
+ />
198
+ </>
199
+ }
200
+ />
201
+ }
202
+ >
203
+ <Center axis="horizontal">
204
+ <Section variant="transparent" maxWidth={CONTENT_MAX_WIDTH} width="100%" padding={0}>
205
+ <VStack gap={10}>
206
+ {/* Skeleton shelves only — the routed collection supplies the h1. */}
207
+ <Card variant="muted" padding={0} width="100%" height={360} />
219
208
 
220
- {SHELVES.map(shelf => (
221
- <VStack key={shelf} gap={4}>
222
- <Card variant="muted" padding={0} width={200} height={24} />
223
- <Grid columns={{minWidth: 160, repeat: 'fit'}} gap={4}>
224
- {CATEGORY_TILES.map(tile => (
225
- <VStack key={tile} gap={2}>
226
- <Card
227
- variant="muted"
228
- padding={0}
229
- width="100%"
230
- height={120}
231
- />
232
- <Card
233
- variant="muted"
234
- padding={0}
235
- width="60%"
236
- height={14}
237
- />
238
- </VStack>
239
- ))}
240
- </Grid>
241
- </VStack>
242
- ))}
243
- </VStack>
244
- </Section>
245
- </Center>
246
- </AppShell>
247
- );
209
+ {SHELVES.map((shelf) => (
210
+ <VStack key={shelf} gap={4}>
211
+ <Card variant="muted" padding={0} width={200} height={24} />
212
+ <Grid columns={{ minWidth: 160, repeat: "fit" }} gap={4}>
213
+ {CATEGORY_TILES.map((tile) => (
214
+ <VStack key={tile} gap={2}>
215
+ <Card variant="muted" padding={0} width="100%" height={120} />
216
+ <Card variant="muted" padding={0} width="60%" height={14} />
217
+ </VStack>
218
+ ))}
219
+ </Grid>
220
+ </VStack>
221
+ ))}
222
+ </VStack>
223
+ </Section>
224
+ </Center>
225
+ </AppShell>
226
+ );
248
227
  }
@@ -1,6 +1,7 @@
1
1
  export default {
2
- type: 'page',
3
- name: 'Side Gallery',
4
- description: 'Headline, CTA, and image collage side by side',
5
- category: 'marketing',
2
+ type: "page",
3
+ name: "Side Gallery",
4
+ description: "Headline, CTA, and image collage side by side",
5
+ keywords: ["gallery", "sidebar", "collection", "grid", "art", "images"],
6
+ category: "marketing",
6
7
  };