@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
|
@@ -30,6 +30,7 @@ const cspNonce = Astro.locals.cspNonce;
|
|
|
30
30
|
const favicon = Astro.locals.tenant.theme_tokens.brand?.favicon ?? "/favicon.svg";
|
|
31
31
|
|
|
32
32
|
import { loadStyleGuideThemes } from "@/lib/styleGuideThemes";
|
|
33
|
+
import { derivedTokenDeclarations } from "@/themes/derivedTokens";
|
|
33
34
|
|
|
34
35
|
const t = (await loadStyleGuideThemes(
|
|
35
36
|
tenant ?? "",
|
|
@@ -62,9 +63,10 @@ const componentVars: Record<string, string> = {
|
|
|
62
63
|
...REFERENCE_COMPONENT_VARS,
|
|
63
64
|
...((t.preview?.componentVars ?? {}) as Record<string, string>),
|
|
64
65
|
};
|
|
65
|
-
const componentVarsInline =
|
|
66
|
-
.map(([k, v]) => `${k}:${v}`)
|
|
67
|
-
|
|
66
|
+
const componentVarsInline = [
|
|
67
|
+
...Object.entries(componentVars).map(([k, v]) => `${k}:${v}`),
|
|
68
|
+
...derivedTokenDeclarations(componentVars),
|
|
69
|
+
].join(";");
|
|
68
70
|
---
|
|
69
71
|
|
|
70
72
|
<!doctype html>
|
|
@@ -88,34 +90,36 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
88
90
|
.sg-bar { position: sticky; top: 0; z-index: 500; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; background: #0f1729; color: #e6edf6; padding: 12px 20px; font: 600 .95rem -apple-system, sans-serif; }
|
|
89
91
|
.sg-bar .flag { font-size: .78rem; letter-spacing: .1em; text-transform: uppercase; background: #f59e0b; color: #221600; font-weight: 800; padding: 4px 8px; border-radius: 5px; }
|
|
90
92
|
.sg-bar .role { font-size: .78rem; text-transform: uppercase; letter-spacing: .08em; background: #0e9f8e; color: #00201b; font-weight: 800; padding: 4px 8px; border-radius: 999px; }
|
|
91
|
-
.sg-bar a { color: #bfe0ff; }
|
|
93
|
+
.sg-bar a { color: #bfe0ff; display: inline-flex; align-items: center; min-height: 44px; }
|
|
92
94
|
.sg-bar a.right { margin-left: auto; }
|
|
93
95
|
.sg-page { background: var(--color-bg); color: var(--color-text); min-height: 100vh; padding-bottom: 40px; }
|
|
94
|
-
.sg-lbl { max-width: var(--container-max, 1240px); margin: 26px auto 6px; padding: 0 20px; font: 800 .
|
|
96
|
+
.sg-lbl { max-width: var(--container-max, 1240px); margin: 26px auto 6px; padding: 0 20px; font: 800 max(.875rem, 14px)/1.3 -apple-system, sans-serif; letter-spacing: .12em; text-transform: uppercase; color: var(--color-primary); }
|
|
95
97
|
.sg-frame { max-width: var(--container-max, 1240px); margin: 0 auto 6px; padding: 0 20px; }
|
|
96
98
|
.sg-frame > * { border: 1px dashed var(--color-border); border-radius: 8px; }
|
|
99
|
+
/* Live pages carry a border-box reset (global.css or the theme sheet); the frames match it. */
|
|
100
|
+
.sg-frame, .sg-frame * { box-sizing: border-box; }
|
|
97
101
|
/* Show the demoed mega panel + age gate open, in normal flow, for review. */
|
|
98
102
|
.sg-open .mega { position: static; opacity: 1; visibility: visible; transform: none; box-shadow: var(--shadow-md); margin-top: 6px; }
|
|
99
103
|
.sg-agegate-frame .age-gate { position: static; display: flex; inset: auto; background: color-mix(in srgb, var(--color-text) 10%, transparent); border-radius: 8px; }
|
|
100
|
-
.sg-note { max-width: var(--container-max, 1240px); margin: 8px auto 0; padding: 16px 20px 0; border-top: 1px solid var(--color-border); font-size: .
|
|
104
|
+
.sg-note { max-width: var(--container-max, 1240px); margin: 8px auto 0; padding: 16px 20px 0; border-top: 1px solid var(--color-border); font-size: max(.875rem, 14px); color: var(--color-muted); }
|
|
101
105
|
</style>
|
|
102
106
|
</head>
|
|
103
107
|
<body>
|
|
104
|
-
<!-- A
|
|
108
|
+
<!-- A labelled nav, not a <header>: a real <header> here would duplicate
|
|
105
109
|
the demoed .site-header's own banner landmark (axe
|
|
106
110
|
landmark-no-duplicate-banner/landmark-unique). -->
|
|
107
|
-
<
|
|
111
|
+
<nav class="sg-bar" aria-label="Style guide toolbar">
|
|
108
112
|
<span class="flag">Preview · shared chrome · noindex</span>
|
|
109
113
|
<span class="role">{t.role}</span>
|
|
110
114
|
<b>{t.name}</b>
|
|
111
115
|
<a class="right" href={`/_style-guide/${tenant}/guide/${t.id}/`}>widget library guide →</a>
|
|
112
116
|
<a href={`/_style-guide/${tenant}/${t.id}/`}>← component guide</a>
|
|
113
|
-
</
|
|
117
|
+
</nav>
|
|
114
118
|
|
|
115
119
|
<main class="sg-page" style={componentVarsInline}>
|
|
116
120
|
<h1 class="sg-lbl">Shared chrome preview — {t.name}</h1>
|
|
117
121
|
<p class="sg-lbl">Announcement + top utility bar</p>
|
|
118
|
-
<div class="sg-frame">
|
|
122
|
+
<div class="sg-frame" data-tot-platform-sample>
|
|
119
123
|
<div>
|
|
120
124
|
<div class="topbar-warning"><div class="container"><p>WARNING: This product contains nicotine. Nicotine is an addictive chemical.</p></div></div>
|
|
121
125
|
<div class="announce"><div class="container">Free US shipping on orders over $100</div></div>
|
|
@@ -129,7 +133,7 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
129
133
|
</div>
|
|
130
134
|
|
|
131
135
|
<p class="sg-lbl">Header — brand · search · actions · primary nav + mega panel</p>
|
|
132
|
-
<div class="sg-frame">
|
|
136
|
+
<div class="sg-frame" data-tot-platform-sample>
|
|
133
137
|
<header class="site-header sg-open">
|
|
134
138
|
<div class="container header-row">
|
|
135
139
|
<a class="brand" href="#"><span class="b-prime">Prime</span><span class="b-vapor">Vapor</span><span class="b-club">Club</span></a>
|
|
@@ -161,7 +165,7 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
161
165
|
</div>
|
|
162
166
|
|
|
163
167
|
<p class="sg-lbl">Brand tiles + category cards</p>
|
|
164
|
-
<div class="sg-frame">
|
|
168
|
+
<div class="sg-frame" data-tot-platform-sample>
|
|
165
169
|
<section class="section container">
|
|
166
170
|
<div class="sec-head"><span class="eyebrow">Shop by</span><h2>Top brands</h2></div>
|
|
167
171
|
<div class="tile-grid">
|
|
@@ -179,12 +183,12 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
179
183
|
</div>
|
|
180
184
|
|
|
181
185
|
<p class="sg-lbl">Compliance strip</p>
|
|
182
|
-
<div class="sg-frame">
|
|
186
|
+
<div class="sg-frame" data-tot-platform-sample>
|
|
183
187
|
<div class="compliance-strip"><div class="container"><p>Must be 21+ to purchase. Products intended for adults of legal smoking age only.</p></div></div>
|
|
184
188
|
</div>
|
|
185
189
|
|
|
186
190
|
<p class="sg-lbl">Age gate (21+ interstitial) — shown open for review</p>
|
|
187
|
-
<div class="sg-frame sg-agegate-frame">
|
|
191
|
+
<div class="sg-frame sg-agegate-frame" data-tot-platform-sample>
|
|
188
192
|
<div class="age-gate is-open">
|
|
189
193
|
<div class="age-gate__panel">
|
|
190
194
|
<p class="age-gate__mark"><span class="is-red">Prime</span>VaporClub</p>
|
|
@@ -200,7 +204,7 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
200
204
|
</div>
|
|
201
205
|
|
|
202
206
|
<p class="sg-lbl">Footer — columns · newsletter · payments · trust marks</p>
|
|
203
|
-
<div class="sg-frame">
|
|
207
|
+
<div class="sg-frame" data-tot-platform-sample>
|
|
204
208
|
<footer class="site-footer">
|
|
205
209
|
<div class="container">
|
|
206
210
|
<div class="footer-grid">
|
|
@@ -21,6 +21,7 @@ import "@/styles/global.css";
|
|
|
21
21
|
import type { MarkdownInstance } from "astro";
|
|
22
22
|
import StyleGuideNav from "@/components/style-guide/StyleGuideNav.astro";
|
|
23
23
|
import { loadStyleGuideThemes } from "@/lib/styleGuideThemes";
|
|
24
|
+
import { derivedTokenDeclarations } from "@/themes/derivedTokens";
|
|
24
25
|
|
|
25
26
|
const { tenant, theme } = Astro.params;
|
|
26
27
|
const cspNonce = Astro.locals.cspNonce;
|
|
@@ -53,7 +54,7 @@ const REFERENCE_COMPONENT_VARS: Record<string, string> = {
|
|
|
53
54
|
"--color-bg": "#f4f1ea", "--color-surface": "#fcfaf5", "--color-surface-2": "#ece7db",
|
|
54
55
|
"--color-text": "#1c2624", "--color-muted": "#5c6663",
|
|
55
56
|
"--color-primary": "#1f3a34", "--color-primary-contrast": "#f4f1ea",
|
|
56
|
-
"--color-accent": "#b5612f", "--color-
|
|
57
|
+
"--color-accent": "#b5612f", "--color-border": "#dcd6c9",
|
|
57
58
|
"--color-success": "#3f6b4f", "--color-sale": "#9e3b2e",
|
|
58
59
|
"--radius-sm": "3px", "--radius-md": "6px", "--radius-lg": "12px", "--radius-full": "999px",
|
|
59
60
|
"--shadow-sm": "0 1px 2px rgba(28,38,36,.06)", "--shadow-md": "0 6px 24px -8px rgba(28,38,36,.14)",
|
|
@@ -63,9 +64,10 @@ const componentVars: Record<string, string> = {
|
|
|
63
64
|
...REFERENCE_COMPONENT_VARS,
|
|
64
65
|
...((t.preview?.componentVars ?? {}) as Record<string, string>),
|
|
65
66
|
};
|
|
66
|
-
const componentVarsInline =
|
|
67
|
-
.map(([k, v]) => `${k}:${v}`)
|
|
68
|
-
|
|
67
|
+
const componentVarsInline = [
|
|
68
|
+
...Object.entries(componentVars).map(([k, v]) => `${k}:${v}`),
|
|
69
|
+
...derivedTokenDeclarations(componentVars),
|
|
70
|
+
].join(";");
|
|
69
71
|
---
|
|
70
72
|
|
|
71
73
|
<!doctype html>
|
|
@@ -91,35 +93,29 @@ const componentVarsInline = Object.entries(componentVars)
|
|
|
91
93
|
.sg-shell { display: grid; grid-template-columns: 264px minmax(0, 1fr); align-items: start; }
|
|
92
94
|
.sg-side { position: sticky; top: 0; align-self: start; max-height: 100vh; overflow-y: auto; background: #0f1729; color: #e6edf6; padding: 22px 14px 40px; }
|
|
93
95
|
|
|
94
|
-
/* Left-nav (StyleGuideNav markup) — styled here so the component stays CSP-clean. */
|
|
95
|
-
.sg-nav__eyebrow { font: 800 .72rem/1.2 -apple-system, sans-serif; letter-spacing: .14em; text-transform: uppercase; color: #7fe3d4; margin: 0 0 12px; padding: 0 10px; }
|
|
96
|
-
.sg-nav__list { list-style: none; margin: 0; padding: 0; }
|
|
97
|
-
.sg-nav__list li { margin: 0; }
|
|
98
|
-
.sg-nav__link { display: block; padding: 9px 10px; border-radius: 8px; color: #bfe0ff; text-decoration: none; font-size: .92rem; min-height: 40px; }
|
|
99
|
-
.sg-nav__link:hover { background: #16223c; }
|
|
100
|
-
.sg-nav__link.is-active { background: #16223c; color: #fff; font-weight: 700; }
|
|
101
|
-
.sg-nav__link--sub { padding-left: 20px; font-size: .88rem; }
|
|
102
|
-
.sg-nav__toc { list-style: none; margin: 2px 0 8px; padding: 0; border-left: 1px solid #26365a; margin-left: 16px; }
|
|
103
|
-
.sg-nav__toc-link { display: block; padding: 5px 10px 5px 14px; color: #9fb0c8; font-size: .82rem; text-decoration: none; line-height: 1.3; }
|
|
104
|
-
.sg-nav__toc-link:hover { color: #e6edf6; }
|
|
105
96
|
|
|
106
97
|
/* Themed prose column. .rte styles come from global.css; token vars are
|
|
107
|
-
supplied by componentVarsInline on this wrapper so they always resolve.
|
|
98
|
+
supplied by componentVarsInline on this wrapper so they always resolve.
|
|
99
|
+
The markdown <Content /> renders outside this page's style scope, so
|
|
100
|
+
every rule reaching into it is :global(). */
|
|
108
101
|
.sg-content { background: var(--color-bg); color: var(--color-text); min-height: 100vh; padding: 34px clamp(20px, 4vw, 56px) 96px; }
|
|
109
102
|
.sg-content .rte { max-width: 900px; }
|
|
110
|
-
.sg-content h1 { font-size: clamp(1.9rem, 4vw, 2.7rem); line-height: 1.1; margin: 0 0 10px; color: var(--color-text); }
|
|
111
|
-
.sg-content .rte h2 { border-top: 1px solid var(--color-border); padding-top: 1.4em; scroll-margin-top: 68px; }
|
|
112
|
-
.sg-content .rte h3 { scroll-margin-top: 68px; }
|
|
113
|
-
.sg-content blockquote { border-left: 3px solid var(--color-accent); margin: 1em 0; padding: 2px 0 2px 16px; color: var(--color-muted); }
|
|
114
|
-
.sg-content pre { background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: 14px 16px; overflow-x: auto; font-family: var(--font-mono, ui-monospace, Menlo, monospace); font-size: .84rem; line-height: 1.5; }
|
|
115
|
-
.sg-content pre code { background: none; padding: 0; }
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.sg-content
|
|
119
|
-
.sg-content
|
|
103
|
+
.sg-content :global(h1) { font-size: clamp(1.9rem, 4vw, 2.7rem); line-height: 1.1; margin: 0 0 10px; color: var(--color-text); }
|
|
104
|
+
.sg-content .rte :global(h2) { border-top: 1px solid var(--color-border); padding-top: 1.4em; scroll-margin-top: 68px; }
|
|
105
|
+
.sg-content .rte :global(h3) { scroll-margin-top: 68px; }
|
|
106
|
+
.sg-content :global(blockquote) { border-left: 3px solid var(--color-accent); margin: 1em 0; padding: 2px 0 2px 16px; color: var(--color-muted); }
|
|
107
|
+
.sg-content :global(pre) { background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: 14px 16px; overflow-x: auto; font-family: var(--font-mono, ui-monospace, Menlo, monospace); font-size: .84rem; line-height: 1.5; }
|
|
108
|
+
.sg-content :global(pre code) { background: none; padding: 0; }
|
|
109
|
+
/* The highlighter's github-dark comment gray (#6a737d) is 3:1 on its own
|
|
110
|
+
#24292e ground; lift it to AA. Its color is an inline style, hence !important. */
|
|
111
|
+
.sg-content :global(.astro-code span[style*="#6A737D" i]) { color: #9ea7b3 !important; }
|
|
112
|
+
.sg-content :global(code) { font-family: var(--font-mono, ui-monospace, Menlo, monospace); font-size: .88em; }
|
|
113
|
+
.sg-content :global(:not(pre) > code) { background: var(--color-surface-2); padding: 1px 5px; border-radius: 4px; }
|
|
114
|
+
.sg-content :global(table) { display: block; width: 100%; overflow-x: auto; border-collapse: collapse; }
|
|
115
|
+
.sg-content :global(th), .sg-content :global(td) { border: 1px solid var(--color-border); padding: 6px 10px; text-align: left; font-size: .9rem; }
|
|
120
116
|
|
|
121
117
|
@media (max-width: 900px) {
|
|
122
|
-
.sg-shell { grid-template-columns: 1fr; }
|
|
118
|
+
.sg-shell { grid-template-columns: minmax(0, 1fr); }
|
|
123
119
|
.sg-side { position: static; max-height: none; }
|
|
124
120
|
}
|
|
125
121
|
</style>
|
|
@@ -53,15 +53,6 @@ const navTheme = themes[0]?.id;
|
|
|
53
53
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; background: #0f1729; color: #e6edf6; line-height: 1.55; }
|
|
54
54
|
.sg-shell { display: grid; grid-template-columns: 264px minmax(0, 1fr); align-items: start; }
|
|
55
55
|
.sg-side { position: sticky; top: 0; align-self: start; max-height: 100vh; overflow-y: auto; padding: 22px 14px 40px; border-right: 1px solid #1c2a44; }
|
|
56
|
-
.sg-nav__eyebrow { font: 800 .72rem/1.2 -apple-system, sans-serif; letter-spacing: .14em; text-transform: uppercase; color: #7fe3d4; margin: 0 0 12px; padding: 0 10px; }
|
|
57
|
-
.sg-nav__list { list-style: none; margin: 0; padding: 0; }
|
|
58
|
-
.sg-nav__list li { margin: 0; }
|
|
59
|
-
.sg-nav__link { display: block; padding: 9px 10px; border-radius: 8px; color: #bfe0ff; text-decoration: none; font-size: .92rem; min-height: 40px; }
|
|
60
|
-
.sg-nav__link:hover { background: #16223c; }
|
|
61
|
-
.sg-nav__link.is-active { background: #16223c; color: #fff; font-weight: 700; }
|
|
62
|
-
.sg-nav__link--sub { padding-left: 20px; font-size: .88rem; }
|
|
63
|
-
.sg-nav__toc { list-style: none; margin: 2px 0 8px; padding: 0; }
|
|
64
|
-
.sg-nav__toc-link { display: block; padding: 4px 10px 4px 26px; color: #9fb0c8; font-size: .82rem; text-decoration: none; }
|
|
65
56
|
.wrap { max-width: 860px; margin: 0 auto; padding: 40px 24px 80px; }
|
|
66
57
|
.flag { display: inline-block; font-size: .72rem; letter-spacing: .1em; text-transform: uppercase; background: #f59e0b; color: #221600; font-weight: 800; padding: 4px 10px; border-radius: 6px; }
|
|
67
58
|
h1 { font-size: 1.9rem; margin: 18px 0 6px; }
|
|
@@ -75,12 +66,14 @@ const navTheme = themes[0]?.id;
|
|
|
75
66
|
.badge.legacy { background: #47536e; color: #dfe6f2; }
|
|
76
67
|
.status { margin-left: auto; font-size: .78rem; color: #9fb0c8; font-family: ui-monospace, Menlo, monospace; }
|
|
77
68
|
.name { font-weight: 700; font-size: 1.05rem; }
|
|
78
|
-
.desc { color: #9fb0c8; font-size: .9rem; margin-top: 8px; }
|
|
69
|
+
.desc { color: #9fb0c8; font-size: max(.9rem, 14px); margin-top: 8px; }
|
|
79
70
|
.sub-links { display: flex; gap: 16px; flex-wrap: wrap; margin: 8px 0 16px 4px; }
|
|
80
|
-
.sub-links a { color: #bfe0ff; font-size: .
|
|
71
|
+
.sub-links a { color: #bfe0ff; font-size: max(.875rem, 14px); text-decoration: none; display: inline-flex; align-items: center; min-height: 44px; }
|
|
81
72
|
.sub-links a:hover { text-decoration: underline; }
|
|
82
|
-
.
|
|
83
|
-
|
|
73
|
+
.back { margin: 14px 0 0; }
|
|
74
|
+
.back a { color: #bfe0ff; font-size: max(.875rem, 14px); text-decoration: none; display: inline-flex; align-items: center; min-height: 44px; }
|
|
75
|
+
.note { margin-top: 28px; font-size: max(.875rem, 14px); color: #7f8fab; border-top: 1px solid #24324f; padding-top: 16px; }
|
|
76
|
+
@media (max-width: 900px) { .sg-shell { grid-template-columns: minmax(0, 1fr); } .sg-side { position: static; max-height: none; border-right: 0; border-bottom: 1px solid #1c2a44; } }
|
|
84
77
|
</style>
|
|
85
78
|
</head>
|
|
86
79
|
<body>
|
|
@@ -90,7 +83,7 @@ const navTheme = themes[0]?.id;
|
|
|
90
83
|
</aside>
|
|
91
84
|
<main class="wrap">
|
|
92
85
|
<span class="flag">Preview · dev only · noindex</span>
|
|
93
|
-
<p
|
|
86
|
+
<p class="back"><a href="/_style-guide/">← all clients</a></p>
|
|
94
87
|
<h1>{tenant} — theme style guides</h1>
|
|
95
88
|
<p class="sub">Open any guide independently to compare looks side-by-side. Nothing here reskins the live site. The <b>Widget Library Guide</b> (left nav) documents every shared component.</p>
|
|
96
89
|
|
|
@@ -70,7 +70,8 @@ const tenants = [...byTenant.entries()]
|
|
|
70
70
|
.sub { color: #9fb0c8; margin: 0 0 32px; }
|
|
71
71
|
.tenant { margin-bottom: 30px; }
|
|
72
72
|
.tenant > h2 { font-size: 1.05rem; margin: 0 0 12px; display: flex; align-items: baseline; gap: 10px; }
|
|
73
|
-
.tenant > h2 a { color: #bfe0ff; font-size: .
|
|
73
|
+
.tenant > h2 a { color: #bfe0ff; font-size: max(.875rem, 14px); font-weight: 600; text-decoration: none; display: inline-flex; align-items: center; min-height: 44px; }
|
|
74
|
+
.sub a { color: #bfe0ff; display: inline-flex; align-items: center; min-height: 44px; }
|
|
74
75
|
.card { display: block; text-decoration: none; color: inherit; background: #16223c; border: 1px solid #24324f; border-radius: 14px; padding: 16px 20px; margin-bottom: 12px; transition: border-color .15s, transform .15s; }
|
|
75
76
|
.card:hover { border-color: #3f5680; transform: translateY(-2px); }
|
|
76
77
|
.row { display: flex; align-items: center; gap: 12px; }
|
|
@@ -80,12 +81,12 @@ const tenants = [...byTenant.entries()]
|
|
|
80
81
|
.badge.legacy { background: #47536e; color: #dfe6f2; }
|
|
81
82
|
.status { margin-left: auto; font-size: .78rem; color: #9fb0c8; font-family: ui-monospace, Menlo, monospace; }
|
|
82
83
|
.name { font-weight: 700; font-size: 1.02rem; }
|
|
83
|
-
.desc { color: #9fb0c8; font-size: .88rem; margin-top: 6px; }
|
|
84
|
-
.note { margin-top: 28px; font-size: .
|
|
84
|
+
.desc { color: #9fb0c8; font-size: max(.88rem, 14px); margin-top: 6px; }
|
|
85
|
+
.note { margin-top: 28px; font-size: max(.875rem, 14px); color: #7f8fab; border-top: 1px solid #24324f; padding-top: 16px; }
|
|
85
86
|
</style>
|
|
86
87
|
</head>
|
|
87
88
|
<body>
|
|
88
|
-
<
|
|
89
|
+
<main class="wrap">
|
|
89
90
|
<span class="flag">Preview · dev only · noindex</span>
|
|
90
91
|
<h1>Client style guides</h1>
|
|
91
92
|
<p class="sub">Every client tenant and its theme guides. Open a guide to preview the standard components and page sections re-skinned under that theme. Nothing here re-skins a live site.</p>
|
|
@@ -117,6 +118,6 @@ const tenants = [...byTenant.entries()]
|
|
|
117
118
|
These are preview/dev review surfaces — excluded from the sitemap and production marketing
|
|
118
119
|
publishing. Approve a tenant's <b>current</b> guide before propagating its style concept across the site.
|
|
119
120
|
</p>
|
|
120
|
-
</
|
|
121
|
+
</main>
|
|
121
122
|
</body>
|
|
122
123
|
</html>
|
|
@@ -16,17 +16,26 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Preview sessions also consult the image-capture rewrite overlay so rewritten
|
|
18
18
|
* `public/*.css` (and similar text assets) serve `/_a/<sha>` references.
|
|
19
|
+
*
|
|
20
|
+
* Version selection, in order: a candidate/revision pin (`tot_preview_version` +
|
|
21
|
+
* token cookie); else, for a shared-preview viewer (preview host, or a valid token
|
|
22
|
+
* for this tenant), the version the content `preview` channel serves — what the
|
|
23
|
+
* preview document itself rendered; else the request's channel pointer.
|
|
19
24
|
*/
|
|
20
25
|
import type { APIContext } from "astro";
|
|
21
26
|
import {
|
|
22
27
|
assetContentType,
|
|
23
28
|
parseTenantAssetPath,
|
|
29
|
+
resolveChannelVersion,
|
|
24
30
|
serveTenantAsset,
|
|
25
31
|
type R2BucketLike,
|
|
26
32
|
} from "@tot/public-runtime";
|
|
27
33
|
import { readWithImagePreviewRewriteOverlay } from "@tot/public-runtime/image-preview-rewrite-overlay";
|
|
28
34
|
import { readR2, readKv, readEnv } from "@/lib/env";
|
|
29
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
resolvePreviewVersionFromCookies,
|
|
37
|
+
resolveSharedPreviewAssetVersion,
|
|
38
|
+
} from "@/lib/assets/preview-version-cookie";
|
|
30
39
|
|
|
31
40
|
export const prerender = false;
|
|
32
41
|
|
|
@@ -73,14 +82,30 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
const urlTenant = context.params.id ?? "";
|
|
76
|
-
const
|
|
85
|
+
const url = new URL(context.request.url);
|
|
86
|
+
const secret = await readEnv("CUSTOMIZATION_PREVIEW_SECRET");
|
|
87
|
+
const tokenCookie = context.cookies.get("tot_preview")?.value;
|
|
88
|
+
const pinnedVersionId = await resolvePreviewVersionFromCookies({
|
|
77
89
|
tenantId: urlTenant,
|
|
78
90
|
previewVersionCookie: context.cookies.get("tot_preview_version")?.value,
|
|
79
|
-
previewTokenCookie:
|
|
80
|
-
secret
|
|
91
|
+
previewTokenCookie: tokenCookie,
|
|
92
|
+
secret,
|
|
81
93
|
});
|
|
94
|
+
// No candidate/revision pin: a shared-preview viewer reads the version the
|
|
95
|
+
// preview channel serves — the same version its document rendered.
|
|
96
|
+
const sharedPreview = pinnedVersionId
|
|
97
|
+
? { preview: false, versionId: null }
|
|
98
|
+
: await resolveSharedPreviewAssetVersion({
|
|
99
|
+
tenantId: urlTenant,
|
|
100
|
+
previewHost: url.hostname.toLowerCase().startsWith("preview."),
|
|
101
|
+
previewToken: url.searchParams.get("__preview") ?? tokenCookie,
|
|
102
|
+
secret,
|
|
103
|
+
readPreviewChannelVersion: () =>
|
|
104
|
+
resolveChannelVersion((key) => kv.get(key), urlTenant, "preview"),
|
|
105
|
+
});
|
|
106
|
+
const previewVersionId = pinnedVersionId ?? sharedPreview.versionId;
|
|
82
107
|
|
|
83
|
-
const pathname =
|
|
108
|
+
const pathname = url.pathname;
|
|
84
109
|
if (previewVersionId) {
|
|
85
110
|
const overlay = await tryServePreviewRewriteOverlay({
|
|
86
111
|
kvGet: (key) => kv.get(key),
|
|
@@ -96,7 +121,7 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
96
121
|
// Runtime binding structurally satisfies the shared-runtime R2 seam.
|
|
97
122
|
bucket: bucket as unknown as R2BucketLike,
|
|
98
123
|
kvGet: (key) => kv.get(key),
|
|
99
|
-
channel: context.locals.customizationEnv ?? "live",
|
|
124
|
+
channel: sharedPreview.preview ? "test" : (context.locals.customizationEnv ?? "live"),
|
|
100
125
|
...(previewVersionId ? { versionId: previewVersionId } : {}),
|
|
101
126
|
ifNoneMatch: context.request.headers.get("if-none-match"),
|
|
102
127
|
},
|
|
@@ -107,7 +132,7 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
107
132
|
// (that would poison the public asset with candidate bytes, and Vary doesn't cover
|
|
108
133
|
// the auth cookie). Force no-store for the preview-version path; the normal
|
|
109
134
|
// channel-served asset keeps its revalidating cache header.
|
|
110
|
-
if (res && previewVersionId) {
|
|
135
|
+
if (res && (previewVersionId || sharedPreview.preview)) {
|
|
111
136
|
res.headers.set("cache-control", "no-store");
|
|
112
137
|
res.headers.set("x-robots-tag", "noindex, nofollow");
|
|
113
138
|
}
|
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
// visibility chokepoint); unlisted posts resolve here but carry noindex and
|
|
6
6
|
// stay out of the index/sitemap/RSS (see collection.ts isVisibleOnLive).
|
|
7
7
|
import Layout from "@/layouts/Layout.astro";
|
|
8
|
+
import Breadcrumbs from "@/components/Breadcrumbs.astro";
|
|
8
9
|
import ReactionBar from "@/components/blog/ReactionBar.astro";
|
|
9
|
-
import
|
|
10
|
+
import ShareRow from "@/components/blog/ShareRow.astro";
|
|
11
|
+
import { getBuildPost, getTheBuildSettings, resolveBuildRedirect, THE_BUILD } from "@/lib/the-build/provider.js";
|
|
12
|
+
import { bylineAuthor, resolveBlogPresentation, shareLinks } from "@/lib/blog/presentation.js";
|
|
13
|
+
import { homeCrumbLabel, siteTrail } from "@/lib/breadcrumbs.js";
|
|
10
14
|
import { getTranslations } from "@/lib/blog/collection.js";
|
|
11
15
|
import { buildAlternates } from "@/lib/seo/alternates.js";
|
|
12
16
|
import { breadcrumbLd } from "@/lib/jsonld.js";
|
|
@@ -27,6 +31,9 @@ if (!post) {
|
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
const canonical = new URL(`/the-build/${post.slug}/`, canonicalBase + "/").toString();
|
|
34
|
+
const presentation = resolveBlogPresentation(await getTheBuildSettings(tenant.tenant_id, content));
|
|
35
|
+
// A post that names no author has no byline and no structured-data author.
|
|
36
|
+
const author = bylineAuthor(post.author);
|
|
30
37
|
const articleLd = {
|
|
31
38
|
"@context": "https://schema.org",
|
|
32
39
|
"@type": "BlogPosting",
|
|
@@ -36,15 +43,21 @@ const articleLd = {
|
|
|
36
43
|
mainEntityOfPage: canonical,
|
|
37
44
|
datePublished: post.publishedAt,
|
|
38
45
|
dateModified: post.modifiedAt,
|
|
39
|
-
author: { "@type": "Person", name:
|
|
46
|
+
...(author ? { author: { "@type": "Person", name: author } } : {}),
|
|
40
47
|
publisher: { "@type": "Organization", name: tenant.displayName, url: canonicalBase },
|
|
41
48
|
...(post.featuredImage ? { image: new URL(post.featuredImage.src, canonicalBase + "/").toString() } : {}),
|
|
42
49
|
};
|
|
43
|
-
const
|
|
44
|
-
|
|
50
|
+
const trail = siteTrail(
|
|
51
|
+
homeCrumbLabel(await content.getChrome()),
|
|
45
52
|
{ label: "The Build", href: "/the-build/" },
|
|
46
53
|
{ label: post.title, href: `/the-build/${post.slug}/` },
|
|
47
|
-
|
|
54
|
+
);
|
|
55
|
+
const crumbs = breadcrumbLd(trail, canonicalBase);
|
|
56
|
+
const share = shareLinks(presentation.share, {
|
|
57
|
+
url: canonical,
|
|
58
|
+
title: post.title,
|
|
59
|
+
image: post.featuredImage ? new URL(post.featuredImage.src, canonicalBase + "/").toString() : undefined,
|
|
60
|
+
});
|
|
48
61
|
const buildHref = `${basePath}/the-build/`;
|
|
49
62
|
|
|
50
63
|
// I2 — hreflang alternates + per-variant <html lang>. `getTranslations` returns
|
|
@@ -73,14 +86,14 @@ const lang = alternates.length ? (post.inLanguage ?? "en") : undefined;
|
|
|
73
86
|
lang={lang}
|
|
74
87
|
>
|
|
75
88
|
<article class="article-shell">
|
|
76
|
-
<
|
|
89
|
+
<Breadcrumbs crumbs={trail} class="breadcrumbs" />
|
|
77
90
|
<header>
|
|
78
|
-
{post.tags[0] && <p class="eyebrow eyebrow--tick">{post.tags[0]}</p>}
|
|
91
|
+
{presentation.postEyebrow && post.tags[0] && <p class="eyebrow eyebrow--tick">{post.tags[0]}</p>}
|
|
79
92
|
<h1>{post.title}</h1>
|
|
80
|
-
<p class="dek">{post.excerpt}</p>
|
|
93
|
+
{presentation.dek && post.excerpt && <p class="dek">{post.excerpt}</p>}
|
|
81
94
|
<div class="byline">
|
|
82
|
-
<span>By {
|
|
83
|
-
<span aria-hidden="true">·</span>
|
|
95
|
+
{author && <span>By {author}</span>}
|
|
96
|
+
{author && <span aria-hidden="true">·</span>}
|
|
84
97
|
<time datetime={post.publishedAt}>{new Date(post.publishedAt).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })}</time>
|
|
85
98
|
</div>
|
|
86
99
|
</header>
|
|
@@ -95,21 +108,29 @@ const lang = alternates.length ? (post.inLanguage ?? "en") : undefined;
|
|
|
95
108
|
decoding="async"
|
|
96
109
|
/>
|
|
97
110
|
)}
|
|
98
|
-
<div class="article-grid">
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
<div class:list={["article-grid", { "article-grid--single": !presentation.aside }]}>
|
|
112
|
+
{presentation.aside && (
|
|
113
|
+
<aside>
|
|
114
|
+
<a href={buildHref}>← All articles</a>
|
|
115
|
+
{post.tags.length > 0 && <div class="article-tags">{post.tags.map((tag) => <span>{tag}</span>)}</div>}
|
|
116
|
+
</aside>
|
|
117
|
+
)}
|
|
118
|
+
<div class="blog-main">
|
|
119
|
+
<div class="blog-body" set:html={post.contentHtml}></div>
|
|
120
|
+
{!presentation.aside && post.tags.length > 0 && (
|
|
121
|
+
<ul class="post-tags" aria-label="Tags">{post.tags.map((tag) => <li><span>{tag}</span></li>)}</ul>
|
|
122
|
+
)}
|
|
123
|
+
<ShareRow links={share} />
|
|
124
|
+
</div>
|
|
104
125
|
</div>
|
|
105
|
-
<ReactionBar collection="the-build" slug={post.slug} />
|
|
126
|
+
{presentation.reactions && <ReactionBar collection="the-build" slug={post.slug} />}
|
|
106
127
|
</article>
|
|
107
128
|
</Layout>
|
|
108
129
|
|
|
109
130
|
<style>
|
|
110
131
|
.article-shell { width: min(1120px, calc(100% - 2rem)); margin-inline: auto; padding-block: clamp(2rem, 5vw, 5rem); }
|
|
111
|
-
.
|
|
112
|
-
.breadcrumbs a { color: var(--color-accent-ink); font-weight: 700; }
|
|
132
|
+
.article-shell :global(.breadcrumbs) { font-size: .84rem; }
|
|
133
|
+
.article-shell :global(.breadcrumbs a) { color: var(--color-accent-ink); font-weight: 700; }
|
|
113
134
|
header { max-width: 880px; margin: clamp(2.5rem, 6vw, 5rem) auto clamp(2rem, 5vw, 3.5rem); text-align: center; }
|
|
114
135
|
header .eyebrow { justify-content: center; }
|
|
115
136
|
h1 { margin-top: .9rem; font-size: clamp(2.5rem, 6vw, 5rem); line-height: 1.02; }
|
|
@@ -120,7 +141,10 @@ const lang = alternates.length ? (post.inLanguage ?? "en") : undefined;
|
|
|
120
141
|
aside { align-self: start; position: sticky; top: 7rem; }
|
|
121
142
|
aside > a { color: var(--color-accent-ink); font-size: .88rem; font-weight: 700; }
|
|
122
143
|
.article-tags { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1.2rem; }
|
|
123
|
-
.article-tags span { padding: .3rem .55rem; border: 1px solid var(--color-border); border-radius: var(--radius-full); color: var(--color-muted); font-size: .7rem; }
|
|
144
|
+
.article-tags span, .post-tags span { padding: .3rem .55rem; border: 1px solid var(--color-border); border-radius: var(--radius-full); color: var(--color-muted); font-size: .7rem; }
|
|
145
|
+
.article-grid--single { grid-template-columns: minmax(0, 720px); }
|
|
146
|
+
.blog-main { min-width: 0; }
|
|
147
|
+
.post-tags { display: flex; flex-wrap: wrap; gap: .45rem; margin: 2rem 0 0; padding: 0; list-style: none; }
|
|
124
148
|
.blog-body { min-width: 0; font-size: 1.05rem; line-height: 1.78; }
|
|
125
149
|
.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; }
|
|
126
150
|
.blog-body :global(h2), .blog-body :global(h3), .blog-body :global(h4) { scroll-margin-top: 7rem; margin: 2.8rem 0 1rem; line-height: 1.18; }
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// with the-build content renders the same way (per-tenant/multi-collection
|
|
6
6
|
// invariant — see docs/plans/2026-08-21-blog-platform-workstream.md §1).
|
|
7
7
|
import TheBuildIndexView from "@/components/the-build/TheBuildIndexView.astro";
|
|
8
|
+
import { resolveBlogPresentation } from "@/lib/blog/presentation.js";
|
|
8
9
|
import { getTheBuildSettings, listBuildPosts } from "@/lib/the-build/provider.js";
|
|
9
10
|
import { paginateBlogPosts } from "@/lib/blog/pagination.js";
|
|
10
11
|
import { resolveTheBuildIndexCopy } from "@/lib/the-build/pagination.js";
|
|
@@ -24,6 +25,7 @@ const { posts: pagePosts, totalPages } = paginateBlogPosts(posts, copy.pageSize,
|
|
|
24
25
|
basePath={basePath}
|
|
25
26
|
canonicalBase={canonicalBase}
|
|
26
27
|
copy={copy}
|
|
28
|
+
presentation={resolveBlogPresentation(settings)}
|
|
27
29
|
pagePosts={pagePosts}
|
|
28
30
|
page={1}
|
|
29
31
|
totalPages={totalPages}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// TheBuildIndexView so the tenant-copy + pagination contract can't drift
|
|
5
5
|
// between the two routes (mirrors src/pages/blog/page/[n].astro).
|
|
6
6
|
import TheBuildIndexView from "@/components/the-build/TheBuildIndexView.astro";
|
|
7
|
+
import { resolveBlogPresentation } from "@/lib/blog/presentation.js";
|
|
7
8
|
import { getTheBuildSettings, listBuildPosts } from "@/lib/the-build/provider.js";
|
|
8
9
|
import { paginateBlogPosts } from "@/lib/blog/pagination.js";
|
|
9
10
|
import { parseTheBuildPageParam, resolveTheBuildIndexCopy } from "@/lib/the-build/pagination.js";
|
|
@@ -28,6 +29,7 @@ if (!sliced) return new Response(null, { status: 404 });
|
|
|
28
29
|
basePath={basePath}
|
|
29
30
|
canonicalBase={canonicalBase}
|
|
30
31
|
copy={copy}
|
|
32
|
+
presentation={resolveBlogPresentation(settings)}
|
|
31
33
|
pagePosts={sliced.posts}
|
|
32
34
|
page={sliced.page}
|
|
33
35
|
totalPages={sliced.totalPages}
|
|
@@ -437,6 +437,53 @@ code {
|
|
|
437
437
|
color: #ffffff;
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
/* A disabled action must look disabled. This sheet is unlayered, so it beats
|
|
441
|
+
global.css's `@layer base` :disabled default; without this rule a disabled
|
|
442
|
+
.btn.primary is pixel-identical to a live one. Declared after every .btn
|
|
443
|
+
variant at equal specificity so it wins over each of them; a component rule
|
|
444
|
+
that paints a .btn variant must exclude :disabled or restyle it (pinned by
|
|
445
|
+
admin-disabled.contract.test.ts). */
|
|
446
|
+
button:disabled,
|
|
447
|
+
button[aria-disabled="true"] {
|
|
448
|
+
cursor: not-allowed;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.btn:disabled,
|
|
452
|
+
.btn[aria-disabled="true"],
|
|
453
|
+
.icon-btn:disabled,
|
|
454
|
+
.icon-btn[aria-disabled="true"] {
|
|
455
|
+
border-color: var(--admin-line);
|
|
456
|
+
background: var(--admin-surface-soft);
|
|
457
|
+
color: var(--admin-muted-2);
|
|
458
|
+
box-shadow: none;
|
|
459
|
+
filter: none;
|
|
460
|
+
cursor: not-allowed;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* The visible "why is this disabled" line beside a disabled action. */
|
|
464
|
+
.action-reason {
|
|
465
|
+
display: block;
|
|
466
|
+
max-width: 34ch;
|
|
467
|
+
margin: 4px 0 0 auto;
|
|
468
|
+
color: var(--admin-muted);
|
|
469
|
+
font-size: 12px;
|
|
470
|
+
line-height: 1.4;
|
|
471
|
+
white-space: normal;
|
|
472
|
+
text-align: right;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.action-reason[hidden] {
|
|
476
|
+
display: none;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.review-actions .action-reason,
|
|
480
|
+
.agg-launch .action-reason {
|
|
481
|
+
flex-basis: 100%;
|
|
482
|
+
max-width: none;
|
|
483
|
+
margin: 0;
|
|
484
|
+
text-align: left;
|
|
485
|
+
}
|
|
486
|
+
|
|
440
487
|
.floating-ask {
|
|
441
488
|
position: fixed;
|
|
442
489
|
right: 24px;
|