repo: remove superseded pilot files, ASCII-clean the build and nginx configs

Drops the legacy course-1 template, the two python assemblers the node
assembler replaced, and the unversioned scene manifest left over from the
manifest-<type> split - none referenced by build.sh, the assembler or the
README, and none produce build output. The v2 pages stay: the README lists
them as parked. Also converts typographic dashes to ASCII in build.sh and the
two nginx configs, and corrects an assembler comment that still pointed at the
removed python build.
This commit is contained in:
2026-08-25 21:13:45 +10:00
parent b67ada9c65
commit b27417ff02
8 changed files with 7 additions and 565 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
// Reads pilot/arc.tpl.html, splits the app <script> out to a fingerprinted arc.js, injects
// the scene manifests, fingerprints every /assets/ file, sets intrinsic image dimensions,
// wraps a full HTML document, writes build.json, and runs preflight gates.
// Runs on bare node (the build image has no python). Artifact build = assemble-arc.py.
// Runs on bare node (the build image has no python). Superseded the earlier python assembler.
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { createHash } from "node:crypto";
import { dirname, join } from "node:path";
-51
View File
@@ -1,51 +0,0 @@
#!/usr/bin/env python3
"""Assemble the delivery-arc page: tpl + inlined GSAP + scene manifests + base64 heroes.
Heroes are inlined ONCE (in the <img> src); the slice engine reuses img.src for the
slab backgrounds, so nothing is duplicated. Manifests come from tools/slice-arc.py.
"""
import base64, json, pathlib, re
ROOT = pathlib.Path(__file__).resolve().parent.parent
def b64(webp):
return "data:image/webp;base64," + base64.b64encode((ROOT / "assets/dist" / webp).read_bytes()).decode()
def man(tag):
return json.dumps(json.loads((ROOT / "pilot" / f"manifest-{tag}.json").read_text()),
separators=(",", ":"))
out = (ROOT / "pilot/arc.tpl.html").read_text() \
.replace("__MAN_SURROUND__", man("surround")) \
.replace("__MAN_NODE__", man("node")) \
.replace("__MAN_POD__", man("pod")) \
.replace("__MAN_TRAFFIC__", man("traffic")) \
.replace("__MAN_ORBIT__", man("orbit")) \
.replace("__MAN_DOCKER__", man("docker")) \
.replace("__MAN_CLUSTER__", man("cluster")) \
.replace("__MAN_GITOPS__", man("gitops")) \
.replace("__MAN_SUPPLY__", man("supply")) \
.replace("__MAN_HELM__", man("helm")) \
.replace("__HERO_INTRO__", b64("intro-establishing.webp")) \
.replace("__HERO_SURROUND__", b64("course-00-surround.webp")) \
.replace("__HERO_NODE__", b64("course-III-node.webp")) \
.replace("__HERO_POD__", b64("course-III-pod.webp")) \
.replace("__HERO_TRAFFIC__", b64("course-IV-traffic.webp")) \
.replace("__HERO_ORBIT__", b64("course-V-orbit.webp")) \
.replace("__HERO_DOCKER__", b64("docker-layers.webp")) \
.replace("__HERO_CLUSTER__", b64("course-II-cluster.webp")) \
.replace("__HERO_GITOPS__", b64("course-VI-gitops.webp")) \
.replace("__HERO_SUPPLY__", b64("course-VII-supply-chain.webp")) \
.replace("__HERO_HELM__", b64("course-VIII-helm-press.webp")) \
.replace("__HERO_APPENDIX__", b64("appendix-dependency-ledger.webp")) \
.replace("__DIAG_FLOW__", "data:image/svg+xml;base64," + base64.b64encode((ROOT / "assets/dist/delivery-flow.svg").read_bytes()).decode())
assert "__MAN_" not in out and "__HERO_" not in out and "__DIAG_" not in out, "unfilled placeholder"
out = (f"<script>{(ROOT / 'gsap.min.js').read_text()}</script>\n"
f"<script>{(ROOT / 'st.min.js').read_text()}</script>\n" + out)
dest = ROOT / "pilot/arc.html"
dest.write_text(out)
heroes = len(re.findall(r"data:image/webp", out))
print(f"built pilot/arc.html {len(out)/1024:.0f}KB ({heroes} heroes inlined)")
-98
View File
@@ -1,98 +0,0 @@
#!/usr/bin/env python3
"""Derive the Course I slice manifest from measured geometry, then assemble the pilot.
The cut lines below are NOT eyeballed. They were fitted from the hero's own pixels:
each slab's bottom edge tracked column-by-column through the void gaps, least-squares
lines through the left and right arms, tip at their intersection. Symmetry of the two
arms came out within 0-10% on every slab, which is the check that the fit is real.
Re-derive with tools/fit-geometry.py if the hero is ever regenerated.
"""
import base64, json, pathlib, re, sys
ROOT = pathlib.Path(__file__).resolve().parent.parent
HERO = ROOT / "assets/dist/docker-layers.webp"
# measured bottom silhouettes: left corner -> bottom tip -> right corner (percent of frame)
CUTS = {
"writable": dict(l=(27.75, 27.24), tip=(50.77, 40.73), r=(72.00, 26.87)),
"code": dict(l=(28.38, 41.34), tip=(49.76, 56.15), r=(71.50, 41.02)),
"deps": dict(l=(27.88, 56.36), tip=(49.48, 76.21), r=(71.88, 56.30)),
"base": dict(l=(28.12, 74.41), tip=(49.95, 94.20), r=(71.75, 74.40)),
}
EPS = 0.7 # regions grow UPWARD only — the slab in front always covers the overlap.
# Growing downward would drag a copy of the slab behind into the slice.
NEST = 0.75 # fraction of each gap closed when collapsed
ORDER = ["base", "deps", "code", "writable"] # paint order: back -> front
LABEL = {
"base": ("Base image", "The OS layer everything else is stacked on. Pull it once, "
"share it across every image on the host."),
"deps": ("Dependencies", "Your packages and runtime. Change these and every layer "
"above has to be rebuilt."),
"code": ("Application code", "Your actual program — usually the smallest layer, and "
"the one that changes every single build."),
"writable": ("Writable layer", "Created fresh per container, thrown away when it dies. "
"Anything written here is not in the image."),
}
def V(c): return [list(c["l"]), list(c["tip"]), list(c["r"])]
def up(v, e): return [[p[0], round(p[1] - e, 2)] for p in v]
def band(top, bot):
"""Region between two silhouettes; sides run to the frame edge, which is pure void."""
p = []
p += ([[0, top[0][1]]] + top + [[100, top[2][1]]]) if top else [[0, 0], [100, 0]]
p += ([[100, bot[2][1]]] + bot[::-1] + [[0, bot[0][1]]]) if bot else [[100, 100], [0, 100]]
return p
def manifest():
tips = {k: CUTS[k]["tip"][1] for k in CUTS}
dy, acc = {}, 0.0
for i, k in enumerate(ORDER):
if i:
acc += (tips[ORDER[i - 1]] - tips[k]) * NEST
dy[k] = round(acc, 2)
regions = {
"base": band(up(V(CUTS["deps"]), EPS), None),
"deps": band(up(V(CUTS["code"]), EPS), V(CUTS["deps"])),
"code": band(up(V(CUTS["writable"]), EPS), V(CUTS["code"])),
"writable": band(None, V(CUTS["writable"])),
}
return {
"scene": "course-I-docker-layers", "eps": EPS, "nest": NEST,
"source": "assets/dist/docker-layers.webp",
"parts": [{"name": k, "title": LABEL[k][0], "blurb": LABEL[k][1],
"anchor": round(CUTS[k]["tip"][1] - 6, 2),
"points": regions[k], "dy": dy[k]} for k in ORDER],
}
def main():
if not HERO.exists():
sys.exit(f"missing hero: {HERO}")
man = manifest()
(ROOT / "pilot/manifest.json").write_text(json.dumps(man, indent=1))
hero = "data:image/webp;base64," + base64.b64encode(HERO.read_bytes()).decode()
out = (ROOT / "pilot/course-1.tpl.html").read_text() \
.replace("__MANIFEST__", json.dumps(man, separators=(",", ":"))) \
.replace("__HERO__", hero)
out = (f"<script>{(ROOT / 'gsap.min.js').read_text()}</script>\n"
f"<script>{(ROOT / 'st.min.js').read_text()}</script>\n" + out)
dest = ROOT / "pilot/course-1.html"
dest.write_text(out)
blocks = re.findall(r"<script>([\s\S]*?)</script>", out)
print(f"built {dest.relative_to(ROOT)} {len(out)/1024:.0f}KB "
f"({len(blocks)} script blocks, hero {len(hero)/1024:.0f}KB inlined)")
for name, part in zip(ORDER, man["parts"]):
print(f" {name:<9} {len(part['points']):2d} pts collapse travel {part['dy']:5.2f}%")
if __name__ == "__main__":
main()
-229
View File
@@ -1,229 +0,0 @@
<header class="masthead">
<p class="eyebrow">The Exploded Cluster · Course I</p>
<h1>An image is not a box.<br><em>It is a stack of frozen diffs.</em></h1>
<p class="lede">Scroll, and the thing you keep calling "a container image" comes apart in your hands.
Four layers. Each one only stores what changed from the layer under it.</p>
<p class="scrollcue" aria-hidden="true">scroll<span></span></p>
</header>
<section class="course" id="course-1" aria-labelledby="c1h">
<h2 id="c1h" class="sr-only">Docker image layers, exploded</h2>
<div class="pin">
<div class="viewport">
<figure class="scene" id="scene">
<img class="flat" src="__HERO__"
alt="An exploded isometric view of a container image: four stacked slabs floating apart —
a heavy metal base, a circuit-etched dependency layer, a magenta-traced code layer,
and a thin frosted-glass writable layer on top.">
</figure>
<ol class="legend" id="legend"></ol>
</div>
</div>
</section>
<section class="after">
<p class="fieldnote">
<b>Field note.</b> The order of your Dockerfile is a caching decision, not a style choice.
Put <code>COPY . .</code> above your dependency install and you have told the builder to
throw away every cached layer every time you change one line of code.
</p>
</section>
<style>
:root{
--void:#070b14; --ink:#c9d7ef; --dim:#7286a8; --line:#1b2740;
--cyan:#3fbaf5; --magenta:#e879f9;
--sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}
*{box-sizing:border-box}
body{margin:0;background:var(--void);color:var(--ink);font-family:var(--sans);
-webkit-font-smoothing:antialiased;overflow-x:hidden}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* ---- masthead ---- */
.masthead{max-width:64ch;margin:0 auto;padding:18vh 24px 10vh;text-align:center}
.eyebrow{font-family:var(--mono);font-size:.72rem;letter-spacing:.22em;text-transform:uppercase;
color:var(--cyan);margin:0 0 1.6rem}
.masthead h1{font-size:clamp(2rem,5.5vw,3.6rem);line-height:1.08;margin:0 0 1.4rem;
font-weight:600;letter-spacing:-.02em;text-wrap:balance}
.masthead h1 em{font-style:normal;color:var(--magenta)}
.lede{font-size:clamp(1rem,1.7vw,1.16rem);line-height:1.65;color:var(--dim);margin:0 auto;max-width:52ch}
.scrollcue{font-family:var(--mono);font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;
color:var(--dim);margin-top:5rem;display:flex;flex-direction:column;align-items:center;gap:.7rem}
.scrollcue span{display:block;width:1px;height:46px;
background:linear-gradient(var(--cyan),transparent)}
/* ---- pinned scene ---- */
.course{position:relative}
.pin{min-height:100svh;display:grid;place-items:center;padding:3vh 20px}
.viewport{width:min(1180px,100%);display:grid;grid-template-columns:minmax(0,1fr) 300px;
gap:28px;align-items:center}
.scene{position:relative;margin:0;line-height:0}
.scene .flat{display:block;width:100%;height:auto}
.slab{position:absolute;inset:0;background-size:100% 100%;background-repeat:no-repeat;
will-change:transform}
/* ---- legend ---- */
.legend{position:relative;list-style:none;margin:0;padding:0;height:100%;min-height:min(58vh,540px)}
.legend li{position:absolute;left:0;right:0;transform:translateY(-50%);
padding-left:26px;border-left:1px solid var(--line);
opacity:.34;transition:opacity .35s ease,border-color .35s ease}
.legend li.lit{opacity:1;border-left-color:var(--cyan)}
.legend .n{font-family:var(--mono);font-size:.68rem;letter-spacing:.16em;color:var(--cyan);
display:block;margin-bottom:.3rem}
.legend .t{font-size:.98rem;font-weight:600;margin:0 0 .3rem;letter-spacing:-.01em}
.legend .b{font-size:.8rem;line-height:1.55;color:var(--dim);margin:0}
.after{max-width:60ch;margin:0 auto;padding:12vh 24px 22vh}
.fieldnote{border-left:2px solid var(--magenta);padding:0 0 0 20px;margin:0;
font-size:.94rem;line-height:1.7;color:var(--dim)}
.fieldnote b{color:var(--magenta);font-family:var(--mono);font-size:.72rem;
letter-spacing:.16em;text-transform:uppercase;display:block;margin-bottom:.5rem}
.fieldnote code{font-family:var(--mono);font-size:.86em;color:var(--ink);
background:#101a2c;padding:.12em .4em;border-radius:3px}
/* ---- small screens: no pin, legend stacks under the scene ---- */
@media (max-width:860px){
.viewport{grid-template-columns:1fr;gap:20px}
.legend{height:auto;min-height:0;display:flex;flex-direction:column;gap:14px}
.legend li{position:static;transform:none;opacity:1;border-left-color:var(--line)}
.legend li.lit{border-left-color:var(--cyan)}
.pin{min-height:0;padding:4vh 18px 8vh}
.masthead{padding:10vh 22px 6vh}
}
/* ---- pilot bench (HUD + hand scrub) - strip before P3 wiring ---- */
.bench{width:min(720px,100%);margin:18px auto 0;text-align:center}
.bench input{width:100%;accent-color:var(--cyan)}
.bench .hud{font-family:var(--mono);font-size:.68rem;letter-spacing:.06em;
color:var(--dim);margin:.5rem 0 0}
/* ---- no JS: the flat hero and the full legend are the page ---- */
body:not(.fx) .slab{display:none}
body:not(.fx) .legend li{position:static;transform:none;opacity:1}
body:not(.fx) .legend{height:auto;min-height:0;display:flex;flex-direction:column;gap:14px}
</style>
<script>
(() => {
"use strict";
const MAN = __MANIFEST__;
const HERO = "__HERO__";
const scene = document.getElementById("scene");
const legend = document.getElementById("legend");
const RM = matchMedia("(prefers-reduced-motion: reduce)").matches;
// ---- legend (always built, JS or not the markup would be empty otherwise) ----
MAN.parts.forEach((p, i) => {
const li = document.createElement("li");
li.dataset.part = p.name;
li.style.top = p.anchor + "%";
li.innerHTML =
'<span class="n">' + String(i + 1).padStart(2, "0") + '</span>' +
'<p class="t">' + p.title + '</p>' +
'<p class="b">' + p.blurb + '</p>';
legend.appendChild(li);
});
if (!window.gsap || !window.ScrollTrigger) return; // progressive enhancement: leave the flat hero
document.body.classList.add("fx");
gsap.registerPlugin(ScrollTrigger);
// ---- slices: one layer per part, all cut from the SAME hero ----
const slabs = MAN.parts.map(p => {
const d = document.createElement("div");
d.className = "slab";
d.dataset.part = p.name;
d.style.backgroundImage = "url(" + HERO + ")";
d.style.clipPath = "polygon(" + p.points.map(q => q[0] + "% " + q[1] + "%").join(",") + ")";
scene.appendChild(d);
return d;
});
scene.querySelector(".flat").style.visibility = "hidden"; // slices replace it; alt text survives
const lis = [...legend.children];
const setLit = t => lis.forEach((li, i) => {
// a layer "arrives" as its own travel completes; base is home from the start
const share = MAN.parts[i].dy / (MAN.parts[MAN.parts.length - 1].dy || 1);
li.classList.toggle("lit", t >= share * 0.82);
});
// Travel is CLAMPED at the hero layout: collapsed -> hero, never past it and never
// sideways. Occlusion only increases on the way in, which is what keeps each slice's
// hidden notch buried. Overshoot or fan and the tears show.
const apply = t => {
slabs.forEach((el, i) => {
el.style.transform = "translate3d(0," + (MAN.parts[i].dy * (1 - t)).toFixed(3) + "%,0)";
});
scene.style.transform = "scale(" + (0.94 + 0.06 * t).toFixed(4) + ")";
setLit(t);
};
// ---- pilot bench: HUD + hand scrub - strip before P3 wiring ----
const bench = document.createElement("div");
bench.className = "bench";
bench.innerHTML =
'<input type="range" min="0" max="1000" value="0" aria-label="scrub the teardown">' +
'<p class="hud">boot</p>';
document.querySelector(".pin").appendChild(bench);
const hud = bench.querySelector(".hud");
const slider = bench.querySelector("input");
let mode = "boot";
const show = v => {
hud.textContent = mode + " \u00b7 " + innerWidth + "x" + innerHeight +
" \u00b7 scroll " + Math.round(scrollY) + " of " +
Math.max(0, document.documentElement.scrollHeight - innerHeight) +
" \u00b7 t=" + v.toFixed(2);
slider.value = String(Math.round(v * 1000));
};
const drive = v => { apply(v); show(v); };
slider.addEventListener("input", () => drive(+slider.value / 1000));
// The artifact viewer can size the iframe to the content and scroll the PARENT page:
// then this window never scrolls and a pinned scrub can never fire. Pick the engine
// by what the environment can actually do, not by viewport width alone.
const initPin = () => {
mode = "pin+scrub";
ScrollTrigger.create({
trigger: "#course-1",
start: "top top",
end: "+=2200",
pin: ".pin",
scrub: 0.6,
onUpdate: self => drive(self.progress),
onRefreshInit: () => apply(0)
});
ScrollTrigger.refresh();
drive(0);
};
const initAssemble = () => {
mode = "assemble";
document.querySelector(".pin").style.minHeight = "0"; // svh is degenerate when the frame is content-sized
drive(0);
const io = new IntersectionObserver(es => {
if (!es.some(e => e.isIntersecting)) return;
io.disconnect();
gsap.to({ v: 0 }, {
v: 1, duration: 1.6, ease: "power3.out",
onUpdate: function () { drive(this.targets()[0].v); }
});
}, { threshold: 0.15 });
io.observe(scene);
};
const boot = () => {
if (RM) { mode = "reduced-motion"; drive(1); return; }
const scrollable = document.documentElement.scrollHeight - innerHeight > 120;
if (scrollable && matchMedia("(min-width: 861px)").matches) initPin();
else initAssemble();
};
const flat = scene.querySelector(".flat");
(flat.decode ? flat.decode().catch(() => {}) : Promise.resolve())
.then(() => requestAnimationFrame(() => requestAnimationFrame(boot)));
})();
</script>
-180
View File
@@ -1,180 +0,0 @@
{
"scene": "course-I-docker-layers",
"eps": 0.7,
"nest": 0.75,
"source": "assets/dist/docker-layers.webp",
"parts": [
{
"name": "base",
"title": "Base image",
"blurb": "The OS layer everything else is stacked on. Pull it once, share it across every image on the host.",
"anchor": 88.2,
"points": [
[
0,
55.66
],
[
27.88,
55.66
],
[
49.48,
75.51
],
[
71.88,
55.6
],
[
100,
55.6
],
[
100,
100
],
[
0,
100
]
],
"dy": 0.0
},
{
"name": "deps",
"title": "Dependencies",
"blurb": "Your packages and runtime. Change these and every layer above has to be rebuilt.",
"anchor": 70.21,
"points": [
[
0,
40.64
],
[
28.38,
40.64
],
[
49.76,
55.45
],
[
71.5,
40.32
],
[
100,
40.32
],
[
100,
56.3
],
[
71.88,
56.3
],
[
49.48,
76.21
],
[
27.88,
56.36
],
[
0,
56.36
]
],
"dy": 13.49
},
{
"name": "code",
"title": "Application code",
"blurb": "Your actual program \u2014 usually the smallest layer, and the one that changes every single build.",
"anchor": 50.15,
"points": [
[
0,
26.54
],
[
27.75,
26.54
],
[
50.77,
40.03
],
[
72.0,
26.17
],
[
100,
26.17
],
[
100,
41.02
],
[
71.5,
41.02
],
[
49.76,
56.15
],
[
28.38,
41.34
],
[
0,
41.34
]
],
"dy": 28.54
},
{
"name": "writable",
"title": "Writable layer",
"blurb": "Created fresh per container, thrown away when it dies. Anything written here is not in the image.",
"anchor": 34.73,
"points": [
[
0,
0
],
[
100,
0
],
[
100,
26.87
],
[
72.0,
26.87
],
[
50.77,
40.73
],
[
27.75,
27.24
],
[
0,
27.24
]
],
"dy": 40.1
}
]
}