← back to Crazy News Channel
cartoons/20260924-the-zipper-that-ended-an-inning.html
171 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Zipper That Ended an Inning — P24 Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: a minor league mascot's costume zipper failure escalates into a league-wide mascot-safety bureaucracy." />
<link rel="stylesheet" href="../assets/style.css" />
</head>
<body data-mood="sports" class="cartoon-page">
<header>
<div>
<div class="kicker">P24 Cartoon Desk · Invented Satire · Chillicothe, OH</div>
<h1>The Zipper That Ended an Inning</h1>
<p class="dek">A minor-league mascot's zipper fails mid-dance, and the fallout escalates through a reinforced costume, an 11-point checklist, and a league-wide Mascot Integrity Officer.</p>
</div>
<div class="header-actions">
<a class="back" href="index.html">← Cartoon Desk</a>
</div>
</header>
<main>
<figure>
<div class="phead"><span>Mascot Wardrobe Incident, Rock Falls Loggers</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 minor league mascot costume malfunction escalating into league-wide rules"></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 teams, mascots, and officials are invented. No actual chipmunk lost his head permanently. 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 chipmunkHead(svg, cx, cy, s, color, detached){
const g = el('g', {}, svg);
el('circle', { cx, cy, r: s*0.42, fill: color, 'fill-opacity': .2, stroke: color, 'stroke-width': 3 }, g);
el('ellipse', { cx: cx - s*0.28, cy: cy - s*0.34, rx: s*0.12, ry: s*0.18, fill: color }, g);
el('ellipse', { cx: cx + s*0.28, cy: cy - s*0.34, rx: s*0.12, ry: s*0.18, fill: color }, g);
el('circle', { cx: cx - s*0.14, cy: cy - s*0.04, r: s*0.05, fill: 'currentColor' }, g);
el('circle', { cx: cx + s*0.14, cy: cy - s*0.04, r: s*0.05, fill: 'currentColor' }, g);
el('ellipse', { cx, cy: cy + s*0.14, rx: s*0.1, ry: s*0.07, fill: '#f2ead9' }, g);
if (detached) el('circle', { cx, cy, r: s*0.42+6, fill:'none', stroke:'var(--accent)', 'stroke-width':2, 'stroke-dasharray':'4 4' }, g);
return g;
}
function chipmunkBody(svg, cx, cy, s, color, headOn){
const g = el('g', {}, svg);
el('ellipse', { cx, cy, rx: s*0.32, ry: s*0.46, fill: color, 'fill-opacity': .2, stroke: color, 'stroke-width': 3 }, g);
el('rect', { x: cx-4, y: cy - s*0.5, width: 8, height: s*0.14, fill: 'var(--accent)', opacity: headOn ? 0.9 : 0.25 }, g);
return g;
}
function bench(svg, x, y, w){
el('rect', { x, y, width: w, height: 8, fill: 'currentColor', opacity: .45, rx: 3 }, svg);
el('rect', { x: x+8, y: y+8, width: 6, height: 20, fill: 'currentColor', opacity: .3 }, svg);
el('rect', { x: x+w-14, y: y+8, width: 6, height: 20, fill: 'currentColor', opacity: .3 }, svg);
}
function checklist(svg, x, y, w, h, n){
el('rect', { x, y, width: w, height: h, rx: 5, fill: 'var(--panel)', stroke: 'var(--focus)', 'stroke-width': 2.4 }, svg);
for (let i=0;i<n;i++){
el('rect', { x: x+10, y: y+10+i*11, width: 8, height: 8, fill:'none', stroke:'currentColor', 'stroke-width':1.4 }, svg);
el('rect', { x: x+24, y: y+13+i*11, width: w-34, height: 3, fill:'currentColor', opacity:.35 }, svg);
}
}
function scene(svg, stage){
svg.innerHTML = '';
el('rect', { x: 40, y: 260, width: 400, height: 10, fill: 'currentColor', opacity: .3, rx: 3 }, svg);
if (stage === 0) {
chipmunkBody(svg, 150, 210, 130, '#c9812f', false);
bench(svg, 260, 236, 90);
chipmunkHead(svg, 305, 216, 90, '#c9812f', true);
txt(svg, 240, 40, 12, '700', 'currentColor', '"Right as Chuck was doing the dance."');
} else if (stage === 1) {
chipmunkBody(svg, 130, 210, 130, '#c9812f', true);
chipmunkHead(svg, 130, 148, 110, '#c9812f', false);
el('path', { d: 'M 100 175 L 100 245', stroke: 'var(--accent2)', 'stroke-width': 3 }, svg);
txt(svg, 330, 90, 10, '700', 'currentColor', 'NEW COSTUME');
txt(svg, 330, 104, 8.5, '400', 'var(--ink-dim)', '$1,200 · zipper guard');
official(svg, 330, 150, 24, '#0f6b52', 'Dana Everhart, backup');
} else if (stage === 2) {
checklist(svg, 250, 60, 170, 130, 8);
txt(svg, 335, 195, 8.5, '700', 'currentColor', '11-POINT CHECKLIST');
chipmunkBody(svg, 110, 210, 130, '#c9812f', true);
chipmunkHead(svg, 110, 148, 110, '#c9812f', true);
official(svg, 110, 260, 18, '#6b4fa0', 'Ray Buskirk, signs off');
} else if (stage === 3) {
chipmunkBody(svg, 100, 220, 100, '#c9812f', true);
chipmunkHead(svg, 100, 168, 86, '#c9812f', true);
chipmunkBody(svg, 220, 220, 100, '#0f6b52', true);
chipmunkHead(svg, 220, 168, 86, '#0f6b52', true);
chipmunkBody(svg, 340, 220, 100, '#b5302f', true);
chipmunkHead(svg, 340, 168, 86, '#b5302f', true);
txt(svg, 240, 40, 11, '700', 'currentColor', 'Every mascot: zipper kit + walkie-talkie, league-wide.');
} else if (stage === 4) {
official(svg, 200, 130, 30, 'var(--accent)', 'Mascot Integrity Officer Sal Untermeyer');
chipmunkBody(svg, 340, 220, 110, '#c9812f', true);
chipmunkHead(svg, 340, 158, 96, '#c9812f', true);
txt(svg, 200, 260, 11, '700', 'var(--accent2)', 'Chuck has not lost his head since.');
}
}
const HEADLINES = ["Loggers Game Delayed Six Minutes After Mascot Costume Zipper Fails", "Loggers Order Reinforced Costume, Add Backup Mascot Performer", "Team Institutes 11-Point Mascot Costume Inspection Checklist Before Games", "League Mandates Every Mascot Carry Emergency Zipper Kit, Walkie-Talkie", "League Creates 'Mascot Integrity Officer' With Power To Halt Games For Costumes"];
const STAMPS = ["DELAYED", "REINFORCED", "CHECKLIST", "LEAGUE MANDATE", "OFFICER NAMED"];
const CONCLUSION_MSG = "The story has concluded. Chuck the Chipmunk, for what it's worth, has not lost his head since.";
const state = { stage: 0 };
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', CONCLUSION_MSG); 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(); }
});
paint();
say('info', 'Case file open. Press N, click the art, or hit Plot Twist to advance the story.');
})();
</script>
</body>
</html>