@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.
@@ -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 text-xs text-muted" data-footer-disclaimer>
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 text-xs text-muted">{compliance}</p>
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="mt-4 flex flex-col gap-3 border-t border-border pt-4 text-xs text-muted sm:flex-row sm:items-center sm:justify-between">
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 && (
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "assignments": {
3
3
  "/contact/contact-sales": { "headerVariant": "minimal" },
4
- "/contact/contact-sales/thank-you": { "headerVariant": "minimal" }
4
+ "/contact/contact-sales/thank-you": { "headerVariant": "minimal", "footerVariant": "minimal" }
5
5
  }
6
6
  }
@@ -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 collapsible = footer.variant === "light-collapsible";
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(footer.variant)}">\n` +
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 `chrome.header.variant` for THIS page
966
- * only. Resolution happens once, here, through the same fail-closed
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
- resolveChromeAssignment(assignments, currentPath)?.headerVariant,
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.4",
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