learn site: one shared masthead for every page
The three pages had each grown their own tokens, type scale and alignment, so the openings looked like three different sites. The document shell now owns the masthead family - tokens, centred masthead, eyebrow, title, lede, fact strip, scroll cue and the index component - and the pages carry only their content. The Mirror gains the section index the arc has, drops its duplicate technical footer, and its title now uses the same magenta accent instead of a one-off gradient.
This commit is contained in:
@@ -57,6 +57,58 @@ function jpegSize(buf) {
|
||||
|
||||
// skip link: emitted by the shared shell, so its accessibility CSS lives in the shell
|
||||
// too - visually hidden until keyboard focus, visible and prominent when focused.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared masthead: ONE opening treatment for every page on the site. Pages used
|
||||
// to declare their own tokens, type scale and alignment, which is why the three
|
||||
// headers drifted apart. This block owns the family - tokens, the centred
|
||||
// masthead, eyebrow, title, lede and scroll cue - and pages simply use it.
|
||||
// ---------------------------------------------------------------------------
|
||||
const MAST_CSS = `<style>
|
||||
:root{
|
||||
--void:#070b14; --ink:#c9d7ef; --dim:#7286a8; --ink-dim:#7286a8; --line:#1b2740;
|
||||
--cyan:#3fbaf5; --magenta:#e879f9; --amber:#e8b44a;
|
||||
--dur-fast:.2s; --dur-base:.35s; --dur-slow:.6s;
|
||||
--sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
||||
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
||||
}
|
||||
.masthead{position:relative;max-width:64ch;margin:0 auto;padding:16vh 24px 9vh;text-align:center}
|
||||
.masthead > :not(.introbg){position:relative}
|
||||
.chapnav{font-family:var(--mono);font-size:.72rem;letter-spacing:.14em;margin:0 0 2.2rem}
|
||||
.chapnav a{color:var(--dim);text-decoration:none;border-bottom:1px solid transparent;
|
||||
padding-bottom:2px;transition:color var(--dur-fast) ease,border-color var(--dur-fast) ease}
|
||||
.chapnav a:hover,.chapnav a:focus-visible{color:var(--cyan);border-color:var(--cyan)}
|
||||
.eyebrow{font-family:var(--mono);font-size:.72rem;letter-spacing:.22em;text-transform:uppercase;
|
||||
color:var(--cyan);margin:0 0 1.5rem}
|
||||
.masthead h1,.thesis{font-size:clamp(1.9rem,4.8vw,3.2rem);line-height:1.1;margin:0 0 1.3rem;
|
||||
font-weight:600;letter-spacing:-.02em;text-wrap:balance;font-family:var(--sans);color:var(--ink)}
|
||||
.masthead h1 em,.thesis em{font-style:normal;color:var(--magenta)}
|
||||
.lede{color:var(--dim);font-size:1.02rem;line-height:1.7;margin:0 auto;max-width:58ch}
|
||||
.scrollcue{font-family:var(--mono);font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;
|
||||
color:var(--dim);margin-top:4.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)}
|
||||
.mastfacts{display:flex;flex-wrap:wrap;justify-content:center;gap:0;margin:2.6rem auto 0;
|
||||
border-top:1px solid var(--line);border-bottom:1px solid var(--line);max-width:52ch}
|
||||
.mastfacts div{flex:1 1 8rem;padding:.85rem 1rem;border-right:1px solid var(--line)}
|
||||
.mastfacts div:last-child{border-right:0}
|
||||
.mastfacts dt{font-family:var(--mono);font-size:.64rem;letter-spacing:.16em;text-transform:uppercase;
|
||||
color:var(--dim);margin:0 0 .3rem}
|
||||
.mastfacts dd{margin:0;font-family:var(--mono);font-size:.88rem;color:var(--cyan)}
|
||||
.toc{max-width:760px;margin:0 auto;padding:0 24px 5vh}
|
||||
.toc .kt{font-family:var(--mono);font-size:.68rem;letter-spacing:.16em;
|
||||
text-transform:uppercase;color:var(--dim);text-align:center;margin:0 0 1rem}
|
||||
.toc ol{list-style:none;margin:0;padding:0;counter-reset:toc;display:flex;flex-direction:column;gap:8px}
|
||||
.toc li{counter-increment:toc}
|
||||
.toc a{display:grid;grid-template-columns:34px 110px 1fr;gap:12px;align-items:baseline;
|
||||
border:1px solid var(--line);border-radius:8px;padding:10px 16px;background:#0a1020;text-decoration:none}
|
||||
.toc a::before{content:counter(toc,decimal-leading-zero);font-family:var(--mono);font-size:.68rem;color:var(--dim)}
|
||||
.toc a:hover,.toc a:focus-visible{border-color:var(--cyan)}
|
||||
.toc b{font-family:var(--mono);font-size:.72rem;letter-spacing:.08em;color:var(--cyan);font-weight:600}
|
||||
.toc span{font-size:.82rem;line-height:1.5;color:var(--dim)}
|
||||
@media (max-width:640px){.toc a{grid-template-columns:34px 1fr}.toc span{grid-column:2}}
|
||||
@media (max-width:900px){.masthead{padding:9vh 22px 6vh}}
|
||||
</style>`;
|
||||
|
||||
const SKIP_CSS = `<style>
|
||||
a.skip{position:absolute;left:-9999px;top:0;z-index:99}
|
||||
a.skip:focus{left:1rem;top:1rem;background:#070b14;color:#3fbaf5;
|
||||
@@ -161,6 +213,7 @@ const shell = ({ title, desc, path, ogImg, scripts, bodyHtml }) => `<!doctype ht
|
||||
<meta name="twitter:image" content="${ogImg}">
|
||||
<link rel="icon" href="${favicon}">
|
||||
${SKIP_CSS}
|
||||
${MAST_CSS}
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip" href="#main-content">Skip to content</a>
|
||||
|
||||
+9
-31
@@ -1,37 +1,15 @@
|
||||
<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}
|
||||
.wrap{max-width:56rem;margin:0 auto;padding:0 1.25rem}
|
||||
header.mast{padding:5.5rem 0 2.5rem;position:relative}
|
||||
.rule{display:flex;gap:6px;margin:0 0 1.6rem}
|
||||
.rule span{height:2px;border-radius:2px}
|
||||
.rule span:nth-child(1){width:52px;background:var(--cyan)}
|
||||
.rule span:nth-child(2){width:20px;background:var(--magenta)}
|
||||
.rule span:nth-child(3){width:8px;background:var(--dim)}
|
||||
.wrap{max-width:64ch;margin:0 auto;padding:0 1.25rem}
|
||||
h1 em{color:transparent;background:linear-gradient(100deg,var(--cyan) 10%,var(--magenta));
|
||||
-webkit-background-clip:text;background-clip:text;padding-bottom:.12em}
|
||||
.stats{display:flex;flex-wrap:wrap;gap:0;margin:2.6rem 0 0;border-top:1px solid var(--line);
|
||||
border-bottom:1px solid var(--line);max-width:44rem}
|
||||
.stats div{flex:1 1 9rem;padding:.9rem 1.2rem .9rem 0;border-right:1px solid var(--line)}
|
||||
.stats div:last-child{border-right:0}
|
||||
.stats dt{font-family:var(--mono);font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;
|
||||
color:var(--dim);margin:0 0 .3rem}
|
||||
.stats dd{margin:0;font-family:var(--mono);font-size:.95rem;color:var(--cyan)}
|
||||
.card{position:relative}
|
||||
.card::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:0;
|
||||
background:linear-gradient(120deg,rgba(63,186,245,.10),transparent 55%);transition:opacity .3s ease}
|
||||
.card:hover::before,.card:focus-visible::before{opacity:1}
|
||||
.eyebrow{font-family:var(--mono);font-size:.78rem;letter-spacing:.2em;color:var(--cyan);text-transform:uppercase;margin:0 0 .8rem}
|
||||
h1{font-size:clamp(2.2rem,6.5vw,3.6rem);line-height:1.05;margin:0 0 1rem;text-wrap:balance;font-weight:700}
|
||||
h1 em{color:var(--cyan);font-style:normal}
|
||||
.lede{max-width:38rem;color:var(--dim);font-size:1.1rem;line-height:1.6;margin:0}
|
||||
.chapters{display:grid;gap:1.4rem;margin:3rem 0 5rem}
|
||||
.card{display:grid;grid-template-columns:minmax(180px,300px) 1fr;gap:0;border:1px solid var(--line);
|
||||
border-radius:.6rem;overflow:hidden;background:#0a101d;text-decoration:none;color:inherit;
|
||||
@@ -57,18 +35,18 @@ footer a{color:var(--cyan)}
|
||||
@media (max-width:640px){.card{grid-template-columns:1fr}.card .thumb{max-height:170px}}
|
||||
</style>
|
||||
|
||||
<header class="mast wrap">
|
||||
<div class="rule" aria-hidden="true"><span></span><span></span><span></span></div>
|
||||
<header class="masthead">
|
||||
<p class="eyebrow">bztmon · the teaching lab</p>
|
||||
<h1>Taken apart, <em>on purpose.</em></h1>
|
||||
<p class="lede">Scroll-driven teardowns of machinery this lab actually runs. Each chapter is one
|
||||
system, exploded - the diagrams move while the words explain them, and every claim traces back to
|
||||
a running estate: configs lifted from the fleet, incidents told with their receipts.</p>
|
||||
<dl class="stats">
|
||||
<h1>Infrastructure is easier to trust<br><em>when you can see it come apart.</em></h1>
|
||||
<p class="lede">Scroll-driven teardowns of machinery this lab actually runs. Each chapter is
|
||||
one system, exploded - the diagrams move while the words explain them, and every claim traces
|
||||
back to a running estate: configs lifted from the fleet, incidents told with their receipts.</p>
|
||||
<dl class="mastfacts">
|
||||
<div><dt>Chapters live</dt><dd>02</dd></div>
|
||||
<div><dt>Subject</dt><dd>container platforms</dd></div>
|
||||
<div><dt>Evidence</dt><dd>labelled, per claim</dd></div>
|
||||
<div><dt>Evidence</dt><dd>labelled per claim</dd></div>
|
||||
</dl>
|
||||
<p class="scrollcue" aria-hidden="true">chapters<span></span></p>
|
||||
</header>
|
||||
|
||||
<nav class="wrap" aria-label="Chapters">
|
||||
|
||||
+21
-54
@@ -1,11 +1,4 @@
|
||||
<style>
|
||||
:root{
|
||||
--void:#070b14; --void-2:#0a0f1b; --ink:#dfe6f0; --ink-dim:#98a2b4;
|
||||
--cyan:#3fbaf5; --magenta:#e879f9; --amber:#e8b44a; --line:#1c2434; --card:#0d1424;
|
||||
--good:#7fe0a7; --bad:#ff8fa3;
|
||||
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
||||
--sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html{scroll-behavior:smooth}
|
||||
body{margin:0;background:var(--void);color:var(--ink);
|
||||
@@ -17,12 +10,9 @@
|
||||
grid-template-columns:[full-start] minmax(1.25rem,1fr) [main-start] min(46rem,calc(100% - 2.5rem)) [main-end] minmax(1.25rem,1fr) [full-end]}
|
||||
.layout>*{grid-column:main}
|
||||
.layout>.scene{grid-column:full}
|
||||
header.hero{min-height:70vh;display:flex;flex-direction:column;justify-content:center;padding:4.5rem 0 1.5rem;position:relative}
|
||||
.eyebrow{font-family:var(--mono);font-size:.78rem;letter-spacing:.22em;color:var(--cyan);text-transform:uppercase}
|
||||
h1{font-family:var(--sans);font-weight:750;font-size:clamp(2.8rem,8vw,4.6rem);
|
||||
line-height:.98;margin:.5rem 0 1rem;text-wrap:balance;letter-spacing:-.01em}
|
||||
.outcome{max-width:36rem;color:var(--ink-dim);font-size:1.12rem}
|
||||
.outcome b{color:var(--ink);font-weight:600}
|
||||
.chapnav{font-family:var(--mono);font-size:.72rem;letter-spacing:.14em;margin:0 0 2.4rem}
|
||||
.chapnav a{color:var(--ink-dim);text-decoration:none;border-bottom:1px solid transparent;
|
||||
padding-bottom:2px;transition:color .2s ease,border-color .2s ease}
|
||||
@@ -31,16 +21,6 @@
|
||||
-webkit-background-clip:text;background-clip:text;color:transparent;padding-bottom:.12em}
|
||||
.standfirst{font-size:clamp(1.15rem,2.4vw,1.5rem);color:var(--ink);font-weight:600;
|
||||
margin:.2rem 0 1rem;letter-spacing:-.01em;max-width:34rem}
|
||||
.herostats{display:flex;flex-wrap:wrap;gap:0;margin:2.4rem 0 0;border-top:1px solid var(--line);
|
||||
border-bottom:1px solid var(--line)}
|
||||
.herostats div{flex:1 1 8rem;padding:.85rem 1.1rem .85rem 0;border-right:1px solid var(--line)}
|
||||
.herostats div:last-child{border-right:0}
|
||||
.herostats dt{font-family:var(--mono);font-size:.66rem;letter-spacing:.16em;
|
||||
text-transform:uppercase;color:var(--ink-dim);margin:0 0 .25rem}
|
||||
.herostats dd{margin:0;font-family:var(--mono);font-size:.92rem;color:var(--cyan)}
|
||||
.scrollcue{font-family:var(--mono);font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;
|
||||
color:var(--ink-dim);margin-top:3rem;display:flex;flex-direction:column;gap:.7rem}
|
||||
.scrollcue span{display:block;width:1px;height:46px;background:linear-gradient(var(--cyan),transparent)}
|
||||
h2{font-family:var(--sans);font-weight:700;font-size:1.85rem;letter-spacing:0;
|
||||
margin:4.2rem 0 1rem;text-wrap:balance;scroll-margin-top:2.5rem}
|
||||
h2 .n{color:var(--cyan);font-family:var(--mono);font-weight:500;font-size:1rem;vertical-align:.35em;margin-right:.65rem;letter-spacing:.1em}
|
||||
@@ -89,13 +69,6 @@
|
||||
.facts dl{display:grid;grid-template-columns:max-content 1fr;gap:.35rem 1.2rem;margin:0}
|
||||
.facts dt{font-family:var(--mono);font-size:.72rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-dim);padding-top:.15rem}
|
||||
.facts dd{margin:0}
|
||||
.toc{border:1px solid var(--line);border-radius:.55rem;padding:1rem 1.3rem;margin:2rem 0;background:var(--void-2)}
|
||||
.toc .k{font-family:var(--mono);font-size:.72rem;letter-spacing:.18em;color:var(--cyan);text-transform:uppercase}
|
||||
.toc ol{margin:.6rem 0 0;padding-left:1.4rem;columns:2;column-gap:2.5rem;font-size:.92rem}
|
||||
.toc li{margin:.3rem 0;break-inside:avoid}
|
||||
.toc a{color:var(--ink);text-decoration:none}
|
||||
.toc a:hover{color:var(--cyan)}
|
||||
@media (max-width:640px){.toc ol{columns:1}}
|
||||
ol{padding-left:1.3rem} ol li{margin:.6rem 0}
|
||||
ul{padding-left:1.2rem} ul li{margin:.5rem 0}
|
||||
em{color:#fff}
|
||||
@@ -125,16 +98,15 @@
|
||||
|
||||
<div class="layout">
|
||||
|
||||
<header class="hero">
|
||||
<header class="masthead">
|
||||
<nav class="chapnav" aria-label="Site"><a href="/">← The Teaching Lab</a></nav>
|
||||
<p class="eyebrow">Chapter 02 · Dossier 005 · operations</p>
|
||||
<h1>The <em>Mirror</em></h1>
|
||||
<p class="standfirst">A pull-through OCI registry, taken apart.</p>
|
||||
<p class="outcome">What the deployed version guarantees, the retention behaviour that
|
||||
evicts digest-pinned content, and how each lesson translates to production. Every claim
|
||||
carries an evidence label — and the corrections to my own first draft are on the
|
||||
page, not quietly edited out.</p>
|
||||
<dl class="herostats">
|
||||
<p class="eyebrow">Chapter 02 · The Mirror · Dossier 005</p>
|
||||
<h1>The cache was the easy part.<br><em>Now watch what it quietly throws away.</em></h1>
|
||||
<p class="lede">A pull-through registry, taken apart: what the deployed version actually
|
||||
guarantees, the retention behaviour that evicts digest-pinned content, and how each lesson
|
||||
translates to production. Every claim carries an evidence label - including the corrections
|
||||
to my own first draft. Scroll, and the machinery comes apart.</p>
|
||||
<dl class="mastfacts">
|
||||
<div><dt>Registry</dt><dd>zot v2.1.17</dd></div>
|
||||
<div><dt>Upstreams</dt><dd>5, on-demand</dd></div>
|
||||
<div><dt>Platform</dt><dd>Talos / containerd</dd></div>
|
||||
@@ -163,19 +135,19 @@
|
||||
<span class="st prop">proposed</span> not deployed · <span class="st open">open</span> not yet run</p>
|
||||
</div>
|
||||
|
||||
<nav class="toc" aria-labelledby="toc-h">
|
||||
<p class="k" id="toc-h">Contents</p>
|
||||
<nav class="toc" aria-label="Section index">
|
||||
<p class="kt">Index</p>
|
||||
<ol>
|
||||
<li><a href="#incident">The build that "hung"</a></li>
|
||||
<li><a href="#anatomy">What the mirror actually does</a></li>
|
||||
<li><a href="#routing">Request routing and five upstreams</a></li>
|
||||
<li><a href="#bootstrap">Bootstrap, fallback and failure modes</a></li>
|
||||
<li><a href="#digests">Digests, manifests and supply chain</a></li>
|
||||
<li><a href="#retention">Retention, GC and the digest-only trap</a></li>
|
||||
<li><a href="#auth">The authentication migration</a></li>
|
||||
<li><a href="#observability">Observability that means something</a></li>
|
||||
<li><a href="#production">The production translation</a></li>
|
||||
<li><a href="#verified">Verified, open and references</a></li>
|
||||
<li><a href="#incident"><b>Section 01</b><span>The build that "hung"</span></a></li>
|
||||
<li><a href="#anatomy"><b>Section 02</b><span>What the mirror does</span></a></li>
|
||||
<li><a href="#routing"><b>Section 03</b><span>Routing and five upstreams</span></a></li>
|
||||
<li><a href="#bootstrap"><b>Section 04</b><span>Bootstrap, fallback, failure</span></a></li>
|
||||
<li><a href="#digests"><b>Section 05</b><span>Digests and supply chain</span></a></li>
|
||||
<li><a href="#retention"><b>Section 06</b><span>Retention and the digest trap</span></a></li>
|
||||
<li><a href="#auth"><b>Section 07</b><span>The authentication migration</span></a></li>
|
||||
<li><a href="#observability"><b>Section 08</b><span>Observability that means something</span></a></li>
|
||||
<li><a href="#production"><b>Section 09</b><span>The production translation</span></a></li>
|
||||
<li><a href="#verified"><b>Section 10</b><span>Verified, open, references</span></a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
@@ -849,12 +821,7 @@ cache re-warm cost.</li>
|
||||
<li>Docker Hub <a href="https://docs.docker.com/docker-hub/usage/">usage and limits</a> · buildah <a href="https://github.com/containers/buildah/blob/main/docs/buildah-build.1.md">build --retry semantics</a></li>
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
DOSSIER / 005 · last reviewed 2026-08-25 against the running estate and the
|
||||
sources above · corrections in this revision: retention/GC semantics, warm-cache
|
||||
behaviour, retry arithmetic, upstream namespace routing ·
|
||||
<a href="/">The Teaching Lab</a> · <a href="https://git.bztmon.com/jwright/exploded-cluster">source</a>
|
||||
</footer>
|
||||
|
||||
|
||||
<footer class="outro">
|
||||
<p>© 2026 Jonathon Wright · <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a>
|
||||
|
||||
Reference in New Issue
Block a user