[object Object]

← back to Model Arena

Build 'The Form to Request a Form' satire strip from minicpm-v arena attempt: 3 SVG panels, 8 twistable endings, triplicate peel, recursive ending, docket + certificate

6a61a4b755f99b90baeac60f93ff7a6cc1bdbff0 · 2026-09-23 16:13:05 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqUe1Hvqsnrkxpe4R6jrk1

Files touched

Diff

commit 6a61a4b755f99b90baeac60f93ff7a6cc1bdbff0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 23 16:13:05 2026 -0700

    Build 'The Form to Request a Form' satire strip from minicpm-v arena attempt: 3 SVG panels, 8 twistable endings, triplicate peel, recursive ending, docket + certificate
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01DqUe1Hvqsnrkxpe4R6jrk1
---
 form-to-request-a-form.html | 1227 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1227 insertions(+)

diff --git a/form-to-request-a-form.html b/form-to-request-a-form.html
new file mode 100644
index 0000000..4f1c191
--- /dev/null
+++ b/form-to-request-a-form.html
@@ -0,0 +1,1227 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>The Form to Request a Form — Bureaucratic Satire</title>
+<meta name="description" content="A three-panel satire strip about an invented municipal office, one mailbox, and a form that requires a form to request a form.">
+<style>
+:root{
+  --bg:#e7dfcb; --sheet:#fbf8ef; --ink:#1d1a16; --muted:#5b554a; --rule:#c8bea5; --rule-strong:#1d1a16;
+  --accent:#f7a30d; --accent-ink:#1d1a16; --blue:#4D77B6; --brick:#b8432f; --teal:#2f6f6a;
+  --focus:#1c55c9; --halo:rgba(28,85,201,.28);
+  --ok-bg:#e3efe9; --ok-ink:#1e5049; --err-bg:#f7e1dc; --err-ink:#8a2c1c; --info-bg:#e2e9f5; --info-ink:#27467a;
+  --manila:#ead9a9; --led:#ff5a3c; --led-bg:#120b09; --btn-bg:#1d1a16; --btn-ink:#f6f0e1;
+  --white-sheet:#f6f0e1; --canary-sheet:#f6e7a1; --pink-sheet:#f3cfcf;
+  --serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
+  --sans:"Helvetica Neue",Helvetica,Arial,system-ui,sans-serif;
+  --cond:"Avenir Next Condensed","Arial Narrow","Roboto Condensed","sans-serif-condensed","Helvetica Neue",Arial,sans-serif;
+  --mono:ui-monospace,"SF Mono",Menlo,"Courier New",monospace;
+  color-scheme:light;
+}
+@media (prefers-color-scheme:dark){
+  :root:not([data-theme="light"]){
+    --bg:#15171a; --sheet:#1f2226; --ink:#ece6d8; --muted:#a8a192; --rule:#3a3e44; --rule-strong:#ece6d8;
+    --focus:#8fb4ff; --halo:rgba(143,180,255,.3);
+    --ok-bg:#16302c; --ok-ink:#9fe0d2; --err-bg:#3a1d17; --err-ink:#ffb4a3; --info-bg:#1a2740; --info-ink:#b5ccff;
+    --manila:#3b3525; --btn-bg:#ece6d8; --btn-ink:#15171a; color-scheme:dark;
+  }
+}
+:root[data-theme="dark"]{
+  --bg:#15171a; --sheet:#1f2226; --ink:#ece6d8; --muted:#a8a192; --rule:#3a3e44; --rule-strong:#ece6d8;
+  --focus:#8fb4ff; --halo:rgba(143,180,255,.3);
+  --ok-bg:#16302c; --ok-ink:#9fe0d2; --err-bg:#3a1d17; --err-ink:#ffb4a3; --info-bg:#1a2740; --info-ink:#b5ccff;
+  --manila:#3b3525; --btn-bg:#ece6d8; --btn-ink:#15171a; color-scheme:dark;
+}
+*{box-sizing:border-box}
+[hidden]{display:none!important}
+html{-webkit-text-size-adjust:100%}
+body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.5 var(--sans);
+  background-image:repeating-linear-gradient(0deg,transparent 0 27px,rgba(77,119,182,.07) 27px 28px)}
+.wrap{max-width:1320px;margin:0 auto;padding:16px}
+@media (min-width:720px){.wrap{padding:24px 32px}}
+:focus-visible{outline:3px solid var(--focus);outline-offset:3px;box-shadow:0 0 0 7px var(--halo);border-radius:4px}
+.sr{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
+.skip{position:absolute;left:12px;top:-60px;background:var(--ink);color:var(--sheet);padding:10px 14px;border-radius:4px;z-index:50;text-decoration:none;font-weight:700}
+.skip:focus{top:12px}
+
+/* ---------- masthead ---------- */
+.mast{border-bottom:4px double var(--rule-strong);padding-bottom:12px;margin-bottom:14px}
+.kicker{display:flex;flex-wrap:wrap;gap:4px 14px;font:700 .72rem/1.3 var(--sans);letter-spacing:.14em;text-transform:uppercase;color:var(--muted);border-bottom:1px solid var(--rule);padding-bottom:8px;margin-bottom:10px}
+.kicker b{color:var(--brick)}
+h1{font:800 clamp(2rem,6vw,3.7rem)/1.02 var(--serif);letter-spacing:-.015em;margin:0 0 8px}
+h1 .form-code{display:inline-block;font:700 .9rem/1 var(--mono);letter-spacing:.06em;vertical-align:.9em;margin-left:.35em;padding:.25em .45em;border:2px solid var(--blue);color:var(--blue);border-radius:3px;transform:rotate(-3deg)}
+.dek{font:italic 1.08rem/1.45 var(--serif);color:var(--muted);margin:0;max-width:62ch}
+
+/* ---------- case file ---------- */
+.casefile{background:var(--sheet);border:2px solid var(--rule-strong);display:grid;grid-template-columns:repeat(2,minmax(0,1fr));margin-bottom:18px;position:relative}
+.casefile::before{content:"FORM RF-0 · CASE FILE · DO NOT FOLD";position:absolute;top:-10px;right:10px;background:var(--bg);padding:0 8px;font:700 .64rem/20px var(--mono);letter-spacing:.12em;color:var(--muted)}
+.cell{padding:9px 12px 10px;border-right:1px solid var(--rule);border-bottom:1px solid var(--rule);min-width:0}
+.cell label,.cell .lbl{display:block;font:700 .64rem/1.2 var(--sans);letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin-bottom:4px}
+.cell .val{font:600 .98rem/1.3 var(--mono);overflow-wrap:anywhere}
+.cell.span2{grid-column:span 2}
+@media (min-width:720px){.casefile{grid-template-columns:repeat(3,minmax(0,1fr))}.cell.span2{grid-column:auto}}
+@media (min-width:1100px){.casefile{grid-template-columns:1.1fr 1.15fr 1.4fr 1.2fr 1fr 1.5fr}}
+.status{display:inline-block;padding:2px 8px;border:2px solid currentColor;border-radius:3px;font:800 .78rem/1.4 var(--sans);letter-spacing:.08em;text-transform:uppercase}
+.st-0{color:var(--muted)} .st-1,.st-2{color:var(--blue)} .st-3{color:var(--brick)} .st-4{color:var(--teal)}
+.ledbox{display:inline-flex;align-items:baseline;gap:8px;background:var(--led-bg);border-radius:4px;padding:3px 9px;box-shadow:inset 0 0 0 2px #000}
+.ledbox .digits{font:700 1.2rem/1.2 var(--mono);color:var(--led);letter-spacing:.14em;text-shadow:0 0 6px rgba(255,90,60,.7)}
+.ledbox.blink .digits{animation:ledblink .9s steps(2) 2}
+@keyframes ledblink{50%{opacity:.15}}
+.yours{display:block;font:500 .74rem/1.3 var(--sans);color:var(--muted);margin-top:4px}
+.steps{display:flex;gap:4px;margin-bottom:6px}
+.steps span{flex:1;height:9px;border:2px solid var(--rule-strong);border-radius:2px;background:transparent}
+.steps span.on{background:var(--blue);border-color:var(--blue)}
+.meter{display:flex;gap:3px;flex-wrap:wrap}
+.meter i{width:14px;height:14px;border:1.5px solid var(--rule-strong);border-radius:2px;display:block}
+.meter i.seen{background:var(--accent)}
+.meter i.cur{outline:2px solid var(--focus);outline-offset:1px}
+.small{font-size:.78rem;color:var(--muted);font-family:var(--sans)}
+
+/* ---------- strip ---------- */
+.strip-head{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 16px;margin:6px 0 10px}
+.strip-head h2{font:800 1.15rem/1.2 var(--serif);margin:0}
+.strip-head p{margin:0;font-size:.85rem;color:var(--muted)}
+.strip{display:grid;grid-template-columns:minmax(0,1fr);gap:22px;max-width:560px;margin:0 auto}
+@media (min-width:1000px){.strip{grid-template-columns:repeat(3,minmax(0,1fr));max-width:none;gap:18px}}
+.panel{margin:0;min-width:0;scroll-margin:16px 0 170px}
+.phead{display:flex;justify-content:space-between;align-items:center;gap:8px;font:800 .72rem/1.2 var(--sans);letter-spacing:.12em;text-transform:uppercase;margin-bottom:6px;min-height:22px}
+.phead .pstate{font-weight:700;color:var(--muted);letter-spacing:.06em;text-align:right}
+.phead .pstate.done{color:var(--teal)}
+.panel-art{position:relative;aspect-ratio:400/460;border:3px solid #1d1a16;background:var(--white-sheet);cursor:pointer;
+  box-shadow:4px 4px 0 rgba(0,0,0,.18)}
+.panel-art svg{display:block;width:100%;height:100%}
+.panel.p3 .panel-art{box-shadow:5px 5px 0 -1px var(--pad1,var(--canary-sheet)),5px 5px 0 1px #1d1a16,10px 10px 0 -1px var(--pad2,var(--pink-sheet)),10px 10px 0 1px #1d1a16}
+.panel-art.thud{animation:thud .2s ease-out}
+@keyframes thud{25%{transform:translate(2px,3px)}55%{transform:translate(-2px,-1px)}}
+figcaption{margin-top:9px;font:italic .92rem/1.45 var(--serif);color:var(--ink)}
+.panel.p3 figcaption{margin-top:15px}
+figcaption .when{font:700 .72rem/1 var(--mono);font-style:normal;letter-spacing:.05em;color:var(--brick);margin-right:4px}
+figcaption.pending{color:var(--muted)}
+
+/* cover = pending panel (empty state) */
+.cover{position:absolute;inset:0;width:100%;height:100%;border:0;margin:0;padding:18px;cursor:pointer;color:#3d3423;font-family:var(--sans);
+  background:repeating-linear-gradient(135deg,rgba(0,0,0,.035) 0 10px,transparent 10px 20px),#ead9a9;
+  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;text-align:center}
+.cover::before{content:"";position:absolute;inset:10px;border:2px dashed rgba(61,52,35,.45);pointer-events:none}
+.cover .tab{position:absolute;top:0;left:18px;background:#1d1a16;color:#f6f0e1;font:800 .66rem/1 var(--sans);letter-spacing:.14em;padding:6px 8px 5px;border-radius:0 0 4px 4px}
+.cover .big{font:800 clamp(1.35rem,3.2vw,2rem)/1.05 var(--serif);color:#1d1a16}
+.cover .sub{font:700 .74rem/1.35 var(--sans);letter-spacing:.1em;text-transform:uppercase}
+.cover .how{font:600 .84rem/1.35 var(--sans);background:#f6f0e1;border:2px solid #1d1a16;padding:7px 12px;border-radius:999px;box-shadow:3px 3px 0 #1d1a16}
+.cover.queued{cursor:pointer;filter:saturate(.55);color:#6a604c}
+.cover.queued .how{background:transparent;box-shadow:none;border-style:dashed}
+.cover:hover .how{background:#fff}
+.cover.nudge{animation:nudge .5s ease-in-out 2}
+@keyframes nudge{50%{box-shadow:inset 0 0 0 6px var(--accent)}}
+.cover svg{width:74px;height:auto}
+.cover:focus-visible{outline-offset:-6px;box-shadow:inset 0 0 0 9px var(--halo)}
+
+/* stamp overlay */
+.stamp{position:absolute;right:5%;bottom:4%;pointer-events:none;color:var(--st,#b8432f);border:4px double currentColor;border-radius:7px;padding:5px 11px 4px;
+  font-family:var(--cond);text-align:center;transform:rotate(-9deg);mix-blend-mode:multiply;opacity:.9;max-width:62%;background:transparent}
+.stamp::after{content:"";position:absolute;inset:-4px;background:radial-gradient(circle,#fff 0 .9px,transparent 1.4px) 0 0/6px 6px,radial-gradient(circle,#fff 0 .7px,transparent 1.2px) 3px 2px/9px 8px;opacity:.55}
+.stamp b{display:block;font-weight:800;font-size:clamp(17px,5.4vw,27px);letter-spacing:.07em;line-height:1}
+@media (min-width:1000px){.stamp b{font-size:clamp(15px,1.75vw,26px)}}
+.stamp small{display:block;font:700 9px/1.2 var(--mono);letter-spacing:.08em;margin-top:3px}
+.stamp.slam{animation:slam .26s cubic-bezier(.2,1.4,.4,1) both}
+@keyframes slam{0%{transform:rotate(-20deg) scale(2.1);opacity:0}70%{opacity:.95}100%{transform:rotate(-9deg) scale(1);opacity:.9}}
+
+/* triplicate peel */
+.peel{position:absolute;inset:-3px;border:3px solid #1d1a16;z-index:4;pointer-events:none;transform-origin:12% 0%;overflow:hidden}
+.peel svg{display:block;width:100%;height:100%}
+.peel.go{animation:peel .72s cubic-bezier(.55,0,.75,.35) forwards}
+@keyframes peel{0%{transform:none}22%{transform:translate(4px,-10px) rotate(-3deg)}100%{transform:translate(-18%,-135%) rotate(-28deg);opacity:0}}
+
+/* ---------- controls ---------- */
+.dock{position:sticky;bottom:0;z-index:20;margin:22px -16px 0;padding:10px 16px calc(10px + env(safe-area-inset-bottom));background:var(--sheet);border-top:3px solid var(--rule-strong);box-shadow:0 -6px 18px rgba(0,0,0,.08)}
+@media (min-width:720px){.dock{margin:24px 0 0;border:3px solid var(--rule-strong);border-radius:6px;bottom:12px}}
+@media (min-width:1000px){.dock{position:relative;bottom:auto;box-shadow:none}}
+@media (max-width:719px){.btn .fc .k{display:none}.btn-primary,.btn-twist{font-size:.95rem;padding:6px 12px}}
+.dock-inner{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
+.feedback{flex:1 1 100%;margin:0;min-height:1.5em;font:.9rem/1.4 var(--sans);padding:6px 10px;border-radius:4px;border-left:5px solid transparent}
+.feedback:empty::before{content:"Nothing processed yet — open the case to begin."; color:var(--muted)}
+.feedback.ok{background:var(--ok-bg);color:var(--ok-ink);border-color:currentColor}
+.feedback.err{background:var(--err-bg);color:var(--err-ink);border-color:currentColor}
+.feedback.info{background:var(--info-bg);color:var(--info-ink);border-color:currentColor}
+.feedback .code{font:800 .72rem/1 var(--mono);letter-spacing:.06em;margin-right:6px;white-space:nowrap}
+.btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:48px;padding:6px 16px;border-radius:6px;border:2.5px solid #1d1a16;
+  font:800 1rem/1.1 var(--sans);cursor:pointer;position:relative;text-align:left;transition:transform .08s}
+.btn:active{transform:translate(2px,2px)}
+.btn .fc{display:block;font:700 .6rem/1 var(--mono);letter-spacing:.1em;opacity:.75;margin-top:3px}
+.btn .lbl{display:block}
+.btn-primary{background:var(--btn-bg);color:var(--btn-ink);border-color:var(--btn-bg);box-shadow:3px 3px 0 var(--blue);flex:1 1 150px}
+.btn-twist{background:var(--accent);color:var(--accent-ink);box-shadow:3px 3px 0 #1d1a16;flex:1 1 150px}
+.btn-ghost{background:transparent;color:var(--ink);border-color:var(--rule-strong);min-height:44px;font-size:.92rem}
+.btn[aria-pressed="true"]{background:var(--teal);color:#fff;border-color:var(--teal)}
+.btn.shake{animation:shake .38s}
+@keyframes shake{20%,60%{transform:translateX(-5px)}40%,80%{transform:translateX(5px)}}
+.btn.confirm{background:var(--err-bg);color:var(--err-ink);border-color:var(--err-ink)}
+.btn svg{flex:none}
+@media (min-width:720px){.btn-primary,.btn-twist{flex:0 1 auto;min-width:220px}}
+.ticket-pop{position:absolute;left:50%;top:-6px;transform:translate(-50%,0);background:#f6f0e1;color:#1d1a16;border:2px solid #1d1a16;font:800 .8rem/1 var(--mono);padding:6px 8px;border-radius:2px;pointer-events:none;animation:pop .9s ease-out forwards;white-space:nowrap;z-index:3}
+@keyframes pop{0%{transform:translate(-50%,10px) rotate(0);opacity:0}25%{opacity:1}100%{transform:translate(-50%,-46px) rotate(-8deg);opacity:0}}
+.utils{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:14px}
+.utils .keys{font-size:.8rem;color:var(--muted);margin-left:auto}
+kbd{font:700 .75rem/1 var(--mono);border:1.5px solid var(--rule-strong);border-bottom-width:3px;border-radius:4px;padding:2px 5px;background:var(--sheet);color:var(--ink)}
+
+/* ---------- lower: docket + cast ---------- */
+.lower{display:grid;gap:22px;margin-top:28px}
+@media (min-width:1000px){.lower{grid-template-columns:minmax(0,1.7fr) minmax(0,1fr)}}
+.card{background:var(--sheet);border:2px solid var(--rule-strong);padding:16px;position:relative}
+.card h2{font:800 1.2rem/1.2 var(--serif);margin:0 0 2px}
+.card .under{font-size:.84rem;color:var(--muted);margin:0 0 12px}
+.docket-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}
+.dk{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:10px;align-items:center;padding:8px 10px;border:1.5px solid var(--rule);border-radius:4px;background:var(--bg)}
+.dk[aria-current="true"]{border-color:var(--rule-strong);box-shadow:inset 5px 0 0 var(--accent)}
+.dk .tk{font:800 .95rem/1 var(--mono);background:var(--led-bg);color:var(--led);padding:6px 7px;border-radius:3px;letter-spacing:.1em}
+.dk .t1{font-weight:800;display:block;overflow-wrap:anywhere}
+.dk .t2{font-size:.8rem;color:var(--muted);display:block}
+.dk .cur{font:800 .7rem/1.2 var(--sans);letter-spacing:.1em;text-transform:uppercase;color:var(--teal);text-align:right}
+.btn-mini{min-height:40px;padding:4px 12px;font-size:.85rem;border-width:2px;background:var(--sheet);color:var(--ink);border-color:var(--rule-strong)}
+.empty{display:flex;gap:14px;align-items:center;padding:14px;border:2px dashed var(--rule);border-radius:6px;color:var(--muted)}
+.empty svg{flex:none;width:64px;height:auto}
+.empty b{display:block;color:var(--ink);font:800 1rem/1.3 var(--serif)}
+.cert{margin-bottom:14px;padding:18px;border:6px double var(--teal);background:#fbf6e6;color:#1d1a16;text-align:center;position:relative}
+.cert h3{font:800 1.35rem/1.15 var(--serif);margin:0 0 6px}
+.cert .fc2{font:700 .7rem/1 var(--mono);letter-spacing:.14em;color:#2f6f6a;margin-bottom:8px}
+.cert p{font:1rem/1.5 var(--serif);margin:0 auto 10px;max-width:56ch}
+.cert .sig{display:flex;justify-content:center;gap:28px;flex-wrap:wrap;font:italic .9rem/1.3 var(--serif);margin:10px 0 14px}
+.cert .sig span{border-top:1.5px solid #1d1a16;padding-top:4px;min-width:140px}
+.cert .row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
+.cert .btn-ghost{color:#1d1a16;border-color:#1d1a16}
+.cert.in{animation:certin .5s ease-out}
+@keyframes certin{from{transform:translateY(12px) rotate(-1.5deg);opacity:0}}
+.cast{display:grid;gap:10px}
+.who{display:grid;grid-template-columns:64px minmax(0,1fr);gap:12px;align-items:center}
+.who svg{width:64px;height:64px;border:2px solid #1d1a16;border-radius:50%;background:#f6f0e1}
+.who b{display:block;font:800 .98rem/1.25 var(--serif)}
+.who .role{display:block;font:700 .66rem/1.3 var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--brick)}
+.who p{margin:2px 0 0;font-size:.86rem;line-height:1.4;color:var(--muted)}
+footer{margin:34px 0 90px;padding-top:12px;border-top:1px solid var(--rule);font-size:.8rem;color:var(--muted)}
+footer p{margin:0 0 6px;max-width:90ch}
+
+/* svg text classes */
+.lt{font-family:var(--cond);font-weight:800;letter-spacing:.2px}
+.lt .em{fill:#b8432f}
+.sg{font-family:var(--cond);font-weight:800}
+.led{font-family:var(--mono);font-weight:700;font-size:14px;fill:#ff5a3c;letter-spacing:1.5px}
+.tiny{font-family:var(--sans);font-weight:700}
+
+@media (prefers-reduced-motion:reduce){
+  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
+}
+</style>
+</head>
+<body>
+<a class="skip" href="#strip">Skip to the comic strip</a>
+
+<!-- shared SVG defs (not display:none, so patterns/filters resolve everywhere) -->
+<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
+  <defs>
+    <pattern id="pt-dots" width="7" height="7" patternUnits="userSpaceOnUse"><circle cx="3.5" cy="3.5" r="1.05" fill="#1d1a16" opacity=".13"/></pattern>
+    <pattern id="pt-hatch" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><line x1="0" y1="0" x2="0" y2="6" stroke="#1d1a16" stroke-width="1.1" opacity=".33"/></pattern>
+    <pattern id="pt-bead" width="16" height="10" patternUnits="userSpaceOnUse"><line x1="1" y1="0" x2="1" y2="10" stroke="#1d1a16" stroke-width="1" opacity=".2"/></pattern>
+    <pattern id="pt-grass" width="10" height="9" patternUnits="userSpaceOnUse"><path d="M2 9 l2 -6 M6 9 l1 -5" stroke="#1d1a16" stroke-width="1" opacity=".28" fill="none"/></pattern>
+    <filter id="boil0" x="-3%" y="-3%" width="106%" height="106%"><feTurbulence type="fractalNoise" baseFrequency="0.032" numOctaves="2" seed="2"/><feDisplacementMap in="SourceGraphic" scale="2.4" xChannelSelector="R" yChannelSelector="G"/></filter>
+    <filter id="boil1" x="-3%" y="-3%" width="106%" height="106%"><feTurbulence type="fractalNoise" baseFrequency="0.032" numOctaves="2" seed="11"/><feDisplacementMap in="SourceGraphic" scale="2.4" xChannelSelector="R" yChannelSelector="G"/></filter>
+    <filter id="boil2" x="-3%" y="-3%" width="106%" height="106%"><feTurbulence type="fractalNoise" baseFrequency="0.032" numOctaves="2" seed="29"/><feDisplacementMap in="SourceGraphic" scale="2.4" xChannelSelector="R" yChannelSelector="G"/></filter>
+  </defs>
+</svg>
+
+<div class="wrap">
+  <header class="mast">
+    <div class="kicker"><span><b>The Lower Pendleford Gazette</b></span><span>Bureaucratic Satire</span><span>Political Cartoon Competition #74</span></div>
+    <h1>The Form to Request a Form<span class="form-code" aria-hidden="true">RF-0</span></h1>
+    <p class="dek">A three-panel saga in which one man asks to repaint his mailbox, and the Municipality of Lower Pendleford asks him to ask.</p>
+  </header>
+
+  <section class="casefile" aria-label="Case file">
+    <div class="cell"><span class="lbl">Case no.</span><span class="val">RF-2026-000417</span></div>
+    <div class="cell"><span class="lbl">Applicant</span><span class="val">B. Oyelaran-Fitch</span></div>
+    <div class="cell span2"><span class="lbl">Matter</span><span class="val">Repaint mailbox, Eggshell → Bone</span></div>
+    <div class="cell"><span class="lbl">Status</span><span id="status" class="status st-0">Awaiting processing</span></div>
+    <div class="cell"><span class="lbl">Now serving</span><span class="ledbox" id="ledbox"><span class="digits" id="nowServing">0417</span></span><span class="yours">Your number: 0418</span></div>
+    <div class="cell span2"><span class="lbl">Progress</span>
+      <div class="steps" aria-hidden="true"><span></span><span></span><span></span></div>
+      <div class="meter" id="meter" aria-hidden="true"></div>
+      <span class="small" id="progressText">Panels processed: 0 of 3 · Endings on file: 0 of 8</span>
+    </div>
+  </section>
+
+  <main>
+    <div class="strip-head">
+      <h2 id="strip-title">The strip</h2>
+      <p>Click a panel (or press <kbd>N</kbd>) to process it. Once all three are stamped, <b>Plot Twist!</b> files a new punchline.</p>
+    </div>
+
+    <section class="strip" id="strip" aria-labelledby="strip-title">
+      <figure class="panel p1" data-i="0">
+        <div class="phead"><span>Panel 1 · Submission</span><span class="pstate">Not yet processed</span></div>
+        <div class="panel-art">
+          <svg viewBox="0 0 400 460" role="img" aria-label="Panel 1, not yet processed."></svg>
+          <button class="cover" type="button"></button>
+          <div class="stamp" aria-hidden="true" hidden></div>
+        </div>
+        <figcaption class="pending">Awaiting processing.</figcaption>
+      </figure>
+      <figure class="panel p2" data-i="1">
+        <div class="phead"><span>Panel 2 · Review</span><span class="pstate">Not yet processed</span></div>
+        <div class="panel-art">
+          <svg viewBox="0 0 400 460" role="img" aria-label="Panel 2, not yet processed."></svg>
+          <button class="cover" type="button"></button>
+          <div class="stamp" aria-hidden="true" hidden></div>
+        </div>
+        <figcaption class="pending">Awaiting processing.</figcaption>
+      </figure>
+      <figure class="panel p3" data-i="2">
+        <div class="phead"><span>Panel 3 · Resolution</span><span class="pstate">Not yet processed</span></div>
+        <div class="panel-art">
+          <svg viewBox="0 0 400 460" role="img" aria-label="Panel 3, not yet processed."></svg>
+          <button class="cover" type="button"></button>
+          <div class="stamp" aria-hidden="true" hidden></div>
+        </div>
+        <figcaption class="pending">Awaiting processing.</figcaption>
+      </figure>
+    </section>
+
+    <div class="dock" role="region" aria-label="Strip controls">
+      <div class="dock-inner">
+        <p class="feedback" id="feedback" role="status" aria-live="polite"></p>
+        <button class="btn btn-primary" id="nextBtn" type="button">
+          <svg width="22" height="22" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 12h13M12 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
+          <span><span class="lbl" id="nextLbl">Process Panel 1</span><span class="fc">FORM NX-2<span class="k"> · KEY N</span></span></span>
+        </button>
+        <button class="btn btn-twist" id="twistBtn" type="button">
+          <svg width="22" height="22" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 12a8 8 0 1 1-2.4-5.7M20 4v5h-5" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
+          <span><span class="lbl">Plot Twist!</span><span class="fc">FORM PT-1<span class="k"> · KEY T</span></span></span>
+        </button>
+      </div>
+    </div>
+
+    <div class="utils">
+      <button class="btn btn-ghost" id="refileBtn" type="button">
+        <svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4v6h6M4.5 10A8 8 0 1 1 6 17" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
+        <span id="refileLbl">Refile from the start</span>
+      </button>
+      <button class="btn btn-ghost" id="soundBtn" type="button" aria-pressed="false">
+        <svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 9h4l5-4v14l-5-4H4z" fill="currentColor"/><path id="soundWaves" d="M16 9c1.5 1.5 1.5 4.5 0 6M18.5 6.5c3 3 3 8 0 11" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" opacity=".35"/></svg>
+        <span id="soundLbl">Stamp sound: off</span>
+      </button>
+      <span class="keys">Keys: <kbd>N</kbd>/<kbd>→</kbd> next panel · <kbd>T</kbd> plot twist · <kbd>R</kbd> refile · <kbd>M</kbd> sound</span>
+    </div>
+
+    <div class="lower">
+      <section class="card" aria-labelledby="docket-title">
+        <h2 id="docket-title">Twist docket</h2>
+        <p class="under">Every plot twist is filed, numbered, and stamped. Re-open any earlier one.</p>
+        <div id="certSlot"></div>
+        <div id="docketEmpty" class="empty">
+          <svg viewBox="0 0 80 56" aria-hidden="true"><path d="M6 30 L16 10 H64 L74 30 V50 H6 Z" fill="#ead9a9" stroke="#1d1a16" stroke-width="3" stroke-linejoin="round"/><path d="M6 30 H26 L30 38 H50 L54 30 H74" fill="none" stroke="#1d1a16" stroke-width="3" stroke-linejoin="round"/><path d="M28 20 H52" stroke="#1d1a16" stroke-width="2" opacity=".4"/></svg>
+          <div><b>Request a Form?</b><span id="emptyText">No plot twists on file. The punchline stands exactly as originally filed.</span></div>
+        </div>
+        <ol class="docket-list" id="docket" reversed></ol>
+      </section>
+      <aside class="card" aria-labelledby="cast-title">
+        <h2 id="cast-title">Dramatis personae</h2>
+        <p class="under">All invented. No actual clerks were inconvenienced.</p>
+        <div class="cast" id="cast"></div>
+      </aside>
+    </div>
+  </main>
+
+  <footer>
+    <p>All persons, pigeons, departments, municipalities and form numbers are invented. Any resemblance to an office you have personally waited in is coincidental and may be reported on Form CO-1 (Coincidence Observed), available upon request.</p>
+    <p>Rebuilt and completed from mac1 · minicpm-v's Model Arena attempt (kept: the “Bureaucratic Satire” title, the orange <b>Plot Twist!</b> button, the form-blue border and the “Request a Form?” prompt).</p>
+  </footer>
+</div>
+
+<script>
+(() => {
+'use strict';
+/* ================= palette & helpers ================= */
+const INK='#1d1a16', BRICK='#b8432f', TEAL='#2f6f6a', MUSTARD='#d9a33b', SLATE='#2f3b45', LAV='#b7aecf';
+const SKIN_D='#e3aa82', SKIN_B='#8a5634', BONE='#ebe1c8';
+const SHEETS=['#f6f0e1','#f6e7a1','#f3cfcf']; const SHEET_NAMES=['white','canary','pink'];
+const RM = window.matchMedia('(prefers-reduced-motion: reduce)');
+const reduced = () => RM.matches;
+const sw = (w=3) => `stroke="${INK}" stroke-width="${w}" stroke-linejoin="round" stroke-linecap="round"`;
+const esc = s => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+const pad4 = n => String(n).padStart(4,'0');
+const limb = (d,color,w=13) => `<path d="${d}" fill="none" stroke="${INK}" stroke-width="${w+5}" stroke-linecap="round"/><path d="${d}" fill="none" stroke="${color}" stroke-width="${w}" stroke-linecap="round"/>`;
+const hand = (x,y,skin,r=7.5) => `<circle cx="${x}" cy="${y}" r="${r}" fill="${skin}" ${sw(2.5)}/>`;
+const today = new Date();
+const STAMP_DATE = today.toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}).toUpperCase().replace(',','');
+
+/* ================= scenery ================= */
+function wall(tint){
+  return `<rect width="400" height="460" fill="${tint}"/>
+  <rect width="400" height="330" fill="url(#pt-dots)"/>
+  <rect y="330" width="400" height="130" fill="#000" opacity=".06"/>
+  <rect y="330" width="400" height="116" fill="url(#pt-bead)"/>
+  <path d="M0 330 H400" ${sw(3)}/><path d="M0 337 H400" stroke="${INK}" stroke-width="1" opacity=".45"/>
+  <rect y="446" width="400" height="14" fill="#000" opacity=".12"/><path d="M0 446 H400" ${sw(2.5)}/>`;
+}
+function clock(cx,cy,h,m){
+  const ha=((h%12)+m/60)*30, ma=m*6;
+  return `<g><circle cx="${cx}" cy="${cy}" r="15" fill="#fbf7ec" ${sw(2.5)}/>
+  ${[0,90,180,270].map(a=>`<path d="M${cx} ${cy-12} v3" transform="rotate(${a} ${cx} ${cy})" stroke="${INK}" stroke-width="1.6"/>`).join('')}
+  <path d="M${cx} ${cy} v-7" transform="rotate(${ha} ${cx} ${cy})" ${sw(2.4)}/>
+  <path d="M${cx} ${cy} v-11" transform="rotate(${ma} ${cx} ${cy})" ${sw(1.6)}/>
+  <circle cx="${cx}" cy="${cy}" r="1.8" fill="${INK}"/></g>`;
+}
+function fern(x){
+  return `<g transform="translate(${x} 446)">
+   ${[-58,-30,-8,14,40,62].map((a,i)=>`<path d="M0 -40 q${a*.5} -34 ${a} -${46+(i%2)*14}" fill="none" stroke="#3f6b3a" stroke-width="5" stroke-linecap="round"/>`).join('')}
+   ${[-58,-30,-8,14,40,62].map((a,i)=>`<path d="M0 -40 q${a*.5} -34 ${a} -${46+(i%2)*14}" fill="none" stroke="#6f9a55" stroke-width="2.5" stroke-dasharray="3 3" stroke-linecap="round"/>`).join('')}
+   <path d="M-17 -42 H17 L13 0 H-13 Z" fill="${BRICK}" ${sw(2.5)}/>
+   <rect x="-12" y="-30" width="24" height="10" fill="#f6f0e1" stroke="${INK}" stroke-width="1.2"/>
+   <text x="0" y="-22.5" text-anchor="middle" font-size="5.5" class="tiny" fill="${INK}">SINCE 1994</text></g>`;
+}
+function booth(led){
+  return `<rect x="222" y="164" width="166" height="130" fill="#9fc6cf" opacity=".3"/>
+  <path d="M246 292 L298 168" stroke="#fff" stroke-width="6" opacity=".5"/><path d="M266 292 L310 190" stroke="#fff" stroke-width="2.2" opacity=".5"/>
+  <path d="M214 300 V158 H400" fill="none" ${sw(4)}/>
+  <rect x="219" y="162" width="190" height="136" fill="none" stroke="${INK}" stroke-width="1.4"/>
+  <rect x="206" y="126" width="200" height="34" fill="${SLATE}" ${sw(3)}/>
+  <text x="217" y="149.5" class="sg" font-size="16.5" fill="#f6f0e1" letter-spacing=".6">WINDOW 3</text>
+  <text x="330" y="136.5" class="tiny" font-size="6.2" fill="#f6f0e1" letter-spacing=".8">NOW SERVING</text>
+  <rect x="328" y="139" width="64" height="17" rx="2" fill="#140c0a" stroke="#000"/>
+  <text x="360" y="152.6" text-anchor="middle" class="led" data-led="1">${led}</text>`;
+}
+function counter(){
+  return `<rect x="252" y="291" width="104" height="8" fill="${INK}" opacity=".85"/>
+  <rect x="196" y="298" width="210" height="15" fill="#8a5a3b" ${sw(3)}/>
+  <rect x="204" y="313" width="200" height="147" fill="#a8714b" ${sw(3)}/>
+  <rect x="204" y="313" width="200" height="147" fill="url(#pt-hatch)" opacity=".45"/>
+  <path d="M214 332 C260 327 300 338 404 329 M214 424 C272 417 330 428 404 420" stroke="${INK}" stroke-width="1.2" opacity=".35" fill="none"/>
+  <rect x="248" y="352" width="116" height="36" fill="#f6f0e1" ${sw(2.5)}/>
+  <text x="306" y="367" text-anchor="middle" font-size="10.5" class="sg" fill="${INK}">PLEASE WAIT</text>
+  <text x="306" y="381" text-anchor="middle" font-size="10.5" class="sg" fill="${INK}">TO BE CALLED</text>`;
+}
+function office(o){
+  // o: {tint, led, clock:[h,m]|null, dolores, props, fore, barnaby, fern:bool}
+  const bg = wall(o.tint) + (o.fern!==false ? fern(26) : '');
+  const art = (o.clock ? clock(186,198,o.clock[0],o.clock[1]) : '')
+    + dolores(o.dolores||{}) + booth(o.led) + counter() + (o.props||'') + barnaby(o.barnaby||{}) + (o.fore||'');
+  return {bg, art};
+}
+
+/* ================= characters ================= */
+function dolores({mood='polite', sheet=null, ticket=null}={}){
+  const eyes = {
+    polite:`<path d="M-16 -84 q6 -5 12 0 M4 -84 q6 -5 12 0" fill="none" ${sw(2)}/>`,
+    delighted:`<path d="M-16 -83 q6 -7 12 0 M4 -83 q6 -7 12 0" fill="none" ${sw(2.4)}/>`,
+    stern:`<path d="M-15 -84 h10 M5 -84 h10" ${sw(2.4)}/><circle cx="-10" cy="-82" r="1.8" fill="${INK}"/><circle cx="10" cy="-82" r="1.8" fill="${INK}"/>`,
+    surprised:`<circle cx="-10" cy="-84" r="4.6" fill="#fff" ${sw(1.6)}/><circle cx="10" cy="-84" r="4.6" fill="#fff" ${sw(1.6)}/><circle cx="-10" cy="-84" r="1.9" fill="${INK}"/><circle cx="10" cy="-84" r="1.9" fill="${INK}"/>`,
+    weary:`<path d="M-16 -85 h12 M4 -85 h12" ${sw(2)}/><path d="M-15 -85 a5 4 0 0 0 10 0 M5 -85 a5 4 0 0 0 10 0" fill="${INK}"/>`
+  }[mood];
+  const brows = {
+    polite:`<path d="M-17 -97 q7 -4 13 0 M4 -97 q7 -4 13 0" fill="none" ${sw(2.2)}/>`,
+    delighted:`<path d="M-17 -99 q7 -5 13 0 M4 -99 q7 -5 13 0" fill="none" ${sw(2.2)}/>`,
+    stern:`<path d="M-17 -95 l13 2 M17 -95 l-13 2" ${sw(2.6)}/>`,
+    surprised:`<path d="M-17 -102 q7 -5 13 -1 M4 -103 q7 -4 13 1" fill="none" ${sw(2.2)}/>`,
+    weary:`<path d="M-17 -94 l13 -3 M17 -94 l-13 -3" ${sw(2.2)}/>`
+  }[mood];
+  const mouth = {
+    polite:`<path d="M-8 -66 q8 6 16 0" fill="none" stroke="${BRICK}" stroke-width="3" stroke-linecap="round"/>`,
+    delighted:`<path d="M-10 -68 q10 14 20 0 z" fill="${BRICK}" ${sw(1.6)}/><path d="M-6 -66 h12" stroke="#fff" stroke-width="2"/>`,
+    stern:`<path d="M-7 -65 h14" stroke="${BRICK}" stroke-width="3" stroke-linecap="round"/>`,
+    surprised:`<ellipse cx="0" cy="-64" rx="4.5" ry="5.5" fill="${BRICK}" ${sw(1.6)}/>`,
+    weary:`<path d="M-8 -64 q4 -3 8 0 t8 0" fill="none" stroke="${BRICK}" stroke-width="3" stroke-linecap="round"/>`
+  }[mood];
+  let extra='';
+  if (sheet){
+    // a sheet held up against the glass
+    const {x,y,w,h,label,body=''} = sheet;
+    extra += `<g transform="translate(${-305} ${-300})">
+      <rect x="${x}" y="${y}" width="${w}" height="${h}" fill="#fff" ${sw(2.5)} transform="rotate(-3 ${x+w/2} ${y+h/2})"/>
+      <g transform="rotate(-3 ${x+w/2} ${y+h/2})">
+        ${label?`<text x="${x+w/2}" y="${y+22}" text-anchor="middle" class="sg" font-size="${sheet.fs||18}" fill="${INK}">${label}</text>`:''}
+        ${body}
+      </g>
+      ${hand(x+4, y+h-6, SKIN_D, 7)}${hand(x+w-4, y+h-8, SKIN_D, 7)}
+    </g>`;
+  }
+  if (ticket){
+    extra += `<g transform="rotate(-8 -44 -52)"><rect x="-60" y="-66" width="32" height="26" fill="#f6f0e1" ${sw(2)}/><text x="-44" y="-58" text-anchor="middle" class="tiny" font-size="5.5" fill="${INK}">No.</text><text x="-44" y="-46" text-anchor="middle" class="sg" font-size="11" fill="${BRICK}">${ticket}</text></g>${hand(-58,-44,SKIN_D,7)}`;
+  }
+  return `<g transform="translate(305 300)">
+    <ellipse cx="0" cy="-112" rx="31" ry="31" fill="${LAV}" ${sw(3)}/>
+    <path d="M16 -120 L34 -140" stroke="${INK}" stroke-width="7" stroke-linecap="round"/><path d="M16 -120 L34 -140" stroke="${MUSTARD}" stroke-width="4" stroke-linecap="round"/><circle cx="34" cy="-140" r="3" fill="#e79aa0" ${sw(1.5)}/>
+    <path d="M-60 0 C-58 -26 -46 -40 -16 -44 L16 -44 C46 -40 58 -26 60 0 Z" fill="${TEAL}" ${sw(3)}/>
+    <path d="M-60 0 C-58 -26 -46 -40 -16 -44 L16 -44 C46 -40 58 -26 60 0 Z" fill="url(#pt-hatch)" opacity=".5"/>
+    <path d="M-16 -44 L0 -16 L16 -44 Z" fill="#f6f0e1" ${sw(2)}/>
+    <path d="M0 -16 V0" ${sw(2)}/><circle cx="-5" cy="-9" r="1.8" fill="#f6f0e1" ${sw(1)}/>
+    ${[-12,-6,0,6,12].map(px=>`<circle cx="${px}" cy="${-43+Math.abs(px)*.12}" r="2.3" fill="#fff" ${sw(1)}/>`).join('')}
+    <rect x="22" y="-32" width="26" height="12" fill="#fff" ${sw(1.4)}/><text x="35" y="-23.5" text-anchor="middle" class="tiny" font-size="5.6" fill="${INK}">DOLORES</text>
+    <rect x="-8" y="-58" width="16" height="16" fill="${SKIN_D}" ${sw(2.5)}/>
+    <ellipse cx="0" cy="-80" rx="26" ry="28" fill="${SKIN_D}" ${sw(3)}/>
+    <path d="M-27 -86 C-30 -118 30 -118 27 -86 C16 -101 -12 -104 -27 -86 Z" fill="${LAV}" ${sw(2.5)}/>
+    <path d="M-12 -104 q6 -6 12 -2 M4 -108 q6 -4 12 0" fill="none" stroke="${INK}" stroke-width="1.3" opacity=".6"/>
+    <circle cx="-26" cy="-74" r="3.2" fill="${MUSTARD}" ${sw(1.3)}/><circle cx="26" cy="-74" r="3.2" fill="${MUSTARD}" ${sw(1.3)}/>
+    <circle cx="-15" cy="-70" r="5" fill="#e0707a" opacity=".3"/><circle cx="15" cy="-70" r="5" fill="#e0707a" opacity=".3"/>
+    ${eyes}
+    <path d="M-21 -88 Q-10 -94 -1 -88 Q-2 -77 -10 -77 Q-19 -77 -21 -88 Z M1 -88 Q10 -94 21 -88 Q19 -77 10 -77 Q2 -77 1 -88 Z" fill="#fff" fill-opacity=".25" stroke="${INK}" stroke-width="2.2"/>
+    <path d="M-21 -88 l-6 -5 M21 -88 l6 -5" ${sw(2.2)}/>
+    <path d="M-27 -90 C-34 -60 -24 -46 0 -45 C24 -46 34 -60 27 -90" fill="none" stroke="${MUSTARD}" stroke-width="1.5" stroke-dasharray="1.5 2.5"/>
+    ${brows}
+    <path d="M1 -78 q5 3 0 7" fill="none" ${sw(2)}/>
+    ${mouth}
+    ${extra}
+  </g>`;
+}
+
+function barnaby({x=110, mood='hopeful', arm='ticket', can=true, ticket='0418', stack=null}={}){
+  const eyes = {
+    hopeful:`<circle cx="6" cy="-250" r="3" fill="${INK}"/><circle cx="20" cy="-251" r="3" fill="${INK}"/><circle cx="7" cy="-251" r="1" fill="#fff"/><circle cx="21" cy="-252" r="1" fill="#fff"/>`,
+    confused:`<circle cx="6" cy="-250" r="3" fill="${INK}"/><circle cx="20" cy="-250" r="2.3" fill="${INK}"/>`,
+    despair:`<circle cx="6" cy="-249" r="2.6" fill="${INK}"/><circle cx="20" cy="-249" r="2.6" fill="${INK}"/><path d="M2 -243 q4 2 8 0 M16 -243 q4 2 8 0" fill="none" ${sw(1.4)}/>`,
+    dazed:`<path d="M6 -250 m-4 0 a4 4 0 1 1 4 4 a2.6 2.6 0 1 1 -2.6 -2.6 M20 -251 m-4 0 a4 4 0 1 1 4 4 a2.6 2.6 0 1 1 -2.6 -2.6" fill="none" ${sw(1.6)}/>`,
+    triumphant:`<path d="M1 -250 q5 -6 10 0 M15 -251 q5 -6 10 0" fill="none" ${sw(2.6)}/>`,
+    astonished:`<circle cx="6" cy="-251" r="5" fill="#fff" ${sw(1.8)}/><circle cx="21" cy="-252" r="5" fill="#fff" ${sw(1.8)}/><circle cx="7" cy="-251" r="1.8" fill="${INK}"/><circle cx="22" cy="-252" r="1.8" fill="${INK}"/>`
+  }[mood];
+  const brows = {
+    hopeful:`<path d="M0 -259 q6 -5 12 -1 M15 -260 q5 -4 10 0" fill="none" ${sw(2.4)}/>`,
+    confused:`<path d="M0 -261 q6 -6 12 -2 M15 -256 l10 2" fill="none" ${sw(2.4)}/>`,
+    despair:`<path d="M0 -255 l12 -5 M15 -260 l10 5" fill="none" ${sw(2.4)}/>`,
+    dazed:`<path d="M0 -259 q6 -2 12 0 M15 -260 q5 -2 10 0" fill="none" ${sw(2)}/>`,
+    triumphant:`<path d="M0 -261 q6 -6 12 -2 M15 -262 q5 -5 10 0" fill="none" ${sw(2.4)}/>`,
+    astonished:`<path d="M0 -263 q6 -6 12 -2 M15 -264 q5 -5 10 0" fill="none" ${sw(2.4)}/>`
+  }[mood];
+  const mouth = {
+    hopeful:`<path d="M4 -238 q7 7 14 0" fill="none" ${sw(2.6)}/>`,
+    confused:`<path d="M4 -236 q3 -3 6 0 t6 0" fill="none" ${sw(2.4)}/>`,
+    despair:`<path d="M4 -233 q7 -9 14 0 z" fill="${INK}" ${sw(1.5)}/>`,
+    dazed:`<path d="M4 -236 q3 2 5 0 t5 0 t5 0" fill="none" ${sw(2.2)}/>`,
+    triumphant:`<path d="M3 -240 q8 13 17 0 z" fill="${INK}" ${sw(1.5)}/><path d="M7 -234 q4 3 8 0" fill="#e0707a"/>`,
+    astonished:`<ellipse cx="11" cy="-235" rx="4.5" ry="6" fill="${INK}"/>`
+  }[mood];
+  let near='', far='';
+  // far arm (behind torso) + paint can
+  if (arm==='up'){
+    far = limb('M-24 -204 C-38 -228 -46 -258 -44 -282', BRICK) + hand(-44,-286,SKIN_B);
+  } else if (arm==='hold'){
+    far = '';
+  } else {
+    far = limb('M-26 -204 C-44 -178 -52 -150 -52 -126', BRICK) + hand(-52,-122,SKIN_B)
+      + (can ? `<path d="M-66 -110 q14 -20 28 0" fill="none" ${sw(2.2)}/>
+        <rect x="-70" y="-112" width="36" height="40" rx="3" fill="#cfd4d8" ${sw(2.5)}/>
+        <rect x="-70" y="-100" width="36" height="16" fill="${BONE}" ${sw(1.6)}/>
+        <text x="-52" y="-88.5" text-anchor="middle" class="sg" font-size="9" fill="${INK}">BONE</text>
+        <path d="M-40 -112 q2 6 0 10" stroke="${BONE}" stroke-width="3" stroke-linecap="round"/>` : '');
+  }
+  if (arm==='ticket'){
+    near = limb('M26 -204 C48 -196 62 -186 72 -178', BRICK)
+      + `<g transform="rotate(10 82 -196)"><rect x="70" y="-214" width="26" height="30" fill="#f6f0e1" ${sw(2)}/><text x="83" y="-204" text-anchor="middle" class="tiny" font-size="5.5" fill="${INK}">No.</text><text x="83" y="-191" text-anchor="middle" class="sg" font-size="11" fill="${BRICK}">${ticket}</text></g>`
+      + hand(74,-178,SKIN_B);
+  } else if (arm==='reach'){
+    near = limb('M26 -204 C50 -190 70 -168 82 -152', BRICK) + hand(84,-148,SKIN_B);
+  } else if (arm==='forms'){
+    const labels = stack || ['RF-2','RF-1'];
+    const n = labels.length;
+    let s='';
+    for (let i=0;i<n;i++){
+      const yy=-176 - i*9;
+      s += `<g transform="rotate(${(i%2?4:-5)} 36 ${yy})"><rect x="8" y="${yy-16}" width="56" height="30" fill="#fff" ${sw(2)}/><text x="36" y="${yy+2}" text-anchor="middle" class="sg" font-size="10.5" fill="${INK}">${labels[i]}</text></g>`;
+    }
+    near = limb('M26 -204 C44 -192 56 -178 60 -164', BRICK) + s + hand(62,-166,SKIN_B);
+  } else if (arm==='up'){
+    near = limb('M26 -204 C40 -230 46 -260 44 -284', BRICK) + hand(44,-288,SKIN_B);
+  } else if (arm==='hold'){
+    near = '';
+  }
+  return `<g transform="translate(${x} 446)">
+    ${far}
+    <ellipse cx="-12" cy="-5" rx="17" ry="6.5" fill="${INK}"/><ellipse cx="22" cy="-5" rx="17" ry="6.5" fill="${INK}"/>
+    <path d="M-26 -118 L-24 -9 L-4 -9 L2 -84 L8 -9 L28 -9 L26 -118 Z" fill="#46505e" ${sw(3)}/>
+    <rect x="-7" y="-232" width="20" height="16" fill="${SKIN_B}" ${sw(2.5)}/>
+    <path d="M-30 -214 C-40 -186 -40 -150 -34 -110 L36 -110 C40 -150 40 -186 32 -214 C18 -224 -16 -224 -30 -214 Z" fill="${BRICK}" ${sw(3)}/>
+    <path d="M-30 -214 C-40 -186 -40 -150 -34 -110 L36 -110 C40 -150 40 -186 32 -214 C18 -224 -16 -224 -30 -214 Z" fill="url(#pt-hatch)" opacity=".55"/>
+    <path d="M0 -219 L12 -188 L24 -216 Z" fill="#f6f0e1" ${sw(2)}/>
+    <path d="M12 -208 L8 -174 L12 -166 L16 -174 Z" fill="${MUSTARD}" ${sw(1.8)}/>
+    <circle cx="20" cy="-150" r="2.4" fill="${INK}"/><circle cx="21" cy="-132" r="2.4" fill="${INK}"/>
+    <ellipse cx="4" cy="-256" rx="27" ry="32" fill="${SKIN_B}" ${sw(3)}/>
+    <path d="M-23 -262 C-29 -250 -25 -238 -18 -232 L-15 -262 Z" fill="${INK}"/>
+    <ellipse cx="-14" cy="-252" rx="5.5" ry="8.5" fill="${SKIN_B}" ${sw(2.3)}/><path d="M-15 -256 q3 4 0 8" fill="none" ${sw(1.4)}/>
+    ${eyes}${brows}
+    <path d="M22 -256 C42 -262 46 -232 24 -234" fill="${SKIN_B}" ${sw(3)}/><path d="M28 -238 q3 -3 6 0" fill="none" ${sw(1.4)}/>
+    ${mouth}
+    <path d="M-27 -272 C-27 -302 33 -304 34 -276 C42 -276 54 -272 57 -266 C42 -262 14 -266 -29 -266 Z" fill="${TEAL}" ${sw(3)}/>
+    <path d="M-10 -296 q14 -6 30 2" fill="none" stroke="#fff" stroke-width="2" opacity=".35"/>
+    <circle cx="4" cy="-298" r="2.6" fill="${TEAL}" ${sw(1.6)}/>
+    ${near}
+  </g>`;
+}
+
+function gerald(x,y,{mood='asleep', s=1}={}){
+  const face = mood==='asleep'
+    ? `<path d="M-7 -15 q2.5 2 5 0 M2 -15 q2.5 2 5 0" fill="none" ${sw(1.2)}/><text x="12" y="-30" class="sg" font-size="8" fill="${INK}">z</text><text x="17" y="-37" class="sg" font-size="6" fill="${INK}">z</text>`
+    : mood==='shout'
+    ? `<circle cx="-5" cy="-16" r="2.4" fill="#fff" ${sw(.9)}/><circle cx="5" cy="-16" r="2.4" fill="#fff" ${sw(.9)}/><circle cx="-5" cy="-16" r="1" fill="${INK}"/><circle cx="5" cy="-16" r="1" fill="${INK}"/><ellipse cx="0" cy="-12" rx="3" ry="1.8" fill="${INK}"/>`
+    : `<circle cx="-5" cy="-15" r="1.3" fill="${INK}"/><circle cx="5" cy="-15" r="1.3" fill="${INK}"/><path d="M-3 -12 q3 2 6 0" fill="none" ${sw(1)}/>`;
+  return `<g transform="translate(${x} ${y}) scale(${s})">
+    <rect x="-15" y="-9" width="30" height="9" rx="1.5" fill="${BRICK}" ${sw(2)}/>
+    <rect x="-12" y="-21" width="24" height="12" rx="2" fill="#7a5433" ${sw(2)}/>
+    <rect x="-3.5" y="-32" width="7" height="11" fill="#7a5433" ${sw(1.8)}/>
+    <circle cx="0" cy="-37" r="7" fill="#7a5433" ${sw(2)}/><path d="M-3 -40 q3 -2 5 0" fill="none" stroke="#fff" stroke-width="1.2" opacity=".5"/>
+    ${face}
+  </g>`;
+}
+
+function pigeon(x,y,s=1){
+  return `<g transform="translate(${x} ${y}) scale(${s})">
+    <path d="M-4 0 v-8 M5 0 v-8 M-8 0 h8 M1 0 h8" stroke="#d9635a" stroke-width="2" stroke-linecap="round"/>
+    <path d="M14 -24 L34 -14 L18 -11 Z" fill="#747d88" ${sw(2)}/>
+    <ellipse cx="2" cy="-20" rx="19" ry="13" fill="#9aa3ae" ${sw(2.4)}/>
+    <path d="M-2 -26 q12 -6 22 4 q-8 6 -22 2 Z" fill="#848d98" ${sw(1.6)}/><path d="M6 -22 l3 5 M12 -21 l3 5" ${sw(1.6)}/>
+    <path d="M-12 -28 C-16 -38 -10 -46 -3 -42 L-2 -28 Z" fill="#3f8c84" ${sw(1.8)}/><path d="M-11 -30 q4 -3 8 1" stroke="#8a5fa8" stroke-width="3" fill="none"/>
+    <circle cx="-10" cy="-45" r="8.5" fill="#9aa3ae" ${sw(2.2)}/>
+    <circle cx="-13" cy="-46.5" r="2.6" fill="#f39c32" ${sw(1)}/><circle cx="-13.4" cy="-46.5" r="1.1" fill="${INK}"/>
+    <path d="M-18 -45 L-27 -42 L-18 -40 Z" fill="#3a3531" ${sw(1.2)}/><circle cx="-18" cy="-45" r="1.6" fill="#f2efe8"/>
+    <path d="M-12 -35 L-15 -23 L-12 -19 L-9 -23 Z" fill="${BRICK}" ${sw(1.4)}/>
+    <path d="M-6 -36 C0 -30 4 -26 6 -18" fill="none" stroke="${TEAL}" stroke-width="1.6"/>
+    <rect x="1" y="-19" width="16" height="10" fill="#fff" ${sw(1.3)}/><text x="9" y="-12" text-anchor="middle" class="tiny" font-size="5" fill="${INK}">DIR.</text>
+  </g>`;
+}
+
+/* ================= speech bubbles ================= */
+// spec: {who, lines:[...], x (center), y (top), to:[tx,ty], kind:'speech'|'whisper'|'shout', fs}
+function bubble(b){
+  const fs = b.fs || 15, lh = fs*1.17;
+  const tsp = b.lines.map((ln,i)=>{
+    const inner = esc(ln).replace(/\*(.+?)\*/g,'<tspan class="em">$1</tspan>');
+    return `<tspan x="${b.x}" dy="${i?lh:0}">${inner}</tspan>`;
+  }).join('');
+  return `<g class="bubble" data-kind="${b.kind||'speech'}" data-to="${b.to[0]},${b.to[1]}">
+    <path class="bshape" d=""/>
+    <text class="lt" x="${b.x}" y="${b.y+fs}" font-size="${fs}" text-anchor="middle" fill="${INK}">${tsp}</text></g>`;
+}
+function layoutBubbles(root){
+  root.querySelectorAll('.bubble').forEach(g=>{
+    if (g.dataset.laid) return;
+    const t = g.querySelector('text');
+    let bb = t.getBBox();
+    const px=11, py=8;
+    // keep inside the panel
+    let shift=0;
+    if (bb.x-px < 5) shift = 5-(bb.x-px);
+    if (bb.x+bb.width+px > 395) shift = 395-(bb.x+bb.width+px);
+    if (shift){ t.querySelectorAll('tspan').forEach(ts=>ts.setAttribute('x', +ts.getAttribute('x')+shift)); bb = t.getBBox(); }
+    const L=bb.x-px, T=bb.y-py, R=bb.x+bb.width+px, B=bb.y+bb.height+py;
+    const [tx,ty] = g.dataset.to.split(',').map(Number);
+    const kind = g.dataset.kind;
+    const shape = g.querySelector('.bshape');
+    shape.setAttribute('fill', kind==='shout' ? '#fff6c9' : '#fff');
+    shape.setAttribute('stroke', INK); shape.setAttribute('stroke-width','2.4'); shape.setAttribute('stroke-linejoin','round');
+    if (kind==='whisper') shape.setAttribute('stroke-dasharray','5 4');
+    if (kind==='shout'){
+      const cx=(L+R)/2, cy=(T+B)/2, rx=(R-L)/2+14, ry=(B-T)/2+14, n=18, pts=[];
+      for (let i=0;i<n*2;i++){ const a=i/(n*2)*Math.PI*2, k=i%2?1:1.22; pts.push([cx+Math.cos(a)*rx*k, cy+Math.sin(a)*ry*k, a]); }
+      // stretch the outer spike that points at the speaker into the tail
+      const at=Math.atan2((ty-cy)/ry,(tx-cx)/rx); let best=0, bd=9;
+      pts.forEach((p,i)=>{ if(i%2) return; let d=Math.abs(((p[2]-at)+Math.PI*3)%(Math.PI*2)-Math.PI); if(d<bd){bd=d;best=i;} });
+      pts[best]=[tx,ty];
+      shape.setAttribute('d', pts.map((p,i)=>(i?'L':'M')+p[0].toFixed(1)+' '+p[1].toFixed(1)).join('')+'Z');
+    } else {
+      const r = Math.min(16,(B-T)/2), hw=7.5;
+      const side = ty>B ? 'b' : ty<T ? 't' : tx<L ? 'l' : 'r';
+      const cl = (v,a,b)=>Math.max(a,Math.min(b,v));
+      const bx = cl(tx + (tx>(L+R)/2 ? -12 : 12), L+r+hw, R-r-hw), by = cl(ty, T+r+hw, B-r-hw);
+      const tip = (x0,y0,x1,y1) => `L${x0} ${y0} Q${((x0+tx)/2).toFixed(1)} ${((y0+ty)/2).toFixed(1)} ${tx} ${ty} Q${((x1+tx)/2).toFixed(1)} ${((y1+ty)/2).toFixed(1)} ${x1} ${y1} `;
+      let d = `M${L+r} ${T} `;
+      if (side==='t') d += tip(bx-hw,T,bx+hw,T);
+      d += `H${R-r} Q${R} ${T} ${R} ${T+r} `;
+      if (side==='r') d += tip(R,by-hw,R,by+hw);
+      d += `V${B-r} Q${R} ${B} ${R-r} ${B} `;
+      if (side==='b') d += tip(bx+hw,B,bx-hw,B);
+      d += `H${L+r} Q${L} ${B} ${L} ${B-r} `;
+      if (side==='l') d += tip(L,by+hw,L,by-hw);
+      d += `V${T+r} Q${L} ${T} ${L+r} ${T} Z`;
+      shape.setAttribute('d', d);
+    }
+    g.dataset.laid='1';
+  });
+}
+
+/* ================= the saga ================= */
+const B_MOUTH=[140,150], D_MOUTH=[276,172];
+const P1 = {
+  when:'9:02 A.M.', stamp:'RECEIVED', stampColor:'#4D77B6',
+  caption:'Barnaby Oyelaran-Fitch arrives at Window 3 with a paint can, a dream, and ticket No. 0418.',
+  scene:'The Office of Form Provision. Barnaby, in a teal flat cap and tweed jacket, holds up ticket 0418 and a can of Bone paint. Behind the glass of Window 3 sits Dolores, lavender beehive, cat-eye glasses. The Now Serving sign reads 0417. The clock says 9:02.',
+  draw: (tint, led) => office({tint, led:'0417', clock:[9,2], dolores:{mood:'polite'},
+      props: gerald(382,298),
+      barnaby:{mood:'hopeful', arm:'ticket'}}),
+  bubbles: [
+    {who:'Barnaby', lines:['ONE *FORM MB-7*,','PLEASE! I’M','REPAINTING MY','MAILBOX FROM','EGGSHELL TO BONE.'], x:96, y:8, to:B_MOUTH},
+    {who:'Dolores', lines:['WONDERFUL!','MAY I SEE YOUR','*FORM RF-2*?'], x:302, y:36, to:D_MOUTH}
+  ]
+};
+const P2 = {
+  when:'10:47 A.M.', stamp:'IN REVIEW', stampColor:'#4D77B6',
+  caption:'The paperwork begins to develop paperwork of its own.',
+  scene:'Same window, an hour and forty-five minutes later. Barnaby, puzzled, clutches forms marked RF-2 with a question mark. Dolores holds Form RF-1 up against the glass. A tangled ball labelled RED TAPE sits on the counter. The sign still reads 0417.',
+  draw: (tint) => office({tint, led:'0417', clock:[10,47],
+      dolores:{mood:'polite', sheet:{x:228,y:246,w:58,h:46,label:'RF-1',fs:17, body:`<path d="M${236} 276 h42 M236 283 h30" stroke="${INK}" stroke-width="1.4" opacity=".5"/>`}},
+      props: redTape(348,286) + gerald(384,298),
+      barnaby:{mood:'confused', arm:'forms', stack:['RF-2?']}}),
+  bubbles: [
+    {who:'Barnaby', lines:['WHERE DO I GET','*FORM RF-2*?'], x:88, y:8, to:B_MOUTH},
+    {who:'Dolores', lines:['SIMPLY FILE *RF-1*:','“REQUEST TO','REQUEST A FORM.”'], x:300, y:16, to:D_MOUTH},
+    {who:'Barnaby', lines:['AND WHERE’S','*RF-1*?'], x:70, y:84, to:B_MOUTH},
+    {who:'Dolores', lines:['AVAILABLE','UPON REQUEST.'], x:212, y:100, to:[266,172]}
+  ]
+};
+function redTape(x,y){
+  let s=''; for(let i=0;i<7;i++){ s+=`<ellipse cx="${x}" cy="${y}" rx="${14-i%3}" ry="${9+i%4}" transform="rotate(${i*26} ${x} ${y})" fill="none" stroke="#c0392b" stroke-width="2.4"/>`; }
+  return `<circle cx="${x}" cy="${y}" r="13" fill="#e25b4a" ${sw(2.2)}/>${s}
+    <path d="M${x+10} ${y+8} C${x+18} ${y+20} ${x+10} ${y+30} ${x+18} ${y+40}" fill="none" stroke="#c0392b" stroke-width="2.4"/>
+    <g transform="rotate(-6 ${x+10} ${y+48})"><rect x="${x-18}" y="${y+40}" width="54" height="16" fill="#f6f0e1" ${sw(1.8)}/><text x="${x+9}" y="${y+52}" text-anchor="middle" class="sg" font-size="10" fill="${INK}">RED TAPE</text></g>`;
+}
+
+/* ---- the eight resolutions of panel 3 (the plot twist reshuffles these) ---- */
+const ENDINGS = [
+ { id:'take-a-number', code:'PT-00', title:'Take a Number', stamp:'PENDING', stampColor:'#b8432f', when:'4:59 P.M.',
+   caption:'Progress, of a kind. The sign advances past 0418 while he reads.',
+   scene:'Late afternoon. Dolores, delighted, presses Form RF-0 against the glass. Barnaby, dazed, holds a stack of forms. The Now Serving sign has moved past his number.',
+   draw:(tint,led)=>office({tint, led, clock:[4,59],
+     dolores:{mood:'delighted', sheet:{x:230,y:238,w:66,h:54,label:'RF-0',fs:21, body:`<path d="M238 272 h50 M238 280 h50 M238 287 h32" stroke="${INK}" stroke-width="1.3" opacity=".5"/>`}},
+     props: gerald(382,298) + `<text x="394" y="121" text-anchor="end" class="sg" font-size="12" fill="${BRICK}" transform="rotate(-8 394 121)">DING!</text>`,
+     barnaby:{mood:'dazed', arm:'forms', stack:['RF-2','RF-1']}}),
+   bubbles:[
+     {who:'Dolores', lines:['GOOD NEWS! HERE’S','*FORM RF-0*: REQUEST','TO REQUEST TO','REQUEST A FORM.'], x:292, y:6, to:D_MOUTH},
+     {who:'Barnaby', lines:['AND HOW DO I','REQUEST *RF-0*?'], x:88, y:30, to:B_MOUTH},
+     {who:'Dolores', lines:['WINDOW 3.','TAKE A NUMBER.'], x:206, y:104, to:[268,176]}
+   ]},
+ { id:'full-circle', code:'PT-01', title:'The Full Circle', stamp:'CIRCULAR', stampColor:'#b8432f', when:'4:59 P.M.',
+   caption:'Mr. Oyelaran-Fitch has now requested the form he came to request. Physicists have been notified.',
+   scene:'Dolores holds up a hand-drawn loop diagram: MB-7 leads to RF-2, to RF-1, to RF-0, and back to MB-7. Barnaby leans on the counter in despair.',
+   draw:(tint,led)=>office({tint, led, clock:[4,59],
+     dolores:{mood:'polite', sheet:{x:232,y:238,w:146,h:58, body: loopDiagram(305,268)}},
+     props: gerald(388,298),
+     barnaby:{mood:'despair', arm:'reach'}}),
+   bubbles:[
+     {who:'Dolores', lines:['TO REQUEST *RF-0*,','YOU’LL JUST NEED','ONE LITTLE FORM…'], x:298, y:6, to:D_MOUTH},
+     {who:'Barnaby', lines:['DON’T.','DON’T SAY IT.'], x:84, y:40, to:B_MOUTH},
+     {who:'Dolores', lines:['…*FORM MB-7*!'], x:214, y:112, to:[266,174]}
+   ]},
+ { id:'director-coo', code:'PT-02', title:'Director Coo', stamp:'DEFERRED', stampColor:'#b8432f', when:'4:59 P.M.',
+   caption:'Director Coo has held the post since 2019 and has never missed a meeting, or attended one.',
+   scene:'A pigeon in a tiny red necktie and a DIRECTOR lanyard stands on the counter. Dolores beams proudly. Barnaby stares, astonished.',
+   draw:(tint,led)=>office({tint, led, clock:null,
+     dolores:{mood:'delighted'},
+     props: pigeon(236,298,1.25) + gerald(384,298),
+     barnaby:{mood:'astonished', arm:'forms', stack:['RF-1','RF-0']}}),
+   bubbles:[
+     {who:'Dolores', lines:['*RF-0* NEEDS THE','DIRECTOR’S','SIGNATURE.'], x:300, y:6, to:D_MOUTH},
+     {who:'Barnaby', lines:['THAT’S A','PIGEON.'], x:80, y:26, to:B_MOUTH},
+     {who:'Dolores', lines:['HE INTERVIEWED','VERY WELL.'], x:306, y:80, to:[288,172]},
+     {who:'Director Coo', lines:['COO.'], x:198, y:192, to:[218,236], fs:14}
+   ]},
+ { id:'other-side', code:'PT-03', title:'The Other Side of the Glass', stamp:'ON HOLD', stampColor:'#b8432f', when:'4:59 P.M.',
+   caption:'Solidarity at Window 3. Both parties agree the sandwich is now a matter for the archive.',
+   scene:'Dolores, weary, holds up her own ticket, number 0002. A sandwich in cling film labelled MARCH sits on the counter. Barnaby, sympathetic, holds his forms.',
+   draw:(tint,led)=>office({tint, led, clock:[4,59],
+     dolores:{mood:'weary', ticket:'0002'},
+     props: sandwich(236,290) + gerald(384,298),
+     barnaby:{mood:'confused', arm:'forms', stack:['RF-1','RF-0']}}),
+   bubbles:[
+     {who:'Barnaby', lines:['SO… CAN YOU','PROCESS *RF-0*?'], x:86, y:8, to:B_MOUTH},
+     {who:'Dolores', lines:['I’D LOVE TO, HON.','BUT I’M STILL','WAITING ON *LB-1*:','PERMISSION TO','TAKE LUNCH.'], x:298, y:4, to:D_MOUTH},
+     {who:'Barnaby', lines:['SINCE WHEN?'], x:90, y:74, to:B_MOUTH},
+     {who:'Dolores', lines:['MARCH.'], x:200, y:96, to:[268,176]}
+   ]},
+ { id:'gerald-speaks', code:'PT-04', title:'Gerald Speaks', stamp:'APPROVED*', stampColor:'#2f6f6a', when:'4:59 P.M.',
+   caption:'Gerald, a self-inking stamp (est. 1974), had not spoken since the Great Staple Shortage of ’98. Worth the wait, pending review.',
+   scene:'Gerald the rubber stamp leaps up on the counter, shouting. Dolores gasps. Barnaby throws both arms in the air; his paint can has tipped over on the floor.',
+   draw:(tint,led)=>{ const o=office({tint, led, clock:[4,59],
+     dolores:{mood:'surprised'},
+     props: gerald(246,298,{mood:'shout', s:2.3}) + `<path d="M206 250 l-10 -6 M204 232 l-12 -2 M288 274 l10 -4 M290 290 l12 0" ${sw(2.2)}/>`,
+     barnaby:{mood:'triumphant', arm:'up', can:false},
+     fore: spilledCan(52)}); return o; },
+   bubbles:[
+     {who:'Barnaby', lines:['FINALLY!'], x:80, y:14, to:[112,142]},
+     {who:'Dolores', lines:['GERALD?!'], x:322, y:24, to:D_MOUTH},
+     {who:'Gerald', lines:['APPROVED!!'], x:196, y:100, to:[238,194], kind:'shout', fs:19},
+     {who:'Gerald (quietly)', lines:['…FOR FURTHER','REVIEW.'], x:346, y:252, to:[270,262], kind:'whisper', fs:12}
+   ]},
+ { id:'courtesy-repaint', code:'PT-05', title:'Courtesy Repaint', stamp:'COMPLETED', stampColor:'#2f6f6a', when:'5:36 P.M.',
+   caption:'The Department repainted it as a courtesy. It is now Bone. It was also Bone before. Form MB-7R (Feedback on Unrequested Repainting) is due Friday.',
+   scene:'Outside Barnaby’s picket fence. His mailbox has already been repainted by the Department, with a notice taped to it and two identical paint swatches labelled Eggshell and Bone. Barnaby, astonished, still holds his own can of Bone.',
+   draw:(tint)=>yard(tint),
+   bubbles:[
+     {who:'Barnaby', lines:['THEY ALREADY','REPAINTED IT?!'], x:88, y:10, to:B_MOUTH},
+     {who:'Barnaby', lines:['IT LOOKS EXACTLY','THE SAME.'], x:258, y:112, to:[162,146]}
+   ]},
+ { id:'droste', code:'PT-06', title:'Self-Filing', stamp:'RECURSIVE', stampColor:'#b8432f', when:'4:59 P.M.',
+   caption:'Page 1 of Form RF-0 contains page 1 of Form RF-0, which contains page 1 of Form RF-0. Please initial every level.',
+   scene:'Barnaby holds up Form RF-0 to the reader. Printed on it is this entire comic strip in miniature — whose third panel contains the form again, and again.',
+   draw:(tint,led)=>{ const o=office({tint, led, clock:null, fern:false,
+       dolores:{mood:'delighted'}, props: gerald(384,298),
+       barnaby:{x:150, mood:'astonished', arm:'hold'}});
+     o.art += drosteForm(); return o; },
+   after: fillDroste,
+   bubbles:[
+     {who:'Dolores', lines:['IT’S SELF-FILING.','EVERYTHING’S','IN THERE.'], x:302, y:6, to:D_MOUTH},
+     {who:'Barnaby', lines:['IS THAT…','US?'], x:84, y:44, to:[150,144]}
+   ]},
+ { id:'paperless', code:'PT-07', title:'Paperless', stamp:'MODERNIZED', stampColor:'#b8432f', when:'4:59 P.M.',
+   caption:'The Office completed its Digital Transformation on schedule. The transformation was filed in triplicate.',
+   scene:'A shiny new e-forms kiosk stands before the counter. Its screen reads: Please print this screen and bring it to Window 3. A long receipt curls to the floor. Dolores is delighted; Barnaby reads, confused.',
+   draw:(tint,led)=>office({tint, led, clock:[4,59], fern:false,
+     dolores:{mood:'delighted'}, props: gerald(384,298),
+     barnaby:{x:96, mood:'confused', arm:'forms', stack:['RF-0']},
+     fore: kiosk()}),
+   bubbles:[
+     {who:'Barnaby', lines:['IT SAYS “PRINT THIS','SCREEN AND BRING IT','TO WINDOW 3.”'], x:104, y:6, to:[122,150]},
+     {who:'Dolores', lines:['WE’VE GONE','PAPERLESS!'], x:304, y:24, to:D_MOUTH}
+   ]}
+];
+
+function loopDiagram(cx,cy){
+  const pts=[['MB-7',cx-50,cy+3],['RF-2',cx,cy-15],['RF-1',cx+50,cy+3],['RF-0',cx,cy+22]];
+  return `<ellipse cx="${cx}" cy="${cy+3}" rx="50" ry="18" fill="none" stroke="${BRICK}" stroke-width="2" stroke-dasharray="4 3"/>
+   <path d="M${cx-24} ${cy-12} l7 -4 l-2 7 Z M${cx+30} ${cy-9} l7 3 l-7 4 Z M${cx+26} ${cy+19} l-7 3 l2 -7 Z M${cx-30} ${cy+16} l-7 -3 l7 -4 Z" fill="${BRICK}"/>
+   ${pts.map(([t,x,y])=>`<rect x="${x-14}" y="${y-8}" width="28" height="12" fill="#fff" stroke="${INK}" stroke-width="1.2"/><text x="${x}" y="${y+1.5}" text-anchor="middle" class="sg" font-size="9" fill="${INK}">${t}</text>`).join('')}`;
+}
+function sandwich(x,y){
+  return `<path d="M${x-22} ${y+8} L${x} ${y-10} L${x+22} ${y+8} Z" fill="#e8c27b" ${sw(2)}/>
+   <path d="M${x-18} ${y+6} L${x} ${y-6} L${x+18} ${y+6}" fill="none" stroke="#6f9a55" stroke-width="3"/>
+   <path d="M${x-24} ${y+9} L${x} ${y-13} L${x+24} ${y+9}" fill="none" stroke="#9fc6cf" stroke-width="1.5" opacity=".8"/>
+   <g transform="rotate(8 ${x+18} ${y-18})"><rect x="${x+4}" y="${y-28}" width="32" height="13" fill="#f6f0e1" ${sw(1.4)}/><text x="${x+20}" y="${y-18.5}" text-anchor="middle" class="sg" font-size="8.5" fill="${BRICK}">MARCH</text></g>`;
+}
+function spilledCan(x){
+  return `<ellipse cx="${x+34}" cy="442" rx="34" ry="6" fill="${BONE}" ${sw(2)}/>
+   <g transform="rotate(-78 ${x} 430)"><rect x="${x-18}" y="${410}" width="36" height="40" rx="3" fill="#cfd4d8" ${sw(2.5)}/><rect x="${x-18}" y="${422}" width="36" height="16" fill="${BONE}" ${sw(1.6)}/></g>`;
+}
+function kiosk(){
+  return `<ellipse cx="204" cy="443" rx="28" ry="6" fill="${INK}"/>
+   <rect x="196" y="262" width="16" height="180" fill="#6c7a86" ${sw(2.5)}/>
+   <path d="M200 262 C188 300 224 330 206 372 C192 404 228 420 214 446" fill="none" stroke="${INK}" stroke-width="13" stroke-linecap="round"/>
+   <path d="M200 262 C188 300 224 330 206 372 C192 404 228 420 214 446" fill="none" stroke="#fff" stroke-width="9" stroke-linecap="round"/>
+   <rect x="162" y="182" width="84" height="84" rx="8" fill="${SLATE}" ${sw(3)}/>
+   <rect x="170" y="190" width="68" height="58" rx="3" fill="#0f2b2f" ${sw(1.5)}/>
+   ${['E-FORMS 2.0','PLEASE PRINT','THIS SCREEN','& BRING IT TO','WINDOW 3.'].map((t,i)=>`<text x="204" y="${201+i*10}" text-anchor="middle" class="tiny" font-size="${i?7:7.4}" fill="${i?'#7ff3d6':'#ffd166'}">${t}</text>`).join('')}
+   <rect x="186" y="256" width="36" height="5" rx="2" fill="${INK}"/>
+   <path d="M240 176 l6 10 l11 -3 l-6 10 l8 8 l-11 1 l-2 11 l-7 -9 l-10 5 l3 -11 l-9 -7 l11 -2 z" fill="${MUSTARD}" ${sw(1.6)}/><text x="245" y="201" text-anchor="middle" class="sg" font-size="7" fill="${INK}">NEW!</text>`;
+}
+function yard(tint){
+  const bg = `<rect width="400" height="460" fill="${tint}"/>
+   <rect width="400" height="300" fill="#9fc6d6" opacity=".45"/><rect width="400" height="300" fill="url(#pt-dots)"/>
+   <rect y="372" width="400" height="88" fill="#8fae6a"/><rect y="372" width="400" height="88" fill="url(#pt-grass)"/>
+   <path d="M0 372 H400" ${sw(2.5)}/>`;
+  let pickets='';
+  for (let px=-4; px<400; px+=24){ pickets += `<path d="M${px} 376 V312 L${px+8} 300 L${px+16} 312 V376 Z" fill="#f4efe2" ${sw(2)}/>`; }
+  const art = `<circle cx="354" cy="48" r="17" fill="${MUSTARD}" ${sw(2.5)}/>
+   ${[0,45,90,135,180,225,270,315].map(a=>`<path d="M354 22 v-7" transform="rotate(${a} 354 48)" ${sw(2.2)}/>`).join('')}
+   <path d="M150 64 q8 -14 22 -8 q10 -12 24 -2 q14 -2 12 12 h-58 Z" fill="#fff" ${sw(2)}/>
+   <rect x="0" y="322" width="400" height="8" fill="#e8e1cf" ${sw(1.8)}/><rect x="0" y="352" width="400" height="8" fill="#e8e1cf" ${sw(1.8)}/>
+   ${pickets}
+   <rect x="283" y="292" width="15" height="154" fill="#7a5436" ${sw(2.5)}/>
+   <path d="M232 300 V258 Q232 232 258 232 H322 Q348 232 348 258 V300 Z" fill="${BONE}" ${sw(3)}/>
+   <path d="M232 300 V258 Q232 232 258 232" fill="none" stroke="#fff" stroke-width="3" opacity=".6"/>
+   <rect x="348" y="238" width="4" height="36" fill="${INK}"/><rect x="352" y="238" width="20" height="13" fill="${BRICK}" ${sw(2)}/>
+   <g transform="rotate(-4 272 266)"><rect x="244" y="244" width="56" height="44" fill="#fffdf6" ${sw(1.6)}/>
+     <rect x="262" y="239" width="20" height="8" fill="${MUSTARD}" opacity=".55"/>
+     ${['COURTESY','REPAINT!','file MB-7R','by Friday'].map((t,i)=>`<text x="272" y="${257+i*9}" text-anchor="middle" class="tiny" font-size="${i<2?8:6.5}" fill="${i<2?BRICK:INK}">${t}</text>`).join('')}</g>
+   <rect x="306" y="246" width="30" height="20" fill="${BONE}" ${sw(1.4)}/><text x="321" y="262" text-anchor="middle" class="tiny" font-size="5.2" fill="${INK}">EGGSHELL</text>
+   <rect x="306" y="270" width="30" height="20" fill="${BONE}" ${sw(1.4)}/><text x="321" y="286" text-anchor="middle" class="tiny" font-size="5.2" fill="${INK}">BONE</text>
+   <path d="M300 446 q30 -10 60 -2 v2 z" fill="#d6d9dc" ${sw(1.5)}/>
+   <rect x="318" y="416" width="28" height="30" rx="2" fill="#cfd4d8" ${sw(2.2)}/><text x="332" y="435" text-anchor="middle" class="sg" font-size="8" fill="${INK}">OFP</text>
+   ${barnaby({x:110, mood:'astonished', arm:'forms', stack:['RF-0']})}`;
+  return {bg, art};
+}
+function drosteForm(){
+  return `<g>
+   <rect x="58" y="246" width="284" height="164" fill="#fffdf4" ${sw(3)}/>
+   <rect x="58" y="246" width="284" height="18" fill="${SLATE}" ${sw(3)}/>
+   <text x="200" y="259.5" text-anchor="middle" class="sg" font-size="10.5" fill="#f6f0e1" letter-spacing=".8">FORM RF-0 · PAGE 1 OF ∞</text>
+   <g class="droste-slot" data-x="66" data-y="270" data-w="268"></g>
+   <text x="66" y="402" class="tiny" font-size="6.5" fill="${INK}">INITIAL HERE ______</text>
+   <text x="334" y="402" text-anchor="end" class="tiny" font-size="6.5" fill="${INK}">AND HERE ______</text>
+   ${hand(96,250,SKIN_B,8)}${hand(304,250,SKIN_B,8)}
+  </g>`;
+}
+
+/* ================= DOM & state ================= */
+const $ = s => document.querySelector(s);
+const panels = [...document.querySelectorAll('.panel')];
+const nextBtn=$('#nextBtn'), nextLbl=$('#nextLbl'), twistBtn=$('#twistBtn'), refileBtn=$('#refileBtn'), refileLbl=$('#refileLbl');
+const soundBtn=$('#soundBtn'), soundLbl=$('#soundLbl'), fb=$('#feedback');
+const strip=$('#strip');
+const state = { active:null, revealed:0, ending:0, seen:new Set(), docket:[], serving:417, sheet:0, busy:false, sound:false, certShown:false, confirm:false, confirmTimer:null };
+const PSTATE = ['Submission','Review','Resolution'];
+
+function panelSpec(i){ return i===0?P1 : i===1?P2 : ENDINGS[state.ending]; }
+function altText(i, spec){
+  const talk = spec.bubbles.map(b=>`${b.who}: “${b.lines.join(' ').replace(/\*/g,'')}”`).join(' ');
+  return `Panel ${i+1}, ${spec.when}. ${spec.scene} ${talk}`;
+}
+let boilFrame=0;
+function renderPanel(i){
+  const fig = panels[i], svg = fig.querySelector('svg');
+  const spec = panelSpec(i);
+  const tint = i===2 ? SHEETS[state.sheet] : SHEETS[0];
+  const {bg, art} = spec.draw(tint, pad4(state.serving));
+  svg.innerHTML = `<g class="bg">${bg}</g><g class="art" filter="url(#boil${boilFrame})">${art}</g><g class="bubbles">${spec.bubbles.map(bubble).join('')}</g>`;
+  layoutBubbles(svg);
+  if (spec.after) spec.after(svg);
+  svg.setAttribute('aria-label', altText(i, spec));
+  fig.querySelector('.panel-art').style.background = tint;
+  const cap = fig.querySelector('figcaption');
+  cap.classList.remove('pending');
+  cap.innerHTML = `<span class="when">${esc(spec.when)}</span> ${esc(spec.caption)}`;
+  const st = fig.querySelector('.stamp');
+  st.style.setProperty('--st', spec.stampColor);
+  st.innerHTML = `<b>${esc(spec.stamp)}</b><small>WINDOW 3 · ${STAMP_DATE}</small>`;
+  const ps = fig.querySelector('.pstate');
+  ps.textContent = i===2 ? `${spec.code} · Stamped ${spec.stamp}` : `Stamped ${spec.stamp}`;
+  ps.classList.add('done');
+  if (i===2){
+    fig.style.setProperty('--pad1', SHEETS[(state.sheet+1)%3]);
+    fig.style.setProperty('--pad2', SHEETS[(state.sheet+2)%3]);
+  }
+}
+function slamStamp(i){
+  const fig=panels[i], st=fig.querySelector('.stamp'), art=fig.querySelector('.panel-art');
+  st.hidden=false;
+  if (!reduced()){
+    st.classList.remove('slam'); void st.offsetWidth; st.classList.add('slam');
+    setTimeout(()=>{ art.classList.remove('thud'); void art.offsetWidth; art.classList.add('thud'); }, 150);
+  }
+  setTimeout(thunk, reduced()?0:150);
+}
+
+/* covers = the empty/pending state for each panel */
+function paintCovers(){
+  panels.forEach((fig,i)=>{
+    const c = fig.querySelector('.cover');
+    if (i < state.revealed){ c.hidden=true; return; }
+    c.hidden=false;
+    const isNext = i===state.revealed;
+    c.classList.toggle('queued', !isNext);
+    c.tabIndex = isNext ? 0 : -1;
+    const icon = `<svg viewBox="0 0 80 56" aria-hidden="true"><path d="M6 30 L16 10 H64 L74 30 V50 H6 Z" fill="#f6f0e1" stroke="#1d1a16" stroke-width="3" stroke-linejoin="round"/><path d="M6 30 H26 L30 38 H50 L54 30 H74" fill="none" stroke="#1d1a16" stroke-width="3" stroke-linejoin="round"/><path d="M24 22 H56 M28 16 H52" stroke="#1d1a16" stroke-width="2.4" opacity=".55"/></svg>`;
+    if (isNext){
+      const big = i===0 ? 'Request a Form?' : `Process Panel ${i+1}`;
+      c.innerHTML = `<span class="tab">IN-TRAY · PANEL ${i+1}</span>${icon}<span class="big">${big}</span><span class="sub">Panel ${i+1} of 3 · ${PSTATE[i]}</span><span class="how">Click, tap, or press N</span>`;
+      c.setAttribute('aria-label', i===0 ? 'Open the case: process Panel 1 of 3' : `Process Panel ${i+1} of 3, ${PSTATE[i]}`);
+    } else {
+      c.innerHTML = `<span class="tab">QUEUED · PANEL ${i+1}</span>${icon}<span class="big">In queue</span><span class="sub">Panel ${i+1} of 3 · ${PSTATE[i]}</span><span class="how">After Panel ${state.revealed+1}</span>`;
+      c.setAttribute('aria-label', `Panel ${i+1} is queued behind Panel ${state.revealed+1}. Activating processes Panel ${state.revealed+1}.`);
+    }
+  });
+}
+
+/* ================= feedback ================= */
+function say(kind, code, msg){
+  fb.className = 'feedback ' + kind;
+  fb.textContent = '';
+  requestAnimationFrame(()=>{ fb.innerHTML = `<span class="code">${esc(code)}</span>${esc(msg)}`; });
+}
+function flash(el, cls){ if (reduced()) return; el.classList.remove(cls); void el.offsetWidth; el.classList.add(cls); setTimeout(()=>el.classList.remove(cls), 700); }
+
+/* ================= case file ================= */
+const STATUS = ['Awaiting processing','Received','Under review','Resolved (pending)','Case closed'];
+function updateCase(){
+  const s = state.certShown || state.seen.size===ENDINGS.length ? 4 : state.revealed;
+  const st=$('#status'); st.textContent=STATUS[s]; st.className='status st-'+s;
+  $('#nowServing').textContent = pad4(state.serving);
+  document.querySelectorAll('.steps span').forEach((el,i)=>el.classList.toggle('on', i<state.revealed));
+  $('#meter').innerHTML = ENDINGS.map((e,i)=>`<i class="${state.seen.has(i)?'seen':''} ${state.revealed===3&&state.ending===i?'cur':''}" title="${esc(e.code+' '+e.title)}"></i>`).join('');
+  $('#progressText').textContent = `Panels processed: ${state.revealed} of 3 · Endings on file: ${state.seen.size} of ${ENDINGS.length}`;
+  nextLbl.textContent = state.revealed<3 ? (state.revealed===0 ? 'Process Panel 1' : `Process Panel ${state.revealed+1}`) : 'All 3 panels processed ✓';
+  renderDocket();
+}
+function blinkLed(){ const b=$('#ledbox'); if(reduced()) return; b.classList.remove('blink'); void b.offsetWidth; b.classList.add('blink'); }
+
+/* ================= actions ================= */
+function next(){
+  if (state.busy) return busyMsg();
+  if (state.revealed>=3){
+    say('info','NX-2 · NOTHING QUEUED','All three panels are already stamped. Press Plot Twist! for a different punchline, or Refile from the start.');
+    flash(twistBtn,'shake');
+    return;
+  }
+  const i = state.revealed;
+  const cover = panels[i].querySelector('.cover');
+  const hadFocus = cover.contains(document.activeElement);
+  if (i===2){ state.ending=0; state.sheet=0; state.serving=419; state.seen.add(0); }
+  state.revealed++;
+  renderPanel(i);
+  paintCovers();
+  slamStamp(i);
+  if (i===2){ blinkLed(); setTimeout(ding, reduced()?0:420); }
+  updateCase();
+  const spec = panelSpec(i);
+  if (i<2) say('ok', `PANEL ${i+1} · ${spec.stamp}`, `Panel ${i+1} processed and stamped ${spec.stamp}. ${i===0?'Panel 2 is now in your in-tray.':'One panel left — the resolution.'}`);
+  else say('ok', `PANEL 3 · ${spec.stamp}`, `All three panels processed. Now serving 0419 — you were 0418. Press Plot Twist! to file a different punchline (7 more on file).`);
+  if (hadFocus){ const nc = panels[i+1] && panels[i+1].querySelector('.cover'); (nc && !nc.hidden ? nc : twistBtn).focus({preventScroll:true}); }
+  if (window.innerWidth < 1000){ panels[i].scrollIntoView({behavior: reduced()?'auto':'smooth', block:'nearest'}); }
+}
+
+function busyMsg(){ say('info','429 · PLEASE HOLD','Gerald is re-inking. One twist at a time, please.'); }
+
+function pickEnding(){
+  const all = ENDINGS.map((_,i)=>i).filter(i=>i!==state.ending);
+  const unseen = all.filter(i=>!state.seen.has(i));
+  const pool = unseen.length ? unseen : all;
+  return pool[Math.floor(Math.random()*pool.length)];
+}
+
+function setEnding(idx, {fromDocket=false}={}){
+  const fig = panels[2], artBox = fig.querySelector('.panel-art');
+  // peel the current sheet off the triplicate pad
+  if (!reduced()){
+    state.busy = true;
+    const peel = document.createElement('div');
+    peel.className='peel'; peel.setAttribute('aria-hidden','true');
+    peel.style.background = SHEETS[state.sheet];
+    const oldSvg = fig.querySelector('svg').cloneNode(true);
+    oldSvg.removeAttribute('role'); oldSvg.removeAttribute('aria-label');
+    oldSvg.querySelectorAll('.art').forEach(g=>g.removeAttribute('filter'));
+    peel.appendChild(oldSvg);
+    const oldStamp = fig.querySelector('.stamp').cloneNode(true); oldStamp.classList.remove('slam'); peel.appendChild(oldStamp);
+    artBox.appendChild(peel);
+    requestAnimationFrame(()=>peel.classList.add('go'));
+    const done = () => { peel.remove(); state.busy=false; };
+    peel.addEventListener('animationend', done, {once:true});
+    setTimeout(()=>{ if (peel.isConnected) done(); }, 1100);
+    rip();
+  }
+  state.ending = idx;
+  state.sheet = (state.sheet+1)%3;
+  state.seen.add(idx);
+  renderPanel(2);
+  const st = fig.querySelector('.stamp'); st.hidden = true;
+  setTimeout(()=>slamStamp(2), reduced()?0:260);
+}
+
+function twist(){
+  if (state.revealed<3){
+    const left = 3-state.revealed;
+    say('err','ERROR PT-409 · PREMATURE TWIST',`A plot can’t be twisted before it has been filed. Process the remaining ${left} panel${left>1?'s':''} first (Panel ${state.revealed+1} is in your in-tray).`);
+    flash(twistBtn,'shake');
+    const c = panels[state.revealed].querySelector('.cover'); flash(c,'nudge');
+    return;
+  }
+  if (state.busy) return busyMsg();
+  const idx = pickEnding();
+  state.serving++;
+  const ticket = pad4(state.serving);
+  const firstTime = !state.seen.has(idx);
+  setEnding(idx);
+  state.docket.unshift({ticket, idx, time:new Date()});
+  state.active = ticket;
+  popTicket(ticket);
+  blinkLed();
+  updateCase();
+  const e = ENDINGS[idx];
+  const remaining = ENDINGS.length - state.seen.size;
+  if (state.seen.size===ENDINGS.length && !state.certShown){
+    state.certShown = true; showCert(); updateCase();
+    say('ok',`TICKET ${ticket} · CASE CLOSED`,`Twist ${e.code} “${e.title}” stamped ${e.stamp}. That’s all 8 endings — your Certificate of Narrative Exhaustion is in the docket.`);
+  } else {
+    say('ok',`TICKET ${ticket} · ${e.stamp}`,`Plot twist filed: ${e.code} “${e.title}”, now on the ${SHEET_NAMES[state.sheet]} copy. ${firstTime ? (remaining? `${remaining} ending${remaining>1?'s':''} still unseen.` : '') : 'You’ve seen every ending — twists now reshuffle at random.'}`);
+  }
+}
+
+function reopen(ticket){
+  if (state.busy) return busyMsg();
+  const entry = state.docket.find(d=>d.ticket===ticket); if (!entry) return;
+  const idx = entry.idx;
+  state.active = ticket;
+  if (idx===state.ending){ updateCase(); say('info','DOCKET · ALREADY ON FILE',`${ENDINGS[idx].code} “${ENDINGS[idx].title}” is already on Panel 3.`); return; }
+  setEnding(idx, {fromDocket:true});
+  updateCase();
+  const e=ENDINGS[idx];
+  say('ok',`DOCKET · RE-OPENED`,`${e.code} “${e.title}” is back on Panel 3 (${SHEET_NAMES[state.sheet]} copy). No new ticket needed — it was already paid for in patience.`);
+  if (window.innerWidth < 1000) panels[2].scrollIntoView({behavior: reduced()?'auto':'smooth', block:'nearest'});
+}
+
+function refile(){
+  const hasProgress = state.revealed>0;
+  if (hasProgress && !state.confirm){
+    state.confirm = true;
+    refileBtn.classList.add('confirm');
+    refileLbl.textContent = 'Press again to confirm (Form RF-R)';
+    say('info','RF-R · CONFIRM','Refiling clears all three panels and the twist docket. Press Refile again within 4 seconds to confirm.');
+    clearTimeout(state.confirmTimer);
+    state.confirmTimer = setTimeout(cancelConfirm, 4000);
+    return;
+  }
+  cancelConfirm(true);
+  Object.assign(state,{active:null, revealed:0, ending:0, seen:new Set(), docket:[], serving:417, sheet:0, busy:false, certShown:false});
+  panels.forEach(fig=>{
+    const svg = fig.querySelector('svg'); svg.innerHTML=''; svg.setAttribute('aria-label',`Panel ${+fig.dataset.i+1}, not yet processed.`);
+    fig.querySelector('.stamp').hidden=true;
+    fig.querySelectorAll('.peel').forEach(p=>p.remove());
+    fig.querySelector('.panel-art').style.background='';
+    const cap=fig.querySelector('figcaption'); cap.className='pending'; cap.textContent='Awaiting processing.';
+    const ps=fig.querySelector('.pstate'); ps.textContent='Not yet processed'; ps.classList.remove('done');
+    fig.style.removeProperty('--pad1'); fig.style.removeProperty('--pad2');
+  });
+  $('#certSlot').innerHTML='';
+  paintCovers(); updateCase();
+  say('ok','RF-R · REFILED', hasProgress ? 'Case refiled from the start. Now serving 0417 again — it’s as if you were never here.' : 'Nothing to refile yet — the case is already at the start. Press N or click Panel 1 to begin.');
+  if (hasProgress) panels[0].querySelector('.cover').focus({preventScroll:true});
+}
+function cancelConfirm(silent){
+  clearTimeout(state.confirmTimer);
+  if (!state.confirm) return;
+  state.confirm=false; refileBtn.classList.remove('confirm'); refileLbl.textContent='Refile from the start';
+  if (silent!==true) say('info','RF-R · WITHDRAWN','Refile request withdrawn. Your case stays exactly as it was.');
+}
+
+/* ================= docket & certificate ================= */
+function renderDocket(){
+  const list=$('#docket'), empty=$('#docketEmpty');
+  empty.hidden = state.docket.length>0;
+  $('#emptyText').textContent = state.revealed<3
+    ? 'No plot twists on file. Process all three panels, then press Plot Twist! to file one.'
+    : 'No plot twists on file. The punchline stands exactly as originally filed (PT-00 “Take a Number”).';
+  list.innerHTML = state.docket.map(d=>{
+    const e=ENDINGS[d.idx], cur = state.active===d.ticket;
+    return `<li class="dk" ${cur?'aria-current="true"':''}>
+      <span class="tk" aria-label="Ticket ${d.ticket}">${d.ticket}</span>
+      <span><span class="t1">${e.code} · ${esc(e.title)}</span><span class="t2">Stamped ${esc(e.stamp)} · ${d.time.toLocaleTimeString([], {hour:'numeric',minute:'2-digit',second:'2-digit'})}</span></span>
+      ${cur ? `<span class="cur">On panel 3</span>` : `<button class="btn btn-mini" type="button" data-reopen="${d.ticket}">Re-open<span class="sr"> ${esc(e.title)}</span></button>`}
+    </li>`;
+  }).join('');
+}
+function showCert(){
+  const when = today.toLocaleDateString('en-US',{month:'long',day:'numeric',year:'numeric'});
+  $('#certSlot').innerHTML = `<div class="cert ${reduced()?'':'in'}" role="region" aria-labelledby="certTitle">
+    <div class="fc2">FORM CNE-8 · ISSUED IN TRIPLICATE</div>
+    <h3 id="certTitle" tabindex="-1">Certificate of Narrative Exhaustion</h3>
+    <p>Let it be recorded that on ${esc(when)} the bearer witnessed all eight (8) resolutions of Case RF-2026-000417, and is hereby entitled to request Form CNE-9 (Certificate of Having Received a Certificate), available upon request at Window 3.</p>
+    <div class="sig"><span>D. Pemberton-Quill, Clerk IV</span><span>Gerald (his mark)</span><span>Dir. Coo</span></div>
+    <div class="row"><button class="btn btn-ghost" type="button" data-cert="keep">Keep twisting</button><button class="btn btn-ghost" type="button" data-cert="refile">Reopen case from the start</button></div>
+  </div>`;
+}
+
+/* ================= audio (synthesised, off by default) ================= */
+let actx=null;
+function ac(){ if(!actx){ const C=window.AudioContext||window.webkitAudioContext; if(!C) return null; actx=new C(); } if(actx.state==='suspended') actx.resume(); return actx; }
+function noise(a, dur, shape){ const b=a.createBuffer(1, Math.floor(a.sampleRate*dur), a.sampleRate), d=b.getChannelData(0); for(let i=0;i<d.length;i++) d[i]=(Math.random()*2-1)*shape(i/d.length); const s=a.createBufferSource(); s.buffer=b; return s; }
+function thunk(){ if(!state.sound) return; const a=ac(); if(!a) return; const t=a.currentTime;
+  const o=a.createOscillator(), g=a.createGain(); o.type='sine'; o.frequency.setValueAtTime(150,t); o.frequency.exponentialRampToValueAtTime(46,t+.18);
+  g.gain.setValueAtTime(.0001,t); g.gain.exponentialRampToValueAtTime(.55,t+.006); g.gain.exponentialRampToValueAtTime(.0001,t+.22); o.connect(g).connect(a.destination); o.start(t); o.stop(t+.25);
+  const n=noise(a,.07,p=>(1-p)**3), f=a.createBiquadFilter(); f.type='lowpass'; f.frequency.value=1600; const g2=a.createGain(); g2.gain.value=.45; n.connect(f).connect(g2).connect(a.destination); n.start(t); }
+function ding(){ if(!state.sound) return; const a=ac(); if(!a) return; const t=a.currentTime;
+  [[988,0],[784,.16]].forEach(([fq,dt])=>{ const o=a.createOscillator(), g=a.createGain(); o.type='triangle'; o.frequency.value=fq; g.gain.setValueAtTime(.0001,t+dt); g.gain.exponentialRampToValueAtTime(.25,t+dt+.01); g.gain.exponentialRampToValueAtTime(.0001,t+dt+.5); o.connect(g).connect(a.destination); o.start(t+dt); o.stop(t+dt+.55); }); }
+function rip(){ if(!state.sound) return; const a=ac(); if(!a) return; const t=a.currentTime;
+  const n=noise(a,.32,p=>Math.sin(p*Math.PI)*(.6+.4*Math.random())), f=a.createBiquadFilter(); f.type='bandpass'; f.Q.value=1.2; f.frequency.setValueAtTime(700,t); f.frequency.exponentialRampToValueAtTime(3200,t+.3); const g=a.createGain(); g.gain.value=.35; n.connect(f).connect(g).connect(a.destination); n.start(t); }
+function toggleSound(){
+  state.sound=!state.sound;
+  soundBtn.setAttribute('aria-pressed', String(state.sound));
+  soundLbl.textContent = `Stamp sound: ${state.sound?'on':'off'}`;
+  $('#soundWaves').setAttribute('opacity', state.sound? '1':'.35');
+  if (state.sound){ const ok = ac(); ding(); say('ok','AU-1 · SOUND ON', ok ? 'Stamp sound on: thunks, dings and carbon-copy rips.' : 'This browser has no Web Audio — sound is unavailable, but everything else works.'); }
+  else say('info','AU-1 · SOUND OFF','Stamp sound off. The office returns to its customary silence.');
+}
+
+/* ticket that pops out of the Plot Twist! button */
+function popTicket(t){
+  if (reduced()) return;
+  const p=document.createElement('span'); p.className='ticket-pop'; p.textContent='No. '+t; p.setAttribute('aria-hidden','true');
+  twistBtn.appendChild(p); setTimeout(()=>p.remove(), 950);
+}
+
+/* ================= recursion (the self-filing form) ================= */
+function fillDroste(svg){
+  const slot0 = svg.querySelector('.droste-slot');
+  if (!slot0) return;
+  const snap = () => {
+    const g = document.createElementNS('http://www.w3.org/2000/svg','g');
+    svg.querySelectorAll(':scope > g').forEach(layer=>g.appendChild(layer.cloneNode(true)));
+    g.querySelectorAll('[filter]').forEach(n=>n.removeAttribute('filter'));
+    return g;
+  };
+  const P = snap(); // panel 3 with an empty slot
+  const mini = i => {
+    if (i===2) return P.cloneNode(true);
+    const src = panels[i].querySelector('svg');
+    const g = document.createElementNS('http://www.w3.org/2000/svg','g');
+    src.querySelectorAll(':scope > g').forEach(layer=>g.appendChild(layer.cloneNode(true)));
+    g.querySelectorAll('[filter]').forEach(n=>n.removeAttribute('filter'));
+    return g;
+  };
+  let slot = slot0;
+  for (let depth=0; depth<4 && slot; depth++){
+    const x=+slot.dataset.x, y=+slot.dataset.y, w=+slot.dataset.w, gap=6*(w/268);
+    const pw=(w-2*gap)/3, k=pw/400;
+    let inner=null;
+    for (let i=0;i<3;i++){
+      const holder = document.createElementNS('http://www.w3.org/2000/svg','g');
+      holder.setAttribute('transform',`translate(${(x+i*(pw+gap)).toFixed(2)} ${y}) scale(${k.toFixed(4)})`);
+      const clip = mini(i);
+      holder.appendChild(clip);
+      const border = document.createElementNS('http://www.w3.org/2000/svg','rect');
+      border.setAttribute('width','400'); border.setAttribute('height','460'); border.setAttribute('fill','none'); border.setAttribute('stroke',INK); border.setAttribute('stroke-width', String(Math.min(40, 2/k)));
+      holder.appendChild(border);
+      slot.appendChild(holder);
+      if (i===2) inner = clip.querySelector('.droste-slot');
+    }
+    slot = inner;
+  }
+}
+
+/* ================= line boil (hand-drawn wobble), paused for reduced motion ================= */
+let boilTimer=null;
+function boilTick(){ boilFrame=(boilFrame+1)%3; document.querySelectorAll('.strip svg > .art').forEach(g=>g.setAttribute('filter',`url(#boil${boilFrame})`)); }
+function syncBoil(){
+  clearInterval(boilTimer); boilTimer=null;
+  if (!reduced() && !document.hidden) boilTimer=setInterval(boilTick, 240);
+}
+RM.addEventListener && RM.addEventListener('change', syncBoil);
+document.addEventListener('visibilitychange', syncBoil);
+
+/* ================= cast portraits (reuse the strip's drawing code) ================= */
+function renderCast(){
+  const people = [
+    {name:'Barnaby Oyelaran-Fitch', role:'Applicant · Ticket 0418', text:'Retired piano tuner. Owner of one (1) mailbox, currently Eggshell, soon to be Bone.', vb:'60 134 108 108', svg: barnaby({mood:'hopeful', arm:'none', can:false})},
+    {name:'Dolores Pemberton-Quill', role:'Clerk, Grade IV · Window 3', text:'Thirty-one years of service. Has never lost a form, and has twice found forms nobody had lost.', vb:'252 150 106 106', svg: dolores({mood:'polite'})},
+    {name:'Gerald', role:'Self-inking stamp · est. 1974', text:'Speaks roughly once a decade. Currently re-inking.', vb:'4 0 112 112', svg: gerald(60,100,{mood:'asleep', s:2})},
+    {name:'Director Coo', role:'Head of Form Provision', text:'In post since 2019. Excellent posture. Declined to comment beyond “coo.”', vb:'4 6 110 110', svg: pigeon(56,100,1.6)}
+  ];
+  $('#cast').innerHTML = people.map(p=>`<div class="who"><svg viewBox="${p.vb}" aria-hidden="true" focusable="false"><rect x="-500" y="-500" width="1500" height="1500" fill="#f6f0e1"/>${p.svg}</svg><div><span class="role">${p.role}</span><b>${p.name}</b><p>${p.text}</p></div></div>`).join('');
+}
+
+/* ================= wiring ================= */
+nextBtn.addEventListener('click', next);
+twistBtn.addEventListener('click', twist);
+refileBtn.addEventListener('click', refile);
+soundBtn.addEventListener('click', toggleSound);
+panels.forEach(fig=>fig.querySelector('.cover').addEventListener('click', e=>{ e.stopPropagation(); next(); }));
+strip.addEventListener('click', e=>{
+  if (e.target.closest('button')) return;
+  if (!e.target.closest('.panel-art')) return;
+  next();
+});
+$('#docket').addEventListener('click', e=>{ const b=e.target.closest('[data-reopen]'); if(b) reopen(b.dataset.reopen); });
+$('#certSlot').addEventListener('click', e=>{
+  const b=e.target.closest('[data-cert]'); if(!b) return;
+  if (b.dataset.cert==='keep'){ $('#certSlot').innerHTML=''; say('ok','CNE-8 · FILED','Certificate filed in your records. Twisting remains available — endings now reshuffle at random.'); twistBtn.focus(); }
+  else { state.confirm=true; refile(); }
+});
+document.addEventListener('keydown', e=>{
+  if (e.altKey||e.ctrlKey||e.metaKey) return;
+  const tag=(e.target.tagName||'').toLowerCase();
+  if (tag==='input'||tag==='textarea'||tag==='select'||e.target.isContentEditable) return;
+  const k=e.key;
+  if (k==='n'||k==='N'||k==='ArrowRight'){ e.preventDefault(); next(); }
+  else if (k==='t'||k==='T'){ e.preventDefault(); twist(); }
+  else if (k==='r'||k==='R'){ e.preventDefault(); refile(); }
+  else if (k==='m'||k==='M'){ e.preventDefault(); toggleSound(); }
+  else if (k==='Escape' && state.confirm){ cancelConfirm(); }
+});
+
+renderCast();
+paintCovers();
+updateCase();
+syncBoil();
+})();
+</script>
+</body>
+</html>

← 3be6fd2 model-arena: multi-select checkboxes for challenge-level iTe  ·  back to Model Arena  ·  model-arena: preserve build-picker checkbox selection across b4a8fe8 →