← back to Crazy News Channel
Unify P24 stylesheet: shared external CSS across front page, cartoon gallery, and all 18 strip pages
648126984a2a91afa15b3d59a19216c6e0c0c803 · 2026-09-24 13:04:18 -0700 · Steve Abrams
Extracts the front page's 818-line inline <style> into assets/style.css
(Part 1, verbatim) and links it from index.html, cartoons/index.html
(Part 2: gallery-only grid/card/controls rules, deduped against Part 1),
and all 18 cartoon strip pages (Part 3), replacing three drifting inline
stylesheets with one shared file — still pure file:// static HTML, no
server/build step.
The 18 strips previously hand-rolled a separate "parchment newspaper"
serif theme (--ink/--ink-dim/--rule/--seal, Georgia font, a per-file
light/dark toggle). Rather than rewrite each strip's SVG-drawing JS
(several read these var() names directly at render time), the legacy
names are aliased onto the shared mint-green token system on the `body`
selector (not `:root` — aliasing at :root freezes the substitution using
html's own default-mood value instead of re-resolving per body[data-mood],
verified by isolated repro) so every strip re-themes automatically,
including a `data-mood="<category>"` attribute per strip matching its
manifest.js section (13 politics, 1 each business/entertainment/scitech/
sports/weather).
Also found and fixed: 17 of 18 strips explicitly set data-theme="light"
on load by default (only one has real 3-state auto/dark/light logic), so
a literal parchment-color override on data-theme="light" was permanently
masking every mood palette. Light is now a no-op (mood palette shows by
default); the toggle's "dark" state layers a night-viewing neutral on
top of whichever mood is active, verified working via Playwright click.
Verified with Chrome headless screenshots + Playwright (front page,
gallery, and 3 varied strips: politics 3-panel, scitech, weather) —
correct per-category theming, legible contrast, 0 console/page errors,
click-advance SVG interaction intact, dark-mode toggle intact.
Out of scope (left untouched, in-flight from other concurrent agents):
cartoons/manifest.js, cartoons/reference/*, and the newer cartoon files
not yet in the 18-file manifest (filibuster-marathon, come-in-and-get-me-
meter, conrad-filibuster-ink, gold-tier-treadmill, row-by-row-marshal) —
those will need the same link+data-mood treatment once their owning
tickets land.
TK-12151
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V29xaoBwcFwJyR4KC43ptn
Files touched
A assets/style.cssM cartoons/20260924-budget-pie-fight.htmlM cartoons/20260924-casserole-succession.htmlM cartoons/20260924-commission-turtles.htmlM cartoons/20260924-conrad-filibuster-qwen25-7b.htmlM cartoons/20260924-filibuster-vs-cloture.htmlM cartoons/20260924-four-minutes-ahead.htmlM cartoons/20260924-gnome-who-wouldnt-hush.htmlM cartoons/20260924-hedge-non-aggression-pact.htmlM cartoons/20260924-jackalope-custody-battle.htmlM cartoons/20260924-lobbyist-revolving-door.htmlM cartoons/20260924-meter-that-wouldnt-yield.htmlM cartoons/20260924-pork-barrel-train.htmlM cartoons/20260924-quorum-call-hide-seek.htmlM cartoons/20260924-row-by-row-only.htmlM cartoons/20260924-the-corner-with-its-own-weather.htmlM cartoons/20260924-the-zipper-that-ended-an-inning.htmlM cartoons/20260924-tollpoints-diamond-status.htmlM cartoons/20260924-tuber-bank-of-chugwater.htmlM cartoons/index.htmlM index.html
Diff
commit 648126984a2a91afa15b3d59a19216c6e0c0c803
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 24 13:04:18 2026 -0700
Unify P24 stylesheet: shared external CSS across front page, cartoon gallery, and all 18 strip pages
Extracts the front page's 818-line inline <style> into assets/style.css
(Part 1, verbatim) and links it from index.html, cartoons/index.html
(Part 2: gallery-only grid/card/controls rules, deduped against Part 1),
and all 18 cartoon strip pages (Part 3), replacing three drifting inline
stylesheets with one shared file — still pure file:// static HTML, no
server/build step.
The 18 strips previously hand-rolled a separate "parchment newspaper"
serif theme (--ink/--ink-dim/--rule/--seal, Georgia font, a per-file
light/dark toggle). Rather than rewrite each strip's SVG-drawing JS
(several read these var() names directly at render time), the legacy
names are aliased onto the shared mint-green token system on the `body`
selector (not `:root` — aliasing at :root freezes the substitution using
html's own default-mood value instead of re-resolving per body[data-mood],
verified by isolated repro) so every strip re-themes automatically,
including a `data-mood="<category>"` attribute per strip matching its
manifest.js section (13 politics, 1 each business/entertainment/scitech/
sports/weather).
Also found and fixed: 17 of 18 strips explicitly set data-theme="light"
on load by default (only one has real 3-state auto/dark/light logic), so
a literal parchment-color override on data-theme="light" was permanently
masking every mood palette. Light is now a no-op (mood palette shows by
default); the toggle's "dark" state layers a night-viewing neutral on
top of whichever mood is active, verified working via Playwright click.
Verified with Chrome headless screenshots + Playwright (front page,
gallery, and 3 varied strips: politics 3-panel, scitech, weather) —
correct per-category theming, legible contrast, 0 console/page errors,
click-advance SVG interaction intact, dark-mode toggle intact.
Out of scope (left untouched, in-flight from other concurrent agents):
cartoons/manifest.js, cartoons/reference/*, and the newer cartoon files
not yet in the 18-file manifest (filibuster-marathon, come-in-and-get-me-
meter, conrad-filibuster-ink, gold-tier-treadmill, row-by-row-marshal) —
those will need the same link+data-mood treatment once their owning
tickets land.
TK-12151
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V29xaoBwcFwJyR4KC43ptn
---
assets/style.css | 963 +++++++++++++++++++++
cartoons/20260924-budget-pie-fight.html | 23 +-
cartoons/20260924-casserole-succession.html | 23 +-
cartoons/20260924-commission-turtles.html | 23 +-
cartoons/20260924-conrad-filibuster-qwen25-7b.html | 36 +-
cartoons/20260924-filibuster-vs-cloture.html | 23 +-
cartoons/20260924-four-minutes-ahead.html | 23 +-
cartoons/20260924-gnome-who-wouldnt-hush.html | 23 +-
cartoons/20260924-hedge-non-aggression-pact.html | 23 +-
cartoons/20260924-jackalope-custody-battle.html | 23 +-
cartoons/20260924-lobbyist-revolving-door.html | 23 +-
cartoons/20260924-meter-that-wouldnt-yield.html | 23 +-
cartoons/20260924-pork-barrel-train.html | 23 +-
cartoons/20260924-quorum-call-hide-seek.html | 23 +-
cartoons/20260924-row-by-row-only.html | 23 +-
.../20260924-the-corner-with-its-own-weather.html | 23 +-
.../20260924-the-zipper-that-ended-an-inning.html | 23 +-
cartoons/20260924-tollpoints-diamond-status.html | 23 +-
cartoons/20260924-tuber-bank-of-chugwater.html | 23 +-
cartoons/index.html | 165 +---
index.html | 811 +----------------
21 files changed, 1024 insertions(+), 1342 deletions(-)
diff --git a/assets/style.css b/assets/style.css
new file mode 100644
index 0000000..40a01f6
--- /dev/null
+++ b/assets/style.css
@@ -0,0 +1,963 @@
+/* ============================================================
+ PANDEMONIUM-24 — shared stylesheet
+ Extracted from index.html (TK-12151) so the front page, the
+ cartoon gallery (cartoons/index.html), and all cartoon strip
+ pages (cartoons/2026*.html) share ONE mint-green/sans-serif
+ design system with per-category data-mood theming, instead of
+ copy-pasted/drifting inline <style> blocks. Pure CSS, no
+ fetch()/build step — safe for offline file:// use.
+
+ PART 1 — site-wide (verbatim from the former index.html inline
+ stylesheet: reset, mood palettes, chrome, story grid,
+ ticker, admin panel, article view, chaos index, etc.)
+ PART 2 — cartoon gallery (cartoons/index.html) additions
+ PART 3 — cartoon strip pages: legacy token aliases + the
+ per-file light/dark toggle, layered on top of Part 1
+ ============================================================ */
+
+/* ============================================================
+ PANDEMONIUM-24 — styles
+ Sections: 1. Reset & base 2. Mood theming (CSS custom props)
+ 3. Chrome (header/nav) 4. Breaking klaxon overlay
+ 5. Story grid & cards 6. Ticker (motion + reduced-motion)
+ 7. Empty / completion states 8. Focus & a11y helpers
+ 9. Responsive breakpoints 10. Reduced-motion overrides
+ ============================================================ */
+
+/* ---------- 1. Reset & base ---------- */
+*, *::before, *::after { box-sizing: border-box; }
+html, body { margin: 0; padding: 0; }
+/* overflow-x on body alone is not enough: with html's overflow unset, the
+ UA propagates body's overflow to the viewport's scroller, but
+ document.documentElement.scrollWidth can still report the wider,
+ unclipped value (verified empirically). Setting it on html too stops the
+ propagation and clips scrollWidth for real, everywhere. */
+html { overflow-x: hidden; }
+/* The [hidden] attribute must always win over any class-based `display`
+ rule below (several elements here are toggled via `.hidden = true/false`
+ in JS while also carrying a class that sets display:flex/grid). Without
+ this, equal-specificity cascade order lets the class rule beat the UA
+ [hidden] stylesheet and the element stays visible. */
+[hidden] { display: none !important; }
+body {
+ 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%);
+ transition: background-color .5s ease, color .5s ease;
+ line-height: 1.4;
+ /* reserve room for the fixed chyron ticker so it never overlaps content */
+ padding-bottom: 3rem;
+ /* Safety net: the chaos-tier-3 TV-bezel tilt rotates a very tall element
+ (main#channelContainer), and rotating a tall box even a fraction of a
+ degree widens its visual bounding box (width*cos + height*sin) beyond
+ the viewport — confirmed via CSSOM bisection to be the sole cause of a
+ real horizontal scrollbar at 390-1024px at max chaos. overflow-x:hidden
+ here clips that leak (vertical/position:sticky is untouched) without
+ changing the tilt's visible effect or any layout. */
+ overflow-x: hidden;
+}
+h1, h2, h3, p { margin: 0; }
+button { font-family: inherit; }
+img, svg { max-width: 100%; display: block; }
+
+.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;
+}
+
+/* ---------- 2. Mood theming — CSS custom properties ----------
+ Default ("all") is the pale mint/green salvaged from the original
+ seed attempt. Every other category swaps the whole palette. */
+: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;
+ --mood-label: "Calm-ish (Default)";
+}
+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;
+}
+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;
+}
+
+/* ---------- 3. Chrome (header / nav) ---------- */
+.chrome {
+ position: sticky; top: 0; z-index: 20;
+ display: flex; flex-wrap: wrap; align-items: center; gap: .75rem 1.25rem;
+ padding: .75rem 1rem;
+ background: var(--chrome);
+ /* No backdrop-filter: combined with position:sticky it caused WebKit/Safari
+ to ghost/bleed scrolled-past content through the header during scroll
+ (CTA-verified, cta/webkit-bug-repro/). --chrome is now fully opaque so
+ no blur was doing visible work anyway. translateZ forces a stable
+ compositing layer as cheap insurance against the same class of bug. */
+ transform: translateZ(0);
+ border-bottom: 2px solid var(--panel-border);
+}
+.brand { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; color: inherit; text-decoration: none; }
+.brand:hover .wordmark { text-decoration: underline; }
+.logo-badge {
+ display: inline-flex; align-items: center; justify-content: center;
+ width: 2.4rem; height: 2.4rem; border-radius: .4rem;
+ background: var(--accent); color: var(--bg);
+ font-weight: 900; font-size: .95rem; letter-spacing: -1px;
+ box-shadow: 0 0 0 2px var(--accent2);
+}
+.wordmark { font-size: clamp(1.1rem, 1.5vw + .8rem, 1.6rem); font-weight: 800; letter-spacing: .5px; }
+.tagline { font-size: .72rem; color: var(--text-dim); font-style: italic; }
+
+.live-badge {
+ display: inline-flex; align-items: center; gap: .4rem;
+ font-weight: 700; font-size: .8rem; letter-spacing: .5px;
+ padding: .3rem .6rem; border-radius: 999px;
+ border: 1px solid var(--panel-border);
+ background: color-mix(in srgb, var(--panel) 70%, transparent);
+}
+.rec-dot {
+ width: .55rem; height: .55rem; border-radius: 50%;
+ background: #ff3b3b;
+ box-shadow: 0 0 6px 1px #ff3b3b;
+ animation: rec-blink 1.4s steps(1) infinite;
+}
+@keyframes rec-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: .25; } }
+
+.controls { display: flex; align-items: center; gap: .75rem; margin-left: auto; flex-wrap: wrap; }
+
+.klaxon-btn {
+ cursor: pointer;
+ border: 2px solid var(--accent2);
+ background: var(--panel);
+ color: var(--text);
+ font-weight: 800;
+ font-size: .85rem;
+ padding: .55rem .9rem;
+ border-radius: .5rem;
+ letter-spacing: .3px;
+ transition: transform .15s ease, background-color .2s ease, color .2s ease;
+}
+.klaxon-btn:hover { transform: translateY(-1px); }
+.klaxon-btn[aria-pressed="true"] {
+ background: #ff2222; border-color: #ffd166; color: #fff;
+ animation: klaxon-btn-pulse 1s ease-in-out infinite;
+}
+@keyframes klaxon-btn-pulse {
+ 0%, 100% { box-shadow: 0 0 0 0 #ff222266; }
+ 50% { box-shadow: 0 0 0 8px #ff222200; }
+}
+
+.sound-toggle { display: inline-flex; align-items: center; gap: .4rem; font-size: .78rem; color: var(--text-dim); cursor: pointer; }
+.sound-toggle input { width: 1rem; height: 1rem; accent-color: var(--accent); cursor: pointer; }
+.audio-note { font-size: .7rem; color: var(--text-dim); max-width: 16rem; }
+
+/* Admin toggle — deliberately NOT themed by mood vars: fixed slate/mono
+ "control room" look so it never reads as part of the in-universe broadcast. */
+.admin-toggle {
+ cursor: pointer;
+ border: 1.5px dashed #6b7280;
+ background: #20242b; color: #d7dee5;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ font-size: .72rem; font-weight: 700; letter-spacing: .3px;
+ padding: .5rem .75rem; border-radius: .4rem;
+ transition: background-color .15s ease, border-color .15s ease, color .15s ease;
+}
+.admin-toggle:hover { background: #2a2f37; }
+.admin-toggle[aria-expanded="true"] { background: #343a40; border-style: solid; border-color: #7dd3fc; color: #7dd3fc; }
+
+/* ---------- category filter (nav) ---------- */
+.category-filter { padding: .6rem 1rem; border-bottom: 1px solid var(--panel-border); }
+.category-back-link { display: inline-block; font-size: .8rem; font-weight: 700; color: var(--accent2); margin-bottom: .3rem; text-decoration: none; }
+.category-back-link:hover { text-decoration: underline; }
+.category-blurb { color: var(--text-dim); font-size: .88rem; margin-top: .2rem; }
+.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;
+}
+
+/* ---------- 4. Breaking klaxon overlay ---------- */
+.breaking-banner {
+ position: relative;
+ margin: 0 1rem; margin-top: .75rem;
+ padding: .75rem 1rem;
+ background: linear-gradient(90deg, #b30000, #ff3b3b, #b30000);
+ background-size: 200% 100%;
+ color: #fff;
+ font-weight: 900;
+ border-radius: .4rem;
+ display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
+ border: 2px solid #ffd166;
+ box-shadow: 0 0 24px 2px #ff000055;
+ animation: breaking-sweep 1.4s linear infinite, breaking-flash 0.6s ease-in-out infinite;
+}
+@keyframes breaking-sweep { 0% { background-position: 0% 0; } 100% { background-position: 200% 0; } }
+@keyframes breaking-flash { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.35); } }
+.breaking-banner .bb-icon { font-size: 1.2rem; }
+.breaking-banner .bb-sub { font-weight: 600; font-size: .82rem; opacity: .95; }
+
+/* full-screen edge vignette while breaking is active */
+body.breaking-mode::before {
+ content: "";
+ position: fixed; inset: 0; z-index: 15; pointer-events: none;
+ box-shadow: inset 0 0 0 6px #ff2222, inset 0 0 60px 10px #ff222266;
+ animation: vignette-pulse 1s ease-in-out infinite;
+}
+@keyframes vignette-pulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
+
+/* ---------- 5. Story grid & cards ---------- */
+main#channelContainer {
+ position: relative;
+ max-width: 1200px;
+ margin: 1rem auto 0;
+ padding: 1rem 1rem 1.5rem;
+ border-radius: .75rem;
+ background: color-mix(in srgb, var(--panel) 55%, transparent);
+ box-shadow: inset 0 0 0 1px var(--panel-border), inset 0 40px 80px -40px #00000055;
+ overflow: hidden;
+}
+/* subtle static CRT scanline texture — no motion, safe under reduced-motion */
+main#channelContainer::after {
+ content: "";
+ position: absolute; inset: 0; pointer-events: none;
+ background: repeating-linear-gradient(
+ to bottom, rgba(255,255,255,0.035) 0px, rgba(255,255,255,0.035) 1px,
+ transparent 2px, transparent 4px
+ );
+ mix-blend-mode: overlay;
+}
+
+.channel-meta { display: flex; justify-content: space-between; align-items: baseline; gap: .5rem; flex-wrap: wrap; margin-bottom: .75rem; }
+.channel-meta h2 { font-size: .95rem; letter-spacing: 1px; text-transform: uppercase; color: var(--accent); }
+.mood-readout { font-size: .75rem; color: var(--text-dim); }
+
+.story-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 1rem;
+}
+.story-card {
+ position: relative;
+ background: var(--panel);
+ border: 1px solid var(--panel-border);
+ border-radius: .6rem;
+ padding: 1rem;
+ display: flex; flex-direction: column; gap: .55rem;
+ box-shadow: 0 4px 18px -10px #00000066;
+}
+.story-photo {
+ display: block; width: calc(100% + 2rem); max-width: none; height: auto;
+ aspect-ratio: 800 / 457; object-fit: cover;
+ margin: -1rem -1rem .2rem; border-radius: .6rem .6rem 0 0;
+ background: var(--panel-border);
+}
+.story-card.concluded .story-photo { filter: grayscale(.6); }
+.article-photo img { width: 100%; height: auto; border-radius: .5rem; display: block; }
+/* Cartoon-thumb base style (card-view / "All Categories" default — the
+ compact inline-link treatment for newspaper-mode overrides this above). A
+ politics story with a linked cartoon shows this INSTEAD OF a photo, same
+ banner slot, so it never looks like a missing image. */
+.story-cartoon-thumb {
+ display: flex; align-items: center; gap: .6rem;
+ width: calc(100% + 2rem); margin: -1rem -1rem .2rem; padding: 1.1rem 1rem;
+ border-radius: .6rem .6rem 0 0;
+ background: color-mix(in srgb, var(--accent) 12%, var(--panel));
+ border-bottom: 1px solid var(--panel-border);
+ text-decoration: none; color: var(--accent2);
+ font-weight: 700; font-size: .95rem;
+}
+.story-cartoon-thumb:hover, .story-cartoon-thumb:focus-visible { background: color-mix(in srgb, var(--accent) 20%, var(--panel)); }
+.story-cartoon-thumb .ct-icon { font-size: 1.3rem; }
+.story-card.concluded .story-cartoon-thumb { filter: grayscale(.5); }
+.story-card.lead { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent), 0 8px 24px -8px #00000077; }
+.story-card .kicker { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
+.badge-cat {
+ font-size: .68rem; font-weight: 800; letter-spacing: .5px; text-transform: uppercase;
+ padding: .18rem .5rem; border-radius: 999px; background: var(--accent); color: var(--bg);
+}
+.badge-lead {
+ font-size: .68rem; font-weight: 800; letter-spacing: .5px; text-transform: uppercase;
+ padding: .18rem .5rem; border-radius: 999px; background: var(--accent2); color: #fff;
+}
+.story-card h3 { font-size: clamp(1rem, .4vw + 1rem, 1.35rem); line-height: 1.25; }
+.story-card.lead h3 { font-size: clamp(1.25rem, 1.2vw + 1rem, 1.9rem); }
+.story-detail { font-size: .9rem; color: var(--text-dim); }
+.story-meta { font-size: .74rem; color: var(--text-dim); display: flex; gap: .5rem; flex-wrap: wrap; }
+
+.escalation {
+ display: flex; align-items: center; gap: .5rem; margin-top: .2rem;
+}
+.escalation .level-label { font-size: .72rem; font-weight: 800; letter-spacing: .5px; text-transform: uppercase; }
+.escalation .bar { display: flex; gap: 3px; }
+.escalation .seg { width: 1.1rem; height: .4rem; border-radius: 2px; background: var(--panel-border); }
+.escalation .seg.on { background: var(--accent); }
+.story-card.concluded { opacity: .96; }
+.concluded-ribbon {
+ align-self: flex-start;
+ font-size: .7rem; font-weight: 800; letter-spacing: .5px; text-transform: uppercase;
+ padding: .2rem .55rem; border-radius: .3rem;
+ background: var(--text); color: var(--bg);
+}
+
+/* ---------- 5b. Category-view "Newspaper Column" (V4) ----------
+ Applied only when state.category !== "all" (a specific category route,
+ e.g. #/category/scitech) via .story-grid.newspaper-mode, toggled in
+ renderStories(). Restyles the SAME storyCardHTML() markup used by the "All
+ Categories" card stack — no markup fork, so patchStoryGrid()'s keyed morph
+ reconcile never has to bridge a shape change from this toggle alone. True
+ CSS multi-column flow (column-count), inline category label instead of the
+ badge pill, headline-only density (detail paragraph hidden), a left rail
+ whose height reads escalation, and a compact photo/cartoon thumbnail. */
+.np-cat-label { display: none; }
+.story-grid.newspaper-mode {
+ display: block;
+ column-gap: 1.5rem;
+ column-count: 1;
+}
+@media (min-width: 640px) { .story-grid.newspaper-mode { column-count: 2; } }
+@media (min-width: 1024px) { .story-grid.newspaper-mode { column-count: 3; } }
+@media (min-width: 1440px) { .story-grid.newspaper-mode { column-count: 4; } }
+.story-grid.newspaper-mode .story-card {
+ break-inside: avoid;
+ display: block;
+ margin: 0 0 .9rem;
+ padding: 0 0 .1rem .7rem;
+ border: 0;
+ border-left: 3px solid var(--panel-border);
+ border-radius: 0;
+ box-shadow: none;
+}
+.story-grid.newspaper-mode .story-card.lead { border-left-color: var(--accent); border-left-width: 4px; }
+.story-grid.newspaper-mode .story-card.concluded { border-left-color: var(--text-dim); }
+.story-grid.newspaper-mode .story-card .kicker { display: none; }
+.story-grid.newspaper-mode .story-card .np-cat-label {
+ display: block;
+ font: 800 .62rem/1 -apple-system, sans-serif;
+ text-transform: uppercase; letter-spacing: .05em;
+ color: var(--accent); margin-bottom: .2rem;
+}
+.story-grid.newspaper-mode .story-card.concluded .np-cat-label { color: var(--text-dim); }
+.story-grid.newspaper-mode .story-card h3 {
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: 1rem; line-height: 1.22; margin-bottom: .25rem;
+}
+.story-grid.newspaper-mode .story-card.lead h3 { font-size: 1.3rem; font-style: italic; }
+.story-grid.newspaper-mode .story-card .story-detail,
+.story-grid.newspaper-mode .story-card .story-read-more { display: none; }
+.story-grid.newspaper-mode .story-card .escalation { margin: .3rem 0 0; gap: .35rem; }
+.story-grid.newspaper-mode .story-card .escalation .level-label { display: none; }
+.story-grid.newspaper-mode .story-card .escalation .seg { width: .55rem; height: .18rem; }
+.story-grid.newspaper-mode .story-card .story-meta {
+ font: 500 .66rem/1.3 ui-monospace, Menlo, monospace;
+ color: var(--text-dim); gap: .45rem; margin-top: .25rem;
+}
+.story-grid.newspaper-mode .story-card .story-meta span:last-child { display: none; } /* drop the "Updated HH:MM" clause — density */
+.story-grid.newspaper-mode .story-card .story-photo {
+ float: left; width: 56px; height: 56px; margin: 0 .55rem .3rem 0;
+ border-radius: .3rem; aspect-ratio: 1/1; object-fit: cover;
+}
+.story-grid.newspaper-mode .story-card .story-cartoon-thumb {
+ display: flex; align-items: center; gap: .4rem;
+ font: 700 .7rem/1.2 -apple-system, sans-serif;
+ color: var(--accent2); text-decoration: none;
+ border: 1px dashed var(--panel-border); border-radius: .35rem;
+ padding: .3rem .5rem; margin-bottom: .3rem;
+}
+.story-grid.newspaper-mode .story-card .story-cartoon-thumb:hover,
+.story-grid.newspaper-mode .story-card .story-cartoon-thumb:focus-visible { border-color: var(--accent); }
+.story-grid.newspaper-mode .story-card .ct-icon { font-size: .95rem; }
+
+/* ---------- 6. Ticker ---------- */
+.ticker-wrap {
+ position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
+ display: flex; align-items: stretch;
+ background: var(--chrome);
+ border-top: 2px solid var(--panel-border);
+ height: 3rem;
+}
+body.breaking-mode .ticker-wrap { border-top-color: #ff3b3b; }
+.ticker-label {
+ flex: 0 0 auto;
+ display: flex; align-items: center;
+ padding: 0 .8rem;
+ font-weight: 900; font-size: .78rem; letter-spacing: .5px;
+ background: var(--accent); color: var(--bg);
+}
+body.breaking-mode .ticker-label { background: #ff2222; color: #fff; }
+.ticker-scroll { position: relative; overflow: hidden; flex: 1 1 auto; min-width: 0; display: flex; align-items: center; }
+.ticker-track {
+ display: inline-flex; white-space: nowrap; gap: 3rem; padding-right: 3rem;
+ will-change: transform;
+ animation: ticker-scroll 38s linear infinite;
+ font-size: .85rem; font-weight: 600;
+}
+body.breaking-mode .ticker-track { color: #ff5959; font-weight: 800; }
+@keyframes ticker-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
+.ticker-static {
+ flex: 1 1 auto; min-width: 0; display: flex; align-items: center; padding: 0 .9rem;
+ font-size: .85rem; font-weight: 700;
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+}
+
+/* ---------- 7. Empty / completion states ---------- */
+.empty-state, .meltdown-banner {
+ text-align: center; padding: 2.5rem 1rem; border: 2px dashed var(--panel-border);
+ border-radius: .6rem; color: var(--text-dim);
+}
+.empty-state .es-emoji, .meltdown-banner .mb-emoji { font-size: 2rem; display: block; margin-bottom: .5rem; }
+.empty-state h3, .meltdown-banner h3 { color: var(--text); margin-bottom: .35rem; }
+.meltdown-banner {
+ margin-top: 1rem; border-color: var(--accent); border-style: solid;
+ background: color-mix(in srgb, var(--accent) 12%, var(--panel));
+}
+.story-card.just-added { outline: 3px dashed var(--accent); outline-offset: 2px; }
+
+/* ---------- 7b. Admin panel (backstage / control-room, not on-air) ----------
+ Deliberately fixed slate/mono palette independent of --mood vars so it
+ never gets confused with the in-universe broadcast chrome. Renders in
+ normal document flow (an expand/collapse section, not an overlay) so it
+ can never overlap content or fight the ticker/vignette z-index stack. */
+.admin-panel {
+ max-width: 1200px;
+ margin: 1rem auto 0;
+ padding: 1rem 1.25rem 1.25rem;
+ border-radius: .6rem;
+ background: #1b1f24;
+ color: #e8eaed;
+ border: 2px dashed #4a5057;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+}
+.admin-panel h2 { font-size: .95rem; letter-spacing: .5px; text-transform: uppercase; color: #7dd3fc; margin-bottom: .15rem; }
+.admin-panel h3 { font-size: .78rem; letter-spacing: .4px; text-transform: uppercase; color: #b8c0c8; margin-bottom: .5rem; }
+.admin-hint { font-size: .72rem; color: #9aa4ad; margin-bottom: 1rem; font-style: italic; }
+.admin-panel hr { border: none; border-top: 1px dashed #3a4048; margin: 1.1rem 0; }
+
+.admin-form { display: grid; gap: .5rem; }
+.admin-form label { font-size: .7rem; text-transform: uppercase; letter-spacing: .4px; color: #b8c0c8; }
+.admin-form input[type="text"], .admin-form select {
+ width: 100%; padding: .5rem .6rem; border-radius: .35rem;
+ border: 1.5px solid #4a5057; background: #262b31; color: #f2f4f6;
+ font-family: inherit; font-size: .85rem;
+}
+.admin-form input[aria-invalid="true"] { border-color: #ff6b6b; }
+.admin-form input[type="text"]:focus-visible,
+.admin-form select:focus-visible,
+.admin-panel button:focus-visible {
+ outline: 3px solid #7dd3fc; outline-offset: 2px;
+}
+.admin-error { font-size: .78rem; color: #ff8b8b; font-weight: 700; }
+.admin-success { font-size: .78rem; color: #7dffb0; font-weight: 700; }
+.admin-submit-btn {
+ justify-self: start; margin-top: .2rem;
+ cursor: pointer; border: none; border-radius: .35rem;
+ padding: .55rem 1.1rem; font-weight: 800; font-size: .82rem;
+ background: #7dd3fc; color: #0b1a24;
+}
+.admin-reset-btn {
+ justify-self: start; margin-top: 1rem;
+ cursor: pointer; border: 1.5px solid #4a5057; border-radius: .35rem;
+ padding: .5rem 1rem; font-weight: 700; font-size: .78rem;
+ background: transparent; color: #e8eaed;
+}
+.admin-reset-btn:hover { background: #262b31; }
+
+.admin-refresh-row { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin-top: .6rem; }
+.admin-refresh-btn {
+ justify-self: start;
+ cursor: pointer; border: 1.5px solid #7dd3fc; border-radius: .35rem;
+ padding: .5rem 1rem; font-weight: 700; font-size: .78rem;
+ background: transparent; color: #7dd3fc;
+}
+.admin-refresh-btn:hover { background: #7dd3fc22; }
+.admin-refresh-btn:disabled { opacity: .55; cursor: wait; }
+.admin-refresh-status { font-size: .75rem; color: #9aa4ad; }
+.admin-refresh-status.is-error { color: #ff8b8b; }
+.admin-refresh-status.is-ok { color: #8ce99a; }
+
+.admin-story-list { list-style: none; margin: 0 0 .5rem; padding: 0; display: grid; gap: .5rem; max-height: 16rem; overflow-y: auto; }
+.admin-story-row {
+ /* min-width: 0 overrides the browser's implicit min-width:auto on grid
+ items — without it this row (and its nowrap/ellipsis child text) won't
+ shrink below its content's min-content width, pushing the Delete
+ button off-screen at narrow viewports (the same class of bug as the
+ ticker/fieldset overflow fixed earlier in this project). */
+ min-width: 0;
+ display: flex; align-items: center; justify-content: space-between; gap: .75rem;
+ padding: .5rem .6rem; border: 1px solid #343a40; border-radius: .4rem; background: #22262b;
+}
+.admin-story-info { flex: 1 1 auto; display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
+.admin-story-cat { font-size: .62rem; text-transform: uppercase; letter-spacing: .4px; color: #7dd3fc; }
+.admin-story-headline { font-size: .8rem; color: #e8eaed; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
+.admin-delete-btn {
+ flex: 0 0 auto; cursor: pointer; border: 1.5px solid #ff6b6b; background: transparent;
+ color: #ff8b8b; border-radius: .35rem; padding: .35rem .6rem; font-weight: 700; font-size: .72rem;
+}
+.admin-delete-btn:hover { background: #ff6b6b22; }
+.admin-empty { font-size: .8rem; color: #9aa4ad; padding: .5rem 0; }
+@media (min-width: 768px) {
+ .admin-panel-body { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; align-items: start; }
+}
+.admin-form textarea {
+ width: 100%; padding: .5rem .6rem; border-radius: .35rem;
+ border: 1.5px solid #4a5057; background: #262b31; color: #f2f4f6;
+ font-family: inherit; font-size: .85rem; resize: vertical; min-height: 6rem;
+}
+.admin-form-hint { font-size: .68rem; color: #9aa4ad; margin-top: -.25rem; }
+
+/* ---------- 7c. Feature articles ("More From PANDEMONIUM-24") ---------- */
+.features-section {
+ max-width: 1200px;
+ margin: 1.25rem auto 0;
+ padding: 1rem 1rem 1.5rem;
+}
+.features-grid { display: grid; grid-template-columns: 1fr; gap: 1rem; }
+.feature-card {
+ background: var(--panel);
+ border: 1px solid var(--panel-border);
+ border-radius: .6rem;
+ padding: 1rem;
+ display: flex; flex-direction: column; gap: .5rem;
+ box-shadow: 0 4px 18px -10px #00000066;
+}
+.feature-card h3 { font-size: 1.05rem; line-height: 1.3; }
+.features-empty-note { color: var(--text-dim); font-size: .85rem; padding: .5rem 0; }
+@media (min-width: 768px) { .features-grid { grid-template-columns: repeat(2, 1fr); } }
+@media (min-width: 1024px) { .features-grid { grid-template-columns: repeat(3, 1fr); } }
+
+/* Shared "open article" link treatment (story cards + feature cards) */
+.story-card h3 a, .feature-card h3 a { color: inherit; text-decoration: none; }
+.story-card h3 a:hover, .feature-card h3 a:hover { text-decoration: underline; }
+.story-read-more {
+ align-self: flex-start; margin-top: .1rem;
+ font-size: .82rem; font-weight: 700; color: var(--accent2);
+ text-decoration: none;
+}
+.story-read-more:hover { text-decoration: underline; }
+
+/* ---------- 7d. Article view (hash-routed, #/article/<id>) ----------
+ Long-form reading layout: comfortable measure (~65ch), generous
+ line-height, still inherits the current category mood's palette. */
+.article-view { max-width: 100%; }
+.article-inner {
+ max-width: 68ch;
+ margin: 1rem auto 0;
+ padding: 1.25rem 1.25rem 2rem;
+ background: var(--panel);
+ border-radius: .75rem;
+ box-shadow: inset 0 0 0 1px var(--panel-border), inset 0 40px 80px -40px #00000055;
+ color: var(--text);
+}
+.article-back-btn {
+ cursor: pointer; border: 1.5px solid var(--panel-border); background: transparent;
+ color: var(--text); font-weight: 700; font-size: .85rem;
+ padding: .5rem .8rem; border-radius: .4rem; margin-bottom: 1.1rem;
+}
+.article-back-btn:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
+.article-cat-badge { display: inline-block; margin-bottom: .6rem; }
+.article-headline { font-size: clamp(1.4rem, 1.6vw + 1rem, 2.1rem); line-height: 1.2; margin-bottom: .5rem; }
+.article-dek { font-size: 1.05rem; color: var(--text-dim); line-height: 1.5; margin-bottom: .9rem; }
+.article-meta-row {
+ font-size: .8rem; color: var(--text-dim); font-weight: 700;
+ display: flex; gap: .75rem; flex-wrap: wrap;
+ padding-bottom: .85rem; margin-bottom: 1.1rem;
+ border-bottom: 1px dashed var(--panel-border);
+}
+.article-dateline { letter-spacing: .3px; }
+.article-photo { margin: 0 0 1.25rem; }
+.article-photo svg { width: 100%; height: auto; border-radius: .5rem; display: block; }
+.article-photo figcaption { font-size: .78rem; color: var(--text-dim); font-style: italic; margin-top: .45rem; }
+.article-body p {
+ margin: 0 0 1.1rem; font-size: 1.02rem; line-height: 1.75; max-width: 65ch;
+}
+.article-body p.kicker { font-style: italic; color: var(--text-dim); }
+.live-update-block {
+ border: 2px dashed var(--accent); border-radius: .5rem;
+ padding: .8rem 1rem; margin: 0 0 1.25rem;
+ background: color-mix(in srgb, var(--accent) 10%, var(--panel));
+}
+.live-update-label {
+ display: block; font-weight: 900; font-size: .74rem; letter-spacing: .5px;
+ text-transform: uppercase; color: var(--accent2); margin-bottom: .4rem;
+}
+.live-update-headline { font-weight: 800; margin-bottom: .3rem; }
+.live-update-detail { color: var(--text-dim); font-size: .92rem; }
+.live-update-concluded { margin-top: .5rem; }
+.article-source-link { margin-top: 1rem; }
+.article-source-link a {
+ display: inline-block;
+ color: var(--accent);
+ font-weight: 800;
+ text-decoration: none;
+ border: 1px solid var(--accent);
+ border-radius: 6px;
+ padding: .5rem .9rem;
+}
+.article-source-link a:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
+.related-section { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px dashed var(--panel-border); }
+.related-section h3 { font-size: .85rem; text-transform: uppercase; letter-spacing: .5px; color: var(--accent); margin-bottom: .6rem; }
+.related-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
+.related-list a { color: var(--accent2); font-weight: 700; }
+.article-not-found { text-align: center; }
+.article-not-found h2 { margin-bottom: .5rem; }
+@media (min-width: 768px) { .article-inner { padding: 1.75rem 2rem 2.25rem; } }
+@media (min-width: 1024px) { .article-inner { padding: 2rem 2.5rem 2.5rem; } }
+
+/* ---------- 8. Focus & a11y helpers ---------- */
+a, button, input, [tabindex] { outline-offset: 2px; }
+:focus-visible {
+ outline: 3px solid var(--focus);
+ outline-offset: 2px;
+ border-radius: 2px;
+}
+.klaxon-btn:focus-visible { outline: 3px solid var(--focus); }
+
+/* ---------- 8b. CHAOS INDEX — global escalation gauge + broadcast decay ----------
+ One computed number (0-100) drives everything below via body.chaos-tier-0..3
+ classes + a couple of CSS custom properties. Every rule here targets the
+ chrome/body/container level, NEVER the story-card markup itself, so
+ patchStoryGrid()'s keyed reconcile never has to re-render a card just
+ because chaos ticked up (headline stamps are pure ::before content). */
+.chaos-row {
+ /* flex-basis 100% inside .chrome's flex-wrap:wrap always forces this onto
+ its own line, so its own width/content changes can never reflow the
+ brand/live-clock/controls line above it. flex-wrap:wrap on ITS OWN
+ children is the overflow safety valve at narrow widths (390px) — if the
+ fixed-width band/pct pieces don't fit alongside the track, they wrap to
+ a second line inside this row instead of ever overflowing horizontally. */
+ flex: 1 1 100%;
+ display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .6rem;
+ padding: .4rem 0 0;
+ margin-top: .4rem;
+ border-top: 1px dashed var(--panel-border);
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ font-size: .72rem;
+}
+.chaos-tag { flex: 0 0 auto; font-weight: 900; letter-spacing: .6px; color: var(--text-dim); }
+/* Fixed min-width sized to the longest label ("Post-Journalism") so the
+ readout's own box never changes width as the label text changes length —
+ the one thing that could otherwise nudge the controls sitting above it. */
+.chaos-band { flex: 0 0 auto; min-width: 10.5rem; font-weight: 800; letter-spacing: .3px; color: var(--text); }
+.chaos-band-icon { margin-right: .3rem; }
+.chaos-track {
+ flex: 1 1 auto; max-width: 14rem; height: .55rem; border-radius: 999px;
+ background: var(--panel-border); overflow: hidden; position: relative;
+}
+.chaos-fill {
+ position: absolute; inset: 0 auto 0 0; width: 0%;
+ background: var(--accent); border-radius: inherit;
+ transition: width .5s ease, background-color .4s ease;
+}
+.chaos-pct { flex: 0 0 auto; min-width: 3ch; text-align: right; font-variant-numeric: tabular-nums; color: var(--text-dim); }
+
+body.chaos-tier-1 .chaos-fill { background: #e0a300; }
+body.chaos-tier-2 .chaos-fill { background: #ff8c00; }
+body.chaos-tier-3 .chaos-fill { background: #ff2222; }
+
+/* Ticker degradation: each tier shortens the scroll loop so the chyron
+ visibly speeds up. The reduced-motion path never uses this animation at
+ all (ticker-track animation is fully disabled + JS swaps to the static
+ frame renderer), so this is motion-safe by construction. */
+/* Duration is set from JS (setTickerSpeed) as track length / reading speed,
+ so the crawl moves at a constant, readable px/s however many headlines
+ are in the loop. 90s is only the pre-measure fallback. */
+.ticker-track { animation: ticker-scroll var(--ticker-dur, 90s) linear infinite; }
+
+/* Broadcast losing its grip: headline stamps via generated content only —
+ zero markup change, so these can flip every tick without ever touching
+ patchStoryGrid()'s diffing. */
+body.chaos-tier-1 .story-card:not(.concluded) h3 a::before,
+body.chaos-tier-2 .story-card:not(.concluded) h3 a::before,
+body.chaos-tier-3 .story-card:not(.concluded) h3 a::before {
+ content: "[DEVELOPING] "; color: var(--accent2); font-weight: 900;
+}
+
+/* Lower-third anchor caption — appears at tier 2+, cycles a fixed set of
+ "anchor notes" that get more unhinged per tier. position:fixed, so
+ showing/hiding it never reflows anything in normal document flow. */
+.anchor-caption {
+ position: fixed; left: 0; right: 0; bottom: 3rem; z-index: 19;
+ padding: .4rem .9rem;
+ background: color-mix(in srgb, var(--chrome) 88%, #000 12%);
+ color: var(--text);
+ border-top: 1px dashed var(--accent2);
+ font-size: .78rem; font-weight: 700; font-style: italic;
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+}
+
+/* TV-bezel tilt — the container physically drifts a fraction of a degree at
+ max tier. Gated entirely behind (prefers-reduced-motion: no-preference)
+ so reduced-motion users NEVER get the transform; the unconditional
+ box-shadow rule right below is their static equivalent (a hot red edge
+ instead of a tilt). Only the broadcast container tilts — chrome/nav/admin
+ controls are untouched so their click targets never move. */
+body.chaos-tier-3 main#channelContainer {
+ box-shadow: inset 0 0 0 1px var(--panel-border), inset 0 40px 80px -40px #00000055, inset 0 0 0 3px #ff222277;
+}
+@media (prefers-reduced-motion: no-preference) {
+ main#channelContainer { transition: transform .6s ease; }
+ body.chaos-tier-3 main#channelContainer { transform: rotate(.6deg); }
+}
+
+/* PLEASE STAND BY — full-screen flash at chaos 100 (all stories concluded).
+ pointer-events:none the whole time, so it can cover the entire viewport
+ without ever blocking a click on the klaxon/chips/admin/article links
+ underneath it. JS never arms this under reduced motion — the existing
+ static #meltdownBanner text is that audience's equivalent. Blink cycle is
+ .7s (~1.4Hz, well under the 3-flashes-per-second ceiling) x4 then it's
+ hidden again by JS, so it's transient, not a permanent strobe. */
+.standby-flash {
+ position: fixed; inset: 0; z-index: 30; pointer-events: none;
+ display: flex; align-items: center; justify-content: center;
+ background: rgba(0,0,0,.6); color: #fff; text-align: center;
+ font-weight: 900; letter-spacing: 2px;
+ font-size: clamp(1.1rem, 1rem + 2vw, 2.2rem);
+}
+.standby-flash.flashing { animation: standby-blink .7s steps(1, end) 4; }
+@keyframes standby-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
+
+/* ---------- 9. Responsive breakpoints ---------- */
+@media (min-width: 560px) {
+ .controls { margin-left: 0; }
+}
+@media (min-width: 768px) {
+ .chrome { padding: 1rem 1.5rem; }
+ .category-filter { padding: .75rem 1.5rem; }
+ main#channelContainer { padding: 1.25rem 1.5rem 1.75rem; }
+ .admin-panel { padding: 1.25rem 1.5rem 1.75rem; }
+ .story-grid { grid-template-columns: repeat(2, 1fr); }
+ .story-card.lead { grid-column: 1 / -1; }
+}
+@media (min-width: 1024px) {
+ .chrome { padding: 1rem 2rem; }
+ .category-filter { padding: .75rem 2rem; }
+ main#channelContainer { padding: 1.5rem 2rem 2rem; }
+ .admin-panel { padding: 1.5rem 2rem 2rem; }
+ .story-grid { grid-template-columns: repeat(3, 1fr); align-items: stretch; }
+ .story-card.lead {
+ grid-column: 1 / -1;
+ display: grid; grid-template-columns: 3fr 2fr; column-gap: 1.5rem; row-gap: .55rem;
+ align-content: start;
+ }
+ .story-card.lead > * { grid-column: 2; }
+ .story-card.lead > .story-photo {
+ grid-column: 1; grid-row: 1 / span 8;
+ width: calc(100% + 1rem); margin: -1rem 0 -1rem -1rem;
+ height: calc(100% + 2rem); border-radius: .6rem 0 0 .6rem;
+ }
+}
+@media (min-width: 1280px) {
+ .wordmark { letter-spacing: .8px; }
+ .ticker-track { font-size: .92rem; }
+}
+
+/* ---------- 10. Reduced-motion overrides ---------- */
+@media (prefers-reduced-motion: reduce) {
+ * { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
+ .rec-dot { animation: none; opacity: 1; }
+ .klaxon-btn[aria-pressed="true"] { animation: none; box-shadow: 0 0 0 4px #ff222255; }
+ .breaking-banner { animation: none; background: #b30000; }
+ body.breaking-mode::before { animation: none; opacity: .8; }
+ .ticker-track { animation: none; }
+}
+
+/* ============================================================
+ PART 2 — CARTOON GALLERY (cartoons/index.html only)
+ Additive rules on top of Part 1's shared reset/palette/chrome/
+ chip-row (the gallery page reuses those verbatim). Only the
+ gallery-specific chrome/grid/card/controls rules live here.
+ ============================================================ */
+:root { --card-w: 300px; }
+
+/* Gallery page has no fixed ticker, so drop the site body's ticker
+ clearance and slightly faster palette transition. */
+body { padding-bottom: 0; transition: background-color .4s ease, color .4s ease; }
+
+.chrome h1 { font-size: 1.15rem; letter-spacing: .02em; margin: 0; }
+.chrome a.home { color: var(--text-dim); text-decoration: none; font-size: .85rem; border: 1px solid var(--panel-border); padding: 6px 10px; border-radius: 6px; }
+.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 sits inside <main> here (not the sticky sub-nav band
+ used on the front page), so it just needs its own bottom margin —
+ the chip-row/chip-item rules themselves are shared from Part 1. */
+.category-filter { margin-bottom: 18px; }
+
+.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); }
+.controls select { background: var(--chrome); color: var(--text); border: 1px solid var(--panel-border); border-radius: 6px; padding: 4px 8px; }
+.view-toggle { display: flex; gap: 4px; }
+.view-toggle button { background: var(--chrome); color: var(--text-dim); border: 1px solid var(--panel-border); border-radius: 6px; padding: 5px 12px; font-size: .8rem; cursor: pointer; font-family: inherit; }
+.view-toggle button:hover { color: var(--text); }
+.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; }
+
+/* ---------- Per-category card accent ----------
+ Each card carries data-cat, which sets --cat-accent to that category's
+ mood --accent (see Part 1's body[data-mood] blocks). Keeps cards
+ multicolored in the "All Categories" view regardless of the page's
+ current mood. */
+.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; 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); }
+.grid.list-mode { display: flex; flex-direction: column; gap: 10px; }
+.grid.list-mode .card { flex-direction: row; align-items: stretch; }
+.grid.list-mode .card:hover, .grid.list-mode .card:focus-visible { transform: translateX(3px); }
+.grid.list-mode .card .thumb { aspect-ratio: auto; width: 120px; min-width: 120px; font-size: 1.6rem; }
+.grid.list-mode .card .body { flex: 1; justify-content: center; min-width: 0; overflow: hidden; }
+.grid.list-mode .card .title, .grid.list-mode .card .blurb { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+@media (max-width: 480px) {
+ .grid.list-mode .card .thumb { width: 72px; min-width: 72px; font-size: 1.2rem; }
+ .grid.list-mode .card .body { padding: 10px; }
+}
+.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; } }
+
+
+/* ============================================================
+ PART 3 — CARTOON STRIP PAGES (cartoons/2026*.html)
+ The 18 individual strip pages were built against an older
+ "parchment newspaper" token set (--ink, --ink-dim, --rule,
+ --seal) and a per-file light/dark toggle. Rather than rewrite
+ every strip's markup/JS (several draw their SVG panels by
+ reading these var() names directly at render time), these
+ names are aliased onto the shared mint-green token system so
+ the strips automatically re-theme with it — including the
+ per-category body[data-mood] palettes in Part 1.
+
+ IMPORTANT: this alias block is declared on the bare `body`
+ selector (NOT `:root`/html). Custom-property var() references
+ are substituted using the referenced property's cascaded value
+ on the SAME element where the alias is declared — declaring it
+ on :root would freeze --ink-dim etc. using html's own (default
+ "all" mood) --text-dim, which does not update when a specific
+ body[data-mood="..."] block further down overrides --text-dim
+ only on <body>. Declaring the alias directly on `body` makes it
+ resolve using body's own final cascaded --text/--text-dim/
+ --accent/--accent2 (whichever mood block — or the dark/light
+ toggle override below — won), so it repaints correctly. */
+body {
+ --ink: var(--text);
+ --ink-dim: var(--text-dim);
+ --rule: var(--accent);
+ --seal: var(--accent2);
+}
+
+/* Universal link color the strips relied on (front page styles its
+ links per-context instead, so this was missing from Part 1). */
+a { color: var(--accent2); }
+
+/* Each strip ships its own "Dark mode: on/off" toggle button that sets
+ data-theme on <html> (see each file's inline script). IMPORTANT: 17
+ of the 18 strips default to explicitly setting data-theme="light"
+ on load (only one has a true 3-state auto/dark/light toggle) — so
+ "light" is each page's normal/default state, not a user opt-in. It
+ must stay a NO-OP here (no bg/panel/text override) so the active
+ body[data-mood="..."] palette from Part 1 is what actually renders
+ by default. Only the explicit "dark" state (reached by the user
+ clicking the toggle button, or a real OS dark preference on the one
+ auto-capable strip) dims things further, as a night-viewing variant
+ layered on top of whichever mood is active. These overrides target
+ the BODY element directly (not :root) so they win over the
+ per-category body[data-mood="..."] blocks in Part 1 that also
+ declare these same custom properties directly on <body>. Only bg/
+ panel/text are touched — --accent/--accent2 stay mood-colored so a
+ politics strip in dark mode still reads as politics-red, just on a
+ darker backdrop. */
+html[data-theme="dark"] body {
+ --bg: #0a0f0c; --bg2: #10201780; --panel: #141f18; --panel-border: #2c3d3255;
+ --text: #eef7f0; --text-dim: #c3d4c9aa;
+}
+@media (prefers-color-scheme: dark) {
+ html:not([data-theme="light"]):not([data-theme="dark"]) body {
+ --bg: #0a0f0c; --bg2: #10201780; --panel: #141f18; --panel-border: #2c3d3255;
+ --text: #eef7f0; --text-dim: #c3d4c9aa;
+ }
+}
diff --git a/cartoons/20260924-budget-pie-fight.html b/cartoons/20260924-budget-pie-fight.html
index cae7fad..e75ffa1 100644
--- a/cartoons/20260924-budget-pie-fight.html
+++ b/cartoons/20260924-budget-pie-fight.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Who Gets the Last Slice — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire strip: an invented town budget is a pie that diners keep re-slicing smaller at a banquet table. Drag the slider to re-carve it and watch the caption rewrite live." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -57,12 +39,11 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
.slider-row input[type=range]{width:100%;accent-color:var(--accent)}
footer{max-width:900px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
.seal-anim{transition:transform .3s ease}
@media (prefers-reduced-motion: reduce){ .seal-anim{transition:none} }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire</div>
diff --git a/cartoons/20260924-casserole-succession.html b/cartoons/20260924-casserole-succession.html
index 17c689c..a6576c8 100644
--- a/cartoons/20260924-casserole-succession.html
+++ b/cartoons/20260924-casserole-succession.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Casserole Succession — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: Savannah imposes term limits on its ceremonial Bean Queen, the sash won't come off, and a courthouse finally settles it." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire · Savannah, GA</div>
diff --git a/cartoons/20260924-commission-turtles.html b/cartoons/20260924-commission-turtles.html
index c4fa735..5adb13a 100644
--- a/cartoons/20260924-commission-turtles.html
+++ b/cartoons/20260924-commission-turtles.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Commissions All the Way Down — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire strip: a blue-ribbon commission recommends forming a commission to study its own recommendation, nesting smaller and smaller." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -52,12 +34,11 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:900px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
.seal-anim{transition:transform .3s ease}
@media (prefers-reduced-motion: reduce){ .seal-anim{transition:none} }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire</div>
diff --git a/cartoons/20260924-conrad-filibuster-qwen25-7b.html b/cartoons/20260924-conrad-filibuster-qwen25-7b.html
index b75961d..7130643 100644
--- a/cartoons/20260924-conrad-filibuster-qwen25-7b.html
+++ b/cartoons/20260924-conrad-filibuster-qwen25-7b.html
@@ -2,19 +2,31 @@
<html lang="en">
<head>
<meta charset="UTF-8">
- <title>The Filibuster Marathon</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>The Filibuster Marathon — P24 Political Cartoon Desk</title>
+ <meta name="description" content="An original invented-satire cartoon: a filibustering delegate stands flat and unmoved as the chamber clock winds on. Model Arena pick (qwen25-7b)." />
+ <link rel="stylesheet" href="../assets/style.css" />
<style>
body {
- background-color: #cdf5e0;
- color: black;
- font-family: 'Times New Roman', serif;
display: flex;
+ flex-direction: column;
justify-content: center;
align-items: center;
- height: 100vh;
- margin: 0;
- overflow: hidden;
+ min-height: 100vh;
+ gap: 1rem;
+ padding: 1rem;
}
+ .back {
+ align-self: flex-start;
+ font-family: -apple-system, sans-serif;
+ font-size: .8rem;
+ color: var(--text-dim);
+ text-decoration: none;
+ border: 1px solid var(--panel-border);
+ border-radius: 6px;
+ padding: 5px 10px;
+ }
+ .back:hover { color: var(--text); }
.cartoon {
width: 100%;
max-width: 1440px;
@@ -24,19 +36,21 @@
.title {
font-size: 2em;
margin-bottom: 1em;
+ color: var(--text);
}
.lectern, .delegate, .phonebook, .clock {
- fill: black;
+ fill: var(--text);
}
.clock-dial, .clock-hand, .chamber-members {
- stroke: black;
+ stroke: var(--text);
}
.accent {
- color: #0f9d78;
+ color: var(--accent);
}
</style>
</head>
-<body>
+<body data-mood="politics">
+<a class="back" href="index.html">← Cartoon Desk</a>
<div class="cartoon">
<div class="title">The Filibuster Marathon</div>
<svg width="100%" height="400" viewBox="0 0 500 400">
diff --git a/cartoons/20260924-filibuster-vs-cloture.html b/cartoons/20260924-filibuster-vs-cloture.html
index 6eafd5e..68f1cbd 100644
--- a/cartoons/20260924-filibuster-vs-cloture.html
+++ b/cartoons/20260924-filibuster-vs-cloture.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cloture o'Clock — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire strip: the Forum of Delegates' ceremonial floor clock is wound tight by one side and wound right back by the other, ticking forward and backward until it just gives up." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -52,12 +34,11 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:900px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
.seal-anim{transition:transform .3s ease}
@media (prefers-reduced-motion: reduce){ .seal-anim{transition:none} }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire</div>
diff --git a/cartoons/20260924-four-minutes-ahead.html b/cartoons/20260924-four-minutes-ahead.html
index 31f3db0..be7bfc3 100644
--- a/cartoons/20260924-four-minutes-ahead.html
+++ b/cartoons/20260924-four-minutes-ahead.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Four Minutes Ahead of Everyone — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: Bozeman sets its town clock four minutes fast, declares its own time zone, and petitions to secede from Mountain Time entirely." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire · Bozeman, MT</div>
diff --git a/cartoons/20260924-gnome-who-wouldnt-hush.html b/cartoons/20260924-gnome-who-wouldnt-hush.html
index d1a5f7f..e110f9f 100644
--- a/cartoons/20260924-gnome-who-wouldnt-hush.html
+++ b/cartoons/20260924-gnome-who-wouldnt-hush.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Gnome Who Wouldn't Hush — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: Duluth taxes loud lawn gnomes, registers them, ankle-monitors repeat offenders, and finally convenes a Gnome Parole Board." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire · Duluth, MN</div>
diff --git a/cartoons/20260924-hedge-non-aggression-pact.html b/cartoons/20260924-hedge-non-aggression-pact.html
index c0f0ac5..dfd9ba3 100644
--- a/cartoons/20260924-hedge-non-aggression-pact.html
+++ b/cartoons/20260924-hedge-non-aggression-pact.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Hedge Non-Aggression Pact — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: Washington regulates statue-shaped hedges, fines a homeowner, gets accused of hedge espionage, and signs a topiary treaty with Idaho." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire · Spokane, WA</div>
diff --git a/cartoons/20260924-jackalope-custody-battle.html b/cartoons/20260924-jackalope-custody-battle.html
index 716f17e..a2c84b6 100644
--- a/cartoons/20260924-jackalope-custody-battle.html
+++ b/cartoons/20260924-jackalope-custody-battle.html
@@ -5,26 +5,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" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="entertainment">
<header>
<div>
<div class="kicker">P24 Cartoon Desk · Invented Satire · Story City, IA</div>
diff --git a/cartoons/20260924-lobbyist-revolving-door.html b/cartoons/20260924-lobbyist-revolving-door.html
index fb35ce0..5b3d57f 100644
--- a/cartoons/20260924-lobbyist-revolving-door.html
+++ b/cartoons/20260924-lobbyist-revolving-door.html
@@ -5,26 +5,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" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -53,12 +35,11 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
.counter{font-family:-apple-system,sans-serif;font-size:.8rem;font-weight:700;color:var(--ink);border:2px dashed var(--seal);border-radius:6px;padding:6px 12px;white-space:nowrap}
footer{max-width:900px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
.door-anim{transition:transform .45s ease}
@media (prefers-reduced-motion: reduce){ .door-anim{transition:none} }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire</div>
diff --git a/cartoons/20260924-meter-that-wouldnt-yield.html b/cartoons/20260924-meter-that-wouldnt-yield.html
index 2c029ee..e64b415 100644
--- a/cartoons/20260924-meter-that-wouldnt-yield.html
+++ b/cartoons/20260924-meter-that-wouldnt-yield.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Meter That Wouldn't Yield — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: Erie's most reliable parking meter is honored, seated, re-elected, and sworn in as honorary mayor — and then won't leave the office." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire · Erie, PA</div>
diff --git a/cartoons/20260924-pork-barrel-train.html b/cartoons/20260924-pork-barrel-train.html
index 5b2331f..f809ff4 100644
--- a/cartoons/20260924-pork-barrel-train.html
+++ b/cartoons/20260924-pork-barrel-train.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Pork Barrel Express — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire strip: a fictional infrastructure bill turns into a runaway train as wildly unrelated riders couple on car by car. Click a car for its invented cost." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -54,12 +36,11 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:900px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
.seal-anim{transition:transform .3s ease}
@media (prefers-reduced-motion: reduce){ .seal-anim{transition:none} }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire</div>
diff --git a/cartoons/20260924-quorum-call-hide-seek.html b/cartoons/20260924-quorum-call-hide-seek.html
index f7d5b51..8f197fb 100644
--- a/cartoons/20260924-quorum-call-hide-seek.html
+++ b/cartoons/20260924-quorum-call-hide-seek.html
@@ -5,26 +5,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" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -52,12 +34,11 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:900px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
.seal-anim{transition:transform .3s ease}
@media (prefers-reduced-motion: reduce){ .seal-anim{transition:none} }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire</div>
diff --git a/cartoons/20260924-row-by-row-only.html b/cartoons/20260924-row-by-row-only.html
index e5ffe2e..9c5c535 100644
--- a/cartoons/20260924-row-by-row-only.html
+++ b/cartoons/20260924-row-by-row-only.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Row by Row Only — P24 Political Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: Kansas legislates the one true way to eat corn on the cob, deputizes Corn Marshals, and bans the cheating devices known as cob holders." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="politics">
<header>
<div>
<div class="kicker">P24 Political Cartoon Desk · Invented Satire · Topeka, KS</div>
diff --git a/cartoons/20260924-the-corner-with-its-own-weather.html b/cartoons/20260924-the-corner-with-its-own-weather.html
index de74139..e27d213 100644
--- a/cartoons/20260924-the-corner-with-its-own-weather.html
+++ b/cartoons/20260924-the-corner-with-its-own-weather.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Corner With Its Own Weather — P24 Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: a persistent crosswind at one small-town intersection escalates from a windsock to a paid Wind Spotter to an annual Hat-Chasing Festival." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="weather">
<header>
<div>
<div class="kicker">P24 Cartoon Desk · Invented Satire · Cut Bank, MT</div>
diff --git a/cartoons/20260924-the-zipper-that-ended-an-inning.html b/cartoons/20260924-the-zipper-that-ended-an-inning.html
index 1a9dff0..6379069 100644
--- a/cartoons/20260924-the-zipper-that-ended-an-inning.html
+++ b/cartoons/20260924-the-zipper-that-ended-an-inning.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Zipper That Ended an Inning — P24 Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: a minor league mascot's costume zipper failure escalates into a league-wide mascot-safety bureaucracy." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="sports">
<header>
<div>
<div class="kicker">P24 Cartoon Desk · Invented Satire · Chillicothe, OH</div>
diff --git a/cartoons/20260924-tollpoints-diamond-status.html b/cartoons/20260924-tollpoints-diamond-status.html
index 0dd1e9a..3b3ab35 100644
--- a/cartoons/20260924-tollpoints-diamond-status.html
+++ b/cartoons/20260924-tollpoints-diamond-status.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Booth That Became an Airline — P24 Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: Ashtabula's toll bridge loyalty program escalates from gift-card points to a Gold Lounge to its own currency and chartered airline." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="business">
<header>
<div>
<div class="kicker">P24 Cartoon Desk · Invented Satire · Ashtabula, OH</div>
diff --git a/cartoons/20260924-tuber-bank-of-chugwater.html b/cartoons/20260924-tuber-bank-of-chugwater.html
index 8097dfd..f71b973 100644
--- a/cartoons/20260924-tuber-bank-of-chugwater.html
+++ b/cartoons/20260924-tuber-bank-of-chugwater.html
@@ -5,26 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Tuber Bank of Chugwater — P24 Cartoon Desk</title>
<meta name="description" content="An original invented-satire cartoon: a Wyoming lab's potato-battery pilot escalates from soil sensors to a custom potato cultivar to a wired 'tuber bank' backing the county grid." />
+<link rel="stylesheet" href="../assets/style.css" />
<style>
-:root{
- --bg:#f4efe4; --panel:#fffdf6; --ink:#241c14; --ink-dim:#6b5f4d; --rule:#241c14;
- --accent:#b5302f; --accent2:#0f6b52; --focus:#0b3d91; --seal:#c9a227;
-}
-@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}}
-:root[data-theme="dark"]{
- --bg:#1b160f; --panel:#241d14; --ink:#f2ead9; --ink-dim:#c9bda3; --rule:#f2ead9;
- --accent:#ff6b64; --accent2:#3ddcb0; --focus:#8ab4ff; --seal:#e0c25a;
-}
-*,*::before,*::after{box-sizing:border-box}
-html{overflow-x:hidden}
-[hidden]{display:none!important}
-body{margin:0;min-height:100vh;background:var(--bg);color:var(--ink);font-family:Georgia,"Times New Roman",serif;line-height:1.4;padding-bottom:2rem}
-h1,h2,p{margin:0}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-a{color:var(--accent2)}
header{padding:16px 20px 10px;border-bottom:3px solid var(--rule);display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:10px}
header a.back{font-family:-apple-system,sans-serif;font-size:.8rem;color:var(--ink-dim);text-decoration:none;border:1px solid var(--ink-dim);border-radius:6px;padding:5px 10px}
.kicker{font-family:-apple-system,sans-serif;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
@@ -46,10 +28,9 @@ button.theme{margin-left:auto;background:transparent;border:1px solid var(--ink-
#status.info{border-color:var(--focus);color:var(--focus)}
footer{max-width:640px;margin:20px auto 0;padding:0 20px;font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim)}
.hint{font-family:-apple-system,sans-serif;font-size:.72rem;color:var(--ink-dim);margin-top:6px}
-@media (prefers-reduced-motion: reduce){ * { animation-duration:0.001ms !important; transition-duration:0.001ms !important; } }
</style>
</head>
-<body>
+<body data-mood="scitech">
<header>
<div>
<div class="kicker">P24 Cartoon Desk · Invented Satire · Chugwater, WY</div>
diff --git a/cartoons/index.html b/cartoons/index.html
index 7cbd09e..4d898e7 100644
--- a/cartoons/index.html
+++ b/cartoons/index.html
@@ -5,170 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<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>
-/* ---------- 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;
-}
-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}
-:focus-visible{outline:3px solid var(--focus); outline-offset:2px}
-.chrome{background:var(--chrome); border-bottom:3px solid var(--accent); padding:14px 20px; display:flex; align-items:center; gap:14px; flex-wrap:wrap}
-.logo-badge{background:var(--accent); color:var(--chrome); font-weight:900; padding:4px 10px; border-radius:4px; letter-spacing:.05em}
-.chrome h1{font-size:1.15rem; letter-spacing:.02em}
-.chrome a.home{color:var(--text-dim); text-decoration:none; font-size:.85rem; border:1px solid var(--panel-border); padding:6px 10px; border-radius:6px}
-.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)}
-.controls select{background:var(--chrome); color:var(--text); border:1px solid var(--panel-border); border-radius:6px; padding:4px 8px}
-.view-toggle{display:flex; gap:4px}
-.view-toggle button{background:var(--chrome); color:var(--text-dim); border:1px solid var(--panel-border); border-radius:6px; padding:5px 12px; font-size:.8rem; cursor:pointer; font-family:inherit}
-.view-toggle button:hover{color:var(--text)}
-.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}
-
-/* ---------- 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; 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)}
-.grid.list-mode{display:flex; flex-direction:column; gap:10px}
-.grid.list-mode .card{flex-direction:row; align-items:stretch}
-.grid.list-mode .card:hover, .grid.list-mode .card:focus-visible{transform:translateX(3px)}
-.grid.list-mode .card .thumb{aspect-ratio:auto; width:120px; min-width:120px; font-size:1.6rem}
-.grid.list-mode .card .body{flex:1; justify-content:center; min-width:0; overflow:hidden}
-.grid.list-mode .card .title, .grid.list-mode .card .blurb{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
-@media (max-width:480px){
- .grid.list-mode .card .thumb{width:72px; min-width:72px; font-size:1.2rem}
- .grid.list-mode .card .body{padding:10px}
-}
-.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} body{transition:none} }
-</style>
+<link rel="stylesheet" href="../assets/style.css" />
</head>
<body data-mood="all">
<header class="chrome">
diff --git a/index.html b/index.html
index 3cb147f..a1097f5 100644
--- a/index.html
+++ b/index.html
@@ -6,816 +6,7 @@
<title>PANDEMONIUM-24 — Wall-to-Wall Chaos Coverage</title>
<meta name="description" content="A satirical invented 24-hour news channel with escalating breaking stories, a live ticker, a BREAKING klaxon, and a category filter that retunes the whole channel's mood." />
-<style>
-/* ============================================================
- PANDEMONIUM-24 — styles
- Sections: 1. Reset & base 2. Mood theming (CSS custom props)
- 3. Chrome (header/nav) 4. Breaking klaxon overlay
- 5. Story grid & cards 6. Ticker (motion + reduced-motion)
- 7. Empty / completion states 8. Focus & a11y helpers
- 9. Responsive breakpoints 10. Reduced-motion overrides
- ============================================================ */
-
-/* ---------- 1. Reset & base ---------- */
-*, *::before, *::after { box-sizing: border-box; }
-html, body { margin: 0; padding: 0; }
-/* overflow-x on body alone is not enough: with html's overflow unset, the
- UA propagates body's overflow to the viewport's scroller, but
- document.documentElement.scrollWidth can still report the wider,
- unclipped value (verified empirically). Setting it on html too stops the
- propagation and clips scrollWidth for real, everywhere. */
-html { overflow-x: hidden; }
-/* The [hidden] attribute must always win over any class-based `display`
- rule below (several elements here are toggled via `.hidden = true/false`
- in JS while also carrying a class that sets display:flex/grid). Without
- this, equal-specificity cascade order lets the class rule beat the UA
- [hidden] stylesheet and the element stays visible. */
-[hidden] { display: none !important; }
-body {
- 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%);
- transition: background-color .5s ease, color .5s ease;
- line-height: 1.4;
- /* reserve room for the fixed chyron ticker so it never overlaps content */
- padding-bottom: 3rem;
- /* Safety net: the chaos-tier-3 TV-bezel tilt rotates a very tall element
- (main#channelContainer), and rotating a tall box even a fraction of a
- degree widens its visual bounding box (width*cos + height*sin) beyond
- the viewport — confirmed via CSSOM bisection to be the sole cause of a
- real horizontal scrollbar at 390-1024px at max chaos. overflow-x:hidden
- here clips that leak (vertical/position:sticky is untouched) without
- changing the tilt's visible effect or any layout. */
- overflow-x: hidden;
-}
-h1, h2, h3, p { margin: 0; }
-button { font-family: inherit; }
-img, svg { max-width: 100%; display: block; }
-
-.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;
-}
-
-/* ---------- 2. Mood theming — CSS custom properties ----------
- Default ("all") is the pale mint/green salvaged from the original
- seed attempt. Every other category swaps the whole palette. */
-: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;
- --mood-label: "Calm-ish (Default)";
-}
-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;
-}
-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;
-}
-
-/* ---------- 3. Chrome (header / nav) ---------- */
-.chrome {
- position: sticky; top: 0; z-index: 20;
- display: flex; flex-wrap: wrap; align-items: center; gap: .75rem 1.25rem;
- padding: .75rem 1rem;
- background: var(--chrome);
- /* No backdrop-filter: combined with position:sticky it caused WebKit/Safari
- to ghost/bleed scrolled-past content through the header during scroll
- (CTA-verified, cta/webkit-bug-repro/). --chrome is now fully opaque so
- no blur was doing visible work anyway. translateZ forces a stable
- compositing layer as cheap insurance against the same class of bug. */
- transform: translateZ(0);
- border-bottom: 2px solid var(--panel-border);
-}
-.brand { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; color: inherit; text-decoration: none; }
-.brand:hover .wordmark { text-decoration: underline; }
-.logo-badge {
- display: inline-flex; align-items: center; justify-content: center;
- width: 2.4rem; height: 2.4rem; border-radius: .4rem;
- background: var(--accent); color: var(--bg);
- font-weight: 900; font-size: .95rem; letter-spacing: -1px;
- box-shadow: 0 0 0 2px var(--accent2);
-}
-.wordmark { font-size: clamp(1.1rem, 1.5vw + .8rem, 1.6rem); font-weight: 800; letter-spacing: .5px; }
-.tagline { font-size: .72rem; color: var(--text-dim); font-style: italic; }
-
-.live-badge {
- display: inline-flex; align-items: center; gap: .4rem;
- font-weight: 700; font-size: .8rem; letter-spacing: .5px;
- padding: .3rem .6rem; border-radius: 999px;
- border: 1px solid var(--panel-border);
- background: color-mix(in srgb, var(--panel) 70%, transparent);
-}
-.rec-dot {
- width: .55rem; height: .55rem; border-radius: 50%;
- background: #ff3b3b;
- box-shadow: 0 0 6px 1px #ff3b3b;
- animation: rec-blink 1.4s steps(1) infinite;
-}
-@keyframes rec-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: .25; } }
-
-.controls { display: flex; align-items: center; gap: .75rem; margin-left: auto; flex-wrap: wrap; }
-
-.klaxon-btn {
- cursor: pointer;
- border: 2px solid var(--accent2);
- background: var(--panel);
- color: var(--text);
- font-weight: 800;
- font-size: .85rem;
- padding: .55rem .9rem;
- border-radius: .5rem;
- letter-spacing: .3px;
- transition: transform .15s ease, background-color .2s ease, color .2s ease;
-}
-.klaxon-btn:hover { transform: translateY(-1px); }
-.klaxon-btn[aria-pressed="true"] {
- background: #ff2222; border-color: #ffd166; color: #fff;
- animation: klaxon-btn-pulse 1s ease-in-out infinite;
-}
-@keyframes klaxon-btn-pulse {
- 0%, 100% { box-shadow: 0 0 0 0 #ff222266; }
- 50% { box-shadow: 0 0 0 8px #ff222200; }
-}
-
-.sound-toggle { display: inline-flex; align-items: center; gap: .4rem; font-size: .78rem; color: var(--text-dim); cursor: pointer; }
-.sound-toggle input { width: 1rem; height: 1rem; accent-color: var(--accent); cursor: pointer; }
-.audio-note { font-size: .7rem; color: var(--text-dim); max-width: 16rem; }
-
-/* Admin toggle — deliberately NOT themed by mood vars: fixed slate/mono
- "control room" look so it never reads as part of the in-universe broadcast. */
-.admin-toggle {
- cursor: pointer;
- border: 1.5px dashed #6b7280;
- background: #20242b; color: #d7dee5;
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
- font-size: .72rem; font-weight: 700; letter-spacing: .3px;
- padding: .5rem .75rem; border-radius: .4rem;
- transition: background-color .15s ease, border-color .15s ease, color .15s ease;
-}
-.admin-toggle:hover { background: #2a2f37; }
-.admin-toggle[aria-expanded="true"] { background: #343a40; border-style: solid; border-color: #7dd3fc; color: #7dd3fc; }
-
-/* ---------- category filter (nav) ---------- */
-.category-filter { padding: .6rem 1rem; border-bottom: 1px solid var(--panel-border); }
-.category-back-link { display: inline-block; font-size: .8rem; font-weight: 700; color: var(--accent2); margin-bottom: .3rem; text-decoration: none; }
-.category-back-link:hover { text-decoration: underline; }
-.category-blurb { color: var(--text-dim); font-size: .88rem; margin-top: .2rem; }
-.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;
-}
-
-/* ---------- 4. Breaking klaxon overlay ---------- */
-.breaking-banner {
- position: relative;
- margin: 0 1rem; margin-top: .75rem;
- padding: .75rem 1rem;
- background: linear-gradient(90deg, #b30000, #ff3b3b, #b30000);
- background-size: 200% 100%;
- color: #fff;
- font-weight: 900;
- border-radius: .4rem;
- display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
- border: 2px solid #ffd166;
- box-shadow: 0 0 24px 2px #ff000055;
- animation: breaking-sweep 1.4s linear infinite, breaking-flash 0.6s ease-in-out infinite;
-}
-@keyframes breaking-sweep { 0% { background-position: 0% 0; } 100% { background-position: 200% 0; } }
-@keyframes breaking-flash { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.35); } }
-.breaking-banner .bb-icon { font-size: 1.2rem; }
-.breaking-banner .bb-sub { font-weight: 600; font-size: .82rem; opacity: .95; }
-
-/* full-screen edge vignette while breaking is active */
-body.breaking-mode::before {
- content: "";
- position: fixed; inset: 0; z-index: 15; pointer-events: none;
- box-shadow: inset 0 0 0 6px #ff2222, inset 0 0 60px 10px #ff222266;
- animation: vignette-pulse 1s ease-in-out infinite;
-}
-@keyframes vignette-pulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
-
-/* ---------- 5. Story grid & cards ---------- */
-main#channelContainer {
- position: relative;
- max-width: 1200px;
- margin: 1rem auto 0;
- padding: 1rem 1rem 1.5rem;
- border-radius: .75rem;
- background: color-mix(in srgb, var(--panel) 55%, transparent);
- box-shadow: inset 0 0 0 1px var(--panel-border), inset 0 40px 80px -40px #00000055;
- overflow: hidden;
-}
-/* subtle static CRT scanline texture — no motion, safe under reduced-motion */
-main#channelContainer::after {
- content: "";
- position: absolute; inset: 0; pointer-events: none;
- background: repeating-linear-gradient(
- to bottom, rgba(255,255,255,0.035) 0px, rgba(255,255,255,0.035) 1px,
- transparent 2px, transparent 4px
- );
- mix-blend-mode: overlay;
-}
-
-.channel-meta { display: flex; justify-content: space-between; align-items: baseline; gap: .5rem; flex-wrap: wrap; margin-bottom: .75rem; }
-.channel-meta h2 { font-size: .95rem; letter-spacing: 1px; text-transform: uppercase; color: var(--accent); }
-.mood-readout { font-size: .75rem; color: var(--text-dim); }
-
-.story-grid {
- display: grid;
- grid-template-columns: 1fr;
- gap: 1rem;
-}
-.story-card {
- position: relative;
- background: var(--panel);
- border: 1px solid var(--panel-border);
- border-radius: .6rem;
- padding: 1rem;
- display: flex; flex-direction: column; gap: .55rem;
- box-shadow: 0 4px 18px -10px #00000066;
-}
-.story-photo {
- display: block; width: calc(100% + 2rem); max-width: none; height: auto;
- aspect-ratio: 800 / 457; object-fit: cover;
- margin: -1rem -1rem .2rem; border-radius: .6rem .6rem 0 0;
- background: var(--panel-border);
-}
-.story-card.concluded .story-photo { filter: grayscale(.6); }
-.article-photo img { width: 100%; height: auto; border-radius: .5rem; display: block; }
-/* Cartoon-thumb base style (card-view / "All Categories" default — the
- compact inline-link treatment for newspaper-mode overrides this above). A
- politics story with a linked cartoon shows this INSTEAD OF a photo, same
- banner slot, so it never looks like a missing image. */
-.story-cartoon-thumb {
- display: flex; align-items: center; gap: .6rem;
- width: calc(100% + 2rem); margin: -1rem -1rem .2rem; padding: 1.1rem 1rem;
- border-radius: .6rem .6rem 0 0;
- background: color-mix(in srgb, var(--accent) 12%, var(--panel));
- border-bottom: 1px solid var(--panel-border);
- text-decoration: none; color: var(--accent2);
- font-weight: 700; font-size: .95rem;
-}
-.story-cartoon-thumb:hover, .story-cartoon-thumb:focus-visible { background: color-mix(in srgb, var(--accent) 20%, var(--panel)); }
-.story-cartoon-thumb .ct-icon { font-size: 1.3rem; }
-.story-card.concluded .story-cartoon-thumb { filter: grayscale(.5); }
-.story-card.lead { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent), 0 8px 24px -8px #00000077; }
-.story-card .kicker { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
-.badge-cat {
- font-size: .68rem; font-weight: 800; letter-spacing: .5px; text-transform: uppercase;
- padding: .18rem .5rem; border-radius: 999px; background: var(--accent); color: var(--bg);
-}
-.badge-lead {
- font-size: .68rem; font-weight: 800; letter-spacing: .5px; text-transform: uppercase;
- padding: .18rem .5rem; border-radius: 999px; background: var(--accent2); color: #fff;
-}
-.story-card h3 { font-size: clamp(1rem, .4vw + 1rem, 1.35rem); line-height: 1.25; }
-.story-card.lead h3 { font-size: clamp(1.25rem, 1.2vw + 1rem, 1.9rem); }
-.story-detail { font-size: .9rem; color: var(--text-dim); }
-.story-meta { font-size: .74rem; color: var(--text-dim); display: flex; gap: .5rem; flex-wrap: wrap; }
-
-.escalation {
- display: flex; align-items: center; gap: .5rem; margin-top: .2rem;
-}
-.escalation .level-label { font-size: .72rem; font-weight: 800; letter-spacing: .5px; text-transform: uppercase; }
-.escalation .bar { display: flex; gap: 3px; }
-.escalation .seg { width: 1.1rem; height: .4rem; border-radius: 2px; background: var(--panel-border); }
-.escalation .seg.on { background: var(--accent); }
-.story-card.concluded { opacity: .96; }
-.concluded-ribbon {
- align-self: flex-start;
- font-size: .7rem; font-weight: 800; letter-spacing: .5px; text-transform: uppercase;
- padding: .2rem .55rem; border-radius: .3rem;
- background: var(--text); color: var(--bg);
-}
-
-/* ---------- 5b. Category-view "Newspaper Column" (V4) ----------
- Applied only when state.category !== "all" (a specific category route,
- e.g. #/category/scitech) via .story-grid.newspaper-mode, toggled in
- renderStories(). Restyles the SAME storyCardHTML() markup used by the "All
- Categories" card stack — no markup fork, so patchStoryGrid()'s keyed morph
- reconcile never has to bridge a shape change from this toggle alone. True
- CSS multi-column flow (column-count), inline category label instead of the
- badge pill, headline-only density (detail paragraph hidden), a left rail
- whose height reads escalation, and a compact photo/cartoon thumbnail. */
-.np-cat-label { display: none; }
-.story-grid.newspaper-mode {
- display: block;
- column-gap: 1.5rem;
- column-count: 1;
-}
-@media (min-width: 640px) { .story-grid.newspaper-mode { column-count: 2; } }
-@media (min-width: 1024px) { .story-grid.newspaper-mode { column-count: 3; } }
-@media (min-width: 1440px) { .story-grid.newspaper-mode { column-count: 4; } }
-.story-grid.newspaper-mode .story-card {
- break-inside: avoid;
- display: block;
- margin: 0 0 .9rem;
- padding: 0 0 .1rem .7rem;
- border: 0;
- border-left: 3px solid var(--panel-border);
- border-radius: 0;
- box-shadow: none;
-}
-.story-grid.newspaper-mode .story-card.lead { border-left-color: var(--accent); border-left-width: 4px; }
-.story-grid.newspaper-mode .story-card.concluded { border-left-color: var(--text-dim); }
-.story-grid.newspaper-mode .story-card .kicker { display: none; }
-.story-grid.newspaper-mode .story-card .np-cat-label {
- display: block;
- font: 800 .62rem/1 -apple-system, sans-serif;
- text-transform: uppercase; letter-spacing: .05em;
- color: var(--accent); margin-bottom: .2rem;
-}
-.story-grid.newspaper-mode .story-card.concluded .np-cat-label { color: var(--text-dim); }
-.story-grid.newspaper-mode .story-card h3 {
- font-family: Georgia, "Times New Roman", serif;
- font-size: 1rem; line-height: 1.22; margin-bottom: .25rem;
-}
-.story-grid.newspaper-mode .story-card.lead h3 { font-size: 1.3rem; font-style: italic; }
-.story-grid.newspaper-mode .story-card .story-detail,
-.story-grid.newspaper-mode .story-card .story-read-more { display: none; }
-.story-grid.newspaper-mode .story-card .escalation { margin: .3rem 0 0; gap: .35rem; }
-.story-grid.newspaper-mode .story-card .escalation .level-label { display: none; }
-.story-grid.newspaper-mode .story-card .escalation .seg { width: .55rem; height: .18rem; }
-.story-grid.newspaper-mode .story-card .story-meta {
- font: 500 .66rem/1.3 ui-monospace, Menlo, monospace;
- color: var(--text-dim); gap: .45rem; margin-top: .25rem;
-}
-.story-grid.newspaper-mode .story-card .story-meta span:last-child { display: none; } /* drop the "Updated HH:MM" clause — density */
-.story-grid.newspaper-mode .story-card .story-photo {
- float: left; width: 56px; height: 56px; margin: 0 .55rem .3rem 0;
- border-radius: .3rem; aspect-ratio: 1/1; object-fit: cover;
-}
-.story-grid.newspaper-mode .story-card .story-cartoon-thumb {
- display: flex; align-items: center; gap: .4rem;
- font: 700 .7rem/1.2 -apple-system, sans-serif;
- color: var(--accent2); text-decoration: none;
- border: 1px dashed var(--panel-border); border-radius: .35rem;
- padding: .3rem .5rem; margin-bottom: .3rem;
-}
-.story-grid.newspaper-mode .story-card .story-cartoon-thumb:hover,
-.story-grid.newspaper-mode .story-card .story-cartoon-thumb:focus-visible { border-color: var(--accent); }
-.story-grid.newspaper-mode .story-card .ct-icon { font-size: .95rem; }
-
-/* ---------- 6. Ticker ---------- */
-.ticker-wrap {
- position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
- display: flex; align-items: stretch;
- background: var(--chrome);
- border-top: 2px solid var(--panel-border);
- height: 3rem;
-}
-body.breaking-mode .ticker-wrap { border-top-color: #ff3b3b; }
-.ticker-label {
- flex: 0 0 auto;
- display: flex; align-items: center;
- padding: 0 .8rem;
- font-weight: 900; font-size: .78rem; letter-spacing: .5px;
- background: var(--accent); color: var(--bg);
-}
-body.breaking-mode .ticker-label { background: #ff2222; color: #fff; }
-.ticker-scroll { position: relative; overflow: hidden; flex: 1 1 auto; min-width: 0; display: flex; align-items: center; }
-.ticker-track {
- display: inline-flex; white-space: nowrap; gap: 3rem; padding-right: 3rem;
- will-change: transform;
- animation: ticker-scroll 38s linear infinite;
- font-size: .85rem; font-weight: 600;
-}
-body.breaking-mode .ticker-track { color: #ff5959; font-weight: 800; }
-@keyframes ticker-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
-.ticker-static {
- flex: 1 1 auto; min-width: 0; display: flex; align-items: center; padding: 0 .9rem;
- font-size: .85rem; font-weight: 700;
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
-}
-
-/* ---------- 7. Empty / completion states ---------- */
-.empty-state, .meltdown-banner {
- text-align: center; padding: 2.5rem 1rem; border: 2px dashed var(--panel-border);
- border-radius: .6rem; color: var(--text-dim);
-}
-.empty-state .es-emoji, .meltdown-banner .mb-emoji { font-size: 2rem; display: block; margin-bottom: .5rem; }
-.empty-state h3, .meltdown-banner h3 { color: var(--text); margin-bottom: .35rem; }
-.meltdown-banner {
- margin-top: 1rem; border-color: var(--accent); border-style: solid;
- background: color-mix(in srgb, var(--accent) 12%, var(--panel));
-}
-.story-card.just-added { outline: 3px dashed var(--accent); outline-offset: 2px; }
-
-/* ---------- 7b. Admin panel (backstage / control-room, not on-air) ----------
- Deliberately fixed slate/mono palette independent of --mood vars so it
- never gets confused with the in-universe broadcast chrome. Renders in
- normal document flow (an expand/collapse section, not an overlay) so it
- can never overlap content or fight the ticker/vignette z-index stack. */
-.admin-panel {
- max-width: 1200px;
- margin: 1rem auto 0;
- padding: 1rem 1.25rem 1.25rem;
- border-radius: .6rem;
- background: #1b1f24;
- color: #e8eaed;
- border: 2px dashed #4a5057;
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
-}
-.admin-panel h2 { font-size: .95rem; letter-spacing: .5px; text-transform: uppercase; color: #7dd3fc; margin-bottom: .15rem; }
-.admin-panel h3 { font-size: .78rem; letter-spacing: .4px; text-transform: uppercase; color: #b8c0c8; margin-bottom: .5rem; }
-.admin-hint { font-size: .72rem; color: #9aa4ad; margin-bottom: 1rem; font-style: italic; }
-.admin-panel hr { border: none; border-top: 1px dashed #3a4048; margin: 1.1rem 0; }
-
-.admin-form { display: grid; gap: .5rem; }
-.admin-form label { font-size: .7rem; text-transform: uppercase; letter-spacing: .4px; color: #b8c0c8; }
-.admin-form input[type="text"], .admin-form select {
- width: 100%; padding: .5rem .6rem; border-radius: .35rem;
- border: 1.5px solid #4a5057; background: #262b31; color: #f2f4f6;
- font-family: inherit; font-size: .85rem;
-}
-.admin-form input[aria-invalid="true"] { border-color: #ff6b6b; }
-.admin-form input[type="text"]:focus-visible,
-.admin-form select:focus-visible,
-.admin-panel button:focus-visible {
- outline: 3px solid #7dd3fc; outline-offset: 2px;
-}
-.admin-error { font-size: .78rem; color: #ff8b8b; font-weight: 700; }
-.admin-success { font-size: .78rem; color: #7dffb0; font-weight: 700; }
-.admin-submit-btn {
- justify-self: start; margin-top: .2rem;
- cursor: pointer; border: none; border-radius: .35rem;
- padding: .55rem 1.1rem; font-weight: 800; font-size: .82rem;
- background: #7dd3fc; color: #0b1a24;
-}
-.admin-reset-btn {
- justify-self: start; margin-top: 1rem;
- cursor: pointer; border: 1.5px solid #4a5057; border-radius: .35rem;
- padding: .5rem 1rem; font-weight: 700; font-size: .78rem;
- background: transparent; color: #e8eaed;
-}
-.admin-reset-btn:hover { background: #262b31; }
-
-.admin-refresh-row { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin-top: .6rem; }
-.admin-refresh-btn {
- justify-self: start;
- cursor: pointer; border: 1.5px solid #7dd3fc; border-radius: .35rem;
- padding: .5rem 1rem; font-weight: 700; font-size: .78rem;
- background: transparent; color: #7dd3fc;
-}
-.admin-refresh-btn:hover { background: #7dd3fc22; }
-.admin-refresh-btn:disabled { opacity: .55; cursor: wait; }
-.admin-refresh-status { font-size: .75rem; color: #9aa4ad; }
-.admin-refresh-status.is-error { color: #ff8b8b; }
-.admin-refresh-status.is-ok { color: #8ce99a; }
-
-.admin-story-list { list-style: none; margin: 0 0 .5rem; padding: 0; display: grid; gap: .5rem; max-height: 16rem; overflow-y: auto; }
-.admin-story-row {
- /* min-width: 0 overrides the browser's implicit min-width:auto on grid
- items — without it this row (and its nowrap/ellipsis child text) won't
- shrink below its content's min-content width, pushing the Delete
- button off-screen at narrow viewports (the same class of bug as the
- ticker/fieldset overflow fixed earlier in this project). */
- min-width: 0;
- display: flex; align-items: center; justify-content: space-between; gap: .75rem;
- padding: .5rem .6rem; border: 1px solid #343a40; border-radius: .4rem; background: #22262b;
-}
-.admin-story-info { flex: 1 1 auto; display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
-.admin-story-cat { font-size: .62rem; text-transform: uppercase; letter-spacing: .4px; color: #7dd3fc; }
-.admin-story-headline { font-size: .8rem; color: #e8eaed; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
-.admin-delete-btn {
- flex: 0 0 auto; cursor: pointer; border: 1.5px solid #ff6b6b; background: transparent;
- color: #ff8b8b; border-radius: .35rem; padding: .35rem .6rem; font-weight: 700; font-size: .72rem;
-}
-.admin-delete-btn:hover { background: #ff6b6b22; }
-.admin-empty { font-size: .8rem; color: #9aa4ad; padding: .5rem 0; }
-@media (min-width: 768px) {
- .admin-panel-body { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; align-items: start; }
-}
-.admin-form textarea {
- width: 100%; padding: .5rem .6rem; border-radius: .35rem;
- border: 1.5px solid #4a5057; background: #262b31; color: #f2f4f6;
- font-family: inherit; font-size: .85rem; resize: vertical; min-height: 6rem;
-}
-.admin-form-hint { font-size: .68rem; color: #9aa4ad; margin-top: -.25rem; }
-
-/* ---------- 7c. Feature articles ("More From PANDEMONIUM-24") ---------- */
-.features-section {
- max-width: 1200px;
- margin: 1.25rem auto 0;
- padding: 1rem 1rem 1.5rem;
-}
-.features-grid { display: grid; grid-template-columns: 1fr; gap: 1rem; }
-.feature-card {
- background: var(--panel);
- border: 1px solid var(--panel-border);
- border-radius: .6rem;
- padding: 1rem;
- display: flex; flex-direction: column; gap: .5rem;
- box-shadow: 0 4px 18px -10px #00000066;
-}
-.feature-card h3 { font-size: 1.05rem; line-height: 1.3; }
-.features-empty-note { color: var(--text-dim); font-size: .85rem; padding: .5rem 0; }
-@media (min-width: 768px) { .features-grid { grid-template-columns: repeat(2, 1fr); } }
-@media (min-width: 1024px) { .features-grid { grid-template-columns: repeat(3, 1fr); } }
-
-/* Shared "open article" link treatment (story cards + feature cards) */
-.story-card h3 a, .feature-card h3 a { color: inherit; text-decoration: none; }
-.story-card h3 a:hover, .feature-card h3 a:hover { text-decoration: underline; }
-.story-read-more {
- align-self: flex-start; margin-top: .1rem;
- font-size: .82rem; font-weight: 700; color: var(--accent2);
- text-decoration: none;
-}
-.story-read-more:hover { text-decoration: underline; }
-
-/* ---------- 7d. Article view (hash-routed, #/article/<id>) ----------
- Long-form reading layout: comfortable measure (~65ch), generous
- line-height, still inherits the current category mood's palette. */
-.article-view { max-width: 100%; }
-.article-inner {
- max-width: 68ch;
- margin: 1rem auto 0;
- padding: 1.25rem 1.25rem 2rem;
- background: var(--panel);
- border-radius: .75rem;
- box-shadow: inset 0 0 0 1px var(--panel-border), inset 0 40px 80px -40px #00000055;
- color: var(--text);
-}
-.article-back-btn {
- cursor: pointer; border: 1.5px solid var(--panel-border); background: transparent;
- color: var(--text); font-weight: 700; font-size: .85rem;
- padding: .5rem .8rem; border-radius: .4rem; margin-bottom: 1.1rem;
-}
-.article-back-btn:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
-.article-cat-badge { display: inline-block; margin-bottom: .6rem; }
-.article-headline { font-size: clamp(1.4rem, 1.6vw + 1rem, 2.1rem); line-height: 1.2; margin-bottom: .5rem; }
-.article-dek { font-size: 1.05rem; color: var(--text-dim); line-height: 1.5; margin-bottom: .9rem; }
-.article-meta-row {
- font-size: .8rem; color: var(--text-dim); font-weight: 700;
- display: flex; gap: .75rem; flex-wrap: wrap;
- padding-bottom: .85rem; margin-bottom: 1.1rem;
- border-bottom: 1px dashed var(--panel-border);
-}
-.article-dateline { letter-spacing: .3px; }
-.article-photo { margin: 0 0 1.25rem; }
-.article-photo svg { width: 100%; height: auto; border-radius: .5rem; display: block; }
-.article-photo figcaption { font-size: .78rem; color: var(--text-dim); font-style: italic; margin-top: .45rem; }
-.article-body p {
- margin: 0 0 1.1rem; font-size: 1.02rem; line-height: 1.75; max-width: 65ch;
-}
-.article-body p.kicker { font-style: italic; color: var(--text-dim); }
-.live-update-block {
- border: 2px dashed var(--accent); border-radius: .5rem;
- padding: .8rem 1rem; margin: 0 0 1.25rem;
- background: color-mix(in srgb, var(--accent) 10%, var(--panel));
-}
-.live-update-label {
- display: block; font-weight: 900; font-size: .74rem; letter-spacing: .5px;
- text-transform: uppercase; color: var(--accent2); margin-bottom: .4rem;
-}
-.live-update-headline { font-weight: 800; margin-bottom: .3rem; }
-.live-update-detail { color: var(--text-dim); font-size: .92rem; }
-.live-update-concluded { margin-top: .5rem; }
-.article-source-link { margin-top: 1rem; }
-.article-source-link a {
- display: inline-block;
- color: var(--accent);
- font-weight: 800;
- text-decoration: none;
- border: 1px solid var(--accent);
- border-radius: 6px;
- padding: .5rem .9rem;
-}
-.article-source-link a:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
-.related-section { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px dashed var(--panel-border); }
-.related-section h3 { font-size: .85rem; text-transform: uppercase; letter-spacing: .5px; color: var(--accent); margin-bottom: .6rem; }
-.related-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
-.related-list a { color: var(--accent2); font-weight: 700; }
-.article-not-found { text-align: center; }
-.article-not-found h2 { margin-bottom: .5rem; }
-@media (min-width: 768px) { .article-inner { padding: 1.75rem 2rem 2.25rem; } }
-@media (min-width: 1024px) { .article-inner { padding: 2rem 2.5rem 2.5rem; } }
-
-/* ---------- 8. Focus & a11y helpers ---------- */
-a, button, input, [tabindex] { outline-offset: 2px; }
-:focus-visible {
- outline: 3px solid var(--focus);
- outline-offset: 2px;
- border-radius: 2px;
-}
-.klaxon-btn:focus-visible { outline: 3px solid var(--focus); }
-
-/* ---------- 8b. CHAOS INDEX — global escalation gauge + broadcast decay ----------
- One computed number (0-100) drives everything below via body.chaos-tier-0..3
- classes + a couple of CSS custom properties. Every rule here targets the
- chrome/body/container level, NEVER the story-card markup itself, so
- patchStoryGrid()'s keyed reconcile never has to re-render a card just
- because chaos ticked up (headline stamps are pure ::before content). */
-.chaos-row {
- /* flex-basis 100% inside .chrome's flex-wrap:wrap always forces this onto
- its own line, so its own width/content changes can never reflow the
- brand/live-clock/controls line above it. flex-wrap:wrap on ITS OWN
- children is the overflow safety valve at narrow widths (390px) — if the
- fixed-width band/pct pieces don't fit alongside the track, they wrap to
- a second line inside this row instead of ever overflowing horizontally. */
- flex: 1 1 100%;
- display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .6rem;
- padding: .4rem 0 0;
- margin-top: .4rem;
- border-top: 1px dashed var(--panel-border);
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
- font-size: .72rem;
-}
-.chaos-tag { flex: 0 0 auto; font-weight: 900; letter-spacing: .6px; color: var(--text-dim); }
-/* Fixed min-width sized to the longest label ("Post-Journalism") so the
- readout's own box never changes width as the label text changes length —
- the one thing that could otherwise nudge the controls sitting above it. */
-.chaos-band { flex: 0 0 auto; min-width: 10.5rem; font-weight: 800; letter-spacing: .3px; color: var(--text); }
-.chaos-band-icon { margin-right: .3rem; }
-.chaos-track {
- flex: 1 1 auto; max-width: 14rem; height: .55rem; border-radius: 999px;
- background: var(--panel-border); overflow: hidden; position: relative;
-}
-.chaos-fill {
- position: absolute; inset: 0 auto 0 0; width: 0%;
- background: var(--accent); border-radius: inherit;
- transition: width .5s ease, background-color .4s ease;
-}
-.chaos-pct { flex: 0 0 auto; min-width: 3ch; text-align: right; font-variant-numeric: tabular-nums; color: var(--text-dim); }
-
-body.chaos-tier-1 .chaos-fill { background: #e0a300; }
-body.chaos-tier-2 .chaos-fill { background: #ff8c00; }
-body.chaos-tier-3 .chaos-fill { background: #ff2222; }
-
-/* Ticker degradation: each tier shortens the scroll loop so the chyron
- visibly speeds up. The reduced-motion path never uses this animation at
- all (ticker-track animation is fully disabled + JS swaps to the static
- frame renderer), so this is motion-safe by construction. */
-/* Duration is set from JS (setTickerSpeed) as track length / reading speed,
- so the crawl moves at a constant, readable px/s however many headlines
- are in the loop. 90s is only the pre-measure fallback. */
-.ticker-track { animation: ticker-scroll var(--ticker-dur, 90s) linear infinite; }
-
-/* Broadcast losing its grip: headline stamps via generated content only —
- zero markup change, so these can flip every tick without ever touching
- patchStoryGrid()'s diffing. */
-body.chaos-tier-1 .story-card:not(.concluded) h3 a::before,
-body.chaos-tier-2 .story-card:not(.concluded) h3 a::before,
-body.chaos-tier-3 .story-card:not(.concluded) h3 a::before {
- content: "[DEVELOPING] "; color: var(--accent2); font-weight: 900;
-}
-
-/* Lower-third anchor caption — appears at tier 2+, cycles a fixed set of
- "anchor notes" that get more unhinged per tier. position:fixed, so
- showing/hiding it never reflows anything in normal document flow. */
-.anchor-caption {
- position: fixed; left: 0; right: 0; bottom: 3rem; z-index: 19;
- padding: .4rem .9rem;
- background: color-mix(in srgb, var(--chrome) 88%, #000 12%);
- color: var(--text);
- border-top: 1px dashed var(--accent2);
- font-size: .78rem; font-weight: 700; font-style: italic;
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
-}
-
-/* TV-bezel tilt — the container physically drifts a fraction of a degree at
- max tier. Gated entirely behind (prefers-reduced-motion: no-preference)
- so reduced-motion users NEVER get the transform; the unconditional
- box-shadow rule right below is their static equivalent (a hot red edge
- instead of a tilt). Only the broadcast container tilts — chrome/nav/admin
- controls are untouched so their click targets never move. */
-body.chaos-tier-3 main#channelContainer {
- box-shadow: inset 0 0 0 1px var(--panel-border), inset 0 40px 80px -40px #00000055, inset 0 0 0 3px #ff222277;
-}
-@media (prefers-reduced-motion: no-preference) {
- main#channelContainer { transition: transform .6s ease; }
- body.chaos-tier-3 main#channelContainer { transform: rotate(.6deg); }
-}
-
-/* PLEASE STAND BY — full-screen flash at chaos 100 (all stories concluded).
- pointer-events:none the whole time, so it can cover the entire viewport
- without ever blocking a click on the klaxon/chips/admin/article links
- underneath it. JS never arms this under reduced motion — the existing
- static #meltdownBanner text is that audience's equivalent. Blink cycle is
- .7s (~1.4Hz, well under the 3-flashes-per-second ceiling) x4 then it's
- hidden again by JS, so it's transient, not a permanent strobe. */
-.standby-flash {
- position: fixed; inset: 0; z-index: 30; pointer-events: none;
- display: flex; align-items: center; justify-content: center;
- background: rgba(0,0,0,.6); color: #fff; text-align: center;
- font-weight: 900; letter-spacing: 2px;
- font-size: clamp(1.1rem, 1rem + 2vw, 2.2rem);
-}
-.standby-flash.flashing { animation: standby-blink .7s steps(1, end) 4; }
-@keyframes standby-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
-
-/* ---------- 9. Responsive breakpoints ---------- */
-@media (min-width: 560px) {
- .controls { margin-left: 0; }
-}
-@media (min-width: 768px) {
- .chrome { padding: 1rem 1.5rem; }
- .category-filter { padding: .75rem 1.5rem; }
- main#channelContainer { padding: 1.25rem 1.5rem 1.75rem; }
- .admin-panel { padding: 1.25rem 1.5rem 1.75rem; }
- .story-grid { grid-template-columns: repeat(2, 1fr); }
- .story-card.lead { grid-column: 1 / -1; }
-}
-@media (min-width: 1024px) {
- .chrome { padding: 1rem 2rem; }
- .category-filter { padding: .75rem 2rem; }
- main#channelContainer { padding: 1.5rem 2rem 2rem; }
- .admin-panel { padding: 1.5rem 2rem 2rem; }
- .story-grid { grid-template-columns: repeat(3, 1fr); align-items: stretch; }
- .story-card.lead {
- grid-column: 1 / -1;
- display: grid; grid-template-columns: 3fr 2fr; column-gap: 1.5rem; row-gap: .55rem;
- align-content: start;
- }
- .story-card.lead > * { grid-column: 2; }
- .story-card.lead > .story-photo {
- grid-column: 1; grid-row: 1 / span 8;
- width: calc(100% + 1rem); margin: -1rem 0 -1rem -1rem;
- height: calc(100% + 2rem); border-radius: .6rem 0 0 .6rem;
- }
-}
-@media (min-width: 1280px) {
- .wordmark { letter-spacing: .8px; }
- .ticker-track { font-size: .92rem; }
-}
-
-/* ---------- 10. Reduced-motion overrides ---------- */
-@media (prefers-reduced-motion: reduce) {
- * { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
- .rec-dot { animation: none; opacity: 1; }
- .klaxon-btn[aria-pressed="true"] { animation: none; box-shadow: 0 0 0 4px #ff222255; }
- .breaking-banner { animation: none; background: #b30000; }
- body.breaking-mode::before { animation: none; opacity: .8; }
- .ticker-track { animation: none; }
-}
-</style>
+<link rel="stylesheet" href="assets/style.css" />
</head>
<body data-mood="all">
← c130b62 Backfill story_id links for 9 orphaned cartoons + codify lin
·
back to Crazy News Channel
·
Animated Filibuster Marathon on P24 front page (WAN 2.2 i2v, 7bf67a3 →