site: directory landing + The Mirror (chapter 02)
The face of learn.bztmon.com is now a chapter index: 01 The Exploded Cluster (moved to /cluster) and 02 The Mirror (/mirror), with a forming-chapter ghost slot. The Mirror ships rev 3 of the dossier - six curated plates fingerprinted under /assets/, its scroll engine as an external fingerprinted script (CSP script-src self holds), diagrams as inline SVG, replay panes from real receipts. Assembler grows a shared document shell + per-page preflight gates; build.sh inline-script check now covers every page.
This commit is contained in:
+65
-31
@@ -115,41 +115,77 @@ const appRef = "/assets/" + appName;
|
||||
const DESC = "Interactive teardown of Kubernetes and OpenShift delivery: containers, Services, GitOps, image supply chains and Helm, explained with scroll-driven exploded diagrams.";
|
||||
const OG_IMG = SITE + heroUrls["__HERO_GITOPS__"];
|
||||
|
||||
const html = `<!doctype html>
|
||||
// shared document shell: every page on the site gets the same head discipline
|
||||
const shell = ({ title, desc, path, ogImg, scripts, bodyHtml }) => `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>The Exploded Cluster</title>
|
||||
<meta name="description" content="${DESC}">
|
||||
<title>${title}</title>
|
||||
<meta name="description" content="${desc}">
|
||||
<meta name="theme-color" content="#070b14">
|
||||
<meta name="build-revision" content="${BUILD_REV}">
|
||||
<link rel="canonical" href="${SITE}/">
|
||||
<meta property="og:title" content="The Exploded Cluster">
|
||||
<meta property="og:description" content="${DESC}">
|
||||
<link rel="canonical" href="${SITE}${path}">
|
||||
<meta property="og:title" content="${title}">
|
||||
<meta property="og:description" content="${desc}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="${SITE}/">
|
||||
<meta property="og:image" content="${OG_IMG}">
|
||||
<meta property="og:url" content="${SITE}${path}">
|
||||
<meta property="og:image" content="${ogImg}">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="The Exploded Cluster">
|
||||
<meta name="twitter:description" content="${DESC}">
|
||||
<meta name="twitter:image" content="${OG_IMG}">
|
||||
<meta name="twitter:title" content="${title}">
|
||||
<meta name="twitter:description" content="${desc}">
|
||||
<meta name="twitter:image" content="${ogImg}">
|
||||
<link rel="icon" href="${favicon}">
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip" href="#main-content">Skip to content</a>
|
||||
<main id="main-content">
|
||||
${body}
|
||||
${bodyHtml}
|
||||
</main>
|
||||
<script src="${gsapRef}"></script>
|
||||
<script src="${stRef}"></script>
|
||||
<script src="${appRef}"></script>
|
||||
${scripts.map(s => `<script src="${s}"></script>`).join("\n")}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
writeFileSync(join(DIST, "index.html"), html);
|
||||
writeFileSync(join(DIST, "build.json"), JSON.stringify({ revision: BUILD_REV, app: appName }) + "\n");
|
||||
const html = shell({
|
||||
title: "The Exploded Cluster", desc: DESC, path: "/cluster", ogImg: OG_IMG,
|
||||
scripts: [gsapRef, stRef, appRef], bodyHtml: body,
|
||||
});
|
||||
|
||||
// ---- The Mirror (chapter 02): fingerprint its plates + its app script -------
|
||||
const MIRROR_DESC = "A pull-through container registry taken apart: why a lab runs its own mirror, the chicken-and-egg loops inside it, and the scars that taught each rule.";
|
||||
let mirror = readFileSync(join(ROOT, "pilot/mirror.tpl.html"), "utf8");
|
||||
const mirrorPlates = {};
|
||||
for (const n of ["20", "21", "22", "23", "24", "25"]) {
|
||||
const { url } = emit(join(ROOT, "assets/mirror", `${n}.jpg`), `mirror-${n}`, "jpg");
|
||||
mirrorPlates[n] = url;
|
||||
mirror = mirror.replace(`__M${n}__`, url);
|
||||
}
|
||||
const mirrorJs = readFileSync(join(ROOT, "pilot/mirror.js"));
|
||||
const mirrorJsName = `mirror.${fp(mirrorJs)}.js`;
|
||||
writeFileSync(join(DIST, "assets", mirrorJsName), mirrorJs);
|
||||
mirror = mirror.replace("__MIRROR_JS__", "/assets/" + mirrorJsName);
|
||||
if (mirror.includes("__M")) throw new Error("unfilled mirror placeholder");
|
||||
const mirrorHtml = shell({
|
||||
title: "The Mirror", desc: MIRROR_DESC, path: "/mirror",
|
||||
ogImg: SITE + mirrorPlates["20"], scripts: [], bodyHtml: mirror,
|
||||
});
|
||||
|
||||
// ---- the directory (index): the chapters of what this place teaches ---------
|
||||
const DIR_DESC = "The Teaching Lab: scroll-driven teardowns of real infrastructure - container platforms, registries and the machinery of delivery, one exploded system per chapter.";
|
||||
let dir = readFileSync(join(ROOT, "pilot/index.tpl.html"), "utf8")
|
||||
.replace("__THUMB_CLUSTER__", heroUrls["__HERO_GITOPS__"])
|
||||
.replace("__THUMB_MIRROR__", mirrorPlates["20"]);
|
||||
if (dir.includes("__THUMB")) throw new Error("unfilled directory placeholder");
|
||||
const dirHtml = shell({
|
||||
title: "The Teaching Lab", desc: DIR_DESC, path: "/",
|
||||
ogImg: SITE + mirrorPlates["20"], scripts: [], bodyHtml: dir,
|
||||
});
|
||||
|
||||
writeFileSync(join(DIST, "index.html"), dirHtml);
|
||||
writeFileSync(join(DIST, "cluster.html"), html);
|
||||
writeFileSync(join(DIST, "mirror.html"), mirrorHtml);
|
||||
writeFileSync(join(DIST, "build.json"), JSON.stringify({ revision: BUILD_REV, app: appName, mirror: mirrorJsName }) + "\n");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Preflight gates: fail the build loudly rather than ship a known defect.
|
||||
@@ -157,20 +193,18 @@ writeFileSync(join(DIST, "build.json"), JSON.stringify({ revision: BUILD_REV, ap
|
||||
const fail = [];
|
||||
const must = (cond, msg) => { if (!cond) fail.push(msg); };
|
||||
|
||||
// no inline <script> may survive (CSP script-src 'self')
|
||||
must(!(html.match(/<script(?![^>]*src=)[^>]*>/gi) || []).length, "inline <script> present");
|
||||
// no unresolved template placeholders
|
||||
must(!/__[A-Z_]+__/.test(html), "unresolved __PLACEHOLDER__ in output");
|
||||
// exactly one <h1>
|
||||
must((html.match(/<h1[\s>]/gi) || []).length === 1, "expected exactly one <h1>");
|
||||
// unique ids
|
||||
const ids = [...html.matchAll(/\sid="([^"]+)"/g)].map(m => m[1]);
|
||||
must(new Set(ids).size === ids.length, "duplicate id attribute(s): " +
|
||||
ids.filter((v, k) => ids.indexOf(v) !== k).join(", "));
|
||||
// internal anchor targets resolve
|
||||
const idset = new Set(ids);
|
||||
for (const m of html.matchAll(/href="#([^"]+)"/g)) {
|
||||
if (m[1] && !idset.has(m[1])) fail.push("dangling internal link #" + m[1]);
|
||||
// per-page structural gates (CSP + document hygiene) for every page shipped
|
||||
for (const [pg, doc] of [["cluster", html], ["mirror", mirrorHtml], ["index", dirHtml]]) {
|
||||
must(!(doc.match(/<script(?![^>]*src=)[^>]*>/gi) || []).length, `${pg}: inline <script> present`);
|
||||
must(!/__[A-Z0-9_]+__/.test(doc), `${pg}: unresolved __PLACEHOLDER__ in output`);
|
||||
must((doc.match(/<h1[\s>]/gi) || []).length === 1, `${pg}: expected exactly one <h1>`);
|
||||
const pids = [...doc.matchAll(/\sid="([^"]+)"/g)].map(m => m[1]);
|
||||
must(new Set(pids).size === pids.length, `${pg}: duplicate id attribute(s): ` +
|
||||
pids.filter((v, k) => pids.indexOf(v) !== k).join(", "));
|
||||
const pidset = new Set(pids);
|
||||
for (const m of doc.matchAll(/href="#([^"]+)"/g)) {
|
||||
if (m[1] && !pidset.has(m[1])) fail.push(`${pg}: dangling internal link #` + m[1]);
|
||||
}
|
||||
}
|
||||
// banned / obsolete strings (user-visible copy)
|
||||
const banned = [
|
||||
|
||||
Reference in New Issue
Block a user