← back to Crazy News Channel Shadowman
cartoons/20260924-meter-that-wouldnt-yield.html
169 lines
<!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." />
<link rel="stylesheet" href="../assets/style.css" />
</head>
<body data-mood="politics" class="cartoon-page">
<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 class="header-actions">
<a class="back" href="index.html">← Cartoon Desk</a>
</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(); }
});
paint();
say('info', 'Case file open. Press N, click the art, or hit Plot Twist to advance the story.');
})();
</script>
</body>
</html>