@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.
- package/LICENSE +58 -0
- package/README.md +40 -0
- package/apps/storefront/.dev.vars.example +41 -0
- package/apps/storefront/.env.example +24 -0
- package/apps/storefront/astro.config.mjs +245 -0
- package/apps/storefront/env.d.ts +155 -0
- package/apps/storefront/lib/ensure-preview-reconcile-secret.sh +31 -0
- package/apps/storefront/lib/resolve-cf-token.sh +48 -0
- package/apps/storefront/lib/resolve-tot-credentials.sh +69 -0
- package/apps/storefront/migrations/0000_baseline.sql +54 -0
- package/apps/storefront/migrations/0001_products_fts.sql +9 -0
- package/apps/storefront/migrations/README.md +54 -0
- package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
- package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
- package/apps/storefront/migrations/meta/_journal.json +20 -0
- package/apps/storefront/package.json +41 -0
- package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
- package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
- package/apps/storefront/public/brand/northwind-og.svg +13 -0
- package/apps/storefront/public/favicon.svg +5 -0
- package/apps/storefront/public/js/dashboard-team.js +147 -0
- package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
- package/apps/storefront/src/components/Badge.astro +34 -0
- package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
- package/apps/storefront/src/components/CollectionCard.astro +56 -0
- package/apps/storefront/src/components/EmptyState.astro +35 -0
- package/apps/storefront/src/components/Img.astro +68 -0
- package/apps/storefront/src/components/ProductCard.astro +228 -0
- package/apps/storefront/src/components/Section.astro +39 -0
- package/apps/storefront/src/components/Seo.astro +70 -0
- package/apps/storefront/src/components/WidgetFrame.astro +34 -0
- package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
- package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
- package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
- package/apps/storefront/src/components/chrome/NavDropdown.astro +116 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +181 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +181 -0
- package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
- package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
- package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
- package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
- package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
- package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
- package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
- package/apps/storefront/src/components/home/Hero.astro +164 -0
- package/apps/storefront/src/components/islands/AgeGate.tsx +105 -0
- package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
- package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
- package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
- package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
- package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
- package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
- package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
- package/apps/storefront/src/components/islands/VariantSelector.tsx +378 -0
- package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
- package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
- package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
- package/apps/storefront/src/components/marketing/Faq.astro +37 -0
- package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
- package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
- package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
- package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
- package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
- package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
- package/apps/storefront/src/components/marketing/Steps.astro +36 -0
- package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
- package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
- package/apps/storefront/src/components/nav/Footer.astro +148 -0
- package/apps/storefront/src/components/nav/Header.astro +190 -0
- package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
- package/apps/storefront/src/components/plp/FacetSidebar.astro +146 -0
- package/apps/storefront/src/components/plp/Pagination.astro +99 -0
- package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
- package/apps/storefront/src/config/devTenants.ts +16 -0
- package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
- package/apps/storefront/src/config/resolver.ts +46 -0
- package/apps/storefront/src/config/tenants.ts +193 -0
- package/apps/storefront/src/layouts/Layout.astro +303 -0
- package/apps/storefront/src/lib/analytics/index.ts +47 -0
- package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
- package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
- package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
- package/apps/storefront/src/lib/auth/loginGate.ts +107 -0
- package/apps/storefront/src/lib/auth/route.ts +146 -0
- package/apps/storefront/src/lib/auth/session.ts +203 -0
- package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
- package/apps/storefront/src/lib/basePath.ts +41 -0
- package/apps/storefront/src/lib/cfImage.ts +55 -0
- package/apps/storefront/src/lib/chrome/model.ts +65 -0
- package/apps/storefront/src/lib/colors.ts +97 -0
- package/apps/storefront/src/lib/content-edit/client.ts +230 -0
- package/apps/storefront/src/lib/content-edit/index.ts +14 -0
- package/apps/storefront/src/lib/content-edit/route.ts +25 -0
- package/apps/storefront/src/lib/content-edit/types.ts +19 -0
- package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
- package/apps/storefront/src/lib/d1/catalog.ts +289 -0
- package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
- package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
- package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
- package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
- package/apps/storefront/src/lib/demoGate.ts +113 -0
- package/apps/storefront/src/lib/dev/ai-widget-client.js +127 -0
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +132 -0
- package/apps/storefront/src/lib/diag/redact.ts +20 -0
- package/apps/storefront/src/lib/edgeCache.ts +128 -0
- package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
- package/apps/storefront/src/lib/env.ts +60 -0
- package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
- package/apps/storefront/src/lib/format.ts +29 -0
- package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
- package/apps/storefront/src/lib/imageAttrs.ts +55 -0
- package/apps/storefront/src/lib/jsonld.ts +136 -0
- package/apps/storefront/src/lib/maintenance.ts +71 -0
- package/apps/storefront/src/lib/memoryKv.ts +32 -0
- package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
- package/apps/storefront/src/lib/placeholder.ts +99 -0
- package/apps/storefront/src/lib/previewReload.ts +125 -0
- package/apps/storefront/src/lib/rawMarketingHtml.ts +133 -0
- package/apps/storefront/src/lib/search/index.ts +74 -0
- package/apps/storefront/src/lib/sections.ts +129 -0
- package/apps/storefront/src/lib/securityHeaders.ts +31 -0
- package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
- package/apps/storefront/src/lib/storyblok/provider.ts +524 -0
- package/apps/storefront/src/lib/tenantRouting.ts +91 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
- package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
- package/apps/storefront/src/lib/tot/query.ts +187 -0
- package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
- package/apps/storefront/src/lib/url.ts +119 -0
- package/apps/storefront/src/lib/wishlist.ts +63 -0
- package/apps/storefront/src/middleware/index.ts +413 -0
- package/apps/storefront/src/pages/404.astro +61 -0
- package/apps/storefront/src/pages/[...slug].astro +90 -0
- package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
- package/apps/storefront/src/pages/api/auth/send.ts +85 -0
- package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
- package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
- package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +64 -0
- package/apps/storefront/src/pages/api/csp-report.ts +25 -0
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
- package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
- package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +44 -0
- package/apps/storefront/src/pages/api/newsletter.ts +104 -0
- package/apps/storefront/src/pages/api/request-access.ts +172 -0
- package/apps/storefront/src/pages/auth/login.astro +149 -0
- package/apps/storefront/src/pages/auth/magic.astro +105 -0
- package/apps/storefront/src/pages/capabilities.astro +292 -0
- package/apps/storefront/src/pages/collections/[handle].astro +154 -0
- package/apps/storefront/src/pages/collections/index.astro +59 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
- package/apps/storefront/src/pages/dashboard/index.astro +160 -0
- package/apps/storefront/src/pages/dev.astro +653 -0
- package/apps/storefront/src/pages/index.astro +290 -0
- package/apps/storefront/src/pages/llms.txt.ts +92 -0
- package/apps/storefront/src/pages/products/[handle].astro +443 -0
- package/apps/storefront/src/pages/robots.txt.ts +24 -0
- package/apps/storefront/src/pages/saved.astro +35 -0
- package/apps/storefront/src/pages/search.astro +150 -0
- package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +646 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +85 -0
- package/apps/storefront/src/pages/style-guide/index.astro +94 -0
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
- package/apps/storefront/src/styles/global.css +277 -0
- package/apps/storefront/src/themes/reference.ts +61 -0
- package/apps/storefront/src/themes/schema.ts +111 -0
- package/apps/storefront/src/themes/tenants/index.ts +51 -0
- package/apps/storefront/tsconfig.json +16 -0
- package/apps/storefront/vitest.config.ts +22 -0
- package/apps/storefront/wrangler.toml +175 -0
- package/package.json +23 -0
- package/packages/public-runtime/LICENSE +58 -0
- package/packages/public-runtime/package.json +22 -0
- package/packages/public-runtime/src/binary-path.ts +68 -0
- package/packages/public-runtime/src/capabilities.ts +89 -0
- package/packages/public-runtime/src/catalog-api.ts +81 -0
- package/packages/public-runtime/src/catalog-d1.ts +192 -0
- package/packages/public-runtime/src/csp.ts +109 -0
- package/packages/public-runtime/src/customization-preview.ts +197 -0
- package/packages/public-runtime/src/customization-reconcile.ts +63 -0
- package/packages/public-runtime/src/customization-runtime.ts +157 -0
- package/packages/public-runtime/src/customization-scripts.ts +64 -0
- package/packages/public-runtime/src/customization-versioning.ts +95 -0
- package/packages/public-runtime/src/foxy.ts +184 -0
- package/packages/public-runtime/src/index.ts +30 -0
- package/packages/public-runtime/src/product.ts +159 -0
- package/packages/public-runtime/src/search.ts +45 -0
- package/packages/public-runtime/src/tenant-assets.ts +444 -0
- package/packages/public-runtime/src/tenant.ts +243 -0
- package/packages/public-runtime/tsconfig.json +8 -0
- package/packages/public-runtime/vitest.config.ts +8 -0
- package/pnpm-workspace.yaml +7 -0
- package/scripts/build/copy-tenant-assets.mjs +69 -0
- package/scripts/tot-dev.mjs +439 -0
- package/tenants/bigdvapor/content/blog.json +52 -0
- package/tenants/bigdvapor/content/chrome.html +149 -0
- package/tenants/bigdvapor/content/chrome.json +47 -0
- package/tenants/bigdvapor/content/home.html +463 -0
- package/tenants/bigdvapor/content/home.json +73 -0
- package/tenants/bigdvapor/content/pages/about.json +5 -0
- package/tenants/bigdvapor/content/pages/privacy.json +6 -0
- package/tenants/bigdvapor/content/pages/shipping-returns.json +6 -0
- package/tenants/bigdvapor/content/pages-html/blogs/news.html +86 -0
- package/tenants/bigdvapor/content/pages-html/pages/about-us.html +106 -0
- package/tenants/bigdvapor/content/pages-html/pages/contact-us.html +61 -0
- package/tenants/bigdvapor/content/pages-html/pages/privacy-policy.html +72 -0
- package/tenants/bigdvapor/content/pages-html/pages/shipping-returns.html +172 -0
- package/tenants/bigdvapor/content/themes/bigd-navy.json +109 -0
- package/tenants/bigdvapor/public/img/brands/adjust-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/again-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/al-fakher-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/arro-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/cookies-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/core-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/cyclone-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/dinner-lady-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/extre-bar-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/fifty-bar-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/firerose-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/flavor-beast-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/foger-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/kk-energy-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/kumi-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/lost-vape-dispoable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/melo-labs-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/brands/mixo-vape-disposable.webp +0 -0
- package/tenants/bigdvapor/public/img/brands/one-tank-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/oxbar-vape-disposable.webp +0 -0
- package/tenants/bigdvapor/public/img/brands/pillow-talk-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/pod-king-vape-disposable.jpg +0 -0
- package/tenants/bigdvapor/public/img/brands/raz-vape-disposable.png +0 -0
- package/tenants/bigdvapor/public/img/hero-texas.jpg +0 -0
- package/tenants/bigdvapor/public/img/og.jpg +0 -0
- package/tenants/bigdvapor/public/img/pages/AdobeStock_202106312_web.jpg +0 -0
- package/tenants/bigdvapor/public/img/pages/CREW_web.jpg +0 -0
- package/tenants/bigdvapor/public/img/pages/Hero-Image.jpg +0 -0
- package/tenants/bigdvapor/public/img/tiles/adjust.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/again.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/al-fakher.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/arro.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/cookies.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/core.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/cyclone.png +0 -0
- package/tenants/bigdvapor/public/img/tiles/disposables.svg +23 -0
- package/tenants/bigdvapor/public/logo-wordmark.png +0 -0
- package/tenants/bigdvapor/public/pages/home.css +118 -0
- package/tenants/bigdvapor/public/pages/mkt.css +185 -0
- package/tenants/bigdvapor/public/pages/page.css +148 -0
- package/tenants/bigdvapor/public/themes/bigd-navy.css +73 -0
- package/tenants/bigdvapor/theme.json +12 -0
- package/tenants/giantvapes/content/chrome.html +152 -0
- package/tenants/giantvapes/content/chrome.json +94 -0
- package/tenants/giantvapes/content/home.html +194 -0
- package/tenants/giantvapes/content/home.json +50 -0
- package/tenants/giantvapes/content/pages/about.json +10 -0
- package/tenants/giantvapes/content/pages/privacy.json +6 -0
- package/tenants/giantvapes/content/pages/shipping-returns.json +6 -0
- package/tenants/giantvapes/content/pages-html/blogs/news.html +68 -0
- package/tenants/giantvapes/content/pages-html/pages/about-us.html +95 -0
- package/tenants/giantvapes/content/pages-html/pages/contact-us.html +68 -0
- package/tenants/giantvapes/content/pages-html/pages/privacy-policy.html +65 -0
- package/tenants/giantvapes/content/pages-html/pages/shipping-returns.html +77 -0
- package/tenants/giantvapes/content/themes/giant-navy.json +73 -0
- package/tenants/giantvapes/public/img/hero-suicide-bunny.jpg +0 -0
- package/tenants/giantvapes/public/img/hero.webp +0 -0
- package/tenants/giantvapes/public/img/og.jpg +0 -0
- package/tenants/giantvapes/public/logo-wordmark.png +0 -0
- package/tenants/giantvapes/public/pages/home.css +120 -0
- package/tenants/giantvapes/public/pages/mkt.css +185 -0
- package/tenants/giantvapes/public/pages/page.css +155 -0
- package/tenants/giantvapes/public/themes/giant-navy.css +76 -0
- package/tenants/giantvapes/theme.json +39 -0
- package/tenants/home/content/chrome.json +11 -0
- package/tenants/home/content/home.html +304 -0
- package/tenants/home/content/home.json +13 -0
- package/tenants/home/public/js/storefront.js +210 -0
- package/tenants/home/public/logo-wordmark.png +0 -0
- package/tenants/home/public/pages/storefront.css +212 -0
- package/tenants/home/theme.json +6 -0
- package/tenants/tokenoftrust/.tot/config.json +9 -0
- package/tenants/tokenoftrust/content/chrome.json +55 -0
- package/tenants/tokenoftrust/content/home.html +723 -0
- package/tenants/tokenoftrust/content/home.json +233 -0
- package/tenants/tokenoftrust/content/pages-html/careers.html +231 -0
- package/tenants/tokenoftrust/content/pages-html/company.html +275 -0
- package/tenants/tokenoftrust/content/pages-html/contact/contact-sales.html +188 -0
- package/tenants/tokenoftrust/content/pages-html/contact/startup-apply.html +206 -0
- package/tenants/tokenoftrust/content/pages-html/contact.html +178 -0
- package/tenants/tokenoftrust/content/pages-html/industries/adult-content-age-verification.html +286 -0
- package/tenants/tokenoftrust/content/pages-html/industries/cannabis.html +277 -0
- package/tenants/tokenoftrust/content/pages-html/pact-act-pricing.html +289 -0
- package/tenants/tokenoftrust/content/pages-html/pricing.html +483 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-estimation.html +278 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-gate.html +279 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance/age-verification.html +265 -0
- package/tenants/tokenoftrust/content/pages-html/product/age-assurance.html +173 -0
- package/tenants/tokenoftrust/content/pages-html/product/compliance/aml.html +214 -0
- package/tenants/tokenoftrust/content/pages-html/product/compliance/pact-act.html +226 -0
- package/tenants/tokenoftrust/content/pages-html/product/fraud-prevention.html +254 -0
- package/tenants/tokenoftrust/content/pages-html/product/social-profile-verification.html +212 -0
- package/tenants/tokenoftrust/content/pages-html/product/tax/excise-tax.html +213 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/aml.html +259 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/biometric-selfie.html +305 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/coverage.html +254 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/document-verification.html +306 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/electronic-identity-verification.html +304 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification/government-id-verification.html +308 -0
- package/tenants/tokenoftrust/content/pages-html/product/verification.html +183 -0
- package/tenants/tokenoftrust/content/pages-html/product.html +228 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations/bigcommerce-integration.html +220 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations/shopify-integration.html +221 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations/wordpress.html +220 -0
- package/tenants/tokenoftrust/content/pages-html/resources/integrations.html +172 -0
- package/tenants/tokenoftrust/content/pages-html/resources/refer-a-friend-program.html +317 -0
- package/tenants/tokenoftrust/content/pages-html/resources.html +177 -0
- package/tenants/tokenoftrust/content/pages-html/reviews.html +273 -0
- package/tenants/tokenoftrust/content/pages-html/solutions.html +292 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-alcohol.html +289 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-cigars-and-tobacco.html +286 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-firearm-accessories.html +267 -0
- package/tenants/tokenoftrust/content/pages-html/vlp-peptides.html +273 -0
- package/tenants/tokenoftrust/content/themes/tot-navy.json +88 -0
- package/tenants/tokenoftrust/public/favicon.png +0 -0
- package/tenants/tokenoftrust/public/logo-icon.png +0 -0
- package/tenants/tokenoftrust/public/logo-wordmark.png +0 -0
- package/tenants/tokenoftrust/public/pages/company.css +108 -0
- package/tenants/tokenoftrust/public/pages/home.css +457 -0
- package/tenants/tokenoftrust/public/pages/mkt.css +482 -0
- package/tenants/tokenoftrust/public/pages/pricing.css +214 -0
- package/tenants/tokenoftrust/public/pages/solutions.css +66 -0
- package/tenants/tokenoftrust/public/themes/tot-navy-1.0.0.css +136 -0
- package/tenants/tokenoftrust/public/themes/tot-navy.css +358 -0
- package/tenants/tokenoftrust/theme.json +31 -0
- 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
|
+
]);
|