:root {
  color-scheme: dark;
  --bg: #0b0b0d;
  --fg: #f5f5f7;
  --dim: #8a8a90;
  --accent: #3ea6ff;
  --accent-contrast: #06121c; /* text drawn on top of an --accent-filled button */
  --surface: #1c1c1f;         /* card/row fill (podcast art fallback, form inputs, list rows) */
  --surface-2: #0f0f12;       /* deeper panel fill (menu panel, info overlay, episode-card gradient) */
  --border-soft: rgba(245, 245, 247, 0.28); /* feint border on photos/cards */
  --hairline: rgba(255, 255, 255, 0.08);    /* faint dividers/input borders */
  --shadow-1: rgba(0, 0, 0, 0.5);           /* drop shadow under photos/cards */
  --chip-fill: rgba(255, 255, 255, 0.08);   /* small round icon buttons (menu/back/remove) */
  --control-fill: rgba(245, 245, 247, 0.06);  /* dial-center/dial-back resting fill */
  --control-pressed: rgba(138, 138, 144, 0.2); /* dial-ring/dial-back pressed fill */
  --track-fill: #2a2a2e; /* progress bar track */
  --overlay-chip-fill: rgba(28, 28, 31, 0.85); /* near-opaque floating button over photos/varied backgrounds (e.g. the header's burger button) */

  /* Shared, permanent row heights for #app's top-level layout (header / carousel /
     scrub / controls) — clamp()'d against dvh so short/tall portrait phones scale
     proportionally instead of overflowing or wasting space. See base.css .header,
     carousel.css .carousel-meta, playing.css .scrub-panel, dial.css .controls. */
  --header-h: clamp(64px, 10dvh, 84px);
  --meta-h: 30px; /* metadata is always a single short line, no need to scale */
  --scrub-h: clamp(60px, 8dvh, 72px);
  /* floor matches the dial's own fixed 220px visual + back/share overhang above it
     (see dial.css .controls) — the dial isn't being made responsive in this pass,
     so this container can't shrink below its real footprint */
  --controls-h: clamp(290px, 34dvh, 320px);
  /* the exact vertical space left for the carousel row once every other permanent
     row above/below it (header/meta/scrub/controls) has taken its fixed share —
     used to size .carousel's square (dial.css) so it shrinks to fit real available
     height on short viewports, rather than a vw-only width overflowing into the
     rows below it. A plain max-height cap doesn't work for this: CSS aspect-ratio
     only derives whichever dimension is 'auto', so capping max-height alone while
     width stays at its explicit 75vw shrinks height but not width, breaking the
     square and still overflowing — this calc() makes width itself already reflect
     whichever constraint (viewport width vs. available height) is tighter. */
  --carousel-stage-h: calc(100dvh - var(--header-h) - env(safe-area-inset-top, 0px) - var(--meta-h) - var(--scrub-h) - var(--controls-h));
}

/* Toggled by applyTheme() in scripts/data.js (persisted as carpod:theme) — the stamped
   [data-theme] attribute always wins over the OS-level prefers-color-scheme guess. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f4f4f6;
  --fg: #14141a;
  --dim: #6b6b74;
  --surface: #e7e7ea;
  --surface-2: #ffffff;
  --border-soft: rgba(20, 20, 26, 0.14);
  --hairline: rgba(20, 20, 26, 0.1);
  --shadow-1: rgba(20, 20, 26, 0.16);
  --chip-fill: rgba(20, 20, 26, 0.06);
  --control-fill: rgba(20, 20, 26, 0.05);
  --control-pressed: rgba(20, 20, 26, 0.12);
  --track-fill: #d8d8dc;
  --overlay-chip-fill: rgba(244, 244, 246, 0.85);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  height: 100%;
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  overflow: hidden;
  overscroll-behavior: none;
  user-select: none;
  -webkit-user-select: none;
}

#app {
  position: fixed;
  inset: 0;
  height: 100dvh;
  width: 100vw;
  touch-action: none;
  display: flex;
  flex-direction: column;
}

.header {
  position: relative;
  z-index: 35; /* stays above .menu-overlay (z-index 30) so the burger button remains tappable-to-close while the menu is open */
  flex: none;
  /* fixed height (not content-driven) so a 1-line marquee title (podcasts) and a
     2-line clamped title (episodes/playing) never change how much space #screen
     gets below — that drift used to shift where the carousel card centers itself */
  height: calc(var(--header-h) + env(safe-area-inset-top, 0px));
  display: flex;
  align-items: center;
  gap: 12px;
  padding-top: env(safe-area-inset-top, 0px);
  padding-right: 20px;
  padding-bottom: 0;
  /* --header-left-inset (set by alignHeaderToCarousel(), screens.js) is the live-measured
     left edge of the podcasts carousel's peek ("prev") card — its rendered position
     depends on several transform values split across carousel.css/dial.css (item
     max-width, offset %, scale, portrait vs. non-portrait), so it's measured via
     getBoundingClientRect() rather than duplicated here as a calc(), which would
     silently drift out of sync if those values ever change. Falls back to .screen's
     own 20px inset (carousel.css) when there's no peek card to measure against
     (a single subscription, or before the carousel has rendered at all). */
  padding-left: var(--header-left-inset, 20px);
}

.header-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font-size: 25px;
  font-weight: 600;
  transition: opacity .2s ease;
}

/* .header stacks above .menu-overlay (z-index 35 vs 30, see .header above) so its
   title stays legible while e.g. the burger menu is open — but the info modal
   shows this same episode title again inside itself, so without dimming this one
   underneath, the screen reads as two overlapping copies of the same title. */
#app.info-modal-open .header-title {
  opacity: 0.15;
}

.header-title-text {
  display: inline-block;
  white-space: nowrap;
}

/* only applied by JS (updateHeaderTitle) when the text actually overflows —
   classic single-element CSS marquee: start fully off-screen right via
   padding-left:100%, animate to translateX(-100%) (now off-screen left), loop */
.header-title.scrolling .header-title-text {
  padding-left: 100%;
  animation: header-marquee 9s linear infinite;
}
@keyframes header-marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-100%); }
}

/* episodes/playing screens (updateHeaderTitle) show the episode title, which can
   run long — clamp to two lines with an ellipsis instead of marquee-scrolling it,
   since scrolling text fights with actually reading it while navigating episodes */
.header-title.multiline .header-title-text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  white-space: normal;
  font-size: 22px; /* very slightly smaller than the 25px default — two lines of episode title reads better a touch smaller */
  text-align: center;
  padding-top: 5px;
}

/* on episodes/playing, #menu-button is hidden (updateMenuButtonIcon) so the title
   is the header's only content — a bottom hairline makes it read as its own
   app-bar rather than text floating over the black background. Hooking off
   .multiline (rather than a dedicated JS-toggled class) since it's already the
   per-screen signal updateHeaderTitle() sets. No padding compensation needed here
   — .header's height is fixed (above), so the border can't change its total height.
   padding-left is overridden back to .header's own 20px (matching padding-right)
   because --header-left-inset (set by alignHeaderToCarousel()) is measured against
   the podcasts screen's carousel peek card and can be much larger on wide screens —
   with #menu-button hidden and no longer eating into that space, the leftover inset
   pushed this title well right of true-center instead of centering across the full row. */
.header:has(.header-title.multiline) {
  border-bottom: 1px solid var(--hairline);
  padding-left: 20px;
}
