← back to Crazy News Channel
cartoons/20260924-quorum-call-hide-seek.html
270 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Quorum Call — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire strip: an invented legislative chamber plays hide-and-seek to dodge a quorum call, with a new hiding spot each plot twist." />
<link rel="stylesheet" href="../assets/style.css" />
</head>
<body data-mood="politics" class="cartoon-page cartoon-page-wide">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire</div>
<h1>The Quorum Call</h1>
<p class="dek">An invented legislative chamber plays hide-and-seek to dodge a quorum call. Nobody has technically left the building.</p>
</div>
<div class="header-actions">
<a class="back" href="index.html">← Cartoon Desk</a>
</div>
</header>
<main>
<div class="strip" id="strip">
<figure class="panel" data-i="0">
<div class="phead"><span>Panel 1 · The Roll</span><span id="stamp0"></span></div>
<svg class="art" id="art0" viewBox="0 0 300 300" role="img" tabindex="0"></svg>
<button class="cover" id="cover0" type="button">Click, tap, or press N to begin</button>
<figcaption id="cap0"></figcaption>
</figure>
<figure class="panel" data-i="1">
<div class="phead"><span>Panel 2 · The Vacancy</span><span id="stamp1"></span></div>
<svg class="art" id="art1" viewBox="0 0 300 300" role="img" tabindex="0"></svg>
<button class="cover" id="cover1" type="button" disabled>In queue</button>
<figcaption id="cap1"></figcaption>
</figure>
<figure class="panel" data-i="2">
<div class="phead"><span>Panel 3 · The Search</span><span id="stamp2"></span></div>
<svg class="art" id="art2" viewBox="0 0 300 300" role="img" tabindex="0"></svg>
<button class="cover" id="cover2" type="button" disabled>In queue</button>
<figcaption id="cap2"></figcaption>
</figure>
</div>
<div class="dock">
<button class="primary" id="nextBtn" type="button">Process Panel 1</button>
<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/→ next panel · T plot twist · click a panel to advance</p>
</main>
<footer>All delegates, clerks, sergeants-at-arms, and closets are invented. No actual legislative body was inconvenienced. 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 reduced(){ return matchMedia('(prefers-reduced-motion: reduce)').matches; }
// ---- invented delegates — simple flat iconographic figures, not detailed characters ----
const MASCOTS = [
{ name: 'Delegate Rourke', color: '#b5302f' },
{ name: 'Delegate Pham', color: '#0f6b52' },
{ name: 'Delegate Struble', color: '#c9a227' },
{ name: 'Delegate Okonkwo', color: '#6b4fa0' },
];
let mascotIdx = 0;
// ---- invented hiding spots — cycled by the Plot Twist button ----
const HIDING_SPOTS = [
{ name: 'the Supply Closet', narrow: false },
{ name: 'the Cloak Room', narrow: false },
{ name: 'the Recording Booth', narrow: true },
{ name: 'a Very Narrow Broom Closet', narrow: true },
];
let hideIdx = 0;
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); // head
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); // shoulders
if (label) {
const t = el('text', { x: cx, y: cy + r + 16, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'11', fill: 'currentColor' }, g);
t.textContent = label;
}
return g;
}
function desk(svg, x, y, w){
el('rect', { x, y, width: w, height: 10, fill: 'currentColor', opacity: 0.5, rx: 2 }, svg);
el('rect', { x: x+6, y: y+10, width: 6, height: 22, fill:'currentColor', opacity:0.35 }, svg);
el('rect', { x: x+w-12, y: y+10, width: 6, height: 22, fill:'currentColor', opacity:0.35 }, svg);
}
function podium(svg, cx, cy){
el('rect', { x: cx-16, y: cy-6, width: 32, height: 40, fill:'none', stroke:'currentColor', 'stroke-width':2.5, rx:3 }, svg);
el('rect', { x: cx-20, y: cy-14, width: 40, height: 10, fill:'currentColor', opacity:0.5, rx:2 }, svg);
}
// a paper cutout propped in a chair — the classic "gone but not really" decoy
function cutout(svg, cx, cy, color){
const g = el('g', {}, svg);
el('rect', { x: cx-16, y: cy-2, width: 32, height: 38, fill: 'none', stroke: color, 'stroke-width': 2, 'stroke-dasharray':'3 3', rx: 4 }, g);
el('circle', { cx, cy: cy-14, r: 10, fill:'none', stroke: color, 'stroke-width':2, 'stroke-dasharray':'3 3' }, g);
// two dot eyes drawn on, like a kid's paper mask
el('circle', { cx: cx-3, cy: cy-15, r: 1.2, fill: color }, g);
el('circle', { cx: cx+3, cy: cy-15, r: 1.2, fill: color }, g);
return g;
}
function seal(svg, cx, cy, r, text, color){
const g = el('g', { transform: `rotate(-12 ${cx} ${cy})` }, svg);
el('circle', { cx, cy, r, fill: 'none', stroke: color, 'stroke-width': 4, 'stroke-dasharray':'4 3' }, g);
const t = el('text', { x: cx, y: cy+5, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-weight':'800', 'font-size': Math.max(9, r*0.32), fill: color }, g);
t.textContent = text;
return g;
}
function panel0(svg, mascot){
svg.innerHTML = '';
el('rect', { x:0, y:0, width:300, height:300, fill:'none' }, svg);
podium(svg, 150, 70);
desk(svg, 50, 170, 60); desk(svg, 130, 170, 60); desk(svg, 210, 170, 60);
desk(svg, 50, 220, 60); desk(svg, 130, 220, 60); desk(svg, 210, 220, 60);
official(svg, 80, 155, 20, mascot.color, null);
official(svg, 240, 155, 20, MASCOTS[(mascotIdx+1)%MASCOTS.length].color, null);
official(svg, 160, 205, 20, MASCOTS[(mascotIdx+2)%MASCOTS.length].color, null);
const b = el('text', { x:150, y:36, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'13', 'font-weight':'700', fill:'currentColor' }, svg);
b.textContent = '"Calling the roll —"';
const b2 = el('text', { x:150, y:284, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'11', fill:'var(--ink-dim)' }, svg);
b2.textContent = 'The Clerk of ' + CHAMBER + ' begins the count.';
}
function panel1(svg, mascot){
svg.innerHTML = '';
podium(svg, 150, 60);
desk(svg, 50, 170, 60); desk(svg, 130, 170, 60); desk(svg, 210, 170, 60);
desk(svg, 50, 220, 60); desk(svg, 130, 220, 60); desk(svg, 210, 220, 60);
// every seat is now a cutout — the tell of panel 2
cutout(svg, 80, 150, mascot.color);
cutout(svg, 160, 150, MASCOTS[(mascotIdx+1)%MASCOTS.length].color);
cutout(svg, 240, 150, MASCOTS[(mascotIdx+2)%MASCOTS.length].color);
cutout(svg, 160, 200, MASCOTS[(mascotIdx+3)%MASCOTS.length].color);
const b = el('text', { x:150, y:36, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'12', 'font-weight':'700', fill:'currentColor' }, svg);
b.textContent = '"...present?"';
const b2 = el('text', { x:150, y:270, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'11', fill:'var(--ink-dim)' }, svg);
b2.textContent = 'Every chair answers. Nobody sitting in one does.';
}
function panel2(svg, mascot, spot){
svg.innerHTML = '';
// a sergeant-at-arms, mid-search
official(svg, 90, 160, 26, mascot.color, 'Sergeant-at-Arms');
// the closet — a door drawn with a suspicious person-shaped panel line, narrower for the "very narrow" spots
const cx = 205, top = 60, bottom = 250;
const halfTop = spot.narrow ? 32 : 42;
const halfMid = spot.narrow ? 16 : 30;
const g = el('g', {}, svg);
el('rect', { x: cx-halfTop, y: top, width: halfTop*2, height: bottom-top, fill:'none', stroke:'currentColor', 'stroke-width':3, rx:4 }, g);
el('circle', { cx: cx+halfTop-8, cy: (top+bottom)/2, r: 3, fill:'currentColor' }, g); // door handle
// faint person-shaped bulge outline behind the door — "suspiciously form-shaped"
el('path', {
d: `M ${cx} ${top+18} q ${halfMid*0.9} 2 ${halfMid*0.9} ${halfMid*1.1} l 0 ${bottom-top-halfMid*2.6} q 0 ${halfMid*0.9} -${halfMid*0.9} ${halfMid*0.9} q -${halfMid*0.9} 0 -${halfMid*0.9} -${halfMid*0.9} l 0 -${bottom-top-halfMid*2.6} q 0 -${halfMid*1.05} ${halfMid*0.9} -${halfMid*1.05} z`,
fill: mascot.color, 'fill-opacity':'0.16', stroke: mascot.color, 'stroke-width':1.4, 'stroke-dasharray':'2 3'
}, g);
const label = el('text', { x: cx, y: bottom+16, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'10', 'font-weight':'700', fill:'var(--ink-dim)' }, svg);
label.textContent = spot.name.toUpperCase();
seal(svg, 205, 40, 24, '?', mascot.color);
const b = el('text', { x:150, y:284, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'11', 'font-weight':'700', fill:'currentColor' }, svg);
b.textContent = 'That door is not usually shaped like that.';
}
const CHAMBER = 'the Assembly of the Provisional Compact';
const CAPTIONS = [
'The Clerk of ' + CHAMBER + ' opens the roll call for an invented quorum vote.',
'Every desk answers present. Every desk is lying — the members left paper stand-ins propped in the chairs.',
'The Sergeant-at-Arms narrows it down to one door that is doing an unconvincing job of looking empty.',
];
const state = { revealed: 0, twists: 0 };
function renderPanel(i){
const svg = document.getElementById('art' + i);
const mascot = MASCOTS[(mascotIdx + i) % MASCOTS.length];
if (i === 0) panel0(svg, mascot);
if (i === 1) panel1(svg, mascot);
if (i === 2) panel2(svg, mascot, HIDING_SPOTS[hideIdx]);
document.getElementById('cap' + i).textContent = i === 2
? CAPTIONS[2] + ' Current suspect: ' + HIDING_SPOTS[hideIdx].name + '.'
: CAPTIONS[i];
document.getElementById('stamp' + i).textContent = i <= state.revealed ? ['CALLED','VACANT','SEARCHING'][i] : '';
const cover = document.getElementById('cover' + i);
cover.hidden = i <= state.revealed - 1 || (i === state.revealed);
if (i === state.revealed) { cover.hidden = false; cover.disabled = false; cover.textContent = i === 0 ? 'Click, tap, or press N to begin' : 'Click, tap, or press N to process'; }
}
function paintAll(){
for (let i=0;i<3;i++) {
renderPanel(i);
document.getElementById('art'+i).setAttribute('tabindex', i === state.revealed ? '0' : '-1');
}
syncButtons();
}
function syncButtons(){
const nextBtn = document.getElementById('nextBtn');
if (state.revealed >= 3) { nextBtn.textContent = 'All 3 panels processed ✓'; nextBtn.disabled = true; }
else { nextBtn.textContent = `Process Panel ${state.revealed+1}`; nextBtn.disabled = false; }
}
function say(kind, msg){
const s = document.getElementById('status');
s.className = kind; s.textContent = msg;
}
function next(){
if (state.revealed >= 3) { say('info', 'All 3 panels are already on file. Try Plot Twist!'); return; }
state.revealed++;
for (let i=0;i<3;i++) {
const cover = document.getElementById('cover'+i);
cover.hidden = i < state.revealed;
cover.disabled = i > state.revealed;
document.getElementById('art'+i).setAttribute('tabindex', i === state.revealed ? '0' : '-1');
}
renderPanel(state.revealed - 1); // the panel that just got processed
if (state.revealed < 3) renderPanel(state.revealed); // the newly-active cover's label
say('ok', `Panel ${state.revealed} processed.`);
syncButtons();
if (state.revealed >= 3) say('ok', 'All 3 panels processed. Press Plot Twist! to relocate the hiding spot.');
}
function twist(){
if (state.revealed < 3) { say('err', 'ERROR QC-409 · Premature twist — process all 3 panels first.'); return; }
mascotIdx = (mascotIdx + 1) % MASCOTS.length;
hideIdx = (hideIdx + 1) % HIDING_SPOTS.length;
state.twists++;
renderPanel(0); renderPanel(1); renderPanel(2);
say('ok', `Plot twist #${state.twists}: the hiding spot has relocated to ${HIDING_SPOTS[hideIdx].name}.`);
}
document.getElementById('nextBtn').addEventListener('click', next);
document.getElementById('twistBtn').addEventListener('click', twist);
for (let i=0;i<3;i++) {
document.getElementById('cover'+i).addEventListener('click', () => { if (i === state.revealed) next(); });
document.getElementById('art'+i).addEventListener('click', () => { if (i === state.revealed) next(); });
document.getElementById('art'+i).addEventListener('keydown', (e) => { if ((e.key==='Enter'||e.key===' ') && i===state.revealed) { e.preventDefault(); next(); } });
}
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(); next(); }
else if (k === 't') { e.preventDefault(); twist(); }
});
paintAll();
say('info', 'Case file open. Press N or click Panel 1 to begin.');
})();
</script>
</body>
</html>