@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,70 @@
1
+ ---
2
+ /**
3
+ * <Seo> — per-page title/meta, Open Graph, Twitter card, canonical, JSON-LD.
4
+ * (brief §8). Tenant-aware: OG image + site name come from the resolved tenant.
5
+ */
6
+ interface JsonLd {
7
+ [key: string]: unknown;
8
+ }
9
+
10
+ interface Props {
11
+ title: string;
12
+ description?: string;
13
+ /** Path-only canonical, e.g. /products/foo. Joined to tenant canonical base. */
14
+ path: string;
15
+ ogType?: "website" | "product" | "article";
16
+ ogImage?: string;
17
+ noindex?: boolean;
18
+ /** One or more JSON-LD graphs (Product, BreadcrumbList, Organization…). */
19
+ jsonLd?: JsonLd | JsonLd[];
20
+ }
21
+
22
+ const {
23
+ title,
24
+ description,
25
+ path,
26
+ ogType = "website",
27
+ ogImage,
28
+ noindex = false,
29
+ jsonLd,
30
+ } = Astro.props;
31
+
32
+ const { tenant, canonicalBase } = Astro.locals;
33
+ const canonical = new URL(path, canonicalBase + "/").toString();
34
+ // Don't double-append the brand: merchant SEO titles (title_tag) often already
35
+ // end with the store name.
36
+ const fullTitle = title.includes(tenant.displayName)
37
+ ? title
38
+ : `${title} — ${tenant.displayName}`;
39
+ const image = ogImage
40
+ ? new URL(ogImage, canonicalBase + "/").toString()
41
+ : new URL(tenant.theme_tokens?.brand?.ogImage ?? "/brand/northwind-og.svg", canonicalBase + "/").toString();
42
+
43
+ const graphs = jsonLd ? (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : [];
44
+ ---
45
+
46
+ <title>{fullTitle}</title>
47
+ {description && <meta name="description" content={description} />}
48
+ <link rel="canonical" href={canonical} />
49
+ {noindex && <meta name="robots" content="noindex,nofollow" />}
50
+
51
+ <meta property="og:type" content={ogType} />
52
+ <meta property="og:site_name" content={tenant.displayName} />
53
+ <meta property="og:title" content={fullTitle} />
54
+ {description && <meta property="og:description" content={description} />}
55
+ <meta property="og:url" content={canonical} />
56
+ <meta property="og:image" content={image} />
57
+
58
+ <meta name="twitter:card" content="summary_large_image" />
59
+ <meta name="twitter:title" content={fullTitle} />
60
+ {description && <meta name="twitter:description" content={description} />}
61
+ <meta name="twitter:image" content={image} />
62
+
63
+ {
64
+ graphs.map((g) => (
65
+ <script
66
+ type="application/ld+json"
67
+ set:html={JSON.stringify(g)}
68
+ />
69
+ ))
70
+ }
@@ -0,0 +1,34 @@
1
+ ---
2
+ /**
3
+ * <WidgetFrame> — renders a tenant-registered widget (isolation:"sandbox") in a
4
+ * sandboxed iframe. The sandbox token list (`allow-scripts`, no
5
+ * `allow-same-origin`) puts the widget in an opaque origin, so it cannot read the
6
+ * page's cookies/DOM or its own storage, even though the bundle is served from our
7
+ * own origin. The widget bundle is hash-pinned in CSP and placed into a
8
+ * platform-defined slot.
9
+ */
10
+ import { widgetFrameAttrs, type ScriptStrategy } from "@tot/public-runtime";
11
+
12
+ interface Props {
13
+ /** Same-origin URL that loads the tenant's widget bundle. */
14
+ src: string;
15
+ /** Accessible title for the iframe (required for a11y). */
16
+ title: string;
17
+ strategy?: ScriptStrategy;
18
+ /** Optional fixed height; widgets may also self-size via postMessage later. */
19
+ height?: string;
20
+ }
21
+
22
+ const { src, title, strategy = "on-idle", height = "auto" } = Astro.props;
23
+ const attrs = widgetFrameAttrs(strategy);
24
+ ---
25
+
26
+ <iframe
27
+ src={src}
28
+ title={title}
29
+ sandbox={attrs.sandbox}
30
+ loading={attrs.loading}
31
+ referrerpolicy={attrs.referrerpolicy}
32
+ class="tot-widget-frame"
33
+ style={`border:0;width:100%;height:${height};`}
34
+ ></iframe>
@@ -0,0 +1,44 @@
1
+ ---
2
+ /**
3
+ * AuthBadge — a small "signed in as … — <role>" pill shown on login-gated hosts.
4
+ *
5
+ * Renders only when `Astro.locals.viewer` is set (i.e. the login gate resolved a
6
+ * valid session). Surfaces the viewer's email + effective capability (with the
7
+ * full role list on hover) and a sign-out control — the "report back what role(s)
8
+ * they're logged in with" affordance. Absent on open/password-gated hosts.
9
+ */
10
+ const viewer = Astro.locals.viewer;
11
+
12
+ const CAP_LABEL: Record<string, string> = {
13
+ owner: "Owner",
14
+ admin: "Admin",
15
+ member: "Member",
16
+ developer: "Developer",
17
+ inviter: "Inviter",
18
+ staff: "ToT Staff",
19
+ };
20
+ const label = viewer ? (CAP_LABEL[viewer.capability] ?? viewer.capability) : "";
21
+ ---
22
+
23
+ {viewer && (
24
+ <div
25
+ data-auth-badge
26
+ title={`Signed in as ${viewer.email} — role(s): ${viewer.roles.join(", ") || "none"}`}
27
+ class="fixed bottom-3 left-3 z-50 flex items-center gap-2 rounded-full border border-[var(--color-border)] bg-[var(--color-bg)] px-3 py-1.5 text-xs shadow-sm"
28
+ >
29
+ <span aria-hidden="true">🔓</span>
30
+ <span class="text-[var(--color-muted)]">Signed in as</span>
31
+ <span class="font-semibold">{viewer.email}</span>
32
+ <span class="rounded-full bg-[var(--color-primary)] px-2 py-0.5 font-semibold text-[var(--color-primary-contrast)]">
33
+ {label}
34
+ </span>
35
+ <form method="post" action="/api/auth/logout" class="contents">
36
+ <button
37
+ type="submit"
38
+ class="text-[var(--color-muted)] underline underline-offset-2 hover:text-[var(--color-primary)]"
39
+ >
40
+ Sign out
41
+ </button>
42
+ </form>
43
+ </div>
44
+ )}
@@ -0,0 +1,108 @@
1
+ ---
2
+ /**
3
+ * <AnnouncementBar> — the top promo strip. Two variants:
4
+ * - "static" : centered single line.
5
+ * - "marquee" : a continuously scrolling track (duplicated for a seamless loop).
6
+ *
7
+ * Reduced-motion safe: `prefers-reduced-motion: reduce` freezes the marquee and
8
+ * shows the message statically (no infinite animation). Tenant-agnostic — colours
9
+ * come from theme tokens; `tone` picks which token pair.
10
+ */
11
+ interface Props {
12
+ text: string;
13
+ variant?: "static" | "marquee";
14
+ /** How many times to repeat the phrase in the marquee track. */
15
+ repeat?: number;
16
+ tone?: "primary" | "sale" | "accent";
17
+ /** Accessible label for the region (marquee hides the visual copies). */
18
+ label?: string;
19
+ class?: string;
20
+ }
21
+
22
+ const {
23
+ text,
24
+ variant = "static",
25
+ repeat = 6,
26
+ tone = "primary",
27
+ label,
28
+ class: className,
29
+ } = Astro.props;
30
+
31
+ const toneClass: Record<NonNullable<Props["tone"]>, string> = {
32
+ primary: "bg-primary text-primary-contrast",
33
+ sale: "bg-[var(--color-sale)] text-white",
34
+ accent: "bg-accent text-white",
35
+ };
36
+ const copies = Array.from({ length: Math.max(2, repeat) });
37
+ ---
38
+
39
+ <div
40
+ class:list={["tot-announce", toneClass[tone], className]}
41
+ role="region"
42
+ aria-label={label ?? text}
43
+ >
44
+ {
45
+ variant === "marquee" ? (
46
+ <div class="tot-announce__viewport">
47
+ <div class="tot-announce__track" aria-hidden="true">
48
+ {copies.map(() => (
49
+ <span class="tot-announce__item">{text}</span>
50
+ ))}
51
+ </div>
52
+ {/* Static fallback shown only when motion is reduced. */}
53
+ <p class="tot-announce__static">{text}</p>
54
+ </div>
55
+ ) : (
56
+ <p class="tot-announce__line">{text}</p>
57
+ )
58
+ }
59
+ </div>
60
+
61
+ <style>
62
+ .tot-announce {
63
+ font-family: var(--font-mono);
64
+ font-size: 0.72rem;
65
+ letter-spacing: 0.08em;
66
+ }
67
+ .tot-announce__line {
68
+ text-align: center;
69
+ padding: 0.375rem 1rem;
70
+ }
71
+ .tot-announce__viewport {
72
+ overflow: hidden;
73
+ white-space: nowrap;
74
+ padding: 0.375rem 0;
75
+ }
76
+ .tot-announce__track {
77
+ display: inline-flex;
78
+ gap: 3rem;
79
+ padding-left: 3rem;
80
+ will-change: transform;
81
+ animation: tot-marquee 22s linear infinite;
82
+ }
83
+ .tot-announce__item {
84
+ display: inline-block;
85
+ }
86
+ .tot-announce__static {
87
+ display: none;
88
+ text-align: center;
89
+ }
90
+ @keyframes tot-marquee {
91
+ from {
92
+ transform: translateX(0);
93
+ }
94
+ to {
95
+ /* Track is duplicated content; -50% loops seamlessly. */
96
+ transform: translateX(-50%);
97
+ }
98
+ }
99
+ @media (prefers-reduced-motion: reduce) {
100
+ .tot-announce__track {
101
+ display: none;
102
+ }
103
+ .tot-announce__static {
104
+ display: block;
105
+ padding: 0 1rem;
106
+ }
107
+ }
108
+ </style>
@@ -0,0 +1,26 @@
1
+ ---
2
+ /**
3
+ * <MegaMenu> — the primary navigation bar: a horizontal list of nav items, each
4
+ * rendered by <NavDropdown> (plain link, single-column dropdown, or multi-column
5
+ * mega panel). Tenant-agnostic; theme-token skinned; CSS-only reveal.
6
+ *
7
+ * Used by <SiteHeader> for the desktop nav, and registerable standalone in the
8
+ * style guide.
9
+ */
10
+ import type { ChromeNavItem } from "@/lib/chrome/model";
11
+ import NavDropdown from "@/components/chrome/NavDropdown.astro";
12
+
13
+ interface Props {
14
+ items: ChromeNavItem[];
15
+ ariaLabel?: string;
16
+ class?: string;
17
+ }
18
+
19
+ const { items, ariaLabel = "Primary", class: className } = Astro.props;
20
+ ---
21
+
22
+ <nav aria-label={ariaLabel} class:list={["hidden md:block", className]}>
23
+ <ul class="flex items-center gap-6">
24
+ {items.map((item) => <NavDropdown item={item} />)}
25
+ </ul>
26
+ </nav>
@@ -0,0 +1,116 @@
1
+ ---
2
+ /**
3
+ * <NavDropdown> — one primary-nav item. Renders as:
4
+ * - a plain link (no children/columns),
5
+ * - a single-column dropdown (`children`), or
6
+ * - a multi-column mega panel (`columns`).
7
+ * With an optional caret, a "NEW"-style badge, and a highlighted `flag` variant.
8
+ *
9
+ * CSS-only reveal (group-hover + group-focus-within) — no JS, keyboard-accessible.
10
+ * Tenant-agnostic; theme-token skinned.
11
+ */
12
+ import type { ChromeNavItem, ChromeLink } from "@/lib/chrome/model";
13
+ import { withBase } from "@/lib/basePath";
14
+
15
+ interface Props {
16
+ item: ChromeNavItem;
17
+ }
18
+
19
+ const { item } = Astro.props;
20
+ const { basePath } = Astro.locals;
21
+
22
+ const itemHref = withBase(basePath, item.href);
23
+ const hrefOf = (l: ChromeLink) => (l.external ? l.href : withBase(basePath, l.href));
24
+ const hasMenu =
25
+ (item.columns && item.columns.length > 0) ||
26
+ (item.children && item.children.length > 0);
27
+ const isMega = !!(item.columns && item.columns.length > 0);
28
+ ---
29
+
30
+ {
31
+ hasMenu ? (
32
+ <li class="group relative">
33
+ <a
34
+ href={itemHref}
35
+ class:list={[
36
+ "inline-flex items-center gap-1 py-2 text-sm transition-colors hover:text-accent group-focus-within:text-accent",
37
+ item.flag && "font-semibold text-accent",
38
+ ]}
39
+ aria-haspopup="true"
40
+ >
41
+ {item.label}
42
+ {item.badge && (
43
+ <span class="ml-1 rounded-full bg-[var(--color-sale)] px-1.5 py-0.5 font-mono text-[0.55rem] uppercase tracking-[0.08em] leading-none text-white">
44
+ {item.badge}
45
+ </span>
46
+ )}
47
+ <span aria-hidden="true" class="text-[0.7em] opacity-60">▾</span>
48
+ </a>
49
+
50
+ <div class="invisible absolute left-0 top-full z-50 translate-y-1 opacity-0 transition-all duration-150 group-hover:visible group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:visible group-focus-within:translate-y-0 group-focus-within:opacity-100">
51
+ {isMega ? (
52
+ <div class="mt-1 grid gap-6 rounded-md border border-border bg-surface p-5 shadow-md sm:grid-cols-2 lg:grid-cols-3">
53
+ {item.columns!.map((col) => (
54
+ <div class="min-w-44">
55
+ {col.title && (
56
+ <p class="mb-2 font-mono text-[0.62rem] uppercase tracking-[0.12em] text-muted">
57
+ {col.title}
58
+ </p>
59
+ )}
60
+ <ul class="grid gap-0.5">
61
+ {col.links.map((child) => (
62
+ <li>
63
+ <a
64
+ href={hrefOf(child)}
65
+ class="flex items-center gap-1.5 rounded-sm px-2 py-1 text-sm text-muted transition-colors hover:bg-bg hover:text-primary"
66
+ {...(child.external ? { rel: "noopener noreferrer" } : {})}
67
+ >
68
+ {child.label}
69
+ {child.badge && (
70
+ <span class="rounded-full bg-primary px-1 py-0.5 font-mono text-[0.5rem] uppercase leading-none text-primary-contrast">
71
+ {child.badge}
72
+ </span>
73
+ )}
74
+ </a>
75
+ </li>
76
+ ))}
77
+ </ul>
78
+ </div>
79
+ ))}
80
+ </div>
81
+ ) : (
82
+ <ul class="mt-1 grid min-w-56 gap-0.5 rounded-md border border-border bg-surface p-3 shadow-md">
83
+ {item.children!.map((child) => (
84
+ <li>
85
+ <a
86
+ href={hrefOf(child)}
87
+ class="block rounded-sm px-3 py-1.5 text-sm text-muted transition-colors hover:bg-bg hover:text-primary"
88
+ {...(child.external ? { rel: "noopener noreferrer" } : {})}
89
+ >
90
+ {child.label}
91
+ </a>
92
+ </li>
93
+ ))}
94
+ </ul>
95
+ )}
96
+ </div>
97
+ </li>
98
+ ) : (
99
+ <li>
100
+ <a
101
+ href={itemHref}
102
+ class:list={[
103
+ "inline-flex items-center gap-1 py-2 text-sm transition-colors hover:text-accent",
104
+ item.flag && "font-semibold text-accent",
105
+ ]}
106
+ >
107
+ {item.label}
108
+ {item.badge && (
109
+ <span class="ml-1 rounded-full bg-[var(--color-sale)] px-1.5 py-0.5 font-mono text-[0.55rem] uppercase tracking-[0.08em] leading-none text-white">
110
+ {item.badge}
111
+ </span>
112
+ )}
113
+ </a>
114
+ </li>
115
+ )
116
+ }
@@ -0,0 +1,181 @@
1
+ ---
2
+ /**
3
+ * <SiteFooter> — brand block + link columns + newsletter signup + a payment-badge
4
+ * row + a trust/compliance line + legal bottom bar. Tenant-agnostic; theme-token
5
+ * skinned. Payment badges are text chips (no external images — CSP-clean).
6
+ *
7
+ * The newsletter is a plain POST form (progressive); it does not embed any cart
8
+ * or checkout logic (commerce boundary).
9
+ */
10
+ import type { ChromeLink, FooterColumn, FooterNewsletter, SocialLink, ChromeLogo } from "@/lib/chrome/model";
11
+ import { withBase } from "@/lib/basePath";
12
+
13
+ interface Props {
14
+ columns: FooterColumn[];
15
+ brand?: { logo?: ChromeLogo; wordmark?: string; contact?: string[] };
16
+ newsletter?: FooterNewsletter;
17
+ /** Payment method labels rendered as chips (e.g. "Visa", "PayPal"). */
18
+ payments?: string[];
19
+ social?: SocialLink[];
20
+ /** Trust / compliance line (plain text, e.g. an age/nicotine notice). */
21
+ compliance?: string;
22
+ /** Legal bottom-bar line. */
23
+ legal?: string;
24
+ class?: string;
25
+ }
26
+
27
+ const {
28
+ columns,
29
+ brand,
30
+ newsletter,
31
+ payments = [],
32
+ social = [],
33
+ compliance,
34
+ legal,
35
+ class: className,
36
+ } = Astro.props;
37
+
38
+ const { basePath } = Astro.locals;
39
+ const hrefOf = (l: ChromeLink) => (l.external ? l.href : withBase(basePath, l.href));
40
+ const showLogo = !!brand?.logo && /^(https?:|\/)/.test(brand.logo.src);
41
+ ---
42
+
43
+ <footer class:list={["border-t border-border bg-surface text-text", className]}>
44
+ <div class="container-prose py-12">
45
+ <div class="grid gap-10 md:grid-cols-[1.4fr_repeat(3,1fr)]">
46
+ <!-- Brand -->
47
+ <div>
48
+ {
49
+ showLogo ? (
50
+ <img
51
+ src={brand!.logo!.src}
52
+ alt={brand!.logo!.alt}
53
+ width={brand!.logo!.width ?? 160}
54
+ height={brand!.logo!.height ?? 48}
55
+ class="h-9 w-auto max-w-[180px] object-contain"
56
+ />
57
+ ) : (
58
+ brand?.wordmark && (
59
+ <span class="font-display text-lg font-bold tracking-tight">{brand.wordmark}</span>
60
+ )
61
+ )
62
+ }
63
+ {
64
+ brand?.contact && brand.contact.length > 0 && (
65
+ <p class="mt-3 text-sm text-muted">
66
+ {brand.contact.map((line, i) => (
67
+ <>
68
+ {i > 0 && <br />}
69
+ {line}
70
+ </>
71
+ ))}
72
+ </p>
73
+ )
74
+ }
75
+ {
76
+ social.length > 0 && (
77
+ <div class="mt-4 flex items-center gap-3">
78
+ {social.map((s) => (
79
+ <a
80
+ href={s.href}
81
+ aria-label={s.label}
82
+ rel="noopener noreferrer"
83
+ class="text-muted transition-colors hover:text-primary"
84
+ >
85
+ {s.iconPath ? (
86
+ <svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
87
+ <path d={s.iconPath} />
88
+ </svg>
89
+ ) : (
90
+ <span class="text-sm">{s.label}</span>
91
+ )}
92
+ </a>
93
+ ))}
94
+ </div>
95
+ )
96
+ }
97
+ </div>
98
+
99
+ <!-- Link columns -->
100
+ {
101
+ columns.map((col) => (
102
+ <div>
103
+ <h3 class="font-display text-sm font-semibold">{col.title}</h3>
104
+ <ul class="mt-3 grid gap-2">
105
+ {col.links.map((l) => (
106
+ <li>
107
+ <a
108
+ href={hrefOf(l)}
109
+ class="text-sm text-muted transition-colors hover:text-primary"
110
+ {...(l.external ? { rel: "noopener noreferrer" } : {})}
111
+ >
112
+ {l.label}
113
+ </a>
114
+ </li>
115
+ ))}
116
+ </ul>
117
+ </div>
118
+ ))
119
+ }
120
+ </div>
121
+
122
+ <!-- Newsletter -->
123
+ {
124
+ newsletter && (
125
+ <div class="mt-10 border-t border-border pt-8">
126
+ {newsletter.title && <h3 class="font-display text-base font-semibold">{newsletter.title}</h3>}
127
+ {newsletter.body && <p class="mt-1 text-sm text-muted">{newsletter.body}</p>}
128
+ <form
129
+ action={withBase(basePath, newsletter.action ?? "/api/newsletter")}
130
+ method="post"
131
+ class="mt-4 flex max-w-md flex-col gap-3 sm:flex-row"
132
+ >
133
+ <input type="hidden" name="source" value={newsletter.source ?? "footer"} />
134
+ <label class="sr-only" for="chrome-news-email">Email address</label>
135
+ <input
136
+ id="chrome-news-email"
137
+ type="email"
138
+ name="email"
139
+ required
140
+ placeholder={newsletter.placeholder ?? "you@example.com"}
141
+ class="flex-1 rounded-full border border-border bg-bg px-4 py-2.5 text-sm text-text placeholder:text-muted"
142
+ />
143
+ <button
144
+ type="submit"
145
+ class="rounded-full bg-primary px-5 py-2.5 font-display text-sm font-medium text-primary-contrast transition-transform hover:-translate-y-0.5"
146
+ >
147
+ Subscribe
148
+ </button>
149
+ </form>
150
+ </div>
151
+ )
152
+ }
153
+
154
+ <!-- Payment badges -->
155
+ {
156
+ payments.length > 0 && (
157
+ <div class="mt-8 flex flex-wrap items-center gap-2" aria-label="Accepted payment methods">
158
+ {payments.map((p) => (
159
+ <span class="rounded border border-border px-2 py-1 font-mono text-[0.62rem] uppercase tracking-[0.08em] text-muted">
160
+ {p}
161
+ </span>
162
+ ))}
163
+ </div>
164
+ )
165
+ }
166
+
167
+ <!-- Compliance / trust line -->
168
+ {
169
+ compliance && (
170
+ <p class="mt-8 border-t border-border pt-6 text-xs text-muted">{compliance}</p>
171
+ )
172
+ }
173
+
174
+ <!-- Legal bottom bar -->
175
+ {
176
+ legal && (
177
+ <p class="mt-4 font-mono text-[0.68rem] uppercase tracking-[0.08em] text-muted">{legal}</p>
178
+ )
179
+ }
180
+ </div>
181
+ </footer>