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,6 +1,7 @@
1
1
  export default {
2
- type: 'page',
3
- name: 'Two-column Form',
4
- description: 'Contact form with illustration and info strip',
5
- category: 'forms',
2
+ type: "page",
3
+ name: "Two-column Form",
4
+ description: "Contact form with illustration and info strip",
5
+ keywords: ["form", "two column", "contact", "split", "signup", "lead"],
6
+ category: "forms",
6
7
  };
@@ -1,58 +1,60 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
- // XLE (canonical structure, validated with `bunx astryx layout check`):
3
- // Ctr > S.transparent[p=10] > V[g=10] > (G[c={min:280,max:2} g=10] > (V[g=6] > (V[g=3] > Hd"Let's conjure together"[level=1 t=display-1] + Tx"Tell us what you're brewing"[t=body]) + AR) + (C[p=4] > V[g=4] > Hd"Your details"[level=2] + TI"Full name"[req] + (G[c={min:180} g=3] > TI"Email"[req] + TI"Company") + (G[c={min:180} g=3] > TI"Job title" + TI"Phone") + (V[g=2] > Tx"Reason"[t=label] + (H[g=2] > Tk"Reason"*7)) + SE"Budget" + TA"Project details" + B.primary"Send it into the night")) + (V[g=6] > D + (G[c={min:200} g=6] > (V[g=1 a=center] > Tx"General"[t=supporting] + Lk"hello@castle.dracula")*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
+ // Ctr > S.transparent[mw=1100 w=100% p=10] > V[g=10] > (G[c={min:280,max:2} g=10 a=center] > (V[g=6] > (V[g=3] > Hd"Let's conjure together"[level=1 t=display-1] + Tx"Tell us what you're brewing"[t=body]) + (AR[ratio=4/3] > Scene{SceneCastle})) + (C[p=4] > V[g=4] > Hd"Your details"[level=2] + TI"Full name"[req] + (G[c={min:180} g=3] > TI"Email"[req] + TI"Company name") + (G[c={min:180} g=3] > TI"Job title" + TI"Phone number") + (V[g=2] > Tx"What are you reaching out about?"[t=label] + (H[g=2] > Tk"Reason"*7)) + SE"Budget range" + TA"Project details" + Bn.success"Sent into the night" + (V[a=stretch] > B.primary"Send it into the night"))) + (V[g=6] > D + (G[c={min:200} g=6] > (V[g=1 a=center] > Tx"General"[t=supporting] + Lk"hello@castle.dracula")*3))
4
6
 
5
- import {useState, type CSSProperties} from 'react';
6
- import {VStack, HStack} from '@astryxdesign/core/Layout';
7
- import {Center} from '@astryxdesign/core/Center';
8
- import {Section} from '@astryxdesign/core/Section';
9
- import {Grid} from '@astryxdesign/core/Grid';
10
- import {AspectRatio} from '@astryxdesign/core/AspectRatio';
11
- import {galleryImageClip} from 'astryx-dracula/shared/gallery-image';
12
- import {Button} from '@astryxdesign/core/Button';
13
- import {Text, Heading} from '@astryxdesign/core/Text';
14
- import {TextInput} from '@astryxdesign/core/TextInput';
15
- import {Token} from '@astryxdesign/core/Token';
16
- import {TextArea} from '@astryxdesign/core/TextArea';
17
- import {Link} from '@astryxdesign/core/Link';
18
- import {Divider} from '@astryxdesign/core/Divider';
19
- import {Card} from '@astryxdesign/core/Card';
20
- import {Selector} from '@astryxdesign/core/Selector';
21
- import {Banner} from '@astryxdesign/core/Banner';
22
- import {Icon} from '@astryxdesign/core/Icon';
23
- import {Check} from 'lucide-react';
24
- import {SceneCastle} from 'astryx-dracula/shared/scene-castle';
7
+ import { AspectRatio } from "@astryxdesign/core/AspectRatio";
8
+ import { Banner } from "@astryxdesign/core/Banner";
9
+ import { Button } from "@astryxdesign/core/Button";
10
+ import { Card } from "@astryxdesign/core/Card";
11
+ import { Center } from "@astryxdesign/core/Center";
12
+ import { Divider } from "@astryxdesign/core/Divider";
13
+ import { Grid } from "@astryxdesign/core/Grid";
14
+ import { Icon } from "@astryxdesign/core/Icon";
15
+ import { HStack, VStack } from "@astryxdesign/core/Layout";
16
+ import { Link } from "@astryxdesign/core/Link";
17
+ import { Section } from "@astryxdesign/core/Section";
18
+ import { Selector } from "@astryxdesign/core/Selector";
19
+ import { Heading, Text } from "@astryxdesign/core/Text";
20
+ import { TextArea } from "@astryxdesign/core/TextArea";
21
+ import { TextInput } from "@astryxdesign/core/TextInput";
22
+ import { Token } from "@astryxdesign/core/Token";
23
+ import { galleryImageClip } from "astryx-dracula/shared/gallery-image";
24
+ import { SceneCastle } from "astryx-dracula/shared/scene-castle";
25
+ import { Check } from "lucide-react";
26
+ import { type CSSProperties, useState } from "react";
25
27
 
26
28
  const INQUIRY_REASONS = [
27
- 'New business',
28
- 'General inquiry',
29
- 'Press & media',
30
- 'Partnerships',
31
- 'Product feedback',
32
- 'Technical support',
33
- 'Other',
29
+ "New business",
30
+ "General inquiry",
31
+ "Press & media",
32
+ "Partnerships",
33
+ "Product feedback",
34
+ "Technical support",
35
+ "Other",
34
36
  ];
35
37
 
36
38
  const BUDGET_OPTIONS = [
37
- 'Under $10k',
38
- '$10k – $50k',
39
- '$50k – $100k',
40
- '$100k – $500k',
41
- '$500k+',
42
- 'Not sure yet',
39
+ "Under $10k",
40
+ "$10k – $50k",
41
+ "$50k – $100k",
42
+ "$100k – $500k",
43
+ "$500k+",
44
+ "Not sure yet",
43
45
  ];
44
46
 
45
47
  const CONTACT_COLUMNS = [
46
- {label: 'General inquiries', email: 'hello@castle.dracula'},
47
- {label: 'New business', email: 'newbiz@castle.dracula'},
48
- {label: 'Press & partnerships', email: 'press@castle.dracula'},
48
+ { label: "General inquiries", email: "hello@castle.dracula" },
49
+ { label: "New business", email: "newbiz@castle.dracula" },
50
+ { label: "Press & partnerships", email: "press@castle.dracula" },
49
51
  ];
50
52
 
51
53
  // Castle illustration lives in shared/scene-castle (`card` variant:
52
54
  // harvest-yellow moon, fg-stroke bats). Fill + radius-clip live in
53
55
  // shared/gallery-image (no Image primitive, #2582).
54
56
  const pageStyle: CSSProperties = {
55
- minHeight: '100%',
57
+ minHeight: "100%",
56
58
  };
57
59
 
58
60
  /**
@@ -70,183 +72,166 @@ const pageStyle: CSSProperties = {
70
72
  * from 504px, one column below.
71
73
  */
72
74
  export default function TwoColumnForm() {
73
- const [fullName, setFullName] = useState('');
74
- const [email, setEmail] = useState('');
75
- const [company, setCompany] = useState('');
76
- const [jobTitle, setJobTitle] = useState('');
77
- const [phone, setPhone] = useState('');
78
- const [inquiryReason, setInquiryReason] = useState('');
79
- const [budget, setBudget] = useState('');
80
- const [details, setDetails] = useState('');
81
- const [submitted, setSubmitted] = useState(false);
75
+ const [fullName, setFullName] = useState("");
76
+ const [email, setEmail] = useState("");
77
+ const [company, setCompany] = useState("");
78
+ const [jobTitle, setJobTitle] = useState("");
79
+ const [phone, setPhone] = useState("");
80
+ const [inquiryReason, setInquiryReason] = useState("");
81
+ const [budget, setBudget] = useState("");
82
+ const [details, setDetails] = useState("");
83
+ const [submitted, setSubmitted] = useState(false);
82
84
 
83
- const errors = submitted
84
- ? {
85
- fullName: !fullName.trim() ? 'Required' : undefined,
86
- email: !email.trim() ? 'Required' : undefined,
87
- details: !details.trim() ? 'Required' : undefined,
88
- }
89
- : {};
85
+ const errors = submitted
86
+ ? {
87
+ fullName: !fullName.trim() ? "Required" : undefined,
88
+ email: !email.trim() ? "Required" : undefined,
89
+ details: !details.trim() ? "Required" : undefined,
90
+ }
91
+ : {};
90
92
 
91
- const isValid =
92
- fullName.trim() !== '' &&
93
- email.trim() !== '' &&
94
- details.trim() !== '';
93
+ const isValid = fullName.trim() !== "" && email.trim() !== "" && details.trim() !== "";
95
94
 
96
- const handleSubmit = () => setSubmitted(true);
95
+ const handleSubmit = () => setSubmitted(true);
97
96
 
98
- return (
99
- <Center style={pageStyle}>
100
- <Section maxWidth={1100} width="100%" padding={10} variant="transparent">
101
- <VStack gap={10}>
102
- <Grid columns={{minWidth: 280, max: 2}} align="center" gap={10}>
103
- <VStack gap={6}>
104
- <VStack gap={3}>
105
- <Heading level={1} type="display-1">
106
- Let&apos;s conjure together
107
- </Heading>
108
- <Text type="body" color="secondary">
109
- Tell us what you&apos;re brewing and we&apos;ll help you find
110
- the best path through the night.
111
- </Text>
112
- </VStack>
113
- <AspectRatio ratio={4 / 3} style={galleryImageClip}>
114
- <SceneCastle variant="card" />
115
- </AspectRatio>
116
- </VStack>
97
+ return (
98
+ <Center style={pageStyle}>
99
+ <Section maxWidth={1100} width="100%" padding={10} variant="transparent">
100
+ <VStack gap={10}>
101
+ <Grid columns={{ minWidth: 280, max: 2 }} align="center" gap={10}>
102
+ <VStack gap={6}>
103
+ <VStack gap={3}>
104
+ <Heading level={1} type="display-1">
105
+ Let&apos;s conjure together
106
+ </Heading>
107
+ <Text type="body" color="secondary">
108
+ Tell us what you&apos;re brewing and we&apos;ll help you find the best path
109
+ through the night.
110
+ </Text>
111
+ </VStack>
112
+ <AspectRatio ratio={4 / 3} style={galleryImageClip}>
113
+ <SceneCastle variant="card" />
114
+ </AspectRatio>
115
+ </VStack>
117
116
 
118
- <Card padding={4}>
119
- <VStack gap={4}>
120
- <Heading level={2}>Your details</Heading>
121
- <TextInput
122
- label="Full name"
123
- isRequired
124
- placeholder="Full name"
125
- value={fullName}
126
- onChange={setFullName}
127
- status={
128
- errors.fullName
129
- ? {type: 'error', message: errors.fullName}
130
- : undefined
131
- }
132
- />
133
- <Grid columns={{minWidth: 180}} gap={3}>
134
- <TextInput
135
- label="Email"
136
- isRequired
137
- placeholder="Email"
138
- value={email}
139
- onChange={setEmail}
140
- status={
141
- errors.email
142
- ? {type: 'error', message: errors.email}
143
- : undefined
144
- }
145
- />
146
- <TextInput
147
- label="Company name"
148
- placeholder="Company name"
149
- value={company}
150
- onChange={setCompany}
151
- />
152
- </Grid>
153
- <Grid columns={{minWidth: 180}} gap={3}>
154
- <TextInput
155
- label="Job title"
156
- placeholder="Job title"
157
- value={jobTitle}
158
- onChange={setJobTitle}
159
- />
160
- <TextInput
161
- label="Phone number"
162
- placeholder="Phone number"
163
- value={phone}
164
- onChange={setPhone}
165
- />
166
- </Grid>
117
+ <Card padding={4}>
118
+ <VStack gap={4}>
119
+ <Heading level={2}>Your details</Heading>
120
+ <TextInput
121
+ label="Full name"
122
+ isRequired
123
+ placeholder="Full name"
124
+ value={fullName}
125
+ onChange={setFullName}
126
+ status={errors.fullName ? { type: "error", message: errors.fullName } : undefined}
127
+ />
128
+ <Grid columns={{ minWidth: 180 }} gap={3}>
129
+ <TextInput
130
+ label="Email"
131
+ isRequired
132
+ placeholder="Email"
133
+ value={email}
134
+ onChange={setEmail}
135
+ status={errors.email ? { type: "error", message: errors.email } : undefined}
136
+ />
137
+ <TextInput
138
+ label="Company name"
139
+ placeholder="Company name"
140
+ value={company}
141
+ onChange={setCompany}
142
+ />
143
+ </Grid>
144
+ <Grid columns={{ minWidth: 180 }} gap={3}>
145
+ <TextInput
146
+ label="Job title"
147
+ placeholder="Job title"
148
+ value={jobTitle}
149
+ onChange={setJobTitle}
150
+ />
151
+ <TextInput
152
+ label="Phone number"
153
+ placeholder="Phone number"
154
+ value={phone}
155
+ onChange={setPhone}
156
+ />
157
+ </Grid>
167
158
 
168
- <VStack gap={2}>
169
- <Text type="label">What are you reaching out about?</Text>
170
- <HStack gap={2} wrap="wrap">
171
- {INQUIRY_REASONS.map(reason => {
172
- const isSelected = inquiryReason === reason;
173
- return (
174
- <Token
175
- key={reason}
176
- label={reason}
177
- color={isSelected ? 'yellow' : 'default'}
178
- icon={
179
- isSelected ? (
180
- <Icon icon={Check} size="xsm" color="inherit" />
181
- ) : undefined
182
- }
183
- description={isSelected ? 'Selected' : undefined}
184
- onClick={() =>
185
- setInquiryReason(prev =>
186
- prev === reason ? '' : reason,
187
- )
188
- }
189
- />
190
- );
191
- })}
192
- </HStack>
193
- </VStack>
194
- <Selector
195
- label="Budget range"
196
- options={BUDGET_OPTIONS}
197
- value={budget}
198
- onChange={setBudget}
199
- placeholder="Select a budget range…"
200
- />
201
- <TextArea
202
- label="Project details"
203
- isRequired
204
- placeholder="Project details"
205
- value={details}
206
- onChange={setDetails}
207
- status={
208
- errors.details
209
- ? {type: 'error', message: errors.details}
210
- : undefined
211
- }
212
- />
213
- {submitted && isValid && (
214
- <Banner
215
- status="success"
216
- title="Sent into the night"
217
- description="The coven received your message and will answer after dark."
218
- />
219
- )}
220
- {/* hAlign="stretch" = full-width button workaround; Button
159
+ <VStack gap={2}>
160
+ <Text type="label">What are you reaching out about?</Text>
161
+ <HStack gap={2} wrap="wrap">
162
+ {INQUIRY_REASONS.map((reason) => {
163
+ const isSelected = inquiryReason === reason;
164
+ return (
165
+ <Token
166
+ key={reason}
167
+ label={reason}
168
+ color={isSelected ? "yellow" : "default"}
169
+ icon={
170
+ isSelected ? (
171
+ <Icon icon={Check} size="xsm" color="inherit" />
172
+ ) : undefined
173
+ }
174
+ description={isSelected ? "Selected" : undefined}
175
+ onClick={() =>
176
+ setInquiryReason((prev) => (prev === reason ? "" : reason))
177
+ }
178
+ />
179
+ );
180
+ })}
181
+ </HStack>
182
+ </VStack>
183
+ <Selector
184
+ label="Budget range"
185
+ options={BUDGET_OPTIONS}
186
+ value={budget}
187
+ onChange={setBudget}
188
+ placeholder="Select a budget range…"
189
+ />
190
+ <TextArea
191
+ label="Project details"
192
+ isRequired
193
+ placeholder="Project details"
194
+ value={details}
195
+ onChange={setDetails}
196
+ status={errors.details ? { type: "error", message: errors.details } : undefined}
197
+ />
198
+ {submitted && isValid && (
199
+ <Banner
200
+ status="success"
201
+ // Core 0.7.0 stopped deriving the live-region role from
202
+ // `status`, so this banner states its own. It mirrors the
203
+ // submit result, and a screen reader must hear it.
204
+ role="status"
205
+ title="Sent into the night"
206
+ description="The coven received your message and will answer after dark."
207
+ />
208
+ )}
209
+ {/* hAlign="stretch" = full-width button workaround; Button
221
210
  has no full-width prop (#2600). */}
222
- <VStack hAlign="stretch">
223
- <Button
224
- label="Send it into the night"
225
- variant="primary"
226
- onClick={handleSubmit}
227
- />
228
- </VStack>
229
- </VStack>
230
- </Card>
231
- </Grid>
211
+ <VStack hAlign="stretch">
212
+ <Button label="Send it into the night" variant="primary" onClick={handleSubmit} />
213
+ </VStack>
214
+ </VStack>
215
+ </Card>
216
+ </Grid>
232
217
 
233
- <VStack gap={6}>
234
- <Divider />
235
- <Grid columns={{minWidth: 200}} gap={6}>
236
- {CONTACT_COLUMNS.map(col => (
237
- <VStack key={col.label} gap={1} hAlign="center">
238
- <Text type="supporting" color="secondary">
239
- {col.label}
240
- </Text>
241
- <Link href={`mailto:${col.email}`} hasUnderline>
242
- {col.email}
243
- </Link>
244
- </VStack>
245
- ))}
246
- </Grid>
247
- </VStack>
248
- </VStack>
249
- </Section>
250
- </Center>
251
- );
218
+ <VStack gap={6}>
219
+ <Divider />
220
+ <Grid columns={{ minWidth: 200 }} gap={6}>
221
+ {CONTACT_COLUMNS.map((col) => (
222
+ <VStack key={col.label} gap={1} hAlign="center">
223
+ <Text type="supporting" color="secondary">
224
+ {col.label}
225
+ </Text>
226
+ <Link href={`mailto:${col.email}`} hasUnderline>
227
+ {col.email}
228
+ </Link>
229
+ </VStack>
230
+ ))}
231
+ </Grid>
232
+ </VStack>
233
+ </VStack>
234
+ </Section>
235
+ </Center>
236
+ );
252
237
  }
@@ -1,6 +1,7 @@
1
1
  export default {
2
- type: 'page',
3
- name: 'Gallery Hero',
4
- description: 'Headline, CTAs, and three-image gallery',
5
- category: 'marketing',
2
+ type: "page",
3
+ name: "Gallery Hero",
4
+ description: "Headline, CTAs, and three-image gallery",
5
+ keywords: ["gallery", "hero", "showcase", "carousel", "featured", "spotlight"],
6
+ category: "marketing",
6
7
  };