← back to Crazy News Channel
assets/style.css
1512 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;
}
/* TK-12243 review-queue actions (rq-*): select 1..many + big Approve/Delete buttons below
each card, a sticky bulk bar, an in-page delete-confirm modal and an Undo toast. Driven by
daily-cartoons/review-server.mjs (body[data-mode="live"]); the static review.html renders
the same controls disabled (body[data-mode="static"]). */
body.cartoon-queue-page [hidden] { display: none !important; }
/* The site-wide overflow-x:hidden on html+body turns body into a (non-scrolling) scroll
container, so the sticky bulk bar pinned to body instead of the viewport and sat off-screen.
overflow-x:clip still clips horizontally but creates no scroll container. */
html:has(body.cartoon-queue-page), body.cartoon-queue-page { overflow-x: clip; }
body.cartoon-queue-page .rq-card { display: flex; flex-direction: column; position: relative; }
body.cartoon-queue-page .rq-card .body { flex: 1; }
body.cartoon-queue-page .rq-card.rq-selected { outline: 4px solid var(--focus); outline-offset: -4px; }
body.cartoon-queue-page .rq-card.rq-approved { border-color: var(--accent); }
body.cartoon-queue-page .rq-select {
position: absolute; top: 8px; left: 8px; z-index: 2; display: flex; align-items: center; gap: 6px;
background: var(--panel); border: 2px solid var(--ink); padding: 6px 10px; font-weight: 700; cursor: pointer;
}
body.cartoon-queue-page .rq-select input { width: 22px; height: 22px; margin: 0; cursor: pointer; accent-color: var(--accent2); }
body.cartoon-queue-page .rq-select:has(input:disabled) { cursor: not-allowed; opacity: .6; }
body.cartoon-queue-page .rq-actions { display: flex; flex-direction: column; gap: 8px; padding: 0 12px 12px; }
body.cartoon-queue-page .rq-btn {
display: block; width: 100%; min-height: 52px; font: 700 1.1rem/1.2 inherit; letter-spacing: .02em;
border: 3px solid var(--ink); cursor: pointer; color: #fff;
}
body.cartoon-queue-page .rq-btn:focus-visible, body.cartoon-queue-page .rq-mini:focus-visible,
body.cartoon-queue-page .rq-select:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
body.cartoon-queue-page .rq-approve { background: #13804a; }
body.cartoon-queue-page .rq-approve:hover:not(:disabled) { background: #0d6639; }
body.cartoon-queue-page .rq-delete { background: #b3261e; }
body.cartoon-queue-page .rq-delete:hover:not(:disabled) { background: #8c1d17; }
body.cartoon-queue-page .rq-cancel { background: var(--panel); color: var(--text); }
body.cartoon-queue-page .rq-btn:disabled { cursor: not-allowed; opacity: .45; }
body.cartoon-queue-page .rq-approved .rq-approve:disabled { opacity: 1; background: var(--panel); color: #13804a; border-color: #13804a; }
body.cartoon-queue-page .rq-err { margin: 8px 0 0; padding: 6px 8px; border: 2px solid #b3261e; color: #b3261e; font-size: .85rem; }
body.cartoon-queue-page .rq-mode-note { margin: 6px 0 0; font-size: .85rem; font-weight: 700; }
body.cartoon-queue-page .rq-static-note { color: #b3261e; }
body.cartoon-queue-page .rq-mini {
min-height: 44px; padding: 0 14px; font: 700 .95rem inherit; background: var(--panel); color: var(--text);
border: 2px solid var(--ink); cursor: pointer;
}
body.cartoon-queue-page.rq-busy { cursor: progress; }
body.cartoon-queue-page .rq-bulk {
position: sticky; bottom: 0; z-index: 30; max-width: 1400px; margin: 16px auto 0; padding: 10px 12px;
display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
background: var(--chrome); border: 3px solid var(--ink); box-shadow: 0 -6px 20px #0003;
}
body.cartoon-queue-page .rq-bulk .rq-btn { width: auto; flex: 1 1 180px; }
body.cartoon-queue-page .rq-count { font-weight: 700; min-width: 7em; }
body.cartoon-queue-page .rq-modal {
position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center;
padding: 16px; background: #000a;
}
body.cartoon-queue-page .rq-modal-box { width: min(460px, 100%); background: var(--panel); border: 4px solid var(--ink); padding: 18px; }
body.cartoon-queue-page .rq-modal-box h2 { margin: 0 0 8px; }
body.cartoon-queue-page .rq-modal-btns { display: flex; gap: 10px; margin-top: 14px; }
body.cartoon-queue-page .rq-toast {
position: fixed; left: 50%; bottom: 96px; transform: translateX(-50%); z-index: 40;
display: flex; align-items: center; gap: 12px; padding: 10px 14px; max-width: calc(100% - 32px);
background: var(--text); color: var(--bg); border: 2px solid var(--ink); font-weight: 700;
}
body.cartoon-queue-page .rq-bulk-err {
order: -1; flex: 1 1 100%; margin: 0; padding: 8px 10px; background: var(--panel); border: 3px solid #b3261e;
color: #b3261e; font-weight: 700; font-size: .95rem;
}
/* Article picker (rq-picker): shown above Approve/Delete on queue cards with no story_id. */
body.cartoon-queue-page .rq-picker {
display: flex; flex-direction: column; gap: 6px; padding: 10px; border: 3px dashed #b3261e; background: var(--bg);
}
body.cartoon-queue-page .rq-picker.rq-picked { border-style: solid; border-color: #13804a; }
body.cartoon-queue-page .rq-picker-h { margin: 0; font-weight: 700; }
body.cartoon-queue-page .rq-req { color: #b3261e; font-weight: 400; font-size: .85rem; }
body.cartoon-queue-page .rq-sugg-h, body.cartoon-queue-page .rq-pk-l { margin: 4px 0 0; font-size: .8rem; font-weight: 700; color: var(--text-dim); }
body.cartoon-queue-page .rq-sugg { display: flex; flex-direction: column; gap: 6px; }
body.cartoon-queue-page .rq-sugg-none { margin: 0; font-size: .85rem; color: var(--text-dim); }
body.cartoon-queue-page .rq-sugg-none.rq-pk-bad { color: #b3261e; font-weight: 700; }
body.cartoon-queue-page .rq-sugg-btn {
display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; min-height: 48px;
padding: 6px 10px; text-align: left; font: 600 .9rem/1.25 inherit; background: var(--panel); color: var(--text);
border: 2px solid var(--ink); cursor: pointer;
}
body.cartoon-queue-page .rq-sugg-btn:hover { background: var(--chrome); }
body.cartoon-queue-page .rq-sugg-btn[aria-pressed="true"] { border-color: #13804a; box-shadow: inset 0 0 0 2px #13804a; }
body.cartoon-queue-page .rq-sugg-t { min-width: 0; }
body.cartoon-queue-page .rq-sugg-src { flex: none; }
body.cartoon-queue-page .rq-pk-q {
width: 100%; min-height: 48px; padding: 0 10px; border: 2px solid var(--ink); background: var(--panel); color: var(--text);
}
body.cartoon-queue-page .rq-pk-s {
width: 100%; min-height: 150px; padding: 2px; border: 2px solid var(--ink); background: var(--panel); color: var(--text); font-size: .9rem;
}
body.cartoon-queue-page .rq-pk-s option { padding: 4px 6px; }
body.cartoon-queue-page .rq-sugg-btn:focus-visible, body.cartoon-queue-page .rq-pk-q:focus-visible,
body.cartoon-queue-page .rq-pk-s:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
body.cartoon-queue-page .rq-pk-chosen { margin: 0; font-size: .85rem; color: var(--text-dim); }
body.cartoon-queue-page .rq-pk-chosen.rq-pk-ok { color: #13804a; font-weight: 700; }
/* ---- 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; }
/* ============================================================
P24 Daily Edition (TK-12354) — top 6 + all the news
============================================================ */
.daily-edition { max-width: 1100px; margin: 18px auto 28px; padding: 0 16px; box-sizing: border-box; }
.daily-edition *, .daily-edition *::before, .daily-edition *::after { box-sizing: border-box; }
.de-head { border-top: 4px double var(--text); border-bottom: 1px solid var(--text); padding: 10px 0 8px; margin-bottom: 14px; text-align: center; }
.de-kicker { margin: 0; font-size: .72rem; letter-spacing: .16em; text-transform: uppercase; color: var(--accent); font-weight: 800; }
.de-title { margin: 4px 0 2px; font-family: Georgia, 'Times New Roman', serif; font-size: clamp(1.4rem, 4vw, 2.2rem); line-height: 1.15; color: var(--text); }
.de-meta { margin: 0; font-size: .82rem; color: var(--text-dim); }
.de-stale { background: #fff3cd; border: 1px solid #e0b400; color: #5c4400; border-radius: 6px; padding: 8px 12px; font-size: .88rem; }
.de-top { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr)); }
.de-card { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; min-width: 0; }
.de-fig { margin: 0; background: #fff; border-bottom: 3px solid #0b0b0c; }
.de-fig img { display: block; width: 100%; height: auto; aspect-ratio: 1024 / 657; object-fit: cover; }
.de-noimg { aspect-ratio: 1024 / 657; display: grid; place-items: center; font-size: 3rem; background: var(--bg); }
.de-cap { font-family: Georgia, serif; font-style: italic; font-size: .92rem; padding: 8px 12px 10px; color: #111; text-align: center; }
.de-body { padding: 10px 14px 14px; }
.de-rank { margin: 0 0 4px; display: flex; align-items: center; gap: 8px; font-size: .72rem; text-transform: uppercase; letter-spacing: .1em; }
.de-num { display: inline-grid; place-items: center; width: 1.7rem; height: 1.7rem; border-radius: 50%; background: var(--text); color: var(--panel); font-weight: 800; font-size: .85rem; letter-spacing: 0; }
.de-sec { color: var(--accent); font-weight: 700; }
.de-h { margin: 0 0 6px; font-family: Georgia, 'Times New Roman', serif; font-size: 1.12rem; line-height: 1.3; overflow-wrap: anywhere; }
.de-h a, .de-list a { color: var(--text); text-decoration: none; }
.de-h a:hover, .de-list a:hover { text-decoration: underline; }
.de-h a:focus-visible, .de-list a:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.de-src { color: var(--text-dim); font-size: .8rem; margin: 0; }
.de-all { margin-top: 26px; background: var(--panel); border: 1px solid var(--panel-border); border-radius: 8px; padding: 14px 16px; }
.de-all-h { margin: 0 0 8px; font-family: Georgia, serif; font-size: 1.3rem; border-bottom: 2px solid var(--text); padding-bottom: 6px; }
.de-sec-block { margin-top: 12px; }
.de-sec-h { margin: 0 0 6px; font-size: .78rem; letter-spacing: .12em; text-transform: uppercase; color: var(--accent); }
.de-list { margin: 0; padding-left: 1.1rem; columns: 2 340px; column-gap: 28px; }
.de-list li { break-inside: avoid; margin: 0 0 8px; line-height: 1.35; overflow-wrap: anywhere; }
.de-credit { margin: 14px 0 0; font-size: .75rem; color: var(--text-dim); }
@media (max-width: 480px) { .de-list { columns: 1; } .daily-edition { padding: 0 12px; } }