@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,653 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* /dev — the developer's getting-started home (what a granted developer lands on after
|
|
4
|
+
* sign-in). Deliberately NOT the operator/reconcile control plane: a small, on-brand view
|
|
5
|
+
* of the shipped first-run story — one command to a running store, edit-or-ask to change
|
|
6
|
+
* it, then a separate, gated "submit for review" step with a real compliance verdict.
|
|
7
|
+
*
|
|
8
|
+
* Brand: this page is a STANDALONE document (it bypasses Layout.astro), but it mirrors the
|
|
9
|
+
* marketing home's design system directly — the tokenoftrust tenant theme (tenants/
|
|
10
|
+
* tokenoftrust/theme.json): navy ink #0A2540 + trust teal #0E9F8E on cool-white #F7F9FC,
|
|
11
|
+
* Plus Jakarta Sans / Inter / JetBrains Mono, radius-16 cards, and the home hero's
|
|
12
|
+
* signature "live green-check status card" — reused here to reflect the developer's own
|
|
13
|
+
* first run. Same CSP as every storefront response (googleapis/gstatic are allowlisted, so
|
|
14
|
+
* the shared typefaces load); inline style/script carry this request's nonce.
|
|
15
|
+
*
|
|
16
|
+
* Value-first ordering mirrors the CLI's `tot start`: the running store comes first;
|
|
17
|
+
* connecting Claude is the supercharge offered after the aha, not a gate. Copy is honest to
|
|
18
|
+
* what shipped — no Docker prerequisite, no sub-second-HMR claim (checkout save→reload is a
|
|
19
|
+
* ~2–4s full page reload).
|
|
20
|
+
*/
|
|
21
|
+
import { readViewerSession } from "@/lib/auth/route";
|
|
22
|
+
import { staticTenants } from "@/config/tenants";
|
|
23
|
+
|
|
24
|
+
export const prerender = false;
|
|
25
|
+
const cspNonce = Astro.locals.cspNonce;
|
|
26
|
+
const url = new URL(Astro.request.url);
|
|
27
|
+
|
|
28
|
+
const session = await readViewerSession({ cookies: Astro.cookies });
|
|
29
|
+
const signedIn = !!session;
|
|
30
|
+
// The live demo calls content_edit as the storefront operator, but it's gated behind the
|
|
31
|
+
// developer's own session + capability (same gate the /api endpoint enforces). Mirror that
|
|
32
|
+
// here so a logged-out / no-capability visitor sees an intentional teaser, never a 401.
|
|
33
|
+
const canUseAI = signedIn && !!session?.capability && session.capability !== "none";
|
|
34
|
+
|
|
35
|
+
// Resolve the developer's tenant from their first membership (or ?tenant= for preview).
|
|
36
|
+
const wanted = (url.searchParams.get("tenant") ?? session?.tenants?.[0]?.resource ?? "").toLowerCase();
|
|
37
|
+
const tenant =
|
|
38
|
+
(wanted
|
|
39
|
+
? staticTenants.find((t) =>
|
|
40
|
+
[t.appDomain, t.tenant_id, t.canonicalDomain, ...t.domains].some(
|
|
41
|
+
(d) => d?.toLowerCase() === wanted,
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
: null) ??
|
|
45
|
+
staticTenants.find((t) => t.tenant_id === "tokenoftrust") ??
|
|
46
|
+
staticTenants[0];
|
|
47
|
+
|
|
48
|
+
const appDomain = tenant?.appDomain ?? "tokenoftrust.com";
|
|
49
|
+
const dir = appDomain; // clone lands in a dir named after the tenant
|
|
50
|
+
const localUrl = `http://localhost:4321/${appDomain}/`;
|
|
51
|
+
const firstName = session?.email?.split("@")[0]?.split(/[.+]/)[0] ?? null;
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<html lang="en">
|
|
55
|
+
<head>
|
|
56
|
+
<meta charset="utf-8" />
|
|
57
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
58
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
59
|
+
<title>Ship your first change — {appDomain}</title>
|
|
60
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
61
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
62
|
+
<link
|
|
63
|
+
rel="stylesheet"
|
|
64
|
+
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700;800&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
65
|
+
/>
|
|
66
|
+
<style nonce={cspNonce}>
|
|
67
|
+
:root {
|
|
68
|
+
/* home tokens — tenants/tokenoftrust/theme.json */
|
|
69
|
+
--bg: #f7f9fc; --surface: #fff; --ink: #0a2540; --ink-2: #33415b; --muted: #55637a;
|
|
70
|
+
--accent: #0e9f8e; --accent-ink: #0b7a6d; --accent-wash: #e7f6f4; --accent-line: #b8e6df;
|
|
71
|
+
--border: #e3e8ef; --warn: #c23b3b; --warn-wash: #fbecec; --warn-line: #f0cccc;
|
|
72
|
+
/* dark insets — the terminals, on-brand (navy, not warm) */
|
|
73
|
+
--term: #0a1a2f; --term-fg: #dbe6f2; --term-dim: #5f7189; --term-prompt: #2dd4bf;
|
|
74
|
+
--term-ok: #34d399; --term-warn: #fbbf24; --term-line: #14263f;
|
|
75
|
+
/* Claude window — a warmer teal-navy, distinct but same family */
|
|
76
|
+
--cl-bg: #0c2233; --cl-fg: #e6eef5; --cl-dim: #6d879c; --cl-prompt: #35cfbd;
|
|
77
|
+
--cl-line: #163247; --cl-ok: #34d399;
|
|
78
|
+
--r-sm: 4px; --r-md: 8px; --r-lg: 16px; --r-full: 999px;
|
|
79
|
+
--sh-md: 0 6px 24px -8px rgba(10, 37, 64, .14);
|
|
80
|
+
--sh-lg: 0 24px 60px -22px rgba(10, 37, 64, .22);
|
|
81
|
+
--display: "Plus Jakarta Sans", system-ui, sans-serif;
|
|
82
|
+
--body: "Inter", system-ui, sans-serif;
|
|
83
|
+
--mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
84
|
+
color-scheme: light;
|
|
85
|
+
}
|
|
86
|
+
* { box-sizing: border-box; }
|
|
87
|
+
html { -webkit-text-size-adjust: 100%; }
|
|
88
|
+
body {
|
|
89
|
+
margin: 0; background: var(--bg); color: var(--ink-2);
|
|
90
|
+
font: 400 16px/1.6 var(--body); letter-spacing: -.011em;
|
|
91
|
+
padding: clamp(1.25rem, 4vw, 3rem) 1.25rem 5rem;
|
|
92
|
+
-webkit-font-smoothing: antialiased;
|
|
93
|
+
/* faint teal wash top-left, echoing the home hero backdrop */
|
|
94
|
+
background-image: radial-gradient(60rem 32rem at 12% -8%, #e9f6f3 0%, transparent 60%);
|
|
95
|
+
background-repeat: no-repeat;
|
|
96
|
+
}
|
|
97
|
+
.wrap { max-width: 61rem; margin: 0 auto; }
|
|
98
|
+
.col { max-width: 44rem; }
|
|
99
|
+
.mono { font-family: var(--mono); }
|
|
100
|
+
h1, h2, h3 { font-family: var(--display); color: var(--ink); letter-spacing: -.02em; margin: 0; }
|
|
101
|
+
strong { font-weight: 600; color: var(--ink); }
|
|
102
|
+
code, .inline { font-family: var(--mono); font-size: .86em; }
|
|
103
|
+
|
|
104
|
+
/* --- eyebrow with the leading teal tick (home's structural motif) --- */
|
|
105
|
+
.eyebrow { display: inline-flex; align-items: center; gap: .6em; font: 600 .72rem/1 var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--muted); margin: 0 0 1.1rem; }
|
|
106
|
+
.eyebrow::before { content: ""; width: 1.6em; height: 2px; background: var(--accent); flex: none; }
|
|
107
|
+
.eyebrow b { color: var(--muted); font-weight: 600; }
|
|
108
|
+
|
|
109
|
+
/* --- hero --- */
|
|
110
|
+
.hero { display: grid; gap: clamp(2rem, 5vw, 3.4rem); align-items: start; margin: 0 0 3.25rem; }
|
|
111
|
+
@media (min-width: 52rem) { .hero { grid-template-columns: 1.08fr .92fr; align-items: center; } }
|
|
112
|
+
h1 { font-size: clamp(2rem, 5.6vw, 3.05rem); line-height: 1.02; font-weight: 800; letter-spacing: -.032em; margin: 0 0 .7rem; text-wrap: balance; }
|
|
113
|
+
h1 .accent { color: var(--accent); }
|
|
114
|
+
.lede { font-size: 1.09rem; line-height: 1.55; color: var(--ink-2); margin: 0 0 1.5rem; max-width: 33rem; }
|
|
115
|
+
.lede code { color: var(--accent-ink); }
|
|
116
|
+
|
|
117
|
+
.wins { list-style: none; padding: 0; margin: 0; display: grid; gap: .5rem; max-width: 33rem; }
|
|
118
|
+
.wins li { display: flex; gap: .55rem; align-items: baseline; font-size: .92rem; color: var(--ink-2); }
|
|
119
|
+
.wins .tick { flex: none; color: var(--accent); font-weight: 700; }
|
|
120
|
+
.wins .wtag { font-weight: 600; color: var(--ink); }
|
|
121
|
+
|
|
122
|
+
/* --- status card: the signature, mirroring the home hero's live card --- */
|
|
123
|
+
.status { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--sh-lg); overflow: hidden; }
|
|
124
|
+
.status-bar { display: flex; align-items: center; gap: .5rem; padding: .7rem .9rem; border-bottom: 1px solid var(--border); background: #fbfcfe; }
|
|
125
|
+
.status-bar .host { flex: 1; font: 500 .74rem/1 var(--mono); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
126
|
+
.livepill { display: inline-flex; align-items: center; gap: .35rem; font: 600 .64rem/1 var(--mono); letter-spacing: .04em; text-transform: uppercase; color: var(--accent-ink); background: var(--accent-wash); border: 1px solid var(--accent-line); border-radius: var(--r-full); padding: .3rem .5rem; }
|
|
127
|
+
.livepill .blip { width: .42rem; height: .42rem; border-radius: 50%; background: var(--accent); }
|
|
128
|
+
.status-row { display: flex; align-items: center; gap: .7rem; padding: .72rem .95rem; border-bottom: 1px solid #eef1f6; font-size: .9rem; }
|
|
129
|
+
.status-row:last-of-type { border-bottom: 0; }
|
|
130
|
+
.status-row .ic { flex: none; width: 1.15rem; height: 1.15rem; display: grid; place-items: center; border-radius: 50%; font: 700 .72rem/1 var(--body); }
|
|
131
|
+
.status-row .ic.ok { color: var(--accent-ink); background: var(--accent-wash); }
|
|
132
|
+
.status-row .ic.warn { color: var(--warn); background: var(--warn-wash); }
|
|
133
|
+
.status-row .k { flex: 1; color: var(--ink); font-weight: 500; }
|
|
134
|
+
.status-row .v { font: 500 .78rem/1 var(--mono); color: var(--muted); }
|
|
135
|
+
.status-row .v.warn { color: var(--warn); }
|
|
136
|
+
.status-foot { padding: .7rem .95rem; font: 500 .72rem/1.4 var(--mono); color: var(--muted); background: #fbfcfe; }
|
|
137
|
+
|
|
138
|
+
/* --- sign-in (inline, on-brand) --- */
|
|
139
|
+
.signin { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--sh-md); padding: 1.15rem 1.4rem; margin: 0 0 2.75rem; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .7rem 1.4rem; }
|
|
140
|
+
.signin p { margin: 0; color: var(--ink-2); font-size: .95rem; }
|
|
141
|
+
.signin a, .cta { flex: none; display: inline-flex; align-items: center; gap: .4rem; background: var(--accent); color: #fff; text-decoration: none; font: 600 .95rem/1 var(--display); padding: .68rem 1.15rem; border-radius: var(--r-full); transition: transform .18s ease, background .18s ease; }
|
|
142
|
+
.signin a:hover, .cta:hover { background: var(--accent-ink); transform: translateY(-1px); }
|
|
143
|
+
|
|
144
|
+
/* --- fast-path terminal sign-in (first-class: bring your web sign-in to the terminal) ---
|
|
145
|
+
A teal-ringed white card, elevated from the plain `tot start` terminal, that reads as
|
|
146
|
+
THE accelerator for a dev already signed in here (arrived via the magic link). The
|
|
147
|
+
copy-the-whole-command button is the primary affordance — typing is never the easy path. */
|
|
148
|
+
.fastpath { background: var(--surface); border: 1px solid var(--accent-line); border-radius: var(--r-lg); box-shadow: var(--sh-md); padding: 1.2rem 1.4rem 1.35rem; margin: 1.4rem 0 0; }
|
|
149
|
+
.fastpath-eyebrow { display: inline-flex; align-items: center; gap: .45rem; font: 600 .66rem/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--accent-ink); background: var(--accent-wash); border: 1px solid var(--accent-line); border-radius: var(--r-full); padding: .3rem .55rem; margin: 0 0 .75rem; }
|
|
150
|
+
.fastpath-eyebrow .blip { width: .42rem; height: .42rem; border-radius: 50%; background: var(--accent); }
|
|
151
|
+
.fastpath h3 { font-size: 1.06rem; line-height: 1.2; margin: 0 0 .4rem; }
|
|
152
|
+
.fastpath-lede { margin: 0 0 1rem; color: var(--ink-2); font-size: .95rem; }
|
|
153
|
+
.fastpath-lede code { font-family: var(--mono); font-size: .86em; background: #eef1f6; border-radius: var(--r-sm); padding: .08rem .34rem; color: var(--ink); }
|
|
154
|
+
.fastpath .term-shell { margin: 0 0 1rem; }
|
|
155
|
+
.fastpath-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .7rem 1.1rem; }
|
|
156
|
+
.fastpath .cta { border: 0; cursor: pointer; min-width: 12.5rem; justify-content: center; }
|
|
157
|
+
.fastpath .cta:disabled { opacity: .7; cursor: default; transform: none; }
|
|
158
|
+
.fastpath-note { flex: 1; min-width: 13rem; margin: 0; font: 500 .8rem/1.45 var(--mono); color: var(--muted); }
|
|
159
|
+
.fastpath-note em { color: var(--ink-2); font-style: normal; font-weight: 600; }
|
|
160
|
+
|
|
161
|
+
/* --- steps --- */
|
|
162
|
+
.step { margin: 0 0 2.75rem; }
|
|
163
|
+
.label { display: flex; align-items: center; gap: .6rem; font: 600 .74rem/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin: 0 0 .85rem; }
|
|
164
|
+
.label .n { width: 1.55rem; height: 1.55rem; border-radius: 50%; background: var(--accent-wash); color: var(--accent-ink); border: 1px solid var(--accent-line); display: grid; place-items: center; font: 700 .82rem/1 var(--display); letter-spacing: 0; flex: none; }
|
|
165
|
+
.step h2 { font-size: 1.24rem; line-height: 1.15; margin: 0 0 .35rem; }
|
|
166
|
+
.step p { margin: 0 0 .9rem; color: var(--ink-2); }
|
|
167
|
+
.step p code, .inline { background: #eef1f6; border-radius: var(--r-sm); padding: .08rem .34rem; color: var(--ink); }
|
|
168
|
+
|
|
169
|
+
/* --- terminal (dark navy inset) --- */
|
|
170
|
+
.term { background: var(--term); border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--sh-lg); }
|
|
171
|
+
.term-bar { display: flex; align-items: center; gap: .45rem; padding: .7rem .9rem; border-bottom: 1px solid var(--term-line); }
|
|
172
|
+
.dot { width: .7rem; height: .7rem; border-radius: 50%; }
|
|
173
|
+
.dot.r { background: #ff5f56; } .dot.y { background: #ffbd2e; } .dot.g { background: #27c93f; }
|
|
174
|
+
.term-title { margin-left: .5rem; font: 500 .72rem/1 var(--mono); color: var(--term-dim); }
|
|
175
|
+
.term pre { margin: 0; padding: 1rem 1.1rem 1.15rem; overflow-x: auto; }
|
|
176
|
+
.term code { font-family: var(--mono); font-size: .83rem; line-height: 1.85; color: var(--term-fg); white-space: pre; }
|
|
177
|
+
.cmt { color: var(--term-dim); }
|
|
178
|
+
.prompt { color: var(--term-prompt); }
|
|
179
|
+
.arrow { color: #7f93ab; }
|
|
180
|
+
.vok { color: var(--term-ok); }
|
|
181
|
+
.vwarn { color: var(--term-warn); }
|
|
182
|
+
.vhint { color: #cbd7e6; }
|
|
183
|
+
.copy { position: absolute; top: .55rem; right: .6rem; z-index: 2; border: 1px solid var(--term-line); border-radius: var(--r-sm); background: #10233c; color: #93a6bf; font: 600 .68rem/1 var(--mono); padding: .4rem .55rem; cursor: pointer; }
|
|
184
|
+
.copy:hover { background: #16304f; color: #cfe0ee; }
|
|
185
|
+
.term-shell { position: relative; }
|
|
186
|
+
.dir { color: #7f93ab; }
|
|
187
|
+
|
|
188
|
+
/* --- Claude window (warm teal-navy, distinct from the shell) --- */
|
|
189
|
+
.claude { background: var(--cl-bg); border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--sh-lg); }
|
|
190
|
+
.claude .term-bar { border-bottom: 1px solid var(--cl-line); }
|
|
191
|
+
.claude .term-title { color: var(--cl-dim); margin-left: .15rem; }
|
|
192
|
+
.claude pre { margin: 0; padding: 1rem 1.1rem 1.15rem; overflow-x: auto; }
|
|
193
|
+
.claude code { font-family: var(--mono); font-size: .83rem; line-height: 1.85; color: var(--cl-fg); white-space: pre; }
|
|
194
|
+
.claude .prompt { color: var(--cl-prompt); font-weight: 600; }
|
|
195
|
+
.claude .cmt { color: var(--cl-dim); }
|
|
196
|
+
.claude .ok { color: var(--cl-ok); }
|
|
197
|
+
.claude .mcp { color: var(--cl-prompt); font-weight: 600; }
|
|
198
|
+
.claude .you { color: #cbdaea; }
|
|
199
|
+
.spark { color: var(--cl-prompt); font-size: .82rem; line-height: 1; }
|
|
200
|
+
.follows { margin-top: .9rem; }
|
|
201
|
+
|
|
202
|
+
/* --- iteration demo (mini browser) --- */
|
|
203
|
+
.demo { margin-top: 1rem; display: grid; grid-template-columns: 1fr; gap: .9rem; }
|
|
204
|
+
@media (min-width: 40rem) { .demo { grid-template-columns: 1.15fr 1fr; align-items: stretch; } }
|
|
205
|
+
.browser { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; display: flex; flex-direction: column; box-shadow: var(--sh-md); }
|
|
206
|
+
.browser-bar { display: flex; align-items: center; gap: .5rem; padding: .55rem .75rem; border-bottom: 1px solid var(--border); background: #fbfcfe; }
|
|
207
|
+
.browser-bar .dot { width: .58rem; height: .58rem; }
|
|
208
|
+
.url { flex: 1; font: 500 .72rem/1 var(--mono); color: var(--muted); background: #fff; border: 1px solid var(--border); border-radius: var(--r-sm); padding: .4rem .55rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
209
|
+
.stage { padding: 1.4rem 1.1rem 1.6rem; flex: 1; display: flex; flex-direction: column; justify-content: center; transition: background .5s ease; }
|
|
210
|
+
.stage .brandline { font: 600 .62rem/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--accent-ink); margin: 0 0 .5rem; transition: color .5s ease; }
|
|
211
|
+
.stage .title { font: 800 1.35rem/1.1 var(--display); letter-spacing: -.02em; color: var(--ink); margin: 0; transition: color .5s ease; }
|
|
212
|
+
.reload { display: inline-flex; align-items: center; gap: .35rem; margin-top: .9rem; font: 600 .68rem/1 var(--mono); color: var(--accent-ink); background: var(--accent-wash); border: 1px solid var(--accent-line); border-radius: var(--r-full); padding: .32rem .55rem; width: max-content; opacity: 0; }
|
|
213
|
+
.caption { font-size: .84rem; color: var(--muted); margin: .6rem 0 0; }
|
|
214
|
+
.caption em { color: var(--ink-2); font-style: normal; font-weight: 600; }
|
|
215
|
+
|
|
216
|
+
/* the AI reskin preview — the same mini-browser, gone dark */
|
|
217
|
+
.stage.dark { background: #0c2233; }
|
|
218
|
+
.stage.dark .brandline { color: var(--cl-prompt); }
|
|
219
|
+
.stage.dark .title { color: #eaf1f7; }
|
|
220
|
+
.reskin { display: inline-flex; align-items: center; gap: .35rem; margin-top: .9rem; font: 600 .68rem/1 var(--mono); color: #eaf1f7; background: #14324a; border: 1px solid #21445f; border-radius: var(--r-full); padding: .32rem .55rem; width: max-content; }
|
|
221
|
+
|
|
222
|
+
/* the loop, looping: on view, the save→reload beat replays continuously —
|
|
223
|
+
type the edit in, the title reloads (fade + flash + badge), hold, erase,
|
|
224
|
+
repeat, on a calm ~6s cycle. steps(90) is high so the 5–22% typing window
|
|
225
|
+
still lands ~15 discrete "keystrokes"; the smooth beats stay on ease. */
|
|
226
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
227
|
+
.demo.play .typed { animation: typeloop 6s steps(90) 0s infinite; }
|
|
228
|
+
.demo.play .title { animation: swaploop 6s ease-in-out 0s infinite; }
|
|
229
|
+
.demo.play .hl { animation: flashloop 6s ease-in-out 0s infinite; }
|
|
230
|
+
.demo.play .reload { animation: poploop 6s ease-in-out 0s infinite; }
|
|
231
|
+
}
|
|
232
|
+
@keyframes typeloop { 0%,5% { width: 0; } 22% { width: 100%; } 80% { width: 100%; } 90%,100% { width: 0; } }
|
|
233
|
+
@keyframes swaploop { 0%,20% { opacity: 1; } 24% { opacity: .35; } 30%,100% { opacity: 1; } }
|
|
234
|
+
@keyframes flashloop { 0%,22% { background: transparent; } 28% { background: #d9f3ee; } 44%,100% { background: transparent; } }
|
|
235
|
+
@keyframes poploop {
|
|
236
|
+
0%,24% { opacity: 0; transform: translateY(3px); }
|
|
237
|
+
32%,76% { opacity: 1; transform: none; }
|
|
238
|
+
86%,100% { opacity: 0; transform: translateY(3px); }
|
|
239
|
+
}
|
|
240
|
+
.hl { border-radius: 3px; padding: 0 .12em; }
|
|
241
|
+
.typed { display: inline-block; overflow: hidden; white-space: nowrap; vertical-align: bottom; }
|
|
242
|
+
|
|
243
|
+
/* --- connect + platforms notes --- */
|
|
244
|
+
.platforms { margin: .9rem 0 0; font-size: .85rem; color: var(--muted); }
|
|
245
|
+
.platforms code { font-family: var(--mono); font-size: .86em; background: #eef1f6; border-radius: var(--r-sm); padding: .08rem .34rem; color: var(--ink); }
|
|
246
|
+
.platforms a { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 2px; }
|
|
247
|
+
|
|
248
|
+
/* --- ask form (live demo) --- */
|
|
249
|
+
.ask-term { display: flex; align-items: center; gap: .5rem; padding: .8rem .9rem; }
|
|
250
|
+
.ask-prompt { color: var(--term-prompt); font-weight: 700; }
|
|
251
|
+
.ask-input { flex: 1; min-width: 0; background: transparent; border: none; outline: none; color: var(--term-fg); font-family: var(--mono); font-size: .84rem; }
|
|
252
|
+
.ask-input::placeholder { color: var(--term-dim); }
|
|
253
|
+
.ask-input:disabled { color: var(--term-dim); }
|
|
254
|
+
.ask-submit { flex: none; border: 1px solid var(--term-line); border-radius: var(--r-sm); background: #10233c; color: #93a6bf; font: 600 .72rem/1 var(--mono); padding: .42rem .8rem; cursor: pointer; }
|
|
255
|
+
.ask-submit:hover:not(:disabled) { background: #16304f; color: #cfe0ee; }
|
|
256
|
+
.ask-submit:disabled { opacity: .5; cursor: default; }
|
|
257
|
+
.ask-term.busy .ask-prompt { animation: blink 1s steps(2) infinite; }
|
|
258
|
+
@keyframes blink { 50% { opacity: .35; } }
|
|
259
|
+
#ai-demo-log { min-height: 2.5rem; }
|
|
260
|
+
.ask-gate { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .8rem; margin-top: .7rem; font-size: .86rem; color: var(--muted); }
|
|
261
|
+
.ask-gate a { color: var(--accent-ink); font-weight: 600; text-decoration: none; border-bottom: 1px solid var(--accent-line); }
|
|
262
|
+
.ask-gate a:hover { border-bottom-color: var(--accent); }
|
|
263
|
+
|
|
264
|
+
/* --- submit (a white card + teal chip; the verdict, not a warm band) --- */
|
|
265
|
+
.submit { margin-top: 3.5rem; border: 1px solid var(--border); background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh-md); padding: clamp(1.3rem, 4vw, 2rem); }
|
|
266
|
+
.submit h2 { font-size: 1.24rem; margin: 0 0 .35rem; }
|
|
267
|
+
.submit p { color: var(--ink-2); margin: 0 0 .9rem; }
|
|
268
|
+
.submit p code { background: #eef1f6; border-radius: var(--r-sm); padding: .08rem .34rem; color: var(--ink); }
|
|
269
|
+
.gatechip { display: inline-flex; align-items: center; gap: .4rem; font: 600 .68rem/1 var(--mono); text-transform: uppercase; letter-spacing: .08em; color: var(--accent-ink); background: var(--accent-wash); border: 1px solid var(--accent-line); border-radius: var(--r-full); padding: .35rem .6rem; margin-bottom: .9rem; }
|
|
270
|
+
|
|
271
|
+
.foot { margin-top: 3.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border); font-size: .85rem; color: var(--muted); display: flex; flex-wrap: wrap; gap: .35rem 1.25rem; }
|
|
272
|
+
.foot a { color: var(--accent-ink); text-decoration: none; }
|
|
273
|
+
.foot a:hover { text-decoration: underline; }
|
|
274
|
+
|
|
275
|
+
a:focus-visible, button:focus-visible, input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
276
|
+
</style>
|
|
277
|
+
</head>
|
|
278
|
+
<body>
|
|
279
|
+
<main class="wrap">
|
|
280
|
+
<!-- HERO — copy left, the live status card right (mirrors the marketing home hero) -->
|
|
281
|
+
<section class="hero">
|
|
282
|
+
<div>
|
|
283
|
+
<p class="eyebrow">Regulated Storefront <b>·</b> Developer</p>
|
|
284
|
+
<h1>{firstName ? <>Welcome, {firstName}. </> : null}Ship your first change to <span class="accent">{appDomain}</span>.</h1>
|
|
285
|
+
<p class="lede">Copy the command below into your terminal to get going.</p>
|
|
286
|
+
<ul class="wins">
|
|
287
|
+
<li><span class="tick">✦</span><span><span class="wtag">Compliance-aware previews.</span> Every submit is checked against your customized compliance rules — so you can develop rapidly with the latest AI agents — safely.</span></li>
|
|
288
|
+
<li><span class="tick">✦</span><span><span class="wtag">Errors that explain themselves.</span> When something's off you get the exact next step — never a stack trace or a broken shared site.</span></li>
|
|
289
|
+
</ul>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="status" aria-label="Your first-run status">
|
|
292
|
+
<div class="status-bar"><span class="host">{appDomain} · dev</span><span class="livepill"><span class="blip"></span> Live</span></div>
|
|
293
|
+
<div class="status-row"><span class="ic ok">✓</span><span class="k">MCP connected</span><span class="v">tot · ready</span></div>
|
|
294
|
+
<div class="status-row"><span class="ic ok">✓</span><span class="k">Store running</span><span class="v">localhost:4321</span></div>
|
|
295
|
+
<div class="status-row"><span class="ic ok">✓</span><span class="k">Ask Claude to edit</span><span class="v">content_edit</span></div>
|
|
296
|
+
<div class="status-row"><span class="ic warn">!</span><span class="k">Compliance</span><span class="v warn">2/3 · 1 fix</span></div>
|
|
297
|
+
<div class="status-foot">Everything below drives this. Not a demo dashboard — your store.</div>
|
|
298
|
+
</div>
|
|
299
|
+
</section>
|
|
300
|
+
|
|
301
|
+
<div class="col">
|
|
302
|
+
{signedIn === false && (
|
|
303
|
+
<div class="signin">
|
|
304
|
+
<p>Sign in with the email your invite came to — then this page shows your store and your commands.</p>
|
|
305
|
+
<a href="/auth/login?next=%2Fdev">Sign in</a>
|
|
306
|
+
</div>
|
|
307
|
+
)}
|
|
308
|
+
|
|
309
|
+
<!-- 1 · get it running (value-first: the running store comes before any MCP talk) -->
|
|
310
|
+
<section class="step">
|
|
311
|
+
<div class="label"><span class="n">1</span> Get it running</div>
|
|
312
|
+
<h2>Get your store running — one paste.</h2>
|
|
313
|
+
{canUseAI ? (
|
|
314
|
+
<div class="fastpath" aria-label="Get your store running in one paste, no browser">
|
|
315
|
+
<p class="fastpath-eyebrow"><span class="blip"></span> You're signed in here</p>
|
|
316
|
+
<p class="fastpath-lede">You reached this page from your magic link, so it already knows you — <strong>no Docker, nothing to provision</strong>. Copy this once: it signs your terminal in with a single-use code, then <strong>checks out your store, runs it, and opens your browser</strong> on the working site. No browser sign-in, no code to type.</p>
|
|
317
|
+
<div class="term-shell">
|
|
318
|
+
<div class="term">
|
|
319
|
+
<div class="term-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="term-title">your terminal</span></div>
|
|
320
|
+
<pre id="cli-signin-cmd"><code><span class="cmt"># copy both lines, paste into your terminal, press Enter</span>
|
|
321
|
+
<span id="cli-signin-text">npx @tokenoftrust/cli login --code … &&
|
|
322
|
+
npx @tokenoftrust/cli start</span>
|
|
323
|
+
<span class="vok">✓</span> signed in{firstName ? <> as {firstName}</> : null}
|
|
324
|
+
<span class="arrow">→</span> your store: {appDomain}
|
|
325
|
+
<span class="vok">✓</span> checked out <span class="dir">./{dir}</span>
|
|
326
|
+
<span class="arrow">→</span> {localUrl}
|
|
327
|
+
|
|
328
|
+
<span class="prompt">✨ You're live.</span> <span class="cmt">edit content/home.html + save → it reloads</span></code></pre>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
<div class="fastpath-actions">
|
|
332
|
+
<button id="cli-signin-btn" class="cta" type="button">Copy the command</button>
|
|
333
|
+
<p class="fastpath-note" id="cli-signin-note">Two lines, one copy — the sign-in code is single-use and short-lived. Paste both and press Enter; don't retype.</p>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
) : (
|
|
337
|
+
<>
|
|
338
|
+
<p><code class="inline">tot start</code> signs you in, checks out your store, and runs it <strong>natively — no Docker, no containers</strong>. Your browser opens on your store. The only prerequisites are <strong>Node.js and an invite</strong>.</p>
|
|
339
|
+
<div class="term-shell">
|
|
340
|
+
<button class="copy" type="button" data-copy="npx @tokenoftrust/cli start">copy</button>
|
|
341
|
+
<div class="term">
|
|
342
|
+
<div class="term-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="term-title">your terminal</span></div>
|
|
343
|
+
<pre><code><span class="cmt"># invite → running store, in one command</span>
|
|
344
|
+
<span class="dir">~</span> <span class="prompt">$</span> npx @tokenoftrust/cli start
|
|
345
|
+
<span class="vok">✓</span> signed in{firstName ? <> as {firstName}</> : null}
|
|
346
|
+
<span class="arrow">→</span> your store: {appDomain}
|
|
347
|
+
<span class="vok">✓</span> checked out <span class="dir">./{dir}</span>
|
|
348
|
+
<span class="cmt">~ fetching the native renderer (first run only)…</span>
|
|
349
|
+
<span class="arrow">→</span> {localUrl}
|
|
350
|
+
|
|
351
|
+
<span class="prompt">✨ You're live.</span> <span class="cmt">edit content/home.html + save → it reloads</span></code></pre>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
</>
|
|
355
|
+
)}
|
|
356
|
+
<p class="platforms">Prefer the steps? <code>npm i -g @tokenoftrust/cli</code>, then <code>tot checkout {appDomain}</code>, <code>cd {dir}</code>, <code>tot dev</code>. New to Node? Get it at <a href="https://nodejs.org">nodejs.org</a> — it includes <code>npm</code>.</p>
|
|
357
|
+
</section>
|
|
358
|
+
|
|
359
|
+
<!-- 2 · the loop -->
|
|
360
|
+
<section class="step">
|
|
361
|
+
<div class="label"><span class="n">2</span> Make a change</div>
|
|
362
|
+
<h2>Edit → save → the page reloads. That's the loop.</h2>
|
|
363
|
+
<p>Open <code class="inline">content/home.html</code>, change the title, and save. The page reloads on its own in a couple of seconds — no rebuild, nothing published. Your edits stay private and local until you choose to submit them.</p>
|
|
364
|
+
<div class="demo" id="demo">
|
|
365
|
+
<div class="term-shell">
|
|
366
|
+
<div class="term">
|
|
367
|
+
<div class="term-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="term-title">content/home.html</span></div>
|
|
368
|
+
<pre><code><title><span class="hl">Token of Trust<span class="typed prompt"> — we verify humans</span></span></title>
|
|
369
|
+
|
|
370
|
+
<span class="cmt"># save the file…</span>
|
|
371
|
+
<span class="dir">{dir}</span> <span class="prompt">$</span> <span class="cmt">saved · page reloaded</span></code></pre>
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
<div class="browser">
|
|
375
|
+
<div class="browser-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="url">localhost:4321/{appDomain}/</span></div>
|
|
376
|
+
<div class="stage">
|
|
377
|
+
<p class="brandline">{appDomain}</p>
|
|
378
|
+
<p class="title"><span class="hl">Token of Trust<span class="typed"> — we verify humans</span></span></p>
|
|
379
|
+
<span class="reload">⟳ reloaded</span>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
</section>
|
|
384
|
+
|
|
385
|
+
<!-- 3 · build with Claude + the MCP (the payoff — what the shortcut tools can't do) -->
|
|
386
|
+
<section class="step">
|
|
387
|
+
<div class="label"><span class="n">3</span> Build with Claude</div>
|
|
388
|
+
<h2>Or don't touch a file — just ask.</h2>
|
|
389
|
+
<p>This is the part the shortcut tools can't do. Because Claude is connected to Token of Trust over the MCP, it doesn't guess how your site works — it <strong>asks</strong> where the theme lives and what's safe to change, then makes the edit. <strong>Content and theme only</strong>, and every change comes back as a reviewable diff. The box below is live — it calls the real <code class="inline">content_edit</code> tool against a sandbox store, not a recording.</p>
|
|
390
|
+
<form id="ai-demo-form" class="term-shell" aria-disabled={canUseAI ? undefined : "true"}>
|
|
391
|
+
<div class="term ask-term" id="ai-demo-term">
|
|
392
|
+
<span class="prompt ask-prompt">></span>
|
|
393
|
+
<input id="ai-demo-prompt" class="ask-input mono" type="text" maxlength="300"
|
|
394
|
+
value="Reskin my site — darker, but still friendly." aria-label="Describe the change"
|
|
395
|
+
disabled={canUseAI ? undefined : true} />
|
|
396
|
+
<button class="ask-submit" type="submit" disabled={canUseAI ? undefined : true}>Ask</button>
|
|
397
|
+
</div>
|
|
398
|
+
</form>
|
|
399
|
+
{canUseAI ? null : (
|
|
400
|
+
<div class="ask-gate">
|
|
401
|
+
<span>Sign in with your invite to run this live.</span>
|
|
402
|
+
<a href="/auth/login?next=%2Fdev">Sign in →</a>
|
|
403
|
+
</div>
|
|
404
|
+
)}
|
|
405
|
+
<div class="demo" id="ai-demo">
|
|
406
|
+
<div class="term-shell">
|
|
407
|
+
<div class="claude">
|
|
408
|
+
<div class="term-bar"><span class="spark">✳</span><span class="term-title">Claude</span></div>
|
|
409
|
+
<pre id="ai-demo-log"><code><span class="cmt">{canUseAI
|
|
410
|
+
? "Ask above — the diff Claude applies shows up here."
|
|
411
|
+
: "Sign in to run this live. Below is what a reskin looks like."}</span></code></pre>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
<div class="browser">
|
|
415
|
+
<div class="browser-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="url">sandbox store</span></div>
|
|
416
|
+
<div id="ai-demo-stage" class:list={["stage", { dark: !canUseAI }]}>
|
|
417
|
+
<p class="brandline">Sandbox Store</p>
|
|
418
|
+
<p class="title" id="ai-demo-title">Welcome to Sandbox Store</p>
|
|
419
|
+
<span class="reskin" id="ai-demo-badge" hidden={canUseAI ? true : undefined}>◐ example reskin</span>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
<p class="caption">The shortcut tools hand you a theme editor. Here your assistant <em>is</em> the editor — and the MCP keeps it editing the right things, the right way.</p>
|
|
424
|
+
<p class="platforms">Not connected yet? <code>tot start</code> offers to connect Claude for you — or run <code>claude mcp add --transport http tot https://mcp.tokenoftrust.com/mcp</code>. Using another assistant? Point it at the same URL.</p>
|
|
425
|
+
</section>
|
|
426
|
+
|
|
427
|
+
<!-- 4 · commit -->
|
|
428
|
+
<section class="step">
|
|
429
|
+
<div class="label"><span class="n">4</span> Commit</div>
|
|
430
|
+
<h2>Save your work to your repo.</h2>
|
|
431
|
+
<p>It's a <strong>real git repo</strong> — normal <code class="inline">git</code> commands, or just ask Claude to commit for you. The <code class="inline">tot</code> CLI only wraps a couple of storefront conveniences on top; underneath, it's all git. This is yours; it doesn't touch the shared site.</p>
|
|
432
|
+
<div class="term-shell">
|
|
433
|
+
<button class="copy" type="button" data-copy={"git add .\ngit commit -m \"Update homepage title\"\ngit push"}>copy</button>
|
|
434
|
+
<div class="term">
|
|
435
|
+
<div class="term-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="term-title">your terminal</span></div>
|
|
436
|
+
<pre><code><span class="dir">{dir}</span> <span class="prompt">$</span> git add .
|
|
437
|
+
<span class="dir">{dir}</span> <span class="prompt">$</span> git commit -m <span style="color:#8fe3c0">"Update homepage title"</span>
|
|
438
|
+
<span class="dir">{dir}</span> <span class="prompt">$</span> git push</code></pre>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
</section>
|
|
442
|
+
|
|
443
|
+
<!-- SUBMIT — gated, separate, with the real per-rule compliance verdict -->
|
|
444
|
+
<section class="submit">
|
|
445
|
+
<span class="gatechip">◆ Ready for review</span>
|
|
446
|
+
<div class="label"><span class="n">→</span> Submit for review</div>
|
|
447
|
+
<h2>When a change is ready, put it up for review.</h2>
|
|
448
|
+
<p>This is the deliberate step — separate from your edit loop on purpose. One command pushes your latest commit to the shared <span class="inline">test</span> preview and runs the compliance checks: not "did it build," but "does it meet the age & identity rules" — <strong>rule by rule, with the exact fix when it doesn't</strong>.</p>
|
|
449
|
+
<div class="term-shell">
|
|
450
|
+
<button class="copy" type="button" data-copy="tot submit">copy</button>
|
|
451
|
+
<div class="term">
|
|
452
|
+
<div class="term-bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="term-title">submit for review</span></div>
|
|
453
|
+
<pre><code><span class="dir">{dir}</span> <span class="prompt">$</span> tot submit
|
|
454
|
+
<span class="arrow">→</span> pushed · reconciled into the shared test preview
|
|
455
|
+
<span class="arrow">→</span> compliance
|
|
456
|
+
<span class="vok">✔ age_gate</span> verified on checkout
|
|
457
|
+
<span class="vok">✔ id_check</span> provider wired
|
|
458
|
+
<span class="vwarn">⚠ data_retention</span> set <span class="vhint">retention_days</span> in your config
|
|
459
|
+
<span class="arrow">→</span> preview: https://preview.{appDomain}/…
|
|
460
|
+
<span class="arrow">→</span> on accept: a shareable dashboard + a branded email</code></pre>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
<p>If anything's off, the preview stays on the last good version and <code>tot submit</code> tells you exactly what to fix — it never breaks for everyone else.</p>
|
|
464
|
+
</section>
|
|
465
|
+
|
|
466
|
+
<div class="foot">
|
|
467
|
+
<span>Signed in{session?.email ? <> as <span class="mono">{session.email}</span></> : null}</span>
|
|
468
|
+
<a href="/preview/dashboard">Preview status →</a>
|
|
469
|
+
<a href="/auth/logout">Sign out</a>
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
</main>
|
|
473
|
+
|
|
474
|
+
<script nonce={cspNonce} is:inline>
|
|
475
|
+
// copy buttons (static data-copy only; the dynamic CLI sign-in button below has its
|
|
476
|
+
// own handler that fetches a one-time token first, so exclude it here).
|
|
477
|
+
for (const btn of document.querySelectorAll(".copy[data-copy]")) {
|
|
478
|
+
btn.addEventListener("click", () => {
|
|
479
|
+
const text = btn.getAttribute("data-copy") || "";
|
|
480
|
+
navigator.clipboard?.writeText(text).then(() => {
|
|
481
|
+
const prev = btn.textContent; btn.textContent = "copied"; setTimeout(() => (btn.textContent = prev), 1200);
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
// play the reload demo once when it scrolls into view
|
|
486
|
+
const demo = document.getElementById("demo");
|
|
487
|
+
if (demo && "IntersectionObserver" in window) {
|
|
488
|
+
const io = new IntersectionObserver((es) => {
|
|
489
|
+
for (const e of es) if (e.isIntersecting) { demo.classList.add("play"); io.disconnect(); }
|
|
490
|
+
}, { threshold: 0.5 });
|
|
491
|
+
io.observe(demo);
|
|
492
|
+
} else if (demo) { demo.classList.add("play"); }
|
|
493
|
+
</script>
|
|
494
|
+
|
|
495
|
+
<script nonce={cspNonce} is:inline>
|
|
496
|
+
// Terminal sign-in (browserless): mint a one-time `tot login --code <token>` for the
|
|
497
|
+
// already-signed-in developer and copy the WHOLE command. The token is fetched on
|
|
498
|
+
// click (kept out of the initial HTML) from /api/dev/cli-signin-code, which mints it
|
|
499
|
+
// for THIS session's own email. Only present when signed in with a capability.
|
|
500
|
+
(function () {
|
|
501
|
+
const btn = document.getElementById("cli-signin-btn");
|
|
502
|
+
const textEl = document.getElementById("cli-signin-text");
|
|
503
|
+
const note = document.getElementById("cli-signin-note");
|
|
504
|
+
if (!btn || !textEl) return;
|
|
505
|
+
let busy = false;
|
|
506
|
+
btn.addEventListener("click", async () => {
|
|
507
|
+
if (busy) return;
|
|
508
|
+
busy = true;
|
|
509
|
+
const label = btn.textContent;
|
|
510
|
+
btn.textContent = "…"; btn.disabled = true;
|
|
511
|
+
let res, body;
|
|
512
|
+
try {
|
|
513
|
+
res = await fetch("/api/dev/cli-signin-code", {
|
|
514
|
+
method: "POST",
|
|
515
|
+
headers: { "content-type": "application/json" },
|
|
516
|
+
body: "{}",
|
|
517
|
+
});
|
|
518
|
+
body = await res.json().catch(() => ({}));
|
|
519
|
+
} catch (err) {
|
|
520
|
+
if (note) note.textContent = "Couldn’t reach the sign-in service — try again in a moment.";
|
|
521
|
+
btn.textContent = label; btn.disabled = false; busy = false;
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (!res.ok || !body.command) {
|
|
525
|
+
const byStatus = {
|
|
526
|
+
401: "Your session expired — reload the page and sign in again.",
|
|
527
|
+
403: "This account doesn’t have developer access to a store yet.",
|
|
528
|
+
502: "The sign-in service is having a moment — try again.",
|
|
529
|
+
503: "Terminal sign-in isn’t configured on this environment.",
|
|
530
|
+
};
|
|
531
|
+
if (note) note.textContent = byStatus[res.status] || "Couldn’t mint a sign-in command — try again.";
|
|
532
|
+
btn.textContent = label; btn.disabled = false; busy = false;
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
// Reveal the real command (contains the one-time code) and copy the WHOLE thing.
|
|
536
|
+
// textContent (never innerHTML) — the command is inserted as a pure text node.
|
|
537
|
+
textEl.textContent = body.command;
|
|
538
|
+
let copied = false;
|
|
539
|
+
try { await navigator.clipboard?.writeText(body.command); copied = true; } catch (e) { copied = false; }
|
|
540
|
+
btn.textContent = copied ? "copied ✓" : "select & copy";
|
|
541
|
+
if (note) {
|
|
542
|
+
note.textContent = copied
|
|
543
|
+
? "Copied. Paste both lines into your terminal and press Enter — it signs you in, runs your store, and opens your browser. Single-use, short-lived code."
|
|
544
|
+
: "Select both lines above and copy them, then paste into your terminal and press Enter. Single-use, short-lived code.";
|
|
545
|
+
}
|
|
546
|
+
btn.disabled = false;
|
|
547
|
+
setTimeout(() => { btn.textContent = "Copy the command"; }, 4000);
|
|
548
|
+
busy = false;
|
|
549
|
+
});
|
|
550
|
+
})();
|
|
551
|
+
</script>
|
|
552
|
+
|
|
553
|
+
<script nonce={cspNonce} is:inline>
|
|
554
|
+
// The live "Build with Claude" demo: a real POST to /api/content-edit/ai-edit
|
|
555
|
+
// (tot-mcp's content_edit, against a fixed sandbox tenant — see that route's header
|
|
556
|
+
// comment). No mock, no scripted animation. Only wired up when the developer is
|
|
557
|
+
// signed in with a capability; the gated (logged-out) state stays a static teaser.
|
|
558
|
+
(function () {
|
|
559
|
+
const form = document.getElementById("ai-demo-form");
|
|
560
|
+
const input = document.getElementById("ai-demo-prompt");
|
|
561
|
+
const askTerm = document.getElementById("ai-demo-term");
|
|
562
|
+
const log = document.getElementById("ai-demo-log");
|
|
563
|
+
const stage = document.getElementById("ai-demo-stage");
|
|
564
|
+
const title = document.getElementById("ai-demo-title");
|
|
565
|
+
const badge = document.getElementById("ai-demo-badge");
|
|
566
|
+
if (!form || !input || !log || !stage || !title || !badge) return;
|
|
567
|
+
if (input.disabled) return; // gated (signed-out / no capability) — leave the teaser as-is
|
|
568
|
+
|
|
569
|
+
function esc(s) {
|
|
570
|
+
return String(s).replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" }[c]));
|
|
571
|
+
}
|
|
572
|
+
function render(lines) {
|
|
573
|
+
log.innerHTML = "<code>" + lines.join("\n") + "</code>";
|
|
574
|
+
}
|
|
575
|
+
function busy(on) {
|
|
576
|
+
const submit = form.querySelector(".ask-submit");
|
|
577
|
+
if (submit) submit.disabled = on;
|
|
578
|
+
input.disabled = on;
|
|
579
|
+
if (askTerm) askTerm.classList.toggle("busy", on);
|
|
580
|
+
}
|
|
581
|
+
// A calm line when the live path can't run (not-configured / auth / model error) —
|
|
582
|
+
// never a raw stack trace. `reason` is already trusted server copy or esc()'d.
|
|
583
|
+
function graceful(promptText, reason) {
|
|
584
|
+
render([
|
|
585
|
+
'<span class="prompt">></span> <span class="you">' + esc(promptText) + "</span>",
|
|
586
|
+
"",
|
|
587
|
+
' <span class="cmt">' + reason + "</span>",
|
|
588
|
+
' <span class="cmt">The live demo isn’t available right now — the edit loop still runs in <span class="mcp">tot dev</span>.</span>',
|
|
589
|
+
]);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
form.addEventListener("submit", async (e) => {
|
|
593
|
+
e.preventDefault();
|
|
594
|
+
const prompt = input.value.trim();
|
|
595
|
+
if (!prompt) return;
|
|
596
|
+
busy(true);
|
|
597
|
+
render([
|
|
598
|
+
'<span class="prompt">></span> <span class="you">' + esc(prompt) + "</span>",
|
|
599
|
+
"",
|
|
600
|
+
' <span class="mcp">↳ tot</span> <span class="cmt">· asking content_edit…</span>',
|
|
601
|
+
]);
|
|
602
|
+
|
|
603
|
+
let res, body;
|
|
604
|
+
try {
|
|
605
|
+
res = await fetch("/api/content-edit/ai-edit", {
|
|
606
|
+
method: "POST",
|
|
607
|
+
headers: { "content-type": "application/json" },
|
|
608
|
+
body: JSON.stringify({ prompt }),
|
|
609
|
+
});
|
|
610
|
+
body = await res.json().catch(() => ({}));
|
|
611
|
+
} catch (err) {
|
|
612
|
+
graceful(prompt, "✗ couldn’t reach the editor: " + esc(err.message || err));
|
|
613
|
+
busy(false);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (!res.ok || body.ok === false) {
|
|
618
|
+
// Map the endpoint's status codes to calm, human copy. 401/403 shouldn't
|
|
619
|
+
// happen here (the form is gated server-side) but degrade anyway.
|
|
620
|
+
const byStatus = {
|
|
621
|
+
401: "Your session expired — sign in again to run it live.",
|
|
622
|
+
403: "This account doesn’t have developer access to a store yet.",
|
|
623
|
+
400: "That prompt didn’t come through — try describing the change again.",
|
|
624
|
+
503: "The editor isn’t configured on this environment.",
|
|
625
|
+
};
|
|
626
|
+
const reason = byStatus[res.status] || esc(body.error || "content_edit couldn’t make that change.");
|
|
627
|
+
graceful(prompt, "✗ " + reason);
|
|
628
|
+
busy(false);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// body.diffs[].path / .diff are model output (content_edit's diff over
|
|
633
|
+
// content/home.html) — escape before ever touching innerHTML.
|
|
634
|
+
const diffLines = (body.diffs || []).map(
|
|
635
|
+
(d) => " " + esc(d.path) + "\n" + esc(d.diff).split("\n").map((l) => " " + l).join("\n"),
|
|
636
|
+
);
|
|
637
|
+
// diffLines' contents are already escaped above — do not re-escape
|
|
638
|
+
// (would double-encode the entities and show literal "<" etc.).
|
|
639
|
+
render([
|
|
640
|
+
'<span class="prompt">></span> <span class="you">' + esc(prompt) + "</span>",
|
|
641
|
+
"",
|
|
642
|
+
' <span class="cmt">' + (diffLines.join("\n\n") || "(no change needed)") + "</span>",
|
|
643
|
+
' <span class="ok">✔ ' + esc(body.summary || "done") + "</span>",
|
|
644
|
+
]);
|
|
645
|
+
stage.classList.add("dark");
|
|
646
|
+
badge.hidden = false;
|
|
647
|
+
badge.textContent = "◐ " + (body.summary || "updated");
|
|
648
|
+
busy(false);
|
|
649
|
+
});
|
|
650
|
+
})();
|
|
651
|
+
</script>
|
|
652
|
+
</body>
|
|
653
|
+
</html>
|