← back to Crazy News Channel Shadowman
TK-12241: Shadow Man gallery page on the Cartoon Desk (approved pieces only)
c3b7dd0d5d7321bec888fb4ff3a5e8ad874a5f6d · 2026-09-25 11:07:45 -0700 · Steve
cartoons/shadow-man.html + shadow-man.js, linked from cartoons/index.html. Data comes from
cartoons/shadow-man-data.js, written by the Inkwell export script, which exports ONLY pieces
approved in the Inkwell curation UI (currently 0 -> page shows an empty state). Theme/era filters,
sort, density slider (persisted), date+time chips, <dialog> viewer. Styling added to
assets/style.css as PART 4 scoped under body.shadow-man-page; no inline styles. Not added to
manifest.js, the daily queue, or the Shorts pipeline.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Files touched
M assets/style.cssM cartoons/index.htmlA cartoons/shadow-man-data.jsA cartoons/shadow-man.htmlA cartoons/shadow-man.jsA cartoons/shadow-man/.gitkeep
Diff
commit c3b7dd0d5d7321bec888fb4ff3a5e8ad874a5f6d
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Sep 25 11:07:45 2026 -0700
TK-12241: Shadow Man gallery page on the Cartoon Desk (approved pieces only)
cartoons/shadow-man.html + shadow-man.js, linked from cartoons/index.html. Data comes from
cartoons/shadow-man-data.js, written by the Inkwell export script, which exports ONLY pieces
approved in the Inkwell curation UI (currently 0 -> page shows an empty state). Theme/era filters,
sort, density slider (persisted), date+time chips, <dialog> viewer. Styling added to
assets/style.css as PART 4 scoped under body.shadow-man-page; no inline styles. Not added to
manifest.js, the daily queue, or the Shorts pipeline.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---
assets/style.css | 19 +++++++++++
cartoons/index.html | 1 +
cartoons/shadow-man-data.js | 6 ++++
cartoons/shadow-man.html | 57 +++++++++++++++++++++++++++++++++
cartoons/shadow-man.js | 76 ++++++++++++++++++++++++++++++++++++++++++++
cartoons/shadow-man/.gitkeep | 0
6 files changed, 159 insertions(+)
diff --git a/assets/style.css b/assets/style.css
index 25e1934..9f8aa1e 100644
--- a/assets/style.css
+++ b/assets/style.css
@@ -1360,3 +1360,22 @@ body.cartoon-page-reference footer { text-align: center; color: var(--text-dim);
.featured-cartoon-caption {
margin: 0; font-style: italic; color: var(--text-dim); font-size: .95rem;
}
+
+/* ============================================================
+ PART 4 — Shadow Man gallery (cartoons/shadow-man.html, TK-12241)
+ Scoped under body.shadow-man-page. Reuses Part 2's .grid/.card/
+ .controls; only the bits that differ live here.
+ ============================================================ */
+body.shadow-man-page .sm-card { font: inherit; text-align: left; cursor: pointer; padding: 0; width: 100%; }
+body.shadow-man-page .sm-card .thumb { aspect-ratio: 7 / 6; background: #fff; }
+body.shadow-man-page .sm-card .thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }
+body.shadow-man-page .sm-card-caption { font-style: italic; }
+body.shadow-man-page .sm-tags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
+body.shadow-man-page .sm-era { font-size: .68rem; font-weight: 800; letter-spacing: .3px; padding: .15rem .5rem; border-radius: 999px; border: 1px solid var(--panel-border); color: var(--text-dim); }
+body.shadow-man-page .sm-dialog { max-width: min(960px, 94vw); width: 100%; background: var(--panel); color: var(--text); border: 1px solid var(--panel-border); border-radius: 12px; padding: 18px 20px 20px; }
+body.shadow-man-page .sm-dialog::backdrop { background: rgba(0, 0, 0, .7); }
+body.shadow-man-page .sm-dialog h2 { margin: 0 32px 4px 0; font-size: 1.3rem; }
+body.shadow-man-page .sm-dialog img { width: 100%; height: auto; display: block; background: #fff; border-radius: 6px; margin: 10px 0; }
+body.shadow-man-page .sm-meta { font-size: .8rem; color: var(--text-dim); margin: 0; }
+body.shadow-man-page .sm-caption { font-style: italic; margin: 0; }
+body.shadow-man-page .sm-close { position: absolute; top: 10px; right: 12px; background: none; border: 0; color: var(--text); font-size: 1.6rem; cursor: pointer; line-height: 1; }
diff --git a/cartoons/index.html b/cartoons/index.html
index 211815b..4d7c4a9 100644
--- a/cartoons/index.html
+++ b/cartoons/index.html
@@ -11,6 +11,7 @@
<header class="chrome">
<span class="logo-badge" aria-hidden="true">P24</span>
<h1>Political Cartoon Desk</h1>
+ <a class="home" href="shadow-man.html">Shadow Man Gallery →</a>
<a class="home" href="reference/index.html">Style Reference: Classic Editorial Ink →</a>
<a class="home" href="../index.html">← Back to PANDEMONIUM-24</a>
</header>
diff --git a/cartoons/shadow-man-data.js b/cartoons/shadow-man-data.js
new file mode 100644
index 0000000..b7244e1
--- /dev/null
+++ b/cartoons/shadow-man-data.js
@@ -0,0 +1,6 @@
+// Generated by the Inkwell export script (TK-12241) — do not edit by hand.
+// Only pieces approved in the Inkwell curation UI are exported.
+window.P24_SHADOW_MAN = {
+ "exported_at": "2026-09-25T18:06:40.257Z",
+ "items": []
+};
diff --git a/cartoons/shadow-man.html b/cartoons/shadow-man.html
new file mode 100644
index 0000000..f38a3fd
--- /dev/null
+++ b/cartoons/shadow-man.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<title>Shadow Man — P24 Political Cartoon Desk</title>
+<meta name="description" content="Shadow Man: original single-panel editorial cartoons from the P24 Cartoon Desk, written fresh from classic editorial themes and eras." />
+<link rel="stylesheet" href="../assets/style.css" />
+</head>
+<body data-mood="all" class="shadow-man-page">
+<header class="chrome">
+ <span class="logo-badge" aria-hidden="true">P24</span>
+ <h1>Shadow Man</h1>
+ <a class="home" href="index.html">← Cartoon Desk</a>
+ <a class="home" href="../index.html">PANDEMONIUM-24</a>
+</header>
+<main>
+ <p class="dek">Original single-panel editorial cartoons signed “Shadow Man” — new scenes and captions written from classic editorial themes and eras. Every figure is an unnamed archetype; no piece redraws any published cartoon.</p>
+
+ <div class="controls" role="group" aria-label="Filter, sort and display density">
+ <label for="smTheme">Theme
+ <select id="smTheme"><option value="all">All themes</option></select>
+ </label>
+ <label for="smEra">Era
+ <select id="smEra"><option value="all">All eras</option></select>
+ </label>
+ <label for="smSort">Sort
+ <select id="smSort">
+ <option value="newest">Newest</option>
+ <option value="oldest">Oldest</option>
+ <option value="title">Title A→Z</option>
+ <option value="theme">Theme</option>
+ <option value="era">Era</option>
+ </select>
+ </label>
+ <label for="smDensity">Density
+ <input type="range" id="smDensity" min="220" max="520" step="20" value="320" aria-valuetext="card width">
+ </label>
+ <span id="smCount"></span>
+ </div>
+
+ <div class="grid sm-grid" id="smGrid" role="list"></div>
+ <div class="empty" id="smEmpty" hidden><p id="smEmptyMsg">No Shadow Man cartoons match this filter.</p></div>
+
+ <dialog class="sm-dialog" id="smDialog" aria-labelledby="smDialogTitle">
+ <button type="button" class="sm-close" id="smClose" aria-label="Close">×</button>
+ <h2 id="smDialogTitle"></h2>
+ <p class="sm-meta" id="smDialogMeta"></p>
+ <img id="smDialogImg" alt="" />
+ <p class="sm-caption" id="smDialogCaption"></p>
+ </dialog>
+</main>
+<footer>Shadow Man cartoons are original, AI-assisted invented satire rendered locally. All persons are fictional archetypes. Part of PANDEMONIUM-24.</footer>
+<script src="shadow-man-data.js"></script>
+<script src="shadow-man.js"></script>
+</body>
+</html>
diff --git a/cartoons/shadow-man.js b/cartoons/shadow-man.js
new file mode 100644
index 0000000..27773c7
--- /dev/null
+++ b/cartoons/shadow-man.js
@@ -0,0 +1,76 @@
+// P24 — Shadow Man gallery (TK-12241). Data: shadow-man-data.js (window.P24_SHADOW_MAN).
+// Theme + era filters, sort, density slider — all persisted to localStorage. Styling lives in
+// ../assets/style.css (body.shadow-man-page); this file sets no inline styles except the
+// --card-w custom property the shared gallery grid already reads.
+(function () {
+ const ITEMS = (window.P24_SHADOW_MAN && window.P24_SHADOW_MAN.items) || [];
+ const $ = (id) => document.getElementById(id);
+ const LS = { theme: 'p24shadowman.theme', era: 'p24shadowman.era', sort: 'p24shadowman.sort', density: 'p24shadowman.density' };
+ const get = (k, d) => { try { return localStorage.getItem(k) ?? d; } catch { return d; } };
+ const put = (k, v) => { try { localStorage.setItem(k, v); } catch {} };
+ const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
+ const fmt = (iso) => { const d = new Date(iso); return isNaN(d) ? '' : d.toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); };
+
+ const state = { theme: get(LS.theme, 'all'), era: get(LS.era, 'all'), sort: get(LS.sort, 'newest'), density: get(LS.density, '320') };
+
+ function fill(sel, values, cur, label) {
+ sel.innerHTML = `<option value="all">${esc(label)}</option>` + values.map(v => `<option value="${esc(v)}">${esc(v)}</option>`).join('');
+ sel.value = values.includes(cur) ? cur : 'all';
+ return sel.value;
+ }
+
+ function sorted(list) {
+ const t = (x) => new Date(x.created_at).getTime() || 0;
+ const a = [...list];
+ switch (state.sort) {
+ case 'oldest': return a.sort((x, y) => t(x) - t(y) || x.id.localeCompare(y.id));
+ case 'title': return a.sort((x, y) => x.title.localeCompare(y.title));
+ case 'theme': return a.sort((x, y) => x.theme.localeCompare(y.theme) || x.title.localeCompare(y.title));
+ case 'era': return a.sort((x, y) => x.era.localeCompare(y.era) || x.title.localeCompare(y.title));
+ default: return a.sort((x, y) => t(y) - t(x) || y.id.localeCompare(x.id));
+ }
+ }
+
+ function render() {
+ const list = sorted(ITEMS.filter(i => (state.theme === 'all' || i.theme === state.theme) && (state.era === 'all' || i.era === state.era)));
+ $('smCount').textContent = `${list.length} of ${ITEMS.length}`;
+ $('smEmpty').hidden = list.length > 0;
+ $('smEmptyMsg').textContent = ITEMS.length ? 'No Shadow Man cartoons match this filter.' : 'No Shadow Man cartoons have been approved for publication yet.';
+ $('smGrid').innerHTML = list.map(it => `
+ <button type="button" class="card sm-card" role="listitem" data-id="${esc(it.id)}">
+ <span class="thumb"><img src="${esc(it.src)}" alt="${esc(it.title)} — ${esc(it.caption)}" loading="lazy"></span>
+ <span class="body">
+ <span class="title">${esc(it.title)}</span>
+ <span class="blurb sm-card-caption">${esc(it.caption)}</span>
+ <span class="sm-tags"><span class="cat-badge">${esc(it.theme)}</span><span class="sm-era">${esc(it.era)}</span></span>
+ <span class="meta" title="${esc(it.created_at)}">🕓 ${esc(fmt(it.created_at))}</span>
+ </span>
+ </button>`).join('');
+ }
+
+ function open(id) {
+ const it = ITEMS.find(x => x.id === id);
+ if (!it) return;
+ $('smDialogTitle').textContent = it.title;
+ $('smDialogMeta').textContent = `${it.theme} · ${it.era} · signed ${it.signature}`;
+ $('smDialogImg').src = it.src;
+ $('smDialogImg').alt = `${it.title} — ${it.caption}`;
+ $('smDialogCaption').textContent = it.caption;
+ $('smDialog').showModal();
+ }
+
+ state.theme = fill($('smTheme'), [...new Set(ITEMS.map(i => i.theme))].sort(), state.theme, 'All themes');
+ state.era = fill($('smEra'), [...new Set(ITEMS.map(i => i.era))].sort(), state.era, 'All eras');
+ $('smSort').value = state.sort;
+ $('smDensity').value = state.density;
+ document.documentElement.style.setProperty('--card-w', state.density + 'px');
+
+ $('smTheme').addEventListener('change', e => { state.theme = e.target.value; put(LS.theme, state.theme); render(); });
+ $('smEra').addEventListener('change', e => { state.era = e.target.value; put(LS.era, state.era); render(); });
+ $('smSort').addEventListener('change', e => { state.sort = e.target.value; put(LS.sort, state.sort); render(); });
+ $('smDensity').addEventListener('input', e => { state.density = e.target.value; put(LS.density, state.density); document.documentElement.style.setProperty('--card-w', state.density + 'px'); });
+ $('smGrid').addEventListener('click', e => { const c = e.target.closest('.sm-card'); if (c) open(c.dataset.id); });
+ $('smClose').addEventListener('click', () => $('smDialog').close());
+ $('smDialog').addEventListener('click', e => { if (e.target === $('smDialog')) $('smDialog').close(); });
+ render();
+})();
diff --git a/cartoons/shadow-man/.gitkeep b/cartoons/shadow-man/.gitkeep
new file mode 100644
index 0000000..e69de29
← 9c5d41b auto-data-snapshot: 2026-09-25T10:22:25 (2 data files) — .cl
·
back to Crazy News Channel Shadowman
·
(newest)