← back to Crazy News Channel

cartoons/20260924-jackalope-custody-battle.html

162 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Jackalope Custody Battle — P24 Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: two Iowa towns feud over which one truly owns the jackalope legend, ending in a statue that commutes between them by flatbed." />
<link rel="stylesheet" href="../assets/style.css" />
</head>
<body data-mood="entertainment" class="cartoon-page">
<header>
  <div>
    <div class="kicker">P24 Cartoon Desk &middot; Invented Satire &middot; Story City, IA</div>
    <h1>The Jackalope Custody Battle</h1>
    <p class="dek">Story City unveils a 14-foot jackalope statue; neighboring Radcliffe claims the legend, and a governor's accord ends with the statue on a biweekly trailer tour.</p>
  </div>
  <div class="header-actions">
    <a class="back" href="index.html">&larr; Cartoon Desk</a>
  </div>
</header>
<main>
  <figure>
    <div class="phead"><span>Jackalope Capital Dispute, Iowa Tourism Board</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 two towns feuding over ownership of a jackalope statue and legend"></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 towns, officials, and jackalopes are invented. No actual statue was subpoenaed. 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 pedestal(svg, cx, y, w){
    el('rect', { x: cx - w/2, y, width: w, height: 26, fill: 'currentColor', opacity: .3, rx: 3 }, svg);
  }
  function jackalope(svg, cx, cy, s, color){
    const g = el('g', {}, svg);
    el('ellipse', { cx, cy, rx: s*0.34, ry: s*0.5, fill: color, 'fill-opacity': .18, stroke: color, 'stroke-width': 3 }, g);
    el('circle', { cx, cy: cy - s*0.55, r: s*0.2, fill: color, 'fill-opacity': .18, stroke: color, 'stroke-width': 3 }, g);
    el('path', { d: `M ${cx-s*0.1} ${cy-s*0.7} Q ${cx-s*0.22} ${cy-s*1.15} ${cx-s*0.06} ${cy-s*1.25}`, fill:'none', stroke: color, 'stroke-width': 3 }, g);
    el('path', { d: `M ${cx+s*0.1} ${cy-s*0.7} Q ${cx+s*0.22} ${cy-s*1.15} ${cx+s*0.06} ${cy-s*1.25}`, fill:'none', stroke: color, 'stroke-width': 3 }, g);
    el('path', { d: `M ${cx-s*0.05} ${cy-s*0.72} L ${cx-s*0.2} ${cy-s*0.95} M ${cx-s*0.05} ${cy-s*0.72} L ${cx+s*0.06} ${cy-s*0.98}`, fill:'none', stroke: color, 'stroke-width': 2.2 }, g);
    el('path', { d: `M ${cx+s*0.05} ${cy-s*0.72} L ${cx+s*0.2} ${cy-s*0.95} M ${cx+s*0.05} ${cy-s*0.72} L ${cx-s*0.06} ${cy-s*0.98}`, fill:'none', stroke: color, 'stroke-width': 2.2 }, g);
    return g;
  }
  function plaque(svg, cx, y, w, label, sub, color){
    el('rect', { x: cx - w/2, y, width: w, height: 40, rx: 5, fill: 'var(--panel)', stroke: color, 'stroke-width': 2.6 }, svg);
    txt(svg, cx, y + 17, 9.5, '800', color, label);
    if (sub) txt(svg, cx, y + 31, 8, '400', 'var(--ink-dim)', sub);
  }

  function scene(svg, stage){
    svg.innerHTML = '';
    if (stage === 0) {
      pedestal(svg, 150, 250, 90);
      jackalope(svg, 150, 220, 130, '#0f6b52');
      plaque(svg, 360, 90, 190, 'STORY CITY, IA', '"quite the photo stop"', 'var(--seal)');
      txt(svg, 150, 40, 12, '700', 'currentColor', '14 ft &middot; $58,000 &middot; 5 months to build');
    } else if (stage === 1) {
      pedestal(svg, 150, 250, 90);
      jackalope(svg, 150, 220, 130, '#0f6b52');
      el('path', { d: 'M 260 70 L 420 70 L 420 130 L 340 130 L 300 150 L 300 130 L 260 130 Z', fill: 'var(--panel)', stroke: 'var(--accent)', 'stroke-width': 2.6 }, svg);
      txt(svg, 340, 92, 9, '700', 'currentColor', '"1890s CLIPPING PROVES');
      txt(svg, 340, 104, 9, '700', 'currentColor', 'IT WAS RADCLIFFE."');
      txt(svg, 340, 118, 8, '400', 'var(--ink-dim)', '&mdash; Clerk Sondra Quist');
    } else if (stage === 2) {
      pedestal(svg, 100, 250, 80);
      jackalope(svg, 100, 222, 110, '#0f6b52');
      plaque(svg, 100, 60, 150, 'STORY CITY RES. 14', 'Jackalope Capital of Iowa', 'var(--seal)');
      pedestal(svg, 360, 250, 80);
      jackalope(svg, 360, 222, 110, '#b5302f');
      plaque(svg, 360, 60, 150, 'RADCLIFFE RES. 9', 'Jackalope Capital of Iowa', 'var(--accent)');
    } else if (stage === 3) {
      el('rect', { x: 150, y: 60, width: 180, height: 60, rx: 6, fill: 'var(--panel)', stroke: 'var(--focus)', 'stroke-width': 3 }, svg);
      txt(svg, 240, 84, 10, '700', 'currentColor', 'IOWA TOURISM BOARD');
      txt(svg, 240, 100, 9, '400', 'var(--ink-dim)', 'reviewing two dueling dossiers');
      pedestal(svg, 90, 250, 70);
      jackalope(svg, 90, 224, 100, '#0f6b52');
      pedestal(svg, 390, 250, 70);
      jackalope(svg, 390, 224, 100, '#b5302f');
    } else if (stage === 4) {
      el('rect', { x: 130, y: 230, width: 220, height: 16, rx: 3, fill: 'currentColor', opacity: .35 }, svg);
      el('rect', { x: 150, y: 200, width: 30, height: 30, fill: 'currentColor', opacity: .5 }, svg);
      el('rect', { x: 300, y: 200, width: 30, height: 30, fill: 'currentColor', opacity: .5 }, svg);
      jackalope(svg, 240, 190, 120, '#0f6b52');
      official(svg, 100, 150, 22, 'var(--focus)', 'State Trooper');
      txt(svg, 240, 40, 12, '700', 'currentColor', 'Biweekly handoff, trooper escort included.');
    }
  }

  const HEADLINES = ["Story City Unveils Giant Jackalope Statue For Tourism Push", "Neighboring Town Claims Jackalope Legend Originated There Instead", "Dueling Town Councils Pass Competing 'Jackalope Capital' Resolutions", "State Tourism Board Asked To Mediate Jackalope Ownership Dispute", "Governor Signs Jackalope Accord, Statue To Alternate Towns Biweekly"];
  const STAMPS = ["UNVEILED", "DISPUTED", "DUELING RES.", "IN MEDIATION", "ACCORD SIGNED"];
  const CONCLUSION_MSG = "The story has concluded. Both towns claim victory; the trailer has its own regional fan following.";

  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>