← back to Crazy News Channel Shadowman

assets/style.css

1382 lines

/* ============================================================
   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, instead of copy-pasted/drifting inline <style>
   blocks. Pure CSS, no fetch()/build step — safe for offline
   file:// use.

   TK-12226 (2026-09-25): removed the per-category "mood" palette
   swap and the per-strip light/dark toggle — Steve's ruling was
   "fix ux so onclick, all fonts and colors do not change. Keep
   entire site 1 style sheet on p24." There is now exactly ONE
   fixed palette (the former "all"/default mint-green) and ONE
   font stack, declared once in :root, used everywhere. Category
   filter clicks still set body[data-mood] (kept for the mood
   text readout + per-card accent dots below) but nothing in this
   file keys the palette/fonts off it anymore.

   PART 1 — site-wide (verbatim from the former index.html inline
            stylesheet: reset, palette, 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, and (new)
            shared cartoon-page layout rules scoped under
            body.cartoon-page so they never leak into index.html
   ============================================================ */

/* ============================================================
   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. Theming — CSS custom properties ----------
   TK-12226: this used to be per-category ("mood") theming — clicking a
   category filter/chip set body[data-mood="politics|weather|..."] and a
   whole different block below redefined --bg/--panel/--accent/--text/etc,
   so every click repainted the entire site's palette. Steve's ruling:
   "keep entire site 1 style sheet on p24" — one fixed palette, full stop.
   The JS still sets `document.body.dataset.mood` on category click (see
   index.html) for the `body[data-mood="all"]` selector below and for the
   `.card[data-cat="..."]` per-card accent dots further down, but no other
   `body[data-mood="..."]` block exists anymore, so nothing in this file
   keys the palette off it. --mood-label was only ever read by the block
   removed here, so it is dropped too (nothing referenced it via var()). */
: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;
}

/* ---------- 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 a fixed
   per-category color (independent of the single site-wide palette
   above). Keeps cards multicolored in the "All Categories" view; this
   does not change on click and is not affected by TK-12226. */
.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 22 individual strip pages were built against an older
   "parchment newspaper" token set (--ink, --ink-dim, --rule,
   --seal). These names are aliased onto the shared token system
   so the strips render with the site's one fixed palette without
   rewriting every strip's markup/JS (several draw their SVG
   panels by reading these var() names directly at render time).

   TK-12226: this block used to sit on bare `body` specifically so
   a per-category body[data-mood="..."] block (removed above) and
   a per-page light/dark toggle (removed below, along with each
   strip's toggle button/script — see individual page diffs) could
   both win the cascade over :root. Neither exists anymore, so the
   alias is declared on :root directly — one palette, no toggle,
   nothing to repaint on click. */
:root {
  --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); }

/* ----------------------------------------------------------------
   PART 3b — Shared cartoon-strip-page layout (TK-12226)
   Every rule below is scoped under a body class so it can NEVER leak
   onto index.html/cartoons/index.html. Consolidated from 10 distinct
   per-page <style> block variants that used to be copy-pasted into
   each of the 22 cartoons/2026*.html files (+ cartoons/reference/
   index.html) — none of them are reproduced verbatim here: every
   `font-family:-apple-system,...` (redundant — body already sets the
   one site font and `button{font-family:inherit}` from Part 1 covers
   buttons), every 'Iowan Old Style'/Palatino/Georgia serif override,
   and every per-page dark-mode toggle/--ink+--paper hardcoded color
   pair was DROPPED so the whole site renders in one font and one
   palette regardless of which cartoon page you're on or what you
   click. ---------------------------------------------------------- */

/* ---- body.cartoon-page: the majority "case file" layout (11 pages:
   casserole-succession, four-minutes-ahead, gnome-who-wouldnt-hush,
   hedge-non-aggression-pact, jackalope-custody-battle, meter-that-
   wouldnt-yield, row-by-row-only, the-corner-with-its-own-weather,
   the-zipper-that-ended-an-inning, tollpoints-diamond-status,
   tuber-bank-of-chugwater) plus the shared base for the "wide" 3-panel
   variant below (commission-turtles, filibuster-vs-cloture, quorum-
   call-hide-seek, budget-pie-fight, lobbyist-revolving-door,
   pork-barrel-train). ---- */
body.cartoon-page 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;
}
body.cartoon-page header a.back {
  font-size: .8rem; color: var(--ink-dim); text-decoration: none;
  border: 1px solid var(--ink-dim); border-radius: 6px; padding: 5px 10px;
}
/* Replaces the identical inline style="display:flex;flex-direction:column;
   gap:8px;align-items:flex-end" wrapper that used to hold the back-link +
   (now removed) per-page theme-toggle button on 17 of these pages. */
body.cartoon-page .header-actions {
  display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
}
body.cartoon-page .kicker {
  font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--accent);
}
body.cartoon-page h1 { font-size: 1.7rem; margin-top: 2px; }
body.cartoon-page .dek { font-style: italic; color: var(--ink-dim); max-width: 60ch; margin-top: 4px; }
body.cartoon-page main { max-width: 640px; margin: 0 auto; padding: 18px 20px; }
body.cartoon-page figure {
  margin: 0; background: var(--panel); border: 3px solid var(--rule);
  border-radius: 8px; overflow: hidden; display: flex; flex-direction: column;
  position: relative;
}
body.cartoon-page .phead {
  display: flex; justify-content: space-between; font-size: .65rem;
  letter-spacing: .05em; text-transform: uppercase; color: var(--ink-dim);
  padding: 6px 10px; border-bottom: 1px solid var(--ink-dim);
}
body.cartoon-page .art { width: 100%; aspect-ratio: 3/2; display: block; cursor: pointer; background: transparent; }
body.cartoon-page .art:focus-visible { outline: 3px solid var(--focus); outline-offset: -3px; }
body.cartoon-page .car { cursor: pointer; }
body.cartoon-page .car:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
body.cartoon-page figcaption { font-size: .92rem; color: var(--ink); padding: 12px; border-top: 1px solid var(--ink-dim); }
body.cartoon-page .dock { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin: 16px 0; }
body.cartoon-page button.twist {
  font-size: .9rem; font-weight: 700; border-radius: 6px; padding: 9px 16px;
  cursor: pointer; border: 2px solid var(--seal); background: var(--seal); color: #241c14;
}
body.cartoon-page button.primary {
  font-size: .9rem; font-weight: 700; border-radius: 6px; padding: 9px 16px;
  cursor: pointer; border: 2px solid var(--rule); background: var(--ink); color: var(--panel);
}
body.cartoon-page button:disabled { opacity: .45; cursor: not-allowed; }
body.cartoon-page .counter {
  font-size: .8rem; font-weight: 700; color: var(--ink); border: 2px dashed var(--seal);
  border-radius: 6px; padding: 6px 12px; white-space: nowrap;
}
body.cartoon-page #status {
  font-size: .82rem; padding: 8px 12px; border-radius: 6px; border: 1px solid var(--ink-dim);
  color: var(--ink-dim); min-height: 1.2em;
}
body.cartoon-page #status.ok { border-color: var(--accent2); color: var(--accent2); }
body.cartoon-page #status.err { border-color: var(--accent); color: var(--accent); }
body.cartoon-page #status.info { border-color: var(--focus); color: var(--focus); }
body.cartoon-page .slider-row {
  display: flex; flex-direction: column; gap: 4px; width: 100%;
  background: var(--panel); border: 2px solid var(--rule); border-radius: 8px; padding: 10px 14px;
}
body.cartoon-page .slider-row .row1 { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; flex-wrap: wrap; }
body.cartoon-page .slider-row label { font-size: .82rem; font-weight: 700; }
body.cartoon-page .slider-row .val { font-size: .82rem; color: var(--ink-dim); }
body.cartoon-page .slider-row input[type=range] { width: 100%; accent-color: var(--accent); }
body.cartoon-page footer { max-width: 640px; margin: 20px auto 0; padding: 0 20px; font-size: .72rem; color: var(--ink-dim); }
body.cartoon-page .hint { font-size: .72rem; color: var(--ink-dim); margin-top: 6px; }
body.cartoon-page .seal-anim { transition: transform .3s ease; }
body.cartoon-page .door-anim { transition: transform .45s ease; }
@media (prefers-reduced-motion: reduce) {
  body.cartoon-page .seal-anim, body.cartoon-page .door-anim { transition: none; }
}

/* ---- body.cartoon-page-wide: the 3-panel strip variant, layered on
   top of body.cartoon-page (commission-turtles, filibuster-vs-cloture,
   quorum-call-hide-seek, budget-pie-fight, lobbyist-revolving-door,
   pork-barrel-train). ---- */
body.cartoon-page-wide main { max-width: 900px; }
body.cartoon-page-wide footer { max-width: 900px; }
body.cartoon-page-wide .strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
@media (max-width: 720px) { body.cartoon-page-wide .strip { grid-template-columns: 1fr; } }
body.cartoon-page-wide .art { aspect-ratio: 1/1; }
body.cartoon-page-wide figcaption { font-size: .8rem; color: var(--ink-dim); padding: 8px 10px; }
body.cartoon-page-wide .cover {
  width: 100%; height: 100%;
  background: repeating-linear-gradient(45deg, var(--panel), var(--panel) 10px, color-mix(in srgb, var(--panel) 85%, var(--ink)) 10px, color-mix(in srgb, var(--panel) 85%, var(--ink)) 20px);
  border: none; display: flex; align-items: center; justify-content: center;
  font-size: .85rem; color: var(--ink-dim); cursor: pointer; aspect-ratio: 1/1;
}
body.cartoon-page-wide .cover:hover { color: var(--ink); }

/* ---- body.cartoon-page-frame: the minimalist "newsprint clipping in a
   parchment frame" layout (come-in-and-get-me-meter, row-by-row-marshal,
   gold-tier-treadmill). Each of these 3 pages used to hardcode its OWN
   --ink/--paper pair (near-duplicates of --text/--panel) and a serif
   font-family; both are dropped in favor of the single shared palette
   and font so these pages read identically to the rest of the site. ---- */
body.cartoon-page-frame {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 24px 16px;
}
body.cartoon-page-frame figure { margin: 0; width: 100%; max-width: 900px; background: none; border: none; }
body.cartoon-page-frame .frame { background: var(--panel); border: 6px solid var(--text); border-radius: 4px; box-shadow: 0 12px 40px #0009; }
body.cartoon-page-frame svg { display: block; width: 100%; height: auto; }
body.cartoon-page-frame figcaption { margin-top: 14px; text-align: center; }
body.cartoon-page-frame .cap { font-size: clamp(18px, 3vw, 26px); font-weight: 700; color: var(--text); }
body.cartoon-page-frame .cap .q { color: var(--accent2); font-style: italic; }
body.cartoon-page-frame .sub { margin-top: 6px; font-size: 13px; color: var(--text-dim); }
body.cartoon-page-frame .back { margin-top: 18px; font-size: 13px; }

/* ---- body.cartoon-queue-page: daily-cartoons/review.html, generated by
   daily-cartoons/build_review.py (a LOCAL-only admin review queue, never
   deployed). TK-12226: the generator used to emit its own <style> block
   with a hardcoded --bg/--ink/--mint/--card/--dim palette, a Georgia font,
   and a `prefers-color-scheme: dark` swap; now it links only this
   stylesheet and gets this one shared layout instead. ---- */
body.cartoon-queue-page { padding: 16px; }
body.cartoon-queue-page header {
  max-width: 1400px; margin: 0 auto 14px; display: flex; flex-wrap: wrap;
  gap: 12px; align-items: center; justify-content: space-between;
}
body.cartoon-queue-page header > div { min-width: 0; }
body.cartoon-queue-page code { word-break: break-all; }
body.cartoon-queue-page h1 { margin: 0; font-size: 1.5rem; }
body.cartoon-queue-page .note { color: var(--text-dim); font-size: .9rem; margin: 4px 0 0; }
body.cartoon-queue-page .ctrl { display: flex; gap: 12px; align-items: center; font-size: .9rem; }
body.cartoon-queue-page select, body.cartoon-queue-page input { font: inherit; }
body.cartoon-queue-page #grid {
  max-width: 1400px; margin: 0 auto; display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--min, 340px), 100%), 1fr));
}
body.cartoon-queue-page .card { background: var(--panel); border: 3px solid var(--ink); overflow: hidden; }
body.cartoon-queue-page .card video, body.cartoon-queue-page .card img {
  display: block; width: 100%; aspect-ratio: 4/3; object-fit: cover; background: #000;
}
body.cartoon-queue-page .body { padding: 10px 12px; }
body.cartoon-queue-page h2 { margin: 0 0 4px; font-size: 1.15rem; }
body.cartoon-queue-page .cap { margin: 0 0 8px; font-style: italic; color: var(--text-dim); }
body.cartoon-queue-page .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
body.cartoon-queue-page .chip { font: 12px/1.6 -apple-system, sans-serif; border: 1px solid var(--accent); padding: 0 8px; border-radius: 10px; }
body.cartoon-queue-page .st-error, body.cartoon-queue-page .st-incomplete-cap { border-color: #c0392b; color: #c0392b; }
body.cartoon-queue-page .st-approved { background: var(--accent); color: #fff; }
body.cartoon-queue-page details { font-size: .8rem; color: var(--text-dim); }
body.cartoon-queue-page .cmd {
  display: block; margin-top: 8px; font-size: 11px; background: var(--ink); color: var(--bg);
  padding: 6px; cursor: pointer; word-break: break-all;
}

/* ---- body.cartoon-page-simple: the minimal single-image layout emitted
   by daily-cartoons/approve.py for every newly-approved cartoon page (TK-
   12226 — the generator used to emit its own <style> block with a
   hardcoded --cream/--ink/--mint palette + Georgia font; now it links only
   this stylesheet and gets this one shared layout instead). ---- */
body.cartoon-page-simple { max-width: 1000px; margin: 0 auto; padding: 24px 16px; }
body.cartoon-page-simple video, body.cartoon-page-simple img { display: block; width: 100%; border: 4px solid var(--ink); }
body.cartoon-page-simple h1 { margin: 14px 0 4px; }
body.cartoon-page-simple .cap { font-style: italic; }

/* ---- body.cartoon-page-diagram: the single-figure centered SVG-diagram
   layout (filibuster-qwen25-7b — the only strip with no <header>/
   <figure> chrome at all, just a centered SVG illustration). ---- */
body.cartoon-page-diagram {
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  min-height: 100vh; gap: 1rem; padding: 1rem;
}
body.cartoon-page-diagram .back {
  align-self: flex-start; font-size: .8rem; color: var(--text-dim); text-decoration: none;
  border: 1px solid var(--panel-border); border-radius: 6px; padding: 5px 10px;
}
body.cartoon-page-diagram .back:hover { color: var(--text); }
body.cartoon-page-diagram .cartoon { width: 100%; max-width: 1440px; min-width: 390px; text-align: center; }
/* .title font-size 2em kept; the inline style="font-size:1.1em" that used
   to sit on the one .accent <p> inside the SVG foreignObject is folded in
   here since that's the only place .accent is used on this page. */
body.cartoon-page-diagram .title { font-size: 2em; margin-bottom: 1em; color: var(--text); }
body.cartoon-page-diagram .accent { color: var(--accent); font-size: 1.1em; }
body.cartoon-page-diagram .lectern,
body.cartoon-page-diagram .delegate,
body.cartoon-page-diagram .phonebook,
body.cartoon-page-diagram .clock { fill: var(--text); }
body.cartoon-page-diagram .clock-dial,
body.cartoon-page-diagram .clock-hand,
body.cartoon-page-diagram .chamber-members { stroke: var(--text); }

/* ---- body.cartoon-page-editorial: the "front page masthead" layout
   (filibuster-ink — the site's one featured/linked-from-index
   cartoon). Its former Georgia/'Times New Roman' font-family and its
   own light/dark theme-toggle button + --frame-shadow swap are both
   dropped: one font, one palette, no toggle. --cream/--mint/--mint-dim
   are this page's fixed parchment-frame accent colors (independent of
   the single site palette, same idea as --ink/--paper on the "frame"
   variant above) — kept local since nothing else on the site uses them. ---- */
body.cartoon-page-editorial {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  padding: clamp(16px, 4vw, 48px) clamp(12px, 3vw, 32px) clamp(28px, 5vw, 56px);
  --cream: #cdf5e0; --mint: #0f9d78; --mint-dim: #0c7d5f;
  --frame-shadow: 0 18px 45px rgba(0, 0, 0, .28);
}
body.cartoon-page-editorial .masthead { width: 100%; max-width: 1040px; text-align: center; margin-bottom: clamp(10px, 2vw, 22px); }
body.cartoon-page-editorial .masthead .kicker {
  font-style: italic; letter-spacing: .14em; text-transform: uppercase;
  font-size: clamp(10px, 1.4vw, 13px); color: var(--mint); margin: 0 0 6px;
}
body.cartoon-page-editorial .masthead h1 {
  font-weight: 900; letter-spacing: .01em; font-size: clamp(26px, 5vw, 52px);
  margin: 0; line-height: 1.02; color: var(--text);
}
body.cartoon-page-editorial .masthead .deck {
  font-style: italic; font-weight: 400; font-size: clamp(12px, 1.8vw, 16px);
  color: var(--text); opacity: .72; margin: 8px 0 0;
}
body.cartoon-page-editorial .frame-wrap {
  width: 100%; max-width: 1040px; min-width: min(100%, 390px);
  background: var(--cream); border-radius: 6px; box-shadow: var(--frame-shadow);
  padding: clamp(10px, 1.6vw, 18px);
}
body.cartoon-page-editorial .cartoon-img { display: block; width: 100%; height: auto; border: 6px solid #0b0b0c; outline: 1.5px solid #0b0b0c; outline-offset: 5px; }
body.cartoon-page-editorial .panel-caption {
  margin: clamp(14px, 2vw, 20px) 0 4px; text-align: center; font-style: italic;
  font-size: clamp(14px, 2.1vw, 20px); color: #0b0b0c; line-height: 1.35;
}
body.cartoon-page-editorial .panel-title {
  margin: 6px 0 2px; text-align: center; font-weight: 700; letter-spacing: .14em;
  font-size: clamp(15px, 2.4vw, 24px); color: var(--mint); border-top: 2px solid var(--mint); padding-top: 8px;
}
body.cartoon-page-editorial .cartoon-svg { display: block; width: 100%; height: auto; }
body.cartoon-page-editorial .caption-row {
  width: 100%; max-width: 1040px; display: flex; justify-content: space-between;
  align-items: baseline; flex-wrap: wrap; gap: 8px; margin-top: clamp(10px, 2vw, 18px); padding: 0 4px;
}
body.cartoon-page-editorial .caption-row .cred { font-size: clamp(11px, 1.5vw, 13px); font-style: italic; opacity: .65; color: var(--text); }
body.cartoon-page-editorial .caption-row .stamp {
  font-size: clamp(10px, 1.4vw, 12px); letter-spacing: .08em; text-transform: uppercase;
  color: var(--mint); font-weight: 700;
}
@media (max-width: 520px) {
  body.cartoon-page-editorial .masthead h1 { letter-spacing: 0; }
  body.cartoon-page-editorial .caption-row { justify-content: center; text-align: center; }
}

/* ---- body.cartoon-page-reference: cartoons/reference/index.html (the
   "how these cartoons are built" design-reference doc — not a strip).
   Its .chrome header bar is renamed to .ref-chrome so it can never
   collide with the site's own sticky nav .chrome from Part 1. Its former
   Iowan Old Style/Palatino/Georgia body font is dropped for the one
   shared sans-serif stack. ---- */
body.cartoon-page-reference { --maxw: 1000px; }
body.cartoon-page-reference .ref-chrome {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  background: var(--chrome); border-bottom: 3px solid var(--accent); padding: 12px 20px;
}
body.cartoon-page-reference .logo-badge {
  background: var(--accent); color: #220808; font-weight: 800; letter-spacing: .05em;
  padding: 3px 8px; border-radius: 3px;
}
body.cartoon-page-reference .ref-chrome h1 { font-size: clamp(18px, 3vw, 26px); margin: 0; flex: 1; }
body.cartoon-page-reference .ref-chrome a.home { color: var(--text-dim); text-decoration: none; font-size: 13px; }
body.cartoon-page-reference .ref-chrome a.home:hover { color: var(--accent2); }
body.cartoon-page-reference main { max-width: var(--maxw); margin: 0 auto; padding: 22px 20px 60px; }
body.cartoon-page-reference .lede {
  font-size: clamp(17px, 2.4vw, 21px); border-left: 4px solid var(--accent);
  padding-left: 14px; margin: 4px 0 24px; color: var(--text);
}
body.cartoon-page-reference .creed { font-style: italic; color: var(--accent2); }
body.cartoon-page-reference section { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 6px; padding: 18px 20px; margin-bottom: 20px; }
body.cartoon-page-reference section > h2 { margin: 0 0 12px; font-size: 20px; border-bottom: 1px solid var(--panel-border); padding-bottom: 6px; }
body.cartoon-page-reference dl.style-grid { margin: 0; display: grid; grid-template-columns: 150px 1fr; gap: 8px 16px; }
body.cartoon-page-reference dl.style-grid dt { color: var(--accent2); font-weight: 700; }
body.cartoon-page-reference dl.style-grid dd { margin: 0; color: var(--text); }
body.cartoon-page-reference ul.devices, body.cartoon-page-reference ol.brief { padding-left: 0; margin: 0; list-style: none; }
body.cartoon-page-reference ul.devices li { padding: 8px 0; border-bottom: 1px dashed var(--panel-border); }
body.cartoon-page-reference ul.devices li:last-child { border-bottom: none; }
body.cartoon-page-reference .dev-name { font-weight: 800; color: var(--accent); }
body.cartoon-page-reference .dev-seed { display: block; font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
body.cartoon-page-reference ol.brief { list-style: decimal; padding-left: 22px; }
body.cartoon-page-reference ol.brief li { margin: 6px 0; }
body.cartoon-page-reference table.works { width: 100%; border-collapse: collapse; font-size: 14px; }
body.cartoon-page-reference table.works caption { text-align: left; color: var(--text-dim); font-size: 12.5px; margin-bottom: 8px; }
body.cartoon-page-reference table.works th, body.cartoon-page-reference table.works td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--panel-border); vertical-align: top; }
body.cartoon-page-reference table.works th { color: var(--accent2); font-size: 12px; text-transform: uppercase; letter-spacing: .05em; }
body.cartoon-page-reference table.works td.year { white-space: nowrap; color: var(--text-dim); }
body.cartoon-page-reference .rights { font-size: 12.5px; color: var(--text-dim); font-style: italic; margin-top: 12px; border-top: 1px solid var(--panel-border); padding-top: 10px; }
body.cartoon-page-reference footer { text-align: center; color: var(--text-dim); font-size: 12.5px; padding: 24px 20px; }
@media (max-width: 620px) {
  body.cartoon-page-reference dl.style-grid { grid-template-columns: 1fr; gap: 2px 0; }
  body.cartoon-page-reference dl.style-grid dt { margin-top: 8px; }
}

/* ============================================================
   PART 4 — TAGS (TK-12165)
   Shared tag-chip/filter-bar/cloud styling for the front page
   (story cards + article view), the cartoon gallery, and every
   individual cartoon strip page. Reuses the shared site tokens
   instead of carrying its own fixed palette.
   ============================================================ */
.tag-chips, .tag-cloud-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.card-tags { margin: 8px 0 0; }
.article-tags { margin: 4px 0 0; }
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .01em;
  text-decoration: none;
  color: var(--text-dim);
  background: color-mix(in srgb, var(--panel) 88%, var(--text) 4%);
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  padding: 3px 10px;
  line-height: 1.4;
  transition: color .15s ease, border-color .15s ease, background-color .15s ease;
}
.tag-chip::before { content: "#"; opacity: .55; }
.tag-chip:hover, .tag-chip:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--panel));
}
.tag-chip.active {
  color: var(--chrome, var(--panel));
  background: var(--accent);
  border-color: var(--accent);
}
.tag-count {
  font-size: .68rem;
  opacity: .75;
}

/* "Tagged: X ✕" indicator — replaces the tag cloud while a filter is active. */
.tag-filter-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin: 10px 0 14px;
  padding: 8px 14px;
  background: color-mix(in srgb, var(--accent) 10%, var(--panel));
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: .85rem;
  color: var(--text);
}
.tag-filter-label strong { color: var(--accent); }
.tag-filter-clear {
  color: var(--text-dim);
  text-decoration: none;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  padding: 2px 10px;
  font-size: .78rem;
}
.tag-filter-clear:hover, .tag-filter-clear:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
}

/* Tag cloud — top tags with counts, hidden while a filter is active. */
.tag-cloud {
  margin: 8px 0 16px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
}
.tag-cloud:empty { margin: 0; }
.tag-cloud-label {
  font-family: -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: .78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-dim);
}

/* Cartoon gallery: the card is an <a> (whole thumb+body is clickable), so
   the tag row sits as a SIBLING inside .card-wrap instead of nesting
   another interactive element inside that anchor. */
.card-wrap { display: flex; flex-direction: column; }
.card-tags-row { margin-top: 6px; }

/* ============================================================
   PART 5 — FRONT-PAGE INLINE STYLE CLEANUP (TK-12226)
   These 9 rules replace `style="..."` attributes that used to live
   directly on index.html markup (cartoon-desk-link + the featured
   editorial cartoon block). Moved here so index.html carries zero
   inline styles and everything routes through the single stylesheet.
   The former inline styles set font-family:Georgia/'Times New Roman'
   on the featured-cartoon title/caption; that's dropped in favor of
   the site's one sans-serif font stack (GOAL: one font system). */
.cartoon-desk-link {
  display: flex; align-items: center; gap: 6px;
  color: var(--text-dim); text-decoration: none; font-size: .8rem;
  border: 1px solid var(--panel-border); padding: 6px 10px;
  border-radius: 6px; white-space: nowrap;
}
.featured-cartoon { max-width: 1100px; margin: 18px auto 64px; padding: 0 16px; }
.featured-cartoon-panel {
  background: var(--panel); border: 1px solid var(--panel-border);
  border-radius: 8px; padding: 12px;
}
.featured-cartoon-link { display: block; text-decoration: none; color: inherit; }
.featured-cartoon-eyebrow {
  margin: 0 0 8px; font-size: .75rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent); font-weight: 700;
}
.featured-cartoon-video {
  display: block; width: 100%; height: auto;
  border: 4px solid #0b0b0c; border-radius: 2px; margin-bottom: 56px;
}
.featured-cartoon-title { margin: 10px 0 2px; font-size: clamp(1.1rem, 2.6vw, 1.6rem); }
.featured-cartoon-caption {
  margin: 0; font-style: italic; color: var(--text-dim); font-size: .95rem;
}

/* ============================================================
   PART 4 — Shadow Man gallery (cartoons/shadow-man.html, TK-12241)
   Scoped under body.shadow-man-page. Reuses Part 2's .grid/.card/
   .controls; only the bits that differ live here.
   ============================================================ */
body.shadow-man-page .sm-card { font: inherit; text-align: left; cursor: pointer; padding: 0; width: 100%; }
body.shadow-man-page .sm-card .thumb { aspect-ratio: 7 / 6; background: #fff; }
body.shadow-man-page .sm-card .thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }
body.shadow-man-page .sm-card-caption { font-style: italic; }
body.shadow-man-page .sm-tags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
body.shadow-man-page .sm-era { font-size: .68rem; font-weight: 800; letter-spacing: .3px; padding: .15rem .5rem; border-radius: 999px; border: 1px solid var(--panel-border); color: var(--text-dim); }
body.shadow-man-page .sm-dialog { max-width: min(960px, 94vw); width: 100%; background: var(--panel); color: var(--text); border: 1px solid var(--panel-border); border-radius: 12px; padding: 18px 20px 20px; }
body.shadow-man-page .sm-dialog::backdrop { background: rgba(0, 0, 0, .7); }
body.shadow-man-page .sm-dialog h2 { margin: 0 32px 4px 0; font-size: 1.3rem; }
body.shadow-man-page .sm-dialog img { width: 100%; height: auto; display: block; background: #fff; border-radius: 6px; margin: 10px 0; }
body.shadow-man-page .sm-meta { font-size: .8rem; color: var(--text-dim); margin: 0; }
body.shadow-man-page .sm-caption { font-style: italic; margin: 0; }
body.shadow-man-page .sm-close { position: absolute; top: 10px; right: 12px; background: none; border: 0; color: var(--text); font-size: 1.6rem; cursor: pointer; line-height: 1; }