@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,85 @@
1
+ ---
2
+ /**
3
+ * Preview/dev-only STYLE-GUIDE HUB for a tenant — `/_style-guide/<tenant>/`.
4
+ *
5
+ * Lists every theme guide (current / candidate / legacy) so reviewers can open
6
+ * them side-by-side WITHOUT reskinning the live marketing site. This is a review
7
+ * surface only: noindex, never in the sitemap, never part of production marketing
8
+ * publishing. Tenant-agnostic route; theme data is tenant-local
9
+ * (tenants/<tenant>/content/themes/*.json).
10
+ */
11
+ const { tenant } = Astro.params;
12
+ const cspNonce = Astro.locals.cspNonce;
13
+
14
+ // Tenant-local theme descriptors. Colocated repo-root layout: from
15
+ // src/pages/style-guide/[tenant]/ the repo root is six levels up.
16
+ const themeModules = import.meta.glob<Record<string, any>>(
17
+ "../../../../../../tenants/*/content/themes/*.json",
18
+ { eager: true, import: "default" },
19
+ );
20
+ const seg = `/tenants/${tenant}/content/themes/`;
21
+ const themes = Object.entries(themeModules)
22
+ .filter(([path]) => path.includes(seg))
23
+ .map(([, mod]) => mod);
24
+
25
+ if (themes.length === 0) return new Response(null, { status: 404 });
26
+
27
+ const ROLE_ORDER: Record<string, number> = { current: 0, candidate: 1, legacy: 2 };
28
+ themes.sort(
29
+ (a, b) => (ROLE_ORDER[a.role] ?? 9) - (ROLE_ORDER[b.role] ?? 9),
30
+ );
31
+ ---
32
+
33
+ <!doctype html>
34
+ <html lang="en">
35
+ <head>
36
+ <meta charset="utf-8" />
37
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
38
+ <meta name="robots" content="noindex, nofollow" />
39
+ <link rel="icon" type="image/png" href={`/tenants/${tenant}/favicon.png`} />
40
+ <title>Style guides — {tenant} (preview)</title>
41
+ <style nonce={cspNonce}>
42
+ :root { color-scheme: light; }
43
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; background: #0f1729; color: #e6edf6; line-height: 1.55; }
44
+ .wrap { max-width: 860px; margin: 0 auto; padding: 48px 24px 80px; }
45
+ .flag { display: inline-block; font-size: .72rem; letter-spacing: .1em; text-transform: uppercase; background: #f59e0b; color: #221600; font-weight: 800; padding: 4px 10px; border-radius: 6px; }
46
+ h1 { font-size: 1.9rem; margin: 18px 0 6px; }
47
+ .sub { color: #9fb0c8; margin: 0 0 32px; }
48
+ .card { display: block; text-decoration: none; color: inherit; background: #16223c; border: 1px solid #24324f; border-radius: 14px; padding: 20px 22px; margin-bottom: 14px; transition: border-color .15s, transform .15s; }
49
+ .card:hover { border-color: #3f5680; transform: translateY(-2px); }
50
+ .card .row { display: flex; align-items: center; gap: 12px; }
51
+ .badge { font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; font-weight: 800; padding: 3px 9px; border-radius: 999px; }
52
+ .badge.current { background: #0e9f8e; color: #001f1a; }
53
+ .badge.candidate { background: #6f7dff; color: #060a26; }
54
+ .badge.legacy { background: #47536e; color: #dfe6f2; }
55
+ .status { margin-left: auto; font-size: .78rem; color: #9fb0c8; font-family: ui-monospace, Menlo, monospace; }
56
+ .name { font-weight: 700; font-size: 1.05rem; }
57
+ .desc { color: #9fb0c8; font-size: .9rem; margin-top: 8px; }
58
+ .note { margin-top: 28px; font-size: .82rem; color: #7f8fab; border-top: 1px solid #24324f; padding-top: 16px; }
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <div class="wrap">
63
+ <span class="flag">Preview · dev only · noindex</span>
64
+ <p style="margin:14px 0 0"><a href="/_style-guide/" style="color:#bfe0ff;font-size:.85rem;text-decoration:none">← all clients</a></p>
65
+ <h1>{tenant} — theme style guides</h1>
66
+ <p class="sub">Open any guide independently to compare looks side-by-side. Nothing here reskins the live site.</p>
67
+
68
+ {themes.map((t) => (
69
+ <a class="card" href={`/_style-guide/${tenant}/${t.id}/`}>
70
+ <div class="row">
71
+ <span class:list={["badge", t.role]}>{t.role}</span>
72
+ <span class="name">{t.name}</span>
73
+ <span class="status">{t.status}</span>
74
+ </div>
75
+ {t.description && <p class="desc">{t.description}</p>}
76
+ </a>
77
+ ))}
78
+
79
+ <p class="note">
80
+ These are preview/dev review surfaces — excluded from the sitemap and production marketing
81
+ publishing. Approve the <b>current</b> guide before propagating its style concept across the site.
82
+ </p>
83
+ </div>
84
+ </body>
85
+ </html>
@@ -0,0 +1,94 @@
1
+ ---
2
+ /**
3
+ * Preview/dev-only STYLE-GUIDE ROOT INDEX — `/_style-guide/`.
4
+ *
5
+ * Lists EVERY tenant and each of its theme guides, so all clients' guides are
6
+ * discoverable from one place. Review surface only: noindex, never in the
7
+ * sitemap, never part of production marketing publishing. Tenant-agnostic —
8
+ * theme data is tenant-local (tenants/<tenant>/content/themes/*.json).
9
+ */
10
+ const cspNonce = Astro.locals.cspNonce;
11
+
12
+ // All tenant-local theme descriptors, grouped by tenant. Colocated repo-root
13
+ // layout: from src/pages/style-guide/ the repo root is five levels up.
14
+ const themeModules = import.meta.glob<Record<string, any>>(
15
+ "../../../../../tenants/*/content/themes/*.json",
16
+ { eager: true, import: "default" },
17
+ );
18
+
19
+ const ROLE_ORDER: Record<string, number> = { current: 0, candidate: 1, legacy: 2 };
20
+ const byTenant = new Map<string, any[]>();
21
+ for (const [path, mod] of Object.entries(themeModules)) {
22
+ const m = /\/tenants\/([^/]+)\/content\/themes\//.exec(path);
23
+ const tenant = m?.[1];
24
+ if (!tenant) continue;
25
+ if (!byTenant.has(tenant)) byTenant.set(tenant, []);
26
+ byTenant.get(tenant)!.push(mod);
27
+ }
28
+ const tenants = [...byTenant.entries()]
29
+ .map(([tenant, themes]) => ({
30
+ tenant,
31
+ themes: themes.sort((a, b) => (ROLE_ORDER[a.role] ?? 9) - (ROLE_ORDER[b.role] ?? 9)),
32
+ }))
33
+ .sort((a, b) => a.tenant.localeCompare(b.tenant));
34
+ ---
35
+
36
+ <!doctype html>
37
+ <html lang="en">
38
+ <head>
39
+ <meta charset="utf-8" />
40
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
41
+ <meta name="robots" content="noindex, nofollow" />
42
+ <title>Style guides — all clients (preview)</title>
43
+ <style nonce={cspNonce}>
44
+ :root { color-scheme: light; }
45
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; background: #0f1729; color: #e6edf6; line-height: 1.55; }
46
+ .wrap { max-width: 920px; margin: 0 auto; padding: 48px 24px 80px; }
47
+ .flag { display: inline-block; font-size: .72rem; letter-spacing: .1em; text-transform: uppercase; background: #f59e0b; color: #221600; font-weight: 800; padding: 4px 10px; border-radius: 6px; }
48
+ h1 { font-size: 1.9rem; margin: 18px 0 6px; }
49
+ .sub { color: #9fb0c8; margin: 0 0 32px; }
50
+ .tenant { margin-bottom: 30px; }
51
+ .tenant > h2 { font-size: 1.05rem; margin: 0 0 12px; display: flex; align-items: baseline; gap: 10px; }
52
+ .tenant > h2 a { color: #bfe0ff; font-size: .82rem; font-weight: 600; text-decoration: none; }
53
+ .card { display: block; text-decoration: none; color: inherit; background: #16223c; border: 1px solid #24324f; border-radius: 14px; padding: 16px 20px; margin-bottom: 12px; transition: border-color .15s, transform .15s; }
54
+ .card:hover { border-color: #3f5680; transform: translateY(-2px); }
55
+ .row { display: flex; align-items: center; gap: 12px; }
56
+ .badge { font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; font-weight: 800; padding: 3px 9px; border-radius: 999px; }
57
+ .badge.current { background: #0e9f8e; color: #001f1a; }
58
+ .badge.candidate { background: #6f7dff; color: #060a26; }
59
+ .badge.legacy { background: #47536e; color: #dfe6f2; }
60
+ .status { margin-left: auto; font-size: .78rem; color: #9fb0c8; font-family: ui-monospace, Menlo, monospace; }
61
+ .name { font-weight: 700; font-size: 1.02rem; }
62
+ .desc { color: #9fb0c8; font-size: .88rem; margin-top: 6px; }
63
+ .note { margin-top: 28px; font-size: .82rem; color: #7f8fab; border-top: 1px solid #24324f; padding-top: 16px; }
64
+ </style>
65
+ </head>
66
+ <body>
67
+ <div class="wrap">
68
+ <span class="flag">Preview · dev only · noindex</span>
69
+ <h1>Client style guides</h1>
70
+ <p class="sub">Every tenant and its theme guides. Open a guide to preview the standard components and page sections re-skinned under that theme. Nothing here re-skins a live site.</p>
71
+
72
+ {tenants.map(({ tenant, themes }) => (
73
+ <section class="tenant">
74
+ <h2>{tenant} <a href={`/_style-guide/${tenant}/`}>tenant hub →</a></h2>
75
+ {themes.map((t) => (
76
+ <a class="card" href={`/_style-guide/${tenant}/${t.id}/`}>
77
+ <div class="row">
78
+ <span class:list={["badge", t.role]}>{t.role}</span>
79
+ <span class="name">{t.name}</span>
80
+ <span class="status">{t.status}</span>
81
+ </div>
82
+ {t.description && <p class="desc">{t.description}</p>}
83
+ </a>
84
+ ))}
85
+ </section>
86
+ ))}
87
+
88
+ <p class="note">
89
+ These are preview/dev review surfaces — excluded from the sitemap and production marketing
90
+ publishing. Approve a tenant's <b>current</b> guide before propagating its style concept across the site.
91
+ </p>
92
+ </div>
93
+ </body>
94
+ </html>
@@ -0,0 +1,57 @@
1
+ /**
2
+ * GET /tenants/<id>/<path> — serve a tenant's versioned static asset from R2.
3
+ *
4
+ * The version-scoped replacement for the build-time `copy-tenant-assets.mjs`
5
+ * baking (decision `assets-r2-versioned`). Resolves the tenant's active
6
+ * channel→snapshot versionId from the `cust:assets-pointer:<id>` KV record and
7
+ * streams `tenants/<id>/<versionId>/<rel>` from the TENANT_ASSETS R2 bucket with
8
+ * the right content-type + a revalidating cache header + a versionId ETag.
9
+ *
10
+ * BACK-COMPAT: this SSR route only runs when NO baked static file matched — the
11
+ * Cloudflare/Vite static layer serves `public/tenants/<id>/…` first. So a
12
+ * NON-versioned tenant (still baked by copy-tenant-assets) never reaches here,
13
+ * and a versioned tenant (skipped from baking via a `.tot/assets-versioned`
14
+ * marker) falls through to R2. When the tenant has no published asset version,
15
+ * `serveTenantAsset` returns null → 404 (nothing published yet).
16
+ */
17
+ import type { APIContext } from "astro";
18
+ import {
19
+ serveTenantAsset,
20
+ type R2BucketLike,
21
+ } from "@tot/public-runtime";
22
+ import { readR2, readKv } from "@/lib/env";
23
+
24
+ export const prerender = false;
25
+
26
+ export async function GET(context: APIContext): Promise<Response> {
27
+ const [bucket, kv] = await Promise.all([
28
+ readR2("TENANT_ASSETS"),
29
+ readKv("TENANT_CACHE"),
30
+ ]);
31
+ // No R2/KV binding (plain `astro dev`, or unconfigured) → nothing to serve.
32
+ if (!bucket || !kv) {
33
+ return new Response("Not found", {
34
+ status: 404,
35
+ headers: { "cache-control": "no-store" },
36
+ });
37
+ }
38
+
39
+ const res = await serveTenantAsset(
40
+ {
41
+ // Runtime binding structurally satisfies the shared-runtime R2 seam.
42
+ bucket: bucket as unknown as R2BucketLike,
43
+ kvGet: (key) => kv.get(key),
44
+ channel: context.locals.customizationEnv ?? "live",
45
+ ifNoneMatch: context.request.headers.get("if-none-match"),
46
+ },
47
+ new URL(context.request.url).pathname,
48
+ );
49
+
50
+ return (
51
+ res ??
52
+ new Response("Not found", {
53
+ status: 404,
54
+ headers: { "cache-control": "no-store" },
55
+ })
56
+ );
57
+ }
@@ -0,0 +1,277 @@
1
+ @import "tailwindcss";
2
+
3
+ /*
4
+ * Theme bridge (brief §6/§7).
5
+ *
6
+ * The per-tenant identity arrives as CSS custom properties in a :root{} block
7
+ * injected by the middleware (src/themes/schema.ts -> Layout.astro). Here we map
8
+ * those vars into Tailwind v4 theme tokens so utilities like `bg-bg`,
9
+ * `text-primary`, `font-display`, `rounded-md` resolve to the LIVE tenant value.
10
+ * Components stay theme-agnostic: they use utilities, the utilities read vars,
11
+ * the vars come from the tenant. Swap a tenant -> the whole UI reskins, no
12
+ * component edits.
13
+ *
14
+ * Fallback values below keep the design coherent before/without the injected
15
+ * block (e.g. error pages, prerendered routes).
16
+ */
17
+ @theme {
18
+ --color-bg: var(--color-bg, #f4f1ea);
19
+ --color-surface: var(--color-surface, #fcfaf5);
20
+ --color-text: var(--color-text, #1c2624);
21
+ --color-muted: var(--color-muted, #5c6663);
22
+ --color-primary: var(--color-primary, #1f3a34);
23
+ --color-primary-contrast: var(--color-primary-contrast, #f4f1ea);
24
+ --color-accent: var(--color-accent, #b5612f);
25
+ /* Darkened accent for SMALL text/links on light bg/surface (the bright
26
+ `accent` is reserved for large display type on the spruce hero, where it
27
+ reads as the brass spark). Derived from the tenant's own accent so it stays
28
+ theme-agnostic. Reference value resolves to ~#985127 (≥5:1 on bg/surface). */
29
+ --color-accent-ink: color-mix(in srgb, var(--color-accent) 84%, #000);
30
+ --color-border: var(--color-border, #dcd6c9);
31
+ --color-success: var(--color-success, #3f6b4f);
32
+ --color-sale: var(--color-sale, #9e3b2e);
33
+
34
+ --font-display: var(--font-display, "Space Grotesk"), system-ui, sans-serif;
35
+ --font-body: var(--font-body, "Source Serif 4"), Georgia, serif;
36
+ --font-mono: var(--font-mono, "Space Mono"), ui-monospace, monospace;
37
+
38
+ --radius-sm: var(--radius-sm, 3px);
39
+ --radius-md: var(--radius-md, 6px);
40
+ --radius-lg: var(--radius-lg, 12px);
41
+ --radius-full: var(--radius-full, 999px);
42
+
43
+ --shadow-sm: var(--shadow-sm, 0 1px 2px rgba(28, 38, 36, 0.06));
44
+ --shadow-md: var(--shadow-md, 0 6px 24px -8px rgba(28, 38, 36, 0.14));
45
+ --shadow-lg: var(--shadow-lg, 0 24px 60px -20px rgba(28, 38, 36, 0.22));
46
+
47
+ --container-max: var(--container-max, 1240px);
48
+ }
49
+
50
+ /* ---- base ---------------------------------------------------------------- */
51
+ @layer base {
52
+ :root {
53
+ /* Fluid modular type scale; overridden by injected --fs-* when present. */
54
+ color-scheme: light;
55
+ }
56
+
57
+ html {
58
+ font-family: var(--font-body);
59
+ color: var(--color-text);
60
+ background: var(--color-bg);
61
+ -webkit-font-smoothing: antialiased;
62
+ text-rendering: optimizeLegibility;
63
+ scroll-behavior: smooth;
64
+ }
65
+
66
+ body {
67
+ font-size: var(--fs-base, 1.0625rem);
68
+ line-height: 1.6;
69
+ letter-spacing: var(--type-tracking, -0.011em);
70
+ }
71
+
72
+ h1, h2, h3, h4, h5 {
73
+ font-family: var(--font-display);
74
+ font-weight: 600;
75
+ line-height: 1.05;
76
+ letter-spacing: -0.02em;
77
+ color: var(--color-text);
78
+ text-wrap: balance;
79
+ }
80
+
81
+ /* Body copy rhythm: a comfortable measure + paragraph spacing for prose-y
82
+ blocks that aren't inside .rte (e.g. editorial subheads). */
83
+ p { text-wrap: pretty; }
84
+
85
+ a {
86
+ color: inherit;
87
+ text-decoration: none;
88
+ }
89
+
90
+ /* Visible keyboard focus everywhere (WCAG 2.2 AA, brief §8). */
91
+ :focus-visible {
92
+ outline: var(--border-thick, 2px) solid var(--color-accent);
93
+ outline-offset: 2px;
94
+ border-radius: 2px;
95
+ }
96
+
97
+ ::selection {
98
+ background: var(--color-primary);
99
+ color: var(--color-primary-contrast);
100
+ }
101
+
102
+ img { max-width: 100%; height: auto; }
103
+
104
+ /* Honor reduced motion (brief §6/§8). */
105
+ @media (prefers-reduced-motion: reduce) {
106
+ *, *::before, *::after {
107
+ animation-duration: 0.001ms !important;
108
+ animation-iteration-count: 1 !important;
109
+ transition-duration: 0.001ms !important;
110
+ scroll-behavior: auto !important;
111
+ }
112
+ }
113
+ }
114
+
115
+ /* ---- shared component primitives ----------------------------------------- */
116
+ @layer components {
117
+ .container-prose {
118
+ width: 100%;
119
+ max-width: var(--container-max);
120
+ margin-inline: auto;
121
+ padding-inline: clamp(1rem, 4vw, 2.5rem);
122
+ }
123
+
124
+ .eyebrow {
125
+ font-family: var(--font-mono);
126
+ font-size: 0.72rem;
127
+ letter-spacing: 0.16em;
128
+ text-transform: uppercase;
129
+ color: var(--color-muted);
130
+ }
131
+
132
+ /* Eyebrow with the signature leading tick — a single short amber rule, the
133
+ "one brass rivet" motif from DESIGN.md, applied as a quiet structural mark.
134
+ Opt-in via .eyebrow.eyebrow--tick so it never fires where it'd read as noise. */
135
+ .eyebrow--tick {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ gap: 0.6em;
139
+ }
140
+ .eyebrow--tick::before {
141
+ content: "";
142
+ width: 1.6em;
143
+ height: 2px;
144
+ background: var(--color-accent);
145
+ flex: none;
146
+ }
147
+
148
+ /* Fluid display scale, derived from the tenant's emitted modular --fs-* steps
149
+ (the major-third scale in DESIGN.md). One rhythm across every page instead
150
+ of ad-hoc per-component clamps. min = small step, max = large step. */
151
+ .display-hero {
152
+ font-family: var(--font-display);
153
+ font-weight: 600;
154
+ line-height: 0.95;
155
+ letter-spacing: -0.03em;
156
+ font-size: clamp(var(--fs-3xl, 3.05rem), 8vw, var(--fs-5xl, 4.77rem));
157
+ }
158
+ .display-xl {
159
+ font-family: var(--font-display);
160
+ font-weight: 600;
161
+ line-height: 1.02;
162
+ letter-spacing: -0.025em;
163
+ font-size: clamp(var(--fs-2xl, 2.44rem), 5vw, var(--fs-4xl, 3.81rem));
164
+ }
165
+ .display-lg {
166
+ font-family: var(--font-display);
167
+ font-weight: 600;
168
+ line-height: 1.05;
169
+ letter-spacing: -0.02em;
170
+ font-size: clamp(var(--fs-xl, 1.95rem), 3.5vw, var(--fs-3xl, 3.05rem));
171
+ }
172
+ .display-md {
173
+ font-family: var(--font-display);
174
+ font-weight: 600;
175
+ line-height: 1.1;
176
+ letter-spacing: -0.018em;
177
+ font-size: clamp(var(--fs-lg, 1.56rem), 3vw, var(--fs-2xl, 2.44rem));
178
+ }
179
+
180
+ /* Tasteful scroll-reveal; opt-in via [data-reveal], JS toggles .is-in. */
181
+ [data-reveal] {
182
+ opacity: 0;
183
+ transform: translateY(14px);
184
+ transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
185
+ }
186
+ [data-reveal].is-in {
187
+ opacity: 1;
188
+ transform: none;
189
+ }
190
+ @media (prefers-reduced-motion: reduce) {
191
+ [data-reveal] { opacity: 1; transform: none; }
192
+ }
193
+
194
+ /* Island animations (MobileNav drawer slide-in). Globally neutralized by the
195
+ reduced-motion guard above; islands also guard in JS. */
196
+ @keyframes tot-slidein {
197
+ from { transform: translateX(100%); }
198
+ to { transform: translateX(0); }
199
+ }
200
+
201
+ /* Rich text from CMS/description. */
202
+ .rte > * + * { margin-top: 1em; }
203
+ .rte h2 { font-size: var(--fs-xl, 1.6rem); margin-top: 1.6em; }
204
+ .rte h3 { font-size: var(--fs-lg, 1.3rem); margin-top: 1.3em; }
205
+ .rte ul, .rte ol { padding-left: 1.4em; }
206
+ .rte ul { list-style: disc; }
207
+ .rte ol { list-style: decimal; }
208
+ .rte li + li { margin-top: 0.35em; }
209
+ .rte strong { font-weight: 600; }
210
+ .rte a { color: var(--color-accent-ink); text-decoration: underline; text-underline-offset: 3px; }
211
+
212
+ /* Accent text-link with an animated underline — restrained micro-interaction
213
+ for inline CTAs. Uses accent-ink (AA on light surfaces). */
214
+ .link-accent {
215
+ color: var(--color-text);
216
+ background-image: linear-gradient(var(--color-accent-ink), var(--color-accent-ink));
217
+ background-position: 0 100%;
218
+ background-repeat: no-repeat;
219
+ background-size: 100% 1.5px;
220
+ transition: color 0.2s ease, background-size 0.25s cubic-bezier(0.22, 1, 0.36, 1);
221
+ }
222
+ .link-accent:hover,
223
+ .link-accent:focus-visible {
224
+ color: var(--color-accent-ink);
225
+ }
226
+ @media (prefers-reduced-motion: no-preference) {
227
+ .link-accent { background-size: 0% 1.5px; }
228
+ .link-accent:hover,
229
+ .link-accent:focus-visible { background-size: 100% 1.5px; }
230
+ }
231
+
232
+ /* Collapsible PDP info sections (native <details>). Zero-JS, accessible. */
233
+ .accordion {
234
+ border-bottom: 1px solid var(--color-border);
235
+ }
236
+ .accordion__summary {
237
+ display: flex;
238
+ align-items: center;
239
+ justify-content: space-between;
240
+ gap: 1rem;
241
+ padding: 1rem 0;
242
+ cursor: pointer;
243
+ list-style: none;
244
+ font-family: var(--font-display);
245
+ font-weight: 500;
246
+ font-size: 0.95rem;
247
+ color: var(--color-text);
248
+ transition: color 0.15s ease;
249
+ }
250
+ .accordion__summary::-webkit-details-marker { display: none; }
251
+ .accordion__summary:hover { color: var(--color-primary); }
252
+ .accordion__chevron {
253
+ font-family: var(--font-mono);
254
+ font-size: 1.1rem;
255
+ line-height: 1;
256
+ color: var(--color-muted);
257
+ transition: transform 0.2s ease;
258
+ }
259
+ .accordion[open] .accordion__chevron { transform: rotate(45deg); }
260
+ .accordion__body { padding-bottom: 1.1rem; }
261
+
262
+ /* Wishlist saved state (robust, no JS text swap): fill the heart and relabel
263
+ when aria-pressed. Applies to card hearts and the PDP "Save for later". */
264
+ .wishlist-toggle[aria-pressed="true"] .wishlist-heart { fill: currentColor; }
265
+ .wishlist-toggle[aria-pressed="true"] .wl-label { font-size: 0; }
266
+ .wishlist-toggle[aria-pressed="true"] .wl-label::after {
267
+ content: "Saved";
268
+ font-size: 0.875rem;
269
+ }
270
+
271
+ /* Quick-view dialog entrance (reduced-motion neutralized by the base guard). */
272
+ .quickview[open] { animation: tot-pop 0.18s cubic-bezier(0.22, 1, 0.36, 1); }
273
+ @keyframes tot-pop {
274
+ from { opacity: 0; transform: translateY(8px) scale(0.98); }
275
+ to { opacity: 1; transform: none; }
276
+ }
277
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Reference theme — "Northwind" pilot identity. See DESIGN.md for full rationale.
3
+ *
4
+ * Thesis: a Pacific-Northwest maker of considered apparel + home goods.
5
+ * The voice is overcast-day calm with one sharp accent — like a single brass
6
+ * rivet on a waxed-canvas bag.
7
+ *
8
+ * Deliberate anti-AI-default moves (brief §6):
9
+ * - Palette is INK + SPRUCE on a warm GREIGE surface — NOT cream/terracotta,
10
+ * NOT near-black/acid-green. Sale red is a muted brick, not a siren.
11
+ * - Type pairing INVERTS the tired "serif headline" cliché: a tight geometric
12
+ * grotesque (Space Grotesk) carries display; a warm humanist serif
13
+ * (Source Serif 4) carries body copy. Personality up top, readability below.
14
+ * - Boldness is spent in ONE place: the homepage hero's oversized split-type
15
+ * lockup. Everything else stays quiet.
16
+ */
17
+ import type { ThemeTokens } from "@tot/public-runtime";
18
+
19
+ export const referenceTheme: ThemeTokens = {
20
+ color: {
21
+ bg: "#F4F1EA", // warm greige paper — NOT cream, lower chroma + cooler
22
+ surface: "#FCFAF5", // raised card surface, a touch lighter than bg
23
+ text: "#1C2624", // spruce-ink, near-black with a green undertone
24
+ muted: "#5C6663", // slate-sage for secondary text
25
+ primary: "#1F3A34", // deep spruce green — the brand anchor
26
+ "primary-contrast": "#F4F1EA",
27
+ accent: "#B5612F", // burnt amber / brass — the single warm spark
28
+ border: "#DCD6C9", // soft sand hairline (NOT broadsheet black)
29
+ success: "#3F6B4F",
30
+ sale: "#9E3B2E", // muted brick red, not fire-engine
31
+ },
32
+ type: {
33
+ display: { family: "Space Grotesk", weights: [500, 600, 700] },
34
+ body: { family: "Source Serif 4", weights: [400, 500, 600] },
35
+ mono: { family: "Space Mono", weights: [400] },
36
+ scale: 1.25, // major third — generous but not theatrical
37
+ tracking: "-0.011em",
38
+ },
39
+ space: {
40
+ base: 4,
41
+ scale: [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64],
42
+ containerMaxWidth: "1240px",
43
+ sectionRhythm: "clamp(4rem, 9vw, 8rem)",
44
+ },
45
+ shape: {
46
+ radius: { sm: "3px", md: "6px", lg: "12px", full: "999px" },
47
+ borderWeights: { thin: "1px", medium: "1.5px", thick: "2px" },
48
+ shadow: {
49
+ sm: "0 1px 2px rgba(28, 38, 36, 0.06)",
50
+ md: "0 6px 24px -8px rgba(28, 38, 36, 0.14)",
51
+ lg: "0 24px 60px -20px rgba(28, 38, 36, 0.22)",
52
+ },
53
+ },
54
+ brand: {
55
+ logoLight: "/brand/northwind-logo-light.svg",
56
+ logoDark: "/brand/northwind-logo-dark.svg",
57
+ favicon: "/favicon.svg",
58
+ ogImage: "/brand/northwind-og.svg",
59
+ motion: "full",
60
+ },
61
+ };
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Theme token -> CSS custom property serialization.
3
+ *
4
+ * Components are theme-AGNOSTIC: they only ever read var(--color-primary),
5
+ * var(--font-display), etc. This module turns a resolved ThemeTokens object
6
+ * into the `<style>` block the middleware injects per request, so one component
7
+ * library renders every tenant's identity. See DESIGN.md.
8
+ */
9
+ import type { DeepPartial, ThemeTokens } from "@tot/public-runtime";
10
+
11
+ /** Deep-merge tenant overrides over the reference defaults. */
12
+ export function mergeTheme(
13
+ base: ThemeTokens,
14
+ override?: DeepPartial<ThemeTokens>,
15
+ ): ThemeTokens {
16
+ if (!override) return base;
17
+ return deepMerge(base, override) as ThemeTokens;
18
+ }
19
+
20
+ function deepMerge<T>(base: T, over: DeepPartial<T>): T {
21
+ if (Array.isArray(base)) return (over as unknown as T) ?? base;
22
+ if (typeof base === "object" && base !== null) {
23
+ const out: Record<string, unknown> = { ...(base as Record<string, unknown>) };
24
+ for (const [k, v] of Object.entries(over as Record<string, unknown>)) {
25
+ if (v === undefined) continue;
26
+ const bv = (base as Record<string, unknown>)[k];
27
+ out[k] =
28
+ bv && typeof bv === "object" && !Array.isArray(bv) && typeof v === "object"
29
+ ? deepMerge(bv, v as DeepPartial<unknown>)
30
+ : v;
31
+ }
32
+ return out as T;
33
+ }
34
+ return (over as unknown as T) ?? base;
35
+ }
36
+
37
+ /**
38
+ * Serialize ThemeTokens into a `:root { --x: y }` declaration block.
39
+ * Returns the INNER CSS (no <style> wrapper) so the caller controls nonce/attrs.
40
+ */
41
+ export function themeToCssVars(t: ThemeTokens): string {
42
+ const lines: string[] = [];
43
+ const push = (name: string, value: string | number) =>
44
+ lines.push(` --${name}: ${value};`);
45
+
46
+ // Color
47
+ for (const [k, v] of Object.entries(t.color)) push(`color-${k}`, v);
48
+
49
+ // Type
50
+ push("font-display", `"${t.type.display.family}"`);
51
+ push("font-body", `"${t.type.body.family}"`);
52
+ if (t.type.mono) push("font-mono", `"${t.type.mono.family}"`);
53
+ push("type-scale", t.type.scale);
54
+ if (t.type.tracking) push("type-tracking", t.type.tracking);
55
+ // Emit a modular type scale (rem) from the ratio.
56
+ const ratio = t.type.scale;
57
+ const steps: Record<string, number> = {
58
+ "xs": -2, "sm": -1, "base": 0, "md": 1, "lg": 2,
59
+ "xl": 3, "2xl": 4, "3xl": 5, "4xl": 6, "5xl": 7,
60
+ };
61
+ for (const [name, step] of Object.entries(steps)) {
62
+ push(`fs-${name}`, `${(1 * Math.pow(ratio, step)).toFixed(4)}rem`);
63
+ }
64
+
65
+ // Space
66
+ push("space-base", `${t.space.base}px`);
67
+ t.space.scale.forEach((mult, i) =>
68
+ push(`space-${i}`, `${(t.space.base * mult).toFixed(2)}px`),
69
+ );
70
+ push("container-max", t.space.containerMaxWidth);
71
+ push("section-rhythm", t.space.sectionRhythm);
72
+
73
+ // Shape
74
+ push("radius-sm", t.shape.radius.sm);
75
+ push("radius-md", t.shape.radius.md);
76
+ push("radius-lg", t.shape.radius.lg);
77
+ push("radius-full", t.shape.radius.full);
78
+ push("border-thin", t.shape.borderWeights.thin);
79
+ push("border-medium", t.shape.borderWeights.medium);
80
+ push("border-thick", t.shape.borderWeights.thick);
81
+ push("shadow-sm", t.shape.shadow.sm);
82
+ push("shadow-md", t.shape.shadow.md);
83
+ push("shadow-lg", t.shape.shadow.lg);
84
+
85
+ return `:root{\n${lines.join("\n")}\n}`;
86
+ }
87
+
88
+ /** Google Fonts <link> for a theme's display+body+mono families (if Google-hosted). */
89
+ export function themeFontHref(t: ThemeTokens): string | null {
90
+ const families = new Map<string, number[]>();
91
+ families.set(t.type.display.family, t.type.display.weights);
92
+ families.set(t.type.body.family, t.type.body.weights);
93
+ if (t.type.mono) families.set(t.type.mono.family, t.type.mono.weights);
94
+
95
+ const params = [...families]
96
+ .filter(([fam]) => !SYSTEM_FONTS.has(fam))
97
+ .map(([fam, weights]) => {
98
+ const w = [...new Set(weights)].sort((a, b) => a - b).join(";");
99
+ return `family=${encodeURIComponent(fam).replace(/%20/g, "+")}:wght@${w}`;
100
+ });
101
+ if (!params.length) return null;
102
+ return `https://fonts.googleapis.com/css2?${params.join("&")}&display=swap`;
103
+ }
104
+
105
+ const SYSTEM_FONTS = new Set([
106
+ "system-ui",
107
+ "-apple-system",
108
+ "Arial",
109
+ "Helvetica",
110
+ "Georgia",
111
+ ]);