← 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
A cartoons/20260924-budget-pie-fight.htmlM cartoons/manifest.js
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 · 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">← 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 · 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 · 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 · 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/→ next panel · T plot twist · click a panel to advance · 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 →