@tokenoftrust/storefront-runner 2.2.126 → 2.3.1
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/package.json +3 -1
- package/apps/storefront/public/shared/commerce-chrome.css +57 -15
- package/apps/storefront/public/shared/commerce-marketing.css +7 -7
- package/apps/storefront/src/components/Badge.astro +1 -1
- package/apps/storefront/src/components/Breadcrumbs.astro +8 -3
- package/apps/storefront/src/components/Section.astro +4 -1
- package/apps/storefront/src/components/Seo.astro +7 -5
- package/apps/storefront/src/components/blog/BlogIndexView.astro +22 -13
- package/apps/storefront/src/components/blog/ShareRow.astro +43 -0
- package/apps/storefront/src/components/chrome/AnnouncementBar.astro +1 -1
- package/apps/storefront/src/components/chrome/NavDropdown.astro +7 -7
- package/apps/storefront/src/components/chrome/SiteFooter.astro +17 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +5 -5
- package/apps/storefront/src/components/commerce/TagFilterCloud.astro +1 -1
- package/apps/storefront/src/components/compliance/NicotineWarning.astro +12 -22
- package/apps/storefront/src/components/compliance/PactActNotice.astro +9 -10
- package/apps/storefront/src/components/content/ProseSections.astro +2 -2
- package/apps/storefront/src/components/home/Hero.astro +34 -12
- package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +39 -5
- package/apps/storefront/src/components/islands/VariantSelector.tsx +3 -3
- package/apps/storefront/src/components/marketing/CardGrid.astro +1 -1
- package/apps/storefront/src/components/marketing/CodeSample.astro +1 -1
- package/apps/storefront/src/components/marketing/CtaBand.astro +1 -1
- package/apps/storefront/src/components/marketing/Faq.astro +1 -1
- package/apps/storefront/src/components/marketing/Integrations.astro +1 -1
- package/apps/storefront/src/components/marketing/MarketingCtas.astro +1 -1
- package/apps/storefront/src/components/marketing/MarketingHero.astro +1 -1
- package/apps/storefront/src/components/marketing/ProofStrip.astro +1 -1
- package/apps/storefront/src/components/marketing/SplitCompare.astro +1 -1
- package/apps/storefront/src/components/marketing/Steps.astro +1 -1
- package/apps/storefront/src/components/marketing/Testimonials.astro +2 -2
- package/apps/storefront/src/components/marketing/TrustBar.astro +1 -1
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +1 -1
- package/apps/storefront/src/components/plp/ActiveFilters.astro +1 -1
- package/apps/storefront/src/components/plp/FacetSidebar.astro +4 -4
- package/apps/storefront/src/components/plp/Pagination.astro +1 -1
- package/apps/storefront/src/components/plp/SortSelect.astro +1 -1
- package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +18 -3
- package/apps/storefront/src/components/the-build/TheBuildIndexView.astro +9 -5
- package/apps/storefront/src/config/devTenantSeed.ts +23 -34
- package/apps/storefront/src/config/resolver.ts +0 -38
- package/apps/storefront/src/layouts/Layout.astro +8 -1
- package/apps/storefront/src/lib/assets/preview-version-cookie.ts +52 -5
- package/apps/storefront/src/lib/blog/pagination.ts +20 -10
- package/apps/storefront/src/lib/blog/presentation.ts +94 -0
- package/apps/storefront/src/lib/blog/rss.ts +1 -1
- package/apps/storefront/src/lib/blog/types.ts +24 -7
- package/apps/storefront/src/lib/breadcrumbs.ts +25 -0
- package/apps/storefront/src/lib/checkoutCommerce.ts +26 -3
- package/apps/storefront/src/lib/chrome/model.ts +4 -0
- package/apps/storefront/src/lib/cloudflare-workers.d.ts +53 -0
- package/apps/storefront/src/lib/compliance/rawComplianceNotices.ts +38 -18
- package/apps/storefront/src/lib/dashboard/delegateGate.ts +28 -0
- package/apps/storefront/src/lib/homeVisualParity.ts +8 -5
- package/apps/storefront/src/lib/jsonld.ts +5 -5
- package/apps/storefront/src/lib/pinnedRouteRewriteBoundary.ts +20 -2
- package/apps/storefront/src/lib/rawChrome.ts +74 -12
- package/apps/storefront/src/lib/runtimeEnv.ts +1 -1
- package/apps/storefront/src/lib/seo/documentTitle.ts +19 -0
- package/apps/storefront/src/lib/shared/addToRelease.ts +32 -0
- package/apps/storefront/src/lib/storyblok/content-model.ts +3 -0
- package/apps/storefront/src/lib/styleGuideThemes.ts +20 -22
- package/apps/storefront/src/pages/[...slug].astro +13 -8
- package/apps/storefront/src/pages/blog/[slug].astro +56 -24
- package/apps/storefront/src/pages/blog/author/[author].astro +17 -9
- package/apps/storefront/src/pages/blog/category/[category].astro +18 -10
- package/apps/storefront/src/pages/blog/index.astro +2 -0
- package/apps/storefront/src/pages/blog/page/[n].astro +2 -0
- package/apps/storefront/src/pages/blog/tag/[tag].astro +18 -10
- package/apps/storefront/src/pages/collections/[handle].astro +5 -4
- package/apps/storefront/src/pages/collections/index.astro +4 -3
- package/apps/storefront/src/pages/index.astro +34 -27
- package/apps/storefront/src/pages/products/[handle].astro +5 -4
- package/apps/storefront/src/pages/saved.astro +4 -3
- package/apps/storefront/src/pages/search.astro +4 -3
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +129 -118
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +19 -15
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +23 -27
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +7 -14
- package/apps/storefront/src/pages/style-guide/index.astro +6 -5
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +32 -7
- package/apps/storefront/src/pages/the-build/[slug].astro +44 -20
- package/apps/storefront/src/pages/the-build/index.astro +2 -0
- package/apps/storefront/src/pages/the-build/page/[n].astro +2 -0
- package/apps/storefront/src/styles/admin.css +47 -0
- package/apps/storefront/src/styles/global.css +104 -56
- package/apps/storefront/src/themes/derivedTokens.ts +34 -0
- package/apps/storefront/src/themes/schema.ts +6 -0
- package/apps/storefront/test/pipeline/harness.ts +164 -0
- package/apps/storefront/test/pipeline/intake-worker.mjs +14 -0
- package/apps/storefront/test/pipeline/pipeline-host.ts +77 -0
- package/apps/storefront/test/pipeline/synthetic-job.ts +156 -0
- package/apps/storefront/vitest.workers.config.cts +67 -0
- package/docs/widget-library-guide.md +1496 -0
- package/package.json +1 -1
- package/packages/cli/src/declared-config.mjs +400 -5
- package/packages/public-runtime/package.json +1 -0
- package/packages/public-runtime/src/checkout.ts +13 -0
- package/packages/public-runtime/src/chrome.ts +93 -2
- package/packages/public-runtime/src/compliance-coherence.d.mts +17 -0
- package/packages/public-runtime/src/compliance-coherence.mjs +84 -0
- package/packages/public-runtime/src/declared-tenant-config.d.mts +1 -1
- package/packages/public-runtime/src/declared-tenant-config.mjs +6 -2
- package/packages/public-runtime/src/extension-contract-values.d.mts +4 -1
- package/packages/public-runtime/src/extension-contract-values.mjs +14 -1
- package/packages/public-runtime/src/extension-contract.ts +2 -0
- package/packages/public-runtime/src/tenant.ts +48 -4
- package/pnpm-lock.runner.yaml +469 -0
- package/scripts/tenant/validate.mjs +27 -1
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Layout from "@/layouts/Layout.astro";
|
|
3
|
+
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
3
4
|
import BlogPaywall from "@/components/blog/BlogPaywall.astro";
|
|
4
5
|
import ReactionBar from "@/components/blog/ReactionBar.astro";
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
6
|
+
import ShareRow from "@/components/blog/ShareRow.astro";
|
|
7
|
+
import { getBlogPost, getBlogSettings, listBlogPosts, resolveBlogRedirect } from "@/lib/blog/provider.js";
|
|
8
|
+
import { authorSlug, categorySlug, getTranslations } from "@/lib/blog/collection.js";
|
|
9
|
+
import { bylineAuthor, resolveBlogPresentation, shareLinks } from "@/lib/blog/presentation.js";
|
|
10
|
+
import { homeCrumbLabel, siteTrail } from "@/lib/breadcrumbs.js";
|
|
7
11
|
import { viewerHasBlogAccess } from "@/lib/blog/access.js";
|
|
8
12
|
import { articleBodyHtml } from "@/lib/blog/teaser.js";
|
|
9
13
|
import { readViewerSession, safeNext } from "@/lib/auth/route.js";
|
|
@@ -41,16 +45,27 @@ const next = safeNext(`${basePath}/blog/${post.slug}/`);
|
|
|
41
45
|
const loginHref = `/auth/login?next=${encodeURIComponent(next)}`;
|
|
42
46
|
|
|
43
47
|
const canonical = new URL(`/blog/${post.slug}/`, canonicalBase + "/").toString();
|
|
48
|
+
const presentation = resolveBlogPresentation(await getBlogSettings(tenant.tenant_id, content));
|
|
49
|
+
// A post that names no author has no byline: never attribute it to anyone (the blog's title, the
|
|
50
|
+
// store) its content does not name.
|
|
51
|
+
const author = bylineAuthor(post.author);
|
|
44
52
|
// The author archive (`/blog/author/<slug>`) lists posts from the curated blog
|
|
45
53
|
// index, and 404s for an author with no LISTED post (an unlisted-only or
|
|
46
54
|
// index-omitted byline). Resolve that up front so the byline links ONLY when the
|
|
47
55
|
// archive will actually render — a byline must never point at a missing page
|
|
48
56
|
// (the invariant `blog/author/[author].astro` documents). Same slug + same source
|
|
49
|
-
// the archive itself filters on, so the two can't disagree.
|
|
50
|
-
|
|
51
|
-
const
|
|
57
|
+
// the archive itself filters on, so the two can't disagree. Tag links follow the
|
|
58
|
+
// same rule against `blog/tag/[tag].astro`.
|
|
59
|
+
const listed = await listBlogPosts(tenant.tenant_id, content, publicationTimestamp);
|
|
60
|
+
const authorArchiveSlug = author ? authorSlug(author) : "";
|
|
61
|
+
const authorHasArchive = authorArchiveSlug !== "" && listed.some(
|
|
52
62
|
(p) => authorSlug(p.author) === authorArchiveSlug,
|
|
53
63
|
);
|
|
64
|
+
const archivedTags = new Set(listed.flatMap((p) => p.tags.map(categorySlug)));
|
|
65
|
+
const tagLinks = post.tags.map((tag) => {
|
|
66
|
+
const slug = categorySlug(tag);
|
|
67
|
+
return { label: tag, href: slug && archivedTags.has(slug) ? `${basePath}/blog/tag/${slug}/` : null };
|
|
68
|
+
});
|
|
54
69
|
const authorUrl = authorHasArchive
|
|
55
70
|
? new URL(`/blog/author/${authorArchiveSlug}/`, canonicalBase + "/").toString()
|
|
56
71
|
: undefined;
|
|
@@ -60,15 +75,21 @@ const articleLd = blogPostingLd(post, {
|
|
|
60
75
|
publisherName: tenant.displayName,
|
|
61
76
|
authorUrl,
|
|
62
77
|
});
|
|
63
|
-
const
|
|
64
|
-
|
|
78
|
+
const trail = siteTrail(
|
|
79
|
+
homeCrumbLabel(await content.getChrome()),
|
|
65
80
|
{ label: "Blog", href: "/blog/" },
|
|
66
81
|
{ label: post.title, href: `/blog/${post.slug}/` },
|
|
67
|
-
|
|
82
|
+
);
|
|
83
|
+
const crumbs = breadcrumbLd(trail, canonicalBase);
|
|
68
84
|
const blogHref = `${basePath}/blog/`;
|
|
69
85
|
const authorHref = authorHasArchive
|
|
70
86
|
? `${basePath}/blog/author/${authorArchiveSlug}/`
|
|
71
87
|
: null;
|
|
88
|
+
const share = shareLinks(presentation.share, {
|
|
89
|
+
url: canonical,
|
|
90
|
+
title: post.title,
|
|
91
|
+
image: post.featuredImage ? new URL(post.featuredImage.src, canonicalBase + "/").toString() : undefined,
|
|
92
|
+
});
|
|
72
93
|
|
|
73
94
|
// I2 — hreflang alternates + per-variant <html lang>. Singleton ⇒ [] alternates
|
|
74
95
|
// and no `lang` override, so a non-translated post is byte-identical to today.
|
|
@@ -104,16 +125,16 @@ const lang = alternates.length ? (post.inLanguage ?? "en") : undefined;
|
|
|
104
125
|
lang={lang}
|
|
105
126
|
>
|
|
106
127
|
<article class="article-shell">
|
|
107
|
-
<
|
|
128
|
+
<Breadcrumbs crumbs={trail} class="breadcrumbs" />
|
|
108
129
|
<header>
|
|
109
|
-
{post.tags[0] && <p class="eyebrow eyebrow--tick">{post.tags[0]}</p>}
|
|
130
|
+
{presentation.postEyebrow && post.tags[0] && <p class="eyebrow eyebrow--tick">{post.tags[0]}</p>}
|
|
110
131
|
<h1>{post.title}</h1>
|
|
111
|
-
<p class="dek">{post.excerpt}</p>
|
|
132
|
+
{presentation.dek && post.excerpt && <p class="dek">{post.excerpt}</p>}
|
|
112
133
|
<div class="byline">
|
|
113
|
-
<span>By {authorHref
|
|
114
|
-
? <a href={authorHref} class="byline-author" rel="author">{
|
|
115
|
-
: <span class="byline-author">{
|
|
116
|
-
<span aria-hidden="true">·</span>
|
|
134
|
+
{author && <span>By {authorHref
|
|
135
|
+
? <a href={authorHref} class="byline-author" rel="author">{author}</a>
|
|
136
|
+
: <span class="byline-author">{author}</span>}</span>}
|
|
137
|
+
{author && <span aria-hidden="true">·</span>}
|
|
117
138
|
<time datetime={post.publishedAt}>{new Date(post.publishedAt).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })}</time>
|
|
118
139
|
</div>
|
|
119
140
|
{translations.length > 1 && (
|
|
@@ -143,26 +164,34 @@ const lang = alternates.length ? (post.inLanguage ?? "en") : undefined;
|
|
|
143
164
|
decoding="async"
|
|
144
165
|
/>
|
|
145
166
|
)}
|
|
146
|
-
<div class="article-grid">
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
167
|
+
<div class:list={["article-grid", { "article-grid--single": !presentation.aside }]}>
|
|
168
|
+
{presentation.aside && (
|
|
169
|
+
<aside>
|
|
170
|
+
<a href={blogHref}>← All articles</a>
|
|
171
|
+
{tagLinks.length > 0 && <div class="article-tags">{tagLinks.map((tag) => tag.href ? <a href={tag.href}>{tag.label}</a> : <span>{tag.label}</span>)}</div>}
|
|
172
|
+
</aside>
|
|
173
|
+
)}
|
|
151
174
|
<div class="blog-main">
|
|
152
175
|
<div class="blog-body" set:html={bodyHtml}></div>
|
|
153
176
|
{gated && !unlock && (
|
|
154
177
|
<BlogPaywall access={post.access ?? "authenticated"} next={next} signedIn={!!viewer} loginHref={loginHref} />
|
|
155
178
|
)}
|
|
179
|
+
{!presentation.aside && tagLinks.length > 0 && (
|
|
180
|
+
<ul class="post-tags" aria-label="Tags">
|
|
181
|
+
{tagLinks.map((tag) => <li>{tag.href ? <a href={tag.href}>{tag.label}</a> : <span>{tag.label}</span>}</li>)}
|
|
182
|
+
</ul>
|
|
183
|
+
)}
|
|
184
|
+
<ShareRow links={share} />
|
|
156
185
|
</div>
|
|
157
186
|
</div>
|
|
158
|
-
<ReactionBar collection="blog" slug={post.slug} />
|
|
187
|
+
{presentation.reactions && <ReactionBar collection="blog" slug={post.slug} />}
|
|
159
188
|
</article>
|
|
160
189
|
</Layout>
|
|
161
190
|
|
|
162
191
|
<style>
|
|
163
192
|
.article-shell { width: min(1120px, calc(100% - 2rem)); margin-inline: auto; padding-block: clamp(2rem, 5vw, 5rem); }
|
|
164
|
-
.
|
|
165
|
-
.breadcrumbs a { color: var(--color-accent-ink); font-weight: 700; }
|
|
193
|
+
.article-shell :global(.breadcrumbs) { font-size: .84rem; }
|
|
194
|
+
.article-shell :global(.breadcrumbs a) { color: var(--color-accent-ink); font-weight: 700; }
|
|
166
195
|
header { max-width: 880px; margin: clamp(2.5rem, 6vw, 5rem) auto clamp(2rem, 5vw, 3.5rem); text-align: center; }
|
|
167
196
|
header .eyebrow { justify-content: center; }
|
|
168
197
|
h1 { margin-top: .9rem; font-size: clamp(2.5rem, 6vw, 5rem); line-height: 1.02; }
|
|
@@ -178,8 +207,11 @@ const lang = alternates.length ? (post.inLanguage ?? "en") : undefined;
|
|
|
178
207
|
.article-grid { display: grid; grid-template-columns: 190px minmax(0, 720px); justify-content: center; gap: clamp(2rem, 6vw, 5rem); margin-top: clamp(3rem, 7vw, 5.5rem); }
|
|
179
208
|
aside { align-self: start; position: sticky; top: 7rem; }
|
|
180
209
|
aside > a { color: var(--color-accent-ink); font-size: .88rem; font-weight: 700; }
|
|
210
|
+
.article-grid--single { grid-template-columns: minmax(0, 720px); }
|
|
181
211
|
.article-tags { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1.2rem; }
|
|
182
|
-
.article-tags span { padding: .3rem .55rem; border: 1px solid var(--color-border); border-radius: var(--radius-full); color: var(--color-muted); font-size: .7rem; }
|
|
212
|
+
.article-tags > *, .post-tags :is(a, span) { padding: .3rem .55rem; border: 1px solid var(--color-border); border-radius: var(--radius-full); color: var(--color-muted); font-size: .7rem; text-decoration: none; }
|
|
213
|
+
.article-tags a:hover, .post-tags a:hover { color: var(--color-accent-ink); border-color: var(--color-accent); }
|
|
214
|
+
.post-tags { display: flex; flex-wrap: wrap; gap: .45rem; margin: 2rem 0 0; padding: 0; list-style: none; }
|
|
183
215
|
.blog-main { min-width: 0; }
|
|
184
216
|
.blog-body { min-width: 0; font-size: 1.05rem; line-height: 1.78; }
|
|
185
217
|
.blog-body :global(p), .blog-body :global(ul), .blog-body :global(ol), .blog-body :global(blockquote), .blog-body :global(figure), .blog-body :global(table), .blog-body :global(pre) { margin-block: 1.25rem; }
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Layout from "@/layouts/Layout.astro";
|
|
3
|
+
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
3
4
|
import PostAccessBadge from "@/components/blog/PostAccessBadge.astro";
|
|
4
|
-
import { listBlogPosts } from "@/lib/blog/provider.js";
|
|
5
|
+
import { getBlogSettings, listBlogPosts } from "@/lib/blog/provider.js";
|
|
6
|
+
import { resolveBlogPresentation } from "@/lib/blog/presentation.js";
|
|
7
|
+
import { homeCrumbLabel, siteTrail } from "@/lib/breadcrumbs.js";
|
|
5
8
|
import { authorSlug, getAuthors } from "@/lib/blog/collection.js";
|
|
6
9
|
import { breadcrumbLd } from "@/lib/jsonld.js";
|
|
7
10
|
|
|
@@ -12,7 +15,7 @@ const { tenant, basePath, canonicalBase, content, publicationTimestamp } = Astro
|
|
|
12
15
|
const slug = Astro.params.author ?? "";
|
|
13
16
|
|
|
14
17
|
const allPosts = await listBlogPosts(tenant.tenant_id, content, publicationTimestamp);
|
|
15
|
-
const posts = allPosts.filter((post) => authorSlug(post.author) === slug);
|
|
18
|
+
const posts = slug ? allPosts.filter((post) => authorSlug(post.author) === slug) : [];
|
|
16
19
|
if (posts.length === 0) return new Response(null, { status: 404 });
|
|
17
20
|
|
|
18
21
|
const authorName = posts[0]!.author;
|
|
@@ -49,11 +52,13 @@ const itemListLd = {
|
|
|
49
52
|
url: new URL(`/blog/${post.slug}/`, canonicalBase + "/").toString(),
|
|
50
53
|
})),
|
|
51
54
|
};
|
|
52
|
-
const
|
|
53
|
-
|
|
55
|
+
const presentation = resolveBlogPresentation(await getBlogSettings(tenant.tenant_id, content));
|
|
56
|
+
const trail = siteTrail(
|
|
57
|
+
homeCrumbLabel(await content.getChrome()),
|
|
54
58
|
{ label: "Blog", href: "/blog/" },
|
|
55
59
|
{ label: authorName, href: `/blog/author/${slug}/` },
|
|
56
|
-
|
|
60
|
+
);
|
|
61
|
+
const crumbs = breadcrumbLd(trail, canonicalBase);
|
|
57
62
|
---
|
|
58
63
|
|
|
59
64
|
<Layout
|
|
@@ -63,9 +68,9 @@ const crumbs = breadcrumbLd([
|
|
|
63
68
|
fullBleed
|
|
64
69
|
jsonLd={[crumbs, profileLd, itemListLd]}
|
|
65
70
|
>
|
|
66
|
-
<header class="author-hero">
|
|
71
|
+
<header class:list={["author-hero", { "author-hero--plain": !presentation.hero }]}>
|
|
67
72
|
<div class="blog-shell">
|
|
68
|
-
<
|
|
73
|
+
<Breadcrumbs crumbs={trail} class="breadcrumbs" />
|
|
69
74
|
<div class="author-id">
|
|
70
75
|
{avatarUrl && <img class="avatar" src={avatarUrl} alt={authorName} width="96" height="96" decoding="async" />}
|
|
71
76
|
<div>
|
|
@@ -115,8 +120,11 @@ const crumbs = breadcrumbLd([
|
|
|
115
120
|
<style>
|
|
116
121
|
.blog-shell { width: min(1180px, calc(100% - 2rem)); margin-inline: auto; }
|
|
117
122
|
.author-hero { padding: clamp(3.5rem, 8vw, 6rem) 0 clamp(2rem, 5vw, 3.5rem); background: linear-gradient(145deg, var(--color-primary), color-mix(in srgb, var(--color-primary) 82%, var(--color-accent))); color: var(--color-primary-contrast); }
|
|
118
|
-
.
|
|
119
|
-
.breadcrumbs
|
|
123
|
+
.author-hero :global(.breadcrumbs) { font-size: .84rem; }
|
|
124
|
+
.author-hero:not(.author-hero--plain) :global(.breadcrumbs), .author-hero:not(.author-hero--plain) :global(.breadcrumbs *) { color: color-mix(in srgb, var(--color-primary-contrast) 78%, transparent); }
|
|
125
|
+
.author-hero :global(.breadcrumbs a) { font-weight: 700; }
|
|
126
|
+
.author-hero--plain { padding: clamp(2rem, 5vw, 3.5rem) 0 0; background: none; color: inherit; }
|
|
127
|
+
.author-hero--plain :is(.eyebrow, .author-title, .author-bio) { color: var(--color-muted); }
|
|
120
128
|
.author-id { display: flex; gap: 1.5rem; align-items: center; margin-top: 1.5rem; }
|
|
121
129
|
.avatar { flex: 0 0 auto; width: 96px; height: 96px; border-radius: var(--radius-full); object-fit: cover; box-shadow: var(--shadow-md); }
|
|
122
130
|
.author-hero h1 { margin-top: .5rem; color: inherit; font-size: clamp(2.4rem, 6vw, 4.2rem); }
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Layout from "@/layouts/Layout.astro";
|
|
3
|
+
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
3
4
|
import PostGrid from "@/components/blog/PostGrid.astro";
|
|
4
|
-
import { listBlogPosts } from "@/lib/blog/provider.js";
|
|
5
|
+
import { getBlogSettings, listBlogPosts } from "@/lib/blog/provider.js";
|
|
5
6
|
import { collectPostsByTerm } from "@/lib/blog/collection.js";
|
|
7
|
+
import { resolveBlogPresentation } from "@/lib/blog/presentation.js";
|
|
8
|
+
import { homeCrumbLabel, siteTrail } from "@/lib/breadcrumbs.js";
|
|
6
9
|
import { breadcrumbLd } from "@/lib/jsonld.js";
|
|
7
10
|
|
|
8
11
|
const { tenant, basePath, canonicalBase, content, publicationTimestamp } = Astro.locals;
|
|
@@ -14,6 +17,7 @@ const all = await listBlogPosts(tenant.tenant_id, content, publicationTimestamp)
|
|
|
14
17
|
const match = collectPostsByTerm(all, "categories", param);
|
|
15
18
|
if (!match) return new Response(null, { status: 404 });
|
|
16
19
|
const { label, posts } = match;
|
|
20
|
+
const presentation = resolveBlogPresentation(await getBlogSettings(tenant.tenant_id, content));
|
|
17
21
|
|
|
18
22
|
const path = `/blog/category/${param}/`;
|
|
19
23
|
const itemList = {
|
|
@@ -27,11 +31,12 @@ const itemList = {
|
|
|
27
31
|
url: new URL(`/blog/${post.slug}/`, canonicalBase + "/").toString(),
|
|
28
32
|
})),
|
|
29
33
|
};
|
|
30
|
-
const
|
|
31
|
-
|
|
34
|
+
const trail = siteTrail(
|
|
35
|
+
homeCrumbLabel(await content.getChrome()),
|
|
32
36
|
{ label: "Blog", href: "/blog/" },
|
|
33
37
|
{ label, href: path },
|
|
34
|
-
|
|
38
|
+
);
|
|
39
|
+
const crumbs = breadcrumbLd(trail, canonicalBase);
|
|
35
40
|
---
|
|
36
41
|
|
|
37
42
|
<Layout
|
|
@@ -41,12 +46,12 @@ const crumbs = breadcrumbLd([
|
|
|
41
46
|
fullBleed
|
|
42
47
|
jsonLd={[crumbs, itemList]}
|
|
43
48
|
>
|
|
44
|
-
<header class="blog-hero">
|
|
49
|
+
<header class:list={["blog-hero", { "blog-hero--plain": !presentation.hero }]}>
|
|
45
50
|
<div class="blog-shell">
|
|
46
|
-
<
|
|
51
|
+
<Breadcrumbs crumbs={trail} class="breadcrumbs" />
|
|
47
52
|
<p class="eyebrow eyebrow--tick">Category</p>
|
|
48
53
|
<h1>{label}</h1>
|
|
49
|
-
<p>{posts.length} {posts.length === 1 ? "article" : "articles"} in this category.</p>
|
|
54
|
+
<p class="blog-hero__tagline">{posts.length} {posts.length === 1 ? "article" : "articles"} in this category.</p>
|
|
50
55
|
</div>
|
|
51
56
|
</header>
|
|
52
57
|
|
|
@@ -58,10 +63,13 @@ const crumbs = breadcrumbLd([
|
|
|
58
63
|
<style>
|
|
59
64
|
.blog-shell { width: min(1180px, calc(100% - 2rem)); margin-inline: auto; }
|
|
60
65
|
.blog-hero { padding: clamp(4.5rem, 9vw, 8rem) 0 clamp(3rem, 6vw, 5rem); background: linear-gradient(145deg, var(--color-primary), color-mix(in srgb, var(--color-primary) 82%, var(--color-accent))); color: var(--color-primary-contrast); }
|
|
61
|
-
.
|
|
62
|
-
.breadcrumbs
|
|
66
|
+
.blog-hero :global(.breadcrumbs) { margin-bottom: 1rem; font-size: .84rem; }
|
|
67
|
+
.blog-hero:not(.blog-hero--plain) :global(.breadcrumbs), .blog-hero:not(.blog-hero--plain) :global(.breadcrumbs *) { color: color-mix(in srgb, var(--color-primary-contrast) 78%, transparent); }
|
|
68
|
+
.blog-hero :global(.breadcrumbs a) { font-weight: 700; }
|
|
63
69
|
.blog-hero h1 { max-width: 14ch; margin-top: .75rem; color: inherit; font-size: clamp(2.6rem, 6vw, 4.6rem); }
|
|
64
|
-
.blog-
|
|
70
|
+
.blog-hero__tagline { max-width: 58ch; margin-top: 1rem; color: color-mix(in srgb, var(--color-primary-contrast) 82%, transparent); font-size: 1.05rem; }
|
|
65
71
|
.blog-hero .eyebrow { color: color-mix(in srgb, var(--color-primary-contrast) 75%, transparent); }
|
|
72
|
+
.blog-hero--plain { padding: clamp(2rem, 5vw, 3.5rem) 0 0; background: none; color: inherit; }
|
|
73
|
+
.blog-hero--plain .blog-hero__tagline, .blog-hero--plain .eyebrow { color: var(--color-muted); }
|
|
66
74
|
.blog-layout { padding-block: clamp(3rem, 7vw, 6rem); }
|
|
67
75
|
</style>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import BlogIndexView from "@/components/blog/BlogIndexView.astro";
|
|
3
3
|
import { getBlogSettings, listBlogPosts } from "@/lib/blog/provider.js";
|
|
4
4
|
import { paginateBlogPosts, resolveBlogIndexCopy } from "@/lib/blog/pagination.js";
|
|
5
|
+
import { resolveBlogPresentation } from "@/lib/blog/presentation.js";
|
|
5
6
|
|
|
6
7
|
const { tenant, basePath, canonicalBase, content, publicationTimestamp } = Astro.locals;
|
|
7
8
|
const posts = await listBlogPosts(tenant.tenant_id, content, publicationTimestamp);
|
|
@@ -28,6 +29,7 @@ const tags = [...new Set(posts.flatMap((post) => post.tags))].sort((a, b) => a.l
|
|
|
28
29
|
basePath={basePath}
|
|
29
30
|
canonicalBase={canonicalBase}
|
|
30
31
|
copy={copy}
|
|
32
|
+
presentation={resolveBlogPresentation(settings)}
|
|
31
33
|
tags={tags}
|
|
32
34
|
pagePosts={pagePosts}
|
|
33
35
|
page={1}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import BlogIndexView from "@/components/blog/BlogIndexView.astro";
|
|
6
6
|
import { getBlogSettings, listBlogPosts } from "@/lib/blog/provider.js";
|
|
7
7
|
import { paginateBlogPosts, parseBlogPageParam, resolveBlogIndexCopy } from "@/lib/blog/pagination.js";
|
|
8
|
+
import { resolveBlogPresentation } from "@/lib/blog/presentation.js";
|
|
8
9
|
|
|
9
10
|
const page = parseBlogPageParam(Astro.params.n);
|
|
10
11
|
if (page === null) return new Response(null, { status: 404 });
|
|
@@ -32,6 +33,7 @@ const tags = [...new Set(posts.flatMap((post) => post.tags))].sort((a, b) => a.l
|
|
|
32
33
|
basePath={basePath}
|
|
33
34
|
canonicalBase={canonicalBase}
|
|
34
35
|
copy={copy}
|
|
36
|
+
presentation={resolveBlogPresentation(settings)}
|
|
35
37
|
tags={tags}
|
|
36
38
|
pagePosts={sliced.posts}
|
|
37
39
|
page={sliced.page}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Layout from "@/layouts/Layout.astro";
|
|
3
|
+
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
3
4
|
import PostGrid from "@/components/blog/PostGrid.astro";
|
|
4
|
-
import { listBlogPosts } from "@/lib/blog/provider.js";
|
|
5
|
+
import { getBlogSettings, listBlogPosts } from "@/lib/blog/provider.js";
|
|
5
6
|
import { collectPostsByTerm } from "@/lib/blog/collection.js";
|
|
7
|
+
import { resolveBlogPresentation } from "@/lib/blog/presentation.js";
|
|
8
|
+
import { homeCrumbLabel, siteTrail } from "@/lib/breadcrumbs.js";
|
|
6
9
|
import { breadcrumbLd } from "@/lib/jsonld.js";
|
|
7
10
|
|
|
8
11
|
const { tenant, basePath, canonicalBase, content, publicationTimestamp } = Astro.locals;
|
|
@@ -12,6 +15,7 @@ const all = await listBlogPosts(tenant.tenant_id, content, publicationTimestamp)
|
|
|
12
15
|
const match = collectPostsByTerm(all, "tags", param);
|
|
13
16
|
if (!match) return new Response(null, { status: 404 });
|
|
14
17
|
const { label, posts } = match;
|
|
18
|
+
const presentation = resolveBlogPresentation(await getBlogSettings(tenant.tenant_id, content));
|
|
15
19
|
|
|
16
20
|
const path = `/blog/tag/${param}/`;
|
|
17
21
|
const itemList = {
|
|
@@ -25,11 +29,12 @@ const itemList = {
|
|
|
25
29
|
url: new URL(`/blog/${post.slug}/`, canonicalBase + "/").toString(),
|
|
26
30
|
})),
|
|
27
31
|
};
|
|
28
|
-
const
|
|
29
|
-
|
|
32
|
+
const trail = siteTrail(
|
|
33
|
+
homeCrumbLabel(await content.getChrome()),
|
|
30
34
|
{ label: "Blog", href: "/blog/" },
|
|
31
35
|
{ label: `#${label}`, href: path },
|
|
32
|
-
|
|
36
|
+
);
|
|
37
|
+
const crumbs = breadcrumbLd(trail, canonicalBase);
|
|
33
38
|
---
|
|
34
39
|
|
|
35
40
|
<Layout
|
|
@@ -39,12 +44,12 @@ const crumbs = breadcrumbLd([
|
|
|
39
44
|
fullBleed
|
|
40
45
|
jsonLd={[crumbs, itemList]}
|
|
41
46
|
>
|
|
42
|
-
<header class="blog-hero">
|
|
47
|
+
<header class:list={["blog-hero", { "blog-hero--plain": !presentation.hero }]}>
|
|
43
48
|
<div class="blog-shell">
|
|
44
|
-
<
|
|
49
|
+
<Breadcrumbs crumbs={trail} class="breadcrumbs" />
|
|
45
50
|
<p class="eyebrow eyebrow--tick">Tag</p>
|
|
46
51
|
<h1>#{label}</h1>
|
|
47
|
-
<p>{posts.length} {posts.length === 1 ? "article" : "articles"} with this tag.</p>
|
|
52
|
+
<p class="blog-hero__tagline">{posts.length} {posts.length === 1 ? "article" : "articles"} with this tag.</p>
|
|
48
53
|
</div>
|
|
49
54
|
</header>
|
|
50
55
|
|
|
@@ -56,10 +61,13 @@ const crumbs = breadcrumbLd([
|
|
|
56
61
|
<style>
|
|
57
62
|
.blog-shell { width: min(1180px, calc(100% - 2rem)); margin-inline: auto; }
|
|
58
63
|
.blog-hero { padding: clamp(4.5rem, 9vw, 8rem) 0 clamp(3rem, 6vw, 5rem); background: linear-gradient(145deg, var(--color-primary), color-mix(in srgb, var(--color-primary) 82%, var(--color-accent))); color: var(--color-primary-contrast); }
|
|
59
|
-
.
|
|
60
|
-
.breadcrumbs
|
|
64
|
+
.blog-hero :global(.breadcrumbs) { margin-bottom: 1rem; font-size: .84rem; }
|
|
65
|
+
.blog-hero:not(.blog-hero--plain) :global(.breadcrumbs), .blog-hero:not(.blog-hero--plain) :global(.breadcrumbs *) { color: color-mix(in srgb, var(--color-primary-contrast) 78%, transparent); }
|
|
66
|
+
.blog-hero :global(.breadcrumbs a) { font-weight: 700; }
|
|
61
67
|
.blog-hero h1 { max-width: 14ch; margin-top: .75rem; color: inherit; font-size: clamp(2.6rem, 6vw, 4.6rem); }
|
|
62
|
-
.blog-
|
|
68
|
+
.blog-hero__tagline { max-width: 58ch; margin-top: 1rem; color: color-mix(in srgb, var(--color-primary-contrast) 82%, transparent); font-size: 1.05rem; }
|
|
63
69
|
.blog-hero .eyebrow { color: color-mix(in srgb, var(--color-primary-contrast) 75%, transparent); }
|
|
70
|
+
.blog-hero--plain { padding: clamp(2rem, 5vw, 3.5rem) 0 0; background: none; color: inherit; }
|
|
71
|
+
.blog-hero--plain .blog-hero__tagline, .blog-hero--plain .eyebrow { color: var(--color-muted); }
|
|
64
72
|
.blog-layout { padding-block: clamp(3rem, 7vw, 6rem); }
|
|
65
73
|
</style>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import Layout from "@/layouts/Layout.astro";
|
|
9
9
|
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
10
|
+
import { homeCrumbLabel, siteTrail } from "@/lib/breadcrumbs";
|
|
10
11
|
import ProductCard from "@/components/ProductCard.astro";
|
|
11
12
|
import EmptyState from "@/components/EmptyState.astro";
|
|
12
13
|
import FacetSidebar from "@/components/plp/FacetSidebar.astro";
|
|
@@ -48,10 +49,10 @@ const basePath = `/collections/${handle}`;
|
|
|
48
49
|
// breadcrumbs, and EmptyState — those prefix on their own via withBase.
|
|
49
50
|
const linkBase = withBase(Astro.locals.basePath, basePath);
|
|
50
51
|
|
|
51
|
-
const crumbs =
|
|
52
|
-
|
|
52
|
+
const crumbs = siteTrail(
|
|
53
|
+
homeCrumbLabel(await Astro.locals.content.getChrome()),
|
|
53
54
|
{ label: collection.title, href: basePath },
|
|
54
|
-
|
|
55
|
+
);
|
|
55
56
|
|
|
56
57
|
const heroImg = collection.image;
|
|
57
58
|
const desc = collection.description_text || collection.description_html?.replace(/<[^>]+>/g, "");
|
|
@@ -93,7 +94,7 @@ if (query.priceMin != null || query.priceMax != null) {
|
|
|
93
94
|
<header class="bg-[var(--color-primary)] text-[var(--color-primary-contrast)]">
|
|
94
95
|
<div class="container-prose grid gap-6 py-[clamp(2.5rem,6vw,4.5rem)] md:grid-cols-[1fr_auto] md:items-end">
|
|
95
96
|
<div data-reveal>
|
|
96
|
-
<p class="eyebrow mb-3
|
|
97
|
+
<p class="eyebrow mb-3 text-[color-mix(in_srgb,var(--color-primary-contrast)_70%,transparent)]">
|
|
97
98
|
Collection
|
|
98
99
|
</p>
|
|
99
100
|
<h1 class="display-xl">
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import Layout from "@/layouts/Layout.astro";
|
|
7
7
|
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
8
|
+
import { homeCrumbLabel, siteTrail } from "@/lib/breadcrumbs";
|
|
8
9
|
import CollectionCard from "@/components/CollectionCard.astro";
|
|
9
10
|
import EmptyState from "@/components/EmptyState.astro";
|
|
10
11
|
import { breadcrumbLd } from "@/lib/jsonld";
|
|
@@ -22,10 +23,10 @@ const [allCollections, activeProducts] = await Promise.all([
|
|
|
22
23
|
]);
|
|
23
24
|
const collections = nonEmptyCollections(allCollections, activeProducts);
|
|
24
25
|
|
|
25
|
-
const crumbs =
|
|
26
|
-
|
|
26
|
+
const crumbs = siteTrail(
|
|
27
|
+
homeCrumbLabel(await Astro.locals.content.getChrome()),
|
|
27
28
|
{ label: "Collections", href: "/collections" },
|
|
28
|
-
|
|
29
|
+
);
|
|
29
30
|
---
|
|
30
31
|
|
|
31
32
|
<Layout
|
|
@@ -67,7 +67,9 @@ if (rawHtml && rawPolicy.allowed) {
|
|
|
67
67
|
// verbatim (backward-compatible).
|
|
68
68
|
const chromeWrapper = await content.getChromeHtml("/");
|
|
69
69
|
const notices = {
|
|
70
|
-
nicotineWarning:
|
|
70
|
+
nicotineWarning: tenant.compliance?.nicotineWarning,
|
|
71
|
+
pactAct: !!tenant.compliance?.pactAct,
|
|
72
|
+
minAge: tenant.compliance?.minAge,
|
|
71
73
|
shippingRestriction: tenant.compliance?.shippingRestriction,
|
|
72
74
|
exciseTaxEnabled: capabilities.exciseTax.enabled && features.catalog,
|
|
73
75
|
};
|
|
@@ -173,7 +175,8 @@ const homeSectionWidgets = marketing
|
|
|
173
175
|
---
|
|
174
176
|
|
|
175
177
|
<Layout
|
|
176
|
-
title={
|
|
178
|
+
title={tenant.displayName}
|
|
179
|
+
seoTitle={home.seoTitle}
|
|
177
180
|
description={home.seoDescription}
|
|
178
181
|
path="/"
|
|
179
182
|
ogType="website"
|
|
@@ -226,13 +229,15 @@ const homeSectionWidgets = marketing
|
|
|
226
229
|
const b = r.block;
|
|
227
230
|
const media = editorialMediaView(b, tenant.tenant_id);
|
|
228
231
|
const right = b.align !== "left";
|
|
232
|
+
const textOnly = media.kind === "none";
|
|
229
233
|
return (
|
|
230
234
|
<Section tone="bg">
|
|
231
235
|
<div
|
|
232
236
|
class:list={[
|
|
233
|
-
"grid items-center gap-10 md:grid-cols-2",
|
|
234
|
-
right ? "" : "md:[&>*:first-child]:order-2",
|
|
237
|
+
textOnly ? "max-w-[68ch]" : "grid items-center gap-10 md:grid-cols-2",
|
|
238
|
+
textOnly || right ? "" : "md:[&>*:first-child]:order-2",
|
|
235
239
|
]}
|
|
240
|
+
data-editorial-layout={textOnly ? "text" : "media"}
|
|
236
241
|
>
|
|
237
242
|
<div data-reveal>
|
|
238
243
|
{b.eyebrow && <p class="eyebrow eyebrow--tick mb-4">{b.eyebrow}</p>}
|
|
@@ -251,30 +256,32 @@ const homeSectionWidgets = marketing
|
|
|
251
256
|
</div>
|
|
252
257
|
)}
|
|
253
258
|
</div>
|
|
254
|
-
|
|
255
|
-
<div
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
259
|
+
{media.kind !== "none" && (
|
|
260
|
+
<div data-reveal>
|
|
261
|
+
<div class="overflow-hidden rounded-[var(--radius-lg)] shadow-[var(--shadow-md)]">
|
|
262
|
+
{media.kind === "image" ? (
|
|
263
|
+
<img
|
|
264
|
+
src={media.src}
|
|
265
|
+
alt={media.alt}
|
|
266
|
+
width="1000"
|
|
267
|
+
height="750"
|
|
268
|
+
loading="lazy"
|
|
269
|
+
class="h-full w-full object-cover"
|
|
270
|
+
/>
|
|
271
|
+
) : (
|
|
272
|
+
<Img
|
|
273
|
+
seed={media.seed}
|
|
274
|
+
label={media.label}
|
|
275
|
+
alt=""
|
|
276
|
+
width={1000}
|
|
277
|
+
height={750}
|
|
278
|
+
sizes="(min-width:768px) 48vw, 100vw"
|
|
279
|
+
class="h-full w-full object-cover"
|
|
280
|
+
/>
|
|
281
|
+
)}
|
|
282
|
+
</div>
|
|
276
283
|
</div>
|
|
277
|
-
|
|
284
|
+
)}
|
|
278
285
|
</div>
|
|
279
286
|
</Section>
|
|
280
287
|
);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import Layout from "@/layouts/Layout.astro";
|
|
9
9
|
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
10
|
+
import { homeCrumbLabel, siteTrail } from "@/lib/breadcrumbs";
|
|
10
11
|
import Badge from "@/components/Badge.astro";
|
|
11
12
|
import ProductCard from "@/components/ProductCard.astro";
|
|
12
13
|
import Section from "@/components/Section.astro";
|
|
@@ -95,13 +96,13 @@ const related = await (async () => {
|
|
|
95
96
|
return [...out.values()].slice(0, 4);
|
|
96
97
|
})();
|
|
97
98
|
|
|
98
|
-
const crumbs =
|
|
99
|
-
|
|
99
|
+
const crumbs = siteTrail(
|
|
100
|
+
homeCrumbLabel(await Astro.locals.content.getChrome()),
|
|
100
101
|
...(product.collections[0]
|
|
101
102
|
? [{ label: titleCase(product.collections[0]), href: `/collections/${product.collections[0]}` }]
|
|
102
103
|
: []),
|
|
103
104
|
{ label: product.title, href: `/products/${product.handle}` },
|
|
104
|
-
|
|
105
|
+
);
|
|
105
106
|
|
|
106
107
|
function titleCase(s: string) {
|
|
107
108
|
return s.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
@@ -343,7 +344,7 @@ const jsonLd = [productJsonLd, breadcrumbLd(crumbs, canonicalBase)];
|
|
|
343
344
|
|
|
344
345
|
{compliance?.nicotineWarning && (
|
|
345
346
|
<div class="mt-6">
|
|
346
|
-
<NicotineWarning variant="block" />
|
|
347
|
+
<NicotineWarning variant="block" declaration={compliance.nicotineWarning} />
|
|
347
348
|
</div>
|
|
348
349
|
)}
|
|
349
350
|
|
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import Layout from "@/layouts/Layout.astro";
|
|
7
7
|
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
8
|
+
import { homeCrumbLabel, siteTrail } from "@/lib/breadcrumbs";
|
|
8
9
|
import SavedList from "@/components/islands/SavedList.tsx";
|
|
9
10
|
|
|
10
11
|
const { tenant } = Astro.locals;
|
|
11
12
|
// Commerce-only route: marketing tenants have no saved-items surface.
|
|
12
13
|
if (!Astro.locals.features.savedItems) return new Response(null, { status: 404 });
|
|
13
|
-
const crumbs =
|
|
14
|
-
|
|
14
|
+
const crumbs = siteTrail(
|
|
15
|
+
homeCrumbLabel(await Astro.locals.content.getChrome()),
|
|
15
16
|
{ label: "Saved", href: "/saved" },
|
|
16
|
-
|
|
17
|
+
);
|
|
17
18
|
---
|
|
18
19
|
|
|
19
20
|
<Layout
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import Layout from "@/layouts/Layout.astro";
|
|
13
13
|
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
14
|
+
import { homeCrumbLabel, siteTrail } from "@/lib/breadcrumbs";
|
|
14
15
|
import ProductCard from "@/components/ProductCard.astro";
|
|
15
16
|
import EmptyState from "@/components/EmptyState.astro";
|
|
16
17
|
import Section from "@/components/Section.astro";
|
|
@@ -42,10 +43,10 @@ if (isJson) {
|
|
|
42
43
|
const results = q ? await tot.search(q, 24) : [];
|
|
43
44
|
const featured = q ? [] : await tot.getFeatured(8);
|
|
44
45
|
|
|
45
|
-
const crumbs =
|
|
46
|
-
|
|
46
|
+
const crumbs = siteTrail(
|
|
47
|
+
homeCrumbLabel(await Astro.locals.content.getChrome()),
|
|
47
48
|
{ label: "Search", href: "/search" },
|
|
48
|
-
|
|
49
|
+
);
|
|
49
50
|
---
|
|
50
51
|
|
|
51
52
|
<Layout
|