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,6 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // L > LC[p=0] > Ctr > S.transparent[p=6 mw=1100] > V[g=5] > (V[g=6] > (V[g=2] > (Hd"Payment Request"[level=1 t=display-2] + Tx"Review your order"[t=body]) + D) + H[g=8] > (SI > V[g=8] > (V[g=1] > (H[j=between] > (Hd"Sign in to check out"[level=2] + B"Sign in") + Tx"Sign in to track your order"[t=body]) + V[g=3] > (Hd"Contact information"[level=2] + TI"Email" + CB"Email offers") + V[g=3] > (Hd"Shipping information"[level=2] + G[c=2 g=3] > (TI"First Name" + TI"Last Name") + TI"Address" + G[c=2 g=3] > (TI"City" + TI"ZIP Code") + SE"State" + TI"Phone Number" + CB"Save information") + V[g=3] > (V[g=1] > (Hd"Delivery"[level=2] + Tx"Processing time"[t=body]) + RL"Delivery method" > RLI*2) + V[g=3] > (V[g=1] > (Hd"Payment method"[level=2] + Tx"Encrypted"[t=body]) + G[c=2 g=3] > (B"PayPal" + B"Google Pay") + D"Or pay with card" + TI"Card Number" + G[c=3 g=3] > (SE"Expiry Month" + SE"Expiry Year" + TI"CVC") + TI"Name on Card" + CB"Billing address") + V[g=3] > (Hd"Promo code"[level=2] + H[g=2] > (TI"Promo code" + B"Apply")) + V[g=3] > (Hd"Gift options"[level=2] + CB"Gift message" + TA"Gift message") + V[g=4] > (H[g=5] > (H[g=1] > (Ic + Tx))*3 + V[g=2] > (B"Place order"[primary] + B"Continue shopping") + D + H[g=4] > (Lk"Refund policy" + Lk"Privacy policy" + Lk"Terms" + Lk"Cancellations"))) + SI > C[p=4] > Col"Order Summary" > V[g=4] > (V[g=3] > V[g=3] > (H[g=3] > (Tmb + V[g=1] > (Tx"Obsidian Ritual Chalice" + Tx"Hand-carved"[t=supporting])) + D)*3 + V[g=3] > (Hd"Order Total"[level=3] + H[j=between] > (Tx"Subtotal" + Tx"$91.20") + D + H[j=between] > (Tx"Total"[t=large] + Tx"$91.20"[t=large]) + Bn"Free shipping over $300"))))
2
4
 
3
5
  import {useState, type CSSProperties} from 'react';
4
6
  import {
@@ -11,7 +13,7 @@ import {
11
13
  } from '@astryxdesign/core/Layout';
12
14
  import {Grid} from '@astryxdesign/core/Grid';
13
15
  import {Button} from '@astryxdesign/core/Button';
14
- import {Text} from '@astryxdesign/core/Text';
16
+ import {Text, Heading} from '@astryxdesign/core/Text';
15
17
  import {TextInput} from '@astryxdesign/core/TextInput';
16
18
  import {Selector} from '@astryxdesign/core/Selector';
17
19
  import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
@@ -21,13 +23,14 @@ import {TextArea} from '@astryxdesign/core/TextArea';
21
23
  import {Divider} from '@astryxdesign/core/Divider';
22
24
  import {Banner} from '@astryxdesign/core/Banner';
23
25
  import {Card} from '@astryxdesign/core/Card';
26
+ import {type SceneHue} from 'astryx-dracula/shared/scene-hues';
27
+ import {SceneTile} from 'astryx-dracula/shared/scene-tile';
24
28
  import {Collapsible} from '@astryxdesign/core/Collapsible';
25
- import {Badge} from '@astryxdesign/core/Badge';
29
+ import {StatusDot} from '@astryxdesign/core/StatusDot';
26
30
  import {NumberInput} from '@astryxdesign/core/NumberInput';
27
31
  import {useMediaQuery} from '@astryxdesign/core/hooks';
28
32
  import {Section} from '@astryxdesign/core/Section';
29
33
  import {Center} from '@astryxdesign/core/Center';
30
- import {Thumbnail} from '@astryxdesign/core/Thumbnail';
31
34
  import {Icon} from '@astryxdesign/core/Icon';
32
35
  import {ShieldCheck, Lock, CircleCheck, Truck} from 'lucide-react';
33
36
 
@@ -102,18 +105,14 @@ const US_STATES = [
102
105
  'Wyoming',
103
106
  ];
104
107
 
105
- // Product photos from the local template-assets set (committed to the
106
- // docsite; the CLI swaps these for an inline placeholder on scaffold).
107
- const ITEM_IMAGES: Record<string, {src: string}> = {
108
- '1': {
109
- src: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20400%20300%22%20preserveAspectRatio%3D%22xMidYMid%20slice%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%2321222C%22%2F%3E%3Cg%20transform%3D%22translate%28200%20150%29%22%20fill%3D%22none%22%20stroke%3D%22%236272A4%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%22-44%22%20y%3D%22-44%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2216%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%22-18%22%20r%3D%222.5%22%20fill%3D%22%236272A4%22%20stroke%3D%22none%22%2F%3E%3Cpath%20d%3D%22M-34%2030%20L-8%200%20L10%2018%20L20%208%20L34%2024%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E',
110
- },
111
- '2': {
112
- src: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20400%20300%22%20preserveAspectRatio%3D%22xMidYMid%20slice%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%2321222C%22%2F%3E%3Cg%20transform%3D%22translate%28200%20150%29%22%20fill%3D%22none%22%20stroke%3D%22%236272A4%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%22-44%22%20y%3D%22-44%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2216%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%22-18%22%20r%3D%222.5%22%20fill%3D%22%236272A4%22%20stroke%3D%22none%22%2F%3E%3Cpath%20d%3D%22M-34%2030%20L-8%200%20L10%2018%20L20%208%20L34%2024%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E',
113
- },
114
- '3': {
115
- src: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20400%20300%22%20preserveAspectRatio%3D%22xMidYMid%20slice%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%2321222C%22%2F%3E%3Cg%20transform%3D%22translate%28200%20150%29%22%20fill%3D%22none%22%20stroke%3D%22%236272A4%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%22-44%22%20y%3D%22-44%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2216%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%22-18%22%20r%3D%222.5%22%20fill%3D%22%236272A4%22%20stroke%3D%22none%22%2F%3E%3Cpath%20d%3D%22M-34%2030%20L-8%200%20L10%2018%20L20%208%20L34%2024%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E',
116
- },
108
+ // One Dracula accent per line item, from the fixed categorical vocabulary.
109
+ // Art lives in shared/scene-tile (lg fork: centered glyph on a flat field), so
110
+ // this map is typed `SceneHue`. `Record<string, string>` let any caller slip
111
+ // purple through to a scene; the union is what stops the next one.
112
+ const ITEM_HUES: Record<string, SceneHue> = {
113
+ '1': 'var(--dracula-cyan)',
114
+ '2': 'var(--dracula-pink)',
115
+ '3': 'var(--dracula-yellow)',
117
116
  };
118
117
 
119
118
  const ORDER_ITEMS = [
@@ -165,19 +164,15 @@ const summarySticky: CSSProperties = {
165
164
  };
166
165
  // On mobile the summary moves above the form.
167
166
  const summaryMobileOrder: CSSProperties = {order: -1};
168
- // Express-checkout buttons (Dracula tokens stand in for brand colors).
169
- const paypalButton: CSSProperties = {
170
- backgroundColor: 'var(--color-warning)',
171
- borderColor: 'var(--color-warning)',
167
+ // Line-item photo: fixed square frame; art lives in shared/scene-tile
168
+ // (lg fork). Astryx has no Image primitive (#2582), so the tile is inline
169
+ // SVG on brand tokens instead of a data-URI bitmap with baked-in hex.
170
+ const itemPhotoFrame: CSSProperties = {
171
+ width: 'var(--spacing-10)',
172
+ height: 'var(--spacing-10)',
173
+ overflow: 'clip',
174
+ flexShrink: 0,
172
175
  };
173
- // Google Pay button: widget-surface token stands in for the official dark
174
- // button background (no raw hex on brand surfaces).
175
- const gpayButton: CSSProperties = {
176
- backgroundColor: 'var(--color-widget-background)',
177
- borderColor: 'var(--color-widget-background)',
178
- };
179
- // Brand logos inside the express-checkout buttons.
180
- const brandLogo: CSSProperties = {height: 'var(--spacing-5)', width: 'auto'};
181
176
  // Accepted card-network marks (Visa/Mastercard/Amex), shared style.
182
177
  const cardLogo: CSSProperties = {
183
178
  height: 'var(--spacing-7)',
@@ -185,12 +180,231 @@ const cardLogo: CSSProperties = {
185
180
  borderRadius: 'var(--radius-element)',
186
181
  borderWidth: 'var(--border-width)',
187
182
  borderStyle: 'solid',
188
- borderColor: 'var(--color-border)',
183
+ borderColor: 'var(--color-separator)',
189
184
  backgroundColor: 'var(--color-background-surface)',
190
185
  };
191
186
 
187
+ function OrderItemPhoto({item}: {item: (typeof ORDER_ITEMS)[number]}) {
188
+ return (
189
+ <Card padding={0} style={itemPhotoFrame}>
190
+ <SceneTile label={item.name} hue={ITEM_HUES[item.id]} size="lg" />
191
+ </Card>
192
+ );
193
+ }
194
+
195
+ function OrderLineItem({
196
+ item,
197
+ qty,
198
+ onChangeQty,
199
+ }: {
200
+ item: (typeof ORDER_ITEMS)[number];
201
+ qty: number;
202
+ onChangeQty: (v: number) => void;
203
+ }) {
204
+ return (
205
+ <VStack gap={3}>
206
+ <HStack gap={3} vAlign="start">
207
+ <OrderItemPhoto item={item} />
208
+ <StackItem size="fill">
209
+ <VStack gap={1}>
210
+ <HStack gap={2} hAlign="between" vAlign="start">
211
+ <HStack gap={2} vAlign="center" wrap="wrap">
212
+ <Text type="body" weight="semibold">
213
+ {item.name}
214
+ </Text>
215
+ {item.limited && (
216
+ <HStack gap={1} vAlign="center">
217
+ <StatusDot variant="warning" label="Limited edition" />
218
+ <Text type="supporting" color="secondary">
219
+ Limited edition
220
+ </Text>
221
+ </HStack>
222
+ )}
223
+ </HStack>
224
+ <Text type="body" hasTabularNumbers>
225
+ {fmt(item.price)}
226
+ </Text>
227
+ </HStack>
228
+ <Text type="supporting" color="secondary">
229
+ {item.variant}
230
+ </Text>
231
+ <HStack gap={2} vAlign="end" wrap="wrap">
232
+ <NumberInput
233
+ label="Qty"
234
+ value={qty}
235
+ onChange={onChangeQty}
236
+ min={1}
237
+ max={10}
238
+ isIntegerOnly
239
+ />
240
+ <Link href={SELF_HREF} type="supporting">
241
+ Remove
242
+ </Link>
243
+ <Link href={SELF_HREF} type="supporting">
244
+ Save
245
+ </Link>
246
+ </HStack>
247
+ </VStack>
248
+ </StackItem>
249
+ </HStack>
250
+ <Divider />
251
+ </VStack>
252
+ );
253
+ }
254
+
255
+ function OrderSummaryCard({
256
+ quantities,
257
+ onChangeQty,
258
+ subtotal,
259
+ shipping,
260
+ tax,
261
+ total,
262
+ }: {
263
+ quantities: Record<string, number>;
264
+ onChangeQty: (id: string, v: number) => void;
265
+ subtotal: number;
266
+ shipping: number;
267
+ tax: number;
268
+ total: number;
269
+ }) {
270
+ return (
271
+ <Card padding={4}>
272
+ <VStack gap={4}>
273
+ {/* Accordion header — clickable on mobile only */}
274
+ <Collapsible trigger="Order Summary" defaultIsOpen={true}>
275
+ <VStack gap={4} style={summaryContent}>
276
+ {ORDER_ITEMS.map(item => (
277
+ <OrderLineItem
278
+ key={item.id}
279
+ item={item}
280
+ qty={quantities[item.id] ?? item.qty}
281
+ onChangeQty={v => onChangeQty(item.id, v)}
282
+ />
283
+ ))}
284
+ <OrderTotalSection
285
+ subtotal={subtotal}
286
+ shipping={shipping}
287
+ tax={tax}
288
+ total={total}
289
+ />
290
+ </VStack>
291
+ </Collapsible>
292
+ </VStack>
293
+ </Card>
294
+ );
295
+ }
296
+
297
+ function OrderTotalSection({
298
+ subtotal,
299
+ shipping,
300
+ tax,
301
+ total,
302
+ }: {
303
+ subtotal: number;
304
+ shipping: number;
305
+ tax: number;
306
+ total: number;
307
+ }) {
308
+ return (
309
+ <VStack gap={3}>
310
+ <Heading level={3}>Order Total</Heading>
311
+ <VStack gap={2}>
312
+ <HStack hAlign="between" vAlign="center">
313
+ <Text type="body" color="secondary">
314
+ Subtotal
315
+ </Text>
316
+ <Text type="body" hasTabularNumbers>
317
+ {fmt(subtotal)}
318
+ </Text>
319
+ </HStack>
320
+ <HStack hAlign="between" vAlign="center">
321
+ <Text type="body" color="secondary">
322
+ Shipping
323
+ </Text>
324
+ <Text type="body" hasTabularNumbers>
325
+ {fmt(shipping)}
326
+ </Text>
327
+ </HStack>
328
+ <HStack hAlign="between" vAlign="center">
329
+ <Text type="body" color="secondary">
330
+ Tax
331
+ </Text>
332
+ <Text type="body" hasTabularNumbers>
333
+ {fmt(tax)}
334
+ </Text>
335
+ </HStack>
336
+ </VStack>
337
+ <Divider />
338
+ <HStack hAlign="between" vAlign="center">
339
+ <Text type="large">
340
+ Total
341
+ </Text>
342
+ <Text type="large" hasTabularNumbers>
343
+ {fmt(total)}
344
+ </Text>
345
+ </HStack>
346
+ <Banner
347
+ status="info"
348
+ icon={<Icon icon={Truck} size="sm" />}
349
+ title="Free shipping on orders over $300"
350
+ />
351
+ </VStack>
352
+ );
353
+ }
354
+
355
+ const TRUST_ITEMS: Array<{icon: typeof ShieldCheck; label: string}> = [
356
+ {icon: ShieldCheck, label: 'Secure Payment'},
357
+ {icon: Lock, label: 'SSL Encrypted'},
358
+ {icon: CircleCheck, label: 'Free Returns'},
359
+ ];
360
+
361
+ const SELF_HREF = '#/templates/payment-form';
362
+
363
+ const POLICY_LINKS = [
364
+ 'Refund policy',
365
+ 'Privacy policy',
366
+ 'Terms of service',
367
+ 'Cancellations',
368
+ ];
369
+
370
+ function TrustBar() {
371
+ return (
372
+ <HStack gap={5} hAlign="center" wrap="wrap">
373
+ {TRUST_ITEMS.map(item => (
374
+ <HStack key={item.label} gap={1} vAlign="center">
375
+ <Icon icon={item.icon} size="sm" color="secondary" />
376
+ <Text type="supporting" color="secondary">
377
+ {item.label}
378
+ </Text>
379
+ </HStack>
380
+ ))}
381
+ </HStack>
382
+ );
383
+ }
384
+
385
+ function PolicyLinks() {
386
+ return (
387
+ <HStack gap={4} vAlign="center" wrap="wrap">
388
+ {POLICY_LINKS.map(label => (
389
+ <Link key={label} href={SELF_HREF} type="supporting">
390
+ {label}
391
+ </Link>
392
+ ))}
393
+ </HStack>
394
+ );
395
+ }
396
+
192
397
  export default function PaymentForm() {
193
- const isMobile = useMediaQuery('(max-width: 767px)');
398
+ // 768, not 767: eight sibling templates gate the same collapse at exactly
399
+ // 768 (`documentation-design.tsx:568`, `documentation-technical.tsx:61`,
400
+ // `editor.tsx:640`, `file-explorer.tsx:375`, `ide.tsx:244`,
401
+ // `settings-sidebar.tsx:127`, `settings.tsx:79`, `messaging-shell.tsx:393`),
402
+ // and this file's own second query below is 1024. At 767 this was the only
403
+ // off-by-one in the set, so at exactly a 768px viewport `isMobile` was
404
+ // false while `isStacked` was true: the outer Stack had already gone
405
+ // vertical (`:514`) and all six `columns={isMobile ? 1 : N}` grids below
406
+ // stayed wide inside it, including the 3-column expiry row at `:821`.
407
+ const isMobile = useMediaQuery('(max-width: 768px)');
194
408
  const isStacked = useMediaQuery('(max-width: 1024px)');
195
409
  const [firstName, setFirstName] = useState('');
196
410
  const [lastName, setLastName] = useState('');
@@ -292,9 +506,9 @@ export default function PaymentForm() {
292
506
  {/* Page header */}
293
507
  <VStack gap={6}>
294
508
  <VStack gap={2}>
295
- <Text type="display-1" as="h1">
509
+ <Heading level={1} type="display-2">
296
510
  Payment Request
297
- </Text>
511
+ </Heading>
298
512
  <Text type="body" color="secondary">
299
513
  Review your order and complete your purchase. All
300
514
  transactions are sealed with 256-bit SSL encryption.
@@ -314,13 +528,11 @@ export default function PaymentForm() {
314
528
  {/* Sign in */}
315
529
  <VStack gap={1}>
316
530
  <HStack gap={2} hAlign="between" vAlign="center">
317
- <Text type="large" weight="bold">
318
- Sign in to check out
319
- </Text>
531
+ <Heading level={2}>Sign in to check out</Heading>
320
532
  <Button
321
- label="Sign In"
533
+ label="Sign in"
322
534
  variant="secondary"
323
- size="sm"
535
+
324
536
  onClick={() => {}}
325
537
  />
326
538
  </HStack>
@@ -332,13 +544,12 @@ export default function PaymentForm() {
332
544
 
333
545
  {/* Contact Information */}
334
546
  <VStack gap={3}>
335
- <Text type="large" weight="bold">
336
- Contact Information
337
- </Text>
547
+ <Heading level={2}>Contact information</Heading>
338
548
  <TextInput
339
549
  size="lg"
340
550
  label="Email"
341
- placeholder="you@example.com"
551
+ isRequired
552
+ placeholder="you@castle.dracula"
342
553
  value={email}
343
554
  onChange={setEmail}
344
555
  status={
@@ -356,14 +567,13 @@ export default function PaymentForm() {
356
567
 
357
568
  {/* Shipping Information */}
358
569
  <VStack gap={3}>
359
- <Text type="large" weight="bold">
360
- Shipping Information
361
- </Text>
570
+ <Heading level={2}>Shipping information</Heading>
362
571
  <Grid columns={isMobile ? 1 : 2} gap={3}>
363
572
  <TextInput
364
573
  size="lg"
365
574
  label="First Name"
366
- placeholder="John"
575
+ isRequired
576
+ placeholder="Vlad"
367
577
  value={firstName}
368
578
  onChange={setFirstName}
369
579
  status={
@@ -375,7 +585,8 @@ export default function PaymentForm() {
375
585
  <TextInput
376
586
  size="lg"
377
587
  label="Last Name"
378
- placeholder="Doe"
588
+ isRequired
589
+ placeholder="Dracul"
379
590
  value={lastName}
380
591
  onChange={setLastName}
381
592
  status={
@@ -388,7 +599,8 @@ export default function PaymentForm() {
388
599
  <TextInput
389
600
  size="lg"
390
601
  label="Address"
391
- placeholder="123 Main Street"
602
+ isRequired
603
+ placeholder="666 Castle Way"
392
604
  value={address}
393
605
  onChange={setAddress}
394
606
  status={
@@ -401,7 +613,8 @@ export default function PaymentForm() {
401
613
  <TextInput
402
614
  size="lg"
403
615
  label="City"
404
- placeholder="New York"
616
+ isRequired
617
+ placeholder="Transylvania"
405
618
  value={city}
406
619
  onChange={setCity}
407
620
  status={
@@ -413,7 +626,8 @@ export default function PaymentForm() {
413
626
  <TextInput
414
627
  size="lg"
415
628
  label="ZIP Code"
416
- placeholder="10001"
629
+ isRequired
630
+ placeholder="40000"
417
631
  value={zip}
418
632
  onChange={setZip}
419
633
  status={
@@ -426,6 +640,7 @@ export default function PaymentForm() {
426
640
  <Selector
427
641
  size="lg"
428
642
  label="State"
643
+ isRequired
429
644
  placeholder="Select state"
430
645
  options={US_STATES}
431
646
  value={state}
@@ -439,6 +654,7 @@ export default function PaymentForm() {
439
654
  <TextInput
440
655
  size="lg"
441
656
  label="Phone Number"
657
+ isRequired
442
658
  placeholder="+1 (555) 123-4567"
443
659
  value={phone}
444
660
  onChange={setPhone}
@@ -459,9 +675,7 @@ export default function PaymentForm() {
459
675
  {/* Delivery */}
460
676
  <VStack gap={3}>
461
677
  <VStack gap={1}>
462
- <Text type="large" weight="bold">
463
- Delivery
464
- </Text>
678
+ <Heading level={2}>Delivery</Heading>
465
679
  <Text type="body" color="secondary">
466
680
  Please allow 1–3 business days processing time
467
681
  before your order ships.
@@ -477,7 +691,7 @@ export default function PaymentForm() {
477
691
  endContent={
478
692
  <Text
479
693
  type="body"
480
- weight="medium"
694
+ weight="semibold"
481
695
  hasTabularNumbers>
482
696
  $4.95
483
697
  </Text>
@@ -489,7 +703,7 @@ export default function PaymentForm() {
489
703
  endContent={
490
704
  <Text
491
705
  type="body"
492
- weight="medium"
706
+ weight="semibold"
493
707
  hasTabularNumbers>
494
708
  $9.95
495
709
  </Text>
@@ -501,9 +715,7 @@ export default function PaymentForm() {
501
715
  {/* Payment Method */}
502
716
  <VStack gap={3}>
503
717
  <VStack gap={1}>
504
- <Text type="large" weight="bold">
505
- Payment Method
506
- </Text>
718
+ <Heading level={2}>Payment method</Heading>
507
719
  <Text type="body" color="secondary">
508
720
  All transactions are secure and encrypted.
509
721
  </Text>
@@ -515,68 +727,95 @@ export default function PaymentForm() {
515
727
  {/* PayPal */}
516
728
  <Button
517
729
  label="PayPal"
518
- variant="primary"
730
+ variant="secondary"
519
731
  size="lg"
520
732
  onClick={() => {}}
521
- style={paypalButton}>
522
- <img
523
- src="https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png"
524
- alt="PayPal"
525
- style={brandLogo}
526
- />
527
- </Button>
733
+ />
528
734
  {/* Google Pay */}
529
735
  <Button
530
736
  label="Google Pay"
531
- variant="primary"
737
+ variant="secondary"
532
738
  size="lg"
533
739
  onClick={() => {}}
534
- style={gpayButton}>
535
- <img
536
- src="https://pay.google.com/about/static_kcs/images/logos/google-pay-logo.svg"
537
- alt="Google Pay"
538
- style={brandLogo}
539
- />
540
- </Button>
740
+ />
541
741
  </Grid>
542
742
  </VStack>
543
743
 
544
- {/* OR divider */}
545
- <HStack gap={3} vAlign="center">
546
- <StackItem size="fill">
547
- <Divider />
548
- </StackItem>
549
- <Text type="supporting" color="secondary">
550
- OR
551
- </Text>
552
- <StackItem size="fill">
553
- <Divider />
554
- </StackItem>
555
- </HStack>
744
+ <Divider label="Or pay with card" />
556
745
 
557
746
  {/* Credit card fields */}
558
747
  <VStack gap={3}>
559
- {/* Card type icons */}
748
+ {/* Card type icons — inline Dracula marks on
749
+ brand tokens; no vendor hotlinks. */}
560
750
  <HStack gap={1.5} vAlign="center">
561
- <img
562
- src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/visa.svg"
563
- alt="Visa"
564
- style={cardLogo}
565
- />
566
- <img
567
- src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/mastercard.svg"
568
- alt="Mastercard"
569
- style={cardLogo}
570
- />
571
- <img
572
- src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/amex.svg"
573
- alt="Amex"
574
- style={cardLogo}
575
- />
751
+ <svg
752
+ viewBox="0 0 48 32"
753
+ role="img"
754
+ aria-label="Visa"
755
+ style={cardLogo}>
756
+ <rect
757
+ width="48"
758
+ height="32"
759
+ fill="var(--color-background-surface)"
760
+ />
761
+ <text
762
+ x="24"
763
+ y="21"
764
+ textAnchor="middle"
765
+ fontSize="10"
766
+ fontStyle="italic"
767
+ fill="var(--dracula-comment)">
768
+ VISA
769
+ </text>
770
+ </svg>
771
+ <svg
772
+ viewBox="0 0 48 32"
773
+ role="img"
774
+ aria-label="Mastercard"
775
+ style={cardLogo}>
776
+ <rect
777
+ width="48"
778
+ height="32"
779
+ fill="var(--color-background-surface)"
780
+ />
781
+ <circle
782
+ cx="19"
783
+ cy="16"
784
+ r="8"
785
+ fill="var(--dracula-red)"
786
+ />
787
+ <circle
788
+ cx="29"
789
+ cy="16"
790
+ r="8"
791
+ fill="var(--dracula-orange)"
792
+ opacity={0.85}
793
+ />
794
+ </svg>
795
+ <svg
796
+ viewBox="0 0 48 32"
797
+ role="img"
798
+ aria-label="Amex"
799
+ style={cardLogo}>
800
+ <rect
801
+ width="48"
802
+ height="32"
803
+ fill="var(--color-background-surface)"
804
+ />
805
+ <text
806
+ x="24"
807
+ y="21"
808
+ textAnchor="middle"
809
+ fontSize="10"
810
+ fill="var(--dracula-cyan)">
811
+ AMEX
812
+ </text>
813
+ </svg>
576
814
  </HStack>
577
815
  <TextInput
578
816
  size="lg"
579
817
  label="Card Number"
818
+ isRequired
580
819
  placeholder="1234 5678 9012 3456"
581
820
  value={cardNumber}
582
821
  onChange={setCardNumber}
@@ -590,6 +829,7 @@ export default function PaymentForm() {
590
829
  <Selector
591
830
  size="lg"
592
831
  label="Expiry Month"
832
+ isRequired
593
833
  placeholder="MM"
594
834
  options={MONTHS}
595
835
  value={expiry}
@@ -603,6 +843,7 @@ export default function PaymentForm() {
603
843
  <Selector
604
844
  size="lg"
605
845
  label="Expiry Year"
846
+ isRequired
606
847
  placeholder="YY"
607
848
  options={YEARS}
608
849
  value={expYear}
@@ -616,6 +857,7 @@ export default function PaymentForm() {
616
857
  <TextInput
617
858
  size="lg"
618
859
  label="CVC"
860
+ isRequired
619
861
  placeholder="123"
620
862
  value={cvc}
621
863
  onChange={setCvc}
@@ -630,7 +872,8 @@ export default function PaymentForm() {
630
872
  <TextInput
631
873
  size="lg"
632
874
  label="Name on Card"
633
- placeholder="John Doe"
875
+ isRequired
876
+ placeholder="Vlad Dracul"
634
877
  value={cardName}
635
878
  onChange={setCardName}
636
879
  status={
@@ -649,7 +892,8 @@ export default function PaymentForm() {
649
892
  <TextInput
650
893
  size="lg"
651
894
  label="Address"
652
- placeholder="123 Main Street"
895
+ isRequired
896
+ placeholder="666 Castle Way"
653
897
  value={billingAddress}
654
898
  onChange={setBillingAddress}
655
899
  status={
@@ -665,7 +909,8 @@ export default function PaymentForm() {
665
909
  <TextInput
666
910
  size="lg"
667
911
  label="City"
668
- placeholder="New York"
912
+ isRequired
913
+ placeholder="Transylvania"
669
914
  value={billingCity}
670
915
  onChange={setBillingCity}
671
916
  status={
@@ -680,7 +925,8 @@ export default function PaymentForm() {
680
925
  <TextInput
681
926
  size="lg"
682
927
  label="ZIP Code"
683
- placeholder="10001"
928
+ isRequired
929
+ placeholder="40000"
684
930
  value={billingZip}
685
931
  onChange={setBillingZip}
686
932
  status={
@@ -696,6 +942,7 @@ export default function PaymentForm() {
696
942
  <Selector
697
943
  size="lg"
698
944
  label="State"
945
+ isRequired
699
946
  placeholder="Select state"
700
947
  options={US_STATES}
701
948
  value={billingState}
@@ -716,9 +963,7 @@ export default function PaymentForm() {
716
963
 
717
964
  {/* Promo Code */}
718
965
  <VStack gap={3}>
719
- <Text type="large" weight="bold">
720
- Promo Code
721
- </Text>
966
+ <Heading level={2}>Promo code</Heading>
722
967
  <HStack gap={2} vAlign="end">
723
968
  <StackItem size="fill">
724
969
  <TextInput
@@ -741,9 +986,7 @@ export default function PaymentForm() {
741
986
 
742
987
  {/* Gift Options */}
743
988
  <VStack gap={3}>
744
- <Text type="large" weight="bold">
745
- Gift Options
746
- </Text>
989
+ <Heading level={2}>Gift options</Heading>
747
990
  <CheckboxInput
748
991
  label="Add a gift message"
749
992
  value={addGiftMessage}
@@ -755,23 +998,20 @@ export default function PaymentForm() {
755
998
  <TextInput
756
999
  size="lg"
757
1000
  label="To"
758
- isLabelHidden
759
- placeholder="To"
1001
+ placeholder="Recipient name"
760
1002
  value={giftTo}
761
1003
  onChange={setGiftTo}
762
1004
  />
763
1005
  <TextInput
764
1006
  size="lg"
765
1007
  label="From"
766
- isLabelHidden
767
- placeholder="From"
1008
+ placeholder="Your name"
768
1009
  value={giftFrom}
769
1010
  onChange={setGiftFrom}
770
1011
  />
771
1012
  </Grid>
772
1013
  <TextArea
773
1014
  label="Gift message"
774
- isLabelHidden
775
1015
  placeholder="Write something here"
776
1016
  value={giftMessage}
777
1017
  onChange={setGiftMessage}
@@ -782,48 +1022,17 @@ export default function PaymentForm() {
782
1022
 
783
1023
  {/* Trust bar + CTAs + policy links */}
784
1024
  <VStack gap={4}>
785
- <HStack gap={5} hAlign="center" wrap="wrap">
786
- <HStack gap={1} vAlign="center">
787
- <Icon
788
- icon={ShieldCheck}
789
- size="sm"
790
- color="secondary"
791
- />
792
- <Text type="supporting" color="secondary">
793
- Secure Payment
794
- </Text>
795
- </HStack>
796
- <HStack gap={1} vAlign="center">
797
- <Icon
798
- icon={Lock}
799
- size="sm"
800
- color="secondary"
801
- />
802
- <Text type="supporting" color="secondary">
803
- SSL Encrypted
804
- </Text>
805
- </HStack>
806
- <HStack gap={1} vAlign="center">
807
- <Icon
808
- icon={CircleCheck}
809
- size="sm"
810
- color="secondary"
811
- />
812
- <Text type="supporting" color="secondary">
813
- Free Returns
814
- </Text>
815
- </HStack>
816
- </HStack>
1025
+ <TrustBar />
817
1026
  <VStack gap={2}>
818
1027
  <Button
819
- label="Place Order"
1028
+ label="Place order"
820
1029
  variant="primary"
821
1030
  size="lg"
822
1031
  style={fullWidth}
823
1032
  onClick={() => setSubmitted(true)}
824
1033
  />
825
1034
  <Button
826
- label="Continue Shopping"
1035
+ label="Continue shopping"
827
1036
  variant="secondary"
828
1037
  size="lg"
829
1038
  style={fullWidth}
@@ -831,20 +1040,7 @@ export default function PaymentForm() {
831
1040
  />
832
1041
  </VStack>
833
1042
  <Divider />
834
- <HStack gap={4} vAlign="center" wrap="wrap">
835
- <Link href="#/templates/payment-form" type="supporting">
836
- Refund policy
837
- </Link>
838
- <Link href="#/templates/payment-form" type="supporting">
839
- Privacy policy
840
- </Link>
841
- <Link href="#/templates/payment-form" type="supporting">
842
- Terms of service
843
- </Link>
844
- <Link href="#/templates/payment-form" type="supporting">
845
- Cancellations
846
- </Link>
847
- </HStack>
1043
+ <PolicyLinks />
848
1044
  </VStack>
849
1045
  </VStack>
850
1046
  </StackItem>
@@ -852,132 +1048,16 @@ export default function PaymentForm() {
852
1048
  <StackItem
853
1049
  size="fill"
854
1050
  style={isStacked ? summaryMobileOrder : summarySticky}>
855
- <Card padding={5}>
856
- <VStack gap={4}>
857
- {/* Accordion header — clickable on mobile only */}
858
- <Collapsible
859
- trigger="Order Summary"
860
- defaultIsOpen={true}>
861
- <VStack gap={4} style={summaryContent}>
862
- {/* Line items */}
863
- {ORDER_ITEMS.map(item => (
864
- <VStack key={item.id} gap={3}>
865
- <HStack gap={3} vAlign="start">
866
- <Thumbnail
867
- src={ITEM_IMAGES[item.id].src}
868
- alt={item.name}
869
- />
870
- <StackItem size="fill">
871
- <VStack gap={1}>
872
- <HStack
873
- gap={2}
874
- hAlign="between"
875
- vAlign="start">
876
- <HStack gap={2} vAlign="center" wrap="wrap">
877
- <Text type="body" weight="medium">
878
- {item.name}
879
- </Text>
880
- {item.limited && (
881
- <Badge
882
- variant="yellow"
883
- label="LIMITED EDITION"
884
- />
885
- )}
886
- </HStack>
887
- <Text
888
- type="body"
889
- weight="bold"
890
- hasTabularNumbers>
891
- {fmt(item.price)}
892
- </Text>
893
- </HStack>
894
- <Text type="supporting" color="secondary">
895
- {item.variant}
896
- </Text>
897
- <HStack gap={2} vAlign="end" wrap="wrap">
898
- <NumberInput
899
- label="Qty"
900
- value={
901
- quantities[item.id] ?? item.qty
902
- }
903
- onChange={v =>
904
- setQuantities(q => ({
905
- ...q,
906
- [item.id]: v,
907
- }))
908
- }
909
- min={1}
910
- max={10}
911
- isIntegerOnly
912
- />
913
- <Link href="#/templates/payment-form" type="supporting">
914
- Remove
915
- </Link>
916
- <Link href="#/templates/payment-form" type="supporting">
917
- Save
918
- </Link>
919
- </HStack>
920
- </VStack>
921
- </StackItem>
922
- </HStack>
923
- <Divider />
924
- </VStack>
925
- ))}
926
-
927
- {/* Order total subsection */}
928
- <VStack gap={3}>
929
- <Text type="large" weight="bold">
930
- Order Total
931
- </Text>
932
- <VStack gap={2}>
933
- <HStack hAlign="between" vAlign="center">
934
- <Text type="body" color="secondary">
935
- Subtotal
936
- </Text>
937
- <Text type="body" hasTabularNumbers>
938
- {fmt(subtotal)}
939
- </Text>
940
- </HStack>
941
- <HStack hAlign="between" vAlign="center">
942
- <Text type="body" color="secondary">
943
- Shipping
944
- </Text>
945
- <Text type="body" hasTabularNumbers>
946
- {fmt(shipping)}
947
- </Text>
948
- </HStack>
949
- <HStack hAlign="between" vAlign="center">
950
- <Text type="body" color="secondary">
951
- Tax
952
- </Text>
953
- <Text type="body" hasTabularNumbers>
954
- {fmt(tax)}
955
- </Text>
956
- </HStack>
957
- </VStack>
958
- <Divider />
959
- <HStack hAlign="between" vAlign="center">
960
- <Text type="large" weight="bold">
961
- Total
962
- </Text>
963
- <Text
964
- type="large"
965
- weight="bold"
966
- hasTabularNumbers>
967
- {fmt(total)}
968
- </Text>
969
- </HStack>
970
- <Banner
971
- status="info"
972
- icon={<Icon icon={Truck} size="sm" />}
973
- title="Free shipping on orders over $300"
974
- />
975
- </VStack>
976
- </VStack>
977
- </Collapsible>
978
- </VStack>
979
- {/* end outer card VStack gap={4} */}
980
- </Card>
1051
+ <OrderSummaryCard
1052
+ quantities={quantities}
1053
+ onChangeQty={(id, v) =>
1054
+ setQuantities(q => ({...q, [id]: v}))
1055
+ }
1056
+ subtotal={subtotal}
1057
+ shipping={shipping}
1058
+ tax={tax}
1059
+ total={total}
1060
+ />
981
1061
  </StackItem>
982
1062
  </Stack>
983
1063
  </VStack>