@tokenoftrust/storefront-runner 2.2.125 → 2.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 (108) hide show
  1. package/apps/storefront/astro.config.mjs +5 -4
  2. package/apps/storefront/package.json +3 -1
  3. package/apps/storefront/public/shared/commerce-chrome.css +57 -15
  4. package/apps/storefront/public/shared/commerce-marketing.css +7 -7
  5. package/apps/storefront/src/components/Badge.astro +1 -1
  6. package/apps/storefront/src/components/Breadcrumbs.astro +8 -3
  7. package/apps/storefront/src/components/Section.astro +4 -1
  8. package/apps/storefront/src/components/Seo.astro +7 -5
  9. package/apps/storefront/src/components/blog/BlogIndexView.astro +22 -13
  10. package/apps/storefront/src/components/blog/ShareRow.astro +43 -0
  11. package/apps/storefront/src/components/chrome/AnnouncementBar.astro +1 -1
  12. package/apps/storefront/src/components/chrome/NavDropdown.astro +7 -7
  13. package/apps/storefront/src/components/chrome/SiteFooter.astro +17 -0
  14. package/apps/storefront/src/components/chrome/SiteHeader.astro +5 -5
  15. package/apps/storefront/src/components/commerce/TagFilterCloud.astro +1 -1
  16. package/apps/storefront/src/components/compliance/NicotineWarning.astro +12 -22
  17. package/apps/storefront/src/components/compliance/PactActNotice.astro +9 -10
  18. package/apps/storefront/src/components/content/ProseSections.astro +2 -2
  19. package/apps/storefront/src/components/home/Hero.astro +34 -12
  20. package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +39 -5
  21. package/apps/storefront/src/components/islands/VariantSelector.tsx +3 -3
  22. package/apps/storefront/src/components/marketing/CardGrid.astro +1 -1
  23. package/apps/storefront/src/components/marketing/CodeSample.astro +1 -1
  24. package/apps/storefront/src/components/marketing/CtaBand.astro +1 -1
  25. package/apps/storefront/src/components/marketing/Faq.astro +1 -1
  26. package/apps/storefront/src/components/marketing/Integrations.astro +1 -1
  27. package/apps/storefront/src/components/marketing/MarketingCtas.astro +1 -1
  28. package/apps/storefront/src/components/marketing/MarketingHero.astro +1 -1
  29. package/apps/storefront/src/components/marketing/ProofStrip.astro +1 -1
  30. package/apps/storefront/src/components/marketing/SplitCompare.astro +1 -1
  31. package/apps/storefront/src/components/marketing/Steps.astro +1 -1
  32. package/apps/storefront/src/components/marketing/Testimonials.astro +2 -2
  33. package/apps/storefront/src/components/marketing/TrustBar.astro +1 -1
  34. package/apps/storefront/src/components/membership/TierComparisonTable.astro +1 -1
  35. package/apps/storefront/src/components/plp/ActiveFilters.astro +1 -1
  36. package/apps/storefront/src/components/plp/FacetSidebar.astro +4 -4
  37. package/apps/storefront/src/components/plp/Pagination.astro +1 -1
  38. package/apps/storefront/src/components/plp/SortSelect.astro +1 -1
  39. package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +18 -3
  40. package/apps/storefront/src/components/the-build/TheBuildIndexView.astro +9 -5
  41. package/apps/storefront/src/config/devTenantSeed.ts +20 -34
  42. package/apps/storefront/src/config/resolver.ts +0 -38
  43. package/apps/storefront/src/layouts/Layout.astro +8 -1
  44. package/apps/storefront/src/lib/assets/preview-version-cookie.ts +52 -5
  45. package/apps/storefront/src/lib/blog/pagination.ts +20 -10
  46. package/apps/storefront/src/lib/blog/presentation.ts +94 -0
  47. package/apps/storefront/src/lib/blog/rss.ts +1 -1
  48. package/apps/storefront/src/lib/blog/types.ts +24 -7
  49. package/apps/storefront/src/lib/breadcrumbs.ts +25 -0
  50. package/apps/storefront/src/lib/chrome/model.ts +4 -0
  51. package/apps/storefront/src/lib/cloudflare-workers.d.ts +53 -0
  52. package/apps/storefront/src/lib/compliance/rawComplianceNotices.ts +38 -18
  53. package/apps/storefront/src/lib/dashboard/delegateGate.ts +28 -0
  54. package/apps/storefront/src/lib/homeVisualParity.ts +8 -5
  55. package/apps/storefront/src/lib/jsonld.ts +5 -5
  56. package/apps/storefront/src/lib/pinnedRouteRewriteBoundary.ts +20 -2
  57. package/apps/storefront/src/lib/rawChrome.ts +74 -12
  58. package/apps/storefront/src/lib/runtimeEnv.ts +1 -1
  59. package/apps/storefront/src/lib/seo/documentTitle.ts +19 -0
  60. package/apps/storefront/src/lib/shared/addToRelease.ts +32 -0
  61. package/apps/storefront/src/lib/storyblok/content-model.ts +3 -0
  62. package/apps/storefront/src/lib/styleGuideThemes.ts +20 -22
  63. package/apps/storefront/src/pages/[...slug].astro +13 -8
  64. package/apps/storefront/src/pages/blog/[slug].astro +56 -24
  65. package/apps/storefront/src/pages/blog/author/[author].astro +17 -9
  66. package/apps/storefront/src/pages/blog/category/[category].astro +18 -10
  67. package/apps/storefront/src/pages/blog/index.astro +2 -0
  68. package/apps/storefront/src/pages/blog/page/[n].astro +2 -0
  69. package/apps/storefront/src/pages/blog/tag/[tag].astro +18 -10
  70. package/apps/storefront/src/pages/collections/[handle].astro +5 -4
  71. package/apps/storefront/src/pages/collections/index.astro +4 -3
  72. package/apps/storefront/src/pages/index.astro +34 -27
  73. package/apps/storefront/src/pages/products/[handle].astro +5 -4
  74. package/apps/storefront/src/pages/saved.astro +4 -3
  75. package/apps/storefront/src/pages/search.astro +4 -3
  76. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +129 -118
  77. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +19 -15
  78. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +23 -27
  79. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +7 -14
  80. package/apps/storefront/src/pages/style-guide/index.astro +6 -5
  81. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +32 -7
  82. package/apps/storefront/src/pages/the-build/[slug].astro +44 -20
  83. package/apps/storefront/src/pages/the-build/index.astro +2 -0
  84. package/apps/storefront/src/pages/the-build/page/[n].astro +2 -0
  85. package/apps/storefront/src/styles/admin.css +47 -0
  86. package/apps/storefront/src/styles/global.css +104 -56
  87. package/apps/storefront/src/themes/derivedTokens.ts +34 -0
  88. package/apps/storefront/src/themes/schema.ts +6 -0
  89. package/apps/storefront/test/pipeline/harness.ts +164 -0
  90. package/apps/storefront/test/pipeline/intake-worker.mjs +14 -0
  91. package/apps/storefront/test/pipeline/pipeline-host.ts +77 -0
  92. package/apps/storefront/test/pipeline/synthetic-job.ts +156 -0
  93. package/apps/storefront/vitest.workers.config.cts +67 -0
  94. package/docs/widget-library-guide.md +1496 -0
  95. package/package.json +1 -1
  96. package/packages/cli/src/declared-config.mjs +400 -5
  97. package/packages/public-runtime/package.json +1 -0
  98. package/packages/public-runtime/src/chrome.ts +93 -2
  99. package/packages/public-runtime/src/compliance-coherence.d.mts +17 -0
  100. package/packages/public-runtime/src/compliance-coherence.mjs +84 -0
  101. package/packages/public-runtime/src/declared-tenant-config.d.mts +1 -1
  102. package/packages/public-runtime/src/declared-tenant-config.mjs +6 -2
  103. package/packages/public-runtime/src/extension-contract-values.d.mts +4 -1
  104. package/packages/public-runtime/src/extension-contract-values.mjs +14 -1
  105. package/packages/public-runtime/src/extension-contract.ts +2 -0
  106. package/packages/public-runtime/src/tenant.ts +48 -4
  107. package/pnpm-lock.runner.yaml +469 -0
  108. package/scripts/tenant/validate.mjs +27 -1
@@ -20,7 +20,8 @@ export const prerender = false;
20
20
  // utilities + reference theme-token fallbacks so the (token-styled) components
21
21
  // render here without the app Layout. The `.sg-commerce` wrapper pins the
22
22
  // reference palette so they look coherent regardless of the marketing theme CSS.
23
- import { loadStyleGuideChrome, loadStyleGuideThemes } from "@/lib/styleGuideThemes";
23
+ import { loadStyleGuideThemes, styleGuideCompositionSheets } from "@/lib/styleGuideThemes";
24
+ import { derivedTokenDeclarations } from "@/themes/derivedTokens";
24
25
  import "@/styles/global.css";
25
26
  import PriceDisplay from "@/components/commerce/PriceDisplay.astro";
26
27
  import RatingStars from "@/components/commerce/RatingStars.astro";
@@ -45,6 +46,7 @@ import NavDropdown from "@/components/chrome/NavDropdown.astro";
45
46
  import SiteFooter from "@/components/chrome/SiteFooter.astro";
46
47
  import { CHROME_PLACEMENT } from "@/lib/chrome/trackingPlacements";
47
48
  import NicotineWarning from "@/components/compliance/NicotineWarning.astro";
49
+ import { NICOTINE_WARNING_PRESETS } from "../../../../../../packages/cli/src/declared-config.mjs";
48
50
  import PactActNotice from "@/components/compliance/PactActNotice.astro";
49
51
  import AdultSignatureNotice from "@/components/compliance/AdultSignatureNotice.astro";
50
52
  import Prop65Warning from "@/components/compliance/Prop65Warning.astro";
@@ -477,19 +479,12 @@ const t = (await loadStyleGuideThemes(
477
479
 
478
480
  if (!t) return new Response(null, { status: 404 });
479
481
 
480
- // Has this tenant adopted the canonical shared chrome? If so it no
481
- // longer ships its own mkt.css (retired at integration) — link the
482
- // platform /shared/commerce-chrome.css instead so the marketing-component
483
- // samples below still render in-brand.
484
- const chromeConfig = await loadStyleGuideChrome(
485
- tenant ?? "",
486
- Astro.locals.tenant,
487
- Astro.locals.content,
488
- );
489
- // Adoption is now "does chrome.json carry the consolidated header/footer
490
- // shape" (lib/chrome/model.ts ChromeConfig) — the old `sharedChrome: true`
491
- // opt-in flag is retired; a successfully-shaped config IS the adopted signal.
492
- const usesSharedChrome = !!chromeConfig?.header && !!chromeConfig?.footer;
482
+ // The platform composition sheet always exists, so the guide always links it.
483
+ // A tenant's own composition layer (e.g. a ported marketing site's mkt.css) is
484
+ // linked only when the theme descriptor declares it in `preview.stylesheets` —
485
+ // never guessed from a path, which 404s for every tenant that has none. Only
486
+ // root-relative paths under this tenant's own /tenants/<tenant>/ tree are taken.
487
+ const compositionSheets = styleGuideCompositionSheets(t, tenant ?? "");
493
488
 
494
489
  const tk = t.tokens ?? {};
495
490
  const storyboard = t.storyboard ?? null;
@@ -506,10 +501,7 @@ const REFERENCE_COMPONENT_VARS: Record<string, string> = {
506
501
  // values so the text/icon-token samples render concretely and re-skin when a
507
502
  // descriptor overrides them via preview.componentVars.
508
503
  "--color-warning": "#8a5717", "--color-error": "#a22014", "--color-info": "#2c5b73",
509
- "--color-accent-ink": "#8a481f",
510
- "--color-focus-ring": "#b5612f", "--color-disabled": "#e4dfd4",
511
- "--color-disabled-contrast": "#8b938f", "--color-field-border": "#8f887a",
512
- "--color-star": "#b5612f", "--tap-target": "44px",
504
+ "--tap-target": "44px",
513
505
  "--radius-sm": "3px", "--radius-md": "6px", "--radius-lg": "12px", "--radius-full": "999px",
514
506
  "--shadow-sm": "0 1px 2px rgba(28,38,36,.06)", "--shadow-md": "0 6px 24px -8px rgba(28,38,36,.14)",
515
507
  "--shadow-lg": "0 24px 60px -20px rgba(28,38,36,.22)", "--container-max": "1240px",
@@ -523,9 +515,13 @@ const componentVars: Record<string, string> = {
523
515
  ...REFERENCE_COMPONENT_VARS,
524
516
  ...((t.preview?.componentVars ?? {}) as Record<string, string>),
525
517
  };
526
- const componentVarsInline = Object.entries(componentVars)
527
- .map(([k, v]) => `${k}:${v}`)
528
- .join(";");
518
+ // Derived tokens (accent-ink, accent-contrast, hovers, focus ring, …) are
519
+ // re-declared on the same element, so they derive from THIS theme's palette
520
+ // rather than inheriting values derived from the page root's.
521
+ const componentVarsInline = [
522
+ ...Object.entries(componentVars).map(([k, v]) => `${k}:${v}`),
523
+ ...derivedTokenDeclarations(componentVars),
524
+ ].join(";");
529
525
  ---
530
526
 
531
527
  <!doctype html>
@@ -537,16 +533,12 @@ const componentVarsInline = Object.entries(componentVars)
537
533
  <link rel="icon" href={favicon} />
538
534
  <meta name="description" content={`${t.name} preview style guide and component contract.`} />
539
535
  <title>{t.name} — style guide (preview)</title>
540
- {/* The tenant's OWN CSS (external, CSP-clean 'self'): the theme token/base
541
- layer under review PLUS the shared marketing composition layer (mkt.css).
542
- Linked for every tenant so the tenant's component classes render in-brand
543
- and the preview visibly re-skins with the theme. */}
536
+ {/* External, CSP-clean 'self' sheets: the theme token/base layer under
537
+ review, the platform composition sheet, and any composition layer the
538
+ descriptor declares. */}
544
539
  <link rel="stylesheet" href={t.stylesheet} />
545
- {usesSharedChrome ? (
546
- <link rel="stylesheet" href="/shared/commerce-chrome.css" />
547
- ) : (
548
- <link rel="stylesheet" href={`/tenants/${tenant}/pages/mkt.css`} />
549
- )}
540
+ <link rel="stylesheet" href="/shared/commerce-chrome.css" />
541
+ {compositionSheets.map((href) => <link rel="stylesheet" href={href} />)}
550
542
  {/* Guide-only chrome; distinct .sg-* names so it never collides with the theme. */}
551
543
  <style nonce={cspNonce}>
552
544
  .sg-bar { position: sticky; top: 0; z-index: 200; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; background: #0f1729; color: #e6edf6; padding: 12px 20px; font: 600 .95rem -apple-system, sans-serif; }
@@ -578,33 +570,36 @@ const componentVarsInline = Object.entries(componentVars)
578
570
  when the descriptor carries t.storyboard. */
579
571
  .sg-story { background: #101b30; color: #e6edf6; padding: 24px 20px 28px; }
580
572
  .sg-story .inner { max-width: 1080px; margin: 0 auto; }
581
- .sg-story h2 { font: 800 .78rem/1.2 -apple-system, sans-serif; letter-spacing: .14em; text-transform: uppercase; color: #7fe3d4; margin: 0 0 8px; }
573
+ .sg-story h2 { font: 800 max(.875rem, 14px)/1.2 -apple-system, sans-serif; letter-spacing: .14em; text-transform: uppercase; color: #7fe3d4; margin: 0 0 8px; }
582
574
  .sg-story p.summary { margin: 0 0 18px; max-width: 76ch; color: #c6d2e4; font-size: .96rem; }
583
575
  .sg-story .insp { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
584
576
  .sg-story .insp .card { background: #16223c; border: 1px solid #26365a; border-radius: 12px; padding: 14px 16px; }
585
- .sg-story .insp .role { display: inline-block; font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; font-weight: 800; color: #0a1a12; background: #7fe3d4; padding: 3px 8px; border-radius: 999px; margin-bottom: 8px; }
586
- .sg-story .insp .slug { font-family: ui-monospace, Menlo, monospace; font-size: .82rem; color: #9fb0c8; margin-bottom: 8px; }
577
+ .sg-story .insp .role { display: inline-block; font-size: max(.875rem, 14px); letter-spacing: .08em; text-transform: uppercase; font-weight: 800; color: #0a1a12; background: #7fe3d4; padding: 3px 8px; border-radius: 999px; margin-bottom: 8px; }
578
+ .sg-story .insp .slug { font-family: ui-monospace, Menlo, monospace; font-size: max(.875rem, 14px); color: #9fb0c8; margin-bottom: 8px; }
587
579
  .sg-story .insp ul { margin: 0; padding-left: 16px; }
588
- .sg-story .insp li { font-size: .84rem; color: #c6d2e4; margin-bottom: 3px; }
580
+ .sg-story .insp li { font-size: max(.875rem, 14px); color: #c6d2e4; margin-bottom: 3px; }
589
581
 
590
582
  /* Page-section samples — a representative slice of the storefront rethemed:
591
583
  hero, promo-card row, category tiles. Token-styled (no per-tenant CSS) so
592
584
  they re-skin from the emitted component vars. */
593
585
  .sg-hero { display: grid; gap: 24px; grid-template-columns: 1.15fr .85fr; align-items: stretch; }
594
586
  .sg-hero .copy { background: var(--color-primary); color: var(--color-primary-contrast); border-radius: var(--radius-lg); padding: clamp(24px, 4vw, 44px); display: flex; flex-direction: column; justify-content: center; }
595
- .sg-hero .copy .kick { font: 800 .72rem/1 -apple-system, sans-serif; letter-spacing: .14em; text-transform: uppercase; opacity: .82; }
587
+ .sg-hero .copy .kick { font: 800 max(.875rem, 14px)/1 -apple-system, sans-serif; letter-spacing: .14em; text-transform: uppercase; }
588
+ .sg-hero .promo .kick { color: var(--color-accent-ink); font: 800 max(.875rem, 14px)/1 -apple-system, sans-serif; letter-spacing: .14em; text-transform: uppercase; }
589
+ .sg-hero .promo h4 { font-family: var(--font-display); font-size: 1.25rem; margin: 8px 0 4px; }
590
+ .sg-hero .promo p { margin: 0; color: var(--color-muted); font-size: max(.875rem, 14px); }
596
591
  .sg-hero .copy h3 { font-family: var(--font-display); font-size: clamp(1.6rem, 3.4vw, 2.6rem); line-height: 1.08; margin: 12px 0 10px; color: inherit; }
597
592
  .sg-hero .copy p { color: inherit; opacity: .9; margin: 0 0 20px; max-width: 40ch; }
598
593
  .sg-hero .promo { border-radius: var(--radius-lg); overflow: hidden; background: var(--color-surface); border: 1px solid var(--color-border); display: flex; align-items: flex-end; min-height: 220px; padding: 22px; color: var(--color-text); box-shadow: var(--shadow-md); }
599
- .sg-btn { display: inline-flex; align-items: center; gap: .5em; align-self: flex-start; min-height: 44px; padding: .7em 1.5em; border-radius: var(--radius-full); font-weight: 700; background: var(--color-accent); color: #fff; border: 2px solid transparent; }
594
+ .sg-btn { display: inline-flex; align-items: center; gap: .5em; align-self: flex-start; min-height: 44px; padding: .7em 1.5em; border-radius: var(--radius-full); font-weight: 700; background: var(--color-accent); color: var(--color-accent-contrast); border: 2px solid transparent; }
600
595
  .sg-btn.ghost { background: transparent; color: inherit; border-color: currentColor; }
601
596
  .sg-promos { display: grid; gap: 16px; grid-template-columns: repeat(3, 1fr); }
602
597
  .sg-promo { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 22px; box-shadow: var(--shadow-sm); }
603
- .sg-promo .tag { display: inline-block; font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; font-weight: 800; color: #fff; background: var(--color-sale); padding: 3px 8px; border-radius: 999px; margin-bottom: 10px; }
598
+ .sg-promo .tag { display: inline-block; font-size: max(.75rem, 12px); letter-spacing: .08em; text-transform: uppercase; font-weight: 800; color: var(--color-primary-contrast); background: var(--color-sale); padding: 3px 8px; border-radius: 999px; margin-bottom: 10px; }
604
599
  .sg-promo h4 { font-family: var(--font-display); font-size: 1.1rem; margin: 0 0 6px; color: var(--color-text); }
605
- .sg-promo p { margin: 0; color: var(--color-muted); font-size: .9rem; }
600
+ .sg-promo p { margin: 0; color: var(--color-muted); font-size: max(.875rem, 14px); }
606
601
  .sg-tiles { display: grid; gap: 14px; grid-template-columns: repeat(4, 1fr); }
607
- .sg-tile { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; min-height: 132px; text-align: center; border-radius: var(--radius-lg); color: #fff; background: linear-gradient(135deg, var(--color-primary), var(--color-primary) 60%, color-mix(in srgb, var(--color-primary) 70%, #000)); padding: 18px; }
602
+ .sg-tile { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; min-height: 132px; text-align: center; border-radius: var(--radius-lg); color: var(--color-primary-contrast); background: linear-gradient(135deg, var(--color-primary), var(--color-primary) 60%, color-mix(in srgb, var(--color-primary) 70%, #000)); padding: 18px; }
608
603
  .sg-tile .ico { width: 40px; height: 40px; border-radius: 999px; background: rgba(255,255,255,.16); display: grid; place-items: center; font-size: 1.1rem; }
609
604
  .sg-tile span { font-family: var(--font-display); font-weight: 700; font-size: .95rem; }
610
605
  @media (max-width: 900px) { .sg-hero { grid-template-columns: 1fr; } .sg-promos { grid-template-columns: 1fr; } .sg-tiles { grid-template-columns: 1fr 1fr; } }
@@ -626,7 +621,7 @@ const componentVarsInline = Object.entries(componentVars)
626
621
  from tokens alone — never a hard-coded color. */
627
622
  .sg-tok-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
628
623
  .sg-tok { border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: 12px 14px; background: var(--color-surface); }
629
- .sg-tok .lab { display: block; font-size: .7rem; letter-spacing: .04em; color: var(--color-muted); font-family: var(--font-mono, ui-monospace, Menlo, monospace); margin-top: 6px; }
624
+ .sg-tok .lab { display: block; font-size: max(.75rem, 12px); letter-spacing: .04em; color: var(--color-muted); font-family: var(--font-mono, ui-monospace, Menlo, monospace); margin-top: 6px; }
630
625
  .sg-tok .txt { font-family: var(--font-display); font-weight: 700; font-size: 1.05rem; line-height: 1.2; display: inline-flex; align-items: center; gap: .5em; }
631
626
  .sg-tok .ico { width: 1.15em; height: 1.15em; flex: none; }
632
627
  .sg-tok-state { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
@@ -645,6 +640,11 @@ const componentVarsInline = Object.entries(componentVars)
645
640
  /* Tap-target demo: an icon button sized to the WCAG minimum. */
646
641
  .sg-tap { display: inline-flex; align-items: center; justify-content: center; min-width: var(--tap-target); min-height: var(--tap-target); border-radius: var(--radius-full); border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-primary); cursor: pointer; }
647
642
  .sg-tap svg { width: 20px; height: 20px; }
643
+ /* Guide-owned captions: the guide meets the bar it enforces (≥14px text). */
644
+ .sg-cap { font: 500 max(.875rem, 14px)/1.45 -apple-system, sans-serif; color: var(--color-muted, #5c6663); margin: 0 0 8px; }
645
+ .sg-tok .txt .ico { color: var(--sg-ico); }
646
+ .sg-foot { padding: 28px 20px; background: #0f1729; color: #cdd8e8; text-align: center; }
647
+ .sg-foot p { margin: 0; font: 500 max(.875rem, 14px)/1.4 -apple-system, sans-serif; }
648
648
  </style>
649
649
  </head>
650
650
  <body>
@@ -658,6 +658,7 @@ const componentVarsInline = Object.entries(componentVars)
658
658
  <a href={`/_style-guide/${tenant}/`}>← all guides</a>
659
659
  </header>
660
660
 
661
+ <main>
661
662
  {storyboard && (
662
663
  <section class="sg-story">
663
664
  <div class="inner">
@@ -680,7 +681,6 @@ const componentVarsInline = Object.entries(componentVars)
680
681
  </section>
681
682
  )}
682
683
 
683
- <main>
684
684
  <div class="container">
685
685
  <section class="sg-block">
686
686
  <p class="sg-h">Color tokens</p>
@@ -745,7 +745,7 @@ const componentVarsInline = Object.entries(componentVars)
745
745
  <button class="btn btn-primary is-loading" type="button" aria-busy="true">Loading</button>
746
746
  <button class="btn btn-primary" type="button" disabled>Disabled</button>
747
747
  </div>
748
- <p class="form-help" style="margin-top:12px">All targets ≥44px. Tab to any control to see the <code>:focus-visible</code> ring.</p>
748
+ <p class="sg-cap" style="margin-top:12px">All targets ≥44px. Tab to any control to see the <code>:focus-visible</code> ring.</p>
749
749
  </section>
750
750
 
751
751
  <section class="sg-block">
@@ -780,7 +780,7 @@ const componentVarsInline = Object.entries(componentVars)
780
780
  <blockquote class="quote">“Testimonial / customer quote placeholder — do not ship until sourced.”</blockquote>
781
781
  <p class="cite">— Role, Company · <b class="opt">needs-review</b></p>
782
782
  </div>
783
- <p class="form-help" style="margin-bottom:8px">Logo strip — "as seen in" / customer &amp; partner logos (placeholder):</p>
783
+ <p class="sg-cap" style="margin-bottom:8px">Logo strip — "as seen in" / customer &amp; partner logos (placeholder):</p>
784
784
  <div class="logo-strip">
785
785
  <span class="logo">LOGO</span><span class="logo">LOGO</span><span class="logo">LOGO</span><span class="logo">LOGO</span>
786
786
  </div>
@@ -835,12 +835,14 @@ const componentVarsInline = Object.entries(componentVars)
835
835
  </div>
836
836
  </section>
837
837
 
838
- {/* ── Marketing page components (mkt.css) ──────────────────────────
839
- The shared composition palette every ported marketing page draws
840
- from. Keep this in sync with mkt.css: when a port harvests a new
841
- reusable pattern into mkt.css, add a sample here (see
842
- playbook/site-migration/style-concept.md §Harvesting components). */}
843
- {theme === "tot-navy" && (
838
+ {/* ── Marketing page components (a declared composition layer) ──────
839
+ The ported-site composition palette (mkt.css-style classes) that a
840
+ tenant's own pages draw from. Rendered when the descriptor declares
841
+ that layer in preview.stylesheets; the token-styled platform marketing
842
+ blocks further down render for every theme. When a port harvests a
843
+ new reusable pattern into the layer, add a sample here (see
844
+ agency-kit/playbooks/style-concept.md §Harvesting components). */}
845
+ {compositionSheets.length > 0 && (
844
846
  <>
845
847
  <section class="sg-block">
846
848
  <p class="sg-h">Marketing components — <code>mkt.css</code></p>
@@ -923,6 +925,17 @@ const componentVarsInline = Object.entries(componentVars)
923
925
  </div>
924
926
  </section>
925
927
 
928
+ <section class="sg-block">
929
+ <p class="sg-h">Footer — <code>footer.site</code> / <code>.foot-grid</code></p>
930
+ <footer class="site" aria-label="Footer sample">
931
+ <div class="foot-grid">
932
+ <div><b>{t.name}</b><p class="copyright">Footer composition sample.</p></div>
933
+ <div><h3>Shop</h3><ul><li><a href="#">New arrivals</a></li><li><a href="#">Best sellers</a></li></ul></div>
934
+ <div><h3>Help</h3><ul><li><a href="#">Shipping</a></li><li><a href="#">Contact</a></li></ul></div>
935
+ </div>
936
+ </footer>
937
+ </section>
938
+
926
939
  <section class="sg-block">
927
940
  <p class="sg-h">CTA band — <code>.cta-band</code></p>
928
941
  <div class="cta-band">
@@ -942,7 +955,7 @@ const componentVarsInline = Object.entries(componentVars)
942
955
  products so this works with no live catalog. These are STANDARD components:
943
956
  fidelity comes from theme tokens + variant props, never a per-tenant fork.
944
957
  See playbook/site-migration/shared-chrome.md. */}
945
- <div class="sg-commerce" style={componentVarsInline}>
958
+ <div class="sg-commerce" data-tot-platform-sample style={componentVarsInline}>
946
959
  <section class="sg-block">
947
960
  <p class="sg-h">Page sections — the site rethemed</p>
948
961
  <p class="lede" style="font-size:.95rem;color:var(--color-muted)">Representative storefront sections rendered from theme tokens only. Swap the theme and this whole slice re-skins — hero, promos, category tiles, carousels, and chrome — with no per-tenant component fork.</p>
@@ -955,9 +968,9 @@ const componentVarsInline = Object.entries(componentVars)
955
968
  </div>
956
969
  <div class="promo">
957
970
  <div>
958
- <span class="kick" style="color:var(--color-accent);font:800 .72rem/1 -apple-system,sans-serif;letter-spacing:.14em;text-transform:uppercase">Featured</span>
959
- <h4 style="font-family:var(--font-display);font-size:1.25rem;margin:8px 0 4px">Right-rail promo tile</h4>
960
- <p style="margin:0;color:var(--color-muted);font-size:.9rem">Two-up hero: banner + promo.</p>
971
+ <span class="kick">Featured</span>
972
+ <h4>Right-rail promo tile</h4>
973
+ <p>Two-up hero: banner + promo.</p>
961
974
  </div>
962
975
  </div>
963
976
  </div>
@@ -973,39 +986,39 @@ const componentVarsInline = Object.entries(componentVars)
973
986
  <p class="sg-h">Semantic text &amp; icon tokens (v1.1)</p>
974
987
  <p class="lede" style="font-size:.95rem;color:var(--color-muted)">Each swatch reads one contract token. A theme passes the quality bar by supplying these — or inheriting the platform default — so every text and icon role resolves and re-skins from tokens alone.</p>
975
988
 
976
- <p class="form-help" style="margin:18px 0 8px">Text roles</p>
989
+ <p class="sg-cap" style="margin:18px 0 8px">Text roles</p>
977
990
  <div class="sg-tok-grid">
978
- <div class="sg-tok"><span class="txt" style="color:var(--color-text)">Primary body text</span><span class="lab">--color-text</span></div>
979
- <div class="sg-tok"><span class="txt" style="color:var(--color-muted)">Secondary / caption</span><span class="lab">--color-muted</span></div>
980
- <div class="sg-tok"><span class="txt" style="color:var(--color-primary)">Brand / link anchor</span><span class="lab">--color-primary</span></div>
981
- <div class="sg-tok"><span class="txt" style="color:var(--color-accent-ink)">Accent text (AA)</span><span class="lab">--color-accent-ink</span></div>
982
- <div class="sg-tok"><span class="txt" style="color:var(--color-success)">In stock · savings</span><span class="lab">--color-success</span></div>
983
- <div class="sg-tok"><span class="txt" style="color:var(--color-sale)">Sale price</span><span class="lab">--color-sale</span></div>
984
- <div class="sg-tok"><span class="txt" style="color:var(--color-warning)">Low stock</span><span class="lab">--color-warning</span></div>
985
- <div class="sg-tok"><span class="txt" style="color:var(--color-error)">Error · out of stock</span><span class="lab">--color-error</span></div>
986
- <div class="sg-tok"><span class="txt" style="color:var(--color-info)">Informational</span><span class="lab">--color-info</span></div>
991
+ <div class="sg-tok" data-token-sample="--color-text"><span class="txt" style="color:var(--color-text)">Primary body text</span><span class="lab">--color-text</span></div>
992
+ <div class="sg-tok" data-token-sample="--color-muted"><span class="txt" style="color:var(--color-muted)">Secondary / caption</span><span class="lab">--color-muted</span></div>
993
+ <div class="sg-tok" data-token-sample="--color-primary"><span class="txt" style="color:var(--color-primary)">Brand / link anchor</span><span class="lab">--color-primary</span></div>
994
+ <div class="sg-tok" data-token-sample="--color-accent-ink"><span class="txt" style="color:var(--color-accent-ink)">Accent text (AA)</span><span class="lab">--color-accent-ink</span></div>
995
+ <div class="sg-tok" data-token-sample="--color-success"><span class="txt" style="color:var(--color-success)">In stock · savings</span><span class="lab">--color-success</span></div>
996
+ <div class="sg-tok" data-token-sample="--color-sale"><span class="txt" style="color:var(--color-sale)">Sale price</span><span class="lab">--color-sale</span></div>
997
+ <div class="sg-tok" data-token-sample="--color-warning"><span class="txt" style="color:var(--color-warning)">Low stock</span><span class="lab">--color-warning</span></div>
998
+ <div class="sg-tok" data-token-sample="--color-error"><span class="txt" style="color:var(--color-error)">Error · out of stock</span><span class="lab">--color-error</span></div>
999
+ <div class="sg-tok" data-token-sample="--color-info"><span class="txt" style="color:var(--color-info)">Informational</span><span class="lab">--color-info</span></div>
987
1000
  </div>
988
1001
 
989
- <p class="form-help" style="margin:22px 0 8px">Icon roles (<code>currentColor</code> tinted by token)</p>
1002
+ <p class="sg-cap" style="margin:22px 0 8px">Icon roles (<code>currentColor</code> tinted by token)</p>
990
1003
  <div class="sg-tok-grid">
991
- <div class="sg-tok"><span class="txt" style="color:var(--color-star)">
1004
+ <div class="sg-tok" data-token-sample="--color-star"><span class="txt" style="--sg-ico:var(--color-star)">
992
1005
  <svg class="ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M10 1.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L10 15l-5.2 2.6 1-5.8L1.5 7.7l5.9-.9L10 1.5z"/></svg>
993
1006
  Rating star</span><span class="lab">--color-star</span></div>
994
- <div class="sg-tok"><span class="txt" style="color:var(--color-success)">
1007
+ <div class="sg-tok" data-token-sample="--color-success"><span class="txt" style="--sg-ico:var(--color-success)">
995
1008
  <svg class="ico" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 10.5l4 4 8-9"/></svg>
996
1009
  Success check</span><span class="lab">--color-success</span></div>
997
- <div class="sg-tok"><span class="txt" style="color:var(--color-warning)">
1010
+ <div class="sg-tok" data-token-sample="--color-warning"><span class="txt" style="--sg-ico:var(--color-warning)">
998
1011
  <svg class="ico" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 2.5L18.5 17H1.5L10 2.5z"/><path d="M10 8v3.5"/><path d="M10 14.4v.1"/></svg>
999
1012
  Warning</span><span class="lab">--color-warning</span></div>
1000
- <div class="sg-tok"><span class="txt" style="color:var(--color-error)">
1013
+ <div class="sg-tok" data-token-sample="--color-error"><span class="txt" style="--sg-ico:var(--color-error)">
1001
1014
  <svg class="ico" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M5 5l10 10M15 5L5 15"/></svg>
1002
1015
  Error</span><span class="lab">--color-error</span></div>
1003
- <div class="sg-tok"><span class="txt" style="color:var(--color-info)">
1016
+ <div class="sg-tok" data-token-sample="--color-info"><span class="txt" style="--sg-ico:var(--color-info)">
1004
1017
  <svg class="ico" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="10" cy="10" r="8"/><path d="M10 9v5"/><path d="M10 6v.1"/></svg>
1005
1018
  Info</span><span class="lab">--color-info</span></div>
1006
1019
  </div>
1007
1020
 
1008
- <p class="form-help" style="margin:22px 0 8px">Interaction states — CTA hover, disabled, loading, focus ring, tap-target</p>
1021
+ <p class="sg-cap" style="margin:22px 0 8px">Interaction states — CTA hover, disabled, loading, focus ring, tap-target</p>
1009
1022
  <div class="sg-tok-state">
1010
1023
  <button class="sg-tok-btn" type="button">Primary CTA</button>
1011
1024
  <button class="sg-tok-btn" type="button" style="background:var(--color-primary-hover)">Hover</button>
@@ -1016,7 +1029,7 @@ const componentVarsInline = Object.entries(componentVars)
1016
1029
  <svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 3h2l1.6 9.2a1 1 0 001 .8h7.6a1 1 0 001-.8L18 6H5"/><circle cx="8" cy="17" r="1"/><circle cx="15" cy="17" r="1"/></svg>
1017
1030
  </button>
1018
1031
  </div>
1019
- <p class="form-help" style="margin-top:10px;color:var(--color-muted)">Fill/hover read <code>--color-primary</code>/<code>--color-primary-hover</code>; disabled reads <code>--color-disabled</code> + <code>--color-disabled-contrast</code>; the field outline reads <code>--color-field-border</code>; Tab shows the <code>--color-focus-ring</code> ring; the icon button is sized to <code>--tap-target</code> (≥44px).</p>
1032
+ <p class="sg-cap" style="margin-top:10px;color:var(--color-muted)">Fill/hover read <code>--color-primary</code>/<code>--color-primary-hover</code>; disabled reads <code>--color-disabled</code> + <code>--color-disabled-contrast</code>; the field outline reads <code>--color-field-border</code>; Tab shows the <code>--color-focus-ring</code> ring; the icon button is sized to <code>--tap-target</code> (≥44px).</p>
1020
1033
  </section>
1021
1034
 
1022
1035
  <section class="sg-block">
@@ -1128,7 +1141,7 @@ const componentVarsInline = Object.entries(componentVars)
1128
1141
  allHref="#"
1129
1142
  allActive={false}
1130
1143
  />
1131
- <p class="form-help" style="margin-top:8px;color:var(--color-muted)">Active chip is filled + <code>aria-current</code>; each chip is a plain link so filtering works server-side.</p>
1144
+ <p class="sg-cap" style="margin-top:8px;color:var(--color-muted)">Active chip is filled + <code>aria-current</code>; each chip is a plain link so filtering works server-side.</p>
1132
1145
  </section>
1133
1146
 
1134
1147
  <section class="sg-block">
@@ -1172,9 +1185,9 @@ const componentVarsInline = Object.entries(componentVars)
1172
1185
  <section class="sg-block">
1173
1186
  <p class="sg-h">Chrome — MegaMenu / NavDropdown (multi-column + caret + NEW badge)</p>
1174
1187
  <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:8px;padding:8px 16px">
1175
- <MegaMenu items={sampleNav} class="!block" />
1188
+ <MegaMenu items={sampleNav} ariaLabel="Primary (MegaMenu sample)" class="!block" />
1176
1189
  </div>
1177
- <p class="form-help" style="margin-top:8px;color:var(--color-muted)">Hover/focus a caret item to reveal its dropdown (CSS-only).</p>
1190
+ <p class="sg-cap" style="margin-top:8px;color:var(--color-muted)">Hover/focus a caret item to reveal its dropdown (CSS-only).</p>
1178
1191
  </section>
1179
1192
 
1180
1193
  <section class="sg-block">
@@ -1192,7 +1205,7 @@ const componentVarsInline = Object.entries(componentVars)
1192
1205
  ))}
1193
1206
  </ul>
1194
1207
  </div>
1195
- <p class="form-help" style="margin-top:8px;color:var(--color-muted)">Same nav items in the mobile menu: mega columns become inline <code>&lt;details&gt;</code> accordions (keyboard + touch operable). Toggle a caret item to expand.</p>
1208
+ <p class="sg-cap" style="margin-top:8px;color:var(--color-muted)">Same nav items in the mobile menu: mega columns become inline <code>&lt;details&gt;</code> accordions (keyboard + touch operable). Toggle a caret item to expand.</p>
1196
1209
  </section>
1197
1210
 
1198
1211
  <section class="sg-block">
@@ -1219,20 +1232,25 @@ const componentVarsInline = Object.entries(componentVars)
1219
1232
  <p class="sg-h">Compliance notices — <code>components/compliance/</code></p>
1220
1233
  <p class="lede" style="font-size:.95rem;color:var(--color-muted)">Data-driven regulated-commerce notices with fixed/mandated legal wording. Rendered here from sample config; each is invisible when its tenant.compliance field is absent.</p>
1221
1234
 
1222
- <p class="form-help" style="margin:18px 0 8px">NicotineWarning — <code>data-compliance="nicotine-warning"</code> (FDA 21 CFR 1143.3, fixed)</p>
1223
- <NicotineWarning variant="bar" />
1224
- <div class="mt-3"><NicotineWarning variant="block" /></div>
1235
+ <p class="sg-cap" style="margin:18px 0 8px">NicotineWarning — <code>data-compliance="nicotine-warning"</code> (FDA 21 CFR 1143.3, fixed wording; one row per validated <code>compliance.nicotineWarning.preset</code>)</p>
1236
+ {Object.keys(NICOTINE_WARNING_PRESETS).map((preset) => (
1237
+ <div class="mt-3">
1238
+ <p class="sg-cap" style="margin:0 0 4px"><code>"preset": "{preset}"</code></p>
1239
+ <NicotineWarning variant="bar" declaration={{ preset }} />
1240
+ <div class="mt-3"><NicotineWarning variant="block" declaration={{ preset }} /></div>
1241
+ </div>
1242
+ ))}
1225
1243
 
1226
- <p class="form-help" style="margin:22px 0 8px">PactActNotice — <code>data-compliance="pact-act"</code> (bar / block / footer)</p>
1244
+ <p class="sg-cap" style="margin:22px 0 8px">PactActNotice — <code>data-compliance="pact-act"</code> (bar / block / footer)</p>
1227
1245
  <PactActNotice enabled minAge={21} variant="bar" />
1228
1246
  <div class="mt-3"><PactActNotice enabled minAge={21} variant="block" /></div>
1229
1247
  <div class="mt-3"><PactActNotice enabled minAge={21} variant="footer" /></div>
1230
1248
 
1231
- <p class="form-help" style="margin:22px 0 8px">AdultSignatureNotice — <code>data-compliance="adult-signature"</code> (inline / block)</p>
1249
+ <p class="sg-cap" style="margin:22px 0 8px">AdultSignatureNotice — <code>data-compliance="adult-signature"</code> (inline / block)</p>
1232
1250
  <AdultSignatureNotice enabled minAge={21} variant="inline" />
1233
1251
  <div class="mt-3"><AdultSignatureNotice enabled minAge={21} variant="block" /></div>
1234
1252
 
1235
- <p class="form-help" style="margin:22px 0 8px">Prop65Warning — <code>data-compliance="prop65"</code> (short + long form, mandated skeleton)</p>
1253
+ <p class="sg-cap" style="margin:22px 0 8px">Prop65Warning — <code>data-compliance="prop65"</code> (short + long form, mandated skeleton)</p>
1236
1254
  <Prop65Warning config={true} variant="block" />
1237
1255
  <div class="mt-3">
1238
1256
  <Prop65Warning
@@ -1242,7 +1260,7 @@ const componentVarsInline = Object.entries(componentVars)
1242
1260
  </div>
1243
1261
  <div class="mt-3"><Prop65Warning config={true} variant="bar" /></div>
1244
1262
 
1245
- <p class="form-help" style="margin:22px 0 8px">PurchaseLimitNotice — <code>data-compliance="purchase-limit"</code> (display only; enforcement is product-owned)</p>
1263
+ <p class="sg-cap" style="margin:22px 0 8px">PurchaseLimitNotice — <code>data-compliance="purchase-limit"</code> (display only; enforcement is product-owned)</p>
1246
1264
  <PurchaseLimitNotice
1247
1265
  config={{ text: "2 units per order", reason: "per state law" }}
1248
1266
  variant="inline"
@@ -1254,7 +1272,7 @@ const componentVarsInline = Object.entries(componentVars)
1254
1272
  />
1255
1273
  </div>
1256
1274
 
1257
- <p class="form-help" style="margin:22px 0 8px">StateEligibilityNotice — <code>data-compliance="state-eligibility"</code> (display only; the eligibility decision is product-owned)</p>
1275
+ <p class="sg-cap" style="margin:22px 0 8px">StateEligibilityNotice — <code>data-compliance="state-eligibility"</code> (display only; the eligibility decision is product-owned)</p>
1258
1276
  <StateEligibilityNotice
1259
1277
  config={{ restrictedStates: ["UT", "AR", "VT"] }}
1260
1278
  variant="block"
@@ -1266,7 +1284,7 @@ const componentVarsInline = Object.entries(componentVars)
1266
1284
  />
1267
1285
  </div>
1268
1286
 
1269
- <p class="form-help" style="margin:22px 0 8px">ShippingRestrictionNotice — <code>data-compliance="shipping-restriction"</code> (the platform footer band; display only)</p>
1287
+ <p class="sg-cap" style="margin:22px 0 8px">ShippingRestrictionNotice — <code>data-compliance="shipping-restriction"</code> (the platform footer band; display only)</p>
1270
1288
  <ShippingRestrictionNotice
1271
1289
  text="Adult signature (21+) required on delivery. Not shipped via USPS."
1272
1290
  variant="footer"
@@ -1278,7 +1296,7 @@ const componentVarsInline = Object.entries(componentVars)
1278
1296
  />
1279
1297
  </div>
1280
1298
 
1281
- <p class="form-help" style="margin:22px 0 8px">ExciseTaxNotice — <code>data-compliance="excise-tax"</code> (display cue only; the tax calculation is product-owned)</p>
1299
+ <p class="sg-cap" style="margin:22px 0 8px">ExciseTaxNotice — <code>data-compliance="excise-tax"</code> (display cue only; the tax calculation is product-owned)</p>
1282
1300
  <ExciseTaxNotice
1283
1301
  config={{ note: "Applicable state vapor excise taxes are calculated at checkout." }}
1284
1302
  variant="inline"
@@ -1309,7 +1327,7 @@ const componentVarsInline = Object.entries(componentVars)
1309
1327
  <p class="sg-h">Membership tiers — <code>components/membership/</code></p>
1310
1328
  <p class="lede" style="font-size:.95rem;color:var(--color-muted)">Education-only tier comparison with a first-class illustrative-figures affordance. Guest shows a real $0; Member/Autopilot figures are illustrative examples, so the guide exercises both the affordance and the opt-out.</p>
1311
1329
 
1312
- <p class="form-help" style="margin:18px 0 8px">TierCards — <code>data-widget="tier-cards"</code> (illustrative badge + "e.g." + footnote; featured ribbon)</p>
1330
+ <p class="sg-cap" style="margin:18px 0 8px">TierCards — <code>data-widget="tier-cards"</code> (illustrative badge + "e.g." + footnote; featured ribbon)</p>
1313
1331
  <TierCards
1314
1332
  eyebrow="Membership"
1315
1333
  heading="Choose how you shop"
@@ -1317,7 +1335,7 @@ const componentVarsInline = Object.entries(componentVars)
1317
1335
  tiers={sampleTiers}
1318
1336
  />
1319
1337
 
1320
- <p class="form-help" style="margin:28px 0 8px">TierComparisonTable — <code>data-widget="tier-comparison-table"</code> (feature matrix; check/dash/text/em-dash cells)</p>
1338
+ <p class="sg-cap" style="margin:28px 0 8px">TierComparisonTable — <code>data-widget="tier-comparison-table"</code> (feature matrix; check/dash/text/em-dash cells)</p>
1321
1339
  <TierComparisonTable
1322
1340
  eyebrow="Compare"
1323
1341
  heading="What each tier includes"
@@ -1337,19 +1355,19 @@ const componentVarsInline = Object.entries(componentVars)
1337
1355
  <p class="sg-h">Structured content — <code>components/content/</code></p>
1338
1356
  <p class="lede" style="font-size:.95rem;color:var(--color-muted)">Data-driven framework widgets (F4): a policy page rendered from structured <code>sections</code> with no HTML/CSS to copy. Accessibility lives in the components; tenant content is pure data.</p>
1339
1357
 
1340
- <p class="form-help" style="margin:18px 0 8px">TrustStrip — <code>data-widget="trust-strip"</code> (f09 real-or-absent: 2 claims + 1 real rating shown; a "Based on 369 reviews" claim + a half-populated rating are dropped, never fabricated)</p>
1358
+ <p class="sg-cap" style="margin:18px 0 8px">TrustStrip — <code>data-widget="trust-strip"</code> (f09 real-or-absent: 2 claims + 1 real rating shown; a "Based on 369 reviews" claim + a half-populated rating are dropped, never fabricated)</p>
1341
1359
  <TrustStrip items={sampleTrustStrip} />
1342
1360
 
1343
- <p class="form-help" style="margin:28px 0 8px">Callout — <code>data-widget="callout"</code> (tones: info / warning / legal / success; role="note", token-tinted, mirrors f05 .disclaimer/.warn-band)</p>
1361
+ <p class="sg-cap" style="margin:28px 0 8px">Callout — <code>data-widget="callout"</code> (tones: info / warning / legal / success; role="note", token-tinted, mirrors f05 .disclaimer/.warn-band)</p>
1344
1362
  <Callout callout={{ tone: "info", title: "Heads up", body: "An informational aside rendered from data." }} />
1345
1363
  <Callout callout={{ tone: "warning", body: "A caution — check this before you rely on it." }} />
1346
1364
  <Callout callout={{ tone: "legal", body: "Restricted to adults 21+ where lawful. Void where prohibited." }} />
1347
1365
  <Callout callout={{ tone: "success", body: "Your terminal is verified and signed in." }} />
1348
1366
 
1349
- <p class="form-help" style="margin:28px 0 8px">NeedsReviewCallout — <code>data-widget="needs-review"</code> (reviewer-only; a placeholder pending human sign-off before go-live — never shown to shoppers)</p>
1367
+ <p class="sg-cap" style="margin:28px 0 8px">NeedsReviewCallout — <code>data-widget="needs-review"</code> (reviewer-only; a placeholder pending human sign-off before go-live — never shown to shoppers)</p>
1350
1368
  <NeedsReviewCallout flag={{ reason: "Refund window (14 vs 30 days) not confirmed with merchant.", severity: "blocker", owner: "ops", href: "#" }} />
1351
1369
 
1352
- <p class="form-help" style="margin:28px 0 8px">ProseToc — <code>data-widget="prose-toc"</code> + ProseSections — <code>data-widget="prose-sections"</code> (a full structured policy body: headings, rich-text links, list, glossary &lt;dl&gt;, callouts, &lt;address&gt; contact; needs-review shown here because the guide is preview)</p>
1370
+ <p class="sg-cap" style="margin:28px 0 8px">ProseToc — <code>data-widget="prose-toc"</code> + ProseSections — <code>data-widget="prose-sections"</code> (a full structured policy body: headings, rich-text links, list, glossary &lt;dl&gt;, callouts, &lt;address&gt; contact; needs-review shown here because the guide is preview)</p>
1353
1371
  <ProseToc sections={sampleProseSections} min={2} />
1354
1372
  <ProseSections sections={sampleProseSections} includeNeedsReview={true} />
1355
1373
  </section>
@@ -1365,7 +1383,7 @@ const componentVarsInline = Object.entries(componentVars)
1365
1383
  <p class="sg-h">Subscription/Autopilot education — <code>components/subscription/</code></p>
1366
1384
  <p class="lede" style="font-size:.95rem;color:var(--color-muted)">Education-only widgets that explain recurring/Autopilot ordering and hand off to the tenant's own manage-subscription flow. No subscription, billing, or scheduling mechanics of their own.</p>
1367
1385
 
1368
- <p class="form-help" style="margin:18px 0 8px">HowItWorksSteps — <code>data-widget="subscription-how-it-works"</code></p>
1386
+ <p class="sg-cap" style="margin:18px 0 8px">HowItWorksSteps — <code>data-widget="subscription-how-it-works"</code></p>
1369
1387
  <HowItWorksSteps
1370
1388
  eyebrow="Autopilot"
1371
1389
  heading="How Autopilot works"
@@ -1373,7 +1391,7 @@ const componentVarsInline = Object.entries(componentVars)
1373
1391
  steps={sampleSubscriptionSteps}
1374
1392
  />
1375
1393
 
1376
- <p class="form-help" style="margin:28px 0 8px">SavingsExplainer — <code>data-widget="subscription-savings-explainer"</code> (illustrative badge + "e.g." + footnote)</p>
1394
+ <p class="sg-cap" style="margin:28px 0 8px">SavingsExplainer — <code>data-widget="subscription-savings-explainer"</code> (illustrative badge + "e.g." + footnote)</p>
1377
1395
  <SavingsExplainer
1378
1396
  eyebrow="Autopilot savings"
1379
1397
  heading="Save on every order"
@@ -1382,7 +1400,7 @@ const componentVarsInline = Object.entries(componentVars)
1382
1400
  cta={{ label: "How Autopilot works", href: "#" }}
1383
1401
  />
1384
1402
 
1385
- <p class="form-help" style="margin:28px 0 8px">ManageSubscriptionEntry — <code>data-widget="subscription-manage-entry"</code></p>
1403
+ <p class="sg-cap" style="margin:28px 0 8px">ManageSubscriptionEntry — <code>data-widget="subscription-manage-entry"</code></p>
1386
1404
  <ManageSubscriptionEntry
1387
1405
  eyebrow="Manage"
1388
1406
  heading="Manage your Autopilot"
@@ -1398,10 +1416,10 @@ const componentVarsInline = Object.entries(componentVars)
1398
1416
  <p class="sg-h">Islands — <code>components/islands/</code></p>
1399
1417
  <p class="lede" style="font-size:.95rem;color:var(--color-muted)">Client-hydrated Preact islands exercised with fixture data — no live catalog, cart, or ToT calls.</p>
1400
1418
 
1401
- <p class="form-help" style="margin:18px 0 8px">AgeGate — full-viewport 21+ affirmation (real overlay; dismiss it once to keep browsing the guide, same as a real visitor)</p>
1419
+ <p class="sg-cap" style="margin:18px 0 8px">AgeGate — full-viewport 21+ affirmation (real overlay; dismiss it once to keep browsing the guide, same as a real visitor)</p>
1402
1420
  <AgeGate client:load minAge={21} brand={t.name} tenantId={tenant ?? "style-guide"} />
1403
1421
 
1404
- <p class="form-help" style="margin:22px 0 8px">VariantSelector — PDP option picker (radiogroup + live price/SKU/stock region)</p>
1422
+ <p class="sg-cap" style="margin:22px 0 8px">VariantSelector — PDP option picker (radiogroup + live price/SKU/stock region)</p>
1405
1423
  <VariantSelector
1406
1424
  client:load
1407
1425
  variants={sampleFromProduct.variants}
@@ -1410,24 +1428,24 @@ const componentVarsInline = Object.entries(componentVars)
1410
1428
  locale="en-US"
1411
1429
  />
1412
1430
 
1413
- <p class="form-help" style="margin:22px 0 8px">QuickView — global peek dialog (opens from any <code>[data-quickview]</code> button, e.g. a ProductCard above)</p>
1431
+ <p class="sg-cap" style="margin:22px 0 8px">QuickView — global peek dialog (opens from any <code>[data-quickview]</code> button, e.g. a ProductCard above)</p>
1414
1432
  <QuickView client:idle locale="en-US" />
1415
1433
 
1416
- <p class="form-help" style="margin:22px 0 8px">SearchTypeahead — header search combobox (debounced, ARIA listbox)</p>
1434
+ <p class="sg-cap" style="margin:22px 0 8px">SearchTypeahead — header search combobox (debounced, ARIA listbox)</p>
1417
1435
  <div style="max-width:22rem">
1418
- <SearchTypeahead client:idle />
1436
+ <SearchTypeahead client:idle label="Search (typeahead sample)" />
1419
1437
  </div>
1420
1438
 
1421
- <p class="form-help" style="margin:22px 0 8px">MobileNav — hamburger + slide-in drawer (mobile-only chrome, hidden ≥768px by design; resize below 768px or use responsive dev tools to preview the drawer)</p>
1439
+ <p class="sg-cap" style="margin:22px 0 8px">MobileNav — hamburger + slide-in drawer (mobile-only chrome, hidden ≥768px by design; resize below 768px or use responsive dev tools to preview the drawer)</p>
1422
1440
  <div style="max-width:20rem">
1423
1441
  <MobileNav client:idle nav={sampleMobileNav} announcement="Free US shipping on orders over $100" />
1424
1442
  </div>
1425
1443
 
1426
- <p class="form-help" style="margin:22px 0 8px">RecentlyViewed — localStorage discovery rail, seeded here for preview (renders empty on a real first visit until a product is viewed)</p>
1444
+ <p class="sg-cap" style="margin:22px 0 8px">RecentlyViewed — localStorage discovery rail, seeded here for preview (renders empty on a real first visit until a product is viewed)</p>
1427
1445
  <script is:inline nonce={cspNonce} set:html={`localStorage.setItem("tot:recently-viewed", ${JSON.stringify(JSON.stringify(sampleRecentlyViewed))}); localStorage.setItem("tot:wishlist", ${JSON.stringify(JSON.stringify(sampleRecentlyViewed.slice(0, 2)))});`} />
1428
1446
  <RecentlyViewed client:idle locale="en-US" />
1429
1447
 
1430
- <p class="form-help" style="margin:22px 0 8px">SavedList — the <code>/saved</code> wishlist, seeded here for preview (empty state shown until an item is saved)</p>
1448
+ <p class="sg-cap" style="margin:22px 0 8px">SavedList — the <code>/saved</code> wishlist, seeded here for preview (empty state shown until an item is saved)</p>
1431
1449
  <SavedList client:only="preact" locale="en-US" />
1432
1450
  </section>
1433
1451
 
@@ -1442,12 +1460,12 @@ const componentVarsInline = Object.entries(componentVars)
1442
1460
  <FacetSidebar facets={sampleFacets} query={samplePlpQuery} basePath="#" />
1443
1461
  <div>
1444
1462
  <div class="sg-row" style="justify-content:space-between;margin-bottom:14px">
1445
- <p class="form-help" style="margin:0">SortSelect</p>
1463
+ <p class="sg-cap" style="margin:0">SortSelect</p>
1446
1464
  <SortSelect query={samplePlpQuery} basePath="#" />
1447
1465
  </div>
1448
- <p class="form-help" style="margin:0 0 8px">ActiveFilters</p>
1466
+ <p class="sg-cap" style="margin:0 0 8px">ActiveFilters</p>
1449
1467
  <ActiveFilters query={samplePlpQuery} basePath="#" />
1450
- <p class="form-help" style="margin:22px 0 8px">Pagination</p>
1468
+ <p class="sg-cap" style="margin:22px 0 8px">Pagination</p>
1451
1469
  <Pagination query={samplePlpQuery} basePath="#" page={2} pageSize={12} total={58} />
1452
1470
  </div>
1453
1471
  </div>
@@ -1461,13 +1479,13 @@ const componentVarsInline = Object.entries(componentVars)
1461
1479
  <p class="sg-h">Home hero — <code>components/home/Hero.astro</code></p>
1462
1480
  <p class="lede" style="font-size:.95rem;color:var(--color-muted)">The homepage "thesis" moment. Bold is the default; light matches merchants whose own site favors a light page with brand-color accents.</p>
1463
1481
 
1464
- <p class="form-help" style="margin:18px 0 8px">Bold (default)</p>
1482
+ <p class="sg-cap" style="margin:18px 0 8px">Bold (default)</p>
1465
1483
  {setHeroVariant("bold")}
1466
- <Hero block={sampleHeroBlock} featureProduct={sampleHeroFeatureProduct} />
1484
+ <Hero block={sampleHeroBlock} featureProduct={sampleHeroFeatureProduct} headingLevel={2} />
1467
1485
 
1468
- <p class="form-help" style="margin:22px 0 8px">Light</p>
1486
+ <p class="sg-cap" style="margin:22px 0 8px">Light</p>
1469
1487
  {setHeroVariant("light")}
1470
- <Hero block={sampleHeroBlock} featureProduct={sampleHeroFeatureProduct} />
1488
+ <Hero block={sampleHeroBlock} featureProduct={sampleHeroFeatureProduct} headingLevel={2} />
1471
1489
  </section>
1472
1490
 
1473
1491
  {/* ── Marketing blocks (components/marketing/) ────────────────────────
@@ -1483,15 +1501,8 @@ const componentVarsInline = Object.entries(componentVars)
1483
1501
  </div>
1484
1502
  </main>
1485
1503
 
1486
- <footer class="site">
1487
- <div class="container">
1488
- <div class="foot-grid">
1489
- <div><b>{t.name}</b><p class="copyright">Footer chrome sample · theme preview only.</p></div>
1490
- <div><h3>Platform</h3><ul><li><a href="#">Identity Verification</a></li><li><a href="#">Age Assurance</a></li></ul></div>
1491
- <div><h3>Industries</h3><ul><li><a href="#">Alcohol &amp; Tobacco</a></li><li><a href="#">Marketplaces</a></li></ul></div>
1492
- <div><h3>Company</h3><ul><li><a href="#">Pricing</a></li><li><a href="#">Contact Sales</a></li></ul></div>
1493
- </div>
1494
- </div>
1504
+ <footer class="sg-foot">
1505
+ <p>{t.name} · theme preview only · noindex</p>
1495
1506
  </footer>
1496
1507
  </body>
1497
1508
  </html>