← back to Crazy News Channel Shadowman
cartoons gallery: front-page per-category colors + category chip filter
90bdc436d193375ef1e3afe9fdc8cce7d51c7aef · 2026-09-24 10:30:34 -0700 · Steve Abrams
Replace the single hard-coded red palette with the same 8 mood blocks
(all/politics/weather/scitech/sports/entertainment/business/uncategorized)
from ../index.html, add the matching 8-chip category filter that retunes
body[data-mood] the same way, give each card a data-cat + --cat-accent
color (left border + badge) so the All view is multicolored, classify
cartoons via section -> story_id lookup (../stories-data.js) -> "Political
Cartoon" -> uncategorized, and persist the selection via
localStorage/#/category/<key> hash (hash wins on load). Sort, density,
grid/list toggle, and textContent-only rendering all preserved.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqUe1Hvqsnrkxpe4R6jrk1
Files touched
Diff
commit 90bdc436d193375ef1e3afe9fdc8cce7d51c7aef
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 24 10:30:34 2026 -0700
cartoons gallery: front-page per-category colors + category chip filter
Replace the single hard-coded red palette with the same 8 mood blocks
(all/politics/weather/scitech/sports/entertainment/business/uncategorized)
from ../index.html, add the matching 8-chip category filter that retunes
body[data-mood] the same way, give each card a data-cat + --cat-accent
color (left border + badge) so the All view is multicolored, classify
cartoons via section -> story_id lookup (../stories-data.js) -> "Political
Cartoon" -> uncategorized, and persist the selection via
localStorage/#/category/<key> hash (hash wins on load). Sort, density,
grid/list toggle, and textContent-only rendering all preserved.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DqUe1Hvqsnrkxpe4R6jrk1
---
cartoons/index.html | 283 ++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 274 insertions(+), 9 deletions(-)
diff --git a/cartoons/index.html b/cartoons/index.html
index 8aa267f..392e42c 100644
--- a/cartoons/index.html
+++ b/cartoons/index.html
@@ -6,18 +6,75 @@
<title>P24 Political Cartoon Desk — PANDEMONIUM-24</title>
<meta name="description" content="The P24 Political Cartoon Desk archive — original invented satire strips, updated daily." />
<style>
-:root {
+/* ---------- Mood theming — CSS custom properties ----------
+ Mirrors the palette blocks in ../index.html exactly (:root, body[data-mood]).
+ Default ("all") is the pale mint/green from the main page's default mood. */
+:root { --card-w: 300px; }
+:root, body[data-mood="all"] {
+ --bg: #cdf5e0;
+ --bg2: #f2fff2;
+ --panel: #ffffff;
+ --panel-border: #0f9d7833;
+ --accent: #0f9d78;
+ --accent2: #056659;
+ --text: #123524;
+ --text-dim: #12352499;
+ --chrome: #ffffff;
+ --focus: #0b3d91;
+}
+body[data-mood="politics"] {
--bg: #2a0b0b; --bg2: #4a0f0f; --panel: #3a1212; --panel-border: #ff3b3b55;
--accent: #ff3b3b; --accent2: #ffb703; --text: #ffe9e9; --text-dim: #ffd7d7aa;
- --chrome: #220808; --focus: #ffd166; --card-w: 300px;
+ --chrome: #220808; --focus: #ffd166;
+}
+body[data-mood="weather"] {
+ --bg: #071a33; --bg2: #0c2b52; --panel: #0e2340; --panel-border: #4fd1ff55;
+ --accent: #4fd1ff; --accent2: #a0e9ff; --text: #eaf6ff; --text-dim: #cfeeffaa;
+ --chrome: #05132c; --focus: #ffe066;
+}
+body[data-mood="sports"] {
+ --bg: #2b1400; --bg2: #4a2200; --panel: #3a1c00; --panel-border: #ff980055;
+ --accent: #ff9800; --accent2: #ffd166; --text: #fff3e0; --text-dim: #ffe0b3aa;
+ --chrome: #200f00; --focus: #4fd1ff;
+}
+body[data-mood="entertainment"] {
+ --bg: #2a0a2e; --bg2: #4a0f52; --panel: #3a1140; --panel-border: #ff5fd155;
+ --accent: #ff5fd1; --accent2: #b388ff; --text: #fbe9ff; --text-dim: #eecbffaa;
+ --chrome: #210822; --focus: #ffe066;
+}
+body[data-mood="business"] {
+ --bg: #08210f; --bg2: #0e3a1c; --panel: #0d2c16; --panel-border: #2ecc7155;
+ --accent: #2ecc71; --accent2: #f1c40f; --text: #eafff0; --text-dim: #c9f5d6aa;
+ --chrome: #06180c; --focus: #f1c40f;
+}
+body[data-mood="scitech"] {
+ --bg: #0a0a23; --bg2: #170b3d; --panel: #140c33; --panel-border: #00f0ff55;
+ --accent: #00f0ff; --accent2: #ff00e6; --text: #eafcff; --text-dim: #c9f3ffaa;
+ --chrome: #08081c; --focus: #ff00e6;
}
+body[data-mood="uncategorized"] {
+ --bg: #e9edf0; --bg2: #ffffff; --panel: #ffffff; --panel-border: #54657833;
+ --accent: #546578; --accent2: #2f3b47; --text: #20272d; --text-dim: #20272d99;
+ --chrome: #ffffff; --focus: #0b3d91;
+}
+
*,*::before,*::after{box-sizing:border-box}
html{overflow-x:hidden}
[hidden]{display:none!important}
+.visually-hidden {
+ position: absolute !important;
+ width: 1px; height: 1px;
+ padding: 0; margin: -1px;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ white-space: nowrap;
+ border: 0;
+}
body{
margin:0; min-height:100vh; font-family:-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;
color:var(--text); background:radial-gradient(circle at 20% -10%, var(--bg2), var(--bg) 60%);
line-height:1.4;
+ transition: background-color .4s ease, color .4s ease;
}
a{color:var(--accent2)}
h1,h2,p{margin:0}
@@ -29,6 +86,37 @@ h1,h2,p{margin:0}
.chrome a.home:hover{color:var(--text)}
main{max-width:1200px; margin:0 auto; padding:20px}
.dek{color:var(--text-dim); max-width:60ch; margin-bottom:18px}
+
+/* ---------- Category filter (chip row) — mirrors ../index.html ---------- */
+.category-filter{ margin-bottom:18px; }
+.category-filter fieldset{ border:0; margin:0; padding:0; min-width:0; }
+.chip-row{
+ display:flex; gap:.5rem; overflow-x:auto; padding-bottom:.25rem;
+ scrollbar-width:thin; min-width:0;
+}
+.chip-row::-webkit-scrollbar{ height:6px; }
+.chip-row::-webkit-scrollbar-thumb{ background:var(--panel-border); border-radius:4px; }
+.chip-item{ position:relative; flex:0 0 auto; }
+.chip-item input{ position:absolute; opacity:0; width:1px; height:1px; }
+.chip-item label{
+ display:inline-block;
+ padding:.45rem .9rem;
+ border-radius:999px;
+ border:1.5px solid var(--panel-border);
+ background:var(--panel);
+ color:var(--text);
+ font-size:.8rem; font-weight:700; letter-spacing:.2px;
+ cursor:pointer; white-space:nowrap;
+ transition:background-color .2s ease, color .2s ease, transform .12s ease;
+}
+.chip-item label:hover{ transform:translateY(-1px); }
+.chip-item input:checked + label{
+ background:var(--accent); color:var(--bg); border-color:var(--accent2);
+}
+.chip-item input:focus-visible + label{
+ outline:3px solid var(--focus); outline-offset:2px;
+}
+
.controls{display:flex; gap:16px; flex-wrap:wrap; align-items:center; background:var(--panel); border:1px solid var(--panel-border); border-radius:10px; padding:12px 16px; margin-bottom:20px}
.controls label{font-size:.8rem; color:var(--text-dim); display:flex; gap:8px; align-items:center}
.controls select, .controls input[type=range]{accent-color:var(--accent)}
@@ -39,10 +127,30 @@ main{max-width:1200px; margin:0 auto; padding:20px}
.view-toggle button[aria-pressed="true"]{background:var(--accent); color:var(--chrome); font-weight:700; border-color:var(--accent)}
#count{margin-left:auto; font-size:.8rem; color:var(--text-dim)}
.grid{display:grid; grid-template-columns:repeat(auto-fill, minmax(var(--card-w), 1fr)); gap:18px}
-.card{background:var(--panel); border:1px solid var(--panel-border); border-radius:12px; overflow:hidden; display:flex; flex-direction:column; text-decoration:none; color:var(--text); transition:transform .15s ease, border-color .15s ease}
+
+/* ---------- Per-category card accent ---------- */
+/* Each card carries data-cat, which sets --cat-accent to that category's
+ mood --accent (see body[data-mood] blocks above). This keeps cards
+ multicolored in the "All Categories" view regardless of the page's
+ current mood, and still tracks the palette if these values ever change. */
+.card[data-cat="politics"]{ --cat-accent:#ff3b3b; }
+.card[data-cat="weather"]{ --cat-accent:#4fd1ff; }
+.card[data-cat="sports"]{ --cat-accent:#ff9800; }
+.card[data-cat="entertainment"]{ --cat-accent:#ff5fd1; }
+.card[data-cat="business"]{ --cat-accent:#2ecc71; }
+.card[data-cat="scitech"]{ --cat-accent:#00f0ff; }
+.card[data-cat="uncategorized"]{ --cat-accent:#546578; }
+
+.card{background:var(--panel); border:1px solid var(--panel-border); border-left:5px solid var(--cat-accent, var(--accent)); border-radius:12px; overflow:hidden; display:flex; flex-direction:column; text-decoration:none; color:var(--text); transition:transform .15s ease, border-color .15s ease}
.card:hover, .card:focus-visible{transform:translateY(-3px); border-color:var(--accent)}
-.card .thumb{aspect-ratio:4/3; background:var(--chrome) center/cover no-repeat; display:flex; align-items:center; justify-content:center; font-size:2.4rem}
+.card .thumb{aspect-ratio:4/3; background:var(--chrome) center/cover no-repeat; display:flex; align-items:center; justify-content:center; font-size:2.4rem; position:relative}
.card .body{padding:12px 14px; display:flex; flex-direction:column; gap:6px; min-width:0}
+.card .cat-badge{
+ align-self:flex-start;
+ font-size:.68rem; font-weight:800; letter-spacing:.3px; text-transform:uppercase;
+ padding:.15rem .5rem; border-radius:999px;
+ background:var(--cat-accent, var(--accent)); color:var(--chrome);
+}
.card .title{font-weight:700; font-size:1rem}
.card .meta{font-size:.75rem; color:var(--text-dim)}
.card .blurb{font-size:.85rem; color:var(--text-dim)}
@@ -59,10 +167,10 @@ main{max-width:1200px; margin:0 auto; padding:20px}
.empty{background:var(--panel); border:1px dashed var(--panel-border); border-radius:12px; padding:40px 20px; text-align:center; color:var(--text-dim)}
.empty a{display:inline-block; margin-top:10px}
footer{max-width:1200px; margin:30px auto; padding:0 20px 30px; color:var(--text-dim); font-size:.75rem}
-@media (prefers-reduced-motion: reduce){ .card{transition:none} }
+@media (prefers-reduced-motion: reduce){ .card{transition:none} body{transition:none} }
</style>
</head>
-<body>
+<body data-mood="all">
<header class="chrome">
<span class="logo-badge" aria-hidden="true">P24</span>
<h1>Political Cartoon Desk</h1>
@@ -71,6 +179,46 @@ footer{max-width:1200px; margin:30px auto; padding:0 20px 30px; color:var(--text
<main>
<p class="dek">Original, invented editorial satire — bureaucracy, committees, and the occasional pigeon director. New strips added daily. All persons, departments, and forms are fictional.</p>
+ <nav class="category-filter" aria-label="Category and mood filter">
+ <fieldset>
+ <legend class="visually-hidden">Choose a cartoon category — changes the page's color mood</legend>
+ <div class="chip-row" id="chipRow">
+ <span class="chip-item">
+ <input type="radio" name="cartoonCategory" id="cat-all" value="all" checked />
+ <label for="cat-all">All Categories</label>
+ </span>
+ <span class="chip-item">
+ <input type="radio" name="cartoonCategory" id="cat-politics" value="politics" />
+ <label for="cat-politics">Politics</label>
+ </span>
+ <span class="chip-item">
+ <input type="radio" name="cartoonCategory" id="cat-weather" value="weather" />
+ <label for="cat-weather">Weather</label>
+ </span>
+ <span class="chip-item">
+ <input type="radio" name="cartoonCategory" id="cat-scitech" value="scitech" />
+ <label for="cat-scitech">Sci-Tech</label>
+ </span>
+ <span class="chip-item">
+ <input type="radio" name="cartoonCategory" id="cat-sports" value="sports" />
+ <label for="cat-sports">Sports</label>
+ </span>
+ <span class="chip-item">
+ <input type="radio" name="cartoonCategory" id="cat-entertainment" value="entertainment" />
+ <label for="cat-entertainment">Entertainment</label>
+ </span>
+ <span class="chip-item">
+ <input type="radio" name="cartoonCategory" id="cat-business" value="business" />
+ <label for="cat-business">Business</label>
+ </span>
+ <span class="chip-item">
+ <input type="radio" name="cartoonCategory" id="cat-uncategorized" value="uncategorized" />
+ <label for="cat-uncategorized">Uncategorized</label>
+ </span>
+ </div>
+ </fieldset>
+ </nav>
+
<div class="controls" role="group" aria-label="Sort and display density">
<label for="sortSel">Sort
<select id="sortSel">
@@ -91,23 +239,27 @@ footer{max-width:1200px; margin:30px auto; padding:0 20px 30px; color:var(--text
<div class="grid" id="grid" role="list"></div>
<div class="empty" id="empty" hidden>
- <p>No cartoons on file yet — the Desk is between assignments.</p>
+ <p id="emptyMsg">No cartoons on file yet — the Desk is between assignments.</p>
<a href="../index.html">Read the rest of P24 →</a>
</div>
</main>
<footer>All cartoons are invented satire. No actual bureaucrats were inconvenienced. Part of PANDEMONIUM-24.</footer>
+<script src="../stories-data.js"></script>
<script src="manifest.js"></script>
<script>
(function(){
const grid = document.getElementById('grid');
const empty = document.getElementById('empty');
+ const emptyMsg = document.getElementById('emptyMsg');
const sortSel = document.getElementById('sortSel');
const density = document.getElementById('density');
const densityLabel = document.getElementById('densityLabel');
const viewGridBtn = document.getElementById('viewGridBtn');
const viewListBtn = document.getElementById('viewListBtn');
const count = document.getElementById('count');
+ const chipRow = document.getElementById('chipRow');
const LS_SORT = 'p24cartoons.sort', LS_DENSITY = 'p24cartoons.density', LS_VIEW = 'p24cartoons.viewmode';
+ const LS_CATEGORY = 'p24cartoons.category';
function safeGet(k, d){ try { return localStorage.getItem(k) ?? d; } catch { return d; } }
function safeSet(k, v){ try { localStorage.setItem(k, v); } catch {} }
@@ -141,19 +293,130 @@ footer{max-width:1200px; margin:30px auto; padding:0 20px 30px; color:var(--text
catch { return iso; }
}
+ /* ---------- Category classification ---------- */
+ // Same 8 keys as ../index.html's mood system ("all" plus these 7).
+ const CATS = [
+ { key: 'all', label: 'All Categories' },
+ { key: 'politics', label: 'Politics' },
+ { key: 'weather', label: 'Weather' },
+ { key: 'scitech', label: 'Sci-Tech' },
+ { key: 'sports', label: 'Sports' },
+ { key: 'entertainment', label: 'Entertainment' },
+ { key: 'business', label: 'Business' },
+ { key: 'uncategorized', label: 'Uncategorized' },
+ ];
+ const CATEGORY_KEYS = CATS.filter((c) => c.key !== 'all').map((c) => c.key);
+ const CATEGORY_LABEL = Object.fromEntries(CATS.map((c) => [c.key, c.label]));
+
+ function isValidCategory(key){ return CATS.some((c) => c.key === key); }
+
+ // Looks a cartoon's linked story up by id across every loaded story data
+ // source (currently just ../stories-data.js's window.P24_EXTRA_STORIES —
+ // ../real-news-data.js is not loaded here because none of the current
+ // manifest.js story_id values resolve there; add it the same way if a
+ // future cartoon links a real-news story).
+ function storyCategoryById(id){
+ if (!id) return null;
+ const sources = [];
+ if (Array.isArray(window.P24_EXTRA_STORIES)) sources.push(window.P24_EXTRA_STORIES);
+ if (Array.isArray(window.P24_REAL_STORIES)) sources.push(window.P24_REAL_STORIES);
+ for (const arr of sources) {
+ const found = arr.find((s) => s && s.id === id);
+ if (found && found.category) return found.category;
+ }
+ return null;
+ }
+
+ // Category of a cartoon, in priority order:
+ // 1. c.section, if it's one of the 7 category keys
+ // 2. c.story_id, looked up against the story data sources above
+ // 3. c.category === "Political Cartoon" -> "politics"
+ // 4. "uncategorized"
+ function categoryOf(c){
+ if (c.section && CATEGORY_KEYS.includes(c.section)) return c.section;
+ if (c.story_id) {
+ const found = storyCategoryById(c.story_id);
+ if (found && CATEGORY_KEYS.includes(found)) return found;
+ }
+ if (c.category === 'Political Cartoon') return 'politics';
+ return 'uncategorized';
+ }
+
+ function getCategoryFromHash(){
+ const m = location.hash.match(/^#\/category\/(.+)$/);
+ if (!m) return null;
+ try { return decodeURIComponent(m[1]); } catch { return null; }
+ }
+
+ let currentCategory = getCategoryFromHash();
+ if (!currentCategory || !isValidCategory(currentCategory)) {
+ currentCategory = safeGet(LS_CATEGORY, 'all');
+ }
+ if (!isValidCategory(currentCategory)) currentCategory = 'all';
+
+ function syncChipRadio(){
+ const input = document.getElementById(`cat-${currentCategory}`);
+ if (input) input.checked = true;
+ }
+
+ function applyMood(){
+ document.body.dataset.mood = currentCategory;
+ }
+
+ function setCategory(key, opts){
+ opts = opts || {};
+ if (!isValidCategory(key)) return;
+ currentCategory = key;
+ applyMood();
+ syncChipRadio();
+ safeSet(LS_CATEGORY, currentCategory);
+ if (!opts.fromHash) {
+ const target = currentCategory === 'all' ? '' : `#/category/${currentCategory}`;
+ if (location.hash !== target) location.hash = target;
+ }
+ render();
+ }
+
+ chipRow.addEventListener('change', (e) => {
+ const t = e.target;
+ if (t && t.name === 'cartoonCategory') setCategory(t.value);
+ });
+
+ window.addEventListener('hashchange', () => {
+ const fromHash = getCategoryFromHash();
+ if (fromHash && isValidCategory(fromHash) && fromHash !== currentCategory) {
+ setCategory(fromHash, { fromHash: true });
+ } else if (!fromHash && currentCategory !== 'all' && location.hash === '') {
+ setCategory('all', { fromHash: true });
+ }
+ });
+
+ // Apply the resolved initial category before first render.
+ applyMood();
+ syncChipRadio();
+
let cartoons = [];
function render(){
- const sorted = [...cartoons];
+ const filtered = currentCategory === 'all'
+ ? cartoons
+ : cartoons.filter((c) => categoryOf(c) === currentCategory);
+
+ const sorted = [...filtered];
if (sortSel.value === 'newest') sorted.sort((a,b)=> (b.created_at||'').localeCompare(a.created_at||''));
else if (sortSel.value === 'oldest') sorted.sort((a,b)=> (a.created_at||'').localeCompare(b.created_at||''));
else if (sortSel.value === 'title') sorted.sort((a,b)=> a.title.localeCompare(b.title));
grid.innerHTML = '';
empty.hidden = sorted.length !== 0;
+ emptyMsg.textContent = currentCategory === 'all'
+ ? 'No cartoons on file yet — the Desk is between assignments.'
+ : `No cartoons in ${CATEGORY_LABEL[currentCategory] || currentCategory} yet.`;
count.textContent = sorted.length ? `${sorted.length} strip${sorted.length===1?'':'s'} on file` : '';
for (const c of sorted) {
+ const cat = categoryOf(c);
const a = document.createElement('a');
a.className = 'card'; a.href = c.file; a.role = 'listitem';
+ a.dataset.cat = cat;
const thumb = document.createElement('div');
thumb.className = 'thumb'; thumb.setAttribute('aria-hidden', 'true');
@@ -161,13 +424,15 @@ footer{max-width:1200px; margin:30px auto; padding:0 20px 30px; color:var(--text
else thumb.textContent = '\u{1F58B}\uFE0F';
const body = document.createElement('div'); body.className = 'body';
+ const badge = document.createElement('span'); badge.className = 'cat-badge';
+ badge.textContent = CATEGORY_LABEL[cat] || cat;
const title = document.createElement('div'); title.className = 'title'; title.textContent = c.title || '';
const meta = document.createElement('div'); meta.className = 'meta';
meta.title = c.created_at || '';
meta.textContent = (c.created_at ? fmtDate(c.created_at) : '') + (c.category ? ' \u00b7 ' + c.category : '');
const blurb = document.createElement('div'); blurb.className = 'blurb'; blurb.textContent = c.blurb || '';
- body.append(title, meta, blurb);
+ body.append(badge, title, meta, blurb);
a.append(thumb, body);
grid.appendChild(a);
}
← 3e6ffab P24: add first cartoon for business/entertainment/scitech/sp
·
back to Crazy News Channel Shadowman
·
Add P24 cartoon: The Quorum Call (invented legislative hide- 35cc1c2 →