astryx-dracula 0.2.1 → 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 +17 -12
  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 +66 -33
  31. package/templates/ai-chat.tsx +36 -19
  32. package/templates/blank.tsx +4 -3
  33. package/templates/centered-hero.tsx +14 -125
  34. package/templates/classic-gallery.tsx +20 -66
  35. package/templates/contact-form.tsx +59 -39
  36. package/templates/dashboard-portfolio.tsx +143 -226
  37. package/templates/dashboard.tsx +122 -122
  38. package/templates/detail-page.tsx +95 -91
  39. package/templates/documentation-design.template.mjs +1 -1
  40. package/templates/documentation-design.tsx +83 -96
  41. package/templates/documentation-technical.template.mjs +1 -1
  42. package/templates/documentation-technical.tsx +29 -19
  43. package/templates/documentation.template.mjs +1 -1
  44. package/templates/documentation.tsx +52 -74
  45. package/templates/editor.tsx +112 -84
  46. package/templates/file-explorer.tsx +131 -84
  47. package/templates/form-two-column.tsx +61 -125
  48. package/templates/gallery-hero.tsx +15 -171
  49. package/templates/ide.tsx +22 -20
  50. package/templates/incident-console.tsx +104 -70
  51. package/templates/kanban-board.tsx +99 -42
  52. package/templates/library.template.mjs +2 -2
  53. package/templates/library.tsx +37 -42
  54. package/templates/login-card.tsx +58 -87
  55. package/templates/login-split.tsx +88 -126
  56. package/templates/login-sso.tsx +105 -64
  57. package/templates/login.tsx +107 -53
  58. package/templates/messaging-shell.tsx +171 -144
  59. package/templates/mixed-gallery.tsx +29 -130
  60. package/templates/payment-form.tsx +368 -290
  61. package/templates/product-detail.tsx +39 -58
  62. package/templates/product-gallery.tsx +30 -41
  63. package/templates/product-tour.template.mjs +6 -0
  64. package/templates/product-tour.tsx +916 -0
  65. package/templates/settings-dialog.tsx +135 -282
  66. package/templates/settings-sidebar.tsx +178 -268
  67. package/templates/settings.tsx +41 -14
  68. package/templates/shell-nav.tsx +31 -24
  69. package/templates/shell-side-nav.tsx +29 -20
  70. package/templates/shell-top-nav.tsx +80 -44
  71. package/templates/side-gallery.tsx +38 -94
  72. package/templates/table-grouped.tsx +62 -34
  73. package/templates/table-page-chart.tsx +23 -302
  74. package/templates/table-page-heatmap-status.tsx +156 -92
  75. package/templates/table-page-shoe-store-heatmap.tsx +18 -293
  76. package/templates/table-page.tsx +23 -7
  77. package/templates/table.tsx +23 -8
  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 +299 -237
  82. package/theme.css +76 -14
  83. package/tokens.css +425 -30
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
  // XLE (canonical structure, validated with `bunx astryx layout check`):
3
- // L > LC[p=0] > Ctr > (S > V[g=5] > (V[g=1] > Hd"Payment Request"[level=1] + Tx"Review your order"[t=body]) + (H > (SI > V[g=6] > (C > V[g=3] > Hd"Sign in"[level=3] + B) + (F > TI"Email" + TI"Address" + B.primary"Place Order")) + (SI > C[p=5] > (Col > V[g=4] > (H > Tmb + V[g=1] > Tx + Tx)*3 + D + (H > Tx + Tx) + Bn.info))))
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"))))
4
4
 
5
5
  import {useState, type CSSProperties} from 'react';
6
6
  import {
@@ -13,7 +13,7 @@ import {
13
13
  } from '@astryxdesign/core/Layout';
14
14
  import {Grid} from '@astryxdesign/core/Grid';
15
15
  import {Button} from '@astryxdesign/core/Button';
16
- import {Text} from '@astryxdesign/core/Text';
16
+ import {Text, Heading} from '@astryxdesign/core/Text';
17
17
  import {TextInput} from '@astryxdesign/core/TextInput';
18
18
  import {Selector} from '@astryxdesign/core/Selector';
19
19
  import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
@@ -23,13 +23,14 @@ import {TextArea} from '@astryxdesign/core/TextArea';
23
23
  import {Divider} from '@astryxdesign/core/Divider';
24
24
  import {Banner} from '@astryxdesign/core/Banner';
25
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';
26
28
  import {Collapsible} from '@astryxdesign/core/Collapsible';
27
- import {Badge} from '@astryxdesign/core/Badge';
29
+ import {StatusDot} from '@astryxdesign/core/StatusDot';
28
30
  import {NumberInput} from '@astryxdesign/core/NumberInput';
29
31
  import {useMediaQuery} from '@astryxdesign/core/hooks';
30
32
  import {Section} from '@astryxdesign/core/Section';
31
33
  import {Center} from '@astryxdesign/core/Center';
32
- import {Thumbnail} from '@astryxdesign/core/Thumbnail';
33
34
  import {Icon} from '@astryxdesign/core/Icon';
34
35
  import {ShieldCheck, Lock, CircleCheck, Truck} from 'lucide-react';
35
36
 
@@ -104,18 +105,14 @@ const US_STATES = [
104
105
  'Wyoming',
105
106
  ];
106
107
 
107
- // Product photos from the local template-assets set (committed to the
108
- // docsite; the CLI swaps these for an inline placeholder on scaffold).
109
- const ITEM_IMAGES: Record<string, {src: string}> = {
110
- '1': {
111
- 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',
112
- },
113
- '2': {
114
- 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',
115
- },
116
- '3': {
117
- 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',
118
- },
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)',
119
116
  };
120
117
 
121
118
  const ORDER_ITEMS = [
@@ -167,19 +164,15 @@ const summarySticky: CSSProperties = {
167
164
  };
168
165
  // On mobile the summary moves above the form.
169
166
  const summaryMobileOrder: CSSProperties = {order: -1};
170
- // Express-checkout buttons (Dracula tokens stand in for brand colors).
171
- const paypalButton: CSSProperties = {
172
- backgroundColor: 'var(--color-warning)',
173
- 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,
174
175
  };
175
- // Google Pay button: widget-surface token stands in for the official dark
176
- // button background (no raw hex on brand surfaces).
177
- const gpayButton: CSSProperties = {
178
- backgroundColor: 'var(--color-widget-background)',
179
- borderColor: 'var(--color-widget-background)',
180
- };
181
- // Brand logos inside the express-checkout buttons.
182
- const brandLogo: CSSProperties = {height: 'var(--spacing-5)', width: 'auto'};
183
176
  // Accepted card-network marks (Visa/Mastercard/Amex), shared style.
184
177
  const cardLogo: CSSProperties = {
185
178
  height: 'var(--spacing-7)',
@@ -187,12 +180,231 @@ const cardLogo: CSSProperties = {
187
180
  borderRadius: 'var(--radius-element)',
188
181
  borderWidth: 'var(--border-width)',
189
182
  borderStyle: 'solid',
190
- borderColor: 'var(--color-border)',
183
+ borderColor: 'var(--color-separator)',
191
184
  backgroundColor: 'var(--color-background-surface)',
192
185
  };
193
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
+
194
397
  export default function PaymentForm() {
195
- 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)');
196
408
  const isStacked = useMediaQuery('(max-width: 1024px)');
197
409
  const [firstName, setFirstName] = useState('');
198
410
  const [lastName, setLastName] = useState('');
@@ -294,9 +506,9 @@ export default function PaymentForm() {
294
506
  {/* Page header */}
295
507
  <VStack gap={6}>
296
508
  <VStack gap={2}>
297
- <Text type="display-1" as="h1">
509
+ <Heading level={1} type="display-2">
298
510
  Payment Request
299
- </Text>
511
+ </Heading>
300
512
  <Text type="body" color="secondary">
301
513
  Review your order and complete your purchase. All
302
514
  transactions are sealed with 256-bit SSL encryption.
@@ -316,13 +528,11 @@ export default function PaymentForm() {
316
528
  {/* Sign in */}
317
529
  <VStack gap={1}>
318
530
  <HStack gap={2} hAlign="between" vAlign="center">
319
- <Text type="large" weight="bold">
320
- Sign in to check out
321
- </Text>
531
+ <Heading level={2}>Sign in to check out</Heading>
322
532
  <Button
323
- label="Sign In"
533
+ label="Sign in"
324
534
  variant="secondary"
325
- size="sm"
535
+
326
536
  onClick={() => {}}
327
537
  />
328
538
  </HStack>
@@ -334,13 +544,12 @@ export default function PaymentForm() {
334
544
 
335
545
  {/* Contact Information */}
336
546
  <VStack gap={3}>
337
- <Text type="large" weight="bold">
338
- Contact Information
339
- </Text>
547
+ <Heading level={2}>Contact information</Heading>
340
548
  <TextInput
341
549
  size="lg"
342
550
  label="Email"
343
- placeholder="you@example.com"
551
+ isRequired
552
+ placeholder="you@castle.dracula"
344
553
  value={email}
345
554
  onChange={setEmail}
346
555
  status={
@@ -358,14 +567,13 @@ export default function PaymentForm() {
358
567
 
359
568
  {/* Shipping Information */}
360
569
  <VStack gap={3}>
361
- <Text type="large" weight="bold">
362
- Shipping Information
363
- </Text>
570
+ <Heading level={2}>Shipping information</Heading>
364
571
  <Grid columns={isMobile ? 1 : 2} gap={3}>
365
572
  <TextInput
366
573
  size="lg"
367
574
  label="First Name"
368
- placeholder="John"
575
+ isRequired
576
+ placeholder="Vlad"
369
577
  value={firstName}
370
578
  onChange={setFirstName}
371
579
  status={
@@ -377,7 +585,8 @@ export default function PaymentForm() {
377
585
  <TextInput
378
586
  size="lg"
379
587
  label="Last Name"
380
- placeholder="Doe"
588
+ isRequired
589
+ placeholder="Dracul"
381
590
  value={lastName}
382
591
  onChange={setLastName}
383
592
  status={
@@ -390,7 +599,8 @@ export default function PaymentForm() {
390
599
  <TextInput
391
600
  size="lg"
392
601
  label="Address"
393
- placeholder="123 Main Street"
602
+ isRequired
603
+ placeholder="666 Castle Way"
394
604
  value={address}
395
605
  onChange={setAddress}
396
606
  status={
@@ -403,7 +613,8 @@ export default function PaymentForm() {
403
613
  <TextInput
404
614
  size="lg"
405
615
  label="City"
406
- placeholder="New York"
616
+ isRequired
617
+ placeholder="Transylvania"
407
618
  value={city}
408
619
  onChange={setCity}
409
620
  status={
@@ -415,7 +626,8 @@ export default function PaymentForm() {
415
626
  <TextInput
416
627
  size="lg"
417
628
  label="ZIP Code"
418
- placeholder="10001"
629
+ isRequired
630
+ placeholder="40000"
419
631
  value={zip}
420
632
  onChange={setZip}
421
633
  status={
@@ -428,6 +640,7 @@ export default function PaymentForm() {
428
640
  <Selector
429
641
  size="lg"
430
642
  label="State"
643
+ isRequired
431
644
  placeholder="Select state"
432
645
  options={US_STATES}
433
646
  value={state}
@@ -441,6 +654,7 @@ export default function PaymentForm() {
441
654
  <TextInput
442
655
  size="lg"
443
656
  label="Phone Number"
657
+ isRequired
444
658
  placeholder="+1 (555) 123-4567"
445
659
  value={phone}
446
660
  onChange={setPhone}
@@ -461,9 +675,7 @@ export default function PaymentForm() {
461
675
  {/* Delivery */}
462
676
  <VStack gap={3}>
463
677
  <VStack gap={1}>
464
- <Text type="large" weight="bold">
465
- Delivery
466
- </Text>
678
+ <Heading level={2}>Delivery</Heading>
467
679
  <Text type="body" color="secondary">
468
680
  Please allow 1–3 business days processing time
469
681
  before your order ships.
@@ -479,7 +691,7 @@ export default function PaymentForm() {
479
691
  endContent={
480
692
  <Text
481
693
  type="body"
482
- weight="medium"
694
+ weight="semibold"
483
695
  hasTabularNumbers>
484
696
  $4.95
485
697
  </Text>
@@ -491,7 +703,7 @@ export default function PaymentForm() {
491
703
  endContent={
492
704
  <Text
493
705
  type="body"
494
- weight="medium"
706
+ weight="semibold"
495
707
  hasTabularNumbers>
496
708
  $9.95
497
709
  </Text>
@@ -503,9 +715,7 @@ export default function PaymentForm() {
503
715
  {/* Payment Method */}
504
716
  <VStack gap={3}>
505
717
  <VStack gap={1}>
506
- <Text type="large" weight="bold">
507
- Payment Method
508
- </Text>
718
+ <Heading level={2}>Payment method</Heading>
509
719
  <Text type="body" color="secondary">
510
720
  All transactions are secure and encrypted.
511
721
  </Text>
@@ -517,68 +727,95 @@ export default function PaymentForm() {
517
727
  {/* PayPal */}
518
728
  <Button
519
729
  label="PayPal"
520
- variant="primary"
730
+ variant="secondary"
521
731
  size="lg"
522
732
  onClick={() => {}}
523
- style={paypalButton}>
524
- <img
525
- src="https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png"
526
- alt="PayPal"
527
- style={brandLogo}
528
- />
529
- </Button>
733
+ />
530
734
  {/* Google Pay */}
531
735
  <Button
532
736
  label="Google Pay"
533
- variant="primary"
737
+ variant="secondary"
534
738
  size="lg"
535
739
  onClick={() => {}}
536
- style={gpayButton}>
537
- <img
538
- src="https://pay.google.com/about/static_kcs/images/logos/google-pay-logo.svg"
539
- alt="Google Pay"
540
- style={brandLogo}
541
- />
542
- </Button>
740
+ />
543
741
  </Grid>
544
742
  </VStack>
545
743
 
546
- {/* OR divider */}
547
- <HStack gap={3} vAlign="center">
548
- <StackItem size="fill">
549
- <Divider />
550
- </StackItem>
551
- <Text type="supporting" color="secondary">
552
- OR
553
- </Text>
554
- <StackItem size="fill">
555
- <Divider />
556
- </StackItem>
557
- </HStack>
744
+ <Divider label="Or pay with card" />
558
745
 
559
746
  {/* Credit card fields */}
560
747
  <VStack gap={3}>
561
- {/* Card type icons */}
748
+ {/* Card type icons — inline Dracula marks on
749
+ brand tokens; no vendor hotlinks. */}
562
750
  <HStack gap={1.5} vAlign="center">
563
- <img
564
- src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/visa.svg"
565
- alt="Visa"
566
- style={cardLogo}
567
- />
568
- <img
569
- src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/mastercard.svg"
570
- alt="Mastercard"
571
- style={cardLogo}
572
- />
573
- <img
574
- src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/amex.svg"
575
- alt="Amex"
576
- style={cardLogo}
577
- />
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>
578
814
  </HStack>
579
815
  <TextInput
580
816
  size="lg"
581
817
  label="Card Number"
818
+ isRequired
582
819
  placeholder="1234 5678 9012 3456"
583
820
  value={cardNumber}
584
821
  onChange={setCardNumber}
@@ -592,6 +829,7 @@ export default function PaymentForm() {
592
829
  <Selector
593
830
  size="lg"
594
831
  label="Expiry Month"
832
+ isRequired
595
833
  placeholder="MM"
596
834
  options={MONTHS}
597
835
  value={expiry}
@@ -605,6 +843,7 @@ export default function PaymentForm() {
605
843
  <Selector
606
844
  size="lg"
607
845
  label="Expiry Year"
846
+ isRequired
608
847
  placeholder="YY"
609
848
  options={YEARS}
610
849
  value={expYear}
@@ -618,6 +857,7 @@ export default function PaymentForm() {
618
857
  <TextInput
619
858
  size="lg"
620
859
  label="CVC"
860
+ isRequired
621
861
  placeholder="123"
622
862
  value={cvc}
623
863
  onChange={setCvc}
@@ -632,7 +872,8 @@ export default function PaymentForm() {
632
872
  <TextInput
633
873
  size="lg"
634
874
  label="Name on Card"
635
- placeholder="John Doe"
875
+ isRequired
876
+ placeholder="Vlad Dracul"
636
877
  value={cardName}
637
878
  onChange={setCardName}
638
879
  status={
@@ -651,7 +892,8 @@ export default function PaymentForm() {
651
892
  <TextInput
652
893
  size="lg"
653
894
  label="Address"
654
- placeholder="123 Main Street"
895
+ isRequired
896
+ placeholder="666 Castle Way"
655
897
  value={billingAddress}
656
898
  onChange={setBillingAddress}
657
899
  status={
@@ -667,7 +909,8 @@ export default function PaymentForm() {
667
909
  <TextInput
668
910
  size="lg"
669
911
  label="City"
670
- placeholder="New York"
912
+ isRequired
913
+ placeholder="Transylvania"
671
914
  value={billingCity}
672
915
  onChange={setBillingCity}
673
916
  status={
@@ -682,7 +925,8 @@ export default function PaymentForm() {
682
925
  <TextInput
683
926
  size="lg"
684
927
  label="ZIP Code"
685
- placeholder="10001"
928
+ isRequired
929
+ placeholder="40000"
686
930
  value={billingZip}
687
931
  onChange={setBillingZip}
688
932
  status={
@@ -698,6 +942,7 @@ export default function PaymentForm() {
698
942
  <Selector
699
943
  size="lg"
700
944
  label="State"
945
+ isRequired
701
946
  placeholder="Select state"
702
947
  options={US_STATES}
703
948
  value={billingState}
@@ -718,9 +963,7 @@ export default function PaymentForm() {
718
963
 
719
964
  {/* Promo Code */}
720
965
  <VStack gap={3}>
721
- <Text type="large" weight="bold">
722
- Promo Code
723
- </Text>
966
+ <Heading level={2}>Promo code</Heading>
724
967
  <HStack gap={2} vAlign="end">
725
968
  <StackItem size="fill">
726
969
  <TextInput
@@ -743,9 +986,7 @@ export default function PaymentForm() {
743
986
 
744
987
  {/* Gift Options */}
745
988
  <VStack gap={3}>
746
- <Text type="large" weight="bold">
747
- Gift Options
748
- </Text>
989
+ <Heading level={2}>Gift options</Heading>
749
990
  <CheckboxInput
750
991
  label="Add a gift message"
751
992
  value={addGiftMessage}
@@ -757,23 +998,20 @@ export default function PaymentForm() {
757
998
  <TextInput
758
999
  size="lg"
759
1000
  label="To"
760
- isLabelHidden
761
- placeholder="To"
1001
+ placeholder="Recipient name"
762
1002
  value={giftTo}
763
1003
  onChange={setGiftTo}
764
1004
  />
765
1005
  <TextInput
766
1006
  size="lg"
767
1007
  label="From"
768
- isLabelHidden
769
- placeholder="From"
1008
+ placeholder="Your name"
770
1009
  value={giftFrom}
771
1010
  onChange={setGiftFrom}
772
1011
  />
773
1012
  </Grid>
774
1013
  <TextArea
775
1014
  label="Gift message"
776
- isLabelHidden
777
1015
  placeholder="Write something here"
778
1016
  value={giftMessage}
779
1017
  onChange={setGiftMessage}
@@ -784,48 +1022,17 @@ export default function PaymentForm() {
784
1022
 
785
1023
  {/* Trust bar + CTAs + policy links */}
786
1024
  <VStack gap={4}>
787
- <HStack gap={5} hAlign="center" wrap="wrap">
788
- <HStack gap={1} vAlign="center">
789
- <Icon
790
- icon={ShieldCheck}
791
- size="sm"
792
- color="secondary"
793
- />
794
- <Text type="supporting" color="secondary">
795
- Secure Payment
796
- </Text>
797
- </HStack>
798
- <HStack gap={1} vAlign="center">
799
- <Icon
800
- icon={Lock}
801
- size="sm"
802
- color="secondary"
803
- />
804
- <Text type="supporting" color="secondary">
805
- SSL Encrypted
806
- </Text>
807
- </HStack>
808
- <HStack gap={1} vAlign="center">
809
- <Icon
810
- icon={CircleCheck}
811
- size="sm"
812
- color="secondary"
813
- />
814
- <Text type="supporting" color="secondary">
815
- Free Returns
816
- </Text>
817
- </HStack>
818
- </HStack>
1025
+ <TrustBar />
819
1026
  <VStack gap={2}>
820
1027
  <Button
821
- label="Place Order"
1028
+ label="Place order"
822
1029
  variant="primary"
823
1030
  size="lg"
824
1031
  style={fullWidth}
825
1032
  onClick={() => setSubmitted(true)}
826
1033
  />
827
1034
  <Button
828
- label="Continue Shopping"
1035
+ label="Continue shopping"
829
1036
  variant="secondary"
830
1037
  size="lg"
831
1038
  style={fullWidth}
@@ -833,20 +1040,7 @@ export default function PaymentForm() {
833
1040
  />
834
1041
  </VStack>
835
1042
  <Divider />
836
- <HStack gap={4} vAlign="center" wrap="wrap">
837
- <Link href="#/templates/payment-form" type="supporting">
838
- Refund policy
839
- </Link>
840
- <Link href="#/templates/payment-form" type="supporting">
841
- Privacy policy
842
- </Link>
843
- <Link href="#/templates/payment-form" type="supporting">
844
- Terms of service
845
- </Link>
846
- <Link href="#/templates/payment-form" type="supporting">
847
- Cancellations
848
- </Link>
849
- </HStack>
1043
+ <PolicyLinks />
850
1044
  </VStack>
851
1045
  </VStack>
852
1046
  </StackItem>
@@ -854,132 +1048,16 @@ export default function PaymentForm() {
854
1048
  <StackItem
855
1049
  size="fill"
856
1050
  style={isStacked ? summaryMobileOrder : summarySticky}>
857
- <Card padding={5}>
858
- <VStack gap={4}>
859
- {/* Accordion header — clickable on mobile only */}
860
- <Collapsible
861
- trigger="Order Summary"
862
- defaultIsOpen={true}>
863
- <VStack gap={4} style={summaryContent}>
864
- {/* Line items */}
865
- {ORDER_ITEMS.map(item => (
866
- <VStack key={item.id} gap={3}>
867
- <HStack gap={3} vAlign="start">
868
- <Thumbnail
869
- src={ITEM_IMAGES[item.id].src}
870
- alt={item.name}
871
- />
872
- <StackItem size="fill">
873
- <VStack gap={1}>
874
- <HStack
875
- gap={2}
876
- hAlign="between"
877
- vAlign="start">
878
- <HStack gap={2} vAlign="center" wrap="wrap">
879
- <Text type="body" weight="medium">
880
- {item.name}
881
- </Text>
882
- {item.limited && (
883
- <Badge
884
- variant="yellow"
885
- label="LIMITED EDITION"
886
- />
887
- )}
888
- </HStack>
889
- <Text
890
- type="body"
891
- weight="bold"
892
- hasTabularNumbers>
893
- {fmt(item.price)}
894
- </Text>
895
- </HStack>
896
- <Text type="supporting" color="secondary">
897
- {item.variant}
898
- </Text>
899
- <HStack gap={2} vAlign="end" wrap="wrap">
900
- <NumberInput
901
- label="Qty"
902
- value={
903
- quantities[item.id] ?? item.qty
904
- }
905
- onChange={v =>
906
- setQuantities(q => ({
907
- ...q,
908
- [item.id]: v,
909
- }))
910
- }
911
- min={1}
912
- max={10}
913
- isIntegerOnly
914
- />
915
- <Link href="#/templates/payment-form" type="supporting">
916
- Remove
917
- </Link>
918
- <Link href="#/templates/payment-form" type="supporting">
919
- Save
920
- </Link>
921
- </HStack>
922
- </VStack>
923
- </StackItem>
924
- </HStack>
925
- <Divider />
926
- </VStack>
927
- ))}
928
-
929
- {/* Order total subsection */}
930
- <VStack gap={3}>
931
- <Text type="large" weight="bold">
932
- Order Total
933
- </Text>
934
- <VStack gap={2}>
935
- <HStack hAlign="between" vAlign="center">
936
- <Text type="body" color="secondary">
937
- Subtotal
938
- </Text>
939
- <Text type="body" hasTabularNumbers>
940
- {fmt(subtotal)}
941
- </Text>
942
- </HStack>
943
- <HStack hAlign="between" vAlign="center">
944
- <Text type="body" color="secondary">
945
- Shipping
946
- </Text>
947
- <Text type="body" hasTabularNumbers>
948
- {fmt(shipping)}
949
- </Text>
950
- </HStack>
951
- <HStack hAlign="between" vAlign="center">
952
- <Text type="body" color="secondary">
953
- Tax
954
- </Text>
955
- <Text type="body" hasTabularNumbers>
956
- {fmt(tax)}
957
- </Text>
958
- </HStack>
959
- </VStack>
960
- <Divider />
961
- <HStack hAlign="between" vAlign="center">
962
- <Text type="large" weight="bold">
963
- Total
964
- </Text>
965
- <Text
966
- type="large"
967
- weight="bold"
968
- hasTabularNumbers>
969
- {fmt(total)}
970
- </Text>
971
- </HStack>
972
- <Banner
973
- status="info"
974
- icon={<Icon icon={Truck} size="sm" />}
975
- title="Free shipping on orders over $300"
976
- />
977
- </VStack>
978
- </VStack>
979
- </Collapsible>
980
- </VStack>
981
- {/* end outer card VStack gap={4} */}
982
- </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
+ />
983
1061
  </StackItem>
984
1062
  </Stack>
985
1063
  </VStack>