astryx-dracula 0.1.3 → 0.2.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 (94) hide show
  1. package/BRAND.md +1 -1
  2. package/README.md +2 -0
  3. package/USAGE.md +13 -0
  4. package/astryx-dracula.js +4 -4
  5. package/astryx.integration.mjs +3 -0
  6. package/package.json +4 -2
  7. package/templates/ai-chat-landing.template.mjs +6 -0
  8. package/templates/ai-chat-landing.tsx +476 -0
  9. package/templates/ai-chat.template.mjs +6 -0
  10. package/templates/ai-chat.tsx +762 -0
  11. package/templates/blank.template.mjs +6 -0
  12. package/templates/blank.tsx +16 -0
  13. package/templates/centered-hero.template.mjs +6 -0
  14. package/templates/centered-hero.tsx +188 -0
  15. package/templates/classic-gallery.template.mjs +6 -0
  16. package/templates/classic-gallery.tsx +181 -0
  17. package/templates/contact-form.template.mjs +6 -0
  18. package/templates/contact-form.tsx +292 -0
  19. package/templates/dashboard-portfolio.template.mjs +6 -0
  20. package/templates/dashboard-portfolio.tsx +800 -0
  21. package/templates/dashboard.template.mjs +6 -0
  22. package/templates/dashboard.tsx +774 -0
  23. package/templates/detail-page.template.mjs +6 -0
  24. package/templates/detail-page.tsx +695 -0
  25. package/templates/documentation-design.template.mjs +6 -0
  26. package/templates/documentation-design.tsx +751 -0
  27. package/templates/documentation-technical.template.mjs +6 -0
  28. package/templates/documentation-technical.tsx +292 -0
  29. package/templates/documentation.template.mjs +6 -0
  30. package/templates/documentation.tsx +347 -0
  31. package/templates/editor.template.mjs +6 -0
  32. package/templates/editor.tsx +959 -0
  33. package/templates/file-explorer.template.mjs +6 -0
  34. package/templates/file-explorer.tsx +577 -0
  35. package/templates/form-two-column.template.mjs +6 -0
  36. package/templates/form-two-column.tsx +314 -0
  37. package/templates/gallery-hero.template.mjs +6 -0
  38. package/templates/gallery-hero.tsx +287 -0
  39. package/templates/ide.template.mjs +6 -0
  40. package/templates/ide.tsx +533 -0
  41. package/templates/incident-console.template.mjs +6 -0
  42. package/templates/incident-console.tsx +584 -0
  43. package/templates/kanban-board.template.mjs +6 -0
  44. package/templates/kanban-board.tsx +746 -0
  45. package/templates/library.template.mjs +6 -0
  46. package/templates/library.tsx +489 -0
  47. package/templates/login-card.template.mjs +6 -0
  48. package/templates/login-card.tsx +200 -0
  49. package/templates/login-split.template.mjs +6 -0
  50. package/templates/login-split.tsx +318 -0
  51. package/templates/login-sso.template.mjs +6 -0
  52. package/templates/login-sso.tsx +277 -0
  53. package/templates/login.template.mjs +6 -0
  54. package/templates/login.tsx +98 -0
  55. package/templates/messaging-shell.template.mjs +6 -0
  56. package/templates/messaging-shell.tsx +675 -0
  57. package/templates/mixed-gallery.template.mjs +6 -0
  58. package/templates/mixed-gallery.tsx +190 -0
  59. package/templates/payment-form.template.mjs +6 -0
  60. package/templates/payment-form.tsx +990 -0
  61. package/templates/product-detail.template.mjs +6 -0
  62. package/templates/product-detail.tsx +366 -0
  63. package/templates/product-gallery.template.mjs +6 -0
  64. package/templates/product-gallery.tsx +173 -0
  65. package/templates/settings-dialog.template.mjs +6 -0
  66. package/templates/settings-dialog.tsx +920 -0
  67. package/templates/settings-sidebar.template.mjs +6 -0
  68. package/templates/settings-sidebar.tsx +952 -0
  69. package/templates/settings.template.mjs +6 -0
  70. package/templates/settings.tsx +235 -0
  71. package/templates/shell-nav.template.mjs +6 -0
  72. package/templates/shell-nav.tsx +306 -0
  73. package/templates/shell-side-nav.template.mjs +6 -0
  74. package/templates/shell-side-nav.tsx +238 -0
  75. package/templates/shell-top-nav.template.mjs +6 -0
  76. package/templates/shell-top-nav.tsx +243 -0
  77. package/templates/side-gallery.template.mjs +6 -0
  78. package/templates/side-gallery.tsx +179 -0
  79. package/templates/table-grouped.template.mjs +6 -0
  80. package/templates/table-grouped.tsx +1174 -0
  81. package/templates/table-page-chart.template.mjs +6 -0
  82. package/templates/table-page-chart.tsx +693 -0
  83. package/templates/table-page-heatmap-status.template.mjs +6 -0
  84. package/templates/table-page-heatmap-status.tsx +547 -0
  85. package/templates/table-page-shoe-store-heatmap.template.mjs +6 -0
  86. package/templates/table-page-shoe-store-heatmap.tsx +1038 -0
  87. package/templates/table-page.template.mjs +6 -0
  88. package/templates/table-page.tsx +445 -0
  89. package/templates/table.template.mjs +6 -0
  90. package/templates/table.tsx +84 -0
  91. package/templates/theme-showcase.template.mjs +6 -0
  92. package/templates/theme-showcase.tsx +1282 -0
  93. package/theme.css +4 -4
  94. package/tokens.css +4 -4
@@ -0,0 +1,314 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {useState, type CSSProperties} from 'react';
4
+ import {VStack, HStack} from '@astryxdesign/core/Layout';
5
+ import {Center} from '@astryxdesign/core/Center';
6
+ import {Section} from '@astryxdesign/core/Section';
7
+ import {Grid} from '@astryxdesign/core/Grid';
8
+ import {AspectRatio} from '@astryxdesign/core/AspectRatio';
9
+ import {Button} from '@astryxdesign/core/Button';
10
+ import {Text, Heading} from '@astryxdesign/core/Text';
11
+ import {TextInput} from '@astryxdesign/core/TextInput';
12
+ import {Token} from '@astryxdesign/core/Token';
13
+ import {TextArea} from '@astryxdesign/core/TextArea';
14
+ import {Link} from '@astryxdesign/core/Link';
15
+ import {Divider} from '@astryxdesign/core/Divider';
16
+ import {Card} from '@astryxdesign/core/Card';
17
+ import {Selector} from '@astryxdesign/core/Selector';
18
+
19
+ function CastleIllustration() {
20
+ return (
21
+ <svg
22
+ style={illustrationImg}
23
+ viewBox="0 0 400 300"
24
+ role="img"
25
+ aria-label="Moonlit castle where the night crew works">
26
+ <title>Castle office after dark</title>
27
+ <rect width="400" height="300" fill="var(--dracula-bg-dark)" />
28
+ <g fill="var(--dracula-purple)">
29
+ <circle cx="40" cy="40" r="2.5" />
30
+ <circle cx="120" cy="70" r="2" />
31
+ <circle cx="210" cy="36" r="2.5" />
32
+ <circle cx="330" cy="60" r="2" />
33
+ </g>
34
+ <g fill="var(--dracula-cyan)">
35
+ <circle cx="80" cy="55" r="2" />
36
+ <circle cx="170" cy="60" r="2" />
37
+ <circle cx="290" cy="36" r="2.5" />
38
+ </g>
39
+ <circle cx="315" cy="70" r="34" fill="var(--dracula-yellow)" />
40
+ <circle
41
+ cx="304"
42
+ cy="61"
43
+ r="6"
44
+ fill="var(--dracula-orange)"
45
+ opacity={0.5}
46
+ />
47
+ <g
48
+ fill="none"
49
+ stroke="var(--dracula-purple)"
50
+ strokeWidth={3}
51
+ strokeLinecap="round">
52
+ <path d="M90 100 q9 -9 18 0 q9 -9 18 0" />
53
+ <path d="M180 84 q9 -9 18 0 q9 -9 18 0" />
54
+ </g>
55
+ <path
56
+ d="M0 220 Q140 180 260 210 T400 200 V300 H0 Z"
57
+ fill="var(--dracula-selection)"
58
+ />
59
+ <path
60
+ d="M0 255 Q160 225 320 250 T400 245 V300 H0 Z"
61
+ fill="var(--dracula-bg-light)"
62
+ />
63
+ <g>
64
+ <rect
65
+ x="140"
66
+ y="150"
67
+ width="120"
68
+ height="100"
69
+ rx={4}
70
+ fill="var(--dracula-bg-lighter)"
71
+ />
72
+ <rect
73
+ x="122"
74
+ y="122"
75
+ width="38"
76
+ height="128"
77
+ rx={4}
78
+ fill="var(--dracula-bg-lighter)"
79
+ />
80
+ <rect
81
+ x="240"
82
+ y="122"
83
+ width="38"
84
+ height="128"
85
+ rx={4}
86
+ fill="var(--dracula-bg-lighter)"
87
+ />
88
+ <g fill="var(--dracula-yellow)">
89
+ <rect x="156" y="172" width="12" height="18" rx={2} />
90
+ <rect x="186" y="172" width="12" height="18" rx={2} />
91
+ <rect x="216" y="172" width="12" height="18" rx={2} />
92
+ <rect x="132" y="146" width="9" height="12" rx={2} />
93
+ <rect x="251" y="146" width="9" height="12" rx={2} />
94
+ </g>
95
+ <path
96
+ d="M185 250 v-24 a15 15 0 0 1 30 0 v24 Z"
97
+ fill="var(--dracula-bg-dark)"
98
+ />
99
+ </g>
100
+ </svg>
101
+ );
102
+ }
103
+
104
+ const INQUIRY_REASONS = [
105
+ 'New business',
106
+ 'General inquiry',
107
+ 'Press & media',
108
+ 'Partnerships',
109
+ 'Product feedback',
110
+ 'Technical support',
111
+ 'Other',
112
+ ];
113
+
114
+ const BUDGET_OPTIONS = [
115
+ 'Under $10k',
116
+ '$10k – $50k',
117
+ '$50k – $100k',
118
+ '$100k – $500k',
119
+ '$500k+',
120
+ 'Not sure yet',
121
+ ];
122
+
123
+ const CONTACT_COLUMNS = [
124
+ {label: 'General inquiries', email: 'hello@castle.dracula'},
125
+ {label: 'New business', email: 'newbiz@castle.dracula'},
126
+ {label: 'Press & partnerships', email: 'press@castle.dracula'},
127
+ ];
128
+
129
+ // AspectRatio has no radius prop and there's no Image primitive (#2582), so
130
+ // the illustration carries its own corner radius directly.
131
+ const pageStyle: CSSProperties = {
132
+ minHeight: '100%',
133
+ };
134
+ const illustrationImg: CSSProperties = {
135
+ width: '100%',
136
+ height: '100%',
137
+ display: 'block',
138
+ borderRadius: 'var(--radius-container)',
139
+ };
140
+
141
+ /**
142
+ * Form (Two-column) — marketing contact form template.
143
+ *
144
+ * Layout:
145
+ * Top: two-column — left has headline + description + illustration,
146
+ * right has the contact form on a card.
147
+ * Bottom: three-column contact info strip.
148
+ * Mobile (<768px): single column stack.
149
+ */
150
+ export default function TwoColumnForm() {
151
+ const [fullName, setFullName] = useState('');
152
+ const [email, setEmail] = useState('');
153
+ const [company, setCompany] = useState('');
154
+ const [jobTitle, setJobTitle] = useState('');
155
+ const [phone, setPhone] = useState('');
156
+ const [inquiryReason, setInquiryReason] = useState('');
157
+ const [budget, setBudget] = useState('');
158
+ const [details, setDetails] = useState('');
159
+ const [submitted, setSubmitted] = useState(false);
160
+
161
+ const errors = submitted
162
+ ? {
163
+ fullName: !fullName.trim() ? 'Required' : undefined,
164
+ email: !email.trim() ? 'Required' : undefined,
165
+ details: !details.trim() ? 'Required' : undefined,
166
+ }
167
+ : {};
168
+
169
+ const handleSubmit = () => setSubmitted(true);
170
+
171
+ return (
172
+ <Center style={pageStyle}>
173
+ <Section maxWidth={1100} width="100%" padding={10} variant="transparent">
174
+ <VStack gap={10}>
175
+ {/* Two-column; stacks to one column below ~520px. */}
176
+ <Grid columns={{minWidth: 320}} align="center" gap={10}>
177
+ <VStack gap={6}>
178
+ <VStack gap={3}>
179
+ <Heading level={1} type="display-1">
180
+ Let&apos;s conjure together
181
+ </Heading>
182
+ <Text type="body" color="secondary">
183
+ Tell us what you&apos;re brewing and we&apos;ll help you find
184
+ the best path through the night.
185
+ </Text>
186
+ </VStack>
187
+ <AspectRatio ratio={4 / 3}>
188
+ <CastleIllustration />
189
+ </AspectRatio>
190
+ </VStack>
191
+
192
+ <Card padding={8}>
193
+ <VStack gap={4}>
194
+ <Text type="label">Your details</Text>
195
+ <TextInput
196
+ label="Full name"
197
+ isLabelHidden
198
+ placeholder="Full name*"
199
+ value={fullName}
200
+ onChange={setFullName}
201
+ status={
202
+ errors.fullName
203
+ ? {type: 'error', message: errors.fullName}
204
+ : undefined
205
+ }
206
+ />
207
+ <Grid columns={{minWidth: 180}} gap={3}>
208
+ <TextInput
209
+ label="Email"
210
+ isLabelHidden
211
+ placeholder="Email*"
212
+ value={email}
213
+ onChange={setEmail}
214
+ status={
215
+ errors.email
216
+ ? {type: 'error', message: errors.email}
217
+ : undefined
218
+ }
219
+ />
220
+ <TextInput
221
+ label="Company name"
222
+ isLabelHidden
223
+ placeholder="Company name"
224
+ value={company}
225
+ onChange={setCompany}
226
+ />
227
+ </Grid>
228
+ <Grid columns={{minWidth: 180}} gap={3}>
229
+ <TextInput
230
+ label="Job title"
231
+ isLabelHidden
232
+ placeholder="Job title"
233
+ value={jobTitle}
234
+ onChange={setJobTitle}
235
+ />
236
+ <TextInput
237
+ label="Phone number"
238
+ isLabelHidden
239
+ placeholder="Phone number"
240
+ value={phone}
241
+ onChange={setPhone}
242
+ />
243
+ </Grid>
244
+
245
+ <VStack gap={2}>
246
+ <Text type="label">What are you reaching out about?</Text>
247
+ <HStack gap={2} wrap="wrap">
248
+ {INQUIRY_REASONS.map(reason => (
249
+ <Token
250
+ key={reason}
251
+ label={reason}
252
+ color={inquiryReason === reason ? 'blue' : 'default'}
253
+ onClick={() =>
254
+ setInquiryReason(prev =>
255
+ prev === reason ? '' : reason,
256
+ )
257
+ }
258
+ />
259
+ ))}
260
+ </HStack>
261
+ </VStack>
262
+ <Selector
263
+ label="Budget range"
264
+ options={BUDGET_OPTIONS}
265
+ value={budget}
266
+ onChange={setBudget}
267
+ placeholder="Select a budget range..."
268
+ />
269
+ <TextArea
270
+ label="Project details"
271
+ isLabelHidden
272
+ placeholder="Project details*"
273
+ value={details}
274
+ onChange={setDetails}
275
+ status={
276
+ errors.details
277
+ ? {type: 'error', message: errors.details}
278
+ : undefined
279
+ }
280
+ />
281
+ {/* hAlign="stretch" = full-width button workaround; Button
282
+ has no full-width prop (#2600). */}
283
+ <VStack hAlign="stretch">
284
+ <Button
285
+ label="Send it into the night"
286
+ variant="primary"
287
+ onClick={handleSubmit}
288
+ />
289
+ </VStack>
290
+ </VStack>
291
+ </Card>
292
+ </Grid>
293
+
294
+ {/* Contact strip; stacks below ~440px. */}
295
+ <VStack gap={6}>
296
+ <Divider />
297
+ <Grid columns={{minWidth: 200}} gap={6}>
298
+ {CONTACT_COLUMNS.map(col => (
299
+ <VStack key={col.label} gap={1} hAlign="center">
300
+ <Text type="supporting" color="secondary">
301
+ {col.label}
302
+ </Text>
303
+ <Link href={`mailto:${col.email}`} type="body" size="sm">
304
+ {col.email}
305
+ </Link>
306
+ </VStack>
307
+ ))}
308
+ </Grid>
309
+ </VStack>
310
+ </VStack>
311
+ </Section>
312
+ </Center>
313
+ );
314
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ type: 'page',
3
+ name: 'Gallery Hero',
4
+ description: 'Headline, CTAs, and three-image gallery',
5
+ category: 'marketing',
6
+ };
@@ -0,0 +1,287 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import type {CSSProperties} from 'react';
4
+ import {VStack, HStack, Layout, LayoutContent} from '@astryxdesign/core/Layout';
5
+ import {Text, Heading} from '@astryxdesign/core/Text';
6
+ import {Button} from '@astryxdesign/core/Button';
7
+ import {Icon} from '@astryxdesign/core/Icon';
8
+ import {Grid} from '@astryxdesign/core/Grid';
9
+ import {AspectRatio} from '@astryxdesign/core/AspectRatio';
10
+ import {ArrowRight} from 'lucide-react';
11
+
12
+ // NOTE: The only custom styling here is gallery fill + corner radius. It
13
+ // exists because Astryx has no image primitive — AspectRatio exposes no
14
+ // objectFit or radius props and there's no Image. Tracked in issue #2582;
15
+ // replace these with component props once it lands.
16
+ // Fills the AspectRatio box. No objectFit prop on AspectRatio (#2582).
17
+ const galleryImage: CSSProperties = {
18
+ width: '100%',
19
+ height: '100%',
20
+ display: 'block',
21
+ };
22
+ // Rounds the gallery corners. No radius prop on AspectRatio (#2582);
23
+ // overflow clip masks the SVG scene to the rounded corners.
24
+ const galleryImageClip: CSSProperties = {
25
+ borderRadius: 'var(--radius-container)',
26
+ overflow: 'clip',
27
+ };
28
+
29
+ function NightCastleScene() {
30
+ return (
31
+ <svg
32
+ style={galleryImage}
33
+ viewBox="0 0 400 500"
34
+ role="img"
35
+ aria-label="Moonlit castle on midnight hills">
36
+ <title>Castle under a harvest moon</title>
37
+ <rect width="400" height="500" fill="var(--dracula-bg-dark)" />
38
+ <g fill="var(--dracula-purple)">
39
+ <circle cx="50" cy="60" r="2.5" />
40
+ <circle cx="140" cy="130" r="2" />
41
+ <circle cx="240" cy="50" r="2.5" />
42
+ <circle cx="330" cy="110" r="2" />
43
+ <circle cx="300" cy="200" r="2.5" />
44
+ </g>
45
+ <g fill="var(--dracula-cyan)">
46
+ <circle cx="95" cy="90" r="2" />
47
+ <circle cx="200" cy="100" r="2.5" />
48
+ <circle cx="360" cy="60" r="2" />
49
+ </g>
50
+ <circle cx="290" cy="120" r="52" fill="var(--dracula-yellow)" />
51
+ <circle
52
+ cx="272"
53
+ cy="106"
54
+ r="9"
55
+ fill="var(--dracula-orange)"
56
+ opacity={0.5}
57
+ />
58
+ <g
59
+ fill="none"
60
+ stroke="var(--dracula-purple)"
61
+ strokeWidth={4}
62
+ strokeLinecap="round">
63
+ <path d="M70 170 q12 -12 24 0 q12 -12 24 0" />
64
+ <path d="M150 130 q9 -9 18 0 q9 -9 18 0" />
65
+ </g>
66
+ <path
67
+ d="M0 360 Q140 300 260 350 T400 335 V500 H0 Z"
68
+ fill="var(--dracula-selection)"
69
+ />
70
+ <path
71
+ d="M0 420 Q160 365 300 410 T400 400 V500 H0 Z"
72
+ fill="var(--dracula-bg-light)"
73
+ />
74
+ <g>
75
+ <rect
76
+ x="140"
77
+ y="270"
78
+ width="120"
79
+ height="110"
80
+ rx={4}
81
+ fill="var(--dracula-bg-lighter)"
82
+ />
83
+ <rect
84
+ x="118"
85
+ y="235"
86
+ width="44"
87
+ height="145"
88
+ rx={4}
89
+ fill="var(--dracula-bg-lighter)"
90
+ />
91
+ <rect
92
+ x="238"
93
+ y="235"
94
+ width="44"
95
+ height="145"
96
+ rx={4}
97
+ fill="var(--dracula-bg-lighter)"
98
+ />
99
+ <g fill="var(--dracula-yellow)">
100
+ <rect x="160" y="300" width="14" height="20" rx={2} />
101
+ <rect x="196" y="300" width="14" height="20" rx={2} />
102
+ <rect x="232" y="300" width="14" height="20" rx={2} />
103
+ <rect x="131" y="262" width="10" height="14" rx={2} />
104
+ <rect x="253" y="262" width="10" height="14" rx={2} />
105
+ </g>
106
+ <path
107
+ d="M185 380 v-28 a15 15 0 0 1 30 0 v28 Z"
108
+ fill="var(--dracula-bg-dark)"
109
+ />
110
+ </g>
111
+ </svg>
112
+ );
113
+ }
114
+
115
+ function HarvestMoonScene() {
116
+ return (
117
+ <svg
118
+ style={galleryImage}
119
+ viewBox="0 0 400 500"
120
+ role="img"
121
+ aria-label="Harvest moon over midnight pines">
122
+ <title>Harvest moon over the pines</title>
123
+ <rect width="400" height="500" fill="var(--dracula-bg-dark)" />
124
+ <g fill="var(--dracula-pink)">
125
+ <circle cx="60" cy="80" r="2" />
126
+ <circle cx="160" cy="50" r="2.5" />
127
+ <circle cx="320" cy="70" r="2" />
128
+ <circle cx="90" cy="190" r="2.5" />
129
+ <circle cx="250" cy="160" r="2" />
130
+ </g>
131
+ <g fill="var(--dracula-cyan)">
132
+ <circle cx="110" cy="120" r="2" />
133
+ <circle cx="220" cy="90" r="2" />
134
+ <circle cx="350" cy="150" r="2.5" />
135
+ </g>
136
+ <circle cx="200" cy="190" r="72" fill="var(--dracula-yellow)" />
137
+ <circle
138
+ cx="176"
139
+ cy="170"
140
+ r="12"
141
+ fill="var(--dracula-orange)"
142
+ opacity={0.5}
143
+ />
144
+ <circle
145
+ cx="222"
146
+ cy="210"
147
+ r="8"
148
+ fill="var(--dracula-orange)"
149
+ opacity={0.5}
150
+ />
151
+ <g
152
+ fill="none"
153
+ stroke="var(--dracula-purple)"
154
+ strokeWidth={4}
155
+ strokeLinecap="round">
156
+ <path d="M90 260 q12 -12 24 0 q12 -12 24 0" />
157
+ <path d="M260 250 q9 -9 18 0 q9 -9 18 0" />
158
+ </g>
159
+ <path
160
+ d="M0 380 Q200 320 400 370 V500 H0 Z"
161
+ fill="var(--dracula-selection)"
162
+ />
163
+ <g fill="var(--dracula-bg-light)">
164
+ <path d="M70 500 V400 l28 -44 28 44 v100 Z" />
165
+ <path d="M170 500 V420 l28 -44 28 44 v80 Z" />
166
+ <path d="M270 500 V410 l28 -44 28 44 v90 Z" />
167
+ </g>
168
+ <g fill="var(--dracula-yellow)">
169
+ <rect x="81" y="442" width="8" height="12" rx={2} />
170
+ <rect x="181" y="458" width="8" height="12" rx={2} />
171
+ <rect x="281" y="450" width="8" height="12" rx={2} />
172
+ </g>
173
+ </svg>
174
+ );
175
+ }
176
+
177
+ function BatFlightScene() {
178
+ return (
179
+ <svg
180
+ style={galleryImage}
181
+ viewBox="0 0 400 500"
182
+ role="img"
183
+ aria-label="Bats crossing a violet night sky">
184
+ <title>Bats over violet hills</title>
185
+ <rect width="400" height="500" fill="var(--dracula-bg-dark)" />
186
+ <circle cx="200" cy="140" r="60" fill="var(--dracula-purple)" />
187
+ <circle
188
+ cx="200"
189
+ cy="140"
190
+ r="60"
191
+ fill="none"
192
+ stroke="var(--dracula-pink)"
193
+ strokeWidth={3}
194
+ opacity={0.6}
195
+ />
196
+ <g fill="var(--dracula-cyan)">
197
+ <circle cx="60" cy="70" r="2" />
198
+ <circle cx="150" cy="40" r="2.5" />
199
+ <circle cx="260" cy="60" r="2" />
200
+ <circle cx="340" cy="110" r="2.5" />
201
+ <circle cx="100" cy="160" r="2" />
202
+ </g>
203
+ <g
204
+ fill="none"
205
+ stroke="var(--dracula-fg)"
206
+ strokeWidth={4}
207
+ strokeLinecap="round">
208
+ <path d="M110 220 q12 -12 24 0 q12 -12 24 0" />
209
+ <path d="M200 180 q12 -12 24 0 q12 -12 24 0" />
210
+ <path d="M250 260 q9 -9 18 0 q9 -9 18 0" />
211
+ <path d="M140 300 q9 -9 18 0 q9 -9 18 0" />
212
+ <path d="M290 320 q9 -9 18 0 q9 -9 18 0" />
213
+ </g>
214
+ <path
215
+ d="M0 370 Q140 310 270 355 T400 345 V500 H0 Z"
216
+ fill="var(--dracula-bg-lighter)"
217
+ />
218
+ <path
219
+ d="M0 430 Q180 375 400 415 V500 H0 Z"
220
+ fill="var(--dracula-selection)"
221
+ />
222
+ <g fill="var(--dracula-green)">
223
+ <circle cx="80" cy="440" r="6" />
224
+ <circle cx="200" cy="455" r="6" />
225
+ <circle cx="320" cy="445" r="6" />
226
+ </g>
227
+ </svg>
228
+ );
229
+ }
230
+
231
+ const SCENES = [
232
+ {alt: 'Moonlit castle on midnight hills', scene: <NightCastleScene />},
233
+ {alt: 'Harvest moon over midnight pines', scene: <HarvestMoonScene />},
234
+ {alt: 'Bats crossing a violet night sky', scene: <BatFlightScene />},
235
+ ];
236
+
237
+ export default function GalleryHero() {
238
+ return (
239
+ <Layout
240
+ content={
241
+ <LayoutContent padding={6}>
242
+ <VStack gap={10}>
243
+ <VStack gap={6} hAlign="center">
244
+ <VStack gap={3} hAlign="center">
245
+ <Heading
246
+ level={1}
247
+ type="display-2"
248
+ justify="center"
249
+ textWrap="balance">
250
+ Little haunts, everywhere you roam
251
+ </Heading>
252
+ <Text
253
+ type="body"
254
+ color="secondary"
255
+ justify="center"
256
+ textWrap="balance">
257
+ Sometimes all it takes is one small shadow to turn the whole
258
+ night around.
259
+ </Text>
260
+ </VStack>
261
+ <HStack gap={3} wrap="wrap" justify="center">
262
+ <Button
263
+ label="Enter the gallery"
264
+ variant="primary"
265
+ endContent={
266
+ <Icon icon={ArrowRight} size="sm" color="inherit" />
267
+ }
268
+ />
269
+ <Button label="Read the lore" variant="secondary" />
270
+ </HStack>
271
+ </VStack>
272
+ <Grid columns={{minWidth: 200, repeat: 'fit'}} gap={4}>
273
+ {SCENES.map(item => (
274
+ <AspectRatio
275
+ key={item.alt}
276
+ ratio={4 / 5}
277
+ style={galleryImageClip}>
278
+ {item.scene}
279
+ </AspectRatio>
280
+ ))}
281
+ </Grid>
282
+ </VStack>
283
+ </LayoutContent>
284
+ }
285
+ />
286
+ );
287
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ type: 'page',
3
+ name: 'IDE Workspace',
4
+ description: 'Tabbed editor with terminal and properties',
5
+ category: 'tooling',
6
+ };