@tokenoftrust/storefront-runner 2.0.41 → 2.0.42
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/apps/storefront/integrations/lifecycle-host-asset.mjs +20 -6
- package/apps/storefront/package.json +1 -0
- package/apps/storefront/platform-reconcile-tenants.json +3 -0
- package/apps/storefront/public/favicon.ico +0 -0
- package/apps/storefront/public/shared/commerce-chrome.css +147 -16
- package/apps/storefront/src/components/AsyncStylesheet.astro +26 -0
- package/apps/storefront/src/components/Breadcrumbs.astro +8 -1
- package/apps/storefront/src/components/Img.astro +6 -5
- package/apps/storefront/src/components/ProductCard.astro +6 -4
- package/apps/storefront/src/components/Seo.astro +2 -1
- package/apps/storefront/src/components/chrome/SiteFooter.astro +45 -11
- package/apps/storefront/src/components/chrome/SiteHeader.astro +83 -13
- package/apps/storefront/src/components/commerce/RatingStars.astro +1 -1
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +2 -2
- package/apps/storefront/src/components/content/PromoTiles.astro +107 -0
- package/apps/storefront/src/components/content/TrustStrip.astro +30 -14
- package/apps/storefront/src/components/home/Hero.astro +156 -4
- package/apps/storefront/src/components/islands/AgeGate.tsx +135 -26
- package/apps/storefront/src/components/islands/ImageGallery.tsx +6 -9
- package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +194 -46
- package/apps/storefront/src/components/islands/SearchTypeahead.tsx +10 -4
- package/apps/storefront/src/components/marketing/Faq.astro +9 -2
- package/apps/storefront/src/config/devTenantSeed.ts +1 -1
- package/apps/storefront/src/config/kvTenantResolver.ts +7 -1
- package/apps/storefront/src/config/publicationSpaceConfig.ts +12 -1
- package/apps/storefront/src/config/publicationSpaceRegistry.ts +5 -0
- package/apps/storefront/src/config/tenants.ts +22 -0
- package/apps/storefront/src/layouts/Layout.astro +69 -44
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +24 -8
- package/apps/storefront/src/lib/activity/recordActivity.ts +15 -0
- package/apps/storefront/src/lib/asyncCss.ts +23 -0
- package/apps/storefront/src/lib/auth/loginGate.ts +22 -11
- package/apps/storefront/src/lib/basePath.ts +70 -9
- package/apps/storefront/src/lib/chrome/managedCart.ts +50 -0
- package/apps/storefront/src/lib/chrome/model.ts +4 -0
- package/apps/storefront/src/lib/chrome/newsletterAgeAttestation.ts +9 -0
- package/apps/storefront/src/lib/content/trustStrip.ts +38 -3
- package/apps/storefront/src/lib/cspStaticHashes.ts +4 -1
- package/apps/storefront/src/lib/diag/capture.ts +161 -0
- package/apps/storefront/src/lib/diag/consoleLine.ts +76 -0
- package/apps/storefront/src/lib/diag/debugConsole.ts +118 -0
- package/apps/storefront/src/lib/diag/debugMode.ts +163 -0
- package/apps/storefront/src/lib/diag/debugWindow.ts +111 -0
- package/apps/storefront/src/lib/diag/reference.ts +148 -0
- package/apps/storefront/src/lib/diag/referenceStore.ts +113 -0
- package/apps/storefront/src/lib/diag/report.ts +109 -0
- package/apps/storefront/src/lib/diag/session.ts +93 -0
- package/apps/storefront/src/lib/featuredProductsPresentation.ts +5 -0
- package/apps/storefront/src/lib/heroSlideshow.ts +41 -0
- package/apps/storefront/src/lib/homeVisualParity.ts +107 -0
- package/apps/storefront/src/lib/imageAttrs.ts +9 -6
- package/apps/storefront/src/lib/integration/nonceStamp.ts +23 -1
- package/apps/storefront/src/lib/integration/renderIntegrationState.ts +36 -0
- package/apps/storefront/src/lib/integration/tenantAnalyticsDeclarations.ts +17 -12
- package/apps/storefront/src/lib/jsonScript.ts +13 -0
- package/apps/storefront/src/lib/pinnedRouteRewriteBoundary.ts +31 -0
- package/apps/storefront/src/lib/previewReload.ts +53 -9
- package/apps/storefront/src/lib/rawChrome.ts +148 -19
- package/apps/storefront/src/lib/rawMarketingHtml.ts +120 -7
- package/apps/storefront/src/lib/storyblok/content-model.ts +46 -3
- package/apps/storefront/src/lib/styleGuideThemes.ts +107 -0
- package/apps/storefront/src/lib/tenantRouting.ts +15 -4
- package/apps/storefront/src/lib/tot/syncState.ts +21 -0
- package/apps/storefront/src/lib/tracking/publishSafetyNet.ts +29 -3
- package/apps/storefront/src/middleware/controlPlane.ts +9 -5
- package/apps/storefront/src/middleware/index.ts +13 -1
- package/apps/storefront/src/middleware/renderChain.ts +54 -11
- package/apps/storefront/src/middleware/routeBranch.ts +58 -12
- package/apps/storefront/src/middleware/staticBundle.ts +37 -3
- package/apps/storefront/src/pages/[...slug].astro +6 -2
- package/apps/storefront/src/pages/api/request-access.ts +5 -4
- package/apps/storefront/src/pages/blog/[slug].astro +11 -1
- package/apps/storefront/src/pages/cart.ts +23 -0
- package/apps/storefront/src/pages/index.astro +64 -85
- package/apps/storefront/src/pages/products/[handle].astro +8 -6
- package/apps/storefront/src/pages/sitemap.xml.ts +8 -2
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +14 -25
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +10 -17
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +9 -16
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +9 -15
- package/apps/storefront/src/pages/the-build/[slug].astro +7 -1
- package/apps/storefront/src/styles/fonts.css +7 -0
- package/apps/storefront/src/styles/global.css +397 -3
- package/package.json +4 -1
- package/packages/cli/src/visual-parity.mjs +321 -0
- package/packages/public-runtime/package.json +2 -0
- package/packages/public-runtime/schemas/publication-space/v1/manifest.schema.json +1 -1
- package/packages/public-runtime/schemas/publication-space/v1/normalized-mount.schema.json +1 -1
- package/packages/public-runtime/src/chrome.ts +44 -7
- package/packages/public-runtime/src/csp.ts +12 -1
- package/packages/public-runtime/src/extension-contract-values.d.mts +19 -0
- package/packages/public-runtime/src/extension-contract-values.mjs +62 -0
- package/packages/public-runtime/src/extension-contract.ts +20 -6
- package/packages/public-runtime/src/index.ts +8 -0
- package/packages/public-runtime/src/integration/consent-broker.ts +10 -7
- package/packages/public-runtime/src/integration/consent-config.ts +9 -0
- package/packages/public-runtime/src/integration/contact-sales-action-source.ts +56 -10
- package/packages/public-runtime/src/integration/governed-event-properties.ts +62 -0
- package/packages/public-runtime/src/integration/index.ts +1 -0
- package/packages/public-runtime/src/integration/lifecycle-boot.ts +30 -2
- package/packages/public-runtime/src/integration/lifecycle-host-asset.d.mts +2 -0
- package/packages/public-runtime/src/integration/lifecycle-host-asset.mjs +17 -3
- package/packages/public-runtime/src/integration/native-amplitude.ts +12 -3
- package/packages/public-runtime/src/integration/native-ga4-sandbox-asset.mjs +9 -0
- package/packages/public-runtime/src/integration/native-ga4-sandbox-entry.ts +27 -3
- package/packages/public-runtime/src/integration/native-ga4-sandbox.ts +13 -0
- package/packages/public-runtime/src/integration/native-ga4.ts +11 -0
- package/packages/public-runtime/src/integration/platform-adapters.ts +5 -1
- package/packages/public-runtime/src/integration/render-phases.ts +28 -2
- package/packages/public-runtime/src/publication-redirects.d.mts +45 -0
- package/packages/public-runtime/src/publication-redirects.mjs +379 -0
- package/packages/public-runtime/src/publication-space.ts +24 -3
- package/packages/public-runtime/src/static-bundle.ts +145 -26
- package/packages/public-runtime/src/tenant-embed-defaults.d.mts +3 -0
- package/packages/public-runtime/src/tenant-embed-defaults.mjs +40 -0
- package/packages/public-runtime/src/tenant.ts +59 -1
- package/pnpm-lock.runner.yaml +12 -0
- package/pnpm-workspace.yaml +4 -0
- package/scripts/dev/git-status.mjs +1 -1
- package/scripts/dev/publish.mjs +6 -0
- package/scripts/lib/tenant-asset-copy.mjs +4 -2
- package/scripts/tenant/materialize-tenant-content.mjs +1 -0
- package/scripts/tenant/validate.mjs +171 -8
- package/apps/storefront/src/lib/cfImage.ts +0 -55
- package/apps/storefront/src/workers/pruneRollupWorker.ts +0 -120
- package/tenants/home/public/fonts/inter-latin-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-700-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-800-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-700-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-800-normal.woff2 +0 -0
- package/tenants/home/public/js/storefront.js +0 -129
- package/tenants/home/public/logo-wordmark.png +0 -0
- package/tenants/home/public/pages/storefront.css +0 -235
- package/tenants/home/theme.json +0 -6
|
@@ -9,9 +9,13 @@
|
|
|
9
9
|
import Layout from "@/layouts/Layout.astro";
|
|
10
10
|
import Section from "@/components/Section.astro";
|
|
11
11
|
import Hero from "@/components/home/Hero.astro";
|
|
12
|
-
import
|
|
12
|
+
import ProductCarousel from "@/components/commerce/ProductCarousel.astro";
|
|
13
|
+
import ProductGrid from "@/components/commerce/ProductGrid.astro";
|
|
13
14
|
import CollectionCard from "@/components/CollectionCard.astro";
|
|
14
15
|
import Img from "@/components/Img.astro";
|
|
16
|
+
import PromoTiles from "@/components/content/PromoTiles.astro";
|
|
17
|
+
import TrustStrip from "@/components/content/TrustStrip.astro";
|
|
18
|
+
import Faq from "@/components/marketing/Faq.astro";
|
|
15
19
|
import MarketingBlocks from "@/components/marketing/MarketingBlocks.astro";
|
|
16
20
|
import TierCards from "@/components/membership/TierCards.astro";
|
|
17
21
|
import { withBase } from "@/lib/basePath";
|
|
@@ -23,10 +27,6 @@ import type { CatalogCollection, CatalogProduct } from "@tot/public-runtime";
|
|
|
23
27
|
import {
|
|
24
28
|
isMarketingBlock,
|
|
25
29
|
type HeroBlock,
|
|
26
|
-
type EditorialBlock,
|
|
27
|
-
type NewsletterBlock,
|
|
28
|
-
type PromoTilesBlock,
|
|
29
|
-
type MembershipBandBlock,
|
|
30
30
|
type MarketingBlock,
|
|
31
31
|
type FeaturedProductsBlock,
|
|
32
32
|
} from "@/lib/storyblok/content-model";
|
|
@@ -35,8 +35,14 @@ import {
|
|
|
35
35
|
normalizeProductSectionSource,
|
|
36
36
|
} from "@tot/public-runtime";
|
|
37
37
|
import { resolveSectionSource } from "@/lib/sections";
|
|
38
|
+
import { featuredProductsPresentationView } from "@/lib/featuredProductsPresentation";
|
|
39
|
+
import {
|
|
40
|
+
editorialMediaView,
|
|
41
|
+
resolveCommerceStaticBlock,
|
|
42
|
+
type CommerceStaticResolved,
|
|
43
|
+
} from "@/lib/homeVisualParity";
|
|
38
44
|
|
|
39
|
-
const { tenant, tot, content, basePath, features } = Astro.locals;
|
|
45
|
+
const { tenant, tot, content, basePath, canonicalBase, features } = Astro.locals;
|
|
40
46
|
const marketing = !features.catalog;
|
|
41
47
|
|
|
42
48
|
// Raw-HTML home (vendor-injected): serve the tenant's own self-contained
|
|
@@ -64,6 +70,7 @@ if (rawHtml && rawHtmlPolicy(tenant).allowed) {
|
|
|
64
70
|
chromeWrapper,
|
|
65
71
|
plan: Astro.locals.integrationPlan,
|
|
66
72
|
consentSnapshot: Astro.locals.consentSnapshot,
|
|
73
|
+
canonicalUrl: new URL("/", canonicalBase + "/").toString(),
|
|
67
74
|
});
|
|
68
75
|
}
|
|
69
76
|
|
|
@@ -79,13 +86,10 @@ const collections = marketing ? [] : await tot.listCollections();
|
|
|
79
86
|
|
|
80
87
|
// --- Resolve each block into a typed, render-ready view-model -----------------
|
|
81
88
|
type Resolved =
|
|
82
|
-
| { kind: "hero"; block: HeroBlock; product: CatalogProduct | null }
|
|
83
|
-
| { kind: "promo_tiles"; title?: string; tiles: PromoTilesBlock["tiles"] }
|
|
89
|
+
| { kind: "hero"; block: HeroBlock; product: CatalogProduct | null; instanceKey: string }
|
|
84
90
|
| { kind: "collections"; title?: string; cols: CatalogCollection[] }
|
|
85
|
-
| { kind: "products"; title?: string; products: CatalogProduct[] }
|
|
86
|
-
|
|
|
87
|
-
| { kind: "editorial"; block: EditorialBlock }
|
|
88
|
-
| { kind: "newsletter"; block: NewsletterBlock };
|
|
91
|
+
| { kind: "products"; title?: string; products: CatalogProduct[]; presentation?: FeaturedProductsBlock["presentation"] }
|
|
92
|
+
| CommerceStaticResolved;
|
|
89
93
|
|
|
90
94
|
async function resolveProducts(
|
|
91
95
|
source: FeaturedProductsBlock["source"],
|
|
@@ -101,6 +105,11 @@ async function resolveProducts(
|
|
|
101
105
|
|
|
102
106
|
const resolved: Resolved[] = [];
|
|
103
107
|
for (const block of marketing ? [] : home.blocks) {
|
|
108
|
+
const staticBlock = resolveCommerceStaticBlock(block);
|
|
109
|
+
if (staticBlock) {
|
|
110
|
+
resolved.push(staticBlock);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
104
113
|
switch (block.component) {
|
|
105
114
|
case "hero": {
|
|
106
115
|
let product: CatalogProduct | null = null;
|
|
@@ -109,13 +118,7 @@ for (const block of marketing ? [] : home.blocks) {
|
|
|
109
118
|
const firstHandle = col?.product_handles[0];
|
|
110
119
|
if (firstHandle) product = await tot.getProductByHandle(firstHandle);
|
|
111
120
|
}
|
|
112
|
-
resolved.push({ kind: "hero", block, product });
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
case "promo_tiles": {
|
|
116
|
-
if (block.tiles?.length) {
|
|
117
|
-
resolved.push({ kind: "promo_tiles", title: block.title, tiles: block.tiles });
|
|
118
|
-
}
|
|
121
|
+
resolved.push({ kind: "hero", block, product, instanceKey: `home-${resolved.length}` });
|
|
119
122
|
break;
|
|
120
123
|
}
|
|
121
124
|
case "featured_collections": {
|
|
@@ -134,18 +137,9 @@ for (const block of marketing ? [] : home.blocks) {
|
|
|
134
137
|
block.handles,
|
|
135
138
|
block.limit ?? 4,
|
|
136
139
|
);
|
|
137
|
-
resolved.push({ kind: "products", title: block.title, products });
|
|
140
|
+
resolved.push({ kind: "products", title: block.title, products, presentation: block.presentation });
|
|
138
141
|
break;
|
|
139
142
|
}
|
|
140
|
-
case "membership_band":
|
|
141
|
-
resolved.push({ kind: "membership_band", block });
|
|
142
|
-
break;
|
|
143
|
-
case "editorial":
|
|
144
|
-
resolved.push({ kind: "editorial", block });
|
|
145
|
-
break;
|
|
146
|
-
case "newsletter":
|
|
147
|
-
resolved.push({ kind: "newsletter", block });
|
|
148
|
-
break;
|
|
149
143
|
}
|
|
150
144
|
}
|
|
151
145
|
|
|
@@ -180,40 +174,13 @@ const homeSectionWidgets = marketing
|
|
|
180
174
|
{
|
|
181
175
|
!marketing && resolved.map((r) => {
|
|
182
176
|
if (r.kind === "hero") {
|
|
183
|
-
return <Hero block={r.block} featureProduct={r.product} />;
|
|
177
|
+
return <Hero block={r.block} featureProduct={r.product} instanceKey={r.instanceKey} />;
|
|
184
178
|
}
|
|
185
179
|
if (r.kind === "promo_tiles") {
|
|
186
|
-
return
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
"grid gap-5",
|
|
191
|
-
r.tiles.length >= 3 ? "sm:grid-cols-3" : "sm:grid-cols-2",
|
|
192
|
-
]}
|
|
193
|
-
>
|
|
194
|
-
{r.tiles.map((t) => (
|
|
195
|
-
<a
|
|
196
|
-
href={withBase(basePath, t.href)}
|
|
197
|
-
class="group relative block aspect-[16/9] overflow-hidden rounded-[var(--radius-lg)] shadow-[var(--shadow-sm)]"
|
|
198
|
-
>
|
|
199
|
-
{t.image && (
|
|
200
|
-
<img
|
|
201
|
-
src={t.image}
|
|
202
|
-
alt={t.label}
|
|
203
|
-
width={1600}
|
|
204
|
-
height={900}
|
|
205
|
-
loading="lazy"
|
|
206
|
-
class="absolute inset-0 h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
207
|
-
/>
|
|
208
|
-
)}
|
|
209
|
-
<span class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent p-4 font-display text-lg font-semibold text-white">
|
|
210
|
-
{t.label}
|
|
211
|
-
</span>
|
|
212
|
-
</a>
|
|
213
|
-
))}
|
|
214
|
-
</div>
|
|
215
|
-
</Section>
|
|
216
|
-
);
|
|
180
|
+
return <PromoTiles block={r.block} basePath={basePath} tenantId={tenant.tenant_id} />;
|
|
181
|
+
}
|
|
182
|
+
if (r.kind === "trust_strip") {
|
|
183
|
+
return <TrustStrip items={r.block.items} tenantId={tenant.tenant_id} mobile={r.block.mobile} />;
|
|
217
184
|
}
|
|
218
185
|
if (r.kind === "collections") {
|
|
219
186
|
return (
|
|
@@ -227,15 +194,10 @@ const homeSectionWidgets = marketing
|
|
|
227
194
|
);
|
|
228
195
|
}
|
|
229
196
|
if (r.kind === "products") {
|
|
230
|
-
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
<ProductCard product={p} position={i} list={r.title} />
|
|
235
|
-
))}
|
|
236
|
-
</div>
|
|
237
|
-
</Section>
|
|
238
|
-
);
|
|
197
|
+
if (featuredProductsPresentationView(r.presentation) === "carousel") {
|
|
198
|
+
return <ProductCarousel products={r.products} title={r.title} eyebrow="Shop" tone="surface" />;
|
|
199
|
+
}
|
|
200
|
+
return <ProductGrid products={r.products} title={r.title} eyebrow="Shop" tone="surface" />;
|
|
239
201
|
}
|
|
240
202
|
if (r.kind === "membership_band") {
|
|
241
203
|
const b = r.block;
|
|
@@ -253,6 +215,7 @@ const homeSectionWidgets = marketing
|
|
|
253
215
|
}
|
|
254
216
|
if (r.kind === "editorial") {
|
|
255
217
|
const b = r.block;
|
|
218
|
+
const media = editorialMediaView(b, tenant.tenant_id);
|
|
256
219
|
const right = b.align !== "left";
|
|
257
220
|
return (
|
|
258
221
|
<Section tone="bg">
|
|
@@ -269,25 +232,38 @@ const homeSectionWidgets = marketing
|
|
|
269
232
|
</h2>
|
|
270
233
|
<div class="rte mt-5 text-[var(--color-muted)]" set:html={b.body_html} />
|
|
271
234
|
{b.ctaLabel && b.ctaHref && (
|
|
272
|
-
<
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
235
|
+
<div class="mt-7">
|
|
236
|
+
<a
|
|
237
|
+
href={withBase(basePath, b.ctaHref)}
|
|
238
|
+
class="link-accent link-accent--cta font-display text-sm font-medium"
|
|
239
|
+
>
|
|
240
|
+
{b.ctaLabel} →
|
|
241
|
+
</a>
|
|
242
|
+
</div>
|
|
278
243
|
)}
|
|
279
244
|
</div>
|
|
280
245
|
<div data-reveal>
|
|
281
246
|
<div class="overflow-hidden rounded-[var(--radius-lg)] shadow-[var(--shadow-md)]">
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
247
|
+
{media.kind === "image" ? (
|
|
248
|
+
<img
|
|
249
|
+
src={media.src}
|
|
250
|
+
alt={media.alt}
|
|
251
|
+
width="1000"
|
|
252
|
+
height="750"
|
|
253
|
+
loading="lazy"
|
|
254
|
+
class="h-full w-full object-cover"
|
|
255
|
+
/>
|
|
256
|
+
) : (
|
|
257
|
+
<Img
|
|
258
|
+
seed={media.seed}
|
|
259
|
+
label={media.label}
|
|
260
|
+
alt=""
|
|
261
|
+
width={1000}
|
|
262
|
+
height={750}
|
|
263
|
+
sizes="(min-width:768px) 48vw, 100vw"
|
|
264
|
+
class="h-full w-full object-cover"
|
|
265
|
+
/>
|
|
266
|
+
)}
|
|
291
267
|
</div>
|
|
292
268
|
</div>
|
|
293
269
|
</div>
|
|
@@ -329,6 +305,9 @@ const homeSectionWidgets = marketing
|
|
|
329
305
|
</Section>
|
|
330
306
|
);
|
|
331
307
|
}
|
|
308
|
+
if (r.kind === "faq") {
|
|
309
|
+
return <Faq block={r.block} />;
|
|
310
|
+
}
|
|
332
311
|
return null;
|
|
333
312
|
})
|
|
334
313
|
}
|
|
@@ -460,12 +460,14 @@ const jsonLd = [productJsonLd, breadcrumbLd(crumbs, canonicalBase)];
|
|
|
460
460
|
Delivery times, rates, and returns are covered in our policy —
|
|
461
461
|
including the adult-signature (21+) requirement on every order.
|
|
462
462
|
</p>
|
|
463
|
-
<
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
463
|
+
<div class="mt-3">
|
|
464
|
+
<a
|
|
465
|
+
href={withBase(basePath, "/shipping-returns")}
|
|
466
|
+
class="link-accent link-accent--cta font-medium"
|
|
467
|
+
>
|
|
468
|
+
Shipping & returns policy
|
|
469
|
+
</a>
|
|
470
|
+
</div>
|
|
469
471
|
</div>
|
|
470
472
|
</details>
|
|
471
473
|
</div>
|
|
@@ -13,6 +13,7 @@ import { nonEmptyCollections } from "@/lib/tot/query.js";
|
|
|
13
13
|
import { listBlogPosts } from "@/lib/blog/provider.js";
|
|
14
14
|
import { authorSlug, categorySlug } from "@/lib/blog/collection.js";
|
|
15
15
|
import { listBuildPosts } from "@/lib/the-build/provider.js";
|
|
16
|
+
import { isIndexableHtml, rawPageCanonicalUrl } from "@/lib/rawMarketingHtml.js";
|
|
16
17
|
|
|
17
18
|
interface SitemapUrl {
|
|
18
19
|
loc: string;
|
|
@@ -58,8 +59,13 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
58
59
|
|
|
59
60
|
// Raw tenant-authored marketing subpages (content/<tenant>/pages-html/*.html).
|
|
60
61
|
const rawPageSlugs = await content.listPageHtmlSlugs();
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
const rawPages = await Promise.all(
|
|
63
|
+
rawPageSlugs.map(async (slug) => ({ slug, html: await content.getPageHtml(slug) })),
|
|
64
|
+
);
|
|
65
|
+
for (const { slug, html } of rawPages) {
|
|
66
|
+
if (html && isIndexableHtml(html)) {
|
|
67
|
+
urls.push({ loc: rawPageCanonicalUrl(canonicalBase, slug) });
|
|
68
|
+
}
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
const pages = await content.listPages();
|
|
@@ -20,7 +20,7 @@ export const prerender = false;
|
|
|
20
20
|
// utilities + reference theme-token fallbacks so the (token-styled) components
|
|
21
21
|
// render here without the app Layout. The `.sg-commerce` wrapper pins the
|
|
22
22
|
// reference palette so they look coherent regardless of the marketing theme CSS.
|
|
23
|
-
import {
|
|
23
|
+
import { loadStyleGuideChrome, loadStyleGuideThemes } from "@/lib/styleGuideThemes";
|
|
24
24
|
import "@/styles/global.css";
|
|
25
25
|
import PriceDisplay from "@/components/commerce/PriceDisplay.astro";
|
|
26
26
|
import RatingStars from "@/components/commerce/RatingStars.astro";
|
|
@@ -87,6 +87,7 @@ import type { HeroBlock, MarketingBlock } from "@/lib/storyblok/content-model";
|
|
|
87
87
|
|
|
88
88
|
const { tenant, theme } = Astro.params;
|
|
89
89
|
const cspNonce = Astro.locals.cspNonce;
|
|
90
|
+
const favicon = Astro.locals.tenant.theme_tokens.brand?.favicon ?? "/favicon.svg";
|
|
90
91
|
|
|
91
92
|
// --- Sample chrome data (preview-only; no live tenant content) ----------------
|
|
92
93
|
// Every actionable item declares its tracking intent (`action`) alongside its
|
|
@@ -468,20 +469,11 @@ const sampleMarketingBlocks: MarketingBlock[] = [
|
|
|
468
469
|
},
|
|
469
470
|
];
|
|
470
471
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
"../../../../../../tenants/e2e/*/content/themes/*.json",
|
|
477
|
-
],
|
|
478
|
-
{ eager: true, import: "default" },
|
|
479
|
-
);
|
|
480
|
-
const seg = `${tenantDirSegment(tenant ?? "")}content/themes/`;
|
|
481
|
-
const t = Object.entries(themeModules)
|
|
482
|
-
.filter(([path]) => path.includes(seg))
|
|
483
|
-
.map(([, mod]) => mod)
|
|
484
|
-
.find((m) => m.id === theme);
|
|
472
|
+
const t = (await loadStyleGuideThemes(
|
|
473
|
+
tenant ?? "",
|
|
474
|
+
Astro.locals.tenant,
|
|
475
|
+
Astro.locals.content,
|
|
476
|
+
)).find((candidate) => candidate.id === theme) as Record<string, any> | undefined;
|
|
485
477
|
|
|
486
478
|
if (!t) return new Response(null, { status: 404 });
|
|
487
479
|
|
|
@@ -489,15 +481,11 @@ if (!t) return new Response(null, { status: 404 });
|
|
|
489
481
|
// longer ships its own mkt.css (retired at integration) — link the
|
|
490
482
|
// platform /shared/commerce-chrome.css instead so the marketing-component
|
|
491
483
|
// samples below still render in-brand.
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
],
|
|
497
|
-
{ eager: true, import: "default" },
|
|
484
|
+
const chromeConfig = await loadStyleGuideChrome(
|
|
485
|
+
tenant ?? "",
|
|
486
|
+
Astro.locals.tenant,
|
|
487
|
+
Astro.locals.content,
|
|
498
488
|
);
|
|
499
|
-
const chromeSeg = `${tenantDirSegment(tenant ?? "")}content/chrome.json`;
|
|
500
|
-
const chromeConfig = Object.entries(chromeModules).find(([path]) => path.includes(chromeSeg))?.[1];
|
|
501
489
|
// Adoption is now "does chrome.json carry the consolidated header/footer
|
|
502
490
|
// shape" (lib/chrome/model.ts ChromeConfig) — the old `sharedChrome: true`
|
|
503
491
|
// opt-in flag is retired; a successfully-shaped config IS the adopted signal.
|
|
@@ -546,7 +534,8 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
546
534
|
<meta charset="utf-8" />
|
|
547
535
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
548
536
|
<meta name="robots" content="noindex, nofollow" />
|
|
549
|
-
<link rel="icon"
|
|
537
|
+
<link rel="icon" href={favicon} />
|
|
538
|
+
<meta name="description" content={`${t.name} preview style guide and component contract.`} />
|
|
550
539
|
<title>{t.name} — style guide (preview)</title>
|
|
551
540
|
{/* The tenant's OWN CSS (external, CSP-clean 'self'): the theme token/base
|
|
552
541
|
layer under review PLUS the shared marketing composition layer (mkt.css).
|
|
@@ -1410,7 +1399,7 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
1410
1399
|
<p class="lede" style="font-size:.95rem;color:var(--color-muted)">Client-hydrated Preact islands exercised with fixture data — no live catalog, cart, or ToT calls.</p>
|
|
1411
1400
|
|
|
1412
1401
|
<p class="form-help" style="margin:18px 0 8px">AgeGate — full-viewport 21+ affirmation (real overlay; dismiss it once to keep browsing the guide, same as a real visitor)</p>
|
|
1413
|
-
<AgeGate client:load minAge={21} brand={t.name} />
|
|
1402
|
+
<AgeGate client:load minAge={21} brand={t.name} tenantId={tenant ?? "style-guide"} />
|
|
1414
1403
|
|
|
1415
1404
|
<p class="form-help" style="margin:22px 0 8px">VariantSelector — PDP option picker (radiogroup + live price/SKU/stock region)</p>
|
|
1416
1405
|
<VariantSelector
|
|
@@ -27,23 +27,15 @@ export const prerender = false;
|
|
|
27
27
|
|
|
28
28
|
const { tenant, theme } = Astro.params;
|
|
29
29
|
const cspNonce = Astro.locals.cspNonce;
|
|
30
|
+
const favicon = Astro.locals.tenant.theme_tokens.brand?.favicon ?? "/favicon.svg";
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
// repo root is seven levels up.
|
|
33
|
-
import { tenantDirSegment } from "@tot/public-runtime";
|
|
32
|
+
import { loadStyleGuideThemes } from "@/lib/styleGuideThemes";
|
|
34
33
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{ eager: true, import: "default" },
|
|
41
|
-
);
|
|
42
|
-
const seg = `${tenantDirSegment(tenant ?? "")}content/themes/`;
|
|
43
|
-
const t = Object.entries(themeModules)
|
|
44
|
-
.filter(([path]) => path.includes(seg))
|
|
45
|
-
.map(([, mod]) => mod)
|
|
46
|
-
.find((m) => m.id === theme);
|
|
34
|
+
const t = (await loadStyleGuideThemes(
|
|
35
|
+
tenant ?? "",
|
|
36
|
+
Astro.locals.tenant,
|
|
37
|
+
Astro.locals.content,
|
|
38
|
+
)).find((candidate) => candidate.id === theme) as Record<string, any> | undefined;
|
|
47
39
|
|
|
48
40
|
if (!t) return new Response(null, { status: 404 });
|
|
49
41
|
|
|
@@ -81,7 +73,8 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
81
73
|
<meta charset="utf-8" />
|
|
82
74
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
83
75
|
<meta name="robots" content="noindex, nofollow" />
|
|
84
|
-
<link rel="icon"
|
|
76
|
+
<link rel="icon" href={favicon} />
|
|
77
|
+
<meta name="description" content={`${t.name} preview shared-chrome contract.`} />
|
|
85
78
|
<title>{t.name} — shared chrome (preview)</title>
|
|
86
79
|
{/* The theme token sheet (fonts + any :root tokens) + the shared chrome sheet.
|
|
87
80
|
NO tenant mkt.css — the chrome renders purely from the platform stylesheet. */}
|
|
@@ -146,7 +139,7 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
146
139
|
</div>
|
|
147
140
|
<div class="header-actions">
|
|
148
141
|
<a href="#" aria-label="Account"><svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"></circle><path d="M4 21c0-4 4-6 8-6s8 2 8 6"></path></svg></a>
|
|
149
|
-
<a class="cart" href="#" aria-label="Cart"><svg viewBox="0 0 24 24"><path d="M3 3h2l2 13h11l2-8H7"></path></svg><span class="count">2</span></a>
|
|
142
|
+
<a class="cart" href="#" aria-label="Cart, 2 items"><svg viewBox="0 0 24 24"><path d="M3 3h2l2 13h11l2-8H7"></path></svg><span class="count">2</span></a>
|
|
150
143
|
</div>
|
|
151
144
|
<button class="hamburger" aria-label="Menu"><span></span><span></span><span></span></button>
|
|
152
145
|
</div>
|
|
@@ -17,28 +17,20 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export const prerender = false;
|
|
19
19
|
|
|
20
|
-
import { tenantDirSegment } from "@tot/public-runtime";
|
|
21
20
|
import "@/styles/global.css";
|
|
22
21
|
import type { MarkdownInstance } from "astro";
|
|
23
22
|
import StyleGuideNav from "@/components/style-guide/StyleGuideNav.astro";
|
|
23
|
+
import { loadStyleGuideThemes } from "@/lib/styleGuideThemes";
|
|
24
24
|
|
|
25
25
|
const { tenant, theme } = Astro.params;
|
|
26
26
|
const cspNonce = Astro.locals.cspNonce;
|
|
27
|
+
const favicon = Astro.locals.tenant.theme_tokens.brand?.favicon ?? "/favicon.svg";
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"../../../../../../../tenants/e2e/*/content/themes/*.json",
|
|
34
|
-
],
|
|
35
|
-
{ eager: true, import: "default" },
|
|
36
|
-
);
|
|
37
|
-
const seg = `${tenantDirSegment(tenant ?? "")}content/themes/`;
|
|
38
|
-
const t = Object.entries(themeModules)
|
|
39
|
-
.filter(([path]) => path.includes(seg))
|
|
40
|
-
.map(([, mod]) => mod)
|
|
41
|
-
.find((m) => m.id === theme);
|
|
29
|
+
const t = (await loadStyleGuideThemes(
|
|
30
|
+
tenant ?? "",
|
|
31
|
+
Astro.locals.tenant,
|
|
32
|
+
Astro.locals.content,
|
|
33
|
+
)).find((candidate) => candidate.id === theme) as Record<string, any> | undefined;
|
|
42
34
|
|
|
43
35
|
if (!t) return new Response(null, { status: 404 });
|
|
44
36
|
|
|
@@ -82,7 +74,8 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
82
74
|
<meta charset="utf-8" />
|
|
83
75
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
84
76
|
<meta name="robots" content="noindex, nofollow" />
|
|
85
|
-
<link rel="icon"
|
|
77
|
+
<link rel="icon" href={favicon} />
|
|
78
|
+
<meta name="description" content={`${t.name} preview widget-library contract.`} />
|
|
86
79
|
<title>Widget library guide — {t.name} (preview)</title>
|
|
87
80
|
{/* The tenant's theme sheet so the prose reads under real brand tokens. */}
|
|
88
81
|
<link rel="stylesheet" href={t.stylesheet} />
|
|
@@ -16,31 +16,24 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export const prerender = false;
|
|
18
18
|
|
|
19
|
-
import { tenantDirSegment } from "@tot/public-runtime";
|
|
20
19
|
import StyleGuideNav from "@/components/style-guide/StyleGuideNav.astro";
|
|
20
|
+
import { loadStyleGuideThemes } from "@/lib/styleGuideThemes";
|
|
21
21
|
|
|
22
22
|
const { tenant } = Astro.params;
|
|
23
23
|
const cspNonce = Astro.locals.cspNonce;
|
|
24
|
+
const favicon = Astro.locals.tenant.theme_tokens.brand?.favicon ?? "/favicon.svg";
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"../../../../../../tenants/*/content/themes/*.json",
|
|
30
|
-
"../../../../../../tenants/e2e/*/content/themes/*.json",
|
|
31
|
-
],
|
|
32
|
-
{ eager: true, import: "default" },
|
|
26
|
+
const themes = await loadStyleGuideThemes(
|
|
27
|
+
tenant ?? "",
|
|
28
|
+
Astro.locals.tenant,
|
|
29
|
+
Astro.locals.content,
|
|
33
30
|
);
|
|
34
|
-
const seg = `${tenantDirSegment(tenant ?? "")}content/themes/`;
|
|
35
|
-
const themes = Object.entries(themeModules)
|
|
36
|
-
.filter(([path]) => path.includes(seg))
|
|
37
|
-
.map(([, mod]) => mod);
|
|
38
31
|
|
|
39
32
|
if (themes.length === 0) return new Response(null, { status: 404 });
|
|
40
33
|
|
|
41
34
|
const ROLE_ORDER: Record<string, number> = { current: 0, candidate: 1, legacy: 2 };
|
|
42
35
|
themes.sort(
|
|
43
|
-
(a, b) => (ROLE_ORDER[a.role] ?? 9) - (ROLE_ORDER[b.role] ?? 9),
|
|
36
|
+
(a, b) => (ROLE_ORDER[a.role ?? ""] ?? 9) - (ROLE_ORDER[b.role ?? ""] ?? 9),
|
|
44
37
|
);
|
|
45
38
|
// The current (or first) theme skins the persistent sidebar's per-theme links.
|
|
46
39
|
const navTheme = themes[0]?.id;
|
|
@@ -52,7 +45,8 @@ const navTheme = themes[0]?.id;
|
|
|
52
45
|
<meta charset="utf-8" />
|
|
53
46
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
54
47
|
<meta name="robots" content="noindex, nofollow" />
|
|
55
|
-
<link rel="icon"
|
|
48
|
+
<link rel="icon" href={favicon} />
|
|
49
|
+
<meta name="description" content={`${tenant} preview theme style guides.`} />
|
|
56
50
|
<title>Style guides — {tenant} (preview)</title>
|
|
57
51
|
<style nonce={cspNonce}>
|
|
58
52
|
:root { color-scheme: light; }
|
|
@@ -11,12 +11,18 @@ import { getTranslations } from "@/lib/blog/collection.js";
|
|
|
11
11
|
import { buildAlternates } from "@/lib/seo/alternates.js";
|
|
12
12
|
import { breadcrumbLd } from "@/lib/jsonld.js";
|
|
13
13
|
|
|
14
|
+
// Same contract as blog/[slug].astro: the publication composition lifts this file into the space's
|
|
15
|
+
// `routes.aliases`, so these redirects reach the CDN plane through the live pointer. The redirect
|
|
16
|
+
// below is the pointer-served plane's implementation of those same declared redirects.
|
|
17
|
+
export const MATERIALIZED_REDIRECT_SOURCE = "content/the-build/redirects.json";
|
|
18
|
+
|
|
14
19
|
const { tenant, canonicalBase, basePath, content, publicationTimestamp } = Astro.locals;
|
|
15
20
|
const slug = Astro.params.slug ?? "";
|
|
16
21
|
const post = await getBuildPost(tenant.tenant_id, slug, { content, now: publicationTimestamp });
|
|
17
22
|
if (!post) {
|
|
18
23
|
const redirect = await resolveBuildRedirect(tenant.tenant_id, slug, content);
|
|
19
|
-
|
|
24
|
+
// 308, matching the `redirect-308` alias contract the published plane serves.
|
|
25
|
+
if (redirect) return Astro.redirect(`${basePath}/the-build/${redirect}/`, 308);
|
|
20
26
|
return new Response(null, { status: 404 });
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* sample-store-3.com: Montserrat 600/700/800 (display), Inter 400/500/600 (body)
|
|
18
18
|
* tokenoftrust.com: Plus Jakarta Sans 500/600/700/800 (display),
|
|
19
19
|
* Inter 400/500/600 (body), JetBrains Mono 400/500 (mono)
|
|
20
|
+
* vapenicotinefree.com: Anuphan 400/500/600/700 (display + body)
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
/* Space Grotesk — reference display */
|
|
@@ -52,3 +53,9 @@
|
|
|
52
53
|
@import "@fontsource/jetbrains-mono/400.css";
|
|
53
54
|
@import "@fontsource/jetbrains-mono/500.css";
|
|
54
55
|
@import "@fontsource/jetbrains-mono/600.css";
|
|
56
|
+
|
|
57
|
+
/* Anuphan — Vape Nicotine Free display + body */
|
|
58
|
+
@import "@fontsource/anuphan/400.css";
|
|
59
|
+
@import "@fontsource/anuphan/500.css";
|
|
60
|
+
@import "@fontsource/anuphan/600.css";
|
|
61
|
+
@import "@fontsource/anuphan/700.css";
|