/* AudioPress platform landing page. Layers on top of app.css (design tokens,
   reset, base type, dark-mode palette). Classes/ids only — no bare element
   selectors (those would leak onto the tenant chrome via the shared app.css).
   All motion is gated behind `prefers-reduced-motion: no-preference`. */

.platform-page {
  /* Wider than a reading column — this is a marketing surface that should make
     use of large screens. */
  --page-max: 72rem;
}

/* The platform's light "daytime studio" runs a touch dimmer + warmer than
   base.css's default light page: the near-white default reads glary on this
   large marketing surface and over-sharpens the contrast with the dark studio
   hardware. Light only — base.css's dark palette wins on specificity
   (`:root:not([data-theme="light"])` / `[data-theme="dark"]` both beat a bare
   `:root`), and this stylesheet is platform-only, so tenant sites are
   untouched. */
:root {
  --bg: hsl(40 32% 93.5%);
  --surface: hsl(42 42% 98%);
}

/* ---- Shared bits -------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-block-size: var(--min-tap);
  padding-block: var(--space-2xs);
  padding-inline: var(--space-l);
  font: inherit;
  font-weight: 600;
  border-radius: 999px;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
/* A button-styled link (`a.btn`) must not pick up the global `a:hover`
   underline (`base.css`) — its specificity would otherwise win on hover. */
.btn:hover,
.btn:focus-visible {
  text-decoration: none;
}
.btn-small {
  min-block-size: auto;
  padding-block: var(--space-3xs);
  padding-inline: var(--space-s);
}
.btn-primary {
  color: var(--on-accent);
  background: var(--accent);
}
.btn-ghost {
  color: var(--text);
  background: transparent;
  border-color: var(--border);
}
@media (prefers-reduced-motion: no-preference) {
  .btn {
    transition:
      transform var(--dur-quick) var(--ease-out),
      box-shadow var(--dur-default) var(--ease-out),
      filter var(--dur-quick) var(--ease-out);
  }
  @media (hover: hover) and (pointer: fine) {
    .btn-primary:hover {
      transform: translateY(-2px);
      box-shadow: 0 8px 24px -8px var(--accent);
    }
    .btn-ghost:hover {
      border-color: var(--muted);
    }
  }
  .btn:active {
    transform: translateY(0) scale(0.98);
  }
}

/* ---- Chrome ------------------------------------------------------------- */

.platform-header {
  position: sticky;
  inset-block-start: 0;
  z-index: 20;
  background: color-mix(in oklab, var(--bg) 82%, transparent);
  backdrop-filter: blur(10px);
  border-block-end: 1px solid var(--border);
}
.platform-header-inner,
.hero-inner,
.features-inner,
.platform-footer {
  inline-size: 100%;
  max-inline-size: var(--page-max);
  margin-inline: auto;
  padding-inline: clamp(var(--space-s), 4vw, var(--space-xl));
}
.platform-header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Wrap at ANY width: when the nav can't sit beside the brand it drops to its
     own full row (rather than squishing + wrapping internally with the toggle
     orphaned). `row-gap` separates the two rows when that happens. */
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-s);
  padding-block: var(--space-2xs);
  min-block-size: var(--min-tap);
}
.brand-wrap {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  /* Let a long show-context name ellipsis instead of shoving the nav. */
  min-inline-size: 0;
}
.platform-brand {
  flex: none;
  font-size: var(--step-1);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
  text-decoration: none;
}
.platform-brand .brand-press {
  color: var(--accent);
}
/* "You are in this show" context beside the wordmark (show-detail page): a quiet
   separator + the show name linking back to the show, truncated when cramped so
   the nav keeps its row. The sticky header keeps it in view while scrolling. */
.brand-sep {
  flex: none;
  color: var(--border);
  font-size: var(--step-1);
  user-select: none;
}
.platform-brand-show {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
  font-size: var(--step-0);
  font-weight: 600;
  text-decoration: none;
}
.platform-brand-show:hover,
.platform-brand-show:focus-visible {
  color: var(--accent);
}
/* The relocated "beta" mark — a quiet pill beside the wordmark, not a banner.
   A leading "live" dot ties it to the studio theme. */
.beta-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.2rem 0.6rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  background: color-mix(in oklab, var(--accent) 13%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent) 34%, transparent);
  border-radius: 999px;
}
.beta-tag::before {
  content: "";
  inline-size: 0.4em;
  block-size: 0.4em;
  border-radius: 999px;
  background: var(--accent);
}
@media (prefers-reduced-motion: no-preference) {
  .beta-tag::before {
    animation: beta-pulse 2.4s ease-in-out infinite;
  }
}
@keyframes beta-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}
.platform-nav {
  display: flex;
  align-items: center;
  gap: var(--space-m);
  /* Grow to fill the space right of the brand (items right-aligned). When the
     header wraps the nav to its own row, this makes it span the full width and
     lay out on one tidy line — never squished beside the brand. Its own items
     can still wrap (long localized CTAs) as a last resort. */
  flex: 1 1 auto;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.platform-nav a:not(.btn) {
  color: var(--muted);
  text-decoration: none;
}
.platform-nav a:not(.btn):hover {
  color: var(--text);
}
@media (max-width: 34rem) {
  /* On phones the header already stacks (brand row, nav row — see the general
     `flex-wrap` above). Drop the decorative beta tag to reclaim width and make
     the nav itself more compact (smaller text + tighter gaps) so the links +
     CTA + toggle fit comfortably however long the localized labels are. */
  .beta-tag {
    display: none;
  }
  .platform-nav {
    gap: var(--space-s);
    font-size: var(--step--1);
  }
}

/* Dark/light theme toggle — same markup + classes as the tenant banner, so the
   shared base.css palette flips and this just styles the button + swaps the
   sun/moon glyph for the effective scheme. The button (unlike the text links)
   stays visible at every width. */
.theme-toggle {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--min-tap);
  block-size: var(--min-tap);
  padding: 0;
  color: var(--text);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  cursor: pointer;
}
.theme-toggle:hover {
  background: color-mix(in oklab, var(--text) 9%, transparent);
}
.theme-glyph {
  inline-size: 1.25rem;
  block-size: 1.25rem;
}
/* Show the glyph for the *current* effective scheme: default follows the OS
   (prefers-color-scheme); an explicit override (`:root[data-theme]`) wins. */
.theme-icon {
  display: none;
  line-height: 0;
}
.theme-icon-sun {
  display: inline-flex;
}
@media (prefers-color-scheme: dark) {
  .theme-icon-sun {
    display: none;
  }
  .theme-icon-moon {
    display: inline-flex;
  }
}
:root[data-theme="light"] .theme-icon-sun {
  display: inline-flex;
}
:root[data-theme="light"] .theme-icon-moon {
  display: none;
}
:root[data-theme="dark"] .theme-icon-sun {
  display: none;
}
:root[data-theme="dark"] .theme-icon-moon {
  display: inline-flex;
}

.platform-footer {
  padding-block: var(--space-l);
  color: var(--muted);
  text-align: center;
}
.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-m);
  justify-content: center;
  margin-block-end: var(--space-2xs);
}
.footer-links a {
  color: var(--muted);
  text-decoration: none;
}
.footer-links a:hover {
  color: var(--text);
}

/* Keep anchor jumps clear of the sticky header. */
#subscribe,
#features,
#demo {
  scroll-margin-block-start: 5rem;
}

/* ---- Hero --------------------------------------------------------------- */

.hero {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.hero-inner {
  max-inline-size: 44rem;
  /* Extra top room so the content isn't jammed under the sticky header. */
  padding-block: clamp(var(--space-2xl), 12vw, 8rem)
    clamp(var(--space-xl), 9vw, calc(var(--space-2xl) * 1.2));
  text-align: center;
}
.hero-title {
  font-size: clamp(var(--step-3), 6vw, var(--step-4, 3.5rem));
  line-height: 1.04;
  letter-spacing: -0.02em;
  margin-block: var(--space-xs) var(--space-s);
  text-wrap: balance;
}
.hero-lead {
  max-inline-size: 40ch;
  margin-inline: auto;
  margin-block-end: var(--space-l);
  color: var(--muted);
  font-size: var(--step-1);
  text-wrap: pretty;
}
/* "On-air" emblem: a studio mic in a soft halo, with sound rings rippling out
   (rings are CSS pseudo-elements; only they animate). Static halo + mic when
   motion is reduced. */
.hero-mic {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: 3.6rem;
  block-size: 3.6rem;
  margin: 0 auto var(--space-m);
  color: var(--accent);
  background: color-mix(in oklab, var(--accent) 16%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent) 38%, transparent);
  border-radius: 999px;
}
.hero-mic .mic-glyph {
  inline-size: 1.9rem;
  block-size: 1.9rem;
}
.hero-mic::before,
.hero-mic::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 999px;
  border: 1px solid color-mix(in oklab, var(--accent) 50%, transparent);
  opacity: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .hero-mic::before {
    animation: mic-ring 3s var(--ease-out) infinite;
  }
  .hero-mic::after {
    animation: mic-ring 3s var(--ease-out) 1.5s infinite;
  }
}
@keyframes mic-ring {
  0% {
    transform: scale(1);
    opacity: 0.7;
  }
  100% {
    transform: scale(2.3);
    opacity: 0;
  }
}

/* Living studio backdrop: filled, blurred colour blobs (no radial-gradient
   rings) that roam the hero, plus a slow hue drift over the whole field —
   "studio lights" energy in the background itself. */
.hero-aurora {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  /* Glow palette. Default (the "daylight studio") = a warm sun-glow + a soft
     lilac, so light mode reads cosy and bright — daylight through the studio
     window. The dark "night studio" swaps in the cool brand purples below. */
  --glow-a: hsl(36 96% 64%);
  --glow-a-op: 0.34;
  --glow-b: hsl(286 68% 72%);
  --glow-b-op: 0.26;
}
.hero-aurora::before,
.hero-aurora::after {
  content: "";
  position: absolute;
  inline-size: 42rem;
  block-size: 42rem;
  border-radius: 50%;
  filter: blur(96px);
  will-change: transform;
}
.hero-aurora::before {
  background: var(--glow-a);
  opacity: var(--glow-a-op);
  inset-block-start: -24rem;
  inset-inline-start: -6rem;
}
.hero-aurora::after {
  background: var(--glow-b);
  opacity: var(--glow-b-op);
  inset-block-start: -26rem;
  inset-inline-end: -6rem;
}
/* Night studio (OS dark unless forced light) → the cool brand glow. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .hero-aurora {
    --glow-a: var(--accent);
    --glow-a-op: 0.38;
    --glow-b: var(--focus);
    --glow-b-op: 0.3;
  }
}
/* Forced dark → the cool brand glow. */
:root[data-theme="dark"] .hero-aurora {
  --glow-a: var(--accent);
  --glow-a-op: 0.38;
  --glow-b: var(--focus);
  --glow-b-op: 0.3;
}
@media (prefers-reduced-motion: no-preference) {
  .hero-aurora {
    animation: aurora-hue 14s ease-in-out infinite alternate;
  }
  .hero-aurora::before {
    animation: blob-a 19s ease-in-out infinite;
  }
  .hero-aurora::after {
    animation: blob-b 23s ease-in-out infinite;
  }
}
@keyframes aurora-hue {
  from {
    filter: hue-rotate(-20deg) saturate(1);
  }
  to {
    filter: hue-rotate(22deg) saturate(1.3);
  }
}
@keyframes blob-a {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  50% {
    transform: translate3d(16%, 9rem, 0) scale(1.18);
  }
}
@keyframes blob-b {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  50% {
    transform: translate3d(-14%, 7rem, 0) scale(1.12);
  }
}

/* Subscribe form (inline in the hero) */
.subscribe-form {
  display: flex;
  flex-wrap: wrap;
  /* Row gap (when the input + submit wrap to two rows on a narrow card) is
     generous so the button doesn't crowd the field; column gap (side-by-side on
     a wide viewport) stays tight. Applies to both the marketing "Notify me" and
     the sign-in "Email me a link" forms. */
  gap: var(--space-m) var(--space-2xs);
  justify-content: center;
  max-inline-size: 32rem;
  margin-inline: auto;
}
.subscribe-form input {
  flex: 1 1 15rem;
  min-block-size: var(--min-tap);
  padding-inline: var(--space-m);
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
}
.subscribe-form input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.hero-fineprint {
  margin-block-start: var(--space-xs);
  color: var(--muted);
  font-size: var(--step--1);
}
/* Signed-in hero CTA: a single "go to dashboard" button where the subscribe
   form sits for signed-out visitors. */
.hero-cta {
  display: flex;
  justify-content: center;
  margin-block-start: var(--space-s);
}

/* ---- Panels ------------------------------------------------------------- */

.panel {
  max-inline-size: var(--page-max);
  margin-inline: auto;
  padding-block: clamp(var(--space-xl), 6vw, var(--space-2xl));
  padding-inline: clamp(var(--space-s), 4vw, var(--space-xl));
}
.panel > h2,
.features-inner > h2 {
  font-size: var(--step-2);
  letter-spacing: -0.01em;
  text-align: center;
  text-wrap: balance;
}
.panel-lead {
  max-inline-size: 52ch;
  margin-inline: auto;
  color: var(--muted);
  font-size: var(--step-0);
  text-align: center;
}

/* ---- Features ----------------------------------------------------------- */

.panel-features {
  position: relative;
  isolation: isolate;
  /* Full-bleed band: the tinted background + borders span the viewport while
     `.features-inner` re-centres the content at `--page-max`. */
  max-inline-size: none;
  padding-block: clamp(var(--space-xl), 6vw, var(--space-2xl));
  padding-inline: 0;
  background: color-mix(in oklab, var(--surface) 50%, var(--bg));
  border-block: 1px solid var(--border);
  overflow: hidden;
}

/* Studio acoustic-foam texture: a faint quilted wedge pattern (cross-hatched
   gradients) for the "treated room" vibe, plus an animated TV-static grain on
   top via `::after`. Both decorative, low-contrast, behind the content. */
.studio-texture {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0.5;
  background-image:
    repeating-linear-gradient(
      45deg,
      color-mix(in oklab, var(--text) 4%, transparent) 0 2px,
      transparent 2px 16px
    ),
    repeating-linear-gradient(
      -45deg,
      color-mix(in oklab, var(--text) 4%, transparent) 0 2px,
      transparent 2px 16px
    );
  background-size: 22px 22px;
}
.studio-texture::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.07;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: no-preference) {
  .studio-texture::after {
    animation: noise-jitter 0.5s steps(4) infinite;
  }
}
@keyframes noise-jitter {
  0% {
    background-position: 0 0;
  }
  25% {
    background-position: -22px 14px;
  }
  50% {
    background-position: 16px -18px;
  }
  75% {
    background-position: -14px -22px;
  }
  100% {
    background-position: 18px 18px;
  }
}

/* ---- Feature "mixing desk" ---------------------------------------------- */
/* Two panels — a colourful channel deck + an LED screen. Side-by-side on wide
   viewports, stacked on narrow. Pure CSS: hidden radios give free single-select
   + keyboard nav; sibling selectors light the pad and swap the screen. The
   screen idles on a level-meter animation until a channel is pushed. */
/* The studio hardware — the mixing console + the demo TV — is a dark device in
   BOTH themes (a desk and a screen stay dark whatever the room), so its
   neon-on-black readouts stay legible and it reads as a deliberate object, not
   a washed-out panel. But a real console catches the room light: in the light
   "daylight studio" it's a *brighter, lit* charcoal; in the dark "night studio"
   it falls to near-black. We re-declare the semantic palette (+ a `--screen`
   token for the emissive displays) on these subtrees, following base.css's own
   light/dark structure, so every inner `var(--surface)` / `color-mix(--bg…)`
   resolves to the lit-or-unlit device value with no per-element change.

   Default (`:root`, i.e. daylight / forced-light) = lit charcoal. Kept a notch
   brighter than a pure dark device so it doesn't punch a harsh black hole in
   the soft daytime page — dark hardware catching plenty of room light. */
.mixer,
.tv {
  --bg: hsl(234 11% 23%);
  --surface: hsl(234 10% 29%);
  --text: hsl(220 22% 95%);
  --muted: hsl(220 12% 74%);
  --border: hsl(234 9% 39%);
  --screen: hsl(232 22% 13%);
  --tv-screen: hsl(232 24% 11%);
  color-scheme: dark;
}
/* Night studio (OS dark, unless forced light) → near-black hardware. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .mixer,
  :root:not([data-theme="light"]) .tv {
    --bg: hsl(230 18% 11%);
    --surface: hsl(230 16% 16%);
    --text: hsl(220 24% 92%);
    --muted: hsl(220 14% 66%);
    --border: hsl(230 12% 27%);
    --screen: #06070c;
    --tv-screen: #04050a;
  }
}
/* Forced dark → near-black hardware. */
:root[data-theme="dark"] .mixer,
:root[data-theme="dark"] .tv {
  --bg: hsl(230 18% 11%);
  --surface: hsl(230 16% 16%);
  --text: hsl(220 24% 92%);
  --muted: hsl(220 14% 66%);
  --border: hsl(230 12% 27%);
  --screen: #06070c;
  --tv-screen: #04050a;
}

.mixer {
  position: relative;
  display: grid;
  gap: var(--space-m);
  max-inline-size: 64rem;
  margin: var(--space-xl) auto 0;
  grid-template-columns: 1fr;
}
@media (min-width: 52rem) {
  .mixer {
    grid-template-columns: 1.05fr 1fr;
    align-items: stretch;
  }
}
/* Radios: visually hidden but kept focusable (not display:none). */
.mixer-radio {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  opacity: 0;
  pointer-events: none;
}
.mixer-deck {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  grid-auto-rows: 1fr;
  gap: var(--space-2xs);
  align-content: start;
}
/* A channel pad — a uniform tile, each a distinct hue (`--pad`), matte until
   lit. Equal size (grid `1fr` rows + stretch), centred caption. */
.mixer-key {
  --pad: var(--accent);
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 3.6rem;
  padding: var(--space-2xs) var(--space-s);
  font-size: var(--step--1);
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-wrap: balance;
  color: color-mix(in oklab, var(--pad) 35%, var(--text));
  background:
    radial-gradient(
      130% 130% at 50% 0%,
      color-mix(in oklab, var(--pad) 20%, transparent),
      transparent 72%
    ),
    color-mix(in oklab, var(--surface) 72%, #000);
  border: 1px solid color-mix(in oklab, var(--pad) 32%, var(--border));
  border-radius: 12px;
  cursor: pointer;
  user-select: none;
}
.mixer-key:nth-child(1) {
  --pad: hsl(255 90% 74%);
}
.mixer-key:nth-child(2) {
  --pad: hsl(218 90% 70%);
}
.mixer-key:nth-child(3) {
  --pad: hsl(190 85% 62%);
}
.mixer-key:nth-child(4) {
  --pad: hsl(160 68% 56%);
}
.mixer-key:nth-child(5) {
  --pad: hsl(45 95% 64%);
}
.mixer-key:nth-child(6) {
  --pad: hsl(25 92% 64%);
}
.mixer-key:nth-child(7) {
  --pad: hsl(330 88% 70%);
}
.mixer-key:nth-child(8) {
  --pad: hsl(284 82% 72%);
}
.mixer-key:nth-child(9) {
  --pad: hsl(205 88% 66%);
}
.mixer-key:nth-child(10) {
  --pad: hsl(140 62% 58%);
}
/* Per-channel motif lives on the SCREEN, behind the readout text — hidden until
   its channel is active, then it glows faintly (replacing the idle meter). The
   pads themselves stay text-only. */
.mixer-art {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: grid;
  place-items: center;
  color: var(--accent);
  opacity: 0;
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .mixer-art {
    transition: opacity var(--dur-default) var(--ease-out);
  }
}
.mixer-art-svg {
  inline-size: clamp(5rem, 30%, 9rem);
  block-size: auto;
  filter: drop-shadow(0 0 14px color-mix(in oklab, var(--accent) 45%, transparent));
}
/* Reveal the active channel's motif. (Per-id: label↔radio link is by id.) */
#mx-0:checked ~ .mixer-screen .art-0,
#mx-1:checked ~ .mixer-screen .art-1,
#mx-2:checked ~ .mixer-screen .art-2,
#mx-3:checked ~ .mixer-screen .art-3,
#mx-4:checked ~ .mixer-screen .art-4,
#mx-5:checked ~ .mixer-screen .art-5,
#mx-6:checked ~ .mixer-screen .art-6,
#mx-7:checked ~ .mixer-screen .art-7,
#mx-8:checked ~ .mixer-screen .art-8,
#mx-9:checked ~ .mixer-screen .art-9 {
  opacity: 0.2;
}
/* Each motif loops a small, *transform-only* motion (opacity stays governed by
   the active-channel rule, so inactive motifs never bleed through). Gentle —
   long durations, nothing that could induce motion sickness. */
@media (prefers-reduced-motion: no-preference) {
  .art-0 {
    animation: art-spin 20s linear infinite;
  }
  .art-1 {
    animation: art-spin 30s linear infinite;
  }
  .art-2 {
    animation: art-pulse 4.5s ease-in-out infinite;
  }
  .art-3 {
    animation: art-pulse 4s ease-in-out infinite;
  }
  .art-4 {
    animation: art-bob 4s ease-in-out infinite;
  }
  .art-5 {
    animation: art-float 8s ease-in-out infinite;
  }
  .art-6 {
    animation: art-pulse 5s ease-in-out infinite;
  }
  .art-7 {
    animation: art-pulse 4.5s ease-in-out infinite;
  }
  .art-8 {
    animation: art-float 8.5s ease-in-out infinite;
  }
  .art-9 {
    animation: art-float 9s ease-in-out infinite;
  }
}
@keyframes art-spin {
  to {
    transform: rotate(360deg);
  }
}
@keyframes art-pulse {
  0%,
  100% {
    transform: scale(0.94);
  }
  50% {
    transform: scale(1.07);
  }
}
@keyframes art-bob {
  0%,
  100% {
    transform: translateY(-4px);
  }
  50% {
    transform: translateY(4px);
  }
}
@keyframes art-float {
  0%,
  100% {
    transform: translateY(-3px) rotate(-1.2deg);
  }
  50% {
    transform: translateY(3px) rotate(1.2deg);
  }
}
@media (hover: hover) and (pointer: fine) {
  .mixer-key:hover {
    color: var(--text);
    border-color: color-mix(in oklab, var(--pad) 60%, var(--border));
  }
}
@media (prefers-reduced-motion: no-preference) {
  .mixer-key {
    transition:
      color var(--dur-quick) var(--ease-out),
      border-color var(--dur-quick) var(--ease-out),
      box-shadow var(--dur-quick) var(--ease-out),
      transform var(--dur-quick) var(--ease-out);
  }
  .mixer-key:active {
    transform: translateY(1px) scale(0.98);
  }
}

/* Active pad — lit in its own hue. (Per-id: a label↔radio link is by id.) */
#mx-0:checked ~ .mixer-deck [for="mx-0"],
#mx-1:checked ~ .mixer-deck [for="mx-1"],
#mx-2:checked ~ .mixer-deck [for="mx-2"],
#mx-3:checked ~ .mixer-deck [for="mx-3"],
#mx-4:checked ~ .mixer-deck [for="mx-4"],
#mx-5:checked ~ .mixer-deck [for="mx-5"],
#mx-6:checked ~ .mixer-deck [for="mx-6"],
#mx-7:checked ~ .mixer-deck [for="mx-7"],
#mx-8:checked ~ .mixer-deck [for="mx-8"],
#mx-9:checked ~ .mixer-deck [for="mx-9"] {
  color: #fff;
  border-color: var(--pad);
  background:
    radial-gradient(
      120% 130% at 50% 0%,
      color-mix(in oklab, var(--pad) 45%, transparent),
      transparent 78%
    ),
    color-mix(in oklab, var(--surface) 58%, #000);
  box-shadow:
    0 0 0 1px var(--pad),
    0 0 22px -4px var(--pad);
}
/* Keyboard focus ring on the visible pad (the radio itself is hidden). */
#mx-0:focus-visible ~ .mixer-deck [for="mx-0"],
#mx-1:focus-visible ~ .mixer-deck [for="mx-1"],
#mx-2:focus-visible ~ .mixer-deck [for="mx-2"],
#mx-3:focus-visible ~ .mixer-deck [for="mx-3"],
#mx-4:focus-visible ~ .mixer-deck [for="mx-4"],
#mx-5:focus-visible ~ .mixer-deck [for="mx-5"],
#mx-6:focus-visible ~ .mixer-deck [for="mx-6"],
#mx-7:focus-visible ~ .mixer-deck [for="mx-7"],
#mx-8:focus-visible ~ .mixer-deck [for="mx-8"],
#mx-9:focus-visible ~ .mixer-deck [for="mx-9"] {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* The LED screen: a dark inset panel. An idle level-meter (`.mixer-eq`) + TV
   grain (`.mixer-noise`) animate behind; a readout line glows on top once a
   channel is pushed (the meter dims behind it). */
.mixer-screen {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  align-items: center;
  min-block-size: 9rem;
  padding: var(--space-m);
  font-family: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
  font-size: var(--step--1);
  line-height: 1.6;
  text-align: start;
  color: color-mix(in oklab, var(--accent) 76%, #fff);
  background:
    repeating-linear-gradient(
      0deg,
      transparent 0 3px,
      color-mix(in oklab, #000 26%, transparent) 3px 4px
    ),
    var(--screen);
  border: 1px solid color-mix(in oklab, var(--accent) 22%, var(--border));
  border-radius: var(--radius);
  box-shadow: inset 0 2px 16px rgb(0 0 0 / 0.85);
}
.mixer-eq {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  align-items: flex-end;
  gap: 2.5%;
  padding: var(--space-m);
  opacity: 0.85;
}
@media (prefers-reduced-motion: no-preference) {
  .mixer-eq {
    transition: opacity var(--dur-default) var(--ease-out);
  }
}
.mixer-eq span {
  flex: 1;
  min-inline-size: 0;
  block-size: 100%;
  border-radius: 2px 2px 0 0;
  /* Layered fill: faint base → accent body → bright cap, so each bar reads like
     a lit LED column rather than a flat rectangle. */
  background: linear-gradient(
    to top,
    color-mix(in oklab, var(--accent) 18%, transparent) 0%,
    var(--accent) 60%,
    color-mix(in oklab, var(--accent) 45%, #fff) 100%
  );
  /* Outer bloom + a bright inner top edge — the glow of a real meter. */
  box-shadow:
    0 0 7px color-mix(in oklab, var(--accent) 45%, transparent),
    inset 0 1px 0 color-mix(in oklab, #fff 55%, transparent);
  transform: scaleY(0.4);
  transform-origin: bottom;
}
/* Per-bar resting heights → a believable frozen waveform when motion is
   reduced (8 distinct steps, repeating across the 16 bars). */
.mixer-eq span:nth-child(8n + 1) {
  transform: scaleY(0.34);
}
.mixer-eq span:nth-child(8n + 2) {
  transform: scaleY(0.7);
}
.mixer-eq span:nth-child(8n + 3) {
  transform: scaleY(0.46);
}
.mixer-eq span:nth-child(8n + 4) {
  transform: scaleY(0.92);
}
.mixer-eq span:nth-child(8n + 5) {
  transform: scaleY(0.28);
}
.mixer-eq span:nth-child(8n + 6) {
  transform: scaleY(0.6);
}
.mixer-eq span:nth-child(8n + 7) {
  transform: scaleY(0.8);
}
.mixer-eq span:nth-child(8n + 8) {
  transform: scaleY(0.4);
}
/* Gentle per-bar hue offset → the meter shimmers across a small spectrum while
   staying in the accent family (composes with the slow global drift below). */
.mixer-eq span:nth-child(8n + 1) {
  filter: hue-rotate(-20deg);
}
.mixer-eq span:nth-child(8n + 2) {
  filter: hue-rotate(-9deg);
}
.mixer-eq span:nth-child(8n + 4) {
  filter: hue-rotate(11deg);
}
.mixer-eq span:nth-child(8n + 5) {
  filter: hue-rotate(22deg);
}
.mixer-eq span:nth-child(8n + 6) {
  filter: hue-rotate(13deg);
}
.mixer-eq span:nth-child(8n + 7) {
  filter: hue-rotate(4deg);
}
.mixer-eq span:nth-child(8n + 8) {
  filter: hue-rotate(-8deg);
}
.mixer-noise {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0.05;
  mix-blend-mode: screen;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.mixer-readout {
  grid-area: 1 / 1;
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  margin: 0;
  opacity: 0;
  text-wrap: pretty;
  text-shadow: 0 0 9px color-mix(in oklab, var(--accent) 55%, transparent);
}
/* Old-school readout header: channel no. + name, in bright LED caps. */
.rd-head {
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.12em;
  color: color-mix(in oklab, var(--accent) 35%, #fff);
}
.rd-body {
  color: color-mix(in oklab, var(--accent) 72%, #fff);
}
#mx-0:checked ~ .mixer-screen .rd-0,
#mx-1:checked ~ .mixer-screen .rd-1,
#mx-2:checked ~ .mixer-screen .rd-2,
#mx-3:checked ~ .mixer-screen .rd-3,
#mx-4:checked ~ .mixer-screen .rd-4,
#mx-5:checked ~ .mixer-screen .rd-5,
#mx-6:checked ~ .mixer-screen .rd-6,
#mx-7:checked ~ .mixer-screen .rd-7,
#mx-8:checked ~ .mixer-screen .rd-8,
#mx-9:checked ~ .mixer-screen .rd-9 {
  opacity: 1;
}
/* Once a channel is selected the idle meter gives way entirely to that
   channel's motif (which fades in behind the readout text). */
.mixer:has(.mixer-radio:checked) .mixer-eq {
  opacity: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .mixer-readout {
    transition: opacity var(--dur-default) var(--ease-out);
  }
  /* Bars bounce at staggered speeds + phases so it reads like a real meter, not
     a synchronised wave. The keyframe itself is irregular — spikes (someone
     speaking) and near-silent dips (a beat of silence) — so the desynced bars
     look like live audio. Durations are deliberately *slow* (2.5s–4.9s): a fast
     meter is hypnotic and can read as flicker; a relaxed one feels studio-grade
     and is comfortable to sit next to. */
  .mixer-eq span {
    animation: eq 4s ease-in-out infinite;
  }
  .mixer-eq span:nth-child(8n + 1) {
    animation-duration: 3.4s;
    animation-delay: -2.6s;
  }
  .mixer-eq span:nth-child(8n + 2) {
    animation-duration: 2.8s;
    animation-delay: -1s;
  }
  .mixer-eq span:nth-child(8n + 3) {
    animation-duration: 4.6s;
    animation-delay: -3.5s;
  }
  .mixer-eq span:nth-child(8n + 4) {
    animation-duration: 2.5s;
    animation-delay: -0.5s;
  }
  .mixer-eq span:nth-child(8n + 5) {
    animation-duration: 3.8s;
    animation-delay: -1.8s;
  }
  .mixer-eq span:nth-child(8n + 6) {
    animation-duration: 3s;
    animation-delay: -1.4s;
  }
  .mixer-eq span:nth-child(8n + 7) {
    animation-duration: 4.9s;
    animation-delay: -2.3s;
  }
  .mixer-eq span:nth-child(8n + 8) {
    animation-duration: 2.6s;
    animation-delay: -3.1s;
  }
  /* The whole meter drifts very slowly through a narrow hue arc — alive, but
     never leaving the accent family (composes with the per-bar offsets above). */
  .mixer-eq {
    animation: eq-hue 26s ease-in-out infinite;
  }
  .mixer-noise {
    animation: noise-jitter 0.5s steps(4) infinite;
  }
}
@keyframes eq-hue {
  0%,
  100% {
    filter: hue-rotate(-16deg);
  }
  50% {
    filter: hue-rotate(16deg);
  }
}
@keyframes eq {
  0% {
    transform: scaleY(0.1);
  }
  9% {
    transform: scaleY(0.92);
  }
  18% {
    transform: scaleY(0.34);
  }
  27% {
    transform: scaleY(0.06);
  }
  39% {
    transform: scaleY(0.7);
  }
  50% {
    transform: scaleY(1);
  }
  60% {
    transform: scaleY(0.22);
  }
  70% {
    transform: scaleY(0.08);
  }
  82% {
    transform: scaleY(0.55);
  }
  91% {
    transform: scaleY(0.28);
  }
  100% {
    transform: scaleY(0.1);
  }
}

/* ---- Demo --------------------------------------------------------------- */

/* Full-bleed studio band: a darker stage that spans the viewport, with the
   heading re-centred in `.demo-inner` and a glowing, deep-bezel "TV". */
.panel-demo {
  padding-block: clamp(var(--space-xl), 6vw, var(--space-2xl));
  text-align: center;
  background: color-mix(in oklab, var(--bg) 86%, #000);
  border-block: 1px solid var(--border);
}
.demo-inner {
  inline-size: 100%;
  max-inline-size: var(--page-max);
  margin-inline: auto;
  padding-inline: clamp(var(--space-s), 4vw, var(--space-xl));
}
.demo-name {
  color: var(--text);
  font-weight: 700;
}
/* The stage: near-full-width, with a soft studio spotlight behind the set. */
.tv-stage {
  position: relative;
  isolation: isolate;
  margin-block-start: var(--space-xl);
  padding-inline: clamp(var(--space-s), 4vw, var(--space-xl));
}
.tv-glow {
  position: absolute;
  inset: -6% 0 auto;
  z-index: -1;
  block-size: 70%;
  background: var(--accent);
  opacity: 0.18;
  filter: blur(120px);
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .tv-glow {
    animation: stage-glow 8s ease-in-out infinite alternate;
  }
}
@keyframes stage-glow {
  from {
    opacity: 0.12;
  }
  to {
    opacity: 0.24;
  }
}
/* The whole unit is one MATTE studio monitor: `.tv` is the graphite chassis
   (its padding is the bezel), `.tv-screen` is the inset display, and `.tv-chin`
   is a control strip that's part of the chassis. No gloss; depth from the matte
   frame + a dark drop shadow. */
.tv {
  --bezel: clamp(0.5rem, 1.1vw, 0.95rem);
  inline-size: 100%;
  max-inline-size: 84rem;
  margin-inline: auto;
  padding: var(--bezel);
  background: color-mix(in oklab, var(--bg) 64%, #000);
  border-radius: 18px;
  box-shadow:
    0 0 0 1px color-mix(in oklab, #000 55%, transparent),
    inset 0 1px 0 color-mix(in oklab, #fff 4%, transparent),
    0 44px 100px -48px rgb(0 0 0 / 0.85),
    0 18px 44px -30px rgb(0 0 0 / 0.6);
}
.tv-screen {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--tv-screen);
  border-radius: 9px;
}
/* On small screens a 16:9 monitor is far too short to show anything useful of
   the live site, so the screen goes portrait — a much taller window. */
@media (max-width: 40rem) {
  .tv-screen {
    aspect-ratio: 4 / 5;
  }
}
.tv-frame {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  border: 0;
  display: block;
}
/* The screen is a real power BUTTON: strip native chrome but keep the
   `.tv-screen` box. Off by default (snow + glyph); a click loads the demo and
   plays the CRT power-on. */
.tv-power {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  padding: 0;
  border: 0;
  inline-size: 100%;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
}
.tv-power[aria-pressed="true"] {
  cursor: default;
}
.tv-power:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}
/* While off, the src-less iframe still renders an about:blank document that
   would swallow a real click into its own browsing context — so the power
   button never sees it (the static + glyph overlays above are already
   pointer-events:none and just fall through to it). Take the frame out of
   hit-testing until it's powered on; JS flips `aria-pressed` → it becomes
   interactive again with its real `src` loaded. */
.tv-power[aria-pressed="false"] .tv-frame {
  pointer-events: none;
}
/* Off-state face: a centred power glyph + hint over the snow. Decorative (the
   button owns the accessible label); fades out on power-on. */
.tv-poweroff {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  color: var(--text);
  text-shadow: 0 1px 4px rgb(0 0 0 / 0.7);
  pointer-events: none;
}
.tv-power-glyph {
  display: inline-flex;
}
.power-glyph {
  display: block;
  inline-size: clamp(2.75rem, 6vw, 4rem);
  block-size: clamp(2.75rem, 6vw, 4rem);
  color: color-mix(in oklab, var(--accent) 55%, var(--text));
  filter: drop-shadow(0 0 12px color-mix(in oklab, var(--accent) 50%, transparent));
}
.tv-power-hint {
  font-size: var(--step-0);
  font-weight: 600;
  letter-spacing: 0.01em;
}
/* Static-noise overlay — the visible default of the OFF screen; fades out when
   the TV is powered on. The noise is a rasterized-once data: SVG; `noise-jitter`
   only steps `background-position` (no per-frame filter), so it's cheap. */
.tv-static {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.6;
  mix-blend-mode: screen;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* Powered on: the off-state face + snow disappear (instant under reduced motion;
   a smooth fade + CRT power-on only under no-preference, below). */
.tv-power[aria-pressed="true"] .tv-static,
.tv-power[aria-pressed="true"] .tv-poweroff {
  opacity: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .tv-static {
    animation: noise-jitter 0.5s steps(4) infinite;
  }
  .tv-power[aria-pressed="true"] .tv-static,
  .tv-power[aria-pressed="true"] .tv-poweroff {
    transition: opacity 240ms var(--ease-out);
  }
  /* CRT power-on: a bright horizontal line snaps open, overshoots, settles.
     transform/opacity/filter only (compositor-friendly), ~480ms. */
  .tv-power[aria-pressed="true"] .tv-frame {
    animation: crt-on 480ms var(--ease-out) both;
  }
}
@keyframes crt-on {
  0% {
    transform: scaleY(0.002);
    filter: brightness(3);
    opacity: 0;
  }
  8% {
    transform: scaleY(0.004) scaleX(1.04);
    filter: brightness(3);
    opacity: 1;
  }
  30% {
    transform: scaleY(1.06);
    filter: brightness(1.7);
  }
  45% {
    transform: scaleY(0.98);
    filter: brightness(1.2);
  }
  60% {
    transform: scaleY(1.01);
    filter: brightness(1.05);
  }
  100% {
    transform: none;
    filter: none;
    opacity: 1;
  }
}
/* The monitor's control strip — a recessed bar in the chassis below the screen. */
.tv-chin {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.55em;
  min-block-size: 2.7rem;
  margin-block-start: var(--bezel);
  padding-inline: var(--space-m);
  font-size: var(--step-0);
  font-weight: 600;
  text-decoration: none;
  color: color-mix(in oklab, var(--accent) 35%, var(--text));
  background: color-mix(in oklab, #000 35%, transparent);
  border-radius: 7px;
  box-shadow: inset 0 1px 3px rgb(0 0 0 / 0.6);
}
.tv-chin-dot {
  inline-size: 0.5em;
  block-size: 0.5em;
  border-radius: 999px;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}
.visit-host {
  font-weight: 800;
  color: var(--accent);
}
@media (prefers-reduced-motion: no-preference) {
  .tv-chin-arrow {
    transition: transform var(--dur-quick) var(--ease-out);
  }
}
@media (hover: hover) and (pointer: fine) {
  .tv-chin:hover {
    color: var(--text);
    background: color-mix(in oklab, var(--accent) 12%, color-mix(in oklab, #000 30%, transparent));
  }
  .tv-chin:hover .tv-chin-arrow {
    transform: translateX(3px);
  }
}
@media (prefers-reduced-motion: no-preference) {
  .tv-chin-dot {
    animation: beta-pulse 2.4s ease-in-out infinite;
  }
}

/* ---- Status pages ------------------------------------------------------- */

.panel-status {
  max-inline-size: var(--page-max);
  margin-inline: auto;
  padding-inline: clamp(var(--space-s), 4vw, var(--space-xl));
  text-align: center;
  min-block-size: 50vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

/* ---- Prose (privacy policy, …) ------------------------------------------ */

.panel-prose {
  /* A comfortable reading measure with generous breathing room. */
  max-inline-size: 44rem;
  padding-block: clamp(var(--space-xl), 7vw, calc(var(--space-2xl) * 1.3));
  line-height: 1.7;
}
.panel-prose h1 {
  margin-block-end: var(--space-s);
}
.panel-prose .panel-lead {
  /* Prose reads left-aligned, not centered like a marketing lead. */
  max-inline-size: none;
  margin-inline: 0;
  margin-block-end: var(--space-2xl);
  text-align: start;
}
.panel-prose h2 {
  margin-block: var(--space-2xl) var(--space-s);
  padding-block-start: var(--space-m);
  border-block-start: 1px solid var(--border);
  font-size: var(--step-1);
  text-align: start;
}
.panel-prose p {
  margin-block: 0 var(--space-s);
  color: var(--muted);
  text-wrap: pretty;
}
.panel-prose strong {
  color: var(--text);
}
.panel-prose ul {
  margin: 0 0 var(--space-s);
  padding-inline-start: var(--space-m);
}
.panel-prose li {
  margin-block-end: var(--space-s);
  padding-inline-start: var(--space-3xs);
  color: var(--muted);
}
.panel-prose a {
  color: var(--accent);
}
.prose-meta {
  margin-block-start: var(--space-2xl);
  color: var(--muted);
}

/* ---- Scroll liveliness -------------------------------------------------- */

/* Reveal `[data-reveal]` sections as they scroll into view. Driven by
   platform-reveal.js (IntersectionObserver), which works in every modern
   browser. The hidden start state is gated on `.js-reveal` (added by the
   script) so no-JS — and the reduced-motion / no-IntersectionObserver paths,
   where the script bails before adding the class — always render painted. */
.js-reveal [data-reveal] {
  opacity: 0;
  transform: translateY(2rem);
}
.js-reveal [data-reveal].in-view {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.6s var(--ease-out),
    transform 0.6s var(--ease-out);
}

/* ---- Dashboard / sign-in ------------------------------------------------ */

/* The dashboard is its own area (noindex, no marketing nav); its header reuses
   `.platform-header`/`.platform-nav`/`.btn`, so only a few bits are new here. */

/* Centers the sign-in card — and the check-inbox / invalid-link notices, which
   reuse `.login-card` — in the viewport. */
.login-wrap {
  display: grid;
  place-items: center;
  /* The dashboard chrome has no header/footer, so the wrap is the whole page —
     centre the card in the full viewport (not a short band that strands it high
     with empty space below). `dvh` accounts for mobile browser UI. */
  min-block-size: 100dvh;
  padding-block: clamp(var(--space-l), 8vh, var(--space-2xl));
  /* A real side gutter on phones (the card mustn't hug the screen edges),
     growing on wider viewports. */
  padding-inline: clamp(var(--space-m), 5vw, var(--space-2xl));
}
/* A compact, self-contained surface: overrides `.panel`'s page width and
   asymmetric padding for a centred card that floats in the wrap. Block padding
   stays generous; inline padding is a touch tighter on phones so the gutter can
   grow without squeezing the content. */
.login-card {
  inline-size: 100%;
  max-inline-size: 27rem;
  padding: clamp(var(--space-l), 7vw, var(--space-xl)) clamp(var(--space-m), 6vw, var(--space-xl));
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}
.login-card h1 {
  margin-block-end: var(--space-2xs);
  font-size: var(--step-2);
  letter-spacing: -0.01em;
  text-wrap: balance;
}
.login-card .subscribe-form {
  margin-block-start: var(--space-l);
}
.login-card .hero-fineprint {
  margin-block-start: var(--space-l);
  text-wrap: pretty;
}

/* Signed-in chrome: account actions (Account / Log out / Log out everywhere)
   live behind the email as a native `<details>` user menu. The body floats
   (absolute), so opening it never reflows the header; `dashboard.js` adds the
   outside-click + Escape dismissal. Mirrors the tenant download popover. */
.user-menu {
  position: relative;
  z-index: var(--z-popover);
}
.user-menu-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  cursor: pointer;
  color: var(--muted);
  font-size: var(--step--1);
  list-style: none; /* drop the default disclosure triangle */
}
.user-menu-trigger::-webkit-details-marker {
  display: none;
}
.user-menu-trigger::after {
  content: "▾";
  font-size: 0.8em;
}
@media (prefers-reduced-motion: no-preference) {
  .user-menu-trigger::after {
    transition: transform var(--dur-quick) var(--ease-out);
  }
}
.user-menu[open] .user-menu-trigger::after {
  transform: rotate(180deg);
}
.user-menu-trigger:hover {
  color: var(--text);
}
.user-menu-body {
  position: absolute;
  inset-inline-end: 0;
  inset-block-start: calc(100% + var(--space-3xs));
  z-index: 1;
  min-inline-size: 12rem;
  display: flex;
  flex-direction: column;
  padding: var(--space-3xs);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 6px 18px -8px rgba(0, 0, 0, 0.25);
  font-size: var(--step--1);
}
/* Each logout form is just a CSRF carrier — its button is the visible row. */
.user-menu-form {
  display: contents;
  margin: 0;
}
.user-menu-item {
  display: block;
  inline-size: 100%;
  padding-block: 0.4em;
  padding-inline: var(--space-2xs);
  border: 0;
  border-radius: calc(var(--radius) - 2px);
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}
.user-menu-item:hover,
.user-menu-item:focus-visible {
  background: var(--bg);
  text-decoration: none;
}
.user-menu-item:focus-visible {
  /* Keep the ring inside the popover's 1px border (same fix as download rows). */
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}
/* "Log out everywhere" is the heavier, rarer action: quieter + set off by a
   divider so it isn't fat-fingered next to plain Log out. */
.user-menu-item-quiet {
  color: var(--muted);
  margin-block-start: var(--space-3xs);
  padding-block-start: var(--space-xs);
  border-block-start: 1px solid var(--border);
}
.dashboard-main {
  min-block-size: 50vh;
  /* App shell, not a marketing surface: content starts close under the sticky
     nav. `.panel`'s `6vw`→`--space-2xl` top padding (right for the hero) leaves
     a cavernous gap here, so tighten the start to an app-like rhythm. */
  padding-block-start: clamp(var(--space-m), 4vw, var(--space-l));
}
/* The shell reads like an app, not a marketing page: its lead sits left under
   the heading (not the centred marketing measure). */
.dashboard-main .panel-lead {
  max-inline-size: none;
  margin-inline: 0;
  text-align: start;
}
.dashboard-admin-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-s);
  margin-block-start: var(--space-m);
  color: var(--muted);
}
.dashboard-admin-note p {
  margin: 0;
}

/* Empty state when the signed-in user is a member of no show. */
.dash-no-shows {
  margin-block-start: var(--space-m);
  color: var(--muted);
}

/* ---- Admin dashboard (show status + users + memberships; §11) ----------- */

.admin-section-heading {
  margin-block: var(--space-l) var(--space-s);
  font-size: var(--step-1);
}
.admin-back {
  display: inline-block;
  margin-block-end: var(--space-2xs);
  color: var(--muted);
}
.admin-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.admin-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-s);
  padding-block: var(--space-s);
  border-block-end: 1px solid var(--border);
}
.admin-row-main {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2xs) var(--space-s);
  /* Take the row's free space so controls sit at the trailing edge. */
  flex: 1 1 12rem;
  min-inline-size: 0;
}
.admin-title {
  font-weight: 700;
}
.admin-user-email {
  font-weight: 600;
  overflow-wrap: anywhere;
}
.admin-handle {
  font-family: ui-monospace, monospace;
  font-size: var(--step--1);
  color: var(--muted);
}
.admin-meta {
  font-size: var(--step--1);
  color: var(--muted);
}

/* Status / account badges — one cohesive set of soft-filled pills. The state
   reads from the text colour on a neutral fill (active picks up the accent;
   hidden/deactivated/inactive stay muted), rather than mixing solid vs. dashed
   outlines. The fill stays neutral so the accent/muted text keeps its contrast. */
.admin-badge {
  display: inline-block;
  padding-block: var(--space-3xs);
  padding-inline: var(--space-xs);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--muted);
  background: color-mix(in oklab, var(--muted) 12%, transparent);
  border-radius: 999px;
  white-space: nowrap;
}
.admin-badge-active {
  color: var(--accent);
}

/* Membership chips (which shows a user belongs to + role). */
.admin-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3xs);
}
.admin-chip {
  padding-block: var(--space-3xs);
  padding-inline: var(--space-2xs);
  font-size: var(--step--1);
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* Inline markers (admin / you / no-account). */
.admin-tag {
  font-size: var(--step--1);
  color: var(--muted);
}
.admin-tag-admin {
  color: var(--accent);
}
.admin-tag-warn {
  color: var(--muted);
  font-style: italic;
}

/* Inline + add forms (status select, role change, remove, add user/member). */
.admin-inline-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
  margin: 0;
}
.admin-add-form {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--space-2xs) var(--space-s);
  margin-block-start: var(--space-m);
  padding-block-start: var(--space-s);
  border-block-start: 1px solid var(--border);
}
.admin-inline-form select,
.admin-add-form input,
.admin-add-form select {
  min-block-size: var(--min-tap);
  padding: var(--space-2xs) var(--space-s);
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.admin-inline-form select:focus-visible,
.admin-add-form input:focus-visible,
.admin-add-form select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Create-show form: two stacked labelled fields (handle + feed URL) + a hint +
 * an inline validation error. It reuses `.admin-add-form` chrome but lays its
 * `.edit-field`s out in a column so the longer feed-URL input gets full width. */
.admin-create-show {
  flex-direction: column;
  align-items: stretch;
}
.admin-create-show .edit-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
}
.admin-create-show input {
  inline-size: 100%;
}
.admin-form-hint {
  margin: 0;
  color: var(--text-muted);
}
.admin-form-error {
  margin: 0;
  padding: var(--space-2xs) var(--space-s);
  color: var(--text);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  border: 1px solid var(--accent);
  border-radius: var(--radius);
}

/* Destructive action (remove a member). Colour-only modifier over `.btn`. */
.btn-danger {
  color: var(--text);
  background: var(--surface);
  border-color: var(--border);
}
.btn-danger:hover {
  color: var(--on-accent);
  background: color-mix(in oklab, crimson 72%, var(--accent));
  border-color: transparent;
}

/* Owner-only "Danger zone" on the show editor — visually set apart so a
   destructive, irreversible action never sits flush with the routine edit
   fields above it. The submit reuses `.btn-danger`; the confirm field mirrors
   `.edit-form input`. */
.danger-zone {
  margin-block-start: var(--space-xl);
  padding: var(--space-m);
  border: 1px solid color-mix(in oklab, var(--danger) 45%, var(--border));
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--danger) 6%, transparent);
}
.danger-zone > h2 {
  margin-block: 0 var(--space-2xs);
  font-size: var(--step-1);
  color: var(--danger);
}
.danger-zone-warning {
  margin-block: 0 var(--space-m);
  max-inline-size: 60ch;
  color: var(--muted);
}
.danger-zone-confirm {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
  margin-block-end: var(--space-m);
  font-weight: 600;
}
.danger-zone-confirm strong {
  font-family: ui-monospace, "SFMono-Regular", monospace;
}
.danger-zone-confirm input {
  flex: 1 1 16ch;
  min-block-size: var(--min-tap);
  padding: var(--space-2xs) var(--space-s);
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.danger-zone-confirm input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ---- Auth-flow motion: success check + card entrances -------------------- */

/* A self-drawing success check — shown on the email-confirm page
   (`.panel-status.is-success`) and the first post-sign-in dashboard view
   (`.dashboard-welcome`). Decorative + `aria-hidden` (the heading carries the
   message). Default — and the reduced-motion state — is a static, fully-drawn
   check; the keyframes below only add the spring-in + draw. */
.success-check {
  display: block;
  inline-size: clamp(3rem, 9vw, 3.75rem);
  block-size: clamp(3rem, 9vw, 3.75rem);
  margin: 0 auto var(--space-m);
  color: hsl(151 55% 45%);
  filter: drop-shadow(0 0 12px hsl(151 55% 45% / 0.4));
}
.success-check-ring,
.success-check-tick {
  transform-box: fill-box;
  transform-origin: center;
}
/* `stroke-dashoffset: 0` = drawn by default, so no-JS / reduced-motion shows the
   whole tick; the keyframe draws it in from fully retracted. */
.success-check-tick {
  stroke-dasharray: 32;
  stroke-dashoffset: 0;
}
/* The dashboard's welcome check sits a touch smaller, above the heading. */
.dashboard-welcome .success-check {
  inline-size: clamp(2.5rem, 7vw, 3rem);
  block-size: clamp(2.5rem, 7vw, 3rem);
  margin-block-end: var(--space-s);
}

@media (prefers-reduced-motion: no-preference) {
  /* Each step of the flow rises + fades as it loads. */
  .login-card,
  .panel-status,
  .dashboard-main {
    animation: card-rise 420ms var(--ease-out) both;
  }
  /* Success beat: the ring springs in, then the tick draws across it. */
  .panel-status.is-success .success-check-ring,
  .dashboard-welcome .success-check-ring,
  .account-celebration .success-check-ring {
    animation: check-ring 480ms var(--ease-out) both;
  }
  .panel-status.is-success .success-check-tick,
  .dashboard-welcome .success-check-tick,
  .account-celebration .success-check-tick {
    animation: check-tick 360ms var(--ease-out) 300ms both;
  }
}
@keyframes card-rise {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
}
@keyframes check-ring {
  from {
    opacity: 0;
    transform: scale(0.4);
  }
  60% {
    transform: scale(1.06);
  }
}
@keyframes check-tick {
  from {
    stroke-dashoffset: 32;
  }
}

/* ---- Creator edit UI (manage show + episodes; §11) ---------------------- */

.manage-episodes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

/* Dashboard home overview: a section heading + a compact row per show, each
   linking to that show's own page. */
.dash-section-heading {
  margin-block: var(--space-l) var(--space-s);
  font-size: var(--step-1);
}
.dash-show-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}
.dash-show-row {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  padding: var(--space-s);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: inherit;
  text-decoration: none;
}
.dash-show-row:hover,
.dash-show-row:focus-visible {
  border-color: var(--accent);
}
.dash-show-row-title {
  font-weight: 600;
}
.dash-show-row-meta {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  margin-inline-start: auto;
  color: var(--muted);
  font-size: var(--step--1);
}

/* Shows index: a responsive grid of show cards (cover + title + count + role). */
.shows-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
  gap: var(--space-m);
  margin-block-start: var(--space-m);
}
.shows-card {
  display: flex;
  align-items: center;
  gap: var(--space-m);
  padding: var(--space-m);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: inherit;
  text-decoration: none;
}
.shows-card:hover,
.shows-card:focus-visible {
  border-color: var(--accent);
}
@media (prefers-reduced-motion: no-preference) {
  .shows-card {
    transition:
      transform var(--dur-quick) var(--ease-out),
      border-color var(--dur-quick) var(--ease-out);
  }
  @media (hover: hover) and (pointer: fine) {
    .shows-card:hover {
      transform: translateY(-2px);
    }
  }
}
.shows-card-cover {
  inline-size: 72px;
  block-size: 72px;
  flex: none;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}
.shows-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  min-inline-size: 0;
}
.shows-card-title {
  margin: 0;
  font-size: var(--step-1);
}
.shows-card-count {
  margin: 0;
  color: var(--muted);
  font-size: var(--step--1);
}
.shows-card-role {
  align-self: start;
  padding-block: 0.1em;
  padding-inline: 0.6em;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--accent);
  background: color-mix(in oklab, var(--accent) 12%, transparent);
  border-radius: 999px;
}

/* Per-show page: a back link, then the sticky sub-nav + episode list. The show
   name now lives in the header (beside the brand) and the episode count in the
   sub-nav, so there's no big text header here. */
.dash-back {
  display: inline-block;
  margin-block-end: var(--space-s);
  color: var(--muted);
  font-size: var(--step--1);
}

/* Sync status panel (above the episode list): live health + per-step progress.
   The dot pulses only while a run is in flight, and only when motion is welcome
   (reuses the `beta-pulse` keyframe). */
.sync-status {
  margin-block: var(--space-m) var(--space-l);
  padding: var(--space-s) var(--space-m);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--accent) 4%, var(--surface));
}
.sync-status-heading {
  margin: 0 0 var(--space-2xs);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
#sync-status-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}
.sync-status-state {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  margin: 0;
}
.sync-status-dot {
  inline-size: 0.55em;
  block-size: 0.55em;
  border-radius: 999px;
  background: var(--muted);
  flex: none;
}
.sync-status-dot--active {
  background: var(--accent);
}
.sync-status-detail {
  color: var(--muted);
}
.sync-status-meta {
  margin: 0;
  color: var(--muted);
  font-size: var(--step--1);
}
@media (prefers-reduced-motion: no-preference) {
  .sync-status-dot--active {
    animation: beta-pulse 1.6s ease-in-out infinite;
  }
}

/* Season groups + the episode rows under each (date + number + title). */
.dash-episodes-heading {
  margin-block-start: var(--space-l);
}
.dash-season {
  margin-block-start: var(--space-m);
  /* A jumped-to / scroll-spied season heading must clear BOTH sticky bars (the
     global header + the sub-nav). Both heights are JS-measured into custom
     properties (they wrap), with fallbacks here for the no-JS path; the same
     sum feeds the scroll-spy's IntersectionObserver `rootMargin` so "in view"
     and "where the anchor lands" agree. The `--space-2xs` tail is a small
     breath below the bars. */
  scroll-margin-block-start: calc(
    var(--dash-header-h, 3.5rem) +
    var(--dash-subnav-h, 4.75rem) +
    var(--space-2xs)
  );
}
.dash-season-heading {
  margin-block: var(--space-s);
  font-size: var(--step-0);
  color: var(--muted);
}
.dash-episode {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  padding: var(--space-2xs) var(--space-s);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: inherit;
  text-decoration: none;
}
.dash-episode:hover,
.dash-episode:focus-visible {
  border-color: var(--muted);
  background: color-mix(in oklab, var(--accent) 6%, var(--surface));
}
.dash-ep-main {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3xs) var(--space-s);
  min-inline-size: 0;
}
.dash-ep-title {
  font-weight: 600;
}
.dash-ep-meta {
  display: flex;
  align-items: baseline;
  gap: var(--space-s);
  color: var(--muted);
  font-size: var(--step--1);
}
.dash-ep-num {
  color: var(--accent);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.dash-ep-date {
  font-variant-numeric: tabular-nums;
}
.dash-episode-edit {
  flex: none;
  color: var(--muted);
}

/* An episode row pairs the edit link (grows) with a compact "view live" link
   that opens the public page in a new tab. */
.dash-episode-row {
  display: flex;
  align-items: stretch;
  gap: var(--space-2xs);
}
.dash-episode-row .dash-episode {
  flex: 1 1 auto;
  min-inline-size: 0;
}
.dash-ep-live {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: var(--min-tap);
  padding-inline: var(--space-s);
  color: var(--muted);
  text-decoration: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.dash-ep-live:hover,
.dash-ep-live:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
}

/* ---- Per-show sticky sub-nav (cover + episode count + actions; season jumps) --
   Rendered in flow under the back link: at the top it sits near the top of the
   page; as you scroll it sticks under the global header and
   floats over the episode list. Two rows in one cohesive bar. The global header
   is sticky at `top: 0` with `z-index: 20`; the sub-nav sticks just below it and
   sits BELOW the header (`z-index: 10`) but above the scrolling content, so the
   list passes cleanly under it. `--dash-header-h` is JS-measured (the header
   wraps), with a fallback for the no-JS path. */
.dash-subnav {
  position: sticky;
  inset-block-start: var(--dash-header-h, 3.5rem);
  z-index: 10;
  margin-block: var(--space-m);
  /* A solid (theme-aware) fill so content can't bleed through; backdrop-blur as
     a progressive nicety where supported. The full-bleed feel comes from the
     bottom border spanning the panel; left/right stay within the content
     column (the panel itself is already inset). */
  background: color-mix(in oklab, var(--surface) 88%, transparent);
  border-block-end: 1px solid var(--border);
  border-start-start-radius: var(--radius);
  border-start-end-radius: var(--radius);
}
@supports (backdrop-filter: blur(1px)) {
  .dash-subnav {
    background: color-mix(in oklab, var(--surface) 72%, transparent);
    backdrop-filter: blur(10px);
  }
}
/* Row 1: the show's cover thumbnail (its identity here) + the show actions. On a
   wide viewport it's one tidy line (cover leads, actions trail); it wraps when
   cramped, and the cover is dropped entirely on narrow screens. */
.dash-subnav-show {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-s);
  padding-block: var(--space-2xs);
  padding-inline: var(--space-s);
}
/* The show's cover as a small identity thumbnail leading the bar — the page
   heading already names the show, so this is the only show mark here (no
   duplicate title). Dropped on narrow viewports so the actions stay in view. */
.dash-subnav-cover {
  inline-size: 2rem;
  block-size: 2rem;
  flex: none;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}
@media (max-width: 32rem) {
  .dash-subnav-cover {
    display: none;
  }
}
/* The episode count, leading the bar beside the cover (the show name is in the
   header). Muted + tabular so it reads as metadata, not an action. */
.dash-subnav-count {
  color: var(--muted);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.dash-subnav-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-m);
  /* Trailing edge: the cover leads, the actions trail. */
  margin-inline-start: auto;
}
.dash-subnav-links a {
  display: inline-flex;
  align-items: center;
  gap: 0.2em;
  color: var(--muted);
  text-decoration: none;
  font-size: var(--step--1);
  font-weight: 600;
  white-space: nowrap;
}
.dash-subnav-links a:hover,
.dash-subnav-links a:focus-visible {
  color: var(--accent);
}

/* Row 2: one jump-link per season, in render order. Horizontally scrollable
   when the labels overflow a narrow viewport (the body never scrolls sideways);
   the list itself owns the overflow. A top border separates it from row 1. */
.dash-season-nav {
  display: flex;
  gap: var(--space-2xs);
  padding-block: var(--space-2xs);
  padding-inline: var(--space-s);
  border-block-start: 1px solid var(--border);
  overflow-x: auto;
  /* Keep momentum scroll from clipping focus rings on the first/last chip. */
  scroll-padding-inline: var(--space-s);
}
.dash-season-nav a {
  flex: none;
  padding-block: var(--space-3xs);
  padding-inline: var(--space-s);
  color: var(--muted);
  text-decoration: none;
  font-size: var(--step--1);
  font-weight: 600;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: 999px;
}
.dash-season-nav a:hover,
.dash-season-nav a:focus-visible {
  color: var(--text);
  border-color: var(--border);
}
/* Active season (scroll-spied by `dash-subnav.js`): an accent pill. The fill is
   a tint of the accent with accent text — both light and dark `--accent` /
   `--text` keep this ≥ WCAG AA. Mirrored on `[aria-current="true"]` so the AT
   state and the visual state can't drift. */
.dash-season-nav a.is-active,
.dash-season-nav a[aria-current="true"] {
  color: var(--accent);
  background: color-mix(in oklab, var(--accent) 14%, transparent);
  border-color: color-mix(in oklab, var(--accent) 36%, transparent);
}

@media (prefers-reduced-motion: no-preference) {
  /* Smooth-scroll an anchor jump only when the user hasn't asked for less
     motion. Scoped to this page's scroll container (the document) via the body
     class, so it never overrides global scroll behaviour elsewhere. */
  .dashboard-page {
    scroll-behavior: smooth;
  }
  .dash-subnav-links a,
  .dash-season-nav a {
    transition:
      color var(--dur-quick) var(--ease-out),
      background-color var(--dur-quick) var(--ease-out),
      border-color var(--dur-quick) var(--ease-out);
  }
}

/* ---- Account / 2FA enrollment ------------------------------------------- */

.account-2fa {
  margin-block-start: var(--space-l);
  max-inline-size: 40rem;
  /* Space the heading, its lead, and the control apart (they sat flush before). */
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: var(--space-s);
}
/* The post-enable celebration: the draws-itself ring + check + a short, green
   affirmation. Shown once (the post-enable redirect). */
.account-celebration {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  align-self: stretch;
  padding: var(--space-s) var(--space-m);
  border: 1px solid color-mix(in oklab, hsl(151 55% 45%) 40%, var(--border));
  border-radius: var(--radius);
  background: color-mix(in oklab, hsl(151 55% 45%) 8%, transparent);
}
.account-celebration .success-check {
  flex: none;
  inline-size: clamp(2.25rem, 6vw, 2.75rem);
  block-size: clamp(2.25rem, 6vw, 2.75rem);
  margin: 0;
}
.account-celebration p {
  margin: 0;
  font-weight: 600;
}
/* The disable control: a summary-as-button (no disclosure triangle) that reveals
   the typed-confirm danger block right beneath it. */
.totp-disable {
  align-self: start;
}
.totp-disable > summary {
  list-style: none;
  cursor: pointer;
}
.totp-disable > summary::-webkit-details-marker {
  display: none;
}
.totp-disable .danger-zone {
  margin-block-start: var(--space-s);
}
/* The QR carries its own white quiet-zone (a `<rect>` in the SVG), so it scans
   the same in light + dark; `qrcodegen` sizes it to 200px via `viewBox`. */
.totp-qr {
  margin-block: var(--space-m);
}
.totp-secret {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2xs);
  margin-block-end: var(--space-m);
}
.totp-secret-label {
  color: var(--muted);
}
.totp-secret-value {
  font-family: ui-monospace, "SFMono-Regular", monospace;
  font-size: var(--step-0);
  letter-spacing: 0.08em;
  -webkit-user-select: all;
  user-select: all;
}
.totp-backups {
  display: grid;
  grid-template-columns: repeat(2, max-content);
  gap: var(--space-3xs) var(--space-l);
  margin-block: var(--space-2xs) var(--space-m);
  padding: 0;
  list-style: none;
  font-family: ui-monospace, "SFMono-Regular", monospace;
}
.totp-backup-warn {
  color: var(--muted);
}
.form-error {
  color: var(--danger);
  font-weight: 600;
}

/* Breadcrumb trail on the editor sub-pages: a quiet "Dashboard › current"
   row above the heading so location + the way back are always visible. */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
  margin-block-end: var(--space-m);
  font-size: var(--step--1);
  color: var(--muted);
}
.breadcrumb a {
  color: var(--muted);
  text-decoration: none;
}
.breadcrumb a:hover {
  color: var(--accent);
  text-decoration: underline;
}
.breadcrumb [aria-current="page"] {
  color: var(--text);
  font-weight: 600;
}

/* The bottom "back" link wants clear air after a long form/transcript block. */
.edit-back {
  margin-block-start: var(--space-l);
}

/* The episode list is the `#episodes` jump target from an editor's back link
   and from the sub-nav's "Episodes" link. Clear the same sticky stack as the
   season headings so the jump lands below both bars, not under them. */
.dash-episodes-heading {
  scroll-margin-block-start: calc(
    var(--dash-header-h, 3.5rem) +
    var(--dash-subnav-h, 4.75rem) +
    var(--space-2xs)
  );
}

.edit-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  /* Wider than the single-field cap below, so the description editor + its live
     preview can sit side by side on a roomy viewport. */
  max-inline-size: 64rem;
}
/* The save button is a normal pill, not a full-width bar — keep the column
   flex from stretching it. (Per-field revert buttons sit in their own forms.) */
.edit-form button[type="submit"] {
  align-self: start;
}
.edit-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}
.edit-field label {
  font-weight: 600;
}
.edit-form input,
.edit-textarea {
  inline-size: 100%;
  min-block-size: var(--min-tap);
  padding: var(--space-2xs) var(--space-s);
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.edit-textarea {
  resize: vertical;
  min-block-size: 8rem;
}
/* Single-line fields keep a readable width even though the form is wide (so the
   description's side-by-side preview has room). */
.edit-form input {
  max-inline-size: 40rem;
}
.edit-form input:focus-visible,
.edit-textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Live description preview: the textarea + a rendered pane. Stacked on narrow
   screens; side by side once there's room. The pane re-renders (datastar SSE)
   as you type, through the same sanitizer the public site uses. */
.edit-with-preview {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-s);
  /* Stretch both columns to the taller one, so the textarea fills the same height
     as the rendered preview (no short, scrolly box beside a tall preview). On a
     single column (narrow) each is its own row, so this is a no-op there. */
  align-items: stretch;
}
@media (min-width: 52rem) {
  .edit-with-preview {
    grid-template-columns: 1fr 1fr;
  }
}
.edit-preview {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-inline-size: 0;
}
.edit-preview-label {
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--muted);
}
/* The rendered notes. The dashboard doesn't load the tenant `.feed-description`
   styles, so give the sanitized elements readable defaults here. A dashed border
   marks it as a non-editable mirror of the textarea. */
.edit-preview-body {
  /* Fill the stretched preview column so the border reaches the bottom even when
     the textarea is the taller side. */
  flex: 1;
  min-block-size: 8rem;
  padding: var(--space-2xs) var(--space-s);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--surface) 55%, transparent);
  overflow-wrap: break-word;
}
.edit-preview-body > :first-child {
  margin-block-start: 0;
}
.edit-preview-body > :last-child {
  margin-block-end: 0;
}
.edit-preview-body p {
  margin-block: var(--space-2xs);
}
.edit-preview-body h3,
.edit-preview-body h4,
.edit-preview-body h5,
.edit-preview-body h6 {
  margin-block: var(--space-s) var(--space-2xs);
  font-size: var(--step-0);
}
.edit-preview-body ul,
.edit-preview-body ol {
  margin-block: var(--space-2xs);
  padding-inline-start: 1.5em;
}
.edit-preview-body a {
  color: var(--accent);
}
.edit-preview-body code {
  font-family: ui-monospace, monospace;
  font-size: 0.9em;
  padding: 0.1em 0.3em;
  background: color-mix(in oklab, var(--text) 8%, transparent);
  border-radius: 4px;
}
.edit-preview-body blockquote {
  margin-inline: 0;
  padding-inline-start: var(--space-s);
  border-inline-start: 3px solid var(--border);
  color: var(--muted);
}
.edit-preview-empty {
  color: var(--muted);
  font-style: italic;
}

/* Colour-only modifier over `.btn` (the base supplies size/shape/inline-flex). */
.btn-secondary {
  color: var(--text);
  background: var(--surface);
  border-color: var(--border);
}
.btn-secondary:hover {
  border-color: var(--accent);
}

.edit-overrides {
  margin-block-start: var(--space-m);
  padding-block-start: var(--space-s);
  border-block-start: 1px solid var(--border);
}
.edit-revert-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}
.edit-revert {
  margin: 0;
}

/* Custom-domain panel (show editor, §11). */
.cd-section {
  margin-block-start: var(--space-l);
  padding-block-start: var(--space-m);
  border-block-start: 1px solid var(--border);
}
.cd-section > h2 {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}
.cd-badge {
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  color: var(--accent);
  background: color-mix(in oklab, var(--accent) 13%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent) 34%, transparent);
}
.cd-form {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--space-s);
  margin-block: var(--space-s);
}
.cd-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  flex: 1 1 18rem;
}
.cd-field-label {
  font-weight: 600;
}
.cd-form input[type="text"] {
  min-block-size: var(--min-tap);
  padding: var(--space-2xs) var(--space-s);
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.cd-form input[type="text"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.cd-note {
  color: var(--muted);
  margin-block: var(--space-s) var(--space-2xs);
}
.cd-record {
  display: grid;
  gap: var(--space-2xs);
  padding: var(--space-s);
  background: color-mix(in oklab, var(--surface) 50%, var(--bg));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-block: var(--space-2xs) var(--space-s);
}
.cd-record-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  align-items: baseline;
}
.cd-record-key {
  flex: 0 0 4rem;
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--muted);
}
.cd-record code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  word-break: break-all;
  color: var(--text);
}
.cd-providers {
  font-size: 0.9rem;
  color: var(--muted);
  margin-block-start: var(--space-s);
}
.cd-provider {
  color: var(--accent);
  margin-inline-end: var(--space-s);
  white-space: nowrap;
}

/* ---- Transcript manager (view / download / upload; §11) ----------------- */

.transcripts-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
  max-inline-size: 48rem;
  margin-block-start: var(--space-l);
  padding-block-start: var(--space-m);
  border-block-start: 1px solid var(--border);
}
.transcript-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}
/* Each transcript reads as a tidy card: the meta on the left, the actions
   grouped on the right, within the section's readable column (not stretched
   across the full page width). */
.transcript-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
  padding: var(--space-2xs) var(--space-s);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.transcript-meta {
  flex: 1 1 12rem;
}
.transcript-badge {
  font-size: var(--step--1);
  color: var(--muted);
}

/* The view preview: a centred modal with a scrollable transcript body. */
.transcript-preview {
  max-inline-size: min(48rem, 92vw);
  max-block-size: 80vh;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  padding: var(--space-m);
}
.transcript-preview::backdrop {
  background: rgb(0 0 0 / 0.5);
}
.transcript-preview-body {
  max-block-size: 60vh;
  overflow-y: auto;
  margin-block-end: var(--space-s);
}

/* The upload drop zone: a labelled box that also accepts drag-and-drop. */
.transcript-upload {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
}
.upload-drop {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-m);
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  text-align: center;
}
.upload-drop.is-dragging {
  border-color: var(--accent);
  background: color-mix(in oklab, var(--accent) 8%, var(--surface));
}
.upload-hint {
  color: var(--muted);
  font-size: var(--step--1);
}

/* Cover-art manager (§11): the current-cover thumbnail + actions, then an image
   upload drop zone (reuses `.upload-drop`/`.upload-hint`). */
.cover-section {
  margin-block-start: var(--space-l);
  padding-block-start: var(--space-m);
  border-block-start: 1px solid var(--border);
}
.cover-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-s);
  margin-block-end: var(--space-s);
}
.cover-thumb {
  inline-size: 96px;
  block-size: 96px;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.cover-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);
}
.cover-upload {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  max-inline-size: 40rem;
  margin-block-start: var(--space-m);
}
/* The view preview: a centred modal showing the full cover. */
.cover-preview {
  max-inline-size: min(40rem, 92vw);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--space-m);
}
.cover-preview::backdrop {
  background: rgb(0 0 0 / 0.5);
}
.cover-preview-img {
  display: block;
  max-inline-size: 100%;
  max-block-size: 70vh;
  block-size: auto;
  margin-block-end: var(--space-s);
  border-radius: var(--radius);
}

/* Audio override manager (§8.6): the effective per-bucket variants + a
   synced/manual badge, then an audio upload drop zone (reuses
   `.upload-drop`/`.upload-hint`). Mirrors the transcript/cover sections. */
.audio-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
  max-inline-size: 48rem;
  margin-block-start: var(--space-l);
  padding-block-start: var(--space-m);
  border-block-start: 1px solid var(--border);
}
.audio-current {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}
.audio-status {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}
.audio-badge {
  font-size: var(--step--1);
  color: var(--muted);
}
.audio-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}
/* One variant per row: the bucket label, then bitrate · size. */
.audio-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-s);
  padding: var(--space-2xs) var(--space-s);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.audio-bucket {
  font-weight: 600;
  min-inline-size: 3rem;
}
.audio-meta {
  color: var(--muted);
  font-size: var(--step--1);
}
.audio-upload {
  display: flex;
  flex-direction: column;
  gap: var(--space-s);
  max-inline-size: 40rem;
}
