[object Object]

← back to Crazy News Channel

Add P24 cartoon: Cloture o'Clock (invented senate-clock filibuster/cloture satire)

7ddc237131f0b8c62c77ffc86bba3d61f0713398 · 2026-09-24 10:39:12 -0700 · Steve Abrams

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Files touched

Diff

commit 7ddc237131f0b8c62c77ffc86bba3d61f0713398
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 24 10:39:12 2026 -0700

    Add P24 cartoon: Cloture o'Clock (invented senate-clock filibuster/cloture satire)
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 cartoons/20260924-filibuster-vs-cloture.html | 390 +++++++++++++++++++++++++++
 cartoons/manifest.js                         |  10 +
 2 files changed, 400 insertions(+)

diff --git a/cartoons/20260924-filibuster-vs-cloture.html b/cartoons/20260924-filibuster-vs-cloture.html
new file mode 100644
index 0000000..6eafd5e
--- /dev/null
+++ b/cartoons/20260924-filibuster-vs-cloture.html
@@ -0,0 +1,390 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>Cloture o'Clock — P24 Political Cartoon Desk</title>
+<meta name="description" content="An original invented-satire strip: the Forum of Delegates' ceremonial floor clock is wound tight by one side and wound right back by the other, ticking forward and backward until it just gives up." />
+<style>
+:root{
+  --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
+  --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
+}
+@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
+  --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
+  --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
+}}
+:root[data-theme="dark"]{
+  --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
+  --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
+}
+*,*::before,*::after{box-sizing:border-box}
+html{overflow-x:hidden}
+[hidden]{display:none!important}
+body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
+h1,h2,p{margin:0}
+:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
+a{color:var(--accent2)}
+header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
+header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
+.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
+h1{font-size:1.7rem;margin-top:2px}
+.dek{font-style:italic;color:var(--ink-dim);max-width:60ch;margin-top:4px}
+main{max-width:900px;margin:0 auto;padding:18px 20px}
+.strip{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
+@media (max-width:720px){ .strip{grid-template-columns:1fr} }
+figure{margin:0;background:var(--panel);border:3px solid var(--rule);border-radius:8px;overflow:hidden;display:flex;flex-direction:column}
+.phead{display:flex;justify-content:space-between;font-family:-apple-system,sans-serif;font-size:.65rem;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-dim);padding:6px 10px;border-bottom:1px solid var(--ink-dim)}
+.art{width:100%;aspect-ratio:1/1;display:block;cursor:pointer;background:transparent}
+.art:focus-visible{outline:3px solid var(--focus); outline-offset:-3px}
+figcaption{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);padding:8px 10px}
+.cover{width:100%;height:100%;background:repeating-linear-gradient(45deg,var(--panel),var(--panel) 10px,color-mix(in srgb, var(--panel) 85%, var(--ink)) 10px,color-mix(in srgb, var(--panel) 85%, var(--ink)) 20px);border:none;display:flex;align-items:center;justify-content:center;font-family:-apple-system,sans-serif;font-size:.85rem;color:var(--ink-dim);cursor:pointer;aspect-ratio:1/1}
+.cover:hover{color:var(--ink)}
+.dock{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:16px 0;font-family:-apple-system,sans-serif}
+button.primary,button.twist{font-family:-apple-system,sans-serif;font-size:.9rem;font-weight:700;border-radius:6px;padding:9px 16px;cursor:pointer;border:2px solid var(--rule)}
+button.primary{background:var(--ink);color:var(--panel)}
+button.twist{background:var(--seal);color:#241c14;border-color:var(--seal)}
+button:disabled{opacity:.45;cursor:not-allowed}
+button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-dim);color:var(--ink-dim);border-radius:6px;padding:6px 10px;font-family:-apple-system,sans-serif;font-size:.8rem;cursor:pointer}
+#status{font-family:-apple-system,sans-serif;font-size:.82rem;padding:8px 12px;border-radius:6px;border:1px solid var(--ink-dim);color:var(--ink-dim);min-height:1.2em}
+#status.ok{border-color:var(--accent2);color:var(--accent2)}
+#status.err{border-color:var(--accent);color:var(--accent)}
+#status.info{border-color:var(--focus);color:var(--focus)}
+footer{max-width:900px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
+.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
+@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
+.seal-anim{transition:transform .3s ease}
+@media (prefers-reduced-motion: reduce){ .seal-anim{transition:none} }
+</style>
+</head>
+<body>
+<header>
+  <div>
+    <div class="kicker">P24 Political Cartoon Desk &middot; Invented Satire</div>
+    <h1>Cloture o'Clock</h1>
+    <p class="dek">The Forum of Delegates' ceremonial floor clock is wound tight by one side and wound right back by the other, ticking forward and backward until it simply gives up.</p>
+  </div>
+  <div style="display:flex;flex-direction:column;gap:8px;align-items:flex-end">
+    <a class="back" href="index.html">&larr; Cartoon Desk</a>
+    <button class="theme" id="themeBtn" type="button" aria-pressed="false">Dark mode: off</button>
+  </div>
+</header>
+<main>
+  <div class="strip" id="strip">
+    <figure class="panel" data-i="0">
+      <div class="phead"><span>Panel 1 &middot; The Winding</span><span id="stamp0"></span></div>
+      <svg class="art" id="art0" viewBox="0 0 300 300" role="img" tabindex="0"></svg>
+      <button class="cover" id="cover0" type="button">Click, tap, or press N to begin</button>
+      <figcaption id="cap0"></figcaption>
+    </figure>
+    <figure class="panel" data-i="1">
+      <div class="phead"><span>Panel 2 &middot; The Unwinding</span><span id="stamp1"></span></div>
+      <svg class="art" id="art1" viewBox="0 0 300 300" role="img" tabindex="0"></svg>
+      <button class="cover" id="cover1" type="button" disabled>In queue</button>
+      <figcaption id="cap1"></figcaption>
+    </figure>
+    <figure class="panel" data-i="2">
+      <div class="phead"><span>Panel 3 &middot; The Shrug</span><span id="stamp2"></span></div>
+      <svg class="art" id="art2" viewBox="0 0 300 300" role="img" tabindex="0"></svg>
+      <button class="cover" id="cover2" type="button" disabled>In queue</button>
+      <figcaption id="cap2"></figcaption>
+    </figure>
+  </div>
+
+  <div class="dock">
+    <button class="primary" id="nextBtn" type="button">Process Panel 1</button>
+    <button class="twist" id="twistBtn" type="button">Plot Twist!</button>
+    <div id="status" role="status" aria-live="polite"></div>
+  </div>
+  <p class="hint">Keys: N/&rarr; next panel &middot; T plot twist &middot; click a panel to advance</p>
+</main>
+<footer>All chambers, floor leaders, and clocks are invented. No actual timepiece was inconvenienced. Part of PANDEMONIUM-24.</footer>
+
+<script>
+(function(){
+  const NS = 'http://www.w3.org/2000/svg';
+  function el(tag, attrs, parent){
+    const n = document.createElementNS(NS, tag);
+    for (const k in attrs) n.setAttribute(k, attrs[k]);
+    if (parent) parent.appendChild(n);
+    return n;
+  }
+  function reduced(){ return matchMedia('(prefers-reduced-motion: reduce)').matches; }
+
+  // ---- invented chamber + floor leaders — simple flat iconographic mascots, not detailed characters ----
+  const DELEGATES = [
+    { name: 'Floor Leader Bellwether', color: '#b5302f' },
+    { name: 'Floor Leader Cinch', color: '#0f6b52' },
+  ];
+
+  function delegate(svg, cx, cy, r, color, label){
+    const g = el('g', {}, svg);
+    el('circle', { cx, cy, r, fill: color, 'fill-opacity': '0.15', stroke: color, 'stroke-width': 3 }, g);
+    el('circle', { cx, cy: cy - r*0.35, r: r*0.28, fill: color }, g); // head
+    el('path', { d: `M ${cx - r*0.5} ${cy + r*0.5} Q ${cx} ${cy + r*0.1} ${cx + r*0.5} ${cy + r*0.5}`, fill: 'none', stroke: color, 'stroke-width': r*0.22, 'stroke-linecap':'round' }, g); // shoulders
+    if (label) {
+      const t = el('text', { x: cx, y: cy + r + 16, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'11', fill: 'currentColor' }, g);
+      t.textContent = label;
+    }
+    return g;
+  }
+
+  function table(svg, x, y, w){
+    el('rect', { x, y, width: w, height: 10, fill: 'currentColor', opacity: 0.5, rx: 2 }, svg);
+  }
+
+  function textline(svg, x, y, str, dim, bold){
+    const t = el('text', { x, y, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size': bold ? '12' : '11', 'font-weight': bold ? '700' : '400', fill: dim ? 'var(--ink-dim)' : 'currentColor' }, svg);
+    t.textContent = str;
+    return t;
+  }
+
+  // ---- the personified clock's facial expressions — this is what Plot Twist changes ----
+  function faceExpr(parent, cx, cy, r, expr, color){
+    const eyeY = cy - r*0.15;
+    const eyeDX = r*0.32;
+    const eyeR = r*0.09;
+    const mouthY = cy + r*0.4;
+    function normalEyes(){
+      el('circle', { cx: cx-eyeDX, cy: eyeY, r: eyeR, fill: 'currentColor' }, parent);
+      el('circle', { cx: cx+eyeDX, cy: eyeY, r: eyeR, fill: 'currentColor' }, parent);
+    }
+    function spiralEye(ex, ey, rr){
+      let d = `M ${ex} ${ey}`;
+      for (let t = 0.2; t <= 3.2; t += 0.4) {
+        const rad = rr * (t/3.2);
+        const ang = t*4;
+        d += ` L ${(ex+Math.cos(ang)*rad).toFixed(2)} ${(ey+Math.sin(ang)*rad).toFixed(2)}`;
+      }
+      el('path', { d, fill: 'none', stroke: 'currentColor', 'stroke-width': 1.4 }, parent);
+    }
+    function crossEye(ex, ey, rr, inward){
+      el('circle', { cx: ex, cy: ey, r: rr, fill: 'none', stroke: 'currentColor', 'stroke-width': 1.4 }, parent);
+      const px = inward ? ex + rr*0.4 : ex - rr*0.4;
+      el('circle', { cx: px, cy: ey, r: rr*0.4, fill: 'currentColor' }, parent);
+    }
+    function lidHalf(ex, ey, rr){
+      el('path', { d: `M ${ex-rr} ${ey} A ${rr} ${rr} 0 0 1 ${ex+rr} ${ey}`, fill: 'none', stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap':'round' }, parent);
+    }
+    switch (expr) {
+      case 'eager': // panel 1 default — wide-eyed and racing the clock
+        normalEyes();
+        el('circle', { cx, cy: mouthY, r: r*0.12, fill: 'none', stroke: 'currentColor', 'stroke-width': 2 }, parent);
+        break;
+      case 'queasy': // panel 2 default — being wound the wrong way does not feel great
+        normalEyes();
+        el('path', { d: `M ${cx-r*0.22} ${mouthY} Q ${cx-r*0.1} ${mouthY+6} ${cx} ${mouthY} Q ${cx+r*0.1} ${mouthY-6} ${cx+r*0.22} ${mouthY}`, fill: 'none', stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap':'round' }, parent);
+        break;
+      case 'flat': // panel 3 default — the shrug face, eyebrows up, mouth level
+        normalEyes();
+        el('line', { x1: cx-r*0.18, y1: mouthY, x2: cx+r*0.18, y2: mouthY, stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap':'round' }, parent);
+        el('line', { x1: cx-eyeDX-6, y1: eyeY-eyeR-6, x2: cx-eyeDX+6, y2: eyeY-eyeR-10, stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap':'round' }, parent);
+        el('line', { x1: cx+eyeDX+6, y1: eyeY-eyeR-6, x2: cx+eyeDX-6, y2: eyeY-eyeR-10, stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap':'round' }, parent);
+        break;
+      case 'dizzy': // Plot Twist option 1
+        spiralEye(cx-eyeDX, eyeY, eyeR*1.7);
+        spiralEye(cx+eyeDX, eyeY, eyeR*1.7);
+        el('path', { d: `M ${cx-r*0.22} ${mouthY} q ${r*0.11} 10 ${r*0.22} 0 q ${r*0.11} 10 ${r*0.22} 0`, fill: 'none', stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap':'round' }, parent);
+        break;
+      case 'exhausted': // Plot Twist option 2
+        lidHalf(cx-eyeDX, eyeY, eyeR*1.3);
+        lidHalf(cx+eyeDX, eyeY, eyeR*1.3);
+        el('path', { d: `M ${cx-r*0.18} ${mouthY+4} Q ${cx} ${mouthY-4} ${cx+r*0.18} ${mouthY+4}`, fill: 'none', stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap':'round' }, parent);
+        break;
+      case 'smirking': // Plot Twist option 3
+        normalEyes();
+        el('path', { d: `M ${cx-r*0.2} ${mouthY} Q ${cx} ${mouthY+2} ${cx+r*0.1} ${mouthY-6} Q ${cx+r*0.16} ${mouthY-10} ${cx+r*0.22} ${mouthY-8}`, fill: 'none', stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap':'round' }, parent);
+        break;
+      case 'crosseyed': // Plot Twist option 4
+        crossEye(cx-eyeDX, eyeY, eyeR*1.3, true);
+        crossEye(cx+eyeDX, eyeY, eyeR*1.3, false);
+        el('circle', { cx, cy: mouthY, r: r*0.1, fill: 'none', stroke: 'currentColor', 'stroke-width': 2 }, parent);
+        break;
+      default:
+        normalEyes();
+        el('line', { x1: cx-r*0.18, y1: mouthY, x2: cx+r*0.18, y2: mouthY, stroke: 'currentColor', 'stroke-width': 2 }, parent);
+    }
+  }
+
+  function clockChar(svg, cx, cy, r, color, hourDeg, minDeg, expr, armsUp){
+    const g = el('g', {}, svg);
+    el('circle', { cx, cy, r, fill: color, 'fill-opacity': '0.14', stroke: color, 'stroke-width': 4 }, g);
+    for (let i=0;i<12;i++){
+      const a = (i/12)*2*Math.PI;
+      const x1 = cx + Math.sin(a)*(r-4), y1 = cy - Math.cos(a)*(r-4);
+      const x2 = cx + Math.sin(a)*(r-11), y2 = cy - Math.cos(a)*(r-11);
+      el('line', { x1: x1.toFixed(2), y1: y1.toFixed(2), x2: x2.toFixed(2), y2: y2.toFixed(2), stroke: color, 'stroke-width': i%3===0 ? 2.4 : 1.4 }, g);
+    }
+    function hand(deg, len, w){
+      const a = deg*Math.PI/180;
+      const x2 = cx + Math.sin(a)*len, y2 = cy - Math.cos(a)*len;
+      el('line', { x1: cx, y1: cy, x2: x2.toFixed(2), y2: y2.toFixed(2), stroke: 'currentColor', 'stroke-width': w, 'stroke-linecap':'round' }, g);
+    }
+    hand(hourDeg, r*0.48, 5);
+    hand(minDeg, r*0.72, 3);
+    el('circle', { cx, cy, r: 4, fill: 'currentColor' }, g);
+    faceExpr(g, cx, cy, r, expr, color);
+    if (armsUp) {
+      [-1, 1].forEach(side => {
+        const sx = cx + side*r;
+        const ex = cx + side*r*1.5;
+        const ey = cy - r*0.9;
+        el('path', { d: `M ${sx} ${cy+r*0.1} Q ${cx+side*r*1.4} ${cy-r*0.1} ${ex} ${ey}`, fill: 'none', stroke: color, 'stroke-width': 7, 'stroke-linecap':'round' }, g);
+        el('circle', { cx: ex, cy: ey, r: 9, fill: color }, g);
+      });
+    }
+    return g;
+  }
+
+  // a small circular winding arrow, clockwise or counter-clockwise, showing which way the key is turning
+  function windArrow(svg, cx, cy, r, color, clockwise){
+    const sweep = clockwise ? 1 : 0;
+    const start = clockwise ? -220 : 40;
+    const end = clockwise ? 40 : -220;
+    function pt(deg){ const a = deg*Math.PI/180; return [cx + Math.cos(a)*r, cy + Math.sin(a)*r]; }
+    const [x1,y1] = pt(start);
+    const [x2,y2] = pt(end);
+    const d = `M ${x1.toFixed(2)} ${y1.toFixed(2)} A ${r} ${r} 0 1 ${sweep} ${x2.toFixed(2)} ${y2.toFixed(2)}`;
+    el('path', { d, fill: 'none', stroke: color, 'stroke-width': 3.5, 'stroke-linecap':'round' }, svg);
+    const tipAngle = (end + (sweep ? 90 : -90)) * Math.PI/180;
+    const size = r*0.45;
+    const b1x = x2 - Math.cos(tipAngle-0.5)*size, b1y = y2 - Math.sin(tipAngle-0.5)*size;
+    const b2x = x2 - Math.cos(tipAngle+0.5)*size, b2y = y2 - Math.sin(tipAngle+0.5)*size;
+    el('polygon', { points: `${x2.toFixed(2)},${y2.toFixed(2)} ${b1x.toFixed(2)},${b1y.toFixed(2)} ${b2x.toFixed(2)},${b2y.toFixed(2)}`, fill: color }, svg);
+  }
+
+  const BASE_EXPR = ['eager', 'queasy', 'flat'];
+  const TWIST_EXPR = ['dizzy', 'exhausted', 'smirking', 'crosseyed'];
+  function currentExpr(i){
+    return state.twists > 0 ? TWIST_EXPR[(state.twists - 1) % TWIST_EXPR.length] : BASE_EXPR[i];
+  }
+
+  function panel0(svg){
+    svg.innerHTML = '';
+    table(svg, 40, 250, 220);
+    delegate(svg, 65, 195, 30, DELEGATES[0].color, DELEGATES[0].name);
+    clockChar(svg, 195, 130, 66, DELEGATES[0].color, 40, 250, currentExpr(0), false);
+    windArrow(svg, 195, 130, 90, DELEGATES[0].color, true);
+    textline(svg, 150, 38, '"Cloture. Now. Before anyone reconsiders."');
+    textline(svg, 150, 56, 'Wound three turns ahead of schedule.', true);
+  }
+
+  function panel1(svg){
+    svg.innerHTML = '';
+    table(svg, 40, 250, 220);
+    delegate(svg, 235, 195, 30, DELEGATES[1].color, DELEGATES[1].name);
+    clockChar(svg, 105, 130, 66, DELEGATES[1].color, 320, 110, currentExpr(1), false);
+    windArrow(svg, 105, 130, 90, DELEGATES[1].color, false);
+    textline(svg, 150, 38, '"Not so fast. We are not nearly finished."');
+    textline(svg, 150, 56, 'Rewound two turns past where it started.', true);
+  }
+
+  function panel2(svg, twists){
+    svg.innerHTML = '';
+    table(svg, 40, 250, 220);
+    delegate(svg, 55, 215, 26, DELEGATES[0].color, DELEGATES[0].name);
+    delegate(svg, 245, 215, 26, DELEGATES[1].color, DELEGATES[1].name);
+    const jitter = (twists % 4) * 22;
+    clockChar(svg, 150, 128, 72, 'var(--seal)', (55+jitter)%360, (285-jitter)%360, currentExpr(2), true);
+    windArrow(svg, 150, 128, 96, DELEGATES[1].color, false);
+    windArrow(svg, 150, 128, 96, DELEGATES[0].color, true);
+    textline(svg, 150, 38, 'Both keys, one clock, zero agreement.');
+    textline(svg, 150, 286, 'Nobody has agreed on what time it is since Tuesday.', true, true);
+  }
+
+  const CAPTIONS = [
+    'Floor Leader Bellwether cranks the ceremonial chamber clock forward, racing to call the question before anyone reconsiders.',
+    'Floor Leader Cinch seizes the same key and winds it back, correcting for a debate that has not nearly finished being had.',
+    'The hands blur past every hour on the dial at once. The clock has stopped keeping anyone’s time but its own.',
+  ];
+
+  const state = { revealed: 0, twists: 0 };
+
+  function renderPanel(i){
+    const svg = document.getElementById('art' + i);
+    if (i === 0) panel0(svg);
+    if (i === 1) panel1(svg);
+    if (i === 2) panel2(svg, state.twists);
+    document.getElementById('cap' + i).textContent = CAPTIONS[i];
+    document.getElementById('stamp' + i).textContent = i <= state.revealed ? ['WOUND','REWOUND','STOPPED'][i] : '';
+    const cover = document.getElementById('cover' + i);
+    cover.hidden = i <= state.revealed - 1 || (i === state.revealed);
+    if (i === state.revealed) { cover.hidden = false; cover.disabled = false; cover.textContent = i === 0 ? 'Click, tap, or press N to begin' : 'Click, tap, or press N to process'; }
+  }
+
+  function paintAll(){
+    for (let i=0;i<3;i++) {
+      renderPanel(i);
+      document.getElementById('art'+i).setAttribute('tabindex', i === state.revealed ? '0' : '-1');
+    }
+    syncButtons();
+  }
+
+  function syncButtons(){
+    const nextBtn = document.getElementById('nextBtn');
+    if (state.revealed >= 3) { nextBtn.textContent = 'All 3 panels processed ✓'; nextBtn.disabled = true; }
+    else { nextBtn.textContent = `Process Panel ${state.revealed+1}`; nextBtn.disabled = false; }
+  }
+
+  function say(kind, msg){
+    const s = document.getElementById('status');
+    s.className = kind; s.textContent = msg;
+  }
+
+  function next(){
+    if (state.revealed >= 3) { say('info', 'All 3 panels are already on file. Try Plot Twist!'); return; }
+    state.revealed++;
+    for (let i=0;i<3;i++) {
+      const cover = document.getElementById('cover'+i);
+      cover.hidden = i < state.revealed;
+      cover.disabled = i > state.revealed;
+      document.getElementById('art'+i).setAttribute('tabindex', i === state.revealed ? '0' : '-1');
+    }
+    renderPanel(state.revealed - 1); // the panel that just got processed
+    if (state.revealed < 3) renderPanel(state.revealed); // the newly-active cover's label
+    say('ok', `Panel ${state.revealed} processed.`);
+    syncButtons();
+    if (state.revealed >= 3) say('ok', 'All 3 panels processed. Press Plot Twist! to change the clock’s expression.');
+  }
+
+  function twist(){
+    if (state.revealed < 3) { say('err', 'ERROR CO-409 · Premature twist — process all 3 panels first.'); return; }
+    state.twists++;
+    renderPanel(0); renderPanel(1); renderPanel(2);
+    say('ok', `Plot twist #${state.twists}: the clock’s face goes ${TWIST_EXPR[(state.twists-1)%TWIST_EXPR.length]}.`);
+  }
+
+  document.getElementById('nextBtn').addEventListener('click', next);
+  document.getElementById('twistBtn').addEventListener('click', twist);
+  for (let i=0;i<3;i++) {
+    document.getElementById('cover'+i).addEventListener('click', () => { if (i === state.revealed) next(); });
+    document.getElementById('art'+i).addEventListener('click', () => { if (i === state.revealed) next(); });
+    document.getElementById('art'+i).addEventListener('keydown', (e) => { if ((e.key==='Enter'||e.key===' ') && i===state.revealed) { e.preventDefault(); next(); } });
+  }
+
+  const themeBtn = document.getElementById('themeBtn');
+  function safeGet(k,d){ try { return localStorage.getItem(k) ?? d; } catch { return d; } }
+  function safeSet(k,v){ try { localStorage.setItem(k,v); } catch {} }
+  let theme = safeGet('p24cartoon.theme', 'auto');
+  function applyTheme(){
+    if (theme === 'dark') { document.documentElement.setAttribute('data-theme','dark'); themeBtn.textContent='Dark mode: on'; themeBtn.setAttribute('aria-pressed','true'); }
+    else { document.documentElement.setAttribute('data-theme','light'); themeBtn.textContent='Dark mode: off'; themeBtn.setAttribute('aria-pressed','false'); }
+  }
+  applyTheme();
+  themeBtn.addEventListener('click', () => { theme = theme === 'dark' ? 'light' : 'dark'; safeSet('p24cartoon.theme', theme); applyTheme(); });
+
+  document.addEventListener('keydown', (e) => {
+    if (e.metaKey || e.ctrlKey || e.altKey) return;
+    const tag = (e.target.tagName || '').toLowerCase();
+    if (tag === 'input' || tag === 'textarea') return;
+    const k = e.key.toLowerCase();
+    if (k === 'n' || e.key === 'ArrowRight') { e.preventDefault(); next(); }
+    else if (k === 't') { e.preventDefault(); twist(); }
+  });
+
+  paintAll();
+  say('info', 'Case file open. Press N or click Panel 1 to begin.');
+})();
+</script>
+</body>
+</html>
diff --git a/cartoons/manifest.js b/cartoons/manifest.js
index 99b3a9d..e5ab3d1 100644
--- a/cartoons/manifest.js
+++ b/cartoons/manifest.js
@@ -104,5 +104,15 @@ window.P24_CARTOONS = [
     "blurb": "An invented legislative chamber plays hide-and-seek to dodge a quorum call, propping paper cutouts in empty chairs while the Sergeant-at-Arms searches a suspiciously form-shaped closet; each plot twist relocates the hiding spot.",
     "thumb": null,
     "story_id": null
+  },
+  {
+    "id": "cloture-oclock",
+    "title": "Cloture o'Clock",
+    "file": "20260924-filibuster-vs-cloture.html",
+    "created_at": "2026-09-24T23:45:00Z",
+    "category": "Political Cartoon",
+    "blurb": "An invented chamber's ceremonial floor clock is wound tight by one side and wound right back by the other, ticking forward and backward until it just shrugs; each plot twist changes its face.",
+    "thumb": null,
+    "story_id": null
   }
 ];

← 9d290d7 crazy-news-channel: live-verify hardened admin server (vp-en  ·  back to Crazy News Channel  ·  P24: register business/entertainment/scitech/sports/weather dcc569c →