[object Object]

← back to Crazy News Channel

Add P24 cartoon: Who Gets the Last Slice (budget pie-slicing satire)

e79e2510ee45c28e696e961d888470dd997e357e · 2026-09-24 10:14:39 -0700 · Steve Abrams

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

Files touched

Diff

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

    Add P24 cartoon: Who Gets the Last Slice (budget pie-slicing satire)
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 cartoons/20260924-budget-pie-fight.html | 424 ++++++++++++++++++++++++++++++++
 cartoons/manifest.js                    |  10 +
 2 files changed, 434 insertions(+)

diff --git a/cartoons/20260924-budget-pie-fight.html b/cartoons/20260924-budget-pie-fight.html
new file mode 100644
index 0000000..cae7fad
--- /dev/null
+++ b/cartoons/20260924-budget-pie-fight.html
@@ -0,0 +1,424 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>Who Gets the Last Slice — P24 Political Cartoon Desk</title>
+<meta name="description" content="An original invented-satire strip: an invented town budget is a pie that diners keep re-slicing smaller at a banquet table. Drag the slider to re-carve it and watch the caption rewrite live." />
+<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;position:relative}
+.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;min-height:2.6em}
+.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;width:100%}
+#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)}
+.slider-row{display:flex;flex-direction:column;gap:4px;width:100%;background:var(--panel);border:2px solid var(--rule);border-radius:8px;padding:10px 14px;font-family:-apple-system,sans-serif}
+.slider-row .row1{display:flex;justify-content:space-between;align-items:baseline;gap:8px;flex-wrap:wrap}
+.slider-row label{font-size:.82rem;font-weight:700}
+.slider-row .val{font-size:.82rem;color:var(--ink-dim)}
+.slider-row input[type=range]{width:100%;accent-color:var(--accent)}
+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>Who Gets the Last Slice</h1>
+    <p class="dek">An invented town's budget arrives at the banquet table as one pie. By dessert, five invented departments are still holding forks and the pie has not gotten any bigger.</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; Opening Slices</span><span id="stamp0"></span></div>
+      <svg class="art" id="art0" viewBox="0 0 300 300" role="img" tabindex="0" aria-label="Round 1 pie chart"></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 Re-Carving</span><span id="stamp1"></span></div>
+      <svg class="art" id="art1" viewBox="0 0 300 300" role="img" tabindex="0" aria-label="Round 2 pie chart"></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; Crumbs Only</span><span id="stamp2"></span></div>
+      <svg class="art" id="art2" viewBox="0 0 300 300" role="img" tabindex="0" aria-label="Round 3 pie chart"></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 class="slider-row">
+      <div class="row1">
+        <label for="pieSlider">Re-carve <strong>Municipal Ducts'</strong> slice</label>
+        <span class="val" id="sliderVal">—</span>
+      </div>
+      <input type="range" id="pieSlider" min="0" max="100" value="0" step="1"
+        aria-label="Redistribute the Municipal Ducts department's percentage of the active round's pie"
+        aria-describedby="sliderVal" disabled />
+    </div>
+    <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; drag the slider to re-carve the active round's pie</p>
+</main>
+<footer>All towns, departments, officials, and mascots are invented. No actual municipal budget was harmed. 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;
+  }
+
+  // ---- invented departments (fictional, non-partisan) ----
+  const DEPARTMENTS = [
+    { name: 'Parks & Wonder', color: '#0f6b52' },
+    { name: 'Municipal Ducts', color: '#b5302f' },
+    { name: 'Bureau of Loud Noises', color: '#c9a227' },
+    { name: 'Office of Overflow Parking', color: '#6b4fa0' },
+    { name: 'Dept. of Suggestive Signage', color: '#2f6fb5' },
+  ];
+  const MOD_IDX = 1; // Municipal Ducts — the slider-controlled department
+
+  const ROUNDS = [
+    { reserved: 0, shape: [24,22,20,18,16], title: 'Round 1 — Opening Slices',
+      blurb: 'The pie lands on the table whole. Every mascot gets an honest slice — for now.' },
+    { reserved: 35, shape: [26,16,22,14,22], title: 'Round 2 — The Re-Carving',
+      blurb: 'The table reserves over a third of the pie "for planning purposes." Nobody has seen the plan.' },
+    { reserved: 62, shape: [30,10,24,10,26], title: 'Round 3 — Crumbs Only',
+      blurb: 'The reserved zone now swallows most of the pie. What is left gets fought over in crumbs.' },
+  ];
+
+  const CAPTIONS = ROUNDS.map(r => r.blurb);
+
+  const state = { revealed: 0, twists: 0, sliderOverride: [null, null, null], touched: [false, false, false] };
+
+  function computeSlices(roundIdx, modPct, twistCount){
+    const round = ROUNDS[roundIdx];
+    const shape = round.shape;
+    const othersShapeSum = shape.reduce((s,v,i) => i === MOD_IDX ? s : s + v, 0);
+    const othersTarget = 100 - modPct;
+    let slices = DEPARTMENTS.map((d,i) => {
+      let shapeShare;
+      if (i === MOD_IDX) shapeShare = modPct;
+      else shapeShare = othersShapeSum > 0 ? (shape[i] / othersShapeSum) * othersTarget : 0;
+      const pct = shapeShare / 100 * (100 - round.reserved);
+      return { name: d.name, color: d.color, pct, dept: true };
+    });
+    if (round.reserved > 0) {
+      slices.push({ name: 'Reserved (Off-Limits)', color: '#9a9086', pct: round.reserved, reserved: true });
+    }
+    if (twistCount > 0) {
+      const adminFee = Math.min(45, 10 + 8 * twistCount);
+      const scale = (100 - adminFee) / 100;
+      slices = slices.map(s => ({ ...s, pct: s.pct * scale }));
+      slices.push({ name: 'Administrative Fee', color: '#5a5248', pct: adminFee, admin: true });
+    }
+    return slices;
+  }
+
+  function currentModPct(i){
+    return state.sliderOverride[i] != null ? state.sliderOverride[i] : ROUNDS[i].shape[MOD_IDX];
+  }
+
+  function polar(cx, cy, r, angleDeg){
+    const rad = angleDeg * Math.PI / 180;
+    return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
+  }
+  function arcPath(cx, cy, r, startDeg, endDeg){
+    const start = polar(cx, cy, r, startDeg);
+    const end = polar(cx, cy, r, endDeg);
+    const largeArc = (endDeg - startDeg) > 180 ? 1 : 0;
+    return `M ${cx} ${cy} L ${start.x.toFixed(2)} ${start.y.toFixed(2)} A ${r} ${r} 0 ${largeArc} 1 ${end.x.toFixed(2)} ${end.y.toFixed(2)} Z`;
+  }
+
+  function mascot(svg, cx, cy, r, color, label){
+    const g = el('g', {}, svg);
+    el('circle', { cx, cy, r, fill: color, 'fill-opacity': '0.18', stroke: color, 'stroke-width': 2.5 }, g);
+    el('circle', { cx, cy: cy - r*0.35, r: r*0.3, fill: color }, g); // head
+    el('path', { d: `M ${cx - r*0.5} ${cy + r*0.5} Q ${cx} ${cy + r*0.15} ${cx + r*0.5} ${cy + r*0.5}`, fill: 'none', stroke: color, 'stroke-width': r*0.24, 'stroke-linecap':'round' }, g); // shoulders
+    if (label) {
+      const t = el('text', { x: cx, y: cy + r + 13, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'8.5', fill: 'currentColor' }, g);
+      const words = label.split(' ');
+      const lines = [];
+      let line = '';
+      words.forEach(w => {
+        if ((line + ' ' + w).trim().length > 14) { lines.push(line.trim()); line = w; }
+        else line = (line + ' ' + w).trim();
+      });
+      if (line) lines.push(line);
+      lines.forEach((ln, idx) => {
+        const ts = el('tspan', { x: cx, dy: idx === 0 ? 0 : 10 }, t);
+        ts.textContent = ln;
+      });
+    }
+    return g;
+  }
+
+  function drawScene(svg, roundIdx, modPct, twistCount){
+    svg.innerHTML = '';
+    const cx = 150, cy = 150, r = 62;
+    const round = ROUNDS[roundIdx];
+    const slices = computeSlices(roundIdx, modPct, twistCount);
+
+    const title = el('text', { x:150, y:16, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-weight':'700', 'font-size':'10', fill:'currentColor' }, svg);
+    title.textContent = round.title.toUpperCase();
+
+    el('rect', { x:40, y:238, width:220, height:8, fill:'currentColor', opacity:0.35, rx:2 }, svg); // banquet table edge
+
+    let angle = -90;
+    slices.forEach(s => {
+      const sweep = (s.pct / 100) * 360;
+      if (sweep > 0.2) {
+        const d = arcPath(cx, cy, r, angle, angle + sweep);
+        el('path', { d, fill: s.color, stroke: 'var(--panel)', 'stroke-width': 2,
+          'stroke-dasharray': s.admin ? '3 2' : (s.reserved ? '5 3' : 'none'),
+          opacity: s.admin ? 0.9 : (s.reserved ? 0.6 : 1) }, svg);
+      }
+      const mid = angle + sweep / 2;
+      if (s.dept) {
+        const pos = polar(cx, cy, r + 40, mid);
+        mascot(svg, pos.x, pos.y, 15, s.color, s.name);
+        if (sweep > 14) {
+          const lp = polar(cx, cy, r * 0.58, mid);
+          const lbl = el('text', { x: lp.x, y: lp.y, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-weight':'700', 'font-size':'10', fill:'#fff' }, svg);
+          lbl.textContent = Math.round(s.pct) + '%';
+        }
+      }
+      angle += sweep;
+    });
+
+    el('circle', { cx, cy, r: r + 3, fill:'none', stroke:'currentColor', 'stroke-width':2, opacity:0.35 }, svg);
+
+    let footY = 268;
+    const reserved = slices.find(s => s.reserved);
+    if (reserved) {
+      const rt = el('text', { x:150, y:footY, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'8.5', fill:'var(--ink-dim)' }, svg);
+      rt.textContent = `Reserved (off-limits): ${Math.round(reserved.pct)}%`;
+      footY += 11;
+    }
+    const admin = slices.find(s => s.admin);
+    if (admin) {
+      const at = el('text', { x:150, y:footY, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-weight':'700', 'font-size':'8.5', fill:'var(--accent)' }, svg);
+      at.textContent = `Administrative Fee (phantom): ${Math.round(admin.pct)}%`;
+    }
+  }
+
+  function describeLive(roundIdx, modPct, twistCount){
+    const slices = computeSlices(roundIdx, modPct, twistCount);
+    const mod = slices.find(s => s.dept && s.name === DEPARTMENTS[MOD_IDX].name);
+    const pct = mod.pct.toFixed(1);
+    let flavor;
+    if (mod.pct < 2) flavor = 'gets nothing and a fruit basket';
+    else if (mod.pct < 8) flavor = `gets ${pct}% of the pie and a strongly worded thank-you card`;
+    else if (mod.pct < 20) flavor = `gets a modest ${pct}% slice and has to say it is "plenty"`;
+    else if (mod.pct < 40) flavor = `walks away with a healthy ${pct}% and a photo op`;
+    else flavor = `somehow ends up with ${pct}% of the pie and will not explain how`;
+    let extra = '';
+    const reserved = slices.find(s => s.reserved);
+    if (reserved) extra += ` Meanwhile ${reserved.pct.toFixed(0)}% sits Reserved, untouched.`;
+    const admin = slices.find(s => s.admin);
+    if (admin) extra += ` A phantom ${admin.pct.toFixed(0)}% Administrative Fee has appeared from nowhere and is eating everyone's share.`;
+    return `${DEPARTMENTS[MOD_IDX].name} now ${flavor}.${extra}`;
+  }
+
+  function updateCaption(i){
+    const capEl = document.getElementById('cap' + i);
+    if (state.touched[i] || state.twists > 0) {
+      capEl.textContent = describeLive(i, currentModPct(i), state.twists);
+    } else {
+      capEl.textContent = CAPTIONS[i];
+    }
+  }
+
+  function renderPanel(i){
+    const svg = document.getElementById('art' + i);
+    drawScene(svg, i, currentModPct(i), state.twists);
+    updateCaption(i);
+    document.getElementById('stamp' + i).textContent = i <= state.revealed - 1 ? ['SERVED','CARVED','RATIONED'][i] : '';
+    const cover = document.getElementById('cover' + i);
+    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 activePanelIndex(){
+    return state.revealed > 0 ? Math.min(state.revealed, 3) - 1 : -1;
+  }
+
+  function syncSlider(){
+    const slider = document.getElementById('pieSlider');
+    const valEl = document.getElementById('sliderVal');
+    const idx = activePanelIndex();
+    if (idx < 0) {
+      slider.disabled = true;
+      slider.value = 0;
+      valEl.textContent = '— (process Panel 1 first)';
+      return;
+    }
+    slider.disabled = false;
+    const pct = currentModPct(idx);
+    slider.value = Math.round(pct);
+    valEl.textContent = `Panel ${idx+1}: ${Math.round(pct)}%`;
+  }
+
+  function paintAll(){
+    for (let i=0;i<3;i++) {
+      renderPanel(i);
+      document.getElementById('art'+i).setAttribute('tabindex', i === state.revealed ? '0' : '-1');
+    }
+    syncButtons();
+    syncSlider();
+  }
+
+  function syncButtons(){
+    const nextBtn = document.getElementById('nextBtn');
+    if (state.revealed >= 3) { nextBtn.textContent = 'All 3 rounds carved ✓'; 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 rounds are already on the table. 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);
+    if (state.revealed < 3) renderPanel(state.revealed);
+    say('ok', `Panel ${state.revealed} processed. Drag the slider to re-carve it.`);
+    syncButtons();
+    syncSlider();
+    if (state.revealed >= 3) say('ok', 'All 3 rounds carved. Press Plot Twist! to see who invents a new fee.');
+  }
+
+  function twist(){
+    if (state.revealed < 3) { say('err', 'ERROR PT-409 · Premature veto — carve all 3 rounds first.'); return; }
+    state.twists++;
+    for (let i=0;i<3;i++) renderPanel(i);
+    syncSlider();
+    say('ok', `Plot twist #${state.twists}: a phantom Administrative Fee has appeared and is eating everyone's share.`);
+  }
+
+  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 slider = document.getElementById('pieSlider');
+  slider.addEventListener('input', () => {
+    const idx = activePanelIndex();
+    if (idx < 0) return;
+    const val = Number(slider.value);
+    state.sliderOverride[idx] = val;
+    state.touched[idx] = true;
+    renderPanel(idx);
+    document.getElementById('sliderVal').textContent = `Panel ${idx+1}: ${val}%`;
+    say('info', describeLive(idx, val, state.twists));
+  });
+
+  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 if (theme === 'light') {
+      document.documentElement.setAttribute('data-theme','light');
+      themeBtn.textContent='Dark mode: off'; themeBtn.setAttribute('aria-pressed','false');
+    } else {
+      // auto — remove any override so the prefers-color-scheme media query decides
+      document.documentElement.removeAttribute('data-theme');
+      const osDark = matchMedia('(prefers-color-scheme: dark)').matches;
+      themeBtn.textContent = osDark ? 'Dark mode: on' : 'Dark mode: off';
+      themeBtn.setAttribute('aria-pressed', osDark ? 'true' : 'false');
+    }
+  }
+  applyTheme();
+  themeBtn.addEventListener('click', () => {
+    const osDark = matchMedia('(prefers-color-scheme: dark)').matches;
+    const currentlyDark = theme === 'dark' || (theme === 'auto' && osDark);
+    theme = currentlyDark ? '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', 'The pie has arrived. Press N or click Panel 1 to begin serving.');
+})();
+</script>
+</body>
+</html>
diff --git a/cartoons/manifest.js b/cartoons/manifest.js
index 06264d3..6c917a9 100644
--- a/cartoons/manifest.js
+++ b/cartoons/manifest.js
@@ -74,5 +74,15 @@ window.P24_CARTOONS = [
     "blurb": "Washington regulates statue-shaped hedges, fines a homeowner, gets accused of hedge espionage, and signs a topiary treaty with Idaho.",
     "thumb": null,
     "story_id": "pw-hedge-statue"
+  },
+  {
+    "id": "budget-pie-fight",
+    "title": "Who Gets the Last Slice",
+    "file": "20260924-budget-pie-fight.html",
+    "created_at": "2026-09-24T21:40:00Z",
+    "category": "Political Cartoon",
+    "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
   }
 ];

← daf7ea3 cartoons: 6 new political cartoons for invented politics sto  ·  back to Crazy News Channel  ·  crazy-news-channel: final refresh of real Google News data ( 6504ba2 →