@tokenoftrust/storefront-runner 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +58 -0
- package/README.md +42 -0
- package/apps/storefront/astro.config.mjs +66 -0
- package/apps/storefront/dev-plugins/dev-loop-state.mjs +147 -0
- package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +238 -0
- package/apps/storefront/env.d.ts +162 -0
- package/apps/storefront/migrations/0000_baseline.sql +54 -0
- package/apps/storefront/migrations/0001_products_fts.sql +9 -0
- package/apps/storefront/migrations/README.md +54 -0
- package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
- package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
- package/apps/storefront/migrations/meta/_journal.json +20 -0
- package/apps/storefront/package.json +42 -0
- package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
- package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
- package/apps/storefront/public/brand/northwind-og.svg +13 -0
- package/apps/storefront/public/favicon.svg +5 -0
- package/apps/storefront/public/js/dashboard-team.js +147 -0
- package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
- package/apps/storefront/public/shared/commerce-chrome.css +369 -0
- package/apps/storefront/src/components/Badge.astro +34 -0
- package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
- package/apps/storefront/src/components/CollectionCard.astro +56 -0
- package/apps/storefront/src/components/EmptyState.astro +55 -0
- package/apps/storefront/src/components/Img.astro +68 -0
- package/apps/storefront/src/components/ProductCard.astro +228 -0
- package/apps/storefront/src/components/Section.astro +39 -0
- package/apps/storefront/src/components/Seo.astro +70 -0
- package/apps/storefront/src/components/WidgetFrame.astro +34 -0
- package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +531 -0
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +597 -0
- package/apps/storefront/src/components/admin/AdminFoxyCartSettingsPanel.astro +227 -0
- package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +465 -0
- package/apps/storefront/src/components/admin/AdminProductsTab.astro +901 -0
- package/apps/storefront/src/components/admin/AdminReportingTab.astro +435 -0
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +782 -0
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +1223 -0
- package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
- package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
- package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
- package/apps/storefront/src/components/chrome/NavDropdown.astro +201 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +201 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +234 -0
- package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
- package/apps/storefront/src/components/commerce/ArticleCard.astro +242 -0
- package/apps/storefront/src/components/commerce/BrandGrid.astro +175 -0
- package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
- package/apps/storefront/src/components/commerce/FoxySidecartStyles.astro +61 -0
- package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
- package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
- package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
- package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
- package/apps/storefront/src/components/commerce/ReviewList.astro +54 -0
- package/apps/storefront/src/components/commerce/ReviewSummary.astro +44 -0
- package/apps/storefront/src/components/commerce/TagFilterCloud.astro +149 -0
- package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +37 -0
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +99 -0
- package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +45 -0
- package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
- package/apps/storefront/src/components/compliance/PactActNotice.astro +35 -0
- package/apps/storefront/src/components/compliance/Prop65Warning.astro +80 -0
- package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +38 -0
- package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +35 -0
- package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +53 -0
- package/apps/storefront/src/components/home/Hero.astro +164 -0
- package/apps/storefront/src/components/islands/AgeGate.tsx +109 -0
- package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
- package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
- package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
- package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
- package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
- package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
- package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
- package/apps/storefront/src/components/islands/VariantSelector.tsx +375 -0
- package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
- package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
- package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
- package/apps/storefront/src/components/marketing/Faq.astro +37 -0
- package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
- package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
- package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
- package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
- package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
- package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
- package/apps/storefront/src/components/marketing/Steps.astro +36 -0
- package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
- package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
- package/apps/storefront/src/components/membership/IllustrativeLabel.astro +56 -0
- package/apps/storefront/src/components/membership/TierCards.astro +164 -0
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +148 -0
- package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
- package/apps/storefront/src/components/plp/FacetSidebar.astro +199 -0
- package/apps/storefront/src/components/plp/Pagination.astro +99 -0
- package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
- package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +95 -0
- package/apps/storefront/src/components/subscription/HowItWorksSteps.astro +67 -0
- package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +67 -0
- package/apps/storefront/src/components/subscription/SavingsExplainer.astro +101 -0
- package/apps/storefront/src/config/adminTargetTenant.ts +28 -0
- package/apps/storefront/src/config/devTenantSeed.ts +78 -0
- package/apps/storefront/src/config/devTenants.ts +16 -0
- package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
- package/apps/storefront/src/config/resolver.ts +102 -0
- package/apps/storefront/src/config/tenants.ts +87 -0
- package/apps/storefront/src/layouts/Layout.astro +368 -0
- package/apps/storefront/src/lib/admin/README.md +122 -0
- package/apps/storefront/src/lib/admin/ai/approvalMode.ts +21 -0
- package/apps/storefront/src/lib/admin/ai/askEnvelope.ts +48 -0
- package/apps/storefront/src/lib/admin/ai/audit.ts +14 -0
- package/apps/storefront/src/lib/admin/ai/index.ts +6 -0
- package/apps/storefront/src/lib/admin/ai/operationalRuns.ts +155 -0
- package/apps/storefront/src/lib/admin/ai/recommendation.ts +20 -0
- package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +172 -0
- package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +65 -0
- package/apps/storefront/src/lib/admin/envelope.ts +10 -0
- package/apps/storefront/src/lib/admin/evidence/evidenceReference.ts +32 -0
- package/apps/storefront/src/lib/admin/memoizeByEnvelope.ts +27 -0
- package/apps/storefront/src/lib/admin/orderClient.ts +146 -0
- package/apps/storefront/src/lib/admin/seed.ts +54 -0
- package/apps/storefront/src/lib/admin/services/mock/aiWorkflows.ts +59 -0
- package/apps/storefront/src/lib/admin/services/mock/customers.ts +86 -0
- package/apps/storefront/src/lib/admin/services/mock/evidence.ts +99 -0
- package/apps/storefront/src/lib/admin/services/mock/foxyCartSettings.ts +78 -0
- package/apps/storefront/src/lib/admin/services/mock/fulfillment.ts +62 -0
- package/apps/storefront/src/lib/admin/services/mock/index.ts +12 -0
- package/apps/storefront/src/lib/admin/services/mock/orders.ts +184 -0
- package/apps/storefront/src/lib/admin/services/mock/products.ts +105 -0
- package/apps/storefront/src/lib/admin/services/mock/reporting.ts +51 -0
- package/apps/storefront/src/lib/admin/services/mock/settings.ts +70 -0
- package/apps/storefront/src/lib/admin/services/mock/tenants.ts +29 -0
- package/apps/storefront/src/lib/admin/services/orders.ts +300 -0
- package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +67 -0
- package/apps/storefront/src/lib/admin/viewmodels/customers.ts +56 -0
- package/apps/storefront/src/lib/admin/viewmodels/fulfillment.ts +49 -0
- package/apps/storefront/src/lib/admin/viewmodels/orders.ts +92 -0
- package/apps/storefront/src/lib/admin/viewmodels/products.ts +74 -0
- package/apps/storefront/src/lib/admin/viewmodels/reporting.ts +34 -0
- package/apps/storefront/src/lib/admin/viewmodels/settings.ts +64 -0
- package/apps/storefront/src/lib/adminTenantEnvelope.ts +90 -0
- package/apps/storefront/src/lib/analytics/index.ts +47 -0
- package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
- package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
- package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
- package/apps/storefront/src/lib/auth/loginGate.ts +119 -0
- package/apps/storefront/src/lib/auth/route.ts +146 -0
- package/apps/storefront/src/lib/auth/session.ts +203 -0
- package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
- package/apps/storefront/src/lib/basePath.ts +41 -0
- package/apps/storefront/src/lib/cfImage.ts +55 -0
- package/apps/storefront/src/lib/chrome/model.ts +244 -0
- package/apps/storefront/src/lib/chrome/parseConfig.ts +34 -0
- package/apps/storefront/src/lib/colors.ts +97 -0
- package/apps/storefront/src/lib/commerce/foxyCartSettings.ts +253 -0
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +91 -0
- package/apps/storefront/src/lib/content-edit/client.ts +230 -0
- package/apps/storefront/src/lib/content-edit/index.ts +14 -0
- package/apps/storefront/src/lib/content-edit/route.ts +25 -0
- package/apps/storefront/src/lib/content-edit/types.ts +19 -0
- package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
- package/apps/storefront/src/lib/d1/catalog.ts +366 -0
- package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
- package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
- package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
- package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
- package/apps/storefront/src/lib/demoGate.ts +113 -0
- package/apps/storefront/src/lib/dev/activityIngestToken.ts +78 -0
- package/apps/storefront/src/lib/dev/activityStore.ts +72 -0
- package/apps/storefront/src/lib/dev/ai-widget-client.js +308 -0
- package/apps/storefront/src/lib/dev/apiBase.ts +14 -0
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +155 -0
- package/apps/storefront/src/lib/dev/devDraft.ts +133 -0
- package/apps/storefront/src/lib/dev/hostedCockpit.ts +169 -0
- package/apps/storefront/src/lib/dev/livePreview.ts +77 -0
- package/apps/storefront/src/lib/dev/runtimeStore.ts +120 -0
- package/apps/storefront/src/lib/dev/storeStatus.ts +142 -0
- package/apps/storefront/src/lib/diag/redact.ts +20 -0
- package/apps/storefront/src/lib/edgeCache.ts +128 -0
- package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
- package/apps/storefront/src/lib/env.ts +60 -0
- package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
- package/apps/storefront/src/lib/format.ts +38 -0
- package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
- package/apps/storefront/src/lib/imageAttrs.ts +55 -0
- package/apps/storefront/src/lib/jsonld.ts +166 -0
- package/apps/storefront/src/lib/maintenance.ts +71 -0
- package/apps/storefront/src/lib/membership/model.ts +97 -0
- package/apps/storefront/src/lib/memoryKv.ts +32 -0
- package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
- package/apps/storefront/src/lib/placeholder.ts +99 -0
- package/apps/storefront/src/lib/previewReload.ts +125 -0
- package/apps/storefront/src/lib/priceBuckets.ts +33 -0
- package/apps/storefront/src/lib/rawChrome.ts +505 -0
- package/apps/storefront/src/lib/rawMarketingHtml.ts +136 -0
- package/apps/storefront/src/lib/search/index.ts +74 -0
- package/apps/storefront/src/lib/sections.ts +129 -0
- package/apps/storefront/src/lib/securityHeaders.ts +31 -0
- package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
- package/apps/storefront/src/lib/storyblok/provider.ts +607 -0
- package/apps/storefront/src/lib/subscription/model.ts +68 -0
- package/apps/storefront/src/lib/tenantRouting.ts +91 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
- package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
- package/apps/storefront/src/lib/tot/query.ts +249 -0
- package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
- package/apps/storefront/src/lib/url.ts +142 -0
- package/apps/storefront/src/lib/wishlist.ts +63 -0
- package/apps/storefront/src/middleware/index.ts +455 -0
- package/apps/storefront/src/pages/404.astro +61 -0
- package/apps/storefront/src/pages/[...slug].astro +90 -0
- package/apps/storefront/src/pages/admin.astro +1675 -0
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +53 -0
- package/apps/storefront/src/pages/api/admin/orders.ts +71 -0
- package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
- package/apps/storefront/src/pages/api/auth/send.ts +85 -0
- package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
- package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
- package/apps/storefront/src/pages/api/checkout-style.css.ts +43 -0
- package/apps/storefront/src/pages/api/cli/policy.ts +70 -0
- package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +164 -0
- package/apps/storefront/src/pages/api/csp-report.ts +25 -0
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
- package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
- package/apps/storefront/src/pages/api/dev/activity.ts +124 -0
- package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +73 -0
- package/apps/storefront/src/pages/api/dev/diff.ts +68 -0
- package/apps/storefront/src/pages/api/dev/file.ts +57 -0
- package/apps/storefront/src/pages/api/dev/locate.ts +125 -0
- package/apps/storefront/src/pages/api/dev/publish.ts +128 -0
- package/apps/storefront/src/pages/api/dev/tree.ts +70 -0
- package/apps/storefront/src/pages/api/dev/write.ts +116 -0
- package/apps/storefront/src/pages/api/newsletter.ts +104 -0
- package/apps/storefront/src/pages/api/request-access.ts +172 -0
- package/apps/storefront/src/pages/auth/login.astro +149 -0
- package/apps/storefront/src/pages/auth/magic.astro +105 -0
- package/apps/storefront/src/pages/capabilities.astro +292 -0
- package/apps/storefront/src/pages/collections/[handle].astro +177 -0
- package/apps/storefront/src/pages/collections/index.astro +59 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
- package/apps/storefront/src/pages/dashboard/index.astro +160 -0
- package/apps/storefront/src/pages/index.astro +290 -0
- package/apps/storefront/src/pages/llms.txt.ts +92 -0
- package/apps/storefront/src/pages/products/[handle].astro +402 -0
- package/apps/storefront/src/pages/robots.txt.ts +24 -0
- package/apps/storefront/src/pages/saved.astro +35 -0
- package/apps/storefront/src/pages/search.astro +150 -0
- package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +1280 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +229 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +144 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +116 -0
- package/apps/storefront/src/pages/style-guide/index.astro +98 -0
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
- package/apps/storefront/src/styles/global.css +292 -0
- package/apps/storefront/src/themes/reference.ts +68 -0
- package/apps/storefront/src/themes/schema.ts +111 -0
- package/apps/storefront/src/themes/tenants/index.ts +51 -0
- package/apps/storefront/tsconfig.json +16 -0
- package/apps/storefront/vitest.config.ts +22 -0
- package/package.json +26 -0
- package/packages/public-runtime/LICENSE +58 -0
- package/packages/public-runtime/package.json +22 -0
- package/packages/public-runtime/src/binary-path.ts +68 -0
- package/packages/public-runtime/src/capabilities.ts +89 -0
- package/packages/public-runtime/src/catalog-api.ts +81 -0
- package/packages/public-runtime/src/catalog-d1.ts +192 -0
- package/packages/public-runtime/src/checkout-style-publish.ts +200 -0
- package/packages/public-runtime/src/checkout-style.ts +375 -0
- package/packages/public-runtime/src/csp.ts +109 -0
- package/packages/public-runtime/src/customization-preview.ts +197 -0
- package/packages/public-runtime/src/customization-reconcile.ts +63 -0
- package/packages/public-runtime/src/customization-runtime.ts +157 -0
- package/packages/public-runtime/src/customization-scripts.ts +64 -0
- package/packages/public-runtime/src/customization-versioning.ts +95 -0
- package/packages/public-runtime/src/foxy-orders.ts +136 -0
- package/packages/public-runtime/src/foxy.ts +196 -0
- package/packages/public-runtime/src/hot-standby-readiness.ts +140 -0
- package/packages/public-runtime/src/index.ts +34 -0
- package/packages/public-runtime/src/product.ts +159 -0
- package/packages/public-runtime/src/search.ts +45 -0
- package/packages/public-runtime/src/tenant-assets.ts +444 -0
- package/packages/public-runtime/src/tenant.ts +384 -0
- package/packages/public-runtime/tsconfig.json +8 -0
- package/packages/public-runtime/vitest.config.ts +8 -0
- package/pnpm-workspace.yaml +7 -0
- package/scripts/build/copy-tenant-assets.mjs +69 -0
- package/scripts/dev/ai-edit.mjs +183 -0
- package/scripts/dev/checkout-watch.mjs +116 -0
- package/scripts/dev/file-browser.mjs +159 -0
- package/scripts/dev/file-writer.mjs +141 -0
- package/scripts/dev/git-status.mjs +171 -0
- package/scripts/dev/locate-handler.mjs +163 -0
- package/scripts/dev/locate.mjs +112 -0
- package/scripts/dev/port-check.mjs +48 -0
- package/scripts/dev/safe-path.mjs +109 -0
- package/scripts/dev/shot.mjs +46 -0
- package/scripts/dev/tenant-source-root.mjs +76 -0
- package/scripts/dev/unified-diff.mjs +81 -0
- package/scripts/tot-dev.mjs +376 -0
- package/tenants/README.md +14 -0
- package/tenants/home/content/chrome.json +7 -0
- package/tenants/home/content/home.html +58 -0
- package/tenants/home/content/home.json +11 -0
- package/tenants/home/public/js/storefront.js +210 -0
- package/tenants/home/public/logo-wordmark.png +0 -0
- package/tenants/home/public/pages/storefront.css +212 -0
- package/tenants/home/theme.json +6 -0
- package/tsconfig.base.json +20 -0
|
@@ -0,0 +1,1280 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Preview/dev-only STANDALONE THEME STYLE GUIDE —
|
|
4
|
+
* `/_style-guide/<tenant>/<theme-id>/`.
|
|
5
|
+
*
|
|
6
|
+
* Independently viewable reference for ONE theme: links that theme's stylesheet
|
|
7
|
+
* and renders its tokens (color/type/space/shape) plus live component samples
|
|
8
|
+
* (buttons, eyebrow, proof strip, footer, card) using the theme's real classes.
|
|
9
|
+
* noindex, never in the sitemap, never production marketing publishing. It does
|
|
10
|
+
* NOT reskin the live marketing pages — it only previews the theme in isolation.
|
|
11
|
+
*/
|
|
12
|
+
// Commerce + chrome component library preview. global.css brings the Tailwind
|
|
13
|
+
// utilities + reference theme-token fallbacks so the (token-styled) components
|
|
14
|
+
// render here without the app Layout. The `.sg-commerce` wrapper pins the
|
|
15
|
+
// reference palette so they look coherent regardless of the marketing theme CSS.
|
|
16
|
+
import "@/styles/global.css";
|
|
17
|
+
import PriceDisplay from "@/components/commerce/PriceDisplay.astro";
|
|
18
|
+
import RatingStars from "@/components/commerce/RatingStars.astro";
|
|
19
|
+
import ReviewSummary from "@/components/commerce/ReviewSummary.astro";
|
|
20
|
+
import ReviewList from "@/components/commerce/ReviewList.astro";
|
|
21
|
+
import type { Review } from "@tot/public-runtime";
|
|
22
|
+
import Badge from "@/components/Badge.astro";
|
|
23
|
+
import ProductCard from "@/components/ProductCard.astro";
|
|
24
|
+
import ProductCarousel from "@/components/commerce/ProductCarousel.astro";
|
|
25
|
+
import ProductGrid from "@/components/commerce/ProductGrid.astro";
|
|
26
|
+
import BrandGrid from "@/components/commerce/BrandGrid.astro";
|
|
27
|
+
import type { Brand } from "@/components/commerce/BrandGrid.astro";
|
|
28
|
+
import ArticleCard from "@/components/commerce/ArticleCard.astro";
|
|
29
|
+
import type { Article } from "@/components/commerce/ArticleCard.astro";
|
|
30
|
+
import TagFilterCloud from "@/components/commerce/TagFilterCloud.astro";
|
|
31
|
+
import type { TagItem } from "@/components/commerce/TagFilterCloud.astro";
|
|
32
|
+
import AnnouncementBar from "@/components/chrome/AnnouncementBar.astro";
|
|
33
|
+
import UtilityNav from "@/components/chrome/UtilityNav.astro";
|
|
34
|
+
import SiteHeader from "@/components/chrome/SiteHeader.astro";
|
|
35
|
+
import MegaMenu from "@/components/chrome/MegaMenu.astro";
|
|
36
|
+
import NavDropdown from "@/components/chrome/NavDropdown.astro";
|
|
37
|
+
import SiteFooter from "@/components/chrome/SiteFooter.astro";
|
|
38
|
+
import NicotineWarning from "@/components/compliance/NicotineWarning.astro";
|
|
39
|
+
import PactActNotice from "@/components/compliance/PactActNotice.astro";
|
|
40
|
+
import AdultSignatureNotice from "@/components/compliance/AdultSignatureNotice.astro";
|
|
41
|
+
import Prop65Warning from "@/components/compliance/Prop65Warning.astro";
|
|
42
|
+
import PurchaseLimitNotice from "@/components/compliance/PurchaseLimitNotice.astro";
|
|
43
|
+
import StateEligibilityNotice from "@/components/compliance/StateEligibilityNotice.astro";
|
|
44
|
+
import ShippingRestrictionNotice from "@/components/compliance/ShippingRestrictionNotice.astro";
|
|
45
|
+
import ExciseTaxNotice from "@/components/compliance/ExciseTaxNotice.astro";
|
|
46
|
+
import TierCards from "@/components/membership/TierCards.astro";
|
|
47
|
+
import TierComparisonTable from "@/components/membership/TierComparisonTable.astro";
|
|
48
|
+
import HowItWorksSteps from "@/components/subscription/HowItWorksSteps.astro";
|
|
49
|
+
import SavingsExplainer from "@/components/subscription/SavingsExplainer.astro";
|
|
50
|
+
import ManageSubscriptionEntry from "@/components/subscription/ManageSubscriptionEntry.astro";
|
|
51
|
+
import AgeGate from "@/components/islands/AgeGate.tsx";
|
|
52
|
+
import VariantSelector from "@/components/islands/VariantSelector.tsx";
|
|
53
|
+
import QuickView from "@/components/islands/QuickView.tsx";
|
|
54
|
+
import SearchTypeahead from "@/components/islands/SearchTypeahead.tsx";
|
|
55
|
+
import MobileNav from "@/components/islands/MobileNav.tsx";
|
|
56
|
+
import RecentlyViewed from "@/components/islands/RecentlyViewed.tsx";
|
|
57
|
+
import type { RVItem } from "@/components/islands/RecentlyViewed.tsx";
|
|
58
|
+
import SavedList from "@/components/islands/SavedList.tsx";
|
|
59
|
+
import FacetSidebar from "@/components/plp/FacetSidebar.astro";
|
|
60
|
+
import ActiveFilters from "@/components/plp/ActiveFilters.astro";
|
|
61
|
+
import SortSelect from "@/components/plp/SortSelect.astro";
|
|
62
|
+
import Pagination from "@/components/plp/Pagination.astro";
|
|
63
|
+
import Hero from "@/components/home/Hero.astro";
|
|
64
|
+
import MarketingBlocks from "@/components/marketing/MarketingBlocks.astro";
|
|
65
|
+
import { sampleProducts, sampleRatings } from "@/lib/fixtures/sampleProducts";
|
|
66
|
+
import type { ChromeNavItem } from "@/lib/chrome/model";
|
|
67
|
+
import type { Tier, ComparisonRow } from "@/lib/membership/model";
|
|
68
|
+
import type { SubscriptionStep, SubscriptionManageEntry } from "@/lib/subscription/model";
|
|
69
|
+
import type { Facet, ListProductsQuery } from "@tot/public-runtime";
|
|
70
|
+
import type { HeroBlock, MarketingBlock } from "@/lib/storyblok/content-model";
|
|
71
|
+
|
|
72
|
+
const { tenant, theme } = Astro.params;
|
|
73
|
+
const cspNonce = Astro.locals.cspNonce;
|
|
74
|
+
|
|
75
|
+
// --- Sample chrome data (preview-only; no live tenant content) ----------------
|
|
76
|
+
const sampleNav: ChromeNavItem[] = [
|
|
77
|
+
{ label: "New", href: "#", flag: true, badge: "NEW" },
|
|
78
|
+
{
|
|
79
|
+
label: "Shop",
|
|
80
|
+
href: "#",
|
|
81
|
+
columns: [
|
|
82
|
+
{ title: "Category", links: [{ label: "Disposables", href: "#" }, { label: "E-Liquid", href: "#" }, { label: "Devices", href: "#" }] },
|
|
83
|
+
{ title: "Featured", links: [{ label: "Best Sellers", href: "#" }, { label: "On Sale", href: "#", badge: "%" }] },
|
|
84
|
+
{ title: "Brands", links: [{ label: "Northwind", href: "#" }, { label: "Summit Co.", href: "#" }] },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{ label: "Coils & Parts", href: "#", children: [{ label: "Coils", href: "#" }, { label: "Pods", href: "#" }, { label: "Batteries", href: "#" }] },
|
|
88
|
+
{ label: "On Sale", href: "#" },
|
|
89
|
+
];
|
|
90
|
+
const sampleSocial = [
|
|
91
|
+
{ label: "X", href: "#", iconPath: "M18.9 2H22l-7.1 8.1L23 22h-6.6l-5-6.6L5.7 22H2.6l7.6-8.7L1.5 2h6.8l4.5 6 5.1-6z" },
|
|
92
|
+
];
|
|
93
|
+
const sampleFooterCols = [
|
|
94
|
+
{ title: "Shop", links: [{ label: "Disposables", href: "#" }, { label: "E-Liquid", href: "#" }, { label: "Devices", href: "#" }] },
|
|
95
|
+
{ title: "Support", links: [{ label: "Contact", href: "#" }, { label: "Shipping", href: "#" }, { label: "Returns", href: "#" }] },
|
|
96
|
+
{ title: "Company", links: [{ label: "About", href: "#" }, { label: "Blog", href: "#" }] },
|
|
97
|
+
];
|
|
98
|
+
// BrandGrid samples: text-wordmark tiles and logo-image tiles (Img renders
|
|
99
|
+
// deterministic placeholders offline so the image code path is exercised
|
|
100
|
+
// without live assets). Fictional sample brands only.
|
|
101
|
+
const sampleBrandsText: Brand[] = [
|
|
102
|
+
{ label: "Aurora Labs", href: "#" }, { label: "Five Rivers", href: "#" },
|
|
103
|
+
{ label: "Coastal Air", href: "#" }, { label: "Twist Works", href: "#" },
|
|
104
|
+
{ label: "Nightshade", href: "#" }, { label: "Orbit Vapor", href: "#" },
|
|
105
|
+
{ label: "Peakline", href: "#" }, { label: "Vantage", href: "#" },
|
|
106
|
+
];
|
|
107
|
+
const sampleBrandsLogo: Brand[] = [
|
|
108
|
+
{ label: "Northwind", href: "#", logoSrc: "#", logoWidth: 200, logoHeight: 100 },
|
|
109
|
+
{ label: "Summit Co.", href: "#", logoSrc: "#", logoWidth: 200, logoHeight: 100 },
|
|
110
|
+
{ label: "Cyclone", href: "#", logoSrc: "#", logoWidth: 200, logoHeight: 100 },
|
|
111
|
+
{ label: "Pod King", href: "#", logoSrc: "#", logoWidth: 200, logoHeight: 100 },
|
|
112
|
+
{ label: "Flavor Beast", href: "#", logoSrc: "#", logoWidth: 200, logoHeight: 100 },
|
|
113
|
+
];
|
|
114
|
+
// Review/UGC samples (P3 reviews-ugc-widgets): illustrative fixture reviews
|
|
115
|
+
// ONLY, for previewing ReviewSummary/ReviewList token styling in isolation —
|
|
116
|
+
// never sourced from or rendered on a live product page (see
|
|
117
|
+
// docs/architecture/review-markup-policy.md; real callers pass tot.getReviews()
|
|
118
|
+
// output, never fixtures like these).
|
|
119
|
+
const sampleReviews: Review[] = [
|
|
120
|
+
{ author: "J. Alvarez", rating: 5, title: "Exactly what I needed", body: "Smooth draw, great flavor, arrived fast. Will reorder.", date: "2026-06-02", verifiedBuyer: true },
|
|
121
|
+
{ author: "Priya K.", rating: 4, body: "Good overall, battery life could be a bit longer.", date: "2026-05-18", verifiedBuyer: true },
|
|
122
|
+
{ author: "T. Nguyen", rating: 3, title: "Decent, not amazing", body: "Does the job but nothing stands out versus similar devices.", date: "2026-04-30" },
|
|
123
|
+
];
|
|
124
|
+
// Blog/article samples (W4): featured-image cards (Img renders deterministic
|
|
125
|
+
// placeholders offline so the image path is exercised) + one image-less card
|
|
126
|
+
// (token-tinted placeholder band). Meta is illustrative example copy only.
|
|
127
|
+
const sampleArticles: Article[] = [
|
|
128
|
+
{
|
|
129
|
+
title: "How to choose your first pod system",
|
|
130
|
+
href: "#",
|
|
131
|
+
excerpt: "A no-nonsense guide to airflow, coil resistance, and nicotine strength for newcomers.",
|
|
132
|
+
image: { src: "#", width: 640, height: 400 },
|
|
133
|
+
tag: "Guides",
|
|
134
|
+
tagHref: "#",
|
|
135
|
+
author: "Jordan Ellis",
|
|
136
|
+
date: "May 12, 2026",
|
|
137
|
+
dateTime: "2026-05-12",
|
|
138
|
+
readMinutes: 5,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
title: "Salt nic vs. freebase: what actually changes",
|
|
142
|
+
href: "#",
|
|
143
|
+
excerpt: "The chemistry behind the two most common e-liquid formulations, minus the marketing.",
|
|
144
|
+
image: { src: "#", width: 640, height: 400 },
|
|
145
|
+
tag: "Explainers",
|
|
146
|
+
tagHref: "#",
|
|
147
|
+
author: "Priya N.",
|
|
148
|
+
date: "Apr 30, 2026",
|
|
149
|
+
dateTime: "2026-04-30",
|
|
150
|
+
readMinutes: 8,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
title: "Store update: new brands landing this month",
|
|
154
|
+
href: "#",
|
|
155
|
+
excerpt: "A quick roundup of the labels joining the shelf, with links to each collection.",
|
|
156
|
+
tag: "News",
|
|
157
|
+
tagHref: "#",
|
|
158
|
+
date: "Apr 18, 2026",
|
|
159
|
+
dateTime: "2026-04-18",
|
|
160
|
+
readMinutes: 3,
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
const sampleTags: TagItem[] = [
|
|
164
|
+
{ label: "Guides", href: "#", count: 12, active: true },
|
|
165
|
+
{ label: "Explainers", href: "#", count: 8 },
|
|
166
|
+
{ label: "News", href: "#", count: 21 },
|
|
167
|
+
{ label: "Reviews", href: "#", count: 15 },
|
|
168
|
+
{ label: "Compliance", href: "#", count: 4 },
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
// --- Sample membership tiers (preview-only; illustrative example figures) ------
|
|
172
|
+
// The Guest/Member/Autopilot pattern. "Guest" asserts real:true (a genuine $0);
|
|
173
|
+
// Member/Autopilot leave figures illustrative so the guide exercises BOTH the
|
|
174
|
+
// illustrative affordance and the opt-out.
|
|
175
|
+
const sampleTiers: Tier[] = [
|
|
176
|
+
{
|
|
177
|
+
name: "Guest",
|
|
178
|
+
tagline: "No account — shop as you go.",
|
|
179
|
+
figure: { value: "$0", caption: "always" },
|
|
180
|
+
real: true,
|
|
181
|
+
features: [
|
|
182
|
+
{ label: "Full catalog access", included: true },
|
|
183
|
+
{ label: "Standard shipping rates", included: true },
|
|
184
|
+
{ label: "Member-only pricing", included: false },
|
|
185
|
+
{ label: "Auto-reorder", included: false },
|
|
186
|
+
],
|
|
187
|
+
cta: { label: "Keep browsing", href: "#" },
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "Member",
|
|
191
|
+
tagline: "Free to join — unlock member pricing.",
|
|
192
|
+
figure: { value: "$19/mo", caption: "billed monthly" },
|
|
193
|
+
featured: true,
|
|
194
|
+
badge: "Most popular",
|
|
195
|
+
features: [
|
|
196
|
+
{ label: "Everything in Guest", included: true },
|
|
197
|
+
{ label: "Member-only pricing", included: true },
|
|
198
|
+
{ label: "Free shipping over $50", included: true },
|
|
199
|
+
{ label: "Early access to drops", included: true },
|
|
200
|
+
],
|
|
201
|
+
cta: { label: "Learn about Member", href: "#" },
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "Autopilot",
|
|
205
|
+
tagline: "Set it and forget it — recurring delivery.",
|
|
206
|
+
figure: { value: "Save 15%", caption: "vs. Member" },
|
|
207
|
+
features: [
|
|
208
|
+
{ label: "Everything in Member", included: true },
|
|
209
|
+
{ label: "Recurring delivery", included: true },
|
|
210
|
+
{ label: "Best per-unit value", included: true },
|
|
211
|
+
{ label: "Pause or cancel anytime", included: true },
|
|
212
|
+
],
|
|
213
|
+
cta: { label: "How Autopilot works", href: "#" },
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
const sampleComparisonRows: ComparisonRow[] = [
|
|
217
|
+
{ label: "Member-only pricing", cells: [false, true, true] },
|
|
218
|
+
{ label: "Free shipping", cells: ["Over $75", "Over $50", true] },
|
|
219
|
+
{ label: "Early access to drops", cells: [false, true, true] },
|
|
220
|
+
{ label: "Recurring delivery", cells: [false, false, true] },
|
|
221
|
+
{ label: "Per-order limit", cells: ["2 units", "5 units", "5 units"] },
|
|
222
|
+
{ label: "Cancel anytime", cells: [null, true, true] },
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
// --- Sample subscription/Autopilot education content (preview-only) -----------
|
|
226
|
+
const sampleSubscriptionSteps: SubscriptionStep[] = [
|
|
227
|
+
{ title: "Choose your products", description: "Pick anything in the catalog to add to your Autopilot." },
|
|
228
|
+
{ title: "Set your schedule", description: "Weekly, biweekly, or monthly — you choose the cadence." },
|
|
229
|
+
{ title: "We ship on autopilot", description: "Your order goes out automatically, right on schedule." },
|
|
230
|
+
{ title: "Pause or cancel anytime", description: "Manage every upcoming order from your account." },
|
|
231
|
+
];
|
|
232
|
+
const sampleManageEntries: SubscriptionManageEntry[] = [
|
|
233
|
+
{ label: "Update delivery date", description: "Move your next order earlier or later.", href: "#" },
|
|
234
|
+
{ label: "Swap products", description: "Change what ships next time.", href: "#" },
|
|
235
|
+
{ label: "Pause or cancel", description: "Take a break or stop anytime, no fees.", href: "#" },
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
// --- Sample islands data (preview-only) ----------------------------------
|
|
239
|
+
const sampleMobileNav = [
|
|
240
|
+
{
|
|
241
|
+
label: "Shop",
|
|
242
|
+
href: "#",
|
|
243
|
+
children: [{ label: "Disposables", href: "#" }, { label: "E-Liquid", href: "#" }],
|
|
244
|
+
},
|
|
245
|
+
{ label: "Coils & Parts", href: "#" },
|
|
246
|
+
{ label: "On Sale", href: "#" },
|
|
247
|
+
];
|
|
248
|
+
const sampleFromProduct = sampleProducts.find((p) => p.handle === "sample-from")!;
|
|
249
|
+
const sampleRecentlyViewed: RVItem[] = [
|
|
250
|
+
{ handle: "sample-sale", title: "Merino Zip Hoodie", price: 64, currency: "USD", vendor: "Northwind", seed: "sample-sale" },
|
|
251
|
+
{ handle: "sample-compliant", title: "Age-Verified Reserve", price: 89, currency: "USD", vendor: "Cellar 21", seed: "sample-compliant" },
|
|
252
|
+
{ handle: "sample-new", title: "Just-Dropped Utility Cap", price: 24, currency: "USD", vendor: "Northwind", seed: "sample-new" },
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
// --- Sample PLP facet/sort/pagination data (preview-only) -----------------
|
|
256
|
+
const sampleFacets: Facet[] = [
|
|
257
|
+
{ name: "Size", values: [{ value: "S", count: 8 }, { value: "M", count: 14 }, { value: "L", count: 6 }] },
|
|
258
|
+
{ name: "tags", values: [{ value: "bestseller", count: 12 }, { value: "new", count: 5 }] },
|
|
259
|
+
{ name: "availability", values: [{ value: "in-stock", count: 22 }, { value: "all", count: 28 }] },
|
|
260
|
+
];
|
|
261
|
+
const samplePlpQuery: ListProductsQuery = {
|
|
262
|
+
sort: "featured",
|
|
263
|
+
filters: { Size: ["M"], tags: ["bestseller"] },
|
|
264
|
+
availability: "in-stock",
|
|
265
|
+
page: 2,
|
|
266
|
+
pageSize: 12,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// --- Sample home Hero content (preview-only) -------------------------------
|
|
270
|
+
const sampleHeroBlock: HeroBlock = {
|
|
271
|
+
component: "hero",
|
|
272
|
+
eyebrow: "New arrivals",
|
|
273
|
+
headline: "Gear built for",
|
|
274
|
+
headlineAccent: "every trail.",
|
|
275
|
+
subhead: "Hand-picked essentials, rethemed live from this theme's tokens.",
|
|
276
|
+
ctaLabel: "Shop new arrivals",
|
|
277
|
+
ctaHref: "#",
|
|
278
|
+
};
|
|
279
|
+
const sampleHeroFeatureProduct = {
|
|
280
|
+
...sampleProducts[0]!,
|
|
281
|
+
images: [{ id: "hero-sample", url: "#", rehosted: false, position: 1, width: 1000, height: 1250 }],
|
|
282
|
+
};
|
|
283
|
+
// Hero reads its bold/light variant from Astro.locals.tenant (not a prop), so
|
|
284
|
+
// the guide fakes locals right before each render to preview both.
|
|
285
|
+
function setHeroVariant(variant: "bold" | "light") {
|
|
286
|
+
(Astro.locals as any).tenant = { theme_tokens: { brand: { heroVariant: variant } } };
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// --- Sample marketing blocks (preview-only; one of each block type) -------
|
|
291
|
+
const sampleMarketingBlocks: MarketingBlock[] = [
|
|
292
|
+
{
|
|
293
|
+
component: "marketing_hero",
|
|
294
|
+
eyebrow: "For builders",
|
|
295
|
+
headline: "Age & identity verification, done right.",
|
|
296
|
+
subhead: "Embed one SDK and ship compliant checkout in an afternoon.",
|
|
297
|
+
trust: ["200+ countries", "GDPR · CCPA · PIPEDA"],
|
|
298
|
+
ctas: [
|
|
299
|
+
{ label: "Start for free", href: "#", variant: "primary" },
|
|
300
|
+
{ label: "Talk to sales", href: "#", variant: "ghost" },
|
|
301
|
+
],
|
|
302
|
+
panel: {
|
|
303
|
+
title: "verify.log",
|
|
304
|
+
lines: ["✓ document scan passed", "✓ liveness check passed", "✓ sanctions screen clear"],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
{ component: "trust_bar", items: ["200+ countries", "GDPR · CCPA · PIPEDA", "256-bit SSL", "SOC 2 Type II"] },
|
|
308
|
+
{
|
|
309
|
+
component: "split_compare",
|
|
310
|
+
eyebrow: "Why Token of Trust",
|
|
311
|
+
title: "One platform vs. a patchwork",
|
|
312
|
+
before: { title: "Patchwork", items: ["Three vendors to reconcile", "Inconsistent age checks", "Manual compliance review"] },
|
|
313
|
+
after: { title: "Token of Trust", items: ["One SDK, every jurisdiction", "Consistent age + identity checks", "Automated compliance evidence"] },
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
component: "steps",
|
|
317
|
+
eyebrow: "How it works",
|
|
318
|
+
title: "Live in three steps",
|
|
319
|
+
steps: [
|
|
320
|
+
{ icon: "①", label: "Select", detail: "Choose the verification level." },
|
|
321
|
+
{ icon: "②", label: "Integrate", detail: "Embed the SDK or API." },
|
|
322
|
+
{ icon: "③", label: "Verify", detail: "Customers clear in seconds." },
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
component: "card_grid",
|
|
327
|
+
eyebrow: "Platform",
|
|
328
|
+
title: "Built for regulated commerce",
|
|
329
|
+
variant: "capability",
|
|
330
|
+
cards: [
|
|
331
|
+
{ icon: "◈", title: "Age verification", body: "Soft affirmation through hard identity checks." },
|
|
332
|
+
{ icon: "◇", title: "Document + database", body: "Global coverage across ID types and registries." },
|
|
333
|
+
{ icon: "▣", title: "Sanctions screening", body: "Real-time watchlist checks at checkout." },
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
component: "integrations",
|
|
338
|
+
eyebrow: "Integrations",
|
|
339
|
+
title: "Drop into your stack",
|
|
340
|
+
platforms: [{ label: "Shopify" }, { label: "BigCommerce" }, { label: "WooCommerce" }, { label: "Custom API" }],
|
|
341
|
+
points: ["No PII touches your servers", "Webhook-driven status updates"],
|
|
342
|
+
code: {
|
|
343
|
+
language: "js",
|
|
344
|
+
caption: "Client-side embed",
|
|
345
|
+
code: "import { verify } from '@tot/sdk';\n\nawait verify({ minAge: 21 });",
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
component: "proof_strip",
|
|
350
|
+
eyebrow: "Trust",
|
|
351
|
+
title: "Built for compliance teams",
|
|
352
|
+
items: ["We will never sell your data", "Encrypted in transit and at rest"],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
component: "testimonials",
|
|
356
|
+
eyebrow: "Customers",
|
|
357
|
+
quotes: [
|
|
358
|
+
{ stat: "40%", quote: "Fewer failed age checks since switching to Token of Trust.", attribution: "VP Compliance, Northwind" },
|
|
359
|
+
],
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
component: "faq",
|
|
363
|
+
title: "Frequently asked",
|
|
364
|
+
items: [
|
|
365
|
+
{ q: "Which platforms are supported?", a: "Shopify, BigCommerce, WooCommerce, and more." },
|
|
366
|
+
{ q: "How fast is verification?", a: "Most customers clear in under 30 seconds." },
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
component: "cta_band",
|
|
371
|
+
title: "Build trust into every transaction",
|
|
372
|
+
subhead: "Start for free — no credit card required.",
|
|
373
|
+
ctas: [{ label: "Start for free", href: "#", variant: "primary" }],
|
|
374
|
+
},
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
// Colocated repo-root layout: from src/pages/style-guide/[tenant]/ the repo
|
|
378
|
+
// root is six levels up.
|
|
379
|
+
const themeModules = import.meta.glob<Record<string, any>>(
|
|
380
|
+
"../../../../../../tenants/*/content/themes/*.json",
|
|
381
|
+
{ eager: true, import: "default" },
|
|
382
|
+
);
|
|
383
|
+
const seg = `/tenants/${tenant}/content/themes/`;
|
|
384
|
+
const t = Object.entries(themeModules)
|
|
385
|
+
.filter(([path]) => path.includes(seg))
|
|
386
|
+
.map(([, mod]) => mod)
|
|
387
|
+
.find((m) => m.id === theme);
|
|
388
|
+
|
|
389
|
+
if (!t) return new Response(null, { status: 404 });
|
|
390
|
+
|
|
391
|
+
// Has this tenant adopted the canonical shared chrome (unit F3)? If so it no
|
|
392
|
+
// longer ships its own mkt.css (retired at integration, I1/I2/I3) — link the
|
|
393
|
+
// platform /shared/commerce-chrome.css instead so the marketing-component
|
|
394
|
+
// samples below still render in-brand.
|
|
395
|
+
const chromeModules = import.meta.glob<Record<string, any>>(
|
|
396
|
+
"../../../../../../tenants/*/content/chrome.json",
|
|
397
|
+
{ eager: true, import: "default" },
|
|
398
|
+
);
|
|
399
|
+
const chromeSeg = `/tenants/${tenant}/content/chrome.json`;
|
|
400
|
+
const chromeConfig = Object.entries(chromeModules).find(([path]) => path.includes(chromeSeg))?.[1];
|
|
401
|
+
const usesSharedChrome = chromeConfig?.sharedChrome === true;
|
|
402
|
+
|
|
403
|
+
const tk = t.tokens ?? {};
|
|
404
|
+
const storyboard = t.storyboard ?? null;
|
|
405
|
+
|
|
406
|
+
// Reference component tokens (the "Northwind" pilot identity from
|
|
407
|
+
// src/themes/reference.ts). Used only as the FALLBACK when a theme descriptor
|
|
408
|
+
// declares no preview.componentVars — never to pin the guide to a fixed palette.
|
|
409
|
+
const REFERENCE_COMPONENT_VARS: Record<string, string> = {
|
|
410
|
+
"--color-bg": "#f4f1ea", "--color-surface": "#fcfaf5", "--color-text": "#1c2624",
|
|
411
|
+
"--color-muted": "#5c6663", "--color-primary": "#1f3a34", "--color-primary-contrast": "#f4f1ea",
|
|
412
|
+
"--color-accent": "#b5612f", "--color-border": "#dcd6c9", "--color-success": "#3f6b4f",
|
|
413
|
+
"--color-sale": "#9e3b2e",
|
|
414
|
+
"--radius-sm": "3px", "--radius-md": "6px", "--radius-lg": "12px", "--radius-full": "999px",
|
|
415
|
+
"--shadow-sm": "0 1px 2px rgba(28,38,36,.06)", "--shadow-md": "0 6px 24px -8px rgba(28,38,36,.14)",
|
|
416
|
+
"--shadow-lg": "0 24px 60px -20px rgba(28,38,36,.22)", "--container-max": "1240px",
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// The reskin mechanism: emit the SELECTED theme's component tokens onto the
|
|
420
|
+
// preview root so the token-styled commerce/chrome components (and the page-
|
|
421
|
+
// section samples) render under THIS theme, not a fixed reference palette.
|
|
422
|
+
// Descriptors override the reference subset via preview.componentVars.
|
|
423
|
+
const componentVars: Record<string, string> = {
|
|
424
|
+
...REFERENCE_COMPONENT_VARS,
|
|
425
|
+
...((t.preview?.componentVars ?? {}) as Record<string, string>),
|
|
426
|
+
};
|
|
427
|
+
const componentVarsInline = Object.entries(componentVars)
|
|
428
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
429
|
+
.join(";");
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
<!doctype html>
|
|
433
|
+
<html lang="en">
|
|
434
|
+
<head>
|
|
435
|
+
<meta charset="utf-8" />
|
|
436
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
437
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
438
|
+
<link rel="icon" type="image/png" href={`/tenants/${tenant}/favicon.png`} />
|
|
439
|
+
<title>{t.name} — style guide (preview)</title>
|
|
440
|
+
{/* The tenant's OWN CSS (external, CSP-clean 'self'): the theme token/base
|
|
441
|
+
layer under review PLUS the shared marketing composition layer (mkt.css).
|
|
442
|
+
Linked for every tenant so the tenant's component classes render in-brand
|
|
443
|
+
and the preview visibly re-skins with the theme. */}
|
|
444
|
+
<link rel="stylesheet" href={t.stylesheet} />
|
|
445
|
+
{usesSharedChrome ? (
|
|
446
|
+
<link rel="stylesheet" href="/shared/commerce-chrome.css" />
|
|
447
|
+
) : (
|
|
448
|
+
<link rel="stylesheet" href={`/tenants/${tenant}/pages/mkt.css`} />
|
|
449
|
+
)}
|
|
450
|
+
{/* Guide-only chrome; distinct .sg-* names so it never collides with the theme. */}
|
|
451
|
+
<style nonce={cspNonce}>
|
|
452
|
+
.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; }
|
|
453
|
+
.sg-bar a { color: #bfe0ff; display: inline-flex; align-items: center; min-height: 44px; }
|
|
454
|
+
.sg-bar .flag { font-size: .875rem; letter-spacing: .1em; text-transform: uppercase; background: #f59e0b; color: #221600; font-weight: 800; padding: 4px 8px; border-radius: 5px; }
|
|
455
|
+
.sg-bar .role { font-size: .875rem; text-transform: uppercase; letter-spacing: .08em; background: #0e9f8e; color: #00201b; font-weight: 800; padding: 4px 8px; border-radius: 999px; }
|
|
456
|
+
.sg-bar .status { margin-left: auto; font-family: ui-monospace, Menlo, monospace; font-size: .875rem; color: #cdd8e8; }
|
|
457
|
+
/* Guide-chrome tokens fall back to a neutral palette so the frame renders
|
|
458
|
+
under ANY tenant theme (the linked theme CSS may not define these vars). */
|
|
459
|
+
.sg-h { font: 800 .9rem/1.3 -apple-system, sans-serif; letter-spacing: .14em; text-transform: uppercase; color: var(--teal-ink, #0b6360); margin: 0 0 14px; }
|
|
460
|
+
.sg-block { padding: 40px 0; border-top: 1px solid var(--line, #e2e2e2); }
|
|
461
|
+
.sg-swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 14px; }
|
|
462
|
+
.sg-sw { border: 1px solid var(--line, #e2e2e2); border-radius: 12px; overflow: hidden; background: var(--card, #fff); }
|
|
463
|
+
.sg-sw .chip { height: 62px; }
|
|
464
|
+
.sg-sw .meta { padding: 10px 12px; font-size: .9rem; }
|
|
465
|
+
.sg-sw .meta b { display: block; font-size: .95rem; }
|
|
466
|
+
.sg-sw .meta code { color: var(--muted, #5c6663); font-family: ui-monospace, Menlo, monospace; font-size: .875rem; }
|
|
467
|
+
.sg-sw .meta .role { color: var(--muted, #5c6663); display: block; margin-top: 4px; }
|
|
468
|
+
.sg-row { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; }
|
|
469
|
+
.sg-space { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; }
|
|
470
|
+
.sg-space .bar { background: var(--teal-ink, #0b6360); border-radius: 4px 4px 0 0; }
|
|
471
|
+
.sg-space .lab { font-size: .875rem; color: var(--muted, #5c6663); text-align: center; margin-top: 6px; font-family: ui-monospace, Menlo, monospace; }
|
|
472
|
+
.sg-shape { display: flex; gap: 18px; flex-wrap: wrap; }
|
|
473
|
+
.sg-shape .box { width: 120px; height: 84px; background: var(--card, #fff); display: grid; place-items: center; font-size: .875rem; color: var(--muted, #5c6663); }
|
|
474
|
+
.sg-card { max-width: 320px; background: var(--card, #fff); border: 1px solid var(--line, #e2e2e2); border-radius: var(--r, 12px); box-shadow: var(--shadow, 0 8px 24px rgba(0,0,0,.08)); padding: 22px; }
|
|
475
|
+
|
|
476
|
+
/* Storyboard / inspiration header — "the storyboard opens the guide"
|
|
477
|
+
(playbook/site-migration/style-concept.md). Rendered per style concept
|
|
478
|
+
when the descriptor carries t.storyboard. */
|
|
479
|
+
.sg-story { background: #101b30; color: #e6edf6; padding: 24px 20px 28px; }
|
|
480
|
+
.sg-story .inner { max-width: 1080px; margin: 0 auto; }
|
|
481
|
+
.sg-story h2 { font: 800 .78rem/1.2 -apple-system, sans-serif; letter-spacing: .14em; text-transform: uppercase; color: #7fe3d4; margin: 0 0 8px; }
|
|
482
|
+
.sg-story p.summary { margin: 0 0 18px; max-width: 76ch; color: #c6d2e4; font-size: .96rem; }
|
|
483
|
+
.sg-story .insp { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
|
|
484
|
+
.sg-story .insp .card { background: #16223c; border: 1px solid #26365a; border-radius: 12px; padding: 14px 16px; }
|
|
485
|
+
.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; }
|
|
486
|
+
.sg-story .insp .slug { font-family: ui-monospace, Menlo, monospace; font-size: .82rem; color: #9fb0c8; margin-bottom: 8px; }
|
|
487
|
+
.sg-story .insp ul { margin: 0; padding-left: 16px; }
|
|
488
|
+
.sg-story .insp li { font-size: .84rem; color: #c6d2e4; margin-bottom: 3px; }
|
|
489
|
+
|
|
490
|
+
/* Page-section samples — a representative slice of the storefront rethemed:
|
|
491
|
+
hero, promo-card row, category tiles. Token-styled (no per-tenant CSS) so
|
|
492
|
+
they re-skin from the emitted component vars. */
|
|
493
|
+
.sg-hero { display: grid; gap: 24px; grid-template-columns: 1.15fr .85fr; align-items: stretch; }
|
|
494
|
+
.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; }
|
|
495
|
+
.sg-hero .copy .kick { font: 800 .72rem/1 -apple-system, sans-serif; letter-spacing: .14em; text-transform: uppercase; opacity: .82; }
|
|
496
|
+
.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; }
|
|
497
|
+
.sg-hero .copy p { color: inherit; opacity: .9; margin: 0 0 20px; max-width: 40ch; }
|
|
498
|
+
.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); }
|
|
499
|
+
.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; }
|
|
500
|
+
.sg-btn.ghost { background: transparent; color: inherit; border-color: currentColor; }
|
|
501
|
+
.sg-promos { display: grid; gap: 16px; grid-template-columns: repeat(3, 1fr); }
|
|
502
|
+
.sg-promo { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 22px; box-shadow: var(--shadow-sm); }
|
|
503
|
+
.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; }
|
|
504
|
+
.sg-promo h4 { font-family: var(--font-display); font-size: 1.1rem; margin: 0 0 6px; color: var(--color-text); }
|
|
505
|
+
.sg-promo p { margin: 0; color: var(--color-muted); font-size: .9rem; }
|
|
506
|
+
.sg-tiles { display: grid; gap: 14px; grid-template-columns: repeat(4, 1fr); }
|
|
507
|
+
.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; }
|
|
508
|
+
.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; }
|
|
509
|
+
.sg-tile span { font-family: var(--font-display); font-weight: 700; font-size: .95rem; }
|
|
510
|
+
@media (max-width: 900px) { .sg-hero { grid-template-columns: 1fr; } .sg-promos { grid-template-columns: 1fr; } .sg-tiles { grid-template-columns: 1fr 1fr; } }
|
|
511
|
+
|
|
512
|
+
/* Commerce + chrome component library scope. The theme's component tokens
|
|
513
|
+
are supplied at runtime via an inline style attr (componentVarsInline)
|
|
514
|
+
from the SELECTED theme descriptor — so the standard components re-skin
|
|
515
|
+
with the theme. Swap the theme, and every sample below re-skins; the
|
|
516
|
+
components never fork. */
|
|
517
|
+
.sg-commerce {
|
|
518
|
+
background: var(--color-bg); color: var(--color-text);
|
|
519
|
+
max-width: 1240px; margin: 40px auto; padding: 0 20px 60px;
|
|
520
|
+
}
|
|
521
|
+
.sg-commerce .sg-h { color: var(--color-primary); }
|
|
522
|
+
.sg-commerce .sg-block { border-top-color: var(--color-border); }
|
|
523
|
+
</style>
|
|
524
|
+
</head>
|
|
525
|
+
<body>
|
|
526
|
+
<header class="sg-bar">
|
|
527
|
+
<span class="flag">Preview</span>
|
|
528
|
+
<span class="role">{t.role}</span>
|
|
529
|
+
<b>{t.name}</b>
|
|
530
|
+
<span class="status">{t.status} · concept {t.concept?.id}@{t.concept?.version}</span>
|
|
531
|
+
<a href={`/_style-guide/${tenant}/chrome/${t.id}/`} style="margin-left:auto">shared chrome →</a>
|
|
532
|
+
<a href={`/_style-guide/${tenant}/guide/${t.id}/`}>widget library guide →</a>
|
|
533
|
+
<a href={`/_style-guide/${tenant}/`}>← all guides</a>
|
|
534
|
+
</header>
|
|
535
|
+
|
|
536
|
+
{storyboard && (
|
|
537
|
+
<section class="sg-story">
|
|
538
|
+
<div class="inner">
|
|
539
|
+
<h2>Storyboard · where this look comes from</h2>
|
|
540
|
+
{storyboard.summary && <p class="summary">{storyboard.summary}</p>}
|
|
541
|
+
{Array.isArray(storyboard.inspirationPages) && storyboard.inspirationPages.length > 0 && (
|
|
542
|
+
<div class="insp">
|
|
543
|
+
{storyboard.inspirationPages.map((p: any) => (
|
|
544
|
+
<div class="card">
|
|
545
|
+
{p.role && <span class="role">{p.role}</span>}
|
|
546
|
+
{p.slug && <div class="slug">{p.slug}</div>}
|
|
547
|
+
{Array.isArray(p.captures) && (
|
|
548
|
+
<ul>{p.captures.slice(0, 5).map((c: string) => <li>{c}</li>)}</ul>
|
|
549
|
+
)}
|
|
550
|
+
</div>
|
|
551
|
+
))}
|
|
552
|
+
</div>
|
|
553
|
+
)}
|
|
554
|
+
</div>
|
|
555
|
+
</section>
|
|
556
|
+
)}
|
|
557
|
+
|
|
558
|
+
<main>
|
|
559
|
+
<div class="container">
|
|
560
|
+
<section class="sg-block">
|
|
561
|
+
<p class="sg-h">Color tokens</p>
|
|
562
|
+
<div class="sg-swatches">
|
|
563
|
+
{(tk.color ?? []).map((c: any) => (
|
|
564
|
+
<div class="sg-sw">
|
|
565
|
+
<div class="chip" style={`background:${c.value}`}></div>
|
|
566
|
+
<div class="meta">
|
|
567
|
+
<b>{c.name}</b>
|
|
568
|
+
<code>{c.var} · {c.value}</code>
|
|
569
|
+
<span class="role">{c.role}</span>
|
|
570
|
+
</div>
|
|
571
|
+
</div>
|
|
572
|
+
))}
|
|
573
|
+
</div>
|
|
574
|
+
{tk.gradient && (
|
|
575
|
+
<div class="sg-sw" style="margin-top:14px;max-width:320px">
|
|
576
|
+
<div class="chip" style={`background:${tk.gradient.value}`}></div>
|
|
577
|
+
<div class="meta"><b>gradient</b><code>{tk.gradient.var}</code><span class="role">{tk.gradient.role}</span></div>
|
|
578
|
+
</div>
|
|
579
|
+
)}
|
|
580
|
+
</section>
|
|
581
|
+
|
|
582
|
+
{(tk.semantic ?? []).length > 0 && (
|
|
583
|
+
<section class="sg-block">
|
|
584
|
+
<p class="sg-h">Semantic tokens (v1.1)</p>
|
|
585
|
+
<div class="sg-swatches">
|
|
586
|
+
{(tk.semantic ?? []).map((c: any) => (
|
|
587
|
+
<div class="sg-sw">
|
|
588
|
+
<div class="chip" style={`background:${c.value}`}></div>
|
|
589
|
+
<div class="meta">
|
|
590
|
+
<b>{c.name}</b>
|
|
591
|
+
<code>{c.var} · {c.value}</code>
|
|
592
|
+
<span class="role">{c.role}</span>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
))}
|
|
596
|
+
</div>
|
|
597
|
+
</section>
|
|
598
|
+
)}
|
|
599
|
+
|
|
600
|
+
<section class="sg-block">
|
|
601
|
+
<p class="sg-h">Typography</p>
|
|
602
|
+
<h1>Heading one with a <span class="grad">gradient</span> accent</h1>
|
|
603
|
+
<h2>Heading two — section title</h2>
|
|
604
|
+
<p class="lede">Lede paragraph — the larger intro copy that sets up a section in muted tone.</p>
|
|
605
|
+
<p>Body copy. {tk.type?.sans}</p>
|
|
606
|
+
<p class="kicker">Kicker label</p>
|
|
607
|
+
<p class="support-line">Support line — warm emphasis (coral), AA on cream.</p>
|
|
608
|
+
</section>
|
|
609
|
+
|
|
610
|
+
<section class="sg-block">
|
|
611
|
+
<p class="sg-h">Buttons — CTA hierarchy & states</p>
|
|
612
|
+
<div class="cta-row" style="margin-bottom:16px">
|
|
613
|
+
<a class="btn btn-primary" href="#">Primary CTA</a>
|
|
614
|
+
<a class="btn btn-secondary" href="#">Secondary</a>
|
|
615
|
+
<a class="btn btn-tertiary" href="#">Tertiary / outline</a>
|
|
616
|
+
</div>
|
|
617
|
+
<div class="cta-row">
|
|
618
|
+
<button class="btn btn-primary" type="button">Default</button>
|
|
619
|
+
<button class="btn btn-primary" type="button" style="transform:translateY(-2px)">Hover</button>
|
|
620
|
+
<button class="btn btn-primary is-loading" type="button" aria-busy="true">Loading</button>
|
|
621
|
+
<button class="btn btn-primary" type="button" disabled>Disabled</button>
|
|
622
|
+
</div>
|
|
623
|
+
<p class="form-help" style="margin-top:12px">All targets ≥44px. Tab to any control to see the <code>:focus-visible</code> ring.</p>
|
|
624
|
+
</section>
|
|
625
|
+
|
|
626
|
+
<section class="sg-block">
|
|
627
|
+
<p class="sg-h">Accessible form</p>
|
|
628
|
+
<form action="#" style="max-width:420px">
|
|
629
|
+
<div class="form-field">
|
|
630
|
+
<label class="form-label" for="sg-email">Work email <span class="req" aria-hidden="true">*</span></label>
|
|
631
|
+
<input class="form-control" id="sg-email" type="email" required aria-describedby="sg-email-help" placeholder="you@company.com" />
|
|
632
|
+
<span class="form-help" id="sg-email-help">We only use this to send your demo link.</span>
|
|
633
|
+
</div>
|
|
634
|
+
<div class="form-field">
|
|
635
|
+
<label class="form-label" for="sg-co">Company <span class="opt">(optional)</span></label>
|
|
636
|
+
<input class="form-control" id="sg-co" type="text" placeholder="Acme Co." />
|
|
637
|
+
</div>
|
|
638
|
+
<div class="form-field">
|
|
639
|
+
<label class="form-label" for="sg-err">Invalid example</label>
|
|
640
|
+
<input class="form-control is-error" id="sg-err" type="text" aria-invalid="true" aria-describedby="sg-err-msg" value="not-an-email" />
|
|
641
|
+
<span class="field-error" id="sg-err-msg">Enter a valid email address.</span>
|
|
642
|
+
</div>
|
|
643
|
+
<span class="field-success">Looks good — success state.</span>
|
|
644
|
+
</form>
|
|
645
|
+
</section>
|
|
646
|
+
|
|
647
|
+
<section class="sg-block">
|
|
648
|
+
<p class="sg-h">Trust & social proof <span class="opt" style="text-transform:none;letter-spacing:0">(placeholder — source before use)</span></p>
|
|
649
|
+
<div class="sg-row" style="margin-bottom:16px;flex-wrap:wrap;gap:14px">
|
|
650
|
+
<span class="rating"><span class="stars" aria-hidden="true">★★★★★</span> <span>4.9 / 5 — needs-review</span></span>
|
|
651
|
+
<span class="trust-badge">🔒 Security / compliance badge — needs-review</span>
|
|
652
|
+
<span class="reassurance">✓ Guarantee / risk-free reassurance — needs-review</span>
|
|
653
|
+
</div>
|
|
654
|
+
<div class="testimonial" style="max-width:520px;margin-bottom:16px">
|
|
655
|
+
<blockquote class="quote">“Testimonial / customer quote placeholder — do not ship until sourced.”</blockquote>
|
|
656
|
+
<p class="cite">— Role, Company · <b class="opt">needs-review</b></p>
|
|
657
|
+
</div>
|
|
658
|
+
<p class="form-help" style="margin-bottom:8px">Logo strip — "as seen in" / customer & partner logos (placeholder):</p>
|
|
659
|
+
<div class="logo-strip">
|
|
660
|
+
<span class="logo">LOGO</span><span class="logo">LOGO</span><span class="logo">LOGO</span><span class="logo">LOGO</span>
|
|
661
|
+
</div>
|
|
662
|
+
</section>
|
|
663
|
+
|
|
664
|
+
<section class="sg-block">
|
|
665
|
+
<p class="sg-h">Components</p>
|
|
666
|
+
<div class="sg-row" style="margin-bottom:20px">
|
|
667
|
+
<span class="eyebrow"><span class="dot"></span> Eyebrow / status chip</span>
|
|
668
|
+
</div>
|
|
669
|
+
<div class="sg-card">
|
|
670
|
+
<p class="kicker">Card</p>
|
|
671
|
+
<h2 style="font-size:1.15rem">Card surface</h2>
|
|
672
|
+
<p class="lede" style="font-size:.95rem">Uses <code>--surface-card</code>, <code>--r</code>, <code>--shadow</code>.</p>
|
|
673
|
+
</div>
|
|
674
|
+
</section>
|
|
675
|
+
|
|
676
|
+
<section class="sg-block">
|
|
677
|
+
<p class="sg-h">Proof strip</p>
|
|
678
|
+
</section>
|
|
679
|
+
</div>
|
|
680
|
+
|
|
681
|
+
<div class="proof">
|
|
682
|
+
<div class="container">
|
|
683
|
+
<span><b>200+</b> countries</span><span class="sep">·</span>
|
|
684
|
+
<span>GDPR · CCPA · PIPEDA</span><span class="sep">·</span>
|
|
685
|
+
<span><b>256-bit</b> SSL</span>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
<div class="container">
|
|
690
|
+
<section class="sg-block">
|
|
691
|
+
<p class="sg-h">Spacing scale</p>
|
|
692
|
+
<div class="sg-space">
|
|
693
|
+
{(tk.space ?? []).map((s: any) => (
|
|
694
|
+
<div>
|
|
695
|
+
<div class="bar" style={`width:${s.value};height:${s.value}`}></div>
|
|
696
|
+
<div class="lab">{s.name}<br />{s.value}</div>
|
|
697
|
+
</div>
|
|
698
|
+
))}
|
|
699
|
+
</div>
|
|
700
|
+
</section>
|
|
701
|
+
|
|
702
|
+
<section class="sg-block">
|
|
703
|
+
<p class="sg-h">Shape</p>
|
|
704
|
+
<div class="sg-shape">
|
|
705
|
+
{(tk.shape ?? []).map((s: any) => (
|
|
706
|
+
<div class="box" style={`border:1px solid var(--line);border-radius:${(s.var === '--r' || s.var === '--r-sm') ? s.value : '12px'};box-shadow:${s.name.includes('shadow') ? s.value : 'none'}`}>
|
|
707
|
+
{s.name}
|
|
708
|
+
</div>
|
|
709
|
+
))}
|
|
710
|
+
</div>
|
|
711
|
+
</section>
|
|
712
|
+
|
|
713
|
+
{/* ── Marketing page components (mkt.css) ──────────────────────────
|
|
714
|
+
The shared composition palette every ported marketing page draws
|
|
715
|
+
from. Keep this in sync with mkt.css: when a port harvests a new
|
|
716
|
+
reusable pattern into mkt.css, add a sample here (see
|
|
717
|
+
playbook/site-migration/style-concept.md §Harvesting components). */}
|
|
718
|
+
{theme === "tot-navy" && (
|
|
719
|
+
<>
|
|
720
|
+
<section class="sg-block">
|
|
721
|
+
<p class="sg-h">Marketing components — <code>mkt.css</code></p>
|
|
722
|
+
<p class="lede" style="font-size:.95rem">The reusable page-composition layer shared by every ported marketing page. One palette; refine here, and every page inherits it.</p>
|
|
723
|
+
</section>
|
|
724
|
+
|
|
725
|
+
<section class="sg-block">
|
|
726
|
+
<p class="sg-h">Breadcrumb — <code>.crumbs</code></p>
|
|
727
|
+
<nav class="crumbs" aria-label="Breadcrumb sample"><a href="#">Home</a><span class="sep">›</span><a href="#">Platform</a><span class="sep">›</span>This page</nav>
|
|
728
|
+
</section>
|
|
729
|
+
|
|
730
|
+
<section class="sg-block">
|
|
731
|
+
<p class="sg-h">Hero — <code>.hero</code> (centred)</p>
|
|
732
|
+
<div class="hero" style="padding:24px 0 0">
|
|
733
|
+
<span class="eyebrow"><span class="dot"></span> Section kicker</span>
|
|
734
|
+
<h1 style="margin-top:14px">Headline with a <span class="grad">gradient accent</span>.</h1>
|
|
735
|
+
<p class="lede">The lede sets up the page in one muted sentence.</p>
|
|
736
|
+
<div class="cta-row"><a class="btn btn-green" href="#">Primary</a><a class="btn btn-line" href="#">Secondary</a></div>
|
|
737
|
+
</div>
|
|
738
|
+
</section>
|
|
739
|
+
|
|
740
|
+
<section class="sg-block">
|
|
741
|
+
<p class="sg-h">Feature grid — <code>.feature-grid</code> / <code>.feat-card</code></p>
|
|
742
|
+
<div class="feature-grid three">
|
|
743
|
+
<article class="feat-card accent"><h3>Card with accent</h3><p>Top-accent variant for category cards.</p><a class="card-link" href="#">Explore →</a></article>
|
|
744
|
+
<article class="feat-card"><h3>Plain card</h3><p>Default surface card with hover lift.</p><a class="card-link" href="#">Explore →</a></article>
|
|
745
|
+
<article class="feat-card"><h3>Third card</h3><p>Grid collapses 3→2→1 responsively.</p><a class="card-link" href="#">Explore →</a></article>
|
|
746
|
+
</div>
|
|
747
|
+
</section>
|
|
748
|
+
|
|
749
|
+
<section class="sg-block">
|
|
750
|
+
<p class="sg-h">Media split — <code>.split</code> + <code>.check-list</code></p>
|
|
751
|
+
<div class="split">
|
|
752
|
+
<div class="split-copy">
|
|
753
|
+
<span class="kicker">Split section</span>
|
|
754
|
+
<h2>Copy beside a proof list</h2>
|
|
755
|
+
<p>Use for “what you get” sections: narrative on one side, a scannable check-list on the other.</p>
|
|
756
|
+
<a class="btn btn-navy" href="#">Learn more</a>
|
|
757
|
+
</div>
|
|
758
|
+
<ul class="check-list">
|
|
759
|
+
<li>International coverage in 200+ countries</li>
|
|
760
|
+
<li>Document + database verification</li>
|
|
761
|
+
<li>Sanction & watchlist screening</li>
|
|
762
|
+
</ul>
|
|
763
|
+
</div>
|
|
764
|
+
</section>
|
|
765
|
+
|
|
766
|
+
<section class="sg-block">
|
|
767
|
+
<p class="sg-h">Steps — <code>.steps</code> (numbered how-it-works)</p>
|
|
768
|
+
<div class="steps three">
|
|
769
|
+
<div class="step"><span class="num">01</span><h3>Select</h3><p>Choose the verification level.</p></div>
|
|
770
|
+
<div class="step"><span class="num">02</span><h3>Integrate</h3><p>Embed the SDK or API.</p></div>
|
|
771
|
+
<div class="step"><span class="num">03</span><h3>Verify</h3><p>Customers clear in seconds.</p></div>
|
|
772
|
+
</div>
|
|
773
|
+
</section>
|
|
774
|
+
|
|
775
|
+
<section class="sg-block">
|
|
776
|
+
<p class="sg-h">Stat row — <code>.stat-row</code></p>
|
|
777
|
+
<div class="stat-row three">
|
|
778
|
+
<div class="stat"><b>200+</b><span>countries — needs-review</span></div>
|
|
779
|
+
<div class="stat"><b><30s</b><span>typical verification — needs-review</span></div>
|
|
780
|
+
<div class="stat"><b>24/7</b><span>compliance evidence</span></div>
|
|
781
|
+
</div>
|
|
782
|
+
</section>
|
|
783
|
+
|
|
784
|
+
<section class="sg-block">
|
|
785
|
+
<p class="sg-h">Reassurance row — <code>.reassure-row</code></p>
|
|
786
|
+
<div class="reassure-row">
|
|
787
|
+
<div class="reassurance">256-bit SSL encryption on every device.</div>
|
|
788
|
+
<div class="reassurance">We will <b>never</b> sell your data.</div>
|
|
789
|
+
<div class="reassurance">Compliant with GDPR & global privacy laws.</div>
|
|
790
|
+
</div>
|
|
791
|
+
</section>
|
|
792
|
+
|
|
793
|
+
<section class="sg-block">
|
|
794
|
+
<p class="sg-h">FAQ — <code>.faq</code> (details/summary)</p>
|
|
795
|
+
<div class="faq">
|
|
796
|
+
<details open><summary>How does verification work?</summary><p>A concise, source-copied answer goes here.</p></details>
|
|
797
|
+
<details><summary>Which platforms are supported?</summary><p>Shopify, BigCommerce, WooCommerce, and more.</p></details>
|
|
798
|
+
</div>
|
|
799
|
+
</section>
|
|
800
|
+
|
|
801
|
+
<section class="sg-block">
|
|
802
|
+
<p class="sg-h">CTA band — <code>.cta-band</code></p>
|
|
803
|
+
<div class="cta-band">
|
|
804
|
+
<span class="kicker">Get started</span>
|
|
805
|
+
<h2>Build trust into every transaction</h2>
|
|
806
|
+
<p>Closing conversion band used at the foot of every marketing page.</p>
|
|
807
|
+
<div class="cta-row"><a class="btn btn-ent" href="#">Start For Free</a><a class="btn btn-line" href="#">Contact Sales</a></div>
|
|
808
|
+
</div>
|
|
809
|
+
</section>
|
|
810
|
+
</>
|
|
811
|
+
)}
|
|
812
|
+
</div>
|
|
813
|
+
|
|
814
|
+
{/* ── Commerce + chrome component library ─────────────────────────────
|
|
815
|
+
The reusable, tenant-agnostic ecommerce components (product cards +
|
|
816
|
+
merchandising sections) and site chrome. Rendered from hard-coded FIXTURE
|
|
817
|
+
products so this works with no live catalog. These are STANDARD components:
|
|
818
|
+
fidelity comes from theme tokens + variant props, never a per-tenant fork.
|
|
819
|
+
See playbook/site-migration/shared-chrome.md. */}
|
|
820
|
+
<div class="sg-commerce" style={componentVarsInline}>
|
|
821
|
+
<section class="sg-block">
|
|
822
|
+
<p class="sg-h">Page sections — the site rethemed</p>
|
|
823
|
+
<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>
|
|
824
|
+
<div class="sg-hero" style="margin-top:18px">
|
|
825
|
+
<div class="copy">
|
|
826
|
+
<span class="kick">New this week</span>
|
|
827
|
+
<h3>The look, rethemed end to end.</h3>
|
|
828
|
+
<p>Hero, promos, and tiles inherit the selected theme's color, type, and shape tokens.</p>
|
|
829
|
+
<a class="sg-btn" href="#">Shop new arrivals →</a>
|
|
830
|
+
</div>
|
|
831
|
+
<div class="promo">
|
|
832
|
+
<div>
|
|
833
|
+
<span class="kick" style="color:var(--color-accent);font:800 .72rem/1 -apple-system,sans-serif;letter-spacing:.14em;text-transform:uppercase">Featured</span>
|
|
834
|
+
<h4 style="font-family:var(--font-display);font-size:1.25rem;margin:8px 0 4px">Right-rail promo tile</h4>
|
|
835
|
+
<p style="margin:0;color:var(--color-muted);font-size:.9rem">Two-up hero: banner + promo.</p>
|
|
836
|
+
</div>
|
|
837
|
+
</div>
|
|
838
|
+
</div>
|
|
839
|
+
</section>
|
|
840
|
+
|
|
841
|
+
<section class="sg-block">
|
|
842
|
+
<p class="sg-h">Promo cards</p>
|
|
843
|
+
<div class="sg-promos">
|
|
844
|
+
<div class="sg-promo"><span class="tag">Sale</span><h4>Clearance event</h4><p>Discount accent uses <code>--color-sale</code>.</p></div>
|
|
845
|
+
<div class="sg-promo"><span class="tag" style="background:var(--color-primary)">Compliant</span><h4>Texas-compliant picks</h4><p>Primary brand color on the badge.</p></div>
|
|
846
|
+
<div class="sg-promo"><span class="tag" style="background:var(--color-success)">In stock</span><h4>Ships today</h4><p>Availability accent from <code>--color-success</code>.</p></div>
|
|
847
|
+
</div>
|
|
848
|
+
</section>
|
|
849
|
+
|
|
850
|
+
<section class="sg-block">
|
|
851
|
+
<p class="sg-h">Category tiles</p>
|
|
852
|
+
<div class="sg-tiles">
|
|
853
|
+
<a class="sg-tile" href="#"><span class="ico" aria-hidden="true">◈</span><span>Disposables</span></a>
|
|
854
|
+
<a class="sg-tile" href="#"><span class="ico" aria-hidden="true">◇</span><span>E-Liquid</span></a>
|
|
855
|
+
<a class="sg-tile" href="#"><span class="ico" aria-hidden="true">▣</span><span>Devices</span></a>
|
|
856
|
+
<a class="sg-tile" href="#"><span class="ico" aria-hidden="true">◉</span><span>Coils & Pods</span></a>
|
|
857
|
+
</div>
|
|
858
|
+
</section>
|
|
859
|
+
|
|
860
|
+
<section class="sg-block">
|
|
861
|
+
<p class="sg-h">Commerce atoms — PriceDisplay / Badge / RatingStars</p>
|
|
862
|
+
<div class="sg-row" style="gap:28px;align-items:flex-start">
|
|
863
|
+
<div>
|
|
864
|
+
<PriceDisplay amount={28} currency="USD" />
|
|
865
|
+
<PriceDisplay amount={64} compareAt={96} currency="USD" showBadge class="mt-2" />
|
|
866
|
+
<PriceDisplay amount={42} from currency="USD" class="mt-2" />
|
|
867
|
+
</div>
|
|
868
|
+
<div class="sg-row" style="gap:8px">
|
|
869
|
+
<Badge variant="new" label="New" />
|
|
870
|
+
<Badge variant="sale" label="-33%" />
|
|
871
|
+
<Badge variant="compliant" label="Compliant" />
|
|
872
|
+
<Badge variant="soldout" label="Sold out" />
|
|
873
|
+
<Badge variant="low-stock" label="Low stock" />
|
|
874
|
+
</div>
|
|
875
|
+
<div>
|
|
876
|
+
<RatingStars rating={4.5} count={128} />
|
|
877
|
+
<div class="mt-2"><RatingStars rating={3} count={12} /></div>
|
|
878
|
+
<div class="mt-2"><RatingStars rating={5} count={4} /></div>
|
|
879
|
+
</div>
|
|
880
|
+
</div>
|
|
881
|
+
</section>
|
|
882
|
+
|
|
883
|
+
<section class="sg-block">
|
|
884
|
+
<p class="sg-h">ReviewSummary / ReviewList — reviews & UGC (fixture data, see docs/architecture/review-markup-policy.md)</p>
|
|
885
|
+
<div class="mb-5">
|
|
886
|
+
<ReviewSummary average={4.3} count={sampleReviews.length} href="#" />
|
|
887
|
+
</div>
|
|
888
|
+
<ReviewList reviews={sampleReviews} />
|
|
889
|
+
</section>
|
|
890
|
+
|
|
891
|
+
<section class="sg-block">
|
|
892
|
+
<p class="sg-h">ProductCard — state permutations</p>
|
|
893
|
+
<div class="grid grid-cols-2 gap-5 lg:grid-cols-4">
|
|
894
|
+
{sampleProducts.map((p) => (
|
|
895
|
+
<ProductCard
|
|
896
|
+
product={p}
|
|
897
|
+
rating={sampleRatings[p.handle]?.rating}
|
|
898
|
+
reviewCount={sampleRatings[p.handle]?.count}
|
|
899
|
+
/>
|
|
900
|
+
))}
|
|
901
|
+
</div>
|
|
902
|
+
</section>
|
|
903
|
+
|
|
904
|
+
<section class="sg-block">
|
|
905
|
+
<p class="sg-h">ProductCarousel — horizontal scroll (fixtures)</p>
|
|
906
|
+
<ProductCarousel title="Best Sellers" eyebrow="Shop" products={sampleProducts} tone="surface" />
|
|
907
|
+
</section>
|
|
908
|
+
|
|
909
|
+
<section class="sg-block">
|
|
910
|
+
<p class="sg-h">ProductCarousel — tabbed (each tab its own source)</p>
|
|
911
|
+
<ProductCarousel
|
|
912
|
+
title="Featured"
|
|
913
|
+
tabs={[
|
|
914
|
+
{ label: "New", products: sampleProducts.slice(0, 4) },
|
|
915
|
+
{ label: "On Sale", products: sampleProducts.slice(1, 5) },
|
|
916
|
+
{ label: "Best Sellers", products: sampleProducts.slice(2, 6) },
|
|
917
|
+
]}
|
|
918
|
+
/>
|
|
919
|
+
</section>
|
|
920
|
+
|
|
921
|
+
<section class="sg-block">
|
|
922
|
+
<p class="sg-h">ProductGrid — paginated-look grid (fixtures)</p>
|
|
923
|
+
<ProductGrid title="All Products" products={sampleProducts} columns={3} />
|
|
924
|
+
</section>
|
|
925
|
+
|
|
926
|
+
<section class="sg-block">
|
|
927
|
+
<p class="sg-h">BrandGrid — text wordmarks (Shop by Brands)</p>
|
|
928
|
+
<BrandGrid
|
|
929
|
+
title="Shop by Brands"
|
|
930
|
+
eyebrow="Find your fav"
|
|
931
|
+
brands={sampleBrandsText}
|
|
932
|
+
viewAllHref="#"
|
|
933
|
+
tone="surface"
|
|
934
|
+
/>
|
|
935
|
+
</section>
|
|
936
|
+
|
|
937
|
+
<section class="sg-block">
|
|
938
|
+
<p class="sg-h">BrandGrid — logo tiles (image path; placeholders offline)</p>
|
|
939
|
+
<BrandGrid title="Our Brands" brands={sampleBrandsLogo} />
|
|
940
|
+
</section>
|
|
941
|
+
|
|
942
|
+
<section class="sg-block">
|
|
943
|
+
<p class="sg-h">TagFilterCloud — blog/article tag filter (URL-addressable, zero-JS)</p>
|
|
944
|
+
<TagFilterCloud
|
|
945
|
+
title="Browse by topic"
|
|
946
|
+
tags={sampleTags}
|
|
947
|
+
allHref="#"
|
|
948
|
+
allActive={false}
|
|
949
|
+
/>
|
|
950
|
+
<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>
|
|
951
|
+
</section>
|
|
952
|
+
|
|
953
|
+
<section class="sg-block">
|
|
954
|
+
<p class="sg-h">ArticleCard — blog cards (featured image + excerpt + meta; last card image-less)</p>
|
|
955
|
+
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
956
|
+
{sampleArticles.map((article) => <ArticleCard article={article} />)}
|
|
957
|
+
</div>
|
|
958
|
+
</section>
|
|
959
|
+
|
|
960
|
+
<section class="sg-block">
|
|
961
|
+
<p class="sg-h">Chrome — AnnouncementBar (static + marquee)</p>
|
|
962
|
+
<AnnouncementBar text="Free US shipping on orders over $100" variant="static" />
|
|
963
|
+
<div class="mt-3">
|
|
964
|
+
<AnnouncementBar text="Free US shipping on orders over $100!" variant="marquee" tone="sale" />
|
|
965
|
+
</div>
|
|
966
|
+
</section>
|
|
967
|
+
|
|
968
|
+
<section class="sg-block">
|
|
969
|
+
<p class="sg-h">Chrome — UtilityNav</p>
|
|
970
|
+
<UtilityNav
|
|
971
|
+
links={[{ label: "News", href: "#" }, { label: "About", href: "#" }, { label: "Contact", href: "#" }]}
|
|
972
|
+
locale="English"
|
|
973
|
+
currency="United States (USD $)"
|
|
974
|
+
social={sampleSocial}
|
|
975
|
+
/>
|
|
976
|
+
</section>
|
|
977
|
+
|
|
978
|
+
<section class="sg-block">
|
|
979
|
+
<p class="sg-h">Chrome — SiteHeader (logo + search + account + cart link + nav)</p>
|
|
980
|
+
<SiteHeader
|
|
981
|
+
wordmark="Sample Store"
|
|
982
|
+
nav={sampleNav}
|
|
983
|
+
searchAction="/search"
|
|
984
|
+
accountHref="/account"
|
|
985
|
+
cartHref="/cart"
|
|
986
|
+
cartCount={2}
|
|
987
|
+
sticky={false}
|
|
988
|
+
/>
|
|
989
|
+
</section>
|
|
990
|
+
|
|
991
|
+
<section class="sg-block">
|
|
992
|
+
<p class="sg-h">Chrome — MegaMenu / NavDropdown (multi-column + caret + NEW badge)</p>
|
|
993
|
+
<div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:8px;padding:8px 16px">
|
|
994
|
+
<MegaMenu items={sampleNav} class="!block" />
|
|
995
|
+
</div>
|
|
996
|
+
<p class="form-help" style="margin-top:8px;color:var(--color-muted)">Hover/focus a caret item to reveal its dropdown (CSS-only).</p>
|
|
997
|
+
</section>
|
|
998
|
+
|
|
999
|
+
<section class="sg-block">
|
|
1000
|
+
<p class="sg-h">Chrome — NavDropdown accordion (mobile mega parity, zero-JS)</p>
|
|
1001
|
+
<div style="max-width:18rem;background:var(--color-surface);border:1px solid var(--color-border);border-radius:8px;padding:8px 12px">
|
|
1002
|
+
<ul class="grid">
|
|
1003
|
+
{sampleNav.map((item) => <NavDropdown item={item} mode="accordion" />)}
|
|
1004
|
+
</ul>
|
|
1005
|
+
</div>
|
|
1006
|
+
<p class="form-help" style="margin-top:8px;color:var(--color-muted)">Same nav items in the mobile menu: mega columns become inline <code><details></code> accordions (keyboard + touch operable). Toggle a caret item to expand.</p>
|
|
1007
|
+
</section>
|
|
1008
|
+
|
|
1009
|
+
<section class="sg-block">
|
|
1010
|
+
<p class="sg-h">Chrome — SiteFooter (columns + newsletter + payments + compliance)</p>
|
|
1011
|
+
<SiteFooter
|
|
1012
|
+
brand={{ wordmark: "Sample Store", contact: ["hello@example.com", "1 (800) 555-0100"] }}
|
|
1013
|
+
columns={sampleFooterCols}
|
|
1014
|
+
newsletter={{ title: "Join the community", body: "New arrivals and exclusive offers.", placeholder: "Email" }}
|
|
1015
|
+
payments={["Visa", "Mastercard", "Amex", "Discover", "PayPal", "Apple Pay"]}
|
|
1016
|
+
social={sampleSocial}
|
|
1017
|
+
compliance="Must be 21+ to purchase. Products intended for adults of legal age only."
|
|
1018
|
+
legal="© Sample Store. All rights reserved."
|
|
1019
|
+
/>
|
|
1020
|
+
</section>
|
|
1021
|
+
|
|
1022
|
+
{/* ── Compliance notice widgets (components/compliance/) ───────────────
|
|
1023
|
+
Platform-owned, data-driven regulated-commerce notices. Each renders
|
|
1024
|
+
ONLY from tenant.compliance data and NEVER fabricates legal copy —
|
|
1025
|
+
here they are fed illustrative sample config so the guide exercises
|
|
1026
|
+
every widget + variant under the theme. In production a tenant opts in
|
|
1027
|
+
via its compliance block; absent data renders nothing. See decision
|
|
1028
|
+
compliance-widgets-platform-owned. */}
|
|
1029
|
+
<section class="sg-block">
|
|
1030
|
+
<p class="sg-h">Compliance notices — <code>components/compliance/</code></p>
|
|
1031
|
+
<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>
|
|
1032
|
+
|
|
1033
|
+
<p class="form-help" style="margin:18px 0 8px">NicotineWarning — <code>data-compliance="nicotine-warning"</code> (FDA 21 CFR 1143.3, fixed)</p>
|
|
1034
|
+
<NicotineWarning variant="bar" />
|
|
1035
|
+
<div class="mt-3"><NicotineWarning variant="block" /></div>
|
|
1036
|
+
|
|
1037
|
+
<p class="form-help" style="margin:22px 0 8px">PactActNotice — <code>data-compliance="pact-act"</code> (bar / block / footer)</p>
|
|
1038
|
+
<PactActNotice enabled minAge={21} variant="bar" />
|
|
1039
|
+
<div class="mt-3"><PactActNotice enabled minAge={21} variant="block" /></div>
|
|
1040
|
+
<div class="mt-3"><PactActNotice enabled minAge={21} variant="footer" /></div>
|
|
1041
|
+
|
|
1042
|
+
<p class="form-help" style="margin:22px 0 8px">AdultSignatureNotice — <code>data-compliance="adult-signature"</code> (inline / block)</p>
|
|
1043
|
+
<AdultSignatureNotice enabled minAge={21} variant="inline" />
|
|
1044
|
+
<div class="mt-3"><AdultSignatureNotice enabled minAge={21} variant="block" /></div>
|
|
1045
|
+
|
|
1046
|
+
<p class="form-help" style="margin:22px 0 8px">Prop65Warning — <code>data-compliance="prop65"</code> (short + long form, mandated skeleton)</p>
|
|
1047
|
+
<Prop65Warning config={true} variant="block" />
|
|
1048
|
+
<div class="mt-3">
|
|
1049
|
+
<Prop65Warning
|
|
1050
|
+
config={{ harm: "reproductive", chemicals: ["nicotine"] }}
|
|
1051
|
+
variant="block"
|
|
1052
|
+
/>
|
|
1053
|
+
</div>
|
|
1054
|
+
<div class="mt-3"><Prop65Warning config={true} variant="bar" /></div>
|
|
1055
|
+
|
|
1056
|
+
<p class="form-help" style="margin:22px 0 8px">PurchaseLimitNotice — <code>data-compliance="purchase-limit"</code> (display only; enforcement is product-owned)</p>
|
|
1057
|
+
<PurchaseLimitNotice
|
|
1058
|
+
config={{ text: "2 units per order", reason: "per state law" }}
|
|
1059
|
+
variant="inline"
|
|
1060
|
+
/>
|
|
1061
|
+
<div class="mt-3">
|
|
1062
|
+
<PurchaseLimitNotice
|
|
1063
|
+
config={{ text: "6 bottles per order" }}
|
|
1064
|
+
variant="block"
|
|
1065
|
+
/>
|
|
1066
|
+
</div>
|
|
1067
|
+
|
|
1068
|
+
<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>
|
|
1069
|
+
<StateEligibilityNotice
|
|
1070
|
+
config={{ restrictedStates: ["UT", "AR", "VT"] }}
|
|
1071
|
+
variant="block"
|
|
1072
|
+
/>
|
|
1073
|
+
<div class="mt-3">
|
|
1074
|
+
<StateEligibilityNotice
|
|
1075
|
+
config={{ eligibleStates: ["CA", "TX", "FL", "NY"], note: "This product ships to select states only." }}
|
|
1076
|
+
variant="bar"
|
|
1077
|
+
/>
|
|
1078
|
+
</div>
|
|
1079
|
+
|
|
1080
|
+
<p class="form-help" style="margin:22px 0 8px">ShippingRestrictionNotice — <code>data-compliance="shipping-restriction"</code> (the platform footer band; display only)</p>
|
|
1081
|
+
<ShippingRestrictionNotice
|
|
1082
|
+
text="Adult signature (21+) required on delivery. Not shipped via USPS."
|
|
1083
|
+
variant="footer"
|
|
1084
|
+
/>
|
|
1085
|
+
<div class="mt-3">
|
|
1086
|
+
<ShippingRestrictionNotice
|
|
1087
|
+
text="No shipping to PO boxes for age-restricted products."
|
|
1088
|
+
variant="block"
|
|
1089
|
+
/>
|
|
1090
|
+
</div>
|
|
1091
|
+
|
|
1092
|
+
<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>
|
|
1093
|
+
<ExciseTaxNotice
|
|
1094
|
+
config={{ note: "Applicable state vapor excise taxes are calculated at checkout." }}
|
|
1095
|
+
variant="inline"
|
|
1096
|
+
/>
|
|
1097
|
+
<div class="mt-3">
|
|
1098
|
+
<ExciseTaxNotice
|
|
1099
|
+
config={{
|
|
1100
|
+
note: "State excise taxes apply:",
|
|
1101
|
+
jurisdictions: [
|
|
1102
|
+
{ label: "CA", detail: "$0.05/ml" },
|
|
1103
|
+
{ label: "NY", detail: "20% wholesale" },
|
|
1104
|
+
],
|
|
1105
|
+
}}
|
|
1106
|
+
variant="block"
|
|
1107
|
+
/>
|
|
1108
|
+
</div>
|
|
1109
|
+
</section>
|
|
1110
|
+
|
|
1111
|
+
{/* ── Membership / pricing tier widgets (components/membership/) ───────
|
|
1112
|
+
Education-only tier comparison (Guest/Member/Autopilot). Presentational
|
|
1113
|
+
— no pricing/checkout/subscription mechanics. Figures are ILLUSTRATIVE
|
|
1114
|
+
by default: unless a tier asserts real:true the widget renders a
|
|
1115
|
+
first-class "Illustrative figures" affordance (badge + "e.g." + note)
|
|
1116
|
+
so an example number is never shown as a live offer (decision
|
|
1117
|
+
compliance-widgets-platform-owned). Fed sample tiers here; a tenant
|
|
1118
|
+
supplies its own on a membership page. */}
|
|
1119
|
+
<section class="sg-block">
|
|
1120
|
+
<p class="sg-h">Membership tiers — <code>components/membership/</code></p>
|
|
1121
|
+
<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>
|
|
1122
|
+
|
|
1123
|
+
<p class="form-help" style="margin:18px 0 8px">TierCards — <code>data-widget="tier-cards"</code> (illustrative badge + "e.g." + footnote; featured ribbon)</p>
|
|
1124
|
+
<TierCards
|
|
1125
|
+
eyebrow="Membership"
|
|
1126
|
+
heading="Choose how you shop"
|
|
1127
|
+
subhead="Compare guest checkout with our membership tiers."
|
|
1128
|
+
tiers={sampleTiers}
|
|
1129
|
+
/>
|
|
1130
|
+
|
|
1131
|
+
<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>
|
|
1132
|
+
<TierComparisonTable
|
|
1133
|
+
eyebrow="Compare"
|
|
1134
|
+
heading="What each tier includes"
|
|
1135
|
+
tiers={sampleTiers}
|
|
1136
|
+
rows={sampleComparisonRows}
|
|
1137
|
+
/>
|
|
1138
|
+
</section>
|
|
1139
|
+
|
|
1140
|
+
{/* ── Subscription/Autopilot education widgets (components/subscription/)
|
|
1141
|
+
Education-only: explains the Autopilot concept and hands off to the
|
|
1142
|
+
tenant's product-owned subscribe/manage flow — no scheduling,
|
|
1143
|
+
billing, or cancellation mechanics of its own. The savings figure is
|
|
1144
|
+
ILLUSTRATIVE by default (same affordance as membership tiers; decision
|
|
1145
|
+
compliance-widgets-platform-owned). Fed sample content here; a tenant
|
|
1146
|
+
supplies its own on an Autopilot/subscription page. */}
|
|
1147
|
+
<section class="sg-block">
|
|
1148
|
+
<p class="sg-h">Subscription/Autopilot education — <code>components/subscription/</code></p>
|
|
1149
|
+
<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>
|
|
1150
|
+
|
|
1151
|
+
<p class="form-help" style="margin:18px 0 8px">HowItWorksSteps — <code>data-widget="subscription-how-it-works"</code></p>
|
|
1152
|
+
<HowItWorksSteps
|
|
1153
|
+
eyebrow="Autopilot"
|
|
1154
|
+
heading="How Autopilot works"
|
|
1155
|
+
subhead="Set it up once — we handle the rest."
|
|
1156
|
+
steps={sampleSubscriptionSteps}
|
|
1157
|
+
/>
|
|
1158
|
+
|
|
1159
|
+
<p class="form-help" style="margin:28px 0 8px">SavingsExplainer — <code>data-widget="subscription-savings-explainer"</code> (illustrative badge + "e.g." + footnote)</p>
|
|
1160
|
+
<SavingsExplainer
|
|
1161
|
+
eyebrow="Autopilot savings"
|
|
1162
|
+
heading="Save on every order"
|
|
1163
|
+
figure={{ value: "Save 15%", caption: "vs. one-time purchase" }}
|
|
1164
|
+
bullets={["Free shipping on every Autopilot order", "Best per-unit pricing", "Pause or cancel anytime"]}
|
|
1165
|
+
cta={{ label: "How Autopilot works", href: "#" }}
|
|
1166
|
+
/>
|
|
1167
|
+
|
|
1168
|
+
<p class="form-help" style="margin:28px 0 8px">ManageSubscriptionEntry — <code>data-widget="subscription-manage-entry"</code></p>
|
|
1169
|
+
<ManageSubscriptionEntry
|
|
1170
|
+
eyebrow="Manage"
|
|
1171
|
+
heading="Manage your Autopilot"
|
|
1172
|
+
entries={sampleManageEntries}
|
|
1173
|
+
/>
|
|
1174
|
+
</section>
|
|
1175
|
+
|
|
1176
|
+
{/* ── Interactive islands (components/islands/) ─────────────────────
|
|
1177
|
+
Client-hydrated Preact islands. Each is tenant-agnostic and reads
|
|
1178
|
+
only fixture/localStorage data here — no live catalog, cart, or ToT
|
|
1179
|
+
calls (unit style-guide-full-coverage). */}
|
|
1180
|
+
<section class="sg-block">
|
|
1181
|
+
<p class="sg-h">Islands — <code>components/islands/</code></p>
|
|
1182
|
+
<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>
|
|
1183
|
+
|
|
1184
|
+
<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>
|
|
1185
|
+
<AgeGate client:load minAge={21} brand={t.name} />
|
|
1186
|
+
|
|
1187
|
+
<p class="form-help" style="margin:22px 0 8px">VariantSelector — PDP option picker (radiogroup + live price/SKU/stock region)</p>
|
|
1188
|
+
<VariantSelector
|
|
1189
|
+
client:load
|
|
1190
|
+
variants={sampleFromProduct.variants}
|
|
1191
|
+
options={sampleFromProduct.options}
|
|
1192
|
+
currency={sampleFromProduct.price_range.currency}
|
|
1193
|
+
locale="en-US"
|
|
1194
|
+
/>
|
|
1195
|
+
|
|
1196
|
+
<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>
|
|
1197
|
+
<QuickView client:idle locale="en-US" />
|
|
1198
|
+
|
|
1199
|
+
<p class="form-help" style="margin:22px 0 8px">SearchTypeahead — header search combobox (debounced, ARIA listbox)</p>
|
|
1200
|
+
<div style="max-width:22rem">
|
|
1201
|
+
<SearchTypeahead client:idle />
|
|
1202
|
+
</div>
|
|
1203
|
+
|
|
1204
|
+
<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>
|
|
1205
|
+
<div style="max-width:20rem">
|
|
1206
|
+
<MobileNav client:idle nav={sampleMobileNav} announcement="Free US shipping on orders over $100" />
|
|
1207
|
+
</div>
|
|
1208
|
+
|
|
1209
|
+
<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>
|
|
1210
|
+
<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)))});`} />
|
|
1211
|
+
<RecentlyViewed client:idle locale="en-US" />
|
|
1212
|
+
|
|
1213
|
+
<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>
|
|
1214
|
+
<SavedList client:only="preact" locale="en-US" />
|
|
1215
|
+
</section>
|
|
1216
|
+
|
|
1217
|
+
{/* ── PLP controls (components/plp/) ─────────────────────────────────
|
|
1218
|
+
URL-addressable faceted search chrome — every link/form carries the
|
|
1219
|
+
query as a real href/param, so filtering needs no JS. */}
|
|
1220
|
+
<section class="sg-block">
|
|
1221
|
+
<p class="sg-h">PLP controls — <code>components/plp/</code></p>
|
|
1222
|
+
<p class="lede" style="font-size:.95rem;color:var(--color-muted)">Faceted search chrome for a collection page. URL-addressable: every control is a real link or GET form carrying the query, never a JS-only filter.</p>
|
|
1223
|
+
|
|
1224
|
+
<div class="grid gap-8 lg:grid-cols-[240px_1fr]" style="margin-top:18px">
|
|
1225
|
+
<FacetSidebar facets={sampleFacets} query={samplePlpQuery} basePath="#" />
|
|
1226
|
+
<div>
|
|
1227
|
+
<div class="sg-row" style="justify-content:space-between;margin-bottom:14px">
|
|
1228
|
+
<p class="form-help" style="margin:0">SortSelect</p>
|
|
1229
|
+
<SortSelect query={samplePlpQuery} basePath="#" />
|
|
1230
|
+
</div>
|
|
1231
|
+
<p class="form-help" style="margin:0 0 8px">ActiveFilters</p>
|
|
1232
|
+
<ActiveFilters query={samplePlpQuery} basePath="#" />
|
|
1233
|
+
<p class="form-help" style="margin:22px 0 8px">Pagination</p>
|
|
1234
|
+
<Pagination query={samplePlpQuery} basePath="#" page={2} pageSize={12} total={58} />
|
|
1235
|
+
</div>
|
|
1236
|
+
</div>
|
|
1237
|
+
</section>
|
|
1238
|
+
|
|
1239
|
+
{/* ── Home hero (components/home/Hero.astro) ─────────────────────────
|
|
1240
|
+
The signature homepage moment — bold (default) vs. light, set by
|
|
1241
|
+
the tenant's brand.heroVariant (not a prop; the guide fakes locals
|
|
1242
|
+
to preview both). */}
|
|
1243
|
+
<section class="sg-block">
|
|
1244
|
+
<p class="sg-h">Home hero — <code>components/home/Hero.astro</code></p>
|
|
1245
|
+
<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>
|
|
1246
|
+
|
|
1247
|
+
<p class="form-help" style="margin:18px 0 8px">Bold (default)</p>
|
|
1248
|
+
{setHeroVariant("bold")}
|
|
1249
|
+
<Hero block={sampleHeroBlock} featureProduct={sampleHeroFeatureProduct} />
|
|
1250
|
+
|
|
1251
|
+
<p class="form-help" style="margin:22px 0 8px">Light</p>
|
|
1252
|
+
{setHeroVariant("light")}
|
|
1253
|
+
<Hero block={sampleHeroBlock} featureProduct={sampleHeroFeatureProduct} />
|
|
1254
|
+
</section>
|
|
1255
|
+
|
|
1256
|
+
{/* ── Marketing blocks (components/marketing/) ────────────────────────
|
|
1257
|
+
The block library a marketing-siteType tenant composes pages from —
|
|
1258
|
+
token-driven, distinct from the ported mkt.css composition layer
|
|
1259
|
+
above. One of every block type, dispatched through the real
|
|
1260
|
+
MarketingBlocks renderer (Integrations nests CodeSample). */}
|
|
1261
|
+
<section class="sg-block">
|
|
1262
|
+
<p class="sg-h">Marketing blocks — <code>components/marketing/</code></p>
|
|
1263
|
+
<p class="lede" style="font-size:.95rem;color:var(--color-muted)">The block-based marketing page renderer (siteType:"marketing" tenants). One instance of every block type, including the nested CodeSample inside Integrations.</p>
|
|
1264
|
+
<MarketingBlocks blocks={sampleMarketingBlocks} basePath="" />
|
|
1265
|
+
</section>
|
|
1266
|
+
</div>
|
|
1267
|
+
</main>
|
|
1268
|
+
|
|
1269
|
+
<footer class="site">
|
|
1270
|
+
<div class="container">
|
|
1271
|
+
<div class="foot-grid">
|
|
1272
|
+
<div><b>{t.name}</b><p class="copyright">Footer chrome sample · theme preview only.</p></div>
|
|
1273
|
+
<div><h3>Platform</h3><ul><li><a href="#">Identity Verification</a></li><li><a href="#">Age Assurance</a></li></ul></div>
|
|
1274
|
+
<div><h3>Industries</h3><ul><li><a href="#">Alcohol & Tobacco</a></li><li><a href="#">Marketplaces</a></li></ul></div>
|
|
1275
|
+
<div><h3>Company</h3><ul><li><a href="#">Pricing</a></li><li><a href="#">Contact Sales</a></li></ul></div>
|
|
1276
|
+
</div>
|
|
1277
|
+
</div>
|
|
1278
|
+
</footer>
|
|
1279
|
+
</body>
|
|
1280
|
+
</html>
|