← back to Crazy News Channel Shadowman

cartoons/20260924-gnome-who-wouldnt-hush.html

172 lines

<!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." />
<link rel="stylesheet" href="../assets/style.css" />
</head>
<body data-mood="politics" class="cartoon-page">
<header>
  <div>
    <div class="kicker">P24 Political Cartoon Desk &middot; Invented Satire &middot; 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 class="header-actions">
    <a class="back" href="index.html">&larr; Cartoon Desk</a>
  </div>
</header>
<main>
  <figure>
    <div class="phead"><span>Ordinance 12-G, Noise &amp; 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 / &rarr; advance the story &middot; 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(); }
  });


  paint();
  say('info', 'Case file open. Press N, click the art, or hit Plot Twist to advance the story.');
})();
</script>
</body>
</html>