[object Object]

← back to Crazy News Channel

Add P24 cartoon: The Pork Barrel Express (invented infrastructure-bill train satire)

bb599b54eb0d7dd811a09713dce553daf4bd87f3 · 2026-09-24 10:26:35 -0700 · Steve Abrams

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

Files touched

Diff

commit bb599b54eb0d7dd811a09713dce553daf4bd87f3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 24 10:26:35 2026 -0700

    Add P24 cartoon: The Pork Barrel Express (invented infrastructure-bill train satire)
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 cartoons/20260924-pork-barrel-train.html | 421 +++++++++++++++++++++++++++++++
 cartoons/manifest.js                     |  10 +
 2 files changed, 431 insertions(+)

diff --git a/cartoons/20260924-pork-barrel-train.html b/cartoons/20260924-pork-barrel-train.html
new file mode 100644
index 0000000..5b2331f
--- /dev/null
+++ b/cartoons/20260924-pork-barrel-train.html
@@ -0,0 +1,421 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>The Pork Barrel Express — P24 Political Cartoon Desk</title>
+<meta name="description" content="An original invented-satire strip: a fictional infrastructure bill turns into a runaway train as wildly unrelated riders couple on car by car. Click a car for its invented cost." />
+<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}
+.car{cursor:pointer}
+.car:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
+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 Pork Barrel Express</h1>
+    <p class="dek">An invented infrastructure bill leaves committee as a single train car and picks up wildly unrelated riders at every stop. Click a car to see what it actually costs.</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; Introduced</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; Riders Couple On</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; Fully Loaded</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 &middot; click a car for its invented cost</p>
+</main>
+<footer>H.B. 4417, the Assembly of Larkspur, every official, town, and train car pictured here are invented. No actual legislature, lighthouse, or ferris wheel was ever billed to a taxpayer. 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 "official" mascots — simple flat iconographic seals, not detailed characters ----
+  const MASCOTS = [
+    { name: 'Comptroller Thistlewood', color: '#b5302f' },
+    { name: 'Assemblymember Grigg', color: '#0f6b52' },
+    { name: 'Track Overseer Pruitt', color: '#c9a227' },
+    { name: 'Whip Delacroix', color: '#6b4fa0' },
+  ];
+  let mascotIdx = 0;
+
+  const BILL_NAME = 'H.B. 4417 — The Interstate Rail Modernization Act';
+  const LEGISLATURE = 'the Assembly of Larkspur';
+
+  // ---- invented cars: the real bill car, riders that couple on by panel, and a rotating pool for twists ----
+  const BASE_CAR   = { name: 'The Interstate Rail Modernization Car (the actual bill)', short: 'RAIL CORE', cost: 40000000 };
+  const RIDERS_2 = [
+    { name: 'Desert Lighthouse Car', short: 'LIGHTHOUSE', cost: 12000000 },
+    { name: 'Ferris Wheel Pavilion Car', short: 'FERRIS WHEEL', cost: 8500000 },
+  ];
+  const RIDERS_3 = [
+    { name: 'Commemorative Spoon Museum Car', short: 'SPOON MUSEUM', cost: 3200000 },
+    { name: 'Heated Sidewalk (Zero-Snowfall Town) Car', short: 'HOT SIDEWALK', cost: 6750000 },
+    { name: 'Bridge to the Island (Location TBD) Car', short: 'BRIDGE TBD', cost: 95000000 },
+  ];
+  const TWIST_POOL = [
+    { name: 'Underwater Petting Zoo Car', short: 'PETTING ZOO', cost: 22000000 },
+    { name: 'Solar-Powered Sundial Car', short: 'SUNDIAL', cost: 1400000 },
+    { name: 'Rolling Cheese Wheel Track Car', short: 'CHEESE TRACK', cost: 4800000 },
+    { name: 'Indoor Snowless Ski Slope Car', short: 'SKI SLOPE', cost: 61000000 },
+    { name: 'Museum of Robotic Roosters Car', short: 'ROBO ROOSTERS', cost: 5600000 },
+    { name: 'Scenic Overlook With No View Car', short: 'NO-VIEW LOOKOUT', cost: 900000 },
+    { name: 'Two-Story Garage for One Car Car', short: '1-CAR GARAGE', cost: 3300000 },
+  ];
+
+  const state = { revealed: 0, twists: 0 };
+  const costState = {}; // key `${panelIndex}:${carIdx}` -> boolean, whether the cost callout is open
+  let focusAfterRender = null; // {panel, idx}
+
+  function twistCars(){
+    const arr = [];
+    for (let t = 0; t < state.twists; t++) arr.push(TWIST_POOL[t % TWIST_POOL.length]);
+    return arr;
+  }
+
+  function carsForPanel(i){
+    if (i === 0) return [BASE_CAR];
+    if (i === 1) return [BASE_CAR, ...RIDERS_2];
+    return [BASE_CAR, ...RIDERS_2, ...RIDERS_3, ...twistCars()];
+  }
+
+  function fmtMoney(n){ return '$' + n.toLocaleString('en-US'); }
+  function fmtMoneyShort(n){ return '$' + Math.round(n / 1e6) + 'M'; }
+  function totalCost(cars){ return cars.reduce((s, c) => s + c.cost, 0); }
+
+  function exasperation(n){
+    if (n <= 3) return 'a modest bill so far';
+    if (n <= 5) return 'already sprawling';
+    if (n <= 8) return 'frankly ridiculous';
+    if (n <= 11) return 'a fiscal cry for help';
+    return 'no longer a train, more of a rolling apology';
+  }
+
+  function captionFor(i){
+    const cars = carsForPanel(i);
+    const total = totalCost(cars);
+    if (i === 0) {
+      return `${BILL_NAME} clears committee in ${LEGISLATURE} with exactly one car: the actual train. Running total: ${fmtMoney(total)}.`;
+    }
+    if (i === 1) {
+      const riders = cars.length - 1;
+      return `Before the ink is dry, ${riders} unrelated rider${riders === 1 ? '' : 's'} couple${riders === 1 ? 's' : ''} on. The train now hauls ${cars.length} cars — running total ${fmtMoney(total)}.`;
+    }
+    const twistNote = state.twists > 0 ? ` Plot twist #${state.twists} just tacked on another car.` : '';
+    return `${cars.length} cars now, running total ${fmtMoney(total)} — ${exasperation(cars.length)}.${twistNote}`;
+  }
+
+  // ---- shared flat iconographic drawing helpers ----
+  function official(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':'10', fill: 'currentColor' }, g);
+      t.textContent = label;
+    }
+    return g;
+  }
+
+  function seal(svg, cx, cy, r, text, color){
+    const g = el('g', { transform: `rotate(-12 ${cx} ${cy})` }, svg);
+    el('circle', { cx, cy, r, fill: 'none', stroke: color, 'stroke-width': 4, 'stroke-dasharray':'4 3' }, g);
+    const t = el('text', { x: cx, y: cy+4, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-weight':'800', 'font-size': Math.max(9, r*0.34), fill: color }, g);
+    t.textContent = text;
+    return g;
+  }
+
+  function track(svg){
+    el('line', { x1:10, y1:230, x2:290, y2:230, stroke:'currentColor', 'stroke-width':3, opacity:0.55 }, svg);
+    for (let x = 14; x <= 286; x += 14) {
+      el('line', { x1:x, y1:227, x2:x-4, y2:236, stroke:'currentColor', 'stroke-width':2, opacity:0.35 }, svg);
+    }
+  }
+
+  function drawEngine(svg, x, y, w, h, color){
+    const g = el('g', {}, svg);
+    el('rect', { x, y, width:w, height:h, rx:6, fill:color, 'fill-opacity':0.22, stroke:color, 'stroke-width':3 }, g);
+    el('rect', { x:x+w*0.55, y:y-14, width:w*0.4, height:14, fill:color, 'fill-opacity':0.32, stroke:color, 'stroke-width':2 }, g);
+    el('rect', { x:x+w*0.14, y:y-11, width:6, height:11, fill:color }, g);
+    el('circle', { cx:x+w*0.28, cy:y+h+6, r:6.5, fill:'currentColor', opacity:0.6 }, g);
+    el('circle', { cx:x+w*0.72, cy:y+h+6, r:6.5, fill:'currentColor', opacity:0.6 }, g);
+  }
+
+  function wrapShort(text, group, cx, y, size){
+    const words = text.split(' ');
+    let line1 = '', line2 = '';
+    let i = 0;
+    while (i < words.length && (line1 + words[i]).length <= 9) { line1 += (line1 ? ' ' : '') + words[i]; i++; }
+    line2 = words.slice(i).join(' ');
+    const t = el('text', { x: cx, y, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-weight':'700', 'font-size': size, fill:'currentColor' }, group);
+    const ts1 = el('tspan', { x: cx, dy: 0 }, t);
+    ts1.textContent = line1;
+    if (line2) {
+      const ts2 = el('tspan', { x: cx, dy: size + 1 }, t);
+      ts2.textContent = line2;
+    }
+  }
+
+  function drawCar(svg, x, y, w, h, car, idx, panelIndex, color, positions){
+    const key = panelIndex + ':' + idx;
+    const g = el('g', {
+      class: 'car', tabindex: '0', role: 'button',
+      'aria-label': `${car.name} — click to reveal its invented cost`,
+      'aria-pressed': costState[key] ? 'true' : 'false',
+      id: `car-${panelIndex}-${idx}`,
+    }, svg);
+    el('rect', { x, y, width:w, height:h, rx:5, fill:color, 'fill-opacity':0.15, stroke:color, 'stroke-width':2.4 }, g);
+    el('circle', { cx:x+w*0.25, cy:y+h+6, r:6, fill:'currentColor', opacity:0.55 }, g);
+    el('circle', { cx:x+w*0.75, cy:y+h+6, r:6, fill:'currentColor', opacity:0.55 }, g);
+    // $ affordance badge, top-right corner of the car — signals "click me"
+    el('circle', { cx:x+w-7, cy:y+7, r:6.5, fill: color }, g);
+    const dt = el('text', { x:x+w-7, y:y+10, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-weight':'800', 'font-size':'8', fill:'var(--panel)' }, g);
+    dt.textContent = '$';
+    const fontSize = Math.max(5.5, Math.min(8, w*0.17));
+    wrapShort(car.short, g, x + w/2, y + h*0.62, fontSize);
+    g.addEventListener('click', (e) => { e.stopPropagation(); toggleCost(panelIndex, idx); });
+    g.addEventListener('keydown', (e) => {
+      if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); toggleCost(panelIndex, idx); }
+    });
+    positions.push({ x, y, w, h, car, idx });
+  }
+
+  function drawCallout(svg, pos, panelIndex){
+    const boxW = 118, boxH = 34;
+    const cx = pos.x + pos.w/2;
+    let bx = cx - boxW/2;
+    bx = Math.max(4, Math.min(300 - boxW - 4, bx));
+    let by = pos.y - boxH - 8;
+    if (by < 4) by = pos.y + pos.h + 16;
+    const g = el('g', { class:'cost-callout', style:'pointer-events:none' }, svg);
+    el('rect', { x:bx, y:by, width:boxW, height:boxH, rx:6, fill:'var(--panel)', stroke:'var(--rule)', 'stroke-width':2 }, g);
+    const t1 = el('text', { x:bx+boxW/2, y:by+14, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'8.5', 'font-weight':'700', fill:'currentColor' }, g);
+    t1.textContent = pos.car.short;
+    const t2 = el('text', { x:bx+boxW/2, y:by+27, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'11', 'font-weight':'800', fill:'var(--accent)' }, g);
+    t2.textContent = fmtMoney(pos.car.cost);
+  }
+
+  function toggleCost(panelIndex, idx){
+    const key = panelIndex + ':' + idx;
+    costState[key] = !costState[key];
+    focusAfterRender = { panel: panelIndex, idx };
+    renderPanel(panelIndex);
+  }
+
+  function drawTrain(svg, panelIndex, cars, mascot){
+    svg.innerHTML = '';
+    el('rect', { x:0, y:0, width:300, height:300, fill:'none' }, svg);
+    track(svg);
+
+    const t = el('text', { x:150, y:14, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'9.5', 'font-weight':'700', fill:'currentColor' }, svg);
+    t.textContent = panelIndex === 0 ? BILL_NAME : `${BILL_NAME} + riders`;
+
+    official(svg, 38, 122, 22, mascot.color, mascot.name);
+
+    const total = totalCost(cars);
+    seal(svg, 252, 66, 26, fmtMoneyShort(total), mascot.color);
+
+    const trackLeft = 14, trackRight = 286;
+    const engineW = 42, gap = 4, h = 46, y = 176;
+    const n = cars.length;
+    const avail = (trackRight - trackLeft) - engineW - gap;
+    const carW = Math.max(18, Math.min(44, avail / n - gap));
+
+    let x = trackLeft;
+    drawEngine(svg, x, y, engineW, h, mascot.color);
+    x += engineW + gap;
+
+    const positions = [];
+    cars.forEach((car, idx) => {
+      const color = idx === 0 ? mascot.color : MASCOTS[(mascotIdx + idx) % MASCOTS.length].color;
+      drawCar(svg, x, y, carW, h, car, idx, panelIndex, color, positions);
+      x += carW + gap;
+    });
+
+    positions.forEach((pos) => {
+      if (costState[panelIndex + ':' + pos.idx]) drawCallout(svg, pos, panelIndex);
+    });
+
+    if (focusAfterRender && focusAfterRender.panel === panelIndex) {
+      const target = document.getElementById(`car-${panelIndex}-${focusAfterRender.idx}`);
+      if (target) target.focus();
+      focusAfterRender = null;
+    }
+  }
+
+  const STAMPS = ['FILED', 'COUPLED', 'LOADED'];
+
+  function renderPanel(i){
+    const svg = document.getElementById('art' + i);
+    const mascot = MASCOTS[(mascotIdx + i) % MASCOTS.length];
+    const cars = carsForPanel(i);
+    drawTrain(svg, i, cars, mascot);
+    document.getElementById('cap' + i).textContent = captionFor(i);
+    document.getElementById('stamp' + i).textContent = i < state.revealed ? STAMPS[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 couple on another car.');
+  }
+
+  function twist(){
+    if (state.revealed < 3) { say('err', 'ERROR RIDER-402 · No twist before the train is fully loaded — process all 3 panels first.'); return; }
+    mascotIdx = (mascotIdx + 1) % MASCOTS.length;
+    state.twists++;
+    renderPanel(0); renderPanel(1); renderPanel(2);
+    const cars = carsForPanel(2);
+    say('ok', `Plot twist #${state.twists}: another rider couples onto the back of the train. ${cars.length} cars now, running total ${fmtMoney(totalCost(cars))}.`);
+  }
+
+  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 6c917a9..c3d7291 100644
--- a/cartoons/manifest.js
+++ b/cartoons/manifest.js
@@ -84,5 +84,15 @@ window.P24_CARTOONS = [
     "blurb": "An invented town's budget pie gets re-sliced smaller across three courses while five invented departments hold their forks; drag the slider and watch who ends up with crumbs.",
     "thumb": null,
     "story_id": null
+  },
+  {
+    "id": "pork-barrel-train",
+    "title": "The Pork Barrel Express",
+    "file": "20260924-pork-barrel-train.html",
+    "created_at": "2026-09-24T22:15:00Z",
+    "category": "Political Cartoon",
+    "blurb": "An invented infrastructure bill leaves committee as a single train car and picks up wildly unrelated riders — a desert lighthouse, a ferris wheel, a spoon museum — at every stop; click a car for its invented cost.",
+    "thumb": null,
+    "story_id": null
   }
 ];

← b0b4970 admin-server: fix 3 verified security/robustness holes  ·  back to Crazy News Channel  ·  P24: add first cartoon for business/entertainment/scitech/sp 3e6ffab →