[object Object]

← back to Crazy News Channel Shadowman

Add filibuster-marathon P24 cartoon (3-panel SVG, click-advance)

edcd6b332e31479c9e76256a0e97cb93497153b0 · 2026-09-24 12:40:57 -0700 · Steve Abrams

Delegate Holloway filibusters the invented Assembly, cycling reading
material (phone book -> cereal box -> furniture instructions -> his
own bill) via Plot Twist. Panel 2 has a melting Dali-style clock;
panel 3 shows the chamber asleep at their desks. Fully self-contained,
matches the commission-turtles.html house style. Verified click-advance
+ plot-twist cycling with Playwright, zero console/page errors.

Also fixed manifest.js, which a concurrent process had left with a
JS syntax error (a raw object literal appended after the array's
closing bracket) — folded that entry properly into the array and
appended this cartoon's entry alongside it.

TK-12146

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V29xaoBwcFwJyR4KC43ptn

Files touched

Diff

commit edcd6b332e31479c9e76256a0e97cb93497153b0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 24 12:40:57 2026 -0700

    Add filibuster-marathon P24 cartoon (3-panel SVG, click-advance)
    
    Delegate Holloway filibusters the invented Assembly, cycling reading
    material (phone book -> cereal box -> furniture instructions -> his
    own bill) via Plot Twist. Panel 2 has a melting Dali-style clock;
    panel 3 shows the chamber asleep at their desks. Fully self-contained,
    matches the commission-turtles.html house style. Verified click-advance
    + plot-twist cycling with Playwright, zero console/page errors.
    
    Also fixed manifest.js, which a concurrent process had left with a
    JS syntax error (a raw object literal appended after the array's
    closing bracket) — folded that entry properly into the array and
    appended this cartoon's entry alongside it.
    
    TK-12146
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01V29xaoBwcFwJyR4KC43ptn
---
 cartoons/20260924-filibuster-marathon.html | 349 +++++++++++++++++++++++++++++
 cartoons/manifest.js                       |  16 +-
 2 files changed, 363 insertions(+), 2 deletions(-)

diff --git a/cartoons/20260924-filibuster-marathon.html b/cartoons/20260924-filibuster-marathon.html
new file mode 100644
index 0000000..601b697
--- /dev/null
+++ b/cartoons/20260924-filibuster-marathon.html
@@ -0,0 +1,349 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>The Filibuster Marathon — P24 Political Cartoon Desk</title>
+<meta name="description" content="An original invented-satire strip: Delegate Holloway filibusters the fictional Assembly by reading anything within arm's reach, while a Dali-style clock melts and the chamber falls asleep standing 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>The Filibuster Marathon</h1>
+    <p class="dek">Delegate Holloway holds the floor of the invented Assembly by reading anything within arm's reach. Nobody has stopped him. Nobody remembers why he started.</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 Floor Is His</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; Time, Melting</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 Chamber Sleeps</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 (swaps Holloway's reading material) &middot; click a panel to advance</p>
+</main>
+<footer>All delegates, the Assembly, and its chamber are invented. No actual legislative body was inconvenienced, though several fictional ones dozed off. 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 reading material Holloway filibusters with, cycled by Plot Twist ----
+  const MATERIALS = [
+    { label: 'the Springfield County phone book', sub: 'Vol. 2, "Aab–Kle"', color: '#b5302f' },
+    { label: 'a family-size cereal box', sub: 'nutrition panel, both sides', color: '#0f6b52' },
+    { label: 'bookshelf assembly instructions', sub: 'translated from Swedish, badly', color: '#c9a227' },
+    { label: 'his own bill', sub: 'still stuck in committee', color: '#6b4fa0' },
+  ];
+  let materialIdx = 0;
+
+  // ---- invented Assembly members, asleep at their desks in panel 3 ----
+  const MEMBERS = [
+    { name: 'Delegate Oduya', color: '#b5302f' },
+    { name: 'Delegate Prine', color: '#0f6b52' },
+    { name: 'Delegate Vance-Kettle', color: '#c9a227' },
+    { name: 'Delegate Abernathy', color: '#6b4fa0' },
+    { name: 'Delegate Suh', color: '#0b3d91' },
+    { name: 'Delegate Marrow', color: '#b5302f' },
+  ];
+
+  function podium(svg, cx, cy){
+    el('rect', { x: cx-24, y: cy, width:48, height:60, fill:'currentColor', opacity:0.35, rx:4 }, svg);
+    el('rect', { x: cx-30, y: cy-6, width:60, height:10, fill:'currentColor', opacity:0.5, rx:2 }, svg);
+  }
+
+  function holloway(svg, cx, cy, r, color, material){
+    const g = el('g', {}, svg);
+    // body
+    el('path', { d: `M ${cx - r*0.55} ${cy + r*0.7} Q ${cx} ${cy + r*0.25} ${cx + r*0.55} ${cy + r*0.7}`, fill: 'none', stroke: color, 'stroke-width': r*0.32, 'stroke-linecap':'round' }, g);
+    // head
+    el('circle', { cx, cy: cy - r*0.15, r: r*0.34, fill: color }, g);
+    // held reading material (a rectangle tilted up, "held open")
+    const mg = el('g', { transform: `rotate(-8 ${cx + r*0.35} ${cy + r*0.25})` }, g);
+    el('rect', { x: cx + r*0.05, y: cy + r*0.05, width: r*0.75, height: r*0.55, fill: 'var(--panel)', stroke: color, 'stroke-width': 2, rx: 2 }, mg);
+    for (let i=0;i<3;i++){
+      el('line', { x1: cx + r*0.15, y1: cy + r*0.2 + i*8, x2: cx + r*0.68, y2: cy + r*0.2 + i*8, stroke: color, 'stroke-width': 1.4, opacity:0.5 }, mg);
+    }
+    if (material) {
+      const t = el('text', { x: cx, y: cy + r + 34, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-weight':'700', 'font-size':'11', fill: 'currentColor' }, g);
+      t.textContent = 'Delegate Holloway';
+      const t2 = el('text', { x: cx, y: cy + r + 48, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'9.5', fill: 'var(--ink-dim)' }, g);
+      t2.textContent = 'reading: ' + material.label;
+    }
+    return g;
+  }
+
+  function chamberClock(svg, cx, cy, r, color){
+    el('circle', { cx, cy, r, fill:'none', stroke: color, 'stroke-width': 3 }, svg);
+    el('line', { x1:cx, y1:cy, x2:cx, y2:cy-r*0.6, stroke: color, 'stroke-width':2.4, 'stroke-linecap':'round' }, svg);
+    el('line', { x1:cx, y1:cy, x2:cx+r*0.4, y2:cy+r*0.25, stroke: color, 'stroke-width':2.4, 'stroke-linecap':'round' }, svg);
+  }
+
+  function meltingClock(svg, cx, cy, r, color, melt){
+    // a Dali-style melting clock: the face droops over an edge, hands bend with it
+    const droop = 18 + melt * 14;
+    const path = `M ${cx-r} ${cy-r*0.2}
+      C ${cx-r} ${cy-r}, ${cx+r} ${cy-r}, ${cx+r} ${cy-r*0.2}
+      C ${cx+r} ${cy+r*0.5}, ${cx+r*0.5} ${cy+r*0.9+droop}, ${cx} ${cy+r*0.95+droop}
+      C ${cx-r*0.5} ${cy+r*0.9+droop}, ${cx-r} ${cy+r*0.5}, ${cx-r} ${cy-r*0.2} Z`;
+    el('path', { d: path, fill:'var(--panel)', stroke: color, 'stroke-width': 3 }, svg);
+    // droopy hands, bent partway down the melt
+    el('path', { d: `M ${cx} ${cy} Q ${cx-4} ${cy-r*0.35} ${cx-2} ${cy-r*0.55}`, fill:'none', stroke: color, 'stroke-width':2.2, 'stroke-linecap':'round' }, svg);
+    el('path', { d: `M ${cx} ${cy} Q ${cx+r*0.2} ${cy+r*0.15} ${cx+r*0.3+droop*0.3} ${cy+r*0.35+droop*0.5}`, fill:'none', stroke: color, 'stroke-width':2.2, 'stroke-linecap':'round' }, svg);
+    // a few numerals sliding down the droop
+    ['XII','III','VI'].forEach((n,i) => {
+      const t = el('text', { x: cx - r*0.5 + i*r*0.5, y: cy - r*0.05 + i*(3+melt*3), 'text-anchor':'middle', 'font-family':'Georgia,serif', 'font-size':'9', fill: color, opacity: 0.75 }, svg);
+      t.textContent = n;
+    });
+  }
+
+  function sleepingMember(svg, cx, cy, r, color, standing){
+    const g = el('g', {}, svg);
+    if (standing) {
+      // asleep standing up, head lolled back
+      el('path', { d: `M ${cx - r*0.4} ${cy + r*0.9} L ${cx - r*0.3} ${cy - r*0.1} L ${cx + r*0.3} ${cy - r*0.1} L ${cx + r*0.4} ${cy + r*0.9} Z`, fill: color, opacity:0.3 }, g);
+      el('circle', { cx: cx + r*0.12, cy: cy - r*0.45, r: r*0.3, fill: color }, g);
+    } else {
+      // slumped forward over a desk
+      el('rect', { x: cx - r*0.6, y: cy + r*0.35, width: r*1.2, height: r*0.18, fill:'currentColor', opacity:0.35, rx:2 }, g);
+      el('ellipse', { cx, cy: cy + r*0.15, rx: r*0.5, ry: r*0.32, fill: color, opacity: 0.9 }, g);
+      el('circle', { cx: cx - r*0.15, cy: cy - r*0.05, r: r*0.28, fill: color }, g);
+    }
+    // "Z" sleep marks
+    const zg = el('g', { opacity: 0.7 }, g);
+    ['z','Z','Z'].forEach((z,i) => {
+      const t = el('text', { x: cx + r*0.55 + i*7, y: cy - r*0.5 - i*10, 'font-family':'-apple-system,sans-serif', 'font-weight':'700', 'font-size': 8 + i*3, fill: color }, zg);
+      t.textContent = z;
+    });
+    return g;
+  }
+
+  function panel0(svg, material){
+    svg.innerHTML = '';
+    podium(svg, 130, 190);
+    holloway(svg, 130, 130, 46, material.color, material);
+    chamberClock(svg, 250, 55, 22, 'var(--ink-dim)');
+    const t = el('text', { x: 250, y: 90, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'8.5', fill:'var(--ink-dim)' }, svg);
+    t.textContent = 'Day 1, Hour 1';
+    const b = el('text', { x:150, y:270, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'11', 'font-weight':'700', fill:'currentColor' }, svg);
+    b.textContent = '"I yield to no one," says Delegate Holloway.';
+    const b2 = el('text', { x:150, y:286, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'9.5', fill:'var(--ink-dim)' }, svg);
+    b2.textContent = 'He opens ' + material.label + ' to page one.';
+  }
+
+  function panel1(svg, material, melt){
+    svg.innerHTML = '';
+    podium(svg, 100, 195);
+    holloway(svg, 100, 138, 40, material.color, null);
+    meltingClock(svg, 205, 120, 46, 'var(--accent)', melt);
+    const t = el('text', { x: 205, y: 60, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'8.5', fill:'var(--ink-dim)' }, svg);
+    t.textContent = 'Hour ' + (14 + melt*11) + ', give or take';
+    const b = el('text', { x:150, y:270, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'11', 'font-weight':'700', fill:'currentColor' }, svg);
+    b.textContent = 'He is now on ' + material.sub + '.';
+    const b2 = el('text', { x:150, y:286, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'9.5', fill:'var(--ink-dim)' }, svg);
+    b2.textContent = 'The chamber clock has stopped pretending to be on time.';
+  }
+
+  function panel2(svg, wave){
+    svg.innerHTML = '';
+    // rows of desks; some slumped, one asleep standing at the aisle mic
+    const cols = 3, rows = 2;
+    let i = 0;
+    for (let r0=0; r0<rows; r0++){
+      for (let c0=0; c0<cols; c0++){
+        const mx = 62 + c0*90;
+        const my = 90 + r0*95;
+        const mem = MEMBERS[(i + wave) % MEMBERS.length];
+        sleepingMember(svg, mx, my, 26, mem.color, false);
+        i++;
+      }
+    }
+    // one delegate asleep standing at the aisle microphone, mid-objection
+    sleepingMember(svg, 250, 235, 24, MEMBERS[(wave+2)%MEMBERS.length].color, true);
+    el('rect', { x: 244, y: 258, width: 4, height: 24, fill:'currentColor', opacity:0.4 }, svg);
+    el('circle', { cx: 246, cy: 256, r: 4, fill:'currentColor', opacity:0.5 }, svg);
+    const b = el('text', { x:150, y:280, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'11', 'font-weight':'700', fill:'currentColor' }, svg);
+    b.textContent = 'Nobody has left the chamber. Several have left consciousness.';
+  }
+
+  const CAPTIONS = [
+    'Delegate Holloway takes the floor of the invented Assembly to object to a routine motion, and does not intend to give it back.',
+    'Hours blur. The reading material changes; the objection, somehow, does not resolve.',
+    'The Assembly remains, technically, in session.',
+  ];
+
+  const state = { revealed: 0, twists: 0 };
+
+  function renderPanel(i){
+    const svg = document.getElementById('art' + i);
+    const material = MATERIALS[materialIdx];
+    if (i === 0) panel0(svg, material);
+    if (i === 1) panel1(svg, material, state.twists % 3);
+    if (i === 2) panel2(svg, state.twists);
+    document.getElementById('cap' + i).textContent = CAPTIONS[i];
+    document.getElementById('stamp' + i).textContent = i <= state.revealed ? ['ON THE RECORD','STILL SPEAKING','QUORUM UNCLEAR'][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 what Holloway is reading.');
+  }
+
+  function twist(){
+    if (state.revealed < 3) { say('err', 'ERROR FB-409 · Premature twist — process all 3 panels first.'); return; }
+    materialIdx = (materialIdx + 1) % MATERIALS.length;
+    state.twists++;
+    renderPanel(0); renderPanel(1); renderPanel(2);
+    say('ok', `Plot twist #${state.twists}: Delegate Holloway has moved on to ${MATERIALS[materialIdx].label}.`);
+  }
+
+  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 c949dde..1e26505 100644
--- a/cartoons/manifest.js
+++ b/cartoons/manifest.js
@@ -191,8 +191,7 @@ window.P24_CARTOONS = [
     "blurb": "An invented capitol official spins out of the revolving door as a regulator and spins back in seconds later as a lobbyist, carrying a slightly bigger briefcase every revolution.",
     "thumb": null,
     "story_id": null
-  }
-];
+  },
   {
     "id": "conrad-filibuster-qwen25",
     "title": "The Filibuster Marathon — Paul Conrad Style (AI Model Contest)",
@@ -207,3 +206,16 @@ window.P24_CARTOONS = [
     "model": "qwen25-7b",
     "challenge_id": "56406f116d2a"
   },
+  {
+    "id": "filibuster-marathon",
+    "title": "The Filibuster Marathon",
+    "file": "20260924-filibuster-marathon.html",
+    "created_at": "2026-09-24T19:15:00Z",
+    "category": "Political Cartoon",
+    "section": "politics",
+    "blurb": "Delegate Holloway holds the floor of the invented Assembly by reading anything within arm's reach — a phone book, a cereal box, furniture instructions, his own bill — while a Dali-style clock melts and the chamber falls asleep standing up.",
+    "thumb": null,
+    "story_id": null,
+    "ai_generated": false
+  }
+];

← 6db3d93 Add Model Arena winner: The Filibuster Marathon (Paul Conrad  ·  back to Crazy News Channel Shadowman  ·  Feature Conrad-style Filibuster Marathon ink cartoon on P24 122888f →