:root {
  color-scheme: light;
  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --blue: #2563EB;
  --blue-light: #EFF6FF;
  --bg: #F7F8FB;
  --card-bg: #FFFFFF;
  --border: #E5E7EB;
  --text: #0F172A;
  --text-soft: #475569;
  --text-faint: #94A3B8;
  --green: #16A34A;
  --red: #DC2626;
  --amber: #D97706;

  /* Neutrals -- name the existing hardcoded slates */
  --slate-50: #F8FAFC; --slate-100: #F1F5F9; --slate-200: var(--border);
  --slate-300: #CBD5E1; --slate-400: var(--text-faint); --slate-600: var(--text-soft); --slate-900: var(--text);

  /* Brand blue, extended */
  --blue-dark: #1D4ED8; --blue-100: #DBEAFE;

  /* New status hues for the 7 forward-declared statuses + "soon" page icons */
  --teal: #0D9488; --teal-light: #F0FDFA;      /* visuals */
  --violet: #7C3AED; --violet-light: #F5F3FF;  /* captions */
  --indigo: #4F46E5; --indigo-light: #EEF2FF;  /* review */
  --sky: #0284C7; --sky-light: #F0F9FF;        /* schedule */
  /* connections reuses slate -- it's a settings/integrations concept, not a pipeline stage */

  /* Radius scale */
  --radius-sm: 6px; --radius-md: 8px; --radius-lg: 12px; --radius-xl: 16px; --radius-full: 999px;

  /* Elevation -- there are currently zero box-shadows anywhere except the range thumb */
  --shadow-sm: 0 1px 2px rgba(15,23,42,.04);
  --shadow-md: 0 4px 12px rgba(15,23,42,.08);
  --shadow-lg: 0 12px 28px rgba(15,23,42,.10);
  --shadow-focus: 0 0 0 3px rgba(37,99,235,.18);

  /* Spacing -- used only in NEW components (see .mt-*/.gap-* utilities below
     for existing-template cleanup, which match exact pre-existing pixel
     values instead of this scale) */
  --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px;

  /* Type scale -- name what's already in use, add two new sizes for stat tiles */
  --text-xs: 11px; --text-sm: 12px; --text-base: 13px; --text-md: 14px;
  --text-lg: 16px; --text-xl: 22px; --text-2xl: 28px;

  /* Motion -- see base of file for the reduced-motion guardrail that must
     always win over these */
  --ease-out: cubic-bezier(0.16,1,0.3,1);        /* entrances -- decelerate hard, snappy */
  --ease-in-out: cubic-bezier(0.4,0,0.2,1);      /* hover/press state changes */
  --ease-spring: cubic-bezier(0.34,1.56,0.64,1); /* small reward "pop" moments only */
  --dur-instant: 100ms; --dur-fast: 150ms; --dur-base: 200ms; --dur-slow: 300ms; --dur-entrance: 400ms;
}

/* Reduced-motion guardrail -- added before anything below uses the motion
   tokens, so every animation/transition this file defines collapses to
   effectively instant for anyone who has asked the OS for less motion. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
}
a { color: inherit; text-decoration: none; }

/* Focus-visible ring -- currently missing entirely */
a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  transition: box-shadow var(--dur-fast) var(--ease-out);
}

.shell { display: flex; min-height: 100vh; }

.sidenav {
  width: 230px;
  flex-shrink: 0;
  background: var(--card-bg);
  border-right: 1px solid var(--border);
  padding: 24px 0;
  position: sticky;
  top: 0;
  height: 100vh;
}
.sidenav-brand {
  font-weight: 800;
  font-size: 15px;
  padding: 0 20px 20px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 8px;
}
.sidenav-list { list-style: none; margin: 0; padding: 8px 12px; }
.sidenav-list li { margin-bottom: 2px; }
.sidenav-section-label {
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-faint);
  padding: 14px 12px 6px;
}
.sidenav-list a {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: space-between;
  padding: 10px 12px;
  border-radius: 8px;
  font-weight: 600;
  font-size: 14px;
  color: var(--text-soft);
  position: relative;
  overflow: hidden;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.sidenav-list a::before {
  content: '';
  position: absolute;
  left: 0; top: 4px; bottom: 4px;
  width: 3px;
  border-radius: var(--radius-full);
  background: var(--blue);
  transform: scaleY(0);
  transform-origin: center;
  transition: transform var(--dur-fast) var(--ease-out);
}
.sidenav-list a:hover { background: var(--blue-light); color: var(--blue); }
.sidenav-list a.active { background: var(--blue-light); color: var(--blue); }
.sidenav-list a:hover::before, .sidenav-list a.active::before { transform: scaleY(1); }
.sidenav-list a.soon { color: var(--text-faint); }
.sidenav-list a.soon:hover { background: var(--bg); color: var(--text-faint); }
.sidenav-list a.soon::before { display: none; }
.sidenav-list svg { flex-shrink: 0; }
.pill {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  background: var(--border);
  color: var(--text-soft);
  padding: 2px 6px;
  border-radius: 999px;
}

.content { flex: 1; padding: 32px 40px; max-width: 960px; }

/* Page content entrance fade -- the CSS-only substitute for a page
   transition, since every nav is a full server reload (no SPA router). */
@keyframes page-enter { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.content { animation: page-enter var(--dur-entrance) var(--ease-out) both; }

h1 { font-size: 22px; font-weight: 800; margin: 0 0 4px; }
h2 { font-size: 16px; font-weight: 700; margin: 0 0 12px; }
.subtitle { color: var(--text-soft); margin: 0 0 28px; font-size: 14px; }

.card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 22px;
  margin-bottom: 20px;
}

/* Card entrance stagger, capped so long pages don't feel slow. Uses the
   CSS4 :nth-child(An+B of S) form so the stagger counts only among .card
   siblings, not every child of .content (headings/notices come first on
   every page and would otherwise throw the count off). */
@keyframes card-enter { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.content > .card { animation: card-enter var(--dur-entrance) var(--ease-out) both; }
.content > .card:nth-child(1 of .card) { animation-delay: 0ms; }
.content > .card:nth-child(2 of .card) { animation-delay: 40ms; }
.content > .card:nth-child(3 of .card) { animation-delay: 80ms; }
.content > .card:nth-child(4 of .card) { animation-delay: 120ms; }
.content > .card:nth-child(n+5 of .card) { animation-delay: 160ms; }
/* Opt out for cards whose sole content is an output preview -- paired with
   .media-reveal on the preview itself so the two motions don't double up. */
.card-no-entrance { animation: none; }

/* Opt-in hover lift -- NOT applied to every .card (most are static forms);
   used by stat tiles (B5) and any pure link-wrapper card. */
.card-interactive { cursor: pointer; transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
.card-interactive:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th { text-align: left; color: var(--text-faint); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; padding: 8px 10px; border-bottom: 1px solid var(--border); }
td { padding: 12px 10px; border-bottom: 1px solid var(--border); vertical-align: top; }
tr:last-child td { border-bottom: none; }
.table-scroll { overflow-x: auto; }

/* Table row stagger-in -- this app has no <tbody>, rows are direct <tr>
   siblings after the header row, so header (nth-child(1)) is excluded and
   data rows count from nth-child(2), capped at 6th+. */
@keyframes row-enter { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
table tr { animation: row-enter var(--dur-base) var(--ease-out) both; }
table tr:first-child { animation: none; }
table tr:nth-child(2) { animation-delay: 0ms; }
table tr:nth-child(3) { animation-delay: 20ms; }
table tr:nth-child(4) { animation-delay: 40ms; }
table tr:nth-child(5) { animation-delay: 60ms; }
table tr:nth-child(6) { animation-delay: 80ms; }
table tr:nth-child(n+7) { animation-delay: 100ms; }

.badge { display: inline-block; padding: 3px 9px; border-radius: 999px; font-size: 11px; font-weight: 700; }
.badge-draft { background: #F1F5F9; color: var(--text-soft); }
.badge-script_ready { background: var(--blue-light); color: var(--blue); }
.badge-voiceover_ready { background: #ECFDF5; color: var(--green); }
.badge-error { background: #FEF2F2; color: var(--red); }
.badge-uploaded { background: #F1F5F9; color: var(--text-soft); }
.badge-rendering { background: #FFFBEB; color: var(--amber); }
.badge-done { background: #ECFDF5; color: var(--green); }
.badge-style { background: #EEF2FF; color: #4F46E5; }

/* The 7 forward-declared statuses (visuals/captions/assembly/schedule/
   connections stages -- not reachable by any current route, see B4).
   Uses the B1 status hues; "review"/"posting"/"posted" aren't their own
   pipeline stage so they borrow the closest semantic hue instead of
   getting a dedicated one. */
.badge-visuals_ready { background: var(--teal-light); color: var(--teal); }
.badge-captions_ready { background: var(--violet-light); color: var(--violet); }
.badge-ready_for_review { background: var(--indigo-light); color: var(--indigo); }
.badge-approved { background: #ECFDF5; color: var(--green); }
.badge-posting { background: var(--sky-light); color: var(--sky); }
.badge-posted { background: #ECFDF5; color: var(--green); }
.badge-failed { background: #FEF2F2; color: var(--red); }

/* Status badge pop-on-load for the success family -- overshoot scale via
   the spring easing, reserved for this one "reward" moment. */
@keyframes badge-pop { 0% { transform: scale(.6); opacity: 0; } 60% { transform: scale(1.15); opacity: 1; } 100% { transform: scale(1); } }
.badge-done, .badge-voiceover_ready, .badge-approved, .badge-posted {
  animation: badge-pop var(--dur-slow) var(--ease-spring) both;
}

.video-wrap { position: relative; width: 100%; background: #000; border-radius: 8px; overflow: hidden; }
.video-wrap video { width: 100%; display: block; }
.draw-overlay { position: absolute; inset: 0; cursor: crosshair; pointer-events: none; }
.draw-overlay.active { pointer-events: auto; background: rgba(37, 99, 235, 0.04); }
.marquee { position: absolute; border: 2px dashed var(--blue); background: rgba(37, 99, 235, 0.15); display: none; pointer-events: none; }

/* Output media reveal -- a bouncier "payoff" entrance for a freshly
   rendered video/image, distinct from the standard card-enter above (used
   together with .card-no-entrance on the wrapping card so the two motions
   don't double up on the Output-only cards). */
@keyframes media-reveal { from { opacity: 0; transform: scale(.97); } to { opacity: 1; transform: scale(1); } }
.media-reveal { animation: media-reveal var(--dur-slow) var(--ease-spring) both; }

/* Form validation shake -- static/app.js adds this class on a native
   HTML5 `invalid` event instead of the browser's default silent focus
   jump. */
@keyframes field-shake {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-4px); }
  40%, 60% { transform: translateX(4px); }
}
.field-shake { animation: field-shake 400ms var(--ease-in-out); border-color: var(--red) !important; box-shadow: 0 0 0 3px rgba(220,38,38,.15) !important; }

label { display: block; font-weight: 600; font-size: 13px; margin: 14px 0 6px; }
label:first-child { margin-top: 0; }
input[type=text], textarea, select {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-family: var(--font);
  font-size: 14px;
  background: #fff;
  color: var(--text);
  transition: border-color var(--dur-fast) var(--ease-out);
}
textarea { resize: vertical; min-height: 70px; }

.radio-row { display: flex; gap: 18px; flex-wrap: wrap; margin-bottom: 4px; }
.radio-row label { display: inline-flex; align-items: center; gap: 6px; font-weight: 500; margin: 0; }
.radio-row input[type=radio], .radio-row input[type=checkbox] { width: auto; }
.hint { font-size: 12px; color: var(--text-faint); margin-top: 6px; }

input[type=range] {
  width: 100%;
  height: 20px;
  padding: 0;
  background: transparent;
  -webkit-appearance: none;
}
input[type=range]::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 999px;
  background: var(--border);
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  margin-top: -6px;
  border-radius: 50%;
  background: var(--blue);
  border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out);
}
input[type=range]:active::-webkit-slider-thumb { transform: scale(1.15); }
input[type=range]::-moz-range-track {
  height: 6px;
  border-radius: 999px;
  background: var(--border);
}
input[type=range]::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--blue);
  border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out);
}
input[type=range]:active::-moz-range-thumb { transform: scale(1.15); }

.duration-estimate { font-size: 12px; color: var(--text-faint); margin-top: 6px; }

.export-progress { display: none; margin-top: 10px; width: 220px; }
.export-progress-track {
  height: 8px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
  position: relative;
}
.export-progress-bar {
  height: 100%;
  width: 0%;
  background: var(--blue);
  border-radius: 999px;
}
/* Subtle indeterminate shimmer sweep, layered over the whole track so a
   long linear fill doesn't read as "stuck" while it's still climbing. */
.export-progress-track::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image: linear-gradient(115deg, transparent 0%, rgba(255,255,255,.55) 45%, transparent 60%);
  background-size: 200% 100%;
  animation: progress-shimmer 1.4s linear infinite;
  pointer-events: none;
}
@keyframes progress-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
.export-progress-label { font-size: 12px; color: var(--text-faint); margin-top: 4px; }

button, .btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--blue);
  color: #fff;
  border: none;
  padding: 10px 16px;
  border-radius: 8px;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
  font-family: var(--font);
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-instant) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
button:hover, .btn:hover { background: #1D4ED8; }
button:active, .btn:active { transform: translateY(1px) scale(.98); }
button.secondary, .btn.secondary { background: #fff; color: var(--text); border: 1px solid var(--border); }
button.secondary:hover, .btn.secondary:hover { background: var(--bg); }
button:disabled { background: var(--border); color: var(--text-faint); cursor: not-allowed; transform: none; }
button:disabled:active, .btn:disabled:active { transform: none; }

/* Delete-confirm buttons -- replaces the old style="color:var(--red)" +
   .secondary inline pattern with a real hover-tinted danger variant. */
.btn-danger, button.btn-danger {
  background: #fff;
  color: var(--red);
  border: 1px solid var(--border);
}
.btn-danger:hover, button.btn-danger:hover { background: #FEF2F2; border-color: #FECACA; }

.notice { padding: 12px 14px; border-radius: 8px; font-size: 13px; margin-bottom: 16px; }
.notice-error { background: #FEF2F2; color: var(--red); border: 1px solid #FECACA; }
.notice-warn { background: #FFFBEB; color: var(--amber); border: 1px solid #FDE68A; }
/* Notice/toast entrance */
@keyframes notice-enter { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.notice { animation: notice-enter var(--dur-base) var(--ease-out) both; }

.beat { border: 1px solid var(--border); border-radius: 10px; padding: 14px; margin-bottom: 10px; background: #FAFBFC; }
.beat-label { font-size: 11px; font-weight: 700; color: var(--text-faint); text-transform: uppercase; margin-bottom: 8px; }

/* .empty already inherits its parent .card's entrance animation (B3) --
   no separate rule needed here. Optional muted icon (B4 stretch) reuses
   the B4 nav-icon macro, scaled up and dimmed. */
.empty { color: var(--text-faint); font-size: 14px; padding: 20px 0; text-align: center; }
.empty-icon { color: var(--text-faint); opacity: .55; margin-bottom: var(--space-3); }
.empty-icon svg { width: 28px; height: 28px; }

/* Upcoming ("soon") page hero (B6) */
.upcoming-hero { text-align: center; padding: var(--space-8) var(--space-4) var(--space-4); }
.upcoming-icon-circle {
  width: 72px; height: 72px; border-radius: var(--radius-full);
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto var(--space-5);
}
.upcoming-icon-circle svg { width: 32px; height: 32px; }
.upcoming-note { max-width: 460px; margin: 0 auto; color: var(--text-soft); font-size: var(--text-md); line-height: 1.5; }
.ratio-bar { max-width: 260px; margin: var(--space-6) auto 0; }
.ratio-bar-track { height: 8px; border-radius: var(--radius-full); background: var(--border); overflow: hidden; }
.ratio-bar-fill { height: 100%; border-radius: var(--radius-full); background: var(--blue); transition: width var(--dur-entrance) var(--ease-out); }
.ratio-bar-label { font-size: var(--text-xs); color: var(--text-faint); margin-top: var(--space-2); }

/* /series dashboard-overview stat tiles (B5) */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-4); margin-bottom: var(--space-6); }
.stat-tile { background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius-xl); padding: var(--space-5); box-shadow: var(--shadow-sm); }
.stat-tile-value { font-size: var(--text-2xl); font-weight: 800; }
.stat-tile-label { font-size: var(--text-xs); text-transform: uppercase; color: var(--text-faint); letter-spacing: .04em; margin-top: 2px; }

audio { width: 100%; margin-top: 8px; }

/* ------------------------------------------------------------ Utilities --
   B2: matches the EXACT pixel values already scattered through templates
   as inline styles, so swapping style="..." for one of these classes is a
   zero-visual-diff find-and-replace, not a retrofit onto a strict 4/8px
   scale (see redesign plan B2 for why). New CSS should use the --space-*
   tokens above instead of these. */
.mt-0 { margin-top: 0; }
.mt-10 { margin-top: 10px; }
.mt-12 { margin-top: 12px; }
.mt-14 { margin-top: 14px; }
.mt-16 { margin-top: 16px; }
.mt-18 { margin-top: 18px; }
.mt-20 { margin-top: 20px; }
.mb-0 { margin-bottom: 0; }
.ml-10 { margin-left: 10px; }
.m-0 { margin: 0; }

.row { display: flex; }
.row-wrap { display: flex; flex-wrap: wrap; }
.items-center { align-items: center; }

.gap-xs { gap: 6px; }
.gap-sm { gap: 10px; }
.gap-md { gap: 14px; }
.gap-lg { gap: 18px; }
.gap-xl { gap: 20px; }

.flex-cell { flex: 1; min-width: 140px; }
.flex-cell-wide { flex: 1; min-width: 160px; }

.text-faint { color: var(--text-faint); }
.text-soft { color: var(--text-soft); }
.text-danger { color: var(--red); }
.text-sm { font-size: 13px; }
.text-xs { font-size: 12px; }
.text-link { color: var(--blue); }

.checkbox-inline { width: auto; display: inline-block; margin-right: 6px; }
.swatch { width: 80px; height: 40px; padding: 4px; }
.code-textarea { min-height: 220px; font-family: monospace; font-size: 12.5px; }
.w-full-input { width: 100%; }

.media-rounded { width: 100%; border-radius: 8px; }
.media-rounded-dark { width: 100%; border-radius: 8px; background: #000; }
.thumb-220 { max-width: 220px; }
.audio-180 { width: 180px; }
.audio-200 { width: 200px; }
.bg-subtle { background: #FAFBFC; }

/* --------------------------------------------------------- Responsive --
   B7: previously zero @media queries anywhere in this file. */

/* Any un-classed image left after the B5 sweep still shouldn't overflow
   its container on a narrow viewport. */
img { max-width: 100%; height: auto; }

@media (max-width: 960px) {
  .content { padding: 20px; max-width: none; }
}

/* Checkbox-hack collapsible sidenav -- a hidden checkbox + label toggle,
   no JS needed (consistent with the "no SPA router" constraint). The
   checkbox and backdrop label are siblings of .shell in base.html so the
   `~` general-sibling combinator can reach into it. */
.nav-toggle-checkbox { display: none; }
.nav-toggle-btn { display: none; }
.nav-backdrop { display: none; }

@media (max-width: 860px) {
  .shell { display: block; }
  .nav-toggle-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: 14px;
    left: 14px;
    z-index: 30;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-md);
    background: var(--card-bg);
    border: 1px solid var(--border);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    font-size: 18px;
  }
  .sidenav {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 25;
    width: 230px;
    transform: translateX(-100%);
    transition: transform var(--dur-slow) var(--ease-out);
    box-shadow: var(--shadow-lg);
  }
  .nav-toggle-checkbox:checked ~ .shell .sidenav { transform: translateX(0); }
  .nav-toggle-checkbox:checked ~ .nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, .35);
    z-index: 20;
  }
  .content { padding: 20px 16px; padding-top: 72px; }
}

/* Small phones -- tighten the stat-grid/gaps a bit further; auto-fit/
   minmax already reflows the grid itself with no extra rule needed. */
@media (max-width: 480px) {
  .content { padding: 16px; padding-top: 68px; }
  .stat-grid { gap: var(--space-2); }
  h1 { font-size: 19px; }
}

/* -------------------------------------------------------- Dark mode ----
   B8 (stretch): @media (prefers-color-scheme:dark) remapping the same
   custom properties -- no JS toggle, no new control. Most of the app
   already reads color through var(--bg)/var(--card-bg)/var(--text)/etc,
   so remapping :root here is most of the work; a handful of rules use a
   literal hex tint directly (badges, notices, .beat/.bg-subtle, plain
   inputs) and need an explicit override alongside it.
   Known gap, accepted rather than restructured: templates/upcoming.html's
   icon-circle background/color are literal hex passed from main.py's
   _UPCOMING (deliberately not CSS-var-interpolated in Jinja, see B6) --
   those few pixels don't repaint for dark mode. Shadows are left
   unchanged too (a dark-on-dark shadow is subtle to the point of
   invisible, which reads as fine rather than broken). */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0B1220;
    --card-bg: #151B29;
    --border: #26314A;
    --text: #E8ECF3;
    --text-soft: #A6B0C3;
    --text-faint: #6B7690;
    --blue: #3B82F6;
    --blue-light: #16233D;
    --teal-light: #0F2A28;
    --violet-light: #241A3D;
    --indigo-light: #1E2247;
    --sky-light: #0F2436;
  }

  .badge-draft, .badge-uploaded { background: #1E2636; }
  .badge-voiceover_ready, .badge-done, .badge-approved, .badge-posted { background: #0F2A1E; }
  .badge-error, .badge-failed { background: #2E1618; }
  .badge-rendering { background: #2E2410; }
  .badge-style { background: #1E2247; color: #A5B4FC; }

  input[type=text], textarea, select { background: #1B2333; }
  button.secondary, .btn.secondary { background: var(--card-bg); }
  .btn-danger, button.btn-danger { background: var(--card-bg); }
  .btn-danger:hover, button.btn-danger:hover { background: #2E1618; border-color: #5C2A2E; }

  .notice-error { background: #2E1618; border-color: #5C2A2E; }
  .notice-warn { background: #2E2410; border-color: #5C4A1E; }

  .beat, .bg-subtle { background: #1B2333; }

  input[type=range]::-webkit-slider-thumb, input[type=range]::-moz-range-thumb { border-color: var(--card-bg); }
}
