← back to Crazy News Channel Shadowman
cartoons/20260924-lobbyist-revolving-door.html
258 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Revolving Door — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire strip: the same invented official exits an invented capitol's revolving door as a regulator and spins back in seconds later as a lobbyist, carrying a slightly bigger briefcase every time." />
<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 Revolving Door</h1>
<p class="dek">An invented capitol's revolving door turns once. A regulator walks out. It turns again. A lobbyist walks in. Same coat, same face, slightly bigger briefcase.</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 Exit</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 Spin</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 Return</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! (Spin the Door)</button>
<span class="counter" id="counter">Revolutions: 0</span>
<div id="status" role="status" aria-live="polite"></div>
</div>
<p class="hint">Keys: N/→ next panel · T spin the door again · click a panel to advance · once all 3 panels are open, click Panel 3 or press T to keep spinning</p>
</main>
<footer>All officials, agencies, and buildings are invented. No actual regulator was inconvenienced, or promoted. 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 "official" — same person, alternating title, plus a rotating cast of runners-up ----
const OFFICIALS = [
{ name: 'Deputy Administrator Voss', color: '#b5302f' },
{ name: 'Under-Secretary Marchetti', color: '#0f6b52' },
{ name: 'Regional Director Ashby', color: '#c9a227' },
{ name: 'Special Counselor Denholm', color: '#6b4fa0' },
];
let officialIdx = 0;
function building(svg){
// invented capitol facade: dome + columns, simple flat iconography
el('rect', { x:20, y:60, width:260, height:170, fill:'none', stroke:'currentColor', 'stroke-width':2.5, opacity:0.55 }, svg);
el('path', { d:'M 130 60 A 20 26 0 0 1 170 60 Z', fill:'none', stroke:'currentColor', 'stroke-width':2.5, opacity:0.55 }, svg);
for (let i=0;i<5;i++){
const x = 40 + i*45;
el('line', { x1:x, y1:70, x2:x, y2:225, stroke:'currentColor', 'stroke-width':3, opacity:0.35 }, svg);
}
el('rect', { x:20, y:225, width:260, height:8, fill:'currentColor', opacity:0.35 }, svg);
const t = el('text', { x:150, y:48, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-size':'10', 'letter-spacing':'2', fill:'var(--ink-dim)' }, svg);
t.textContent = 'ROTUNDA HOUSE OF INVENTED AFFAIRS';
}
function figure(svg, cx, cy, r, color, briefcaseScale){
const g = el('g', {}, svg);
el('circle', { cx, cy: cy - r*1.1, r: r*0.55, fill: color }, g); // head
el('path', { d: `M ${cx - r*0.9} ${cy + r*0.9} Q ${cx} ${cy - r*0.1} ${cx + r*0.9} ${cy + r*0.9}`, fill:'none', stroke: color, 'stroke-width': r*0.45, 'stroke-linecap':'round' }, g); // coat/shoulders
// briefcase, grows with revolutions
const bw = 16 * briefcaseScale, bh = 12 * briefcaseScale;
const bx = cx + r*0.75, by = cy + r*0.55;
el('rect', { x: bx, y: by, width: bw, height: bh, rx: 2, fill:'#3a2c1a', stroke: color, 'stroke-width':1.5 }, g);
el('rect', { x: bx + bw*0.4, y: by - bh*0.18, width: bw*0.2, height: bh*0.25, fill:'#3a2c1a', stroke: color, 'stroke-width':1 }, g);
return g;
}
function doorFrame(svg, cx, cy, r, rotation){
const g = el('g', {}, svg);
el('rect', { x: cx - r - 14, y: cy - r - 14, width: (r+14)*2, height: (r+14)*2, fill:'none', stroke:'currentColor', 'stroke-width':3, opacity:0.6, rx:6 }, g);
el('circle', { cx, cy, r, fill: 'none', stroke:'currentColor', 'stroke-width':2, opacity:0.5 }, g);
const cross = el('g', { class:'door-anim', transform:`rotate(${rotation} ${cx} ${cy})` }, g);
el('line', { x1:cx-r, y1:cy, x2:cx+r, y2:cy, stroke:'var(--accent2)', 'stroke-width':4 }, cross);
el('line', { x1:cx, y1:cy-r, x2:cx, y2:cy+r, stroke:'var(--accent2)', 'stroke-width':4 }, cross);
return g;
}
function label(svg, x, y, text, color){
const t = el('text', { x, y, 'text-anchor':'middle', 'font-family':'-apple-system,sans-serif', 'font-weight':'800', 'font-size':'13', fill: color }, svg);
t.textContent = text;
return t;
}
function panel0(svg, official){
svg.innerHTML = '';
building(svg);
doorFrame(svg, 150, 150, 42, 0);
figure(svg, 210, 150, 26, official.color, 1);
label(svg, 210, 205, 'REGULATOR', official.color);
label(svg, 150, 270, official.name, 'var(--ink-dim)');
}
function panel1(svg, official){
svg.innerHTML = '';
building(svg);
doorFrame(svg, 150, 150, 42, 45);
// motion blur strokes suggesting mid-spin, figure obscured inside
for (let i=0;i<4;i++){
el('line', { x1:150-30+i*20, y1:110, x2:150-24+i*20, y2:190, stroke:'currentColor', 'stroke-width':2, opacity:0.18 }, svg);
}
const l1 = label(svg, 95, 150, 'REGULATOR', official.color);
l1.setAttribute('opacity','0.35');
const l2 = label(svg, 205, 150, 'LOBBYIST', official.color);
l2.setAttribute('opacity','0.55');
label(svg, 150, 270, 'mid-revolution — title pending', 'var(--ink-dim)');
}
function panel2(svg, official, revolutions, briefcaseScale, asLobbyist){
svg.innerHTML = '';
building(svg);
doorFrame(svg, 150, 150, 42, 90);
figure(svg, 90, 150, 26, official.color, briefcaseScale);
label(svg, 90, 205, asLobbyist ? 'LOBBYIST' : 'REGULATOR', official.color);
label(svg, 150, 270, `Revolution #${revolutions}`, 'var(--ink-dim)');
}
const CAPTIONS_STATIC = [
'An invented official exits Rotunda House through the revolving door, badge freshly clipped, title: Regulator.',
'The door keeps turning. Nobody on either side can tell which way is in and which way is out anymore.',
];
const state = { revealed: 0, revolutions: 0, asLobbyist: true, briefcaseScale: 1 };
function briefcaseScaleFor(n){
return 1 + Math.min(n, 10) * 0.09;
}
function panel2Caption(){
const pct = Math.round((state.briefcaseScale - 1) * 100);
const role = state.asLobbyist ? 'lobbyist' : 'regulator';
return `Revolution #${state.revolutions}: ${OFFICIALS[officialIdx].name} spins back in as a ${role}. Briefcase now ${pct}% bigger than the day they started.`;
}
function renderPanel(i){
const svg = document.getElementById('art' + i);
const official = OFFICIALS[officialIdx];
if (i === 0) { panel0(svg, official); document.getElementById('cap0').textContent = CAPTIONS_STATIC[0]; }
if (i === 1) { panel1(svg, official); document.getElementById('cap1').textContent = CAPTIONS_STATIC[1]; }
if (i === 2) { panel2(svg, official, state.revolutions, state.briefcaseScale, state.asLobbyist); document.getElementById('cap2').textContent = panel2Caption(); }
document.getElementById('stamp' + i).textContent = i <= state.revealed ? ['EXIT LOGGED','IN TRANSIT','RE-ENTERED'][i] : '';
const cover = document.getElementById('cover' + i);
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'; }
else { cover.hidden = i <= state.revealed - 1; }
}
function paintAll(){
for (let i=0;i<3;i++) {
renderPanel(i);
document.getElementById('art'+i).setAttribute('tabindex', i === state.revealed ? '0' : '-1');
}
syncButtons();
updateCounter();
}
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 updateCounter(){
document.getElementById('counter').textContent = `Revolutions: ${state.revolutions}`;
}
function say(kind, msg){
const s = document.getElementById('status');
s.className = kind; s.textContent = msg;
}
function next(){
if (state.revealed >= 3) { say('info', 'The door is already open on all 3 frames. Try Plot Twist! to spin it again.'); return; }
state.revealed++;
if (state.revealed === 3) { state.revolutions = 1; state.briefcaseScale = briefcaseScaleFor(1); state.asLobbyist = true; }
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
if (state.revealed === 3) { renderPanel(2); updateCounter(); }
say('ok', `Panel ${state.revealed} processed.`);
syncButtons();
if (state.revealed >= 3) say('ok', `All 3 panels processed. Revolution #1 logged. Press Plot Twist! or click Panel 3 to spin the door again.`);
}
function spinDoor(){
if (state.revealed < 3) { say('err', 'ERROR RD-409 · The door has not finished its first turn — process all 3 panels first.'); return; }
state.revolutions++;
state.asLobbyist = !state.asLobbyist;
state.briefcaseScale = briefcaseScaleFor(state.revolutions);
if (state.revolutions % 2 === 0) officialIdx = (officialIdx + 1) % OFFICIALS.length;
renderPanel(0); renderPanel(1); renderPanel(2);
updateCounter();
say('ok', panel2Caption());
}
document.getElementById('nextBtn').addEventListener('click', next);
document.getElementById('twistBtn').addEventListener('click', spinDoor);
for (let i=0;i<3;i++) {
document.getElementById('cover'+i).addEventListener('click', () => { if (i === state.revealed) next(); else if (i === 2 && state.revealed >= 3) spinDoor(); });
document.getElementById('art'+i).addEventListener('click', () => { if (i === state.revealed) next(); else if (i === 2 && state.revealed >= 3) spinDoor(); });
document.getElementById('art'+i).addEventListener('keydown', (e) => {
if ((e.key==='Enter'||e.key===' ')) {
if (i===state.revealed) { e.preventDefault(); next(); }
else if (i === 2 && state.revealed >= 3) { e.preventDefault(); spinDoor(); }
}
});
}
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(); spinDoor(); }
});
paintAll();
say('info', 'The door is still. Press N or click Panel 1 to begin the first exit.');
})();
</script>
</body>
</html>