← back to Crazy News Channel
cartoons: 6 new political cartoons for invented politics stories, story_id-linked (TK-12138)
daf7ea3f86829f4670831de9609d0ec06bfb56f9 · 2026-09-24 10:11:16 -0700 · Steve Abrams
- The Meter That Wouldn't Yield (pw-meter-street): a parking meter's honors escalate from street-naming to a council seat to a re-election bid to a one-day mayoralty it refuses to vacate.
- Row by Row Only (pw-corn-etiquety): Kansas legislates the one true corn-eating method, deputizes Corn Marshals, and bans cob holders as cheating devices.
- The Gnome Who Wouldn't Hush (pw-lawn-gnome-tax): a loud lawn gnome gets registered, ankle-monitored, sued over, and finally reviewed by a Gnome Parole Board.
- Four Minutes Ahead of Everyone (pw-clock-secession): a town clock set 4 minutes fast escalates into its own named time zone and a secession petition from Mountain Time.
- The Casserole Succession (pw-bean-queen): a ceremonial Bean Queen title gets term-limited, the sash is contested, and a courthouse rules succession passes by casserole, not bloodline.
- The Hedge Non-Aggression Pact (pw-hedge-statue): a statue-shaped hedge draws a height limit, a fine, an interstate espionage accusation, and a formal treaty with Idaho.
Each cartoon is a single-panel inline SVG scene with a Plot Twist button that cycles through the story's own 5-headline escalation arc, concluding cleanly at the final stage (no loop-back). Dark mode, keyboard nav (N/arrow/Enter/Space), focus-visible outlines, and prefers-reduced-motion all match the commission-turtles template. manifest.js gains an additive story_id field on every entry (null on the pre-existing commission-turtles cartoon) linking each cartoon to its source invented-politics story for category-view lookup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKaojLnVW8hyxsVriY5f9a
Files touched
A cartoons/20260924-casserole-succession.htmlA cartoons/20260924-four-minutes-ahead.htmlA cartoons/20260924-gnome-who-wouldnt-hush.htmlA cartoons/20260924-hedge-non-aggression-pact.htmlA cartoons/20260924-meter-that-wouldnt-yield.htmlA cartoons/20260924-row-by-row-only.htmlM cartoons/manifest.js
Diff
commit daf7ea3f86829f4670831de9609d0ec06bfb56f9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 24 10:11:16 2026 -0700
cartoons: 6 new political cartoons for invented politics stories, story_id-linked (TK-12138)
- The Meter That Wouldn't Yield (pw-meter-street): a parking meter's honors escalate from street-naming to a council seat to a re-election bid to a one-day mayoralty it refuses to vacate.
- Row by Row Only (pw-corn-etiquety): Kansas legislates the one true corn-eating method, deputizes Corn Marshals, and bans cob holders as cheating devices.
- The Gnome Who Wouldn't Hush (pw-lawn-gnome-tax): a loud lawn gnome gets registered, ankle-monitored, sued over, and finally reviewed by a Gnome Parole Board.
- Four Minutes Ahead of Everyone (pw-clock-secession): a town clock set 4 minutes fast escalates into its own named time zone and a secession petition from Mountain Time.
- The Casserole Succession (pw-bean-queen): a ceremonial Bean Queen title gets term-limited, the sash is contested, and a courthouse rules succession passes by casserole, not bloodline.
- The Hedge Non-Aggression Pact (pw-hedge-statue): a statue-shaped hedge draws a height limit, a fine, an interstate espionage accusation, and a formal treaty with Idaho.
Each cartoon is a single-panel inline SVG scene with a Plot Twist button that cycles through the story's own 5-headline escalation arc, concluding cleanly at the final stage (no loop-back). Dark mode, keyboard nav (N/arrow/Enter/Space), focus-visible outlines, and prefers-reduced-motion all match the commission-turtles template. manifest.js gains an additive story_id field on every entry (null on the pre-existing commission-turtles cartoon) linking each cartoon to its source invented-politics story for category-view lookup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKaojLnVW8hyxsVriY5f9a
---
cartoons/20260924-casserole-succession.html | 218 ++++++++++++++++++++++
cartoons/20260924-four-minutes-ahead.html | 215 +++++++++++++++++++++
cartoons/20260924-gnome-who-wouldnt-hush.html | 224 ++++++++++++++++++++++
cartoons/20260924-hedge-non-aggression-pact.html | 214 +++++++++++++++++++++
cartoons/20260924-meter-that-wouldnt-yield.html | 221 ++++++++++++++++++++++
cartoons/20260924-row-by-row-only.html | 227 +++++++++++++++++++++++
cartoons/manifest.js | 63 ++++++-
7 files changed, 1381 insertions(+), 1 deletion(-)
diff --git a/cartoons/20260924-casserole-succession.html b/cartoons/20260924-casserole-succession.html
new file mode 100644
index 0000000..17c689c
--- /dev/null
+++ b/cartoons/20260924-casserole-succession.html
@@ -0,0 +1,218 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>The Casserole Succession — P24 Political Cartoon Desk</title>
+<meta name="description" content="An original invented-satire cartoon: Savannah imposes term limits on its ceremonial Bean Queen, the sash won't come off, and a courthouse finally settles it." />
+<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:640px;margin:0 auto;padding:18px 20px}
+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:3/2;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:.92rem;color:var(--ink);padding:12px;border-top:1px solid var(--ink-dim)}
+.dock{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:16px 0;font-family:-apple-system,sans-serif}
+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(--seal);background:var(--seal);color:#241c14}
+button.twist: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:640px;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; } }
+</style>
+</head>
+<body>
+<header>
+ <div>
+ <div class="kicker">P24 Political Cartoon Desk · Invented Satire · Savannah, GA</div>
+ <h1>The Casserole Succession</h1>
+ <p class="dek">Savannah's council is debating whether to impose term limits on the city's ceremonial "Bean Queen" title, a bean-casserole honor that has never had one.</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>
+ <figure>
+ <div class="phead"><span>Chatham Co. Ceremonial Registry</span><span id="stamp"></span></div>
+ <svg class="art" id="art" viewBox="0 0 480 320" role="img" tabindex="0" aria-label="Editorial cartoon about the Bean Queen succession dispute"></svg>
+ <figcaption id="cap"></figcaption>
+ </figure>
+ <div class="dock">
+ <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 / → advance the story · Enter/Space on the art also advances</p>
+</main>
+<footer>All officials, courts, and casseroles are invented. No actual bean dish was deposed. 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 txt(svg, x, y, size, weight, fill, content, anchor){
+ const t = el('text', { x, y, 'text-anchor': anchor||'middle', 'font-family':'-apple-system,sans-serif', 'font-size':size, 'font-weight':weight||'400', fill: fill||'currentColor' }, svg);
+ t.textContent = content;
+ return t;
+ }
+
+ 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);
+ 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);
+ if (label) txt(svg, cx, cy + r + 16, 11, '400', 'var(--ink-dim)', label);
+ return g;
+ }
+
+ function casserole(svg, cx, cy, s, crowned, sashed){
+ const g = el('g', {}, svg);
+ el('ellipse', { cx, cy: cy+s*0.1, rx: s*0.42, ry: s*0.22, fill: '#8a5a2b' }, g);
+ el('ellipse', { cx, cy: cy+s*0.02, rx: s*0.42, ry: s*0.22, fill: '#c9a227' }, g);
+ for (let i=0;i<6;i++){
+ const bx = cx - s*0.3 + i*(s*0.6/5);
+ el('circle', { cx: bx, cy: cy - s*0.02 + (i%2)*4, r: s*0.045, fill: '#b5302f' }, g);
+ }
+ if (sashed) {
+ el('path', { d: `M ${cx-s*0.34} ${cy-s*0.14} L ${cx+s*0.3} ${cy+s*0.2}`, stroke: '#b5302f', 'stroke-width': 8, opacity: .85 }, g);
+ txt(svg, cx, cy+s*0.42, 9, '800', '#fff', 'BEAN QUEEN');
+ el('rect', { x: cx-s*0.24, y: cy+s*0.32, width: s*0.48, height: s*0.14, rx: 3, fill: '#b5302f' }, g);
+ txt(svg, cx, cy+s*0.42, 9, '800', '#fff', 'BEAN QUEEN');
+ }
+ if (crowned) {
+ const cr = el('g', { transform: `translate(${cx},${cy-s*0.24})` }, g);
+ el('path', { d: 'M -16 6 L -16 -10 L -6 2 L 0 -14 L 6 2 L 16 -10 L 16 6 Z', fill: 'var(--seal)' }, cr);
+ }
+ return g;
+ }
+
+ const HEADLINES = [
+ "Savannah Council to Debate Term Limits for Ceremonial 'Bean Queen' Title",
+ "Council Imposes Two-Year Term Limit on Bean Queen After 'Dynasty' Concerns",
+ "Ousted Bean Queen Refuses to Relinquish Sash, Cites 'Unconstitutional' Term Limit",
+ 'Bean Queen Succession Dispute Reaches Chatham County Courthouse',
+ 'Court Rules Bean Queen Title Passes by Casserole, Not Bloodline, Ending Whitfield Reign'
+ ];
+ const STAMPS = ['DEBATED', 'LIMITED', 'CONTESTED', 'IN COURT', 'RESOLVED'];
+
+ const state = { stage: 0 };
+
+ function scene(svg, stage){
+ svg.innerHTML = '';
+ if (stage === 0) {
+ casserole(svg, 150, 200, 200, true, true);
+ official(svg, 380, 220, 28, '#0f6b52', 'Councilmember Trask');
+ txt(svg, 150, 300, 12, '700', 'currentColor', 'A ceremonial title, never once term-limited.');
+ txt(svg, 350, 90, 11, '400', 'var(--ink-dim)', '"How long can a casserole reign?"');
+ } else if (stage === 1) {
+ casserole(svg, 200, 200, 190, true, true);
+ const stamp = el('g', { transform: 'translate(360,140) rotate(-12)' }, svg);
+ el('circle', { cx: 0, cy: 0, r: 40, fill: 'none', stroke: 'var(--accent2)', 'stroke-width': 5 }, stamp);
+ txt(svg, 360, 136, 10, '800', 'var(--accent2)', '2-YEAR');
+ txt(svg, 360, 150, 10, '800', 'var(--accent2)', 'LIMIT');
+ } else if (stage === 2) {
+ casserole(svg, 190, 200, 200, true, true);
+ const bubble = el('g', {}, svg);
+ el('path', { d: 'M 300 80 h 140 v 56 h -60 l -16 20 v -20 h -64 z', fill: 'var(--panel)', stroke: 'var(--accent)', 'stroke-width': 3 }, bubble);
+ txt(svg, 370, 102, 10.5, '700', 'var(--accent)', '"UNCONSTITUTIONAL');
+ txt(svg, 370, 116, 10.5, '700', 'var(--accent)', 'AND IN BAD TASTE."');
+ } else if (stage === 3) {
+ el('rect', { x: 320, y: 70, width: 10, height: 200, fill: 'var(--rule)' }, svg);
+ for (let i=0;i<3;i++){ el('rect', { x: 300+i*14, y: 70, width: 8, height: 200, fill: 'var(--rule)', opacity: .6 - i*0.15 }, svg); }
+ txt(svg, 400, 100, 10, '700', 'currentColor', 'CHATHAM CO.');
+ txt(svg, 400, 115, 9, '400', 'var(--ink-dim)', 'COURTHOUSE');
+ casserole(svg, 160, 220, 190, true, true);
+ official(svg, 410, 220, 24, '#6b4fa0', 'Presiding Judge');
+ } else if (stage === 4) {
+ casserole(svg, 150, 210, 150, false, false);
+ txt(svg, 150, 290, 10, '400', 'var(--ink-dim)', 'former sash, retired');
+ casserole(svg, 340, 200, 190, true, true);
+ const gavel = el('g', { transform: 'translate(240,90) rotate(-18)' }, svg);
+ el('rect', { x: -5, y: -26, width: 10, height: 38, rx: 3, fill: '#8a5a2b' }, gavel);
+ el('rect', { x: -18, y: -40, width: 36, height: 16, rx: 3, fill: '#6b5f4d' }, gavel);
+ txt(svg, 240, 60, 11, '800', 'currentColor', 'RULING: BY CASSEROLE, NOT BLOODLINE');
+ }
+ }
+
+ function say(kind, msg){ const s = document.getElementById('status'); s.className = kind; s.textContent = msg; }
+
+ function paint(){
+ scene(document.getElementById('art'), state.stage);
+ document.getElementById('cap').textContent = HEADLINES[state.stage];
+ document.getElementById('stamp').textContent = STAMPS[state.stage];
+ const btn = document.getElementById('twistBtn');
+ if (state.stage >= HEADLINES.length - 1) { btn.textContent = 'Story Concluded'; btn.disabled = true; }
+ else { btn.textContent = 'Plot Twist!'; btn.disabled = false; }
+ }
+
+ function advance(){
+ if (state.stage >= HEADLINES.length - 1) { say('info', 'The story has concluded. The sash has been reassigned, by casserole.'); return; }
+ state.stage++;
+ paint();
+ say('ok', `Stage ${state.stage + 1} of ${HEADLINES.length}.`);
+ }
+
+ document.getElementById('twistBtn').addEventListener('click', advance);
+ const art = document.getElementById('art');
+ art.addEventListener('click', advance);
+ art.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); advance(); } });
+
+ 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(); advance(); }
+ });
+
+ 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(); });
+
+ paint();
+ say('info', 'Case file open. Press N, click the art, or hit Plot Twist to advance the story.');
+})();
+</script>
+</body>
+</html>
diff --git a/cartoons/20260924-four-minutes-ahead.html b/cartoons/20260924-four-minutes-ahead.html
new file mode 100644
index 0000000..31f3db0
--- /dev/null
+++ b/cartoons/20260924-four-minutes-ahead.html
@@ -0,0 +1,215 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>Four Minutes Ahead of Everyone — P24 Political Cartoon Desk</title>
+<meta name="description" content="An original invented-satire cartoon: Bozeman sets its town clock four minutes fast, declares its own time zone, and petitions to secede from Mountain Time entirely." />
+<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:640px;margin:0 auto;padding:18px 20px}
+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:3/2;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:.92rem;color:var(--ink);padding:12px;border-top:1px solid var(--ink-dim)}
+.dock{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:16px 0;font-family:-apple-system,sans-serif}
+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(--seal);background:var(--seal);color:#241c14}
+button.twist: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:640px;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; } }
+</style>
+</head>
+<body>
+<header>
+ <div>
+ <div class="kicker">P24 Political Cartoon Desk · Invented Satire · Bozeman, MT</div>
+ <h1>Four Minutes Ahead of Everyone</h1>
+ <p class="dek">Bozeman's city council is debating whether to permanently reset the town clock four minutes ahead of standard time in hopes of nudging residents toward punctuality.</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>
+ <figure>
+ <div class="phead"><span>Council Docket, Timekeeping</span><span id="stamp"></span></div>
+ <svg class="art" id="art" viewBox="0 0 480 320" role="img" tabindex="0" aria-label="Editorial cartoon about Bozeman's town clock breaking away from Mountain Time"></svg>
+ <figcaption id="cap"></figcaption>
+ </figure>
+ <div class="dock">
+ <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 / → advance the story · Enter/Space on the art also advances</p>
+</main>
+<footer>All officials, counties, and clocks are invented. No actual time zone 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;
+ }
+ function txt(svg, x, y, size, weight, fill, content, anchor){
+ const t = el('text', { x, y, 'text-anchor': anchor||'middle', 'font-family':'-apple-system,sans-serif', 'font-size':size, 'font-weight':weight||'400', fill: fill||'currentColor' }, svg);
+ t.textContent = content;
+ return t;
+ }
+
+ 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);
+ 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);
+ if (label) txt(svg, cx, cy + r + 16, 11, '400', 'var(--ink-dim)', label);
+ return g;
+ }
+
+ function clockTower(svg, cx, cy, s, fastMinutes, color){
+ const g = el('g', {}, svg);
+ el('rect', { x: cx-s*0.08, y: cy+s*0.1, width: s*0.16, height: s*0.7, fill: color, opacity: .55 }, g);
+ el('circle', { cx, cy, r: s*0.32, fill: 'var(--panel)', stroke: color, 'stroke-width': 5 }, g);
+ for (let i=0;i<12;i++){
+ const a = (i/12)*2*Math.PI;
+ el('line', { x1: cx+Math.sin(a)*s*0.26, y1: cy-Math.cos(a)*s*0.26, x2: cx+Math.sin(a)*s*0.29, y2: cy-Math.cos(a)*s*0.29, stroke: color, 'stroke-width': 2 }, g);
+ }
+ const minAngle = (fastMinutes/60)*2*Math.PI;
+ el('line', { x1: cx, y1: cy, x2: cx+Math.sin(minAngle)*s*0.24, y2: cy-Math.cos(minAngle)*s*0.24, stroke: color, 'stroke-width': 3, 'stroke-linecap':'round' }, g);
+ el('line', { x1: cx, y1: cy, x2: cx, y2: cy-s*0.15, stroke: color, 'stroke-width': 4, 'stroke-linecap':'round' }, g);
+ return g;
+ }
+
+ const HEADLINES = [
+ 'Bozeman Council Debates Setting Town Clock Four Minutes Fast',
+ 'Council Votes to Run Town Clock Permanently Four Minutes Ahead of Standard Time',
+ "Bozeman Declares Its Own 'Bozeman Standard Time,' Four Minutes Off Mountain Time",
+ "Neighboring County Refuses to Recognize Bozeman Standard Time, Cites 'Scheduling Chaos'",
+ 'Bozeman Petitions to Secede From Mountain Time Zone Entirely'
+ ];
+ const STAMPS = ['DEBATED', 'ADOPTED', 'DECLARED', 'DISPUTED', 'PETITIONED'];
+
+ const state = { stage: 0 };
+
+ function scene(svg, stage){
+ svg.innerHTML = '';
+ if (stage === 0) {
+ clockTower(svg, 150, 170, 220, 0, '#0f6b52');
+ const arrow = el('g', {}, svg);
+ el('path', { d: 'M 220 90 q 30 -6 46 6', fill:'none', stroke:'var(--seal)', 'stroke-width':3, 'marker-end':'none' }, arrow);
+ txt(svg, 280, 78, 11, '700', 'var(--seal)', '+4 MIN?');
+ official(svg, 380, 220, 28, '#b5302f', 'Councilmember Rask');
+ txt(svg, 150, 300, 12, '700', 'currentColor', 'A four-minute nudge toward punctuality.');
+ } else if (stage === 1) {
+ clockTower(svg, 200, 170, 220, 4, '#0f6b52');
+ const gavel = el('g', { transform: 'translate(340,150) rotate(-20)' }, svg);
+ el('rect', { x: -6, y: -30, width: 12, height: 44, rx: 4, fill: '#8a5a2b' }, gavel);
+ el('rect', { x: -22, y: -46, width: 44, height: 18, rx: 4, fill: '#6b5f4d' }, gavel);
+ txt(svg, 200, 300, 12, '700', 'var(--accent2)', 'Adopted, permanent, +4 minutes.');
+ } else if (stage === 2) {
+ clockTower(svg, 220, 170, 220, 4, '#0f6b52');
+ el('rect', { x: 300, y: 130, width: 150, height: 70, rx: 6, fill: 'var(--panel)', stroke: 'var(--rule)', 'stroke-width': 3 }, svg);
+ txt(svg, 375, 155, 11, '800', 'currentColor', 'BOZEMAN');
+ txt(svg, 375, 170, 10, '800', 'var(--seal)', 'STANDARD TIME');
+ txt(svg, 375, 186, 9, '400', 'var(--ink-dim)', 'MT + 4 minutes');
+ } else if (stage === 3) {
+ el('rect', { x: 250, y: 60, width: 6, height: 220, fill: 'var(--rule)', opacity: .5 }, svg);
+ txt(svg, 258, 74, 9, '700', 'var(--ink-dim)', 'COUNTY LINE', 'start');
+ clockTower(svg, 150, 170, 200, 4, '#0f6b52');
+ official(svg, 350, 190, 32, '#b5302f', 'Gallatin Co. Clerk');
+ txt(svg, 350, 250, 11, '700', 'var(--accent)', '"Scheduling chaos!"');
+ for (let i=0;i<3;i++){
+ el('path', { d: `M ${300+i*16} 90 l 8 14 l 8 -14`, fill:'none', stroke:'var(--accent)', 'stroke-width':2 }, svg);
+ }
+ } else if (stage === 4) {
+ clockTower(svg, 170, 170, 230, 4, '#0f6b52');
+ el('rect', { x: 280, y: 60, width: 6, height: 220, fill: 'var(--rule)' }, svg);
+ const flag = el('g', { transform: 'translate(200,90)' }, svg);
+ el('rect', { x: 0, y: 0, width: 4, height: 60, fill: '#6b5f4d' }, flag);
+ el('path', { d: 'M 4 0 l 34 10 l -34 10 z', fill: 'var(--seal)' }, flag);
+ txt(svg, 170, 300, 12, '800', 'currentColor', 'Petition filed: secession from Mountain Time.');
+ official(svg, 400, 220, 28, '#6b4fa0', 'Federal Timekeeping Office');
+ }
+ }
+
+ function say(kind, msg){ const s = document.getElementById('status'); s.className = kind; s.textContent = msg; }
+
+ function paint(){
+ scene(document.getElementById('art'), state.stage);
+ document.getElementById('cap').textContent = HEADLINES[state.stage];
+ document.getElementById('stamp').textContent = STAMPS[state.stage];
+ const btn = document.getElementById('twistBtn');
+ if (state.stage >= HEADLINES.length - 1) { btn.textContent = 'Story Concluded'; btn.disabled = true; }
+ else { btn.textContent = 'Plot Twist!'; btn.disabled = false; }
+ }
+
+ function advance(){
+ if (state.stage >= HEADLINES.length - 1) { say('info', 'The story has concluded. Bozeman remains four minutes ahead of everyone.'); return; }
+ state.stage++;
+ paint();
+ say('ok', `Stage ${state.stage + 1} of ${HEADLINES.length}.`);
+ }
+
+ document.getElementById('twistBtn').addEventListener('click', advance);
+ const art = document.getElementById('art');
+ art.addEventListener('click', advance);
+ art.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); advance(); } });
+
+ 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(); advance(); }
+ });
+
+ 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(); });
+
+ paint();
+ say('info', 'Case file open. Press N, click the art, or hit Plot Twist to advance the story.');
+})();
+</script>
+</body>
+</html>
diff --git a/cartoons/20260924-gnome-who-wouldnt-hush.html b/cartoons/20260924-gnome-who-wouldnt-hush.html
new file mode 100644
index 0000000..d1a5f7f
--- /dev/null
+++ b/cartoons/20260924-gnome-who-wouldnt-hush.html
@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>The Gnome Who Wouldn't Hush — P24 Political Cartoon Desk</title>
+<meta name="description" content="An original invented-satire cartoon: Duluth taxes loud lawn gnomes, registers them, ankle-monitors repeat offenders, and finally convenes a Gnome Parole Board." />
+<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:640px;margin:0 auto;padding:18px 20px}
+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:3/2;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:.92rem;color:var(--ink);padding:12px;border-top:1px solid var(--ink-dim)}
+.dock{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:16px 0;font-family:-apple-system,sans-serif}
+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(--seal);background:var(--seal);color:#241c14}
+button.twist: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:640px;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; } }
+</style>
+</head>
+<body>
+<header>
+ <div>
+ <div class="kicker">P24 Political Cartoon Desk · Invented Satire · Duluth, MN</div>
+ <h1>The Gnome Who Wouldn't Hush</h1>
+ <p class="dek">Duluth officials are weighing a new annual fee on unusually loud lawn ornaments after residents complained about a rise in singing gnomes and motion-triggered flamingos.</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>
+ <figure>
+ <div class="phead"><span>Ordinance 12-G, Noise & Ornaments</span><span id="stamp"></span></div>
+ <svg class="art" id="art" viewBox="0 0 480 320" role="img" tabindex="0" aria-label="Editorial cartoon about a lawn gnome's escalating brushes with city hall"></svg>
+ <figcaption id="cap"></figcaption>
+ </figure>
+ <div class="dock">
+ <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 / → advance the story · Enter/Space on the art also advances</p>
+</main>
+<footer>All officials, boards, and gnomes are invented. No actual lawn ornament was detained. 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 txt(svg, x, y, size, weight, fill, content, anchor){
+ const t = el('text', { x, y, 'text-anchor': anchor||'middle', 'font-family':'-apple-system,sans-serif', 'font-size':size, 'font-weight':weight||'400', fill: fill||'currentColor' }, svg);
+ t.textContent = content;
+ return t;
+ }
+
+ 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);
+ 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);
+ if (label) txt(svg, cx, cy + r + 16, 11, '400', 'var(--ink-dim)', label);
+ return g;
+ }
+
+ function gnome(svg, cx, cy, s, monitor){
+ const g = el('g', {}, svg);
+ el('path', { d: `M ${cx-s*0.3} ${cy+s*0.3} Q ${cx} ${cy-s*0.15} ${cx+s*0.3} ${cy+s*0.3} Z`, fill: '#0f6b52' }, g);
+ el('circle', { cx, cy: cy-s*0.05, r: s*0.16, fill: '#e8c9a0' }, g);
+ el('path', { d: `M ${cx-s*0.16} ${cy-s*0.18} Q ${cx} ${cy-s*0.55} ${cx+s*0.16} ${cy-s*0.18} Z`, fill: '#b5302f' }, g);
+ el('circle', { cx: cx-s*0.4, cy: cy+s*0.34, r: s*0.06, fill: '#e8c9a0' }, g);
+ el('circle', { cx: cx+s*0.4, cy: cy+s*0.34, r: s*0.06, fill: '#e8c9a0' }, g);
+ el('ellipse', { cx, cy: cy+s*0.42, rx: s*0.1, ry: s*0.05, fill: '#8a5a2b' }, g);
+ if (monitor) {
+ el('rect', { x: cx-s*0.12, y: cy+s*0.36, width: s*0.24, height: s*0.09, rx: 3, fill: 'var(--accent)' }, g);
+ el('circle', { cx: cx+s*0.12, cy: cy+s*0.4, r: 3, fill: '#fff' }, g);
+ }
+ return g;
+ }
+
+ const HEADLINES = [
+ "Duluth Considers 'Silence Tax' on Excessively Loud Lawn Ornaments",
+ 'Council Approves Gnome Registry to Track Noise-Emitting Lawn Decor',
+ 'City Begins Issuing Ankle Monitors for Repeat Noise-Violation Gnomes',
+ "Gnome Owners Sue City, Claim Ankle Monitors Violate Ornament 'Dignity'",
+ 'Duluth Establishes Gnome Parole Board to Review Ankle-Monitor Appeals'
+ ];
+ const STAMPS = ['PROPOSED', 'REGISTERED', 'MONITORED', 'IN LITIGATION', 'ON APPEAL'];
+
+ const state = { stage: 0 };
+
+ function noteBurst(svg, cx, cy){
+ for (let i=0;i<3;i++){
+ const x = cx + 18 + i*16, y = cy - 30 - i*14;
+ const g = el('g', { transform: `translate(${x},${y})` }, svg);
+ el('circle', { cx:0, cy:0, r:4.5, fill:'var(--accent)' }, g);
+ el('rect', { x:3.5, y:-16, width:2.4, height:18, fill:'var(--accent)' }, g);
+ }
+ }
+
+ function scene(svg, stage){
+ svg.innerHTML = '';
+ if (stage === 0) {
+ el('rect', { x: 60, y: 260, width: 360, height: 10, fill: 'currentColor', opacity: .3, rx: 3 }, svg);
+ gnome(svg, 150, 220, 160, false);
+ noteBurst(svg, 150, 200);
+ official(svg, 380, 230, 30, '#0f6b52', 'Alderperson Nyquist');
+ txt(svg, 150, 60, 14, '700', 'currentColor', '"How loud is TOO loud, gnomally speaking?"');
+ } else if (stage === 1) {
+ gnome(svg, 200, 220, 150, false);
+ const tag = el('g', {}, svg);
+ el('rect', { x: 330, y: 150, width: 100, height: 60, rx: 6, fill: 'var(--panel)', stroke: 'var(--rule)', 'stroke-width': 3 }, tag);
+ txt(svg, 380, 172, 10, '700', 'currentColor', 'GNOME REGISTRY');
+ txt(svg, 380, 190, 16, '800', 'var(--seal)', '#0412');
+ txt(svg, 380, 204, 8.5, '400', 'var(--ink-dim)', 'noise class: II');
+ el('path', { d: 'M 330 180 L 260 210', stroke: 'var(--ink-dim)', 'stroke-width': 2, 'stroke-dasharray':'3 3' }, svg);
+ } else if (stage === 2) {
+ gnome(svg, 220, 220, 160, true);
+ txt(svg, 220, 300, 12, '700', 'var(--accent)', 'Second citation this season.');
+ official(svg, 380, 240, 26, '#c9a227', 'Enforcement Officer');
+ } else if (stage === 3) {
+ el('rect', { x: 340, y: 90, width: 8, height: 160, fill: 'var(--rule)' }, svg);
+ el('rect', { x: 300, y: 90, width: 160, height: 40, fill: 'none', stroke: 'var(--rule)', 'stroke-width': 3 }, svg);
+ txt(svg, 380, 115, 10, '700', 'currentColor', 'ST. LOUIS CO.');
+ txt(svg, 380, 130, 9, '400', 'var(--ink-dim)', 'DISTRICT COURT');
+ gnome(svg, 170, 220, 150, true);
+ const banner = el('g', {}, svg);
+ el('rect', { x: 60, y: 90, width: 210, height: 30, rx: 4, fill: 'var(--accent2)' }, banner);
+ txt(svg, 165, 110, 11, '700', '#fff', '"DIGNITY IS NOT OPTIONAL"');
+ } else if (stage === 4) {
+ el('rect', { x: 60, y: 90, width: 360, height: 12, fill: 'currentColor', opacity: .4, rx: 3 }, svg);
+ official(svg, 130, 130, 26, '#0f6b52', 'Board Chair');
+ official(svg, 240, 130, 26, '#b5302f', 'Board Member');
+ official(svg, 350, 130, 26, '#6b4fa0', 'Board Member');
+ txt(svg, 240, 65, 12, '700', 'currentColor', 'GNOME PAROLE BOARD · IN SESSION');
+ gnome(svg, 240, 250, 150, true);
+ txt(svg, 240, 305, 11, '400', 'var(--ink-dim)', 'Case No. 114: petition for ankle-monitor removal.');
+ }
+ }
+
+ function say(kind, msg){ const s = document.getElementById('status'); s.className = kind; s.textContent = msg; }
+
+ function paint(){
+ scene(document.getElementById('art'), state.stage);
+ document.getElementById('cap').textContent = HEADLINES[state.stage];
+ document.getElementById('stamp').textContent = STAMPS[state.stage];
+ const btn = document.getElementById('twistBtn');
+ if (state.stage >= HEADLINES.length - 1) { btn.textContent = 'Story Concluded'; btn.disabled = true; }
+ else { btn.textContent = 'Plot Twist!'; btn.disabled = false; }
+ }
+
+ function advance(){
+ if (state.stage >= HEADLINES.length - 1) { say('info', 'The story has concluded. The board is still deliberating, quietly.'); return; }
+ state.stage++;
+ paint();
+ say('ok', `Stage ${state.stage + 1} of ${HEADLINES.length}.`);
+ }
+
+ document.getElementById('twistBtn').addEventListener('click', advance);
+ const art = document.getElementById('art');
+ art.addEventListener('click', advance);
+ art.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); advance(); } });
+
+ 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(); advance(); }
+ });
+
+ 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(); });
+
+ paint();
+ say('info', 'Case file open. Press N, click the art, or hit Plot Twist to advance the story.');
+})();
+</script>
+</body>
+</html>
diff --git a/cartoons/20260924-hedge-non-aggression-pact.html b/cartoons/20260924-hedge-non-aggression-pact.html
new file mode 100644
index 0000000..c0f0ac5
--- /dev/null
+++ b/cartoons/20260924-hedge-non-aggression-pact.html
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>The Hedge Non-Aggression Pact — P24 Political Cartoon Desk</title>
+<meta name="description" content="An original invented-satire cartoon: Washington regulates statue-shaped hedges, fines a homeowner, gets accused of hedge espionage, and signs a topiary treaty with Idaho." />
+<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:640px;margin:0 auto;padding:18px 20px}
+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:3/2;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:.92rem;color:var(--ink);padding:12px;border-top:1px solid var(--ink-dim)}
+.dock{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:16px 0;font-family:-apple-system,sans-serif}
+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(--seal);background:var(--seal);color:#241c14}
+button.twist: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:640px;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; } }
+</style>
+</head>
+<body>
+<header>
+ <div>
+ <div class="kicker">P24 Political Cartoon Desk · Invented Satire · Spokane, WA</div>
+ <h1>The Hedge Non-Aggression Pact</h1>
+ <p class="dek">Washington lawmakers are proposing a height limit on hedges trimmed to resemble statues after complaints that some now overshadow actual public monuments.</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>
+ <figure>
+ <div class="phead"><span>SB 41-T, Topiary Height & Likeness</span><span id="stamp"></span></div>
+ <svg class="art" id="art" viewBox="0 0 480 320" role="img" tabindex="0" aria-label="Editorial cartoon about a topiary hedge escalating into an interstate treaty"></svg>
+ <figcaption id="cap"></figcaption>
+ </figure>
+ <div class="dock">
+ <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 / → advance the story · Enter/Space on the art also advances</p>
+</main>
+<footer>All officials, hedges, and territorial governors are invented. No actual shrubbery was extradited. 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 txt(svg, x, y, size, weight, fill, content, anchor){
+ const t = el('text', { x, y, 'text-anchor': anchor||'middle', 'font-family':'-apple-system,sans-serif', 'font-size':size, 'font-weight':weight||'400', fill: fill||'currentColor' }, svg);
+ t.textContent = content;
+ return t;
+ }
+
+ 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);
+ 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);
+ if (label) txt(svg, cx, cy + r + 16, 11, '400', 'var(--ink-dim)', label);
+ return g;
+ }
+
+ function hedge(svg, cx, cy, s, arm){
+ const g = el('g', {}, svg);
+ el('rect', { x: cx-s*0.22, y: cy+s*0.3, width: s*0.44, height: s*0.5, fill: '#0f6b52' }, g);
+ el('circle', { cx, cy: cy, r: s*0.24, fill: '#12805f' }, g);
+ el('circle', { cx: cx-s*0.18, cy: cy-s*0.06, r: s*0.16, fill: '#12805f' }, g);
+ el('circle', { cx: cx+s*0.18, cy: cy-s*0.06, r: s*0.16, fill: '#12805f' }, g);
+ el('circle', { cx, cy: cy-s*0.34, r: s*0.14, fill: '#0d5c46' }, g);
+ if (arm === 'wave') {
+ el('rect', { x: cx+s*0.2, y: cy-s*0.15, width: s*0.14, height: s*0.42, rx: 8, fill: '#0d5c46', transform: `rotate(-25 ${cx+s*0.2} ${cy+s*0.05})` }, g);
+ }
+ return g;
+ }
+
+ const HEADLINES = [
+ 'Washington Lawmakers Propose Height Limit on Hedges Shaped Like Statues',
+ 'Hedge Height Bill Passes, Requires Permits for Topiary Resembling a Historical Figure',
+ 'State Fines Spokane Homeowner Over Unpermitted Topiary Resembling Territorial Governor',
+ "Neighboring State Accuses Washington of 'Hedge Espionage' Over Topiary Resembling Its Governor",
+ 'Washington and Idaho Sign Formal Hedge Non-Aggression Pact'
+ ];
+ const STAMPS = ['PROPOSED', 'PERMITTED', 'FINED', 'ACCUSED', 'RATIFIED'];
+
+ const state = { stage: 0 };
+
+ function scene(svg, stage){
+ svg.innerHTML = '';
+ if (stage === 0) {
+ el('rect', { x: 40, y: 260, width: 400, height: 10, fill: 'currentColor', opacity: .3, rx: 3 }, svg);
+ hedge(svg, 150, 190, 220, false);
+ const ruler = el('g', {}, svg);
+ el('line', { x1: 260, y1: 80, x2: 260, y2: 260, stroke: 'var(--seal)', 'stroke-width': 3, 'stroke-dasharray':'4 4' }, ruler);
+ txt(svg, 275, 90, 10.5, '700', 'var(--seal)', 'HEIGHT LIMIT?', 'start');
+ official(svg, 390, 220, 26, '#0f6b52', 'State Rep. Ohlsen');
+ } else if (stage === 1) {
+ hedge(svg, 180, 190, 210, false);
+ el('rect', { x: 300, y: 130, width: 140, height: 66, rx: 6, fill: 'var(--panel)', stroke: 'var(--rule)', 'stroke-width': 3 }, svg);
+ txt(svg, 370, 154, 10, '700', 'currentColor', 'TOPIARY PERMIT');
+ txt(svg, 370, 172, 9, '400', 'var(--ink-dim)', 'likeness: historical figure');
+ txt(svg, 370, 186, 9, '400', 'var(--ink-dim)', 'status: PENDING REVIEW');
+ } else if (stage === 2) {
+ hedge(svg, 170, 190, 220, false);
+ const stamp = el('g', { transform: 'translate(360,140) rotate(-14)' }, svg);
+ el('circle', { cx: 0, cy: 0, r: 40, fill: 'none', stroke: 'var(--accent)', 'stroke-width': 5 }, stamp);
+ txt(svg, 360, 148, 15, '800', 'var(--accent)', 'FINED');
+ txt(svg, 170, 300, 11, '400', 'var(--ink-dim)', 'Unpermitted likeness: a territorial governor.');
+ } else if (stage === 3) {
+ el('rect', { x: 260, y: 70, width: 6, height: 210, fill: 'var(--rule)', opacity: .5 }, svg);
+ txt(svg, 270, 84, 9, '700', 'var(--ink-dim)', 'STATE LINE', 'start');
+ hedge(svg, 150, 190, 210, false);
+ official(svg, 370, 200, 30, '#b5302f', 'Neighboring Delegate');
+ const bin = el('g', { transform: 'translate(340,140)' }, svg);
+ el('rect', { x: -18, y: -10, width: 36, height: 18, rx: 3, fill: '#241c14' }, bin);
+ el('circle', { cx: -8, cy: 0, r: 6, fill: 'none', stroke: '#f2ead9', 'stroke-width': 2.4 }, bin);
+ el('circle', { cx: 8, cy: 0, r: 6, fill: 'none', stroke: '#f2ead9', 'stroke-width': 2.4 }, bin);
+ txt(svg, 340, 175, 10, '700', 'var(--accent)', '"Hedge espionage!"');
+ } else if (stage === 4) {
+ hedge(svg, 150, 190, 190, 'wave');
+ hedge(svg, 330, 190, 190, 'wave');
+ el('rect', { x: 205, y: 250, width: 70, height: 44, rx: 4, fill: 'var(--panel)', stroke: 'var(--rule)', 'stroke-width': 2.5 }, svg);
+ txt(svg, 240, 268, 8.5, '700', 'currentColor', 'HEDGE');
+ txt(svg, 240, 280, 8.5, '700', 'currentColor', 'TREATY');
+ txt(svg, 240, 70, 12, '800', 'currentColor', 'WA and ID Hedge Non-Aggression Pact, signed.');
+ }
+ }
+
+ function say(kind, msg){ const s = document.getElementById('status'); s.className = kind; s.textContent = msg; }
+
+ function paint(){
+ scene(document.getElementById('art'), state.stage);
+ document.getElementById('cap').textContent = HEADLINES[state.stage];
+ document.getElementById('stamp').textContent = STAMPS[state.stage];
+ const btn = document.getElementById('twistBtn');
+ if (state.stage >= HEADLINES.length - 1) { btn.textContent = 'Story Concluded'; btn.disabled = true; }
+ else { btn.textContent = 'Plot Twist!'; btn.disabled = false; }
+ }
+
+ function advance(){
+ if (state.stage >= HEADLINES.length - 1) { say('info', 'The story has concluded. Peace in our time, hedge-wise.'); return; }
+ state.stage++;
+ paint();
+ say('ok', `Stage ${state.stage + 1} of ${HEADLINES.length}.`);
+ }
+
+ document.getElementById('twistBtn').addEventListener('click', advance);
+ const art = document.getElementById('art');
+ art.addEventListener('click', advance);
+ art.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); advance(); } });
+
+ 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(); advance(); }
+ });
+
+ 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(); });
+
+ paint();
+ say('info', 'Case file open. Press N, click the art, or hit Plot Twist to advance the story.');
+})();
+</script>
+</body>
+</html>
diff --git a/cartoons/20260924-meter-that-wouldnt-yield.html b/cartoons/20260924-meter-that-wouldnt-yield.html
new file mode 100644
index 0000000..2c029ee
--- /dev/null
+++ b/cartoons/20260924-meter-that-wouldnt-yield.html
@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>The Meter That Wouldn't Yield — P24 Political Cartoon Desk</title>
+<meta name="description" content="An original invented-satire cartoon: Erie's most reliable parking meter is honored, seated, re-elected, and sworn in as honorary mayor — and then won't leave the office." />
+<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:640px;margin:0 auto;padding:18px 20px}
+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:3/2;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:.92rem;color:var(--ink);padding:12px;border-top:1px solid var(--ink-dim)}
+.dock{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:16px 0;font-family:-apple-system,sans-serif}
+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(--seal);background:var(--seal);color:#241c14}
+button.twist: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:640px;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; } }
+</style>
+</head>
+<body>
+<header>
+ <div>
+ <div class="kicker">P24 Political Cartoon Desk · Invented Satire · Erie, PA</div>
+ <h1>The Meter That Wouldn't Yield</h1>
+ <p class="dek">Erie's city council will vote Thursday on renaming a stretch of Elm Street after a parking meter that officials credit with decades of uneventful, reliable quarter collection.</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>
+ <figure>
+ <div class="phead"><span>Case File 114-B</span><span id="stamp"></span></div>
+ <svg class="art" id="art" viewBox="0 0 480 320" role="img" tabindex="0" aria-label="Editorial cartoon about a parking meter's civic career"></svg>
+ <figcaption id="cap"></figcaption>
+ </figure>
+ <div class="dock">
+ <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 / → advance the story · Enter/Space on the art also advances</p>
+</main>
+<footer>All officials, meters, and city halls are invented. No actual parking meter holds elected office. 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 txt(svg, x, y, size, weight, fill, content, anchor){
+ const t = el('text', { x, y, 'text-anchor': anchor||'middle', 'font-family':'-apple-system,sans-serif', 'font-size':size, 'font-weight':weight||'400', fill: fill||'currentColor' }, svg);
+ t.textContent = content;
+ return t;
+ }
+
+ 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);
+ 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);
+ if (label) txt(svg, cx, cy + r + 16, 11, '400', 'var(--ink-dim)', label);
+ return g;
+ }
+
+ function meter(svg, cx, cy, s, color){
+ const g = el('g', {}, svg);
+ el('rect', { x: cx-4, y: cy+s*0.2, width: 8, height: s*0.6, fill: color, opacity: .6 }, g);
+ el('rect', { x: cx-16, y: cy+s*0.76, width: 32, height: 8, rx: 2, fill: color, opacity: .6 }, g);
+ el('rect', { x: cx-s*0.3, y: cy-s*0.55, width: s*0.6, height: s*0.75, rx: 10, fill: color }, g);
+ el('circle', { cx, cy: cy-s*0.16, r: s*0.2, fill: 'var(--panel)', stroke: color, 'stroke-width': 3 }, g);
+ el('line', { x1: cx, y1: cy-s*0.16, x2: cx+s*0.13, y2: cy-s*0.3, stroke: color, 'stroke-width': 2.4, 'stroke-linecap':'round' }, g);
+ el('rect', { x: cx-6, y: cy-s*0.46, width: 12, height: 5, rx: 1.5, fill: 'var(--panel)' }, g);
+ return g;
+ }
+
+ const HEADLINES = [
+ 'Erie Council to Consider Naming Street After Longtime Parking Meter',
+ 'Council Votes to Grant Parking Meter Honorary Key to the City',
+ 'Parking Meter Granted Non-Voting Seat on City Council',
+ "Meter No. 114 Announces Re-Election Bid, Faces Attack Ads Over 'Nickel Leniency'",
+ 'Sworn In as One-Day Honorary Mayor, Parking Meter Refuses to Vacate Office'
+ ];
+ const STAMPS = ['PROPOSED', 'HONORED', 'SEATED', 'CAMPAIGNING', 'HOLDOVER'];
+
+ const state = { stage: 0 };
+
+ function scene(svg, stage){
+ svg.innerHTML = '';
+ if (stage === 0) {
+ el('line', { x1: 40, y1: 260, x2: 440, y2: 260, stroke: 'currentColor', 'stroke-width': 3, opacity: .3 }, svg);
+ el('rect', { x: 330, y: 150, width: 6, height: 110, fill: 'var(--ink-dim)' }, svg);
+ el('rect', { x: 300, y: 128, width: 68, height: 26, rx: 3, fill: 'var(--accent2)' }, svg);
+ txt(svg, 334, 145, 11, '700', '#fff', 'ELM ST');
+ meter(svg, 150, 230, 130, '#0f6b52');
+ txt(svg, 150, 60, 15, '700', 'currentColor', '"A street named for reliable service."');
+ official(svg, 380, 250, 26, '#b5302f', 'Councilmember Voss');
+ } else if (stage === 1) {
+ meter(svg, 240, 230, 150, '#0f6b52');
+ const key = el('g', { transform: 'translate(240,80) rotate(20)' }, svg);
+ el('circle', { cx: 0, cy: 0, r: 14, fill: 'none', stroke: 'var(--seal)', 'stroke-width': 5 }, key);
+ el('rect', { x: -3, y: 10, width: 6, height: 34, fill: 'var(--seal)' }, key);
+ el('rect', { x: -3, y: 34, width: 14, height: 6, fill: 'var(--seal)' }, key);
+ el('rect', { x: -3, y: 44, width: 10, height: 6, fill: 'var(--seal)' }, key);
+ txt(svg, 240, 150, 12, '700', 'var(--seal)', 'HONORARY KEY TO THE CITY');
+ official(svg, 100, 250, 24, '#c9a227', 'Deputy Envoy Fitch');
+ official(svg, 380, 250, 24, '#6b4fa0', 'Acting Liaison Okafor');
+ } else if (stage === 2) {
+ el('rect', { x: 60, y: 210, width: 360, height: 14, fill: 'currentColor', opacity: .45, rx: 3 }, svg);
+ meter(svg, 240, 175, 120, '#0f6b52');
+ official(svg, 120, 200, 30, '#b5302f', 'Chairperson');
+ official(svg, 360, 200, 30, '#c9a227', 'Deputy Envoy Fitch');
+ el('rect', { x: 180, y: 235, width: 120, height: 24, rx: 3, fill: 'var(--panel)', stroke: 'var(--rule)', 'stroke-width': 2 }, svg);
+ txt(svg, 240, 251, 10.5, '700', 'currentColor', 'METER NO. 114 · NON-VOTING');
+ } else if (stage === 3) {
+ meter(svg, 150, 220, 140, '#0f6b52');
+ el('rect', { x: 60, y: 90, width: 190, height: 34, rx: 4, fill: 'var(--accent2)' }, svg);
+ txt(svg, 155, 112, 13, '800', '#fff', 'RE-ELECT METER 114');
+ const bubble = el('g', {}, svg);
+ el('path', { d: 'M 320 90 h 130 v 60 h -70 l -18 22 v -22 h -42 z', fill: 'var(--panel)', stroke: 'var(--accent)', 'stroke-width': 3 }, bubble);
+ txt(svg, 385, 115, 11, '700', 'var(--accent)', '"NICKEL"');
+ txt(svg, 385, 130, 11, '700', 'var(--accent)', 'LENIENCY?!"');
+ official(svg, 400, 240, 26, '#b5302f', 'Rival Candidate');
+ } else if (stage === 4) {
+ el('rect', { x: 330, y: 60, width: 14, height: 220, fill: 'var(--rule)' }, svg);
+ el('rect', { x: 330, y: 60, width: 130, height: 220, fill: 'none', stroke: 'var(--rule)', 'stroke-width': 6 }, svg);
+ el('rect', { x: 355, y: 150, width: 12, height: 12, fill: 'var(--accent)' }, svg);
+ el('path', { d: 'M 361 156 L 300 60', stroke: 'var(--accent)', 'stroke-width': 3, 'stroke-dasharray':'4 3' }, svg);
+ meter(svg, 240, 220, 150, '#0f6b52');
+ el('path', { d: 'M 190 175 q 50 -18 100 0 l -6 14 q -44 -14 -88 0 z', fill: 'var(--seal)' }, svg);
+ txt(svg, 240, 170, 10, '800', '#241c14', 'HONORARY MAYOR · DAY 1');
+ official(svg, 420, 250, 22, '#c9a227', 'Building Security');
+ txt(svg, 420, 285, 9.5, '400', 'var(--ink-dim)', 'knocking, politely');
+ }
+ txt(svg, 240, 305, 12, '700', 'currentColor', HEADLINES[stage]);
+ }
+
+ function say(kind, msg){ const s = document.getElementById('status'); s.className = kind; s.textContent = msg; }
+
+ function paint(){
+ scene(document.getElementById('art'), state.stage);
+ document.getElementById('cap').textContent = HEADLINES[state.stage];
+ document.getElementById('stamp').textContent = STAMPS[state.stage];
+ const btn = document.getElementById('twistBtn');
+ if (state.stage >= HEADLINES.length - 1) { btn.textContent = 'Story Concluded'; btn.disabled = true; }
+ else { btn.textContent = 'Plot Twist!'; btn.disabled = false; }
+ }
+
+ function advance(){
+ if (state.stage >= HEADLINES.length - 1) { say('info', 'The story has concluded. Meter 114 is not going anywhere.'); return; }
+ state.stage++;
+ paint();
+ say('ok', `Stage ${state.stage + 1} of ${HEADLINES.length}.`);
+ }
+
+ document.getElementById('twistBtn').addEventListener('click', advance);
+ const art = document.getElementById('art');
+ art.addEventListener('click', advance);
+ art.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); advance(); } });
+
+ 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(); advance(); }
+ });
+
+ 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(); });
+
+ paint();
+ say('info', 'Case file open. Press N, click the art, or hit Plot Twist to advance the story.');
+})();
+</script>
+</body>
+</html>
diff --git a/cartoons/20260924-row-by-row-only.html b/cartoons/20260924-row-by-row-only.html
new file mode 100644
index 0000000..e5ffe2e
--- /dev/null
+++ b/cartoons/20260924-row-by-row-only.html
@@ -0,0 +1,227 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>Row by Row Only — P24 Political Cartoon Desk</title>
+<meta name="description" content="An original invented-satire cartoon: Kansas legislates the one true way to eat corn on the cob, deputizes Corn Marshals, and bans the cheating devices known as cob holders." />
+<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:640px;margin:0 auto;padding:18px 20px}
+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:3/2;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:.92rem;color:var(--ink);padding:12px;border-top:1px solid var(--ink-dim)}
+.dock{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:16px 0;font-family:-apple-system,sans-serif}
+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(--seal);background:var(--seal);color:#241c14}
+button.twist: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:640px;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; } }
+</style>
+</head>
+<body>
+<header>
+ <div>
+ <div class="kicker">P24 Political Cartoon Desk · Invented Satire · Topeka, KS</div>
+ <h1>Row by Row Only</h1>
+ <p class="dek">Kansas lawmakers are considering legislation that would establish a single official method for eating corn on the cob at state-funded events, citing years of inconsistent technique.</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>
+ <figure>
+ <div class="phead"><span>House Bill, Agriculture Etiquette</span><span id="stamp"></span></div>
+ <svg class="art" id="art" viewBox="0 0 480 320" role="img" tabindex="0" aria-label="Editorial cartoon about Kansas regulating corn-on-the-cob eating technique"></svg>
+ <figcaption id="cap"></figcaption>
+ </figure>
+ <div class="dock">
+ <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 / → advance the story · Enter/Space on the art also advances</p>
+</main>
+<footer>All officials, marshals, and cobs are invented. No actual corn was disciplined. 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 txt(svg, x, y, size, weight, fill, content, anchor){
+ const t = el('text', { x, y, 'text-anchor': anchor||'middle', 'font-family':'-apple-system,sans-serif', 'font-size':size, 'font-weight':weight||'400', fill: fill||'currentColor' }, svg);
+ t.textContent = content;
+ return t;
+ }
+
+ 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);
+ 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);
+ if (label) txt(svg, cx, cy + r + 16, 11, '400', 'var(--ink-dim)', label);
+ return g;
+ }
+
+ function corn(svg, cx, cy, s, color, bites){
+ const g = el('g', {}, svg);
+ el('ellipse', { cx, cy, rx: s*0.24, ry: s*0.62, fill: color }, g);
+ const rows = 9;
+ for (let i=0;i<rows;i++){
+ const y = cy - s*0.55 + (i*(s*1.1/rows));
+ el('ellipse', { cx: cx - s*0.13, cy: y, rx: s*0.05, ry: s*0.045, fill: 'var(--panel)', opacity: .8 }, g);
+ el('ellipse', { cx: cx + s*0.13, cy: y, rx: s*0.05, ry: s*0.045, fill: 'var(--panel)', opacity: .8 }, g);
+ }
+ el('rect', { x: cx-6, y: cy+s*0.6, width: 12, height: s*0.22, fill: '#6b5f4d' }, g);
+ if (bites === 'freestyle') {
+ for (let i=0;i<5;i++){
+ const y = cy - s*0.3 + i*(s*0.28);
+ el('circle', { cx: cx + (i%2===0?-s*0.1:s*0.16), cy: y, r: s*0.09, fill: 'var(--bg)' }, g);
+ }
+ } else if (bites === 'row') {
+ el('rect', { x: cx-s*0.28, y: cy-s*0.1, width: s*0.56, height: s*0.16, fill: 'var(--bg)' }, g);
+ }
+ return g;
+ }
+
+ const HEADLINES = [
+ 'Kansas Lawmakers Weigh Bill Setting Official Corn-on-the-Cob Eating Method',
+ "Corn Etiquette Bill Advances, Adds Fines for 'Freestyle Gnawing'",
+ 'State to Certify Corn Marshals to Monitor Eating Technique at Public Events',
+ 'Corn Marshals Granted Authority to Confiscate Improperly Eaten Cobs',
+ "Kansas Declares Statewide 'Row-by-Row Only' Zone, Bans Cob Holders as 'Cheating Devices'"
+ ];
+ const STAMPS = ['PROPOSED', 'FINED', 'CERTIFIED', 'CONFISCATED', 'BANNED'];
+
+ const state = { stage: 0 };
+
+ function scene(svg, stage){
+ svg.innerHTML = '';
+ if (stage === 0) {
+ el('rect', { x: 170, y: 250, width: 140, height: 12, fill: 'currentColor', opacity: .4, rx: 3 }, svg);
+ corn(svg, 240, 190, 140, '#c9a227', null);
+ official(svg, 100, 240, 28, '#0f6b52', 'State Rep. Calloway');
+ official(svg, 380, 240, 28, '#b5302f', 'State Rep. Deeks');
+ txt(svg, 240, 60, 14, '700', 'currentColor', '"One method. For everyone. Forever."');
+ } else if (stage === 1) {
+ corn(svg, 200, 190, 150, '#c9a227', 'freestyle');
+ el('g', { transform: 'translate(340,140) rotate(-14)' }, svg);
+ const stamp = el('g', { transform: 'translate(340,150) rotate(-14)' }, svg);
+ el('rect', { x: -46, y: -20, width: 92, height: 40, rx: 4, fill: 'none', stroke: 'var(--accent)', 'stroke-width': 5 }, stamp);
+ txt(svg, 340, 156, 15, '800', 'var(--accent)', 'FINED');
+ txt(svg, 200, 300, 11, '700', 'currentColor', 'Freestyle gnawing: $40 civil penalty.');
+ } else if (stage === 2) {
+ corn(svg, 340, 190, 130, '#c9a227', 'row');
+ official(svg, 150, 200, 40, '#0f6b52', null);
+ const badge = el('g', {}, svg);
+ el('rect', { x: 118, y: 150, width: 64, height: 30, rx: 4, fill: 'var(--seal)', stroke: 'var(--rule)', 'stroke-width': 2 }, badge);
+ txt(svg, 150, 170, 10.5, '800', '#241c14', 'CORN');
+ txt(svg, 150, 182, 8, '800', '#241c14', 'MARSHAL');
+ txt(svg, 150, 265, 11, '400', 'var(--ink-dim)', 'Certified, first graduating class.');
+ } else if (stage === 3) {
+ official(svg, 130, 190, 38, '#0f6b52', 'Corn Marshal');
+ corn(svg, 320, 210, 120, '#c9a227', 'freestyle');
+ el('path', { d: 'M 170 190 L 280 210', stroke: 'currentColor', 'stroke-width': 3, 'stroke-dasharray':'2 4', opacity:.6 }, svg);
+ el('g', { transform: 'translate(320,90)' }, svg);
+ const cuff = el('g', { transform: 'translate(320,90)' }, svg);
+ el('circle', { cx: -14, cy: 0, r: 12, fill: 'none', stroke: 'var(--accent)', 'stroke-width': 4 }, cuff);
+ el('circle', { cx: 14, cy: 0, r: 12, fill: 'none', stroke: 'var(--accent)', 'stroke-width': 4 }, cuff);
+ el('line', { x1: -2, y1: 0, x2: 2, y2: 0, stroke: 'var(--accent)', 'stroke-width': 4 }, cuff);
+ txt(svg, 320, 260, 11, '700', 'currentColor', 'Evidence, Improperly Eaten, Exhibit C');
+ } else if (stage === 4) {
+ corn(svg, 200, 190, 150, '#c9a227', 'row');
+ const banned = el('g', { transform: 'translate(370,150)' }, svg);
+ el('rect', { x: -34, y: -8, width: 68, height: 16, rx: 6, fill: '#8a7a52' }, banned);
+ el('rect', { x: -30, y: -8, width: 8, height: 34, fill: '#8a7a52' }, banned);
+ el('rect', { x: 22, y: -8, width: 8, height: 34, fill: '#8a7a52' }, banned);
+ el('circle', { cx: 0, cy: 8, r: 34, fill: 'none', stroke: 'var(--accent)', 'stroke-width': 5 }, banned);
+ el('line', { x1: -24, y1: -16, x2: 24, y2: 32, stroke: 'var(--accent)', 'stroke-width': 5 }, banned);
+ txt(svg, 370, 215, 11, '700', 'var(--accent)', 'COB HOLDERS: CHEATING DEVICE');
+ txt(svg, 200, 300, 12, '700', 'currentColor', 'Statewide Row-by-Row-Only Zone now in effect.');
+ }
+ }
+
+ function say(kind, msg){ const s = document.getElementById('status'); s.className = kind; s.textContent = msg; }
+
+ function paint(){
+ scene(document.getElementById('art'), state.stage);
+ document.getElementById('cap').textContent = HEADLINES[state.stage];
+ document.getElementById('stamp').textContent = STAMPS[state.stage];
+ const btn = document.getElementById('twistBtn');
+ if (state.stage >= HEADLINES.length - 1) { btn.textContent = 'Story Concluded'; btn.disabled = true; }
+ else { btn.textContent = 'Plot Twist!'; btn.disabled = false; }
+ }
+
+ function advance(){
+ if (state.stage >= HEADLINES.length - 1) { say('info', 'The story has concluded. Row-by-row is now the law of the land.'); return; }
+ state.stage++;
+ paint();
+ say('ok', `Stage ${state.stage + 1} of ${HEADLINES.length}.`);
+ }
+
+ document.getElementById('twistBtn').addEventListener('click', advance);
+ const art = document.getElementById('art');
+ art.addEventListener('click', advance);
+ art.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); advance(); } });
+
+ 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(); advance(); }
+ });
+
+ 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(); });
+
+ paint();
+ say('info', 'Case file open. Press N, click the art, or hit Plot Twist to advance the story.');
+})();
+</script>
+</body>
+</html>
diff --git a/cartoons/manifest.js b/cartoons/manifest.js
index 1cb78d6..06264d3 100644
--- a/cartoons/manifest.js
+++ b/cartoons/manifest.js
@@ -12,6 +12,67 @@ window.P24_CARTOONS = [
"created_at": "2026-09-24T17:05:00Z",
"category": "Political Cartoon",
"blurb": "A blue-ribbon commission recommends forming a commission to study its own recommendation. It keeps going.",
- "thumb": null
+ "thumb": null,
+ "story_id": null
+ },
+ {
+ "id": "meter-that-wouldnt-yield",
+ "title": "The Meter That Wouldn't Yield",
+ "file": "20260924-meter-that-wouldnt-yield.html",
+ "created_at": "2026-09-24T17:10:26Z",
+ "category": "Political Cartoon",
+ "blurb": "Erie's most reliable parking meter is honored, seated on the council, re-elected, and sworn in as one-day mayor — and now won't leave the office.",
+ "thumb": null,
+ "story_id": "pw-meter-street"
+ },
+ {
+ "id": "row-by-row-only",
+ "title": "Row by Row Only",
+ "file": "20260924-row-by-row-only.html",
+ "created_at": "2026-09-24T17:10:26Z",
+ "category": "Political Cartoon",
+ "blurb": "Kansas legislates the one true method for eating corn on the cob, deputizes Corn Marshals, and bans cob holders as cheating devices.",
+ "thumb": null,
+ "story_id": "pw-corn-etiquette"
+ },
+ {
+ "id": "gnome-who-wouldnt-hush",
+ "title": "The Gnome Who Wouldn't Hush",
+ "file": "20260924-gnome-who-wouldnt-hush.html",
+ "created_at": "2026-09-24T17:10:26Z",
+ "category": "Political Cartoon",
+ "blurb": "Duluth taxes loud lawn gnomes, registers them, ankle-monitors repeat offenders, and finally convenes a Gnome Parole Board.",
+ "thumb": null,
+ "story_id": "pw-lawn-gnome-tax"
+ },
+ {
+ "id": "four-minutes-ahead",
+ "title": "Four Minutes Ahead of Everyone",
+ "file": "20260924-four-minutes-ahead.html",
+ "created_at": "2026-09-24T17:10:26Z",
+ "category": "Political Cartoon",
+ "blurb": "Bozeman sets its town clock four minutes fast, declares its own time zone, and petitions to secede from Mountain Time entirely.",
+ "thumb": null,
+ "story_id": "pw-clock-secession"
+ },
+ {
+ "id": "casserole-succession",
+ "title": "The Casserole Succession",
+ "file": "20260924-casserole-succession.html",
+ "created_at": "2026-09-24T17:10:26Z",
+ "category": "Political Cartoon",
+ "blurb": "Savannah imposes term limits on its ceremonial Bean Queen, the sash won't come off, and a courthouse finally settles it by casserole, not bloodline.",
+ "thumb": null,
+ "story_id": "pw-bean-queen"
+ },
+ {
+ "id": "hedge-non-aggression-pact",
+ "title": "The Hedge Non-Aggression Pact",
+ "file": "20260924-hedge-non-aggression-pact.html",
+ "created_at": "2026-09-24T17:10:26Z",
+ "category": "Political Cartoon",
+ "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"
}
];
← 0839cdd Category view: V4 Newspaper Column dense listing + cartoon-t
·
back to Crazy News Channel
·
Add P24 cartoon: Who Gets the Last Slice (budget pie-slicing e79e251 →