@tokenoftrust/storefront-runner 6.1.4 → 6.1.5
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/public/shared/commerce-chrome.css +5 -0
- package/apps/storefront/src/components/content/storefront/chrome/SiteFooter.astro +9 -6
- package/apps/storefront/src/lib/content/chrome/examples/tokenoftrust.chrome-assignments.example.json +1 -1
- package/apps/storefront/src/lib/content/shared/rawChrome.ts +18 -16
- package/package.json +1 -1
- package/packages/public-runtime/src/chrome.ts +1 -1
- package/scripts/tenant/validate.mjs +1 -1
|
@@ -421,6 +421,11 @@ a.article-card__tag:hover { text-decoration: underline; }
|
|
|
421
421
|
.footer-bottom nav ul { display: flex; flex-wrap: wrap; gap: 6px 20px; list-style: none; margin: 0; padding: 0; }
|
|
422
422
|
.footer-bottom nav a:hover { color: var(--color-primary-contrast); text-decoration: underline; }
|
|
423
423
|
|
|
424
|
+
.site-footer[data-variant="minimal"] { padding-block: clamp(1.5rem, 3vw, 2rem); }
|
|
425
|
+
.site-footer[data-variant="minimal"] .footer-bottom { margin-top: 0; padding-top: 0; border-top: 0; }
|
|
426
|
+
.site-footer[data-variant="minimal"] .footer-disclaimer,
|
|
427
|
+
.site-footer[data-variant="minimal"] .footer-warning { margin-top: 0; margin-bottom: 1.5rem; }
|
|
428
|
+
|
|
424
429
|
/* Source-parity light footer. Native details provide real zero-JS disclosure
|
|
425
430
|
semantics on mobile; desktop CSS presents the same content as open columns. */
|
|
426
431
|
.site-footer[data-variant="light-collapsible"] {
|
|
@@ -73,10 +73,12 @@ const bottomLinkStamp = (l: ChromeLink, index: number) =>
|
|
|
73
73
|
});
|
|
74
74
|
const newsletterAgeAttestation = resolveNewsletterAgeAttestation(newsletter?.ageAttestation);
|
|
75
75
|
const collapsible = variant === "light-collapsible";
|
|
76
|
+
const minimal = variant === "minimal";
|
|
76
77
|
---
|
|
77
78
|
|
|
78
79
|
<footer class:list={["border-t border-border bg-surface text-text", collapsible && "footer-collapsible pb-[62px] md:pb-0", className]} data-variant={variant}>
|
|
79
|
-
<div class="container-prose py-12">
|
|
80
|
+
<div class:list={["container-prose", minimal ? "py-6 md:py-8" : "py-12"]}>
|
|
81
|
+
{!minimal && (
|
|
80
82
|
<div class="grid gap-10 md:grid-cols-[1.4fr_repeat(3,1fr)]">
|
|
81
83
|
<!-- Brand -->
|
|
82
84
|
<div>
|
|
@@ -176,10 +178,11 @@ const collapsible = variant === "light-collapsible";
|
|
|
176
178
|
})
|
|
177
179
|
}
|
|
178
180
|
</div>
|
|
181
|
+
)}
|
|
179
182
|
|
|
180
183
|
<!-- Newsletter -->
|
|
181
184
|
{
|
|
182
|
-
newsletter && (
|
|
185
|
+
!minimal && newsletter && (
|
|
183
186
|
<div class="mt-10 border-t border-border pt-8">
|
|
184
187
|
{newsletter.title && <h3 class="font-display text-base font-semibold">{newsletter.title}</h3>}
|
|
185
188
|
{newsletter.body && <p class="mt-1 text-sm text-muted">{newsletter.body}</p>}
|
|
@@ -225,7 +228,7 @@ const collapsible = variant === "light-collapsible";
|
|
|
225
228
|
|
|
226
229
|
<!-- Payment badges -->
|
|
227
230
|
{
|
|
228
|
-
payments.length > 0 && (
|
|
231
|
+
!minimal && payments.length > 0 && (
|
|
229
232
|
<div class="mt-8 flex flex-wrap items-center gap-2" aria-label="Accepted payment methods">
|
|
230
233
|
{payments.map((p) => (
|
|
231
234
|
<span class="rounded border border-border px-2 py-1 font-mono text-[0.62rem] uppercase tracking-[0.08em] text-muted">
|
|
@@ -239,7 +242,7 @@ const collapsible = variant === "light-collapsible";
|
|
|
239
242
|
<!-- Merchant legal copy (verbatim) -->
|
|
240
243
|
{
|
|
241
244
|
disclaimer?.trim() && (
|
|
242
|
-
<p class="mt-8 border-t border-border pt-6
|
|
245
|
+
<p class:list={["text-xs text-muted", !minimal && "mt-8 border-t border-border pt-6"]} data-footer-disclaimer>
|
|
243
246
|
{disclaimer.trim().split(/\r?\n/).map((line, i) => (
|
|
244
247
|
<>
|
|
245
248
|
{i > 0 && <br />}
|
|
@@ -253,14 +256,14 @@ const collapsible = variant === "light-collapsible";
|
|
|
253
256
|
<!-- Compliance / trust line -->
|
|
254
257
|
{
|
|
255
258
|
compliance && (
|
|
256
|
-
<p class="mt-8 border-t border-border pt-6
|
|
259
|
+
<p class:list={["text-xs text-muted", minimal ? disclaimer?.trim() && "mt-4" : "mt-8 border-t border-border pt-6"]}>{compliance}</p>
|
|
257
260
|
)
|
|
258
261
|
}
|
|
259
262
|
|
|
260
263
|
<!-- Legal bottom bar: text line + optional policy links -->
|
|
261
264
|
{
|
|
262
265
|
(legal || bottomLinks.length > 0) && (
|
|
263
|
-
<div class="
|
|
266
|
+
<div class:list={["flex flex-col gap-3 text-xs text-muted sm:flex-row sm:items-center sm:justify-between", (!minimal || disclaimer?.trim() || compliance) && "mt-4 border-t border-border pt-4"]}>
|
|
264
267
|
{legal && <p class="font-mono text-[0.68rem] uppercase tracking-[0.08em]">{legal}</p>}
|
|
265
268
|
{
|
|
266
269
|
bottomLinks.length > 0 && (
|
|
@@ -49,11 +49,12 @@ import type {
|
|
|
49
49
|
ChromeNavItem,
|
|
50
50
|
ChromeSearch,
|
|
51
51
|
ChromeUtilityNav,
|
|
52
|
+
FooterVariant,
|
|
52
53
|
HeaderVariant,
|
|
53
54
|
SocialLink,
|
|
54
55
|
} from "../chrome/model.js";
|
|
55
56
|
import { isSafeAgeGateHref, resolveAgeGateCopy, resolveChromeAssignment } from "../chrome/model.js";
|
|
56
|
-
import { resolveHeaderVariant } from "../chrome/variant.js";
|
|
57
|
+
import { resolveFooterVariant, resolveHeaderVariant } from "../chrome/variant.js";
|
|
57
58
|
|
|
58
59
|
/** HTML-escape text/attribute content. Covers the five significant characters. */
|
|
59
60
|
function esc(value: string): string {
|
|
@@ -534,11 +535,12 @@ function renderFooterNewsletter(n: NonNullable<ChromeFooter["newsletter"]>): str
|
|
|
534
535
|
);
|
|
535
536
|
}
|
|
536
537
|
|
|
537
|
-
function renderFooter(footer: ChromeFooter): string {
|
|
538
|
-
const
|
|
538
|
+
function renderFooter(footer: ChromeFooter, variant: FooterVariant): string {
|
|
539
|
+
const minimal = variant === "minimal";
|
|
540
|
+
const collapsible = variant === "light-collapsible";
|
|
539
541
|
const brand = footer.brand;
|
|
540
542
|
const brandBlock =
|
|
541
|
-
brand || footer.newsletter
|
|
543
|
+
!minimal && (brand || footer.newsletter)
|
|
542
544
|
? (() => {
|
|
543
545
|
const logo = brand?.logo && /^(https?:|\/)/.test(brand.logo.src)
|
|
544
546
|
? `<img src="${esc(brand.logo.src)}" alt="${esc(brand.logo.alt)}"${
|
|
@@ -556,7 +558,7 @@ function renderFooter(footer: ChromeFooter): string {
|
|
|
556
558
|
// — scope indices count RENDERED grid columns, so they describe where a link
|
|
557
559
|
// actually is rather than where it sat in the authored array.
|
|
558
560
|
const { gridColumns, legalLinks } = splitFooterLegal(footer.columns);
|
|
559
|
-
const columns = gridColumns
|
|
561
|
+
const columns = (minimal ? [] : gridColumns)
|
|
560
562
|
.map((c, colIndex) => {
|
|
561
563
|
const links = c.links
|
|
562
564
|
.map((l, i) => {
|
|
@@ -576,15 +578,15 @@ function renderFooter(footer: ChromeFooter): string {
|
|
|
576
578
|
: ` <div class="footer-col">\n <p class="footer-col-title">${esc(c.title)}</p>\n <ul>${links}</ul>\n </div>`;
|
|
577
579
|
})
|
|
578
580
|
.join("\n");
|
|
579
|
-
const trust = footer.trustMarks?.length
|
|
581
|
+
const trust = !minimal && footer.trustMarks?.length
|
|
580
582
|
? `\n <div class="trust-marks" aria-label="Store commitments">${footer.trustMarks
|
|
581
583
|
.map((t) => `<span><svg viewBox="0 0 24 24">${t.icon}</svg>${esc(t.label)}</span>`)
|
|
582
584
|
.join("")}</div>`
|
|
583
585
|
: "";
|
|
584
|
-
const social = footer.social?.length
|
|
586
|
+
const social = !minimal && footer.social?.length
|
|
585
587
|
? `\n <div class="footer-social">${renderSocial(footer.social)}</div>`
|
|
586
588
|
: "";
|
|
587
|
-
const payments = footer.payments?.length
|
|
589
|
+
const payments = !minimal && footer.payments?.length
|
|
588
590
|
? `\n <div class="pay-badges" aria-label="Accepted payments">${footer.payments
|
|
589
591
|
.map((p) => `<span>${esc(p)}</span>`)
|
|
590
592
|
.join("")}</div>`
|
|
@@ -620,9 +622,9 @@ function renderFooter(footer: ChromeFooter): string {
|
|
|
620
622
|
const bottom =
|
|
621
623
|
bottomText || legalNav ? `\n <div class="footer-bottom">${bottomText}${legalNav}</div>` : "";
|
|
622
624
|
return (
|
|
623
|
-
`<footer class="site-footer" data-variant="${esc(
|
|
625
|
+
`<footer class="site-footer" data-variant="${esc(variant)}">\n` +
|
|
624
626
|
` <div class="container">\n` +
|
|
625
|
-
` <div class="footer-grid">\n${brandBlock}${columns}\n </div>` +
|
|
627
|
+
(minimal ? "" : ` <div class="footer-grid">\n${brandBlock}${columns}\n </div>`) +
|
|
626
628
|
`${trust}${social}${payments}${disclaimer}${warning}${bottom}\n` +
|
|
627
629
|
` </div>\n` +
|
|
628
630
|
`</footer>`
|
|
@@ -962,10 +964,8 @@ function faviconMime(href: string): string {
|
|
|
962
964
|
* `aria-current="page"` on the primary-nav link that exactly matches it.
|
|
963
965
|
*
|
|
964
966
|
* `assignments`, when given, is the tenant's page-assignment manifest: the
|
|
965
|
-
* entry matching `currentPath` overrides
|
|
966
|
-
*
|
|
967
|
-
* resolveHeaderVariant the Layout.astro prop path uses, and the resolved value
|
|
968
|
-
* is what reaches the markup.
|
|
967
|
+
* entry matching `currentPath` overrides the header/footer variants for this page
|
|
968
|
+
* through the same fail-closed resolvers the Layout.astro prop path uses.
|
|
969
969
|
*/
|
|
970
970
|
export function renderRawChrome(
|
|
971
971
|
chrome: ChromeConfig,
|
|
@@ -987,10 +987,12 @@ export function renderRawChrome(
|
|
|
987
987
|
.filter(Boolean)
|
|
988
988
|
.join("\n");
|
|
989
989
|
|
|
990
|
+
const assignment = resolveChromeAssignment(assignments, currentPath);
|
|
990
991
|
const headerVariant = resolveHeaderVariant(
|
|
991
|
-
|
|
992
|
+
assignment?.headerVariant,
|
|
992
993
|
chrome.header.variant,
|
|
993
994
|
);
|
|
995
|
+
const footerVariant = resolveFooterVariant(assignment?.footerVariant, chrome.footer.variant);
|
|
994
996
|
|
|
995
997
|
const skipHref = esc(chrome.skipLink?.href ?? "#main");
|
|
996
998
|
const skipLabel = esc(chrome.skipLink?.label ?? "Skip to main content");
|
|
@@ -1006,7 +1008,7 @@ export function renderRawChrome(
|
|
|
1006
1008
|
chrome.complianceStrip
|
|
1007
1009
|
? `<div class="compliance-strip"><div class="container"><p>${esc(chrome.complianceStrip)}</p></div></div>`
|
|
1008
1010
|
: "",
|
|
1009
|
-
renderFooter(chrome.footer),
|
|
1011
|
+
renderFooter(chrome.footer, footerVariant),
|
|
1010
1012
|
].filter(Boolean);
|
|
1011
1013
|
|
|
1012
1014
|
return (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.5",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -289,7 +289,7 @@ export const HEADER_VARIANTS = ["primary", "minimal", "compact-centered"] as con
|
|
|
289
289
|
export type HeaderVariant = (typeof HEADER_VARIANTS)[number];
|
|
290
290
|
|
|
291
291
|
/** Footer content shapes. The string union keeps presentation explicit and validated. */
|
|
292
|
-
export const FOOTER_VARIANTS = ["default", "light-collapsible"] as const;
|
|
292
|
+
export const FOOTER_VARIANTS = ["default", "light-collapsible", "minimal"] as const;
|
|
293
293
|
export type FooterVariant = (typeof FOOTER_VARIANTS)[number];
|
|
294
294
|
|
|
295
295
|
export function isHeaderVariant(v: unknown): v is HeaderVariant {
|
|
@@ -203,7 +203,7 @@ function validateRawHtmlBody(html) {
|
|
|
203
203
|
// packages/public-runtime/tests/chrome-config-shape-parity.test.ts. Kept in
|
|
204
204
|
// step with packages/cli/src/validate.mjs's identical copy.
|
|
205
205
|
const CHROME_HEADER_VARIANTS = new Set(["primary", "minimal", "compact-centered"]);
|
|
206
|
-
const CHROME_FOOTER_VARIANTS = new Set(["default", "light-collapsible"]);
|
|
206
|
+
const CHROME_FOOTER_VARIANTS = new Set(["default", "light-collapsible", "minimal"]);
|
|
207
207
|
/**
|
|
208
208
|
* The declarative-interaction-tracking rule, mirrored: every actionable item —
|
|
209
209
|
* an object carrying both `id` and `href` — declares EXACTLY ONE of a non-empty
|