@tokenoftrust/storefront-runner 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +40 -0
  3. package/apps/storefront/.dev.vars.example +41 -0
  4. package/apps/storefront/.env.example +24 -0
  5. package/apps/storefront/astro.config.mjs +245 -0
  6. package/apps/storefront/env.d.ts +155 -0
  7. package/apps/storefront/lib/ensure-preview-reconcile-secret.sh +31 -0
  8. package/apps/storefront/lib/resolve-cf-token.sh +48 -0
  9. package/apps/storefront/lib/resolve-tot-credentials.sh +69 -0
  10. package/apps/storefront/migrations/0000_baseline.sql +54 -0
  11. package/apps/storefront/migrations/0001_products_fts.sql +9 -0
  12. package/apps/storefront/migrations/README.md +54 -0
  13. package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
  14. package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
  15. package/apps/storefront/migrations/meta/_journal.json +20 -0
  16. package/apps/storefront/package.json +41 -0
  17. package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
  18. package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
  19. package/apps/storefront/public/brand/northwind-og.svg +13 -0
  20. package/apps/storefront/public/favicon.svg +5 -0
  21. package/apps/storefront/public/js/dashboard-team.js +147 -0
  22. package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
  23. package/apps/storefront/src/components/Badge.astro +34 -0
  24. package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
  25. package/apps/storefront/src/components/CollectionCard.astro +56 -0
  26. package/apps/storefront/src/components/EmptyState.astro +35 -0
  27. package/apps/storefront/src/components/Img.astro +68 -0
  28. package/apps/storefront/src/components/ProductCard.astro +228 -0
  29. package/apps/storefront/src/components/Section.astro +39 -0
  30. package/apps/storefront/src/components/Seo.astro +70 -0
  31. package/apps/storefront/src/components/WidgetFrame.astro +34 -0
  32. package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
  33. package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
  34. package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
  35. package/apps/storefront/src/components/chrome/NavDropdown.astro +116 -0
  36. package/apps/storefront/src/components/chrome/SiteFooter.astro +181 -0
  37. package/apps/storefront/src/components/chrome/SiteHeader.astro +181 -0
  38. package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
  39. package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
  40. package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
  41. package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
  42. package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
  43. package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
  44. package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
  45. package/apps/storefront/src/components/home/Hero.astro +164 -0
  46. package/apps/storefront/src/components/islands/AgeGate.tsx +105 -0
  47. package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
  48. package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
  49. package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
  50. package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
  51. package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
  52. package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
  53. package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
  54. package/apps/storefront/src/components/islands/VariantSelector.tsx +378 -0
  55. package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
  56. package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
  57. package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
  58. package/apps/storefront/src/components/marketing/Faq.astro +37 -0
  59. package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
  60. package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
  61. package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
  62. package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
  63. package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
  64. package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
  65. package/apps/storefront/src/components/marketing/Steps.astro +36 -0
  66. package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
  67. package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
  68. package/apps/storefront/src/components/nav/Footer.astro +148 -0
  69. package/apps/storefront/src/components/nav/Header.astro +190 -0
  70. package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
  71. package/apps/storefront/src/components/plp/FacetSidebar.astro +146 -0
  72. package/apps/storefront/src/components/plp/Pagination.astro +99 -0
  73. package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
  74. package/apps/storefront/src/config/devTenants.ts +16 -0
  75. package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
  76. package/apps/storefront/src/config/resolver.ts +46 -0
  77. package/apps/storefront/src/config/tenants.ts +193 -0
  78. package/apps/storefront/src/layouts/Layout.astro +303 -0
  79. package/apps/storefront/src/lib/analytics/index.ts +47 -0
  80. package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
  81. package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
  82. package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
  83. package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
  84. package/apps/storefront/src/lib/auth/loginGate.ts +107 -0
  85. package/apps/storefront/src/lib/auth/route.ts +146 -0
  86. package/apps/storefront/src/lib/auth/session.ts +203 -0
  87. package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
  88. package/apps/storefront/src/lib/basePath.ts +41 -0
  89. package/apps/storefront/src/lib/cfImage.ts +55 -0
  90. package/apps/storefront/src/lib/chrome/model.ts +65 -0
  91. package/apps/storefront/src/lib/colors.ts +97 -0
  92. package/apps/storefront/src/lib/content-edit/client.ts +230 -0
  93. package/apps/storefront/src/lib/content-edit/index.ts +14 -0
  94. package/apps/storefront/src/lib/content-edit/route.ts +25 -0
  95. package/apps/storefront/src/lib/content-edit/types.ts +19 -0
  96. package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
  97. package/apps/storefront/src/lib/d1/catalog.ts +289 -0
  98. package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
  99. package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
  100. package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
  101. package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
  102. package/apps/storefront/src/lib/demoGate.ts +113 -0
  103. package/apps/storefront/src/lib/dev/ai-widget-client.js +127 -0
  104. package/apps/storefront/src/lib/dev/cliSignInCode.ts +132 -0
  105. package/apps/storefront/src/lib/diag/redact.ts +20 -0
  106. package/apps/storefront/src/lib/edgeCache.ts +128 -0
  107. package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
  108. package/apps/storefront/src/lib/env.ts +60 -0
  109. package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
  110. package/apps/storefront/src/lib/format.ts +29 -0
  111. package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
  112. package/apps/storefront/src/lib/imageAttrs.ts +55 -0
  113. package/apps/storefront/src/lib/jsonld.ts +136 -0
  114. package/apps/storefront/src/lib/maintenance.ts +71 -0
  115. package/apps/storefront/src/lib/memoryKv.ts +32 -0
  116. package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
  117. package/apps/storefront/src/lib/placeholder.ts +99 -0
  118. package/apps/storefront/src/lib/previewReload.ts +125 -0
  119. package/apps/storefront/src/lib/rawMarketingHtml.ts +133 -0
  120. package/apps/storefront/src/lib/search/index.ts +74 -0
  121. package/apps/storefront/src/lib/sections.ts +129 -0
  122. package/apps/storefront/src/lib/securityHeaders.ts +31 -0
  123. package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
  124. package/apps/storefront/src/lib/storyblok/provider.ts +524 -0
  125. package/apps/storefront/src/lib/tenantRouting.ts +91 -0
  126. package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
  127. package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
  128. package/apps/storefront/src/lib/tot/query.ts +187 -0
  129. package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
  130. package/apps/storefront/src/lib/url.ts +119 -0
  131. package/apps/storefront/src/lib/wishlist.ts +63 -0
  132. package/apps/storefront/src/middleware/index.ts +413 -0
  133. package/apps/storefront/src/pages/404.astro +61 -0
  134. package/apps/storefront/src/pages/[...slug].astro +90 -0
  135. package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
  136. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
  137. package/apps/storefront/src/pages/api/auth/send.ts +85 -0
  138. package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
  139. package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
  140. package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +64 -0
  141. package/apps/storefront/src/pages/api/csp-report.ts +25 -0
  142. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
  143. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
  144. package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +44 -0
  145. package/apps/storefront/src/pages/api/newsletter.ts +104 -0
  146. package/apps/storefront/src/pages/api/request-access.ts +172 -0
  147. package/apps/storefront/src/pages/auth/login.astro +149 -0
  148. package/apps/storefront/src/pages/auth/magic.astro +105 -0
  149. package/apps/storefront/src/pages/capabilities.astro +292 -0
  150. package/apps/storefront/src/pages/collections/[handle].astro +154 -0
  151. package/apps/storefront/src/pages/collections/index.astro +59 -0
  152. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
  153. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
  154. package/apps/storefront/src/pages/dashboard/index.astro +160 -0
  155. package/apps/storefront/src/pages/dev.astro +653 -0
  156. package/apps/storefront/src/pages/index.astro +290 -0
  157. package/apps/storefront/src/pages/llms.txt.ts +92 -0
  158. package/apps/storefront/src/pages/products/[handle].astro +443 -0
  159. package/apps/storefront/src/pages/robots.txt.ts +24 -0
  160. package/apps/storefront/src/pages/saved.astro +35 -0
  161. package/apps/storefront/src/pages/search.astro +150 -0
  162. package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
  163. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +646 -0
  164. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +85 -0
  165. package/apps/storefront/src/pages/style-guide/index.astro +94 -0
  166. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
  167. package/apps/storefront/src/styles/global.css +277 -0
  168. package/apps/storefront/src/themes/reference.ts +61 -0
  169. package/apps/storefront/src/themes/schema.ts +111 -0
  170. package/apps/storefront/src/themes/tenants/index.ts +51 -0
  171. package/apps/storefront/tsconfig.json +16 -0
  172. package/apps/storefront/vitest.config.ts +22 -0
  173. package/apps/storefront/wrangler.toml +175 -0
  174. package/package.json +23 -0
  175. package/packages/public-runtime/LICENSE +58 -0
  176. package/packages/public-runtime/package.json +22 -0
  177. package/packages/public-runtime/src/binary-path.ts +68 -0
  178. package/packages/public-runtime/src/capabilities.ts +89 -0
  179. package/packages/public-runtime/src/catalog-api.ts +81 -0
  180. package/packages/public-runtime/src/catalog-d1.ts +192 -0
  181. package/packages/public-runtime/src/csp.ts +109 -0
  182. package/packages/public-runtime/src/customization-preview.ts +197 -0
  183. package/packages/public-runtime/src/customization-reconcile.ts +63 -0
  184. package/packages/public-runtime/src/customization-runtime.ts +157 -0
  185. package/packages/public-runtime/src/customization-scripts.ts +64 -0
  186. package/packages/public-runtime/src/customization-versioning.ts +95 -0
  187. package/packages/public-runtime/src/foxy.ts +184 -0
  188. package/packages/public-runtime/src/index.ts +30 -0
  189. package/packages/public-runtime/src/product.ts +159 -0
  190. package/packages/public-runtime/src/search.ts +45 -0
  191. package/packages/public-runtime/src/tenant-assets.ts +444 -0
  192. package/packages/public-runtime/src/tenant.ts +243 -0
  193. package/packages/public-runtime/tsconfig.json +8 -0
  194. package/packages/public-runtime/vitest.config.ts +8 -0
  195. package/pnpm-workspace.yaml +7 -0
  196. package/scripts/build/copy-tenant-assets.mjs +69 -0
  197. package/scripts/tot-dev.mjs +439 -0
  198. package/tenants/bigdvapor/content/blog.json +52 -0
  199. package/tenants/bigdvapor/content/chrome.html +149 -0
  200. package/tenants/bigdvapor/content/chrome.json +47 -0
  201. package/tenants/bigdvapor/content/home.html +463 -0
  202. package/tenants/bigdvapor/content/home.json +73 -0
  203. package/tenants/bigdvapor/content/pages/about.json +5 -0
  204. package/tenants/bigdvapor/content/pages/privacy.json +6 -0
  205. package/tenants/bigdvapor/content/pages/shipping-returns.json +6 -0
  206. package/tenants/bigdvapor/content/pages-html/blogs/news.html +86 -0
  207. package/tenants/bigdvapor/content/pages-html/pages/about-us.html +106 -0
  208. package/tenants/bigdvapor/content/pages-html/pages/contact-us.html +61 -0
  209. package/tenants/bigdvapor/content/pages-html/pages/privacy-policy.html +72 -0
  210. package/tenants/bigdvapor/content/pages-html/pages/shipping-returns.html +172 -0
  211. package/tenants/bigdvapor/content/themes/bigd-navy.json +109 -0
  212. package/tenants/bigdvapor/public/img/brands/adjust-vape-disposable.png +0 -0
  213. package/tenants/bigdvapor/public/img/brands/again-vape-disposable.png +0 -0
  214. package/tenants/bigdvapor/public/img/brands/al-fakher-vape-disposable.png +0 -0
  215. package/tenants/bigdvapor/public/img/brands/arro-vape-disposable.png +0 -0
  216. package/tenants/bigdvapor/public/img/brands/cookies-vape-disposable.png +0 -0
  217. package/tenants/bigdvapor/public/img/brands/core-vape-disposable.png +0 -0
  218. package/tenants/bigdvapor/public/img/brands/cyclone-vape-disposable.png +0 -0
  219. package/tenants/bigdvapor/public/img/brands/dinner-lady-vape-disposable.png +0 -0
  220. package/tenants/bigdvapor/public/img/brands/extre-bar-vape-disposable.png +0 -0
  221. package/tenants/bigdvapor/public/img/brands/fifty-bar-vape-disposable.jpg +0 -0
  222. package/tenants/bigdvapor/public/img/brands/firerose-vape-disposable.jpg +0 -0
  223. package/tenants/bigdvapor/public/img/brands/flavor-beast-vape-disposable.jpg +0 -0
  224. package/tenants/bigdvapor/public/img/brands/foger-vape-disposable.png +0 -0
  225. package/tenants/bigdvapor/public/img/brands/kk-energy-vape-disposable.png +0 -0
  226. package/tenants/bigdvapor/public/img/brands/kumi-vape-disposable.png +0 -0
  227. package/tenants/bigdvapor/public/img/brands/lost-vape-dispoable.png +0 -0
  228. package/tenants/bigdvapor/public/img/brands/melo-labs-vape-disposable.png +0 -0
  229. package/tenants/bigdvapor/public/img/brands/mixo-vape-disposable.webp +0 -0
  230. package/tenants/bigdvapor/public/img/brands/one-tank-vape-disposable.jpg +0 -0
  231. package/tenants/bigdvapor/public/img/brands/oxbar-vape-disposable.webp +0 -0
  232. package/tenants/bigdvapor/public/img/brands/pillow-talk-vape-disposable.jpg +0 -0
  233. package/tenants/bigdvapor/public/img/brands/pod-king-vape-disposable.jpg +0 -0
  234. package/tenants/bigdvapor/public/img/brands/raz-vape-disposable.png +0 -0
  235. package/tenants/bigdvapor/public/img/hero-texas.jpg +0 -0
  236. package/tenants/bigdvapor/public/img/og.jpg +0 -0
  237. package/tenants/bigdvapor/public/img/pages/AdobeStock_202106312_web.jpg +0 -0
  238. package/tenants/bigdvapor/public/img/pages/CREW_web.jpg +0 -0
  239. package/tenants/bigdvapor/public/img/pages/Hero-Image.jpg +0 -0
  240. package/tenants/bigdvapor/public/img/tiles/adjust.png +0 -0
  241. package/tenants/bigdvapor/public/img/tiles/again.png +0 -0
  242. package/tenants/bigdvapor/public/img/tiles/al-fakher.png +0 -0
  243. package/tenants/bigdvapor/public/img/tiles/arro.png +0 -0
  244. package/tenants/bigdvapor/public/img/tiles/cookies.png +0 -0
  245. package/tenants/bigdvapor/public/img/tiles/core.png +0 -0
  246. package/tenants/bigdvapor/public/img/tiles/cyclone.png +0 -0
  247. package/tenants/bigdvapor/public/img/tiles/disposables.svg +23 -0
  248. package/tenants/bigdvapor/public/logo-wordmark.png +0 -0
  249. package/tenants/bigdvapor/public/pages/home.css +118 -0
  250. package/tenants/bigdvapor/public/pages/mkt.css +185 -0
  251. package/tenants/bigdvapor/public/pages/page.css +148 -0
  252. package/tenants/bigdvapor/public/themes/bigd-navy.css +73 -0
  253. package/tenants/bigdvapor/theme.json +12 -0
  254. package/tenants/giantvapes/content/chrome.html +152 -0
  255. package/tenants/giantvapes/content/chrome.json +94 -0
  256. package/tenants/giantvapes/content/home.html +194 -0
  257. package/tenants/giantvapes/content/home.json +50 -0
  258. package/tenants/giantvapes/content/pages/about.json +10 -0
  259. package/tenants/giantvapes/content/pages/privacy.json +6 -0
  260. package/tenants/giantvapes/content/pages/shipping-returns.json +6 -0
  261. package/tenants/giantvapes/content/pages-html/blogs/news.html +68 -0
  262. package/tenants/giantvapes/content/pages-html/pages/about-us.html +95 -0
  263. package/tenants/giantvapes/content/pages-html/pages/contact-us.html +68 -0
  264. package/tenants/giantvapes/content/pages-html/pages/privacy-policy.html +65 -0
  265. package/tenants/giantvapes/content/pages-html/pages/shipping-returns.html +77 -0
  266. package/tenants/giantvapes/content/themes/giant-navy.json +73 -0
  267. package/tenants/giantvapes/public/img/hero-suicide-bunny.jpg +0 -0
  268. package/tenants/giantvapes/public/img/hero.webp +0 -0
  269. package/tenants/giantvapes/public/img/og.jpg +0 -0
  270. package/tenants/giantvapes/public/logo-wordmark.png +0 -0
  271. package/tenants/giantvapes/public/pages/home.css +120 -0
  272. package/tenants/giantvapes/public/pages/mkt.css +185 -0
  273. package/tenants/giantvapes/public/pages/page.css +155 -0
  274. package/tenants/giantvapes/public/themes/giant-navy.css +76 -0
  275. package/tenants/giantvapes/theme.json +39 -0
  276. package/tenants/home/content/chrome.json +11 -0
  277. package/tenants/home/content/home.html +304 -0
  278. package/tenants/home/content/home.json +13 -0
  279. package/tenants/home/public/js/storefront.js +210 -0
  280. package/tenants/home/public/logo-wordmark.png +0 -0
  281. package/tenants/home/public/pages/storefront.css +212 -0
  282. package/tenants/home/theme.json +6 -0
  283. package/tenants/tokenoftrust/.tot/config.json +9 -0
  284. package/tenants/tokenoftrust/content/chrome.json +55 -0
  285. package/tenants/tokenoftrust/content/home.html +723 -0
  286. package/tenants/tokenoftrust/content/home.json +233 -0
  287. package/tenants/tokenoftrust/content/pages-html/careers.html +231 -0
  288. package/tenants/tokenoftrust/content/pages-html/company.html +275 -0
  289. package/tenants/tokenoftrust/content/pages-html/contact/contact-sales.html +188 -0
  290. package/tenants/tokenoftrust/content/pages-html/contact/startup-apply.html +206 -0
  291. package/tenants/tokenoftrust/content/pages-html/contact.html +178 -0
  292. package/tenants/tokenoftrust/content/pages-html/industries/adult-content-age-verification.html +286 -0
  293. package/tenants/tokenoftrust/content/pages-html/industries/cannabis.html +277 -0
  294. package/tenants/tokenoftrust/content/pages-html/pact-act-pricing.html +289 -0
  295. package/tenants/tokenoftrust/content/pages-html/pricing.html +483 -0
  296. package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-estimation.html +278 -0
  297. package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-gate.html +279 -0
  298. package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-verification.html +265 -0
  299. package/tenants/tokenoftrust/content/pages-html/product/age-assurance.html +173 -0
  300. package/tenants/tokenoftrust/content/pages-html/product/compliance/aml.html +214 -0
  301. package/tenants/tokenoftrust/content/pages-html/product/compliance/pact-act.html +226 -0
  302. package/tenants/tokenoftrust/content/pages-html/product/fraud-prevention.html +254 -0
  303. package/tenants/tokenoftrust/content/pages-html/product/social-profile-verification.html +212 -0
  304. package/tenants/tokenoftrust/content/pages-html/product/tax/excise-tax.html +213 -0
  305. package/tenants/tokenoftrust/content/pages-html/product/verification/aml.html +259 -0
  306. package/tenants/tokenoftrust/content/pages-html/product/verification/biometric-selfie.html +305 -0
  307. package/tenants/tokenoftrust/content/pages-html/product/verification/coverage.html +254 -0
  308. package/tenants/tokenoftrust/content/pages-html/product/verification/document-verification.html +306 -0
  309. package/tenants/tokenoftrust/content/pages-html/product/verification/electronic-identity-verification.html +304 -0
  310. package/tenants/tokenoftrust/content/pages-html/product/verification/government-id-verification.html +308 -0
  311. package/tenants/tokenoftrust/content/pages-html/product/verification.html +183 -0
  312. package/tenants/tokenoftrust/content/pages-html/product.html +228 -0
  313. package/tenants/tokenoftrust/content/pages-html/resources/integrations/bigcommerce-integration.html +220 -0
  314. package/tenants/tokenoftrust/content/pages-html/resources/integrations/shopify-integration.html +221 -0
  315. package/tenants/tokenoftrust/content/pages-html/resources/integrations/wordpress.html +220 -0
  316. package/tenants/tokenoftrust/content/pages-html/resources/integrations.html +172 -0
  317. package/tenants/tokenoftrust/content/pages-html/resources/refer-a-friend-program.html +317 -0
  318. package/tenants/tokenoftrust/content/pages-html/resources.html +177 -0
  319. package/tenants/tokenoftrust/content/pages-html/reviews.html +273 -0
  320. package/tenants/tokenoftrust/content/pages-html/solutions.html +292 -0
  321. package/tenants/tokenoftrust/content/pages-html/vlp-alcohol.html +289 -0
  322. package/tenants/tokenoftrust/content/pages-html/vlp-cigars-and-tobacco.html +286 -0
  323. package/tenants/tokenoftrust/content/pages-html/vlp-firearm-accessories.html +267 -0
  324. package/tenants/tokenoftrust/content/pages-html/vlp-peptides.html +273 -0
  325. package/tenants/tokenoftrust/content/themes/tot-navy.json +88 -0
  326. package/tenants/tokenoftrust/public/favicon.png +0 -0
  327. package/tenants/tokenoftrust/public/logo-icon.png +0 -0
  328. package/tenants/tokenoftrust/public/logo-wordmark.png +0 -0
  329. package/tenants/tokenoftrust/public/pages/company.css +108 -0
  330. package/tenants/tokenoftrust/public/pages/home.css +457 -0
  331. package/tenants/tokenoftrust/public/pages/mkt.css +482 -0
  332. package/tenants/tokenoftrust/public/pages/pricing.css +214 -0
  333. package/tenants/tokenoftrust/public/pages/solutions.css +66 -0
  334. package/tenants/tokenoftrust/public/themes/tot-navy-1.0.0.css +136 -0
  335. package/tenants/tokenoftrust/public/themes/tot-navy.css +358 -0
  336. package/tenants/tokenoftrust/theme.json +31 -0
  337. package/tsconfig.base.json +20 -0
@@ -0,0 +1,20 @@
1
+ ---
2
+ /**
3
+ * <CodeSample> — a display-only fenced code block. SECURITY: the code is
4
+ * rendered as ESCAPED TEXT inside <pre><code> (never set:html), so it can't
5
+ * inject markup or scripts. Samples must be public/server-side framed — never
6
+ * embed secret keys or tokens (see the epic's D7 secret-scan guardrail).
7
+ */
8
+ import type { CodeSample } from "@/lib/storyblok/content-model";
9
+ interface Props {
10
+ sample: CodeSample;
11
+ }
12
+ const { sample } = Astro.props;
13
+ ---
14
+ <figure class="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-text)]">
15
+ <figcaption class="flex items-center justify-between border-b border-white/10 px-4 py-2 font-mono text-[0.68rem] uppercase tracking-[0.12em] text-white/60">
16
+ <span>{sample.caption ?? "Example"}</span>
17
+ {sample.language && <span>{sample.language}</span>}
18
+ </figcaption>
19
+ <pre class="overflow-x-auto px-4 py-4 text-[0.82rem] leading-relaxed"><code class="font-mono text-[#e6edf3]">{sample.code}</code></pre>
20
+ </figure>
@@ -0,0 +1,24 @@
1
+ ---
2
+ /**
3
+ * <CtaBand> — the final conversion band: a headline, optional subhead, and a
4
+ * centered CTA row. Sits on the surface tone to close the page.
5
+ */
6
+ import type { CtaBandBlock } from "@/lib/storyblok/content-model";
7
+ import MarketingCtas from "@/components/marketing/MarketingCtas.astro";
8
+ interface Props {
9
+ block: CtaBandBlock;
10
+ basePath: string;
11
+ }
12
+ const { block, basePath } = Astro.props;
13
+ ---
14
+ <section class="bg-[var(--color-surface)] py-[clamp(3.5rem,8vw,6.5rem)]">
15
+ <div class="container-prose">
16
+ <div class="mx-auto max-w-2xl text-center" data-reveal>
17
+ <h2 class="display-lg">{block.title}</h2>
18
+ {block.subhead && (
19
+ <p class="mx-auto mt-4 max-w-[48ch] text-[var(--color-muted)]">{block.subhead}</p>
20
+ )}
21
+ <MarketingCtas ctas={block.ctas} basePath={basePath} class="mt-8 justify-center" />
22
+ </div>
23
+ </div>
24
+ </section>
@@ -0,0 +1,37 @@
1
+ ---
2
+ /**
3
+ * <Faq> — a no-JS accordion of question/answer pairs using native
4
+ * <details>/<summary>. Emits FAQPage JSON-LD so answers are eligible for rich
5
+ * results. Answers are plain text (auto-escaped).
6
+ */
7
+ import Section from "@/components/Section.astro";
8
+ import type { FaqBlock } from "@/lib/storyblok/content-model";
9
+ interface Props {
10
+ block: FaqBlock;
11
+ }
12
+ const { block } = Astro.props;
13
+
14
+ const faqLd = {
15
+ "@context": "https://schema.org",
16
+ "@type": "FAQPage",
17
+ mainEntity: block.items.map((it) => ({
18
+ "@type": "Question",
19
+ name: it.q,
20
+ acceptedAnswer: { "@type": "Answer", text: it.a },
21
+ })),
22
+ };
23
+ ---
24
+ <Section eyebrow={block.eyebrow} title={block.title} tone="surface">
25
+ <div class="mx-auto max-w-[46rem] divide-y divide-[var(--color-border)]" data-reveal>
26
+ {block.items.map((it) => (
27
+ <details class="group py-4">
28
+ <summary class="flex cursor-pointer list-none items-center justify-between gap-4 font-display text-base font-semibold text-[var(--color-text)]">
29
+ {it.q}
30
+ <span aria-hidden="true" class="shrink-0 text-[var(--color-accent)] transition-transform group-open:rotate-45">+</span>
31
+ </summary>
32
+ <p class="mt-3 text-[var(--color-muted)]">{it.a}</p>
33
+ </details>
34
+ ))}
35
+ </div>
36
+ </Section>
37
+ <script type="application/ld+json" set:html={JSON.stringify(faqLd)} is:inline />
@@ -0,0 +1,63 @@
1
+ ---
2
+ /**
3
+ * <Integrations> — the "fits the store you already run" section: a strip of
4
+ * platform names/links, optional implementation points, and an optional safe
5
+ * code sample. Platform links open externally when they carry an http href.
6
+ */
7
+ import Section from "@/components/Section.astro";
8
+ import CodeSample from "@/components/marketing/CodeSample.astro";
9
+ import type { IntegrationsBlock } from "@/lib/storyblok/content-model";
10
+ import { withBase } from "@/lib/basePath";
11
+
12
+ interface Props {
13
+ block: IntegrationsBlock;
14
+ basePath: string;
15
+ }
16
+ const { block, basePath } = Astro.props;
17
+ const isExternal = (href: string) => /^https?:\/\//i.test(href);
18
+ ---
19
+ <Section eyebrow={block.eyebrow} title={block.title} tone="surface">
20
+ {block.subhead && (
21
+ <p class="-mt-4 mb-9 max-w-[60ch] text-[var(--color-muted)]" data-reveal>
22
+ {block.subhead}
23
+ </p>
24
+ )}
25
+ <div class="grid items-start gap-10 lg:grid-cols-2">
26
+ <div data-reveal>
27
+ <ul class="flex flex-wrap gap-2.5">
28
+ {block.platforms.map((p) =>
29
+ p.href ? (
30
+ <li>
31
+ <a
32
+ href={isExternal(p.href) ? p.href : withBase(basePath, p.href)}
33
+ {...(isExternal(p.href) ? { target: "_blank", rel: "noopener noreferrer" } : {})}
34
+ class="inline-flex rounded-full border border-[var(--color-border)] bg-[var(--color-bg)] px-4 py-2 font-display text-sm font-medium text-[var(--color-text)] transition-colors hover:border-[var(--color-primary)]"
35
+ >
36
+ {p.label}
37
+ </a>
38
+ </li>
39
+ ) : (
40
+ <li class="inline-flex rounded-full border border-[var(--color-border)] bg-[var(--color-bg)] px-4 py-2 font-display text-sm font-medium text-[var(--color-text)]">
41
+ {p.label}
42
+ </li>
43
+ ),
44
+ )}
45
+ </ul>
46
+ {block.points?.length && (
47
+ <ul class="mt-7 grid gap-3 sm:grid-cols-2">
48
+ {block.points.map((pt) => (
49
+ <li class="flex items-start gap-2 text-sm text-[var(--color-muted)]">
50
+ <span aria-hidden="true" class="mt-0.5 text-[var(--color-accent)]">✓</span>
51
+ {pt}
52
+ </li>
53
+ ))}
54
+ </ul>
55
+ )}
56
+ </div>
57
+ {block.code && (
58
+ <div data-reveal>
59
+ <CodeSample sample={block.code} />
60
+ </div>
61
+ )}
62
+ </div>
63
+ </Section>
@@ -0,0 +1,53 @@
1
+ ---
2
+ /**
3
+ * <MarketingBlocks> — the marketing page renderer. Dispatches a list of
4
+ * MarketingBlock view-models to their block components. Tenant-agnostic: every
5
+ * block is styled through theme tokens and reads its copy from tenant content.
6
+ * Reused by the homepage and (future) marketing content pages.
7
+ */
8
+ import type { MarketingBlock } from "@/lib/storyblok/content-model";
9
+ import MarketingHero from "@/components/marketing/MarketingHero.astro";
10
+ import TrustBar from "@/components/marketing/TrustBar.astro";
11
+ import SplitCompare from "@/components/marketing/SplitCompare.astro";
12
+ import Steps from "@/components/marketing/Steps.astro";
13
+ import CardGrid from "@/components/marketing/CardGrid.astro";
14
+ import Integrations from "@/components/marketing/Integrations.astro";
15
+ import ProofStrip from "@/components/marketing/ProofStrip.astro";
16
+ import Testimonials from "@/components/marketing/Testimonials.astro";
17
+ import Faq from "@/components/marketing/Faq.astro";
18
+ import CtaBand from "@/components/marketing/CtaBand.astro";
19
+
20
+ interface Props {
21
+ blocks: MarketingBlock[];
22
+ basePath: string;
23
+ }
24
+ const { blocks, basePath } = Astro.props;
25
+ ---
26
+ {
27
+ blocks.map((block) => {
28
+ switch (block.component) {
29
+ case "marketing_hero":
30
+ return <MarketingHero block={block} basePath={basePath} />;
31
+ case "trust_bar":
32
+ return <TrustBar block={block} />;
33
+ case "split_compare":
34
+ return <SplitCompare block={block} />;
35
+ case "steps":
36
+ return <Steps block={block} />;
37
+ case "card_grid":
38
+ return <CardGrid block={block} basePath={basePath} />;
39
+ case "integrations":
40
+ return <Integrations block={block} basePath={basePath} />;
41
+ case "proof_strip":
42
+ return <ProofStrip block={block} />;
43
+ case "testimonials":
44
+ return <Testimonials block={block} />;
45
+ case "faq":
46
+ return <Faq block={block} />;
47
+ case "cta_band":
48
+ return <CtaBand block={block} basePath={basePath} />;
49
+ default:
50
+ return null;
51
+ }
52
+ })
53
+ }
@@ -0,0 +1,48 @@
1
+ ---
2
+ /**
3
+ * <MarketingCtas> — a row of call-to-action buttons for marketing blocks.
4
+ * Tenant-agnostic: styling is all theme tokens. External hrefs (http[s]) open in
5
+ * a new tab with rel="noopener"; same-origin root-absolute hrefs get the tenant
6
+ * base-path prefix so path-routed tenants stay in-tenant.
7
+ */
8
+ import type { MarketingCta } from "@/lib/storyblok/content-model";
9
+ import { withBase } from "@/lib/basePath";
10
+
11
+ interface Props {
12
+ ctas?: MarketingCta[];
13
+ basePath: string;
14
+ class?: string;
15
+ }
16
+
17
+ const { ctas = [], basePath, class: className } = Astro.props;
18
+
19
+ const styles: Record<NonNullable<MarketingCta["variant"]>, string> = {
20
+ primary:
21
+ "rounded-full bg-accent px-6 py-3 font-display text-sm font-semibold text-[var(--color-primary-contrast)] shadow-[var(--shadow-sm)] transition-transform hover:-translate-y-0.5",
22
+ secondary:
23
+ "rounded-full border border-[var(--color-border)] bg-[var(--color-surface)] px-6 py-3 font-display text-sm font-semibold text-[var(--color-text)] transition-colors hover:border-[var(--color-primary)]",
24
+ ghost:
25
+ "inline-flex items-center font-display text-sm font-semibold text-[var(--color-primary)] underline-offset-4 hover:underline",
26
+ };
27
+
28
+ const isExternal = (href: string) => /^https?:\/\//i.test(href);
29
+ ---
30
+
31
+ {
32
+ ctas.length > 0 && (
33
+ <div class:list={["flex flex-wrap items-center gap-3", className]}>
34
+ {ctas.map((cta) => {
35
+ const external = isExternal(cta.href);
36
+ return (
37
+ <a
38
+ href={external ? cta.href : withBase(basePath, cta.href)}
39
+ class={styles[cta.variant ?? "primary"]}
40
+ {...(external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
41
+ >
42
+ {cta.label}
43
+ </a>
44
+ );
45
+ })}
46
+ </div>
47
+ )
48
+ }
@@ -0,0 +1,72 @@
1
+ ---
2
+ /**
3
+ * <MarketingHero> — the signature top-of-page moment for a marketing tenant.
4
+ * Left: eyebrow + oversized headline + subhead/body + CTAs + trust chips.
5
+ * Right (optional): a "product moment" panel of demo/evidence log lines.
6
+ * All copy is plain text (auto-escaped); no raw HTML is rendered.
7
+ */
8
+ import type { MarketingHeroBlock } from "@/lib/storyblok/content-model";
9
+ import MarketingCtas from "@/components/marketing/MarketingCtas.astro";
10
+
11
+ interface Props {
12
+ block: MarketingHeroBlock;
13
+ basePath: string;
14
+ }
15
+ const { block, basePath } = Astro.props;
16
+ const hasPanel = !!block.panel?.lines?.length;
17
+ ---
18
+
19
+ <section class="bg-[var(--color-bg)] py-[clamp(3.5rem,8vw,7rem)]">
20
+ <div class="container-prose">
21
+ <div
22
+ class:list={[
23
+ "grid items-center gap-12",
24
+ hasPanel ? "lg:grid-cols-[1.05fr_0.95fr]" : "",
25
+ ]}
26
+ >
27
+ <div data-reveal>
28
+ {block.eyebrow && <p class="eyebrow eyebrow--tick mb-4">{block.eyebrow}</p>}
29
+ <h1 class="display-xl max-w-[18ch]">{block.headline}</h1>
30
+ {block.subhead && (
31
+ <p class="mt-5 max-w-[46ch] text-[1.15rem] font-medium text-[var(--color-text)]">
32
+ {block.subhead}
33
+ </p>
34
+ )}
35
+ {block.body && (
36
+ <p class="mt-4 max-w-[52ch] text-[var(--color-muted)]">{block.body}</p>
37
+ )}
38
+ <MarketingCtas ctas={block.ctas} basePath={basePath} class="mt-8" />
39
+ {block.trust?.length && (
40
+ <ul class="mt-8 flex flex-wrap gap-x-6 gap-y-2">
41
+ {block.trust.map((t) => (
42
+ <li class="flex items-center gap-2 text-sm text-[var(--color-muted)]">
43
+ <span aria-hidden="true" class="text-[var(--color-accent)]">✓</span>
44
+ {t}
45
+ </li>
46
+ ))}
47
+ </ul>
48
+ )}
49
+ </div>
50
+
51
+ {hasPanel && (
52
+ <div data-reveal>
53
+ <div class="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-surface)] shadow-[var(--shadow-lg)]">
54
+ {block.panel!.title && (
55
+ <div class="border-b border-[var(--color-border)] px-5 py-3 font-mono text-xs uppercase tracking-[0.12em] text-[var(--color-muted)]">
56
+ {block.panel!.title}
57
+ </div>
58
+ )}
59
+ <ul class="divide-y divide-[var(--color-border)]">
60
+ {block.panel!.lines.map((line) => (
61
+ <li class="flex items-start gap-3 px-5 py-3 font-mono text-[0.82rem] text-[var(--color-text)]">
62
+ <span aria-hidden="true" class="mt-0.5 text-[var(--color-accent)]">›</span>
63
+ <span>{line}</span>
64
+ </li>
65
+ ))}
66
+ </ul>
67
+ </div>
68
+ </div>
69
+ )}
70
+ </div>
71
+ </div>
72
+ </section>
@@ -0,0 +1,37 @@
1
+ ---
2
+ /**
3
+ * <ProofStrip> — a privacy/trust proof band (e.g. "We will never sell your
4
+ * data"). A bold statement + supporting copy + a grid of proof bullets. Painted
5
+ * in the primary color for a single strong moment. Plain text only.
6
+ */
7
+ import type { ProofStripBlock } from "@/lib/storyblok/content-model";
8
+ interface Props {
9
+ block: ProofStripBlock;
10
+ }
11
+ const { block } = Astro.props;
12
+ ---
13
+ <section class="bg-[var(--color-primary)] py-[clamp(3rem,7vw,6rem)] text-[var(--color-primary-contrast)]">
14
+ <div class="container-prose">
15
+ <div class="mx-auto max-w-3xl text-center" data-reveal>
16
+ {block.eyebrow && (
17
+ <p class="mb-3 font-mono text-xs uppercase tracking-[0.14em] opacity-70">
18
+ {block.eyebrow}
19
+ </p>
20
+ )}
21
+ <h2 class="display-lg">{block.title}</h2>
22
+ {block.body && <p class="mx-auto mt-5 max-w-[52ch] opacity-85">{block.body}</p>}
23
+ </div>
24
+ {block.items?.length && (
25
+ <ul
26
+ class="mx-auto mt-10 grid max-w-3xl gap-3 sm:grid-cols-2 lg:grid-cols-3"
27
+ data-reveal
28
+ >
29
+ {block.items.map((item) => (
30
+ <li class="flex items-center gap-2 rounded-[var(--radius-md)] bg-[color-mix(in_srgb,var(--color-primary-contrast)_12%,transparent)] px-4 py-2.5 text-sm">
31
+ <span aria-hidden="true">✓</span> {item}
32
+ </li>
33
+ ))}
34
+ </ul>
35
+ )}
36
+ </div>
37
+ </section>
@@ -0,0 +1,53 @@
1
+ ---
2
+ /**
3
+ * <SplitCompare> — a before/after two-column comparison (e.g. "today's
4
+ * patchwork" vs "one platform"). The `before` column reads as the problem
5
+ * (muted, ⚠) and `after` as the solution (accent, ✓).
6
+ */
7
+ import Section from "@/components/Section.astro";
8
+ import type { SplitCompareBlock } from "@/lib/storyblok/content-model";
9
+ interface Props {
10
+ block: SplitCompareBlock;
11
+ }
12
+ const { block } = Astro.props;
13
+ ---
14
+ <Section eyebrow={block.eyebrow} title={block.title} tone="bg">
15
+ {block.subhead && (
16
+ <p class="-mt-4 mb-9 max-w-[60ch] text-[var(--color-muted)]" data-reveal>
17
+ {block.subhead}
18
+ </p>
19
+ )}
20
+ <div class="grid gap-6 md:grid-cols-2">
21
+ <div
22
+ class="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-surface)] p-7"
23
+ data-reveal
24
+ >
25
+ <h3 class="flex items-center gap-2 font-display text-lg font-semibold text-[var(--color-muted)]">
26
+ <span aria-hidden="true">⚠</span> {block.before.title}
27
+ </h3>
28
+ <ul class="mt-5 space-y-3">
29
+ {block.before.items.map((item) => (
30
+ <li class="flex items-start gap-3 text-sm text-[var(--color-muted)] line-through decoration-[var(--color-border)]">
31
+ {item}
32
+ </li>
33
+ ))}
34
+ </ul>
35
+ </div>
36
+ <div
37
+ class="rounded-[var(--radius-lg)] border-2 border-[var(--color-primary)] bg-[var(--color-surface)] p-7 shadow-[var(--shadow-md)]"
38
+ data-reveal
39
+ >
40
+ <h3 class="flex items-center gap-2 font-display text-lg font-semibold text-[var(--color-primary)]">
41
+ <span aria-hidden="true">✓</span> {block.after.title}
42
+ </h3>
43
+ <ul class="mt-5 space-y-3">
44
+ {block.after.items.map((item) => (
45
+ <li class="flex items-start gap-3 text-sm text-[var(--color-text)]">
46
+ <span aria-hidden="true" class="mt-0.5 text-[var(--color-accent)]">✓</span>
47
+ {item}
48
+ </li>
49
+ ))}
50
+ </ul>
51
+ </div>
52
+ </div>
53
+ </Section>
@@ -0,0 +1,36 @@
1
+ ---
2
+ /**
3
+ * <Steps> — a numbered/emoji journey (e.g. the checkout verification flow).
4
+ * Renders as a horizontal-scrolling rail on small screens, a wrapped row above.
5
+ */
6
+ import Section from "@/components/Section.astro";
7
+ import type { StepsBlock } from "@/lib/storyblok/content-model";
8
+ interface Props {
9
+ block: StepsBlock;
10
+ }
11
+ const { block } = Astro.props;
12
+ ---
13
+ <Section eyebrow={block.eyebrow} title={block.title} tone="surface">
14
+ {block.subhead && (
15
+ <p class="-mt-4 mb-9 max-w-[60ch] text-[var(--color-muted)]" data-reveal>
16
+ {block.subhead}
17
+ </p>
18
+ )}
19
+ <ol class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4" data-reveal>
20
+ {block.steps.map((step, i) => (
21
+ <li class="relative rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-bg)] p-5">
22
+ <div class="flex items-center gap-3">
23
+ <span class="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-[var(--color-primary)] font-mono text-sm text-[var(--color-primary-contrast)]">
24
+ {step.icon ?? i + 1}
25
+ </span>
26
+ <span class="font-display text-sm font-semibold text-[var(--color-text)]">
27
+ {step.label}
28
+ </span>
29
+ </div>
30
+ {step.detail && (
31
+ <p class="mt-3 text-sm text-[var(--color-muted)]">{step.detail}</p>
32
+ )}
33
+ </li>
34
+ ))}
35
+ </ol>
36
+ </Section>
@@ -0,0 +1,38 @@
1
+ ---
2
+ /**
3
+ * <Testimonials> — outcome/quote cards with an optional stat lead-in and
4
+ * attribution, plus an optional row of rating tokens. Plain text only.
5
+ */
6
+ import Section from "@/components/Section.astro";
7
+ import type { TestimonialsBlock } from "@/lib/storyblok/content-model";
8
+ interface Props {
9
+ block: TestimonialsBlock;
10
+ }
11
+ const { block } = Astro.props;
12
+ ---
13
+ <Section eyebrow={block.eyebrow} title={block.title} tone="bg">
14
+ <div class="grid gap-5 md:grid-cols-3" data-reveal>
15
+ {block.quotes.map((q) => (
16
+ <figure class="flex flex-col rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-surface)] p-6">
17
+ {q.stat && (
18
+ <p class="font-display text-2xl font-bold text-[var(--color-accent)]">{q.stat}</p>
19
+ )}
20
+ <blockquote class="mt-3 flex-1 text-[var(--color-text)]">
21
+ <p>“{q.quote}”</p>
22
+ </blockquote>
23
+ {q.attribution && (
24
+ <figcaption class="mt-4 text-sm text-[var(--color-muted)]">
25
+ — {q.attribution}
26
+ </figcaption>
27
+ )}
28
+ </figure>
29
+ ))}
30
+ </div>
31
+ {block.ratings?.length && (
32
+ <ul class="mt-8 flex flex-wrap justify-center gap-x-8 gap-y-2" data-reveal>
33
+ {block.ratings.map((r) => (
34
+ <li class="font-mono text-sm text-[var(--color-muted)]">{r}</li>
35
+ ))}
36
+ </ul>
37
+ )}
38
+ </Section>
@@ -0,0 +1,22 @@
1
+ ---
2
+ /**
3
+ * <TrustBar> — a quiet horizontal strip of proof tokens (countries, standards,
4
+ * platform names, ratings). Plain text, dot-separated on small screens.
5
+ */
6
+ import type { TrustBarBlock } from "@/lib/storyblok/content-model";
7
+ interface Props {
8
+ block: TrustBarBlock;
9
+ }
10
+ const { block } = Astro.props;
11
+ ---
12
+ <div class="border-y border-[var(--color-border)] bg-[var(--color-surface)]">
13
+ <div class="container-prose py-4">
14
+ <ul class="flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-center font-mono text-[0.72rem] uppercase tracking-[0.1em] text-[var(--color-muted)]">
15
+ {block.items.map((item) => (
16
+ <li class="flex items-center gap-5">
17
+ <span>{item}</span>
18
+ </li>
19
+ ))}
20
+ </ul>
21
+ </div>
22
+ </div>
@@ -0,0 +1,148 @@
1
+ ---
2
+ /**
3
+ * <Footer> — brand column + link columns + newsletter signup + social row +
4
+ * bottom policy bar. Theme-agnostic (CSS-var-backed utilities only).
5
+ */
6
+ import type { SiteChrome } from "@/lib/storyblok/content-model";
7
+ import { withBase } from "@/lib/basePath";
8
+
9
+ interface Props {
10
+ chrome: SiteChrome;
11
+ }
12
+
13
+ const { chrome } = Astro.props;
14
+ const { tenant, basePath, features } = Astro.locals;
15
+ const year = new Date().getFullYear();
16
+ // Bottom-bar links: if a column is explicitly titled "Legal", use just those
17
+ // (marketing tenants); otherwise fall back to all column links (commerce, as before).
18
+ const legalCol = chrome.footer.columns.find((c) => /legal/i.test(c.title));
19
+ const policyLinks = legalCol
20
+ ? legalCol.links
21
+ : chrome.footer.columns.flatMap((c) => c.links);
22
+ // Marketing tenants have no newsletter form; when it's hidden the columns get
23
+ // the full width. The bottom bar shows a legal/compliance line if provided.
24
+ const showNewsletter = features.newsletter;
25
+ const legal = chrome.footer.legal;
26
+ ---
27
+
28
+ <footer class="mt-24 border-t border-border bg-surface">
29
+ <div class="container-prose py-16">
30
+ <div class="grid gap-12 md:grid-cols-2 lg:grid-cols-4">
31
+ <!-- Brand -->
32
+ <div class="lg:col-span-1">
33
+ <a
34
+ href={withBase(basePath, "/")}
35
+ class="font-display text-lg font-bold tracking-tight transition-colors hover:text-primary"
36
+ >
37
+ {tenant.displayName}
38
+ </a>
39
+ {
40
+ chrome.footer.tagline && (
41
+ <p class="mt-3 max-w-xs text-sm text-muted">
42
+ {chrome.footer.tagline}
43
+ </p>
44
+ )
45
+ }
46
+ </div>
47
+
48
+ <!-- Link columns -->
49
+ {
50
+ chrome.footer.columns.map((col) => (
51
+ <nav aria-label={col.title}>
52
+ <h2 class="eyebrow">{col.title}</h2>
53
+ <ul class="mt-4 space-y-2.5">
54
+ {col.links.map((link) => (
55
+ <li>
56
+ <a
57
+ href={withBase(basePath, link.href)}
58
+ class="text-sm text-muted transition-colors hover:text-primary"
59
+ >
60
+ {link.label}
61
+ </a>
62
+ </li>
63
+ ))}
64
+ </ul>
65
+ </nav>
66
+ ))
67
+ }
68
+
69
+ <!-- Newsletter (commerce only) -->
70
+ {showNewsletter && (
71
+ <section id="newsletter" aria-labelledby="newsletter-heading">
72
+ <h2 id="newsletter-heading" class="eyebrow">Newsletter</h2>
73
+ <form
74
+ action={withBase(basePath, "/api/newsletter")}
75
+ method="post"
76
+ class="mt-4 flex flex-col gap-2"
77
+ >
78
+ <input type="hidden" name="source" value="footer" />
79
+ <label for="footer-newsletter-email" class="sr-only">
80
+ Email address
81
+ </label>
82
+ <input
83
+ id="footer-newsletter-email"
84
+ type="email"
85
+ name="email"
86
+ required
87
+ autocomplete="email"
88
+ placeholder="you@example.com"
89
+ aria-label="Email address"
90
+ class="w-full rounded-md border border-border bg-bg px-3 py-2 text-sm text-text placeholder:text-muted"
91
+ />
92
+ <button
93
+ type="submit"
94
+ class="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 font-display text-sm font-medium text-primary-contrast transition-opacity hover:opacity-90"
95
+ >
96
+ Subscribe
97
+ </button>
98
+ </form>
99
+ </section>
100
+ )}
101
+ </div>
102
+
103
+ <!-- Social -->
104
+ {
105
+ chrome.footer.social.length > 0 && (
106
+ <ul class="mt-12 flex flex-wrap gap-x-6 gap-y-2">
107
+ {chrome.footer.social.map((s) => (
108
+ <li>
109
+ <a
110
+ href={withBase(basePath, s.href)}
111
+ class="font-mono text-xs uppercase tracking-[0.12em] text-muted transition-colors hover:text-primary"
112
+ >
113
+ {s.label}
114
+ </a>
115
+ </li>
116
+ ))}
117
+ </ul>
118
+ )
119
+ }
120
+ </div>
121
+
122
+ <!-- Bottom bar -->
123
+ <div class="border-t border-border">
124
+ <div
125
+ class="container-prose flex flex-col gap-3 py-6 text-xs text-muted sm:flex-row sm:items-center sm:justify-between"
126
+ >
127
+ <p>© {year} {tenant.displayName}{legal ? ` · ${legal}` : ""}</p>
128
+ {
129
+ policyLinks.length > 0 && (
130
+ <nav aria-label="Legal">
131
+ <ul class="flex flex-wrap gap-x-5 gap-y-1">
132
+ {policyLinks.map((link) => (
133
+ <li>
134
+ <a
135
+ href={withBase(basePath, link.href)}
136
+ class="transition-colors hover:text-primary"
137
+ >
138
+ {link.label}
139
+ </a>
140
+ </li>
141
+ ))}
142
+ </ul>
143
+ </nav>
144
+ )
145
+ }
146
+ </div>
147
+ </div>
148
+ </footer>