/* ============================================================
   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;
  }
}

/* ============================================================
   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 existing mood tokens
   so tag chips retheme with body[data-mood="..."] like everything
   else, instead of carrying their 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; }
