← back to Crazy News Channel Shadowman

cartoons/20260924-four-minutes-ahead.html

163 lines

<!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." />
<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; 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 class="header-actions">
    <a class="back" href="index.html">&larr; Cartoon Desk</a>
  </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 / &rarr; advance the story &middot; 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(); }
  });


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