
* { box-sizing: border-box; margin: 0; padding: 0; }

/* Watch (cinematic dark) — default theme */
:root {
  --bg: #0A0A10;
  --surface: #161821;
  --surface-2: #1F2230;
  --surface-3: #2A2D3D;
  --border: #2E3145;
  --text: #F5F2EA;
  --text-dim: #A19B8E;
  --text-muted: #6B6354;
  --accent: #D4AF37;
  --accent-hover: #B8962E;
  --success: #4ADE80;
  --success-dim: rgba(74, 222, 128, 0.15);
  --warning: #F59E0B;
  --vpn: #38BDF8;
  --vpn-dim: rgba(56, 189, 248, 0.15);
  --cinema: #EF4444;
  --cinema-dim: rgba(239, 68, 68, 0.15);
  --cinema-soon: #A855F7;
  --cinema-soon-dim: rgba(168, 85, 247, 0.15);
  --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, system-ui, sans-serif;
  --topbar-bg: rgba(22, 24, 33, 0.94);
  /* Foreground color for chips / badges / pills painted on a colored
     background (--accent, --success, --vpn, --warning). Defaults to --bg
     because each dark theme's bg is dark enough to read on the light-tinted
     accent. The light Watch override flips it to white (#92). */
  --on-accent: var(--bg);
}

/* Read (Kindle / parchment) — applied when category === 'books' */
body.cat-books {
  --bg: #F4ECD7;
  --surface: #EBE0C4;
  --surface-2: #E0D2B0;
  --surface-3: #D4C49B;
  --border: #C9B98B;
  --text: #3C2E26;
  --text-dim: #6B5947;
  --text-muted: #8B7960;
  --accent: #7B2C2C;
  --accent-hover: #5A1F1F;
  --success: #2F7D4E;
  --success-dim: rgba(47, 125, 78, 0.18);
  --warning: #B58A2A;
  --vpn: #355C7D;
  --vpn-dim: rgba(53, 92, 125, 0.18);
  --font-body: Georgia, 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', serif;
  --topbar-bg: rgba(235, 224, 196, 0.94);
}

/* Play (arcade / neon) — applied when category === 'games'.
   Deep violet base + electric purple accent + cyan secondary. Reads as
   "gamer" without going garish; surfaces still have enough contrast for
   long browsing sessions. */
body.cat-games {
  --bg: #0E0B1A;
  --surface: #1A1530;
  --surface-2: #251D40;
  --surface-3: #312657;
  --border: #3A2D6E;
  --text: #F2F0FF;
  --text-dim: #B0A4D8;
  --text-muted: #6E5FA0;
  --accent: #A855F7;
  --accent-hover: #9333EA;
  --success: #22D3EE;
  --success-dim: rgba(34, 211, 238, 0.16);
  --warning: #FBBF24;
  --vpn: #38BDF8;
  --vpn-dim: rgba(56, 189, 248, 0.16);
  --topbar-bg: rgba(26, 21, 48, 0.94);
}

/* Light Watch — daytime variant of the cinematic dark default (#92, #228).
   The accent / success / vpn / warning colors are darkened so badges that
   paint --on-accent (white) on top hit WCAG AA. Triggered by explicit Light
   mode OR by Auto mode + system-light (#228). */
body.cat-movies.theme-light,
body.cat-movies.theme-auto.system-light {
  --bg: #FAFAF7;
  --surface: #FFFFFF;
  --surface-2: #F2EFE8;
  --surface-3: #E9E5DA;
  --border: #E1DDD2;
  --text: #1A1818;
  --text-dim: #5C564B;
  --text-muted: #8A8474;
  --accent: #B8862E;
  --accent-hover: #9C7124;
  --success: #15803D;
  --success-dim: rgba(21, 128, 61, 0.15);
  --warning: #B45309;
  --vpn: #0369A1;
  --vpn-dim: rgba(3, 105, 161, 0.15);
  --cinema: #DC2626;
  --cinema-dim: rgba(220, 38, 38, 0.15);
  --cinema-soon: #7C3AED;
  --cinema-soon-dim: rgba(124, 58, 237, 0.15);
  --topbar-bg: rgba(255, 255, 255, 0.94);
  /* Badges/pills sit on the darkened accent colors above — white text reads
     reliably. (Default in :root is --bg, which would be light cream here.) */
  --on-accent: #FFFFFF;
  color-scheme: light;
}

/* Read dark — paper-feel night mode (#228). Inverts parchment while keeping
   the bookish serif identity (--font-body inherits from body.cat-books).
   Triggered by explicit Dark mode OR by Auto mode + system-dark. White on
   the warm-brick accent reads ~5:1 (AA). */
body.cat-books.theme-dark,
body.cat-books.theme-auto.system-dark {
  --bg: #1A1410;
  --surface: #241B14;
  --surface-2: #2D2218;
  --surface-3: #36281D;
  --border: #3A2E22;
  --text: #E8DCC4;
  --text-dim: #B8A988;
  --text-muted: #9A8B6E;
  --accent: #A0524C;
  --accent-hover: #8A453F;
  --success: #4ADE80;
  --success-dim: rgba(74, 222, 128, 0.16);
  --warning: #F59E0B;
  --vpn: #38BDF8;
  --vpn-dim: rgba(56, 189, 248, 0.16);
  --topbar-bg: rgba(36, 27, 20, 0.94);
  --on-accent: #FFFFFF;
  color-scheme: dark;
}

/* Play light — non-neon games variant (#228). Saturated violet accent stays
   — that's the gaming hook — but bumped from #A855F7 to #7C3AED so accent
   links + badges (white text on accent bg) hit AA on the pale lavender
   surface. Triggered by explicit Light mode OR by Auto + system-light. */
body.cat-games.theme-light,
body.cat-games.theme-auto.system-light {
  --bg: #F2EEFA;
  --surface: #FFFFFF;
  --surface-2: #EAE3F5;
  --surface-3: #DDD2EC;
  --border: #CFC1E0;
  --text: #1A0F2E;
  --text-dim: #5C4A85;
  --text-muted: #6B5B96;
  --accent: #7C3AED;
  --accent-hover: #6D28D9;
  --success: #0891B2;
  --success-dim: rgba(8, 145, 178, 0.16);
  --warning: #B45309;
  --vpn: #0369A1;
  --vpn-dim: rgba(3, 105, 161, 0.15);
  --topbar-bg: rgba(255, 255, 255, 0.94);
  --on-accent: #FFFFFF;
  color-scheme: light;
}

/* Belt-and-braces against horizontal page scroll: any descendant that
   accidentally exceeds 100vw (e.g. a header overflow under a longer
   Romance-language locale + iOS larger Dynamic Type) won't make the
   document scroll sideways. Don't lean on this — fix the root cause —
   but it stops past-shape regressions like #53 and #98 from recurring. */
html, body {
  overflow-x: hidden;
  max-width: 100vw;
}
body {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  line-height: 1.5;
  color-scheme: dark;
  /* Don't let iOS auto-scale text in WebKit (separate from Dynamic Type).
     We size things deliberately for narrow viewports; auto-adjust on top
     of those values pushes the header back into overflow territory. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* Force dark theming on native <option> dropdowns so they don't render
   white-on-white in light-OS-default browsers (notably the region picker). */
select option {
  background: var(--surface-2);
  color: var(--text);
}

button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
a { color: var(--accent); }

input, select {
  font: inherit; color: inherit;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px;
  outline: none;
  transition: border-color 0.15s;
}
input:focus, select:focus { border-color: var(--accent); }

/* IMPORTANT: don't put backdrop-filter on #topbar — it creates a containing
   block, which makes any descendant position:fixed (e.g. mobile bottom tabs)
   stick to the topbar instead of the viewport. The blur lives on header
   and on .tabs individually so the layout still looks frosted. */
#topbar {
  position: sticky;
  top: 0;
  z-index: 100;
}
header {
  /* position + z-index keep the header's stacking context above .tabs.
     Both elements have backdrop-filter (each creating a stacking context);
     without explicit z-index, source order makes .tabs paint on top, which
     clips the absolutely-positioned avatar dropdown where it overflows
     below the header into the tabs region. */
  position: relative;
  z-index: 2;
  padding: 14px 24px;
  /* iOS PWA standalone (#283): with apple-mobile-web-app-status-bar-style
     "black-translucent", page content extends *behind* the status bar. Add
     the top inset so the wordmark / pills / icons clear the status-bar UI.
     Counterpart to the bottom-safe-area fix in #223 (.tabs padding-bottom).
     env(safe-area-inset-top) is 0 outside notched standalone PWA contexts,
     so this is a no-op on desktop / regular Safari / Android. The header's
     existing background fills the padded zone, so the status-bar UI sits on
     a themed strip rather than transparent space. */
  padding-top: calc(14px + env(safe-area-inset-top));
  display: flex; align-items: center; gap: 12px;
  border-bottom: 1px solid var(--border);
  background: var(--topbar-bg);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.tabs {
  background: var(--topbar-bg);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.logo {
  display: flex; align-items: center;
}
.wordmark {
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--text);
  user-select: none;
}
.wordmark-accent { color: var(--accent); }
body.cat-books .wordmark {
  font-family: 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  font-weight: 700;
  font-style: italic;
  letter-spacing: 0;
}
/* Play wordmark: monospace gives it an "arcade marquee" / dev-console feel
   that pairs with the violet+cyan palette. */
body.cat-games .wordmark {
  font-family: 'JetBrains Mono', 'SF Mono', 'Source Code Pro', 'Menlo', 'Consolas', ui-monospace, monospace;
  font-weight: 700;
  letter-spacing: -0.04em;
}
body.cat-games .wordmark-accent {
  text-shadow: 0 0 12px rgba(168, 85, 247, 0.45);
}
.spacer { flex: 1; }
.region-picker {
  display: flex; align-items: center; gap: 6px;
  height: 32px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0 10px 0 12px;
  box-sizing: border-box;
  position: relative;
}
.region-picker:hover { border-color: var(--accent); }
/* Mobile flag-only display (#162). Hidden on desktop — the native select
   shows "🇺🇸 United States" inline. On narrow viewports the country name
   is dropped in favour of just the flag and this overlay span renders it
   while the underlying select goes opacity:0 (still tappable, still opens
   the native dropdown which keeps full names per option). */
.region-flag-display { display: none; pointer-events: none; }
/* Header category selector — segmented (Watch / Read / Play). Tighter than
   filter-bar segmenteds since the header is a denser strip. Same height
   as .region-picker so the two controls line up cleanly.
   Wrapper uses min-height (not height) so it can grow to accommodate the
   36px touch-target min-height applied to .segmented button on coarse-
   pointer devices. With a hard height, the buttons overflowed and the
   active pill (with its accent background) appeared bulkier than the
   unselected ones. */
.segmented.category-toggle {
  min-height: 32px;
  padding: 2px;
  box-sizing: border-box;
}
.segmented.category-toggle button {
  padding: 0 10px;
  font-size: 12px;
  display: inline-flex;
  align-items: center;
}
/* Brand selector (#243) — unify wordmark + category pills into one rounded
   rectangle on the left of the header. The wrapper provides the chrome
   (border + tinted bg); the inner segmented strips its own. Wordmark uses
   per-category typography (sans / serif / mono) and is non-interactive so
   users don't tap it expecting a 4th option. A subtle divider separates
   the wordmark from the pills so the brand reads as identity, not button. */
.brand-selector {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px 3px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  height: 38px;
  box-sizing: border-box;
}
.brand-selector .logo {
  height: 100%;
  padding-right: 10px;
  border-right: 1px solid var(--border);
}
.brand-selector .segmented.category-toggle {
  background: transparent;
  border: 0;
  padding: 0;
  min-height: auto;
  gap: 2px;
}
.brand-selector .segmented.category-toggle button {
  height: 28px;
  padding: 0 10px;
}
/* Mobile fallback — Option A (icon pills). Full localized label is rendered
   alongside a small SVG icon; CSS picks one based on viewport. Icons are
   used (not first-letter abbreviations) because Italian Guardare/Giocare
   and German Sehen/Spielen both collide on a single starting letter. */
.cat-pill-short { display: none; }
.cat-pill-short svg { display: block; }
.region-picker select {
  background: none; border: none;
  padding: 0;
  height: 100%;
  font-size: 13px;
  cursor: pointer;
}
/* Header profile slot (#120, simplified in #183 / #185). Hosts the single
   silhouette button + its absolutely-positioned dropdown menu. */
.header-avatar-slot {
  position: relative;
  display: inline-flex;
  align-items: center;
}
/* Single profile-icon button (#183). Always shows the silhouette in both
   signed-in and signed-out states; the dropdown menu items adapt by auth
   state instead. Replaces the prior avatar+chevron / sign-in-pill twin. */
.header-avatar {
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-dim);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  border: 2px solid var(--border);
  overflow: hidden;
  position: relative;
  box-sizing: border-box;
  transition: transform 0.12s, border-color 0.12s, color 0.12s;
}
.header-avatar:hover {
  transform: scale(1.05);
  border-color: var(--accent);
  color: var(--accent);
}
.header-avatar[aria-expanded="true"] {
  border-color: var(--accent);
  color: var(--accent);
}
/* Signed-in state (#187): fill with accent + white silhouette so auth state
   reads at a glance. Toggled in renderHeaderAvatar() via .signed-in. The
   hover/expanded overrides keep the icon white (the default :hover sets
   color to accent — on an accent background that disappears). */
.header-avatar.signed-in {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}
.header-avatar.signed-in:hover,
.header-avatar.signed-in[aria-expanded="true"] {
  filter: brightness(1.08);
  color: #fff;
  border-color: var(--accent);
}
.header-avatar-icon {
  width: 60%; height: 60%;
}

.header-avatar-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 50;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 6px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
  min-width: 176px;
  display: flex; flex-direction: column;
  gap: 1px;
}
.header-avatar-menu[hidden] { display: none; }
.header-avatar-menu-item {
  text-align: left;
  padding: 9px 12px;
  font-size: 13.5px;
  border-radius: 6px;
  color: var(--text);
  background: transparent;
  cursor: pointer;
  border: none;
}
.header-avatar-menu-item:hover { background: var(--surface-2); }

.tabs {
  /* Lower stacking context than header (which is z-index: 2) so the
     avatar dropdown overflowing out of the header paints over the tabs.
     The mobile rule below switches to position: fixed; z-index: 100. */
  position: relative;
  z-index: 1;
  display: flex; gap: 4px;
  padding: 16px 24px 0;
  border-bottom: 1px solid var(--border);
}
.tab {
  padding: 12px 18px;
  font-weight: 500; font-size: 14px;
  color: var(--text-dim);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 0.15s, border-color 0.15s;
  display: flex; align-items: center; gap: 8px;
  background: transparent;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }
svg.tab-icon {
  width: 20px; height: 20px;
  display: block;
  flex-shrink: 0;
}
.tab-label { display: inline-flex; align-items: center; gap: 6px; }
.tab-count {
  background: var(--surface-3); padding: 2px 8px;
  font-size: 11px; border-radius: 999px;
  color: var(--text-dim);
}
.tab.active .tab-count { background: var(--accent); color: white; }

main {
  padding: 24px;
  max-width: 1400px; margin: 0 auto;
}

.search-bar {
  position: relative;
  margin-bottom: 24px;
}
.search-bar input {
  width: 100%;
  padding: 14px 16px 14px 48px;
  font-size: 16px;
  background: var(--surface);
}
.search-icon {
  position: absolute; left: 16px; top: 50%;
  transform: translateY(-50%);
  color: var(--text-dim);
  pointer-events: none;
}
.btn-icon { vertical-align: -2px; margin-right: 4px; flex-shrink: 0; }
.inline-icon { vertical-align: -2px; }
.ui-icon { display: inline-block; vertical-align: middle; }

.filter-bar {
  display: flex; gap: 12px; align-items: center;
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.filter-bar label {
  font-size: 13px; color: var(--text-dim);
}
.filter-bar select { padding: 8px 12px; font-size: 14px; }

.segmented {
  display: inline-flex;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 3px;
  gap: 2px;
}
.segmented button {
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-dim);
  border-radius: 6px;
  transition: background 0.15s, color 0.15s;
}
.segmented button:hover { color: var(--text); }
.segmented button.active {
  background: var(--accent);
  color: white;
}

.movie-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 20px;
}

/* Grid card chrome mirrors the .swipe-card visual language (#195): larger
   border-radius, baseline shadow, immersive feel. Distinct from .swipe-card
   in that the action strip lives below the poster as a slim button row —
   that frequent-use target stays outside the art. */
.movie-card {
  background: var(--surface);
  border-radius: 18px;
  overflow: hidden;
  border: 1px solid var(--border);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.28);
  transition: transform 0.15s, border-color 0.15s, box-shadow 0.15s;
  cursor: pointer;
  display: flex; flex-direction: column;
  position: relative;
}
.movie-card:hover {
  transform: translateY(-3px);
  border-color: var(--accent);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);
}
/* Theatrical highlights for movies — declared first so .available /
   .vpn-available win when both classes coexist (CSS source order; both
   selectors have the same specificity).
   Each modifier stacks an inset border-glow on top of the baseline drop
   shadow (#195) so the unified card chrome holds across status states. */
.movie-card.in-theaters-soon {
  border-color: var(--cinema-soon);
  box-shadow: 0 0 0 1px var(--cinema-soon-dim) inset, 0 6px 16px rgba(0, 0, 0, 0.28);
}
.movie-card.in-theaters-soon:hover {
  border-color: var(--cinema-soon);
  box-shadow: 0 0 0 1px var(--cinema-soon-dim) inset, 0 12px 28px rgba(168, 85, 247, 0.3);
}
.movie-card.in-theaters {
  border-color: var(--cinema);
  box-shadow: 0 0 0 1px var(--cinema-dim) inset, 0 6px 16px rgba(0, 0, 0, 0.28);
}
.movie-card.in-theaters:hover {
  border-color: var(--cinema);
  box-shadow: 0 0 0 1px var(--cinema-dim) inset, 0 12px 28px rgba(239, 68, 68, 0.3);
}
.movie-card.vpn-available {
  border-color: var(--vpn);
  box-shadow: 0 0 0 1px var(--vpn-dim) inset, 0 6px 16px rgba(0, 0, 0, 0.28);
}
.movie-card.vpn-available:hover {
  border-color: var(--vpn);
  box-shadow: 0 0 0 1px var(--vpn-dim) inset, 0 12px 28px rgba(56, 189, 248, 0.3);
}
.movie-card.available {
  border-color: var(--success);
  box-shadow: 0 0 0 1px var(--success-dim) inset, 0 6px 16px rgba(0, 0, 0, 0.28);
}
.movie-card.available:hover {
  border-color: var(--success);
  box-shadow: 0 0 0 1px var(--success-dim) inset, 0 12px 28px rgba(74, 222, 128, 0.3);
}
.movie-poster {
  aspect-ratio: 2/3;
  background: var(--surface-2);
  background-size: cover; background-position: center;
  position: relative;
}
/* Native-lazy <img> sits inside .movie-poster so cover requests fire only as
   cards approach the viewport. Object-fit: cover preserves the prior crop
   behavior (game art is 16:9 — sides crop into the 2:3 frame). */
.movie-poster .poster-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
/* Game cards use the same 2:3 poster aspect as movies and books — even
   though RAWG hero art is 16:9 landscape (so object-fit: cover crops the
   sides), keeping a single card shape across Watch / Read / Play matters
   more than showing every pixel of the source art. */
.movie-poster.no-image::before {
  content: '🎬';
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 48px; opacity: 0.3;
}
.poster-badge {
  position: absolute; top: 8px;
  padding: 4px 8px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 600;
  color: white;
  display: flex; align-items: center; gap: 4px;
  max-width: calc(100% - 16px);
  white-space: nowrap;
  overflow: hidden;
}
/* Left + right max-widths must sum to less than 100% minus the two 8px
   inset offsets, otherwise the badges visibly overlap on narrow cards.
   Combined max here is 100% - 24px (8 inset + 8 inset + 8 gap). */
.poster-badge.right { right: 8px; max-width: calc(45% - 12px); }
.poster-badge.left { left: 8px; max-width: calc(55% - 12px); }
.poster-badge .badge-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.poster-badge.streaming { background: var(--success); color: var(--on-accent); }
.poster-badge.vpn { background: var(--vpn); color: var(--on-accent); }
.poster-badge.unavailable { background: rgba(0,0,0,0.78); color: #E5E5E5; }
.poster-badge.theatrical-now  { background: var(--cinema); color: white; }
.poster-badge.theatrical-soon { background: var(--cinema-soon); color: white; }
.poster-badge.corner-status.watched { background: var(--warning); color: var(--on-accent); }
.poster-badge.corner-status.onlist { background: var(--accent); color: var(--on-accent); }

/* Quick-action overlay button on Watchlist/Watched grid cards (#255).
   Top-right of the poster, dedicated slot. Watchlist cards get a "mark
   watched / read / played" check; Watched cards get a "move back to
   watchlist / reading list / wishlist" bookmark. The whole-card click
   still opens the modal — this button is a one-tap shortcut for the
   most common state move. Kept visually distinct from .poster-badge
   (circular, blurred, larger tap target) so it reads as interactive. */
.poster-quick-action {
  position: absolute;
  top: 8px; right: 8px;
  width: 36px; height: 36px;
  border-radius: 50%;
  border: none;
  background: rgba(0,0,0,0.62);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  z-index: 2;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  padding: 0;
  transition: opacity 0.15s ease, transform 0.15s ease, background 0.15s ease;
}
.poster-quick-action:hover { background: rgba(0,0,0,0.82); }
.poster-quick-action:active { transform: scale(0.92); }
.poster-quick-action:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Push the TV-progress pill below the quick-action button so they don't
   overlap when both are present on the same card. */
.movie-card .poster-quick-action ~ .poster-badge.tv-progress {
  top: 52px;
}
/* On hover-capable devices, hide the button until the card is hovered or
   focused — keeps the immersive poster quiet for desktop browse. On
   touch (the most common surface for quick moves) it's always visible. */
@media (hover: hover) and (pointer: fine) {
  .poster-quick-action { opacity: 0; transform: scale(0.9); }
  .movie-card:hover .poster-quick-action,
  .movie-card:focus-within .poster-quick-action {
    opacity: 1; transform: scale(1);
  }
}
/* Optimistic-leave animation when the user taps the quick-action — the
   card fades + scales down before re-render removes it from the grid. */
.movie-card.is-leaving {
  transition: opacity 0.22s ease, transform 0.22s ease;
  opacity: 0;
  transform: scale(0.94);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .movie-card.is-leaving { transition: none; }
}

/* Quick-add corner buttons (#259) on Discover / For-You-grid cards.
   Anchored over the .movie-card-info gradient at the bottom of the
   poster so they don't fight the title for vertical space — the meta
   line (year + rating) is hidden on .has-quick-add cards because the
   action buttons need that strip. The .movie-card-info gradient gives
   the buttons enough contrast against bright posters; both buttons use
   the same dark blurred chrome as #255's .poster-quick-action with the
   primary swapped to accent for the dominant action. z-index sits above
   .movie-card-info (z-index 1) and the .provider-cluster (z-index 2)
   so the buttons stay tappable on cards where availability info loaded
   in afterwards. */
.poster-quick-add-row {
  position: absolute;
  left: 8px; right: 8px; bottom: 8px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  z-index: 3;
  pointer-events: none;
}
/* #315 retired the .single-action variant — every grid surface now uses the
   same 2-button [secondary, primary] row (Discover: + Watchlist / ✓ Watched;
   Watchlist: Remove / ✓ Watched; Watched: Remove / ★ Rate). The class is
   kept as a no-op alias for any legacy callers; new callers should not
   add it. */
.poster-quick-add-row.single-action { justify-content: space-between; }
.poster-quick-add-btn {
  pointer-events: auto;
  border: none;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: calc(50% - 4px);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: transform 0.15s ease, background 0.15s ease, opacity 0.15s ease;
}
.poster-quick-add-btn:active { transform: scale(0.94); }
.poster-quick-add-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.poster-quick-add-primary {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 2px 8px rgba(0,0,0,0.35);
}
.poster-quick-add-primary:hover { background: var(--accent-hover, var(--accent)); }
.poster-quick-add-secondary {
  background: rgba(0,0,0,0.62);
  color: #fff;
}
.poster-quick-add-secondary:hover { background: rgba(0,0,0,0.82); }
/* Status chip (#323) — replaces the 2-button row on Discover cards
   that are already on the user's Watchlist or Watched. The user wants
   the *same* bottom-strip real-estate, just rendering a non-interactive
   "✓ On list" or "★ N" pill instead of action buttons. Geometry mirrors
   .poster-quick-add-btn so the bottom strip reads as a sibling family;
   chip color follows the prior corner-status convention (#301):
   --warning for Watched (rated/unrated), --accent for Watchlist. */
.poster-quick-add-row.is-status { justify-content: center; pointer-events: none; }
.poster-quick-add-chip {
  pointer-events: none;
  border: none;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  padding: 8px 14px;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  box-shadow: 0 2px 8px rgba(0,0,0,0.35);
}
.poster-quick-add-chip.is-watched { background: var(--warning); color: var(--on-accent); }
.poster-quick-add-chip.is-onlist { background: var(--accent); color: var(--on-accent); }
/* Reserve the bottom strip the action row occupies (#299, #306, #311)
   so the title and meta line (year + source rating) sit above the
   buttons. The reservation fits the pill (~28px tall + 8px bottom
   inset = 36px) plus a small breathing gap. The reservation is
   constant on both touch and desktop so the title position does NOT
   shift when the row fades in on hover (the prior #306 hover-toggled
   value caused a visible vertical jump on every desktop hover, #311).
   Provider icons moved to the top-left in #311 so they no longer eat
   into the bottom strip; the icon cluster sits next to the title only
   if it would otherwise overflow the available width. */
.movie-card.has-quick-add .movie-card-info { padding-bottom: 44px; }
/* #315 dropped the hover-only opacity gating that #259 and #306 had on
   desktop. The row is now permanently visible on every surface — touch
   and desktop alike — because the user wants the action affordances
   discoverable without needing to hover, especially given the Watchlist /
   Watched grids where the primary action (graduate / re-rate) is the
   point of the surface. Layout reservation is the constant 44px above
   so nothing layout-shifts; only this opacity rule was a hover toggle,
   and it's gone. */
/* Bottom-right .provider-cluster (#210) overlaps the pill row's
   bottom-anchored buttons — drop it on .has-quick-add cards (#306).
   #311 relocates the cluster to the top-left on these surfaces (see
   .provider-cluster.top-left below) so the icons replace the prior
   single-name text chip; this rule still hides the bottom-right
   variant. The :not(.top-left) scope keeps the new top-left cluster
   visible. JS render path also short-circuits to skip the bottom-right
   DOM insertion entirely; this rule is the safety net + handles cases
   where the .has-quick-add class is added after the cluster renders. */
.movie-card.has-quick-add .provider-cluster:not(.top-left) { display: none; }
/* On the smallest grid (390pt mobile) the buttons shrink so they fit
   side-by-side without the secondary getting elided. The 140-180px card
   width on mobile leaves ~70px per button after the 50% max-width split,
   so the padding has to be tight to keep "+ Watchlist" / "✓ Watched"
   readable end-to-end without ellipsis. The slimmer buttons also need
   slightly less reserved space below the title. */
@media (max-width: 600px) {
  .poster-quick-add-row { left: 6px; right: 6px; bottom: 6px; gap: 6px; }
  .poster-quick-add-btn { font-size: 10.5px; padding: 6px 7px; }
  .movie-card.has-quick-add .movie-card-info { padding-bottom: 38px; }
}
/* Pick-for-me modal "Already watched" inline action (#259). Sits between
   Roll-again and Watch-it; lower visual weight than Watch-it (the primary
   path on the pick stage) but available as a one-tap "actually I've seen
   this — pick something else" without hopping through the modal. */
.pick-already-watched {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
}
.pick-already-watched:hover { background: var(--surface-3); }

.modal-info h2 .modal-type-pill {
  display: inline-block;
  vertical-align: middle;
  margin-left: 8px;
  background: var(--vpn-dim);
  color: var(--vpn);
  border: 1px solid var(--vpn);
  font-size: 11px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 6px;
  letter-spacing: 0.04em;
}

/* Poster-bottom info overlay (#195) — parallels .swipe-card-info. Title +
   one meta line (type pill + year) sit on a scrim gradient over the cover
   art. Grid cards are poster-only with no below-poster strip; the whole
   card opens the modal where actions live. The dark gradient works across
   all three themes (Watch / Read / Play) because it sits over the cover
   image, not the surface — book covers and game art vary, but white-on-
   dark-scrim clears WCAG AA at any grid scale. */
.movie-card-info {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 28px 12px 10px;
  background: linear-gradient(to top, rgba(0,0,0,0.94) 30%, rgba(0,0,0,0.55) 70%, transparent);
  color: #fff;
  pointer-events: none;
  z-index: 1;
}
.movie-card-title {
  font-size: 15px; font-weight: 700;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
}
.movie-card-meta {
  font-size: 11px;
  margin-top: 4px;
  display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
  opacity: 0.92;
}
/* Source rating in the meta line (#261) — TMDB vote_average for movies/TV,
   RAWG/IGDB metacritic for games. Same typography as the year it sits next
   to; no separate chip surface, just a sibling span the meta line's flex
   layout handles like any other entry. Reserved width on the right keeps
   the rating from sliding under the bottom-right .provider-cluster on
   cards that have one (Watch streaming icons / Play platform icons); on
   cards without a cluster (books, no-availability) the meta line uses
   the full width as before. */
.movie-card-rating { white-space: nowrap; }
.movie-card:has(.provider-cluster:not(.top-left)) .movie-card-meta { padding-right: 64px; }
/* Bottom-right brand cluster (#210) — Watch streaming providers and Play
   parent platforms, anchored over the poster scrim. Replaces the previous
   inline meta-line .platform-icon treatment. Up to 3 icons + a +N overflow
   chip; fits inside the .movie-card-info gradient so legibility holds
   across all three themes without an additional backdrop. */
.provider-cluster {
  position: absolute;
  right: 8px;
  bottom: 8px;
  display: flex;
  gap: 4px;
  align-items: center;
  z-index: 2;
  pointer-events: none;
}
.provider-cluster-icon {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.55);
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.provider-cluster-icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
/* Letter fallback (#323) — when a provider/platform doesn't have an icon
   asset available (TMDB returned no logo_path, or a game's parent
   platform isn't in PARENT_BRAND), render its initial inside the same
   chip so the slot still carries identity. Tunes to ~70% of the chip
   for legibility without crowding. Mobile shrinks proportionally to
   match the 20px chip override below. */
.provider-cluster-icon-letter {
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.02em;
}
@media (max-width: 600px) {
  .provider-cluster-icon-letter { font-size: 11px; }
}
/* Bootstrap-icons SVGs are monochrome — paint them white via a CSS mask so
   Xbox / Nintendo / Windows show on the dark backdrop (parallels the
   .brand-mask treatment used for store / platform chips elsewhere). */
.provider-cluster-icon .brand-mask {
  width: 14px;
  height: 14px;
  background: white;
  -webkit-mask: var(--brand-mask) center / contain no-repeat;
          mask: var(--brand-mask) center / contain no-repeat;
}
.provider-cluster-icon.owned {
  outline: 1.5px solid var(--success);
  outline-offset: 1px;
}
.provider-cluster-overflow {
  font-size: 10px;
  font-weight: 700;
  color: white;
  background: rgba(0, 0, 0, 0.6);
  border-radius: 4px;
  padding: 2px 5px;
  line-height: 1;
  letter-spacing: 0.02em;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
/* On the smallest grid (390pt mobile) the cluster shrinks to keep poster
   real-estate for the title overlay. */
@media (max-width: 600px) {
  .provider-cluster { gap: 3px; right: 6px; bottom: 6px; }
  .provider-cluster-icon { width: 20px; height: 20px; }
  .provider-cluster-icon .brand-mask { width: 12px; height: 12px; }
  .provider-cluster-overflow { font-size: 9px; padding: 2px 4px; }
}
/* Top-left cluster variant (#311). Replaces the single-name
   .poster-badge.streaming/.vpn text chip on .has-quick-add cards
   (Discover quick-add, Watchlist, Watched). Multiple icons fit in the
   same horizontal slot a single ellipsised provider name was eating,
   and the icons read faster on a glance. The bottom-right variant is
   suppressed on .has-quick-add cards (see :not(.top-left) rule above);
   this is the relocation, not a duplication. The .is-vpn modifier
   tints the wrapper to encode the VPN-required state that the prior
   blue-vs-green text chip carried, and a small leading globe glyph
   makes the VPN-ness scannable without color alone (a11y). Max-width
   matches .poster-badge.left so right-side corner-status badges
   (Watched / On list) on Watchlist + Watched cards don't collide. */
.provider-cluster.top-left {
  top: 8px;
  left: 8px;
  right: auto;
  bottom: auto;
  max-width: calc(55% - 12px);
  flex-wrap: nowrap;
}
.provider-cluster.top-left.is-vpn::before {
  content: '🌐';
  font-size: 11px;
  line-height: 1;
  background: var(--vpn);
  color: var(--on-accent);
  padding: 3px 5px;
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
@media (max-width: 600px) {
  .provider-cluster.top-left { top: 6px; left: 6px; }
  .provider-cluster.top-left.is-vpn::before { font-size: 9px; padding: 2px 4px; }
}
/* Swipe card top-left cluster (#315). Mirrors the grid card's
   .provider-cluster.top-left convention from #311 so the For You surface
   reads the same as the Watchlist / Watched / Discover grids. Slightly
   larger icons (22px vs 18px) since the swipe card is the focal element.
   The previous in-flow `.swipe-card-info .provider-cluster` rule (icons
   sat under the meta line at the bottom of the gradient) retired in
   favour of this absolute top-left placement. */
.swipe-card .provider-cluster.top-left {
  top: 14px;
  left: 14px;
  gap: 5px;
}
.swipe-card .provider-cluster.top-left .provider-cluster-icon { width: 22px; height: 22px; }
.swipe-card .provider-cluster.top-left .provider-cluster-icon .brand-mask { width: 14px; height: 14px; }
.swipe-card .provider-cluster.top-left .provider-cluster-overflow { font-size: 11px; padding: 3px 6px; }

/* Watched-state dim (#195): apply opacity to the poster's children rather
   than the poster element itself, so the .movie-card-info title overlay
   stays at full opacity (legibility) while the cover art + badges dim. */
.movie-card.is-watched .movie-poster > *:not(.movie-card-info) {
  opacity: 0.78;
  transition: opacity 0.15s;
}
.movie-card.is-watched:hover .movie-poster > *:not(.movie-card-info) {
  opacity: 1;
}
.movie-card.is-watched .movie-poster.no-image::before { opacity: 0.78; }
.movie-card.is-watched:hover .movie-poster.no-image::before { opacity: 1; }

/* Hover-preview trailer (#88). Iframe is inserted right after the .poster-img
   so the badges (corner status, tv progress, streaming) keep painting over
   it; .movie-card-info has z-index: 1 and stays on top regardless.
   pointer-events: none is load-bearing — without it the YouTube iframe
   captures the cursor and mouseleave never fires on the card, so teardown
   never runs. Black background fills the 2:3 frame while the 16:9 video
   letterboxes (covers the poster underneath during the brief load gap). */
.hover-trailer-iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0;
  background: #000;
  pointer-events: none;
  opacity: 0;
  animation: hover-trailer-fade-in 200ms ease forwards;
}
@keyframes hover-trailer-fade-in {
  to { opacity: 1; }
}

.empty-state {
  text-align: center;
  padding: 80px 24px;
  color: var(--text-dim);
}
.empty-state-icon {
  font-size: 48px;
  margin-bottom: 12px;
  opacity: 0.5;
  display: flex; justify-content: center;
}
.empty-state-icon svg { width: 56px; height: 56px; }
.empty-state-title {
  font-size: 18px; font-weight: 600;
  color: var(--text);
  margin-bottom: 6px;
}

.modal-overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0,0,0,0.75);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}
.modal-overlay.visible { opacity: 1; pointer-events: auto; }
.modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  max-width: 800px; width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  position: relative;
  display: flex;
  flex-direction: column;
}
.modal-close {
  position: absolute; top: 12px; right: 12px; z-index: 10;
  width: 36px; height: 36px;
  border-radius: 8px;
  background: rgba(0,0,0,0.6);
  display: flex; align-items: center; justify-content: center;
  color: white;
  font-size: 16px;
}
.modal-close:hover { background: rgba(0,0,0,0.9); }

/* Bottom-sheet drag handle (#91). On mobile (≤720px) the movie / rate /
   settings modals render as bottom sheets that slide up from the bottom
   edge; the handle is the grab affordance for drag-to-dismiss and the
   tap target for toggling between peek (60%) and full (90%) snap-points.
   Hidden entirely on desktop — the modals stay centered dialogs there. */
.bottom-sheet-handle {
  display: none;
}
.bottom-sheet-handle::before {
  content: '';
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.55);
}

/* Floating close — child of .modal that sticks to the top-right of the card
   itself (not the viewport) so it stays on the card on wide screens and
   stays visible while the modal content scrolls. The negative margin-bottom
   reclaims the vertical space the button would otherwise reserve, so
   #modalContent still starts at the top of the card. */
.modal-close-floating {
  position: sticky;
  top: 12px;
  align-self: flex-end;
  margin: 12px 12px -48px 0;
  z-index: 11;
  width: 40px; height: 40px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.85);
  border: 1px solid rgba(255, 255, 255, 0.12);
  font-size: 18px;
  color: white;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 16px rgba(0,0,0,0.5);
  transition: background 0.15s, transform 0.1s;
  cursor: pointer;
  flex-shrink: 0;
}
.modal-close-floating:hover {
  background: rgba(0, 0, 0, 0.95);
  transform: scale(1.08);
}
.modal-close-floating:active { transform: scale(0.96); }

.modal-backdrop {
  height: 280px;
  background-size: cover; background-position: center;
  background-color: var(--surface-2);
  position: relative;
}
.modal-backdrop::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(20,20,31,0) 0%, var(--surface) 95%);
}
.modal-backdrop.playing {
  height: 0;
  padding-bottom: 56.25%;
  background: #000 !important;
  background-image: none !important;
}
.modal-backdrop.playing::after { display: none; }
.modal-backdrop.playing iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0;
  z-index: 1;
}
.trailer-fallback {
  position: absolute;
  bottom: 8px; right: 8px;
  background: rgba(0, 0, 0, 0.78);
  color: white;
  padding: 5px 10px;
  font-size: 11px;
  font-weight: 500;
  border-radius: 6px;
  text-decoration: none;
  z-index: 2;
  opacity: 0.85;
  transition: opacity 0.15s, background 0.15s;
}
.trailer-fallback:hover { opacity: 1; background: rgba(0, 0, 0, 0.95); }
.modal:has(.modal-backdrop.playing) .modal-body { margin-top: 16px; }
.action-trailer {
  background: rgba(255, 0, 0, 0.12) !important;
  color: #ff5555 !important;
  border-color: rgba(255, 0, 0, 0.3) !important;
  font-weight: 600;
}
.action-trailer:hover {
  background: rgba(255, 0, 0, 0.22) !important;
  border-color: #ff5555 !important;
}
.action-watched-state {
  background: rgba(251, 191, 36, 0.12) !important;
  color: var(--warning) !important;
  border-color: rgba(251, 191, 36, 0.3) !important;
  font-weight: 600;
}
.action-watched-state:hover {
  background: rgba(251, 191, 36, 0.22) !important;
  border-color: var(--warning) !important;
}

/* Tinder-style swipe deck (For You → Swipe mode) */
.swipe-stack {
  position: relative;
  width: 100%;
  max-width: 380px;
  aspect-ratio: 2/3;
  margin: 0 auto;
  user-select: none;
  /* All gestures inside the stack belong to the swipe handler — never let the
     browser interpret a vertical drag as a page scroll. */
  touch-action: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  overscroll-behavior: contain;
}
.swipe-card {
  position: absolute;
  inset: 0;
  border-radius: 18px;
  overflow: hidden;
  cursor: grab;
  background: var(--surface-2);
  background-size: cover;
  background-position: center;
  box-shadow: 0 16px 36px rgba(0,0,0,0.5);
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s;
  will-change: transform, opacity;
  touch-action: none;
}
.swipe-card.dragging { cursor: grabbing; transition: none; }
.swipe-card.gone-right {
  transform: translate(150%, -10%) rotate(28deg);
  opacity: 0;
  transition: transform 0.4s ease-out, opacity 0.3s;
}
.swipe-card.gone-left {
  transform: translate(-150%, -10%) rotate(-28deg);
  opacity: 0;
  transition: transform 0.4s ease-out, opacity 0.3s;
}
.swipe-card.gone-up {
  transform: translate(0, -150%) rotate(0);
  opacity: 0;
  transition: transform 0.4s ease-out, opacity 0.3s;
}
.swipe-card.behind {
  transform: scale(0.95) translateY(8px);
  filter: brightness(0.85);
  pointer-events: none;
}
.swipe-card-info {
  position: absolute; bottom: 0; left: 0; right: 0;
  padding: 28px 22px 22px;
  background: linear-gradient(to top, rgba(0,0,0,0.96) 30%, rgba(0,0,0,0.6) 70%, transparent);
  color: white;
}
.swipe-card-title { font-size: 22px; font-weight: 700; margin-bottom: 4px; line-height: 1.2; }
.swipe-card-meta {
  font-size: 12px;
  opacity: 0.85;
  margin-bottom: 8px;
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
}
.swipe-card-overview {
  font-size: 13px;
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  opacity: 0.9;
}
.swipe-card-typepill {
  background: rgba(56, 189, 248, 0.85);
  color: #0A0A10;
  font-size: 10px;
  font-weight: 700;
  padding: 2px 6px;
  border-radius: 4px;
}
.swipe-card-info-btn {
  position: absolute;
  bottom: 14px; right: 14px;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  display: flex; align-items: center; justify-content: center;
  color: white;
  font-family: Georgia, 'Times New Roman', serif;
  font-style: italic;
  font-size: 17px;
  font-weight: 700;
  cursor: pointer;
  z-index: 5;
  border: 1px solid rgba(255, 255, 255, 0.32);
  user-select: none;
  transition: background 0.15s, transform 0.1s;
}
.swipe-card-info-btn:hover {
  background: rgba(0, 0, 0, 0.78);
  transform: scale(1.08);
}
.swipe-card-info-btn:active { transform: scale(0.94); }

.swipe-card-pill {
  position: absolute;
  top: 24px;
  font-size: 30px; font-weight: 900;
  letter-spacing: 2px;
  padding: 6px 14px;
  border-radius: 10px;
  border: 4px solid;
  opacity: 0;
  transition: opacity 0.1s;
  pointer-events: none;
  text-transform: uppercase;
  white-space: nowrap;
  z-index: 2;
}
.swipe-card-pill.like {
  right: 24px; color: #4ade80; border-color: #4ade80; transform: rotate(15deg);
}
.swipe-card-pill.skip {
  left: 24px; color: #ef4444; border-color: #ef4444; transform: rotate(-15deg);
}
.swipe-card-pill.watched {
  top: 24px; left: 50%; color: var(--warning); border-color: var(--warning);
  transform: translateX(-50%);
}
.swipe-actions {
  display: flex; justify-content: center; gap: 22px;
  margin-top: 24px;
}
.swipe-btn {
  width: 60px; height: 60px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 24px;
  font-weight: 700;
  background: var(--surface);
  border: 2px solid var(--border);
  transition: transform 0.1s, background 0.15s, box-shadow 0.15s;
  cursor: pointer;
}
.swipe-btn.skip { color: #ef4444; border-color: #ef4444; }
.swipe-btn.watchlist { color: var(--accent); border-color: var(--accent); }
.swipe-btn.watched { color: var(--warning); border-color: var(--warning); }
.swipe-btn:hover { transform: scale(1.1); box-shadow: 0 4px 16px rgba(255,77,109,0.2); }
.swipe-btn:active { transform: scale(0.92); }
.swipe-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.swipe-hint {
  text-align: center;
  font-size: 12px;
  color: var(--text-muted);
  margin-top: 16px;
}
.swipe-empty {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-dim);
}
.swipe-empty-title { color: var(--text); font-weight: 600; font-size: 16px; margin-bottom: 6px; }
.swipe-empty button {
  margin-top: 16px;
  padding: 10px 20px;
  background: var(--accent);
  color: white;
  border-radius: 8px;
  font-weight: 500;
  border: none;
  cursor: pointer;
}

@media (max-width: 600px) {
  /* Lock the swipe surface to fit comfortably in the viewport so the user
     never has to scroll while interacting with cards. The math: subtract the
     sticky topbar (~50), tabs (~50), For You toggle row (~52), filters (~58),
     actions (~80), hint (~30), and some breathing room (~30) ≈ 350px. */
  .swipe-stack {
    aspect-ratio: 2/3;
    height: min(540px, calc(100dvh - 350px));
    width: auto;
    max-width: min(380px, calc((100dvh - 350px) * 2 / 3));
    min-height: 320px;
  }
  .swipe-card-title { font-size: 19px; }
  .swipe-card-info { padding: 22px 18px 18px; }
  .swipe-actions { gap: 16px; margin-top: 14px; }
  .swipe-btn { width: 54px; height: 54px; font-size: 22px; }
  .swipe-hint { margin-top: 10px; font-size: 11px; }
}

/* Cast & crew */
.cast-section {
  padding: 0 24px 8px;
}
.cast-section h3 {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin-bottom: 12px;
}
.cast-strip {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-bottom: 6px;
}
.cast-strip::-webkit-scrollbar { display: none; }
.cast-card {
  flex: 0 0 90px;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: transform 0.15s;
}
.cast-card:hover { transform: translateY(-2px); }
.cast-photo {
  width: 90px; height: 110px;
  border-radius: 8px;
  background-size: cover;
  background-position: center top;
  background-color: var(--surface-2);
  border: 1px solid var(--border);
  margin-bottom: 6px;
  position: relative;
}
.cast-photo.no-image::after {
  content: '👤';
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 32px; opacity: 0.4;
}
.cast-card:hover .cast-photo { border-color: var(--accent); }
.cast-name {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.cast-role {
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.2;
  margin-top: 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@media (max-width: 600px) {
  .cast-section { padding: 0 16px 4px; }
  .cast-card { flex: 0 0 76px; }
  .cast-photo { width: 76px; height: 96px; }
}

/* Cinema / showtimes */
.cinema-row {
  display: flex; gap: 10px; flex-wrap: wrap;
  margin-bottom: 16px;
}
.cinema-btn {
  padding: 10px 16px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, transform 0.1s;
  display: flex; align-items: center; gap: 8px;
}
.cinema-btn:hover {
  background: var(--surface-3);
  border-color: var(--accent);
}
.cinema-btn:active { transform: translateY(1px); }
.cinema-btn:disabled { opacity: 0.6; cursor: wait; }
.cinema-btn-icon { font-size: 16px; }

/* Similar movies */
.similar-section {
  padding: 20px 24px 24px;
  border-top: 1px solid var(--border);
}
.similar-section h3 {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin-bottom: 14px;
}
.similar-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 12px;
}
.similar-card {
  cursor: pointer;
  display: flex; flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.similar-poster {
  aspect-ratio: 2/3;
  border-radius: 8px;
  background-size: cover;
  background-color: var(--surface-2);
  background-position: center;
  border: 1px solid var(--border);
  transition: transform 0.15s, border-color 0.15s, box-shadow 0.15s;
  position: relative;
}
.similar-card:hover .similar-poster {
  transform: scale(1.04);
  border-color: var(--accent);
  box-shadow: 0 4px 14px rgba(255, 77, 109, 0.2);
}
.similar-title {
  font-size: 12px;
  font-weight: 500;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.similar-year {
  font-size: 11px;
  color: var(--text-dim);
}
.similar-badge {
  position: absolute;
  top: 4px; right: 4px;
  padding: 2px 6px;
  font-size: 10px;
  font-weight: 700;
  border-radius: 4px;
  background: rgba(0,0,0,0.78);
  color: white;
}
.similar-badge.watched { background: var(--warning); color: var(--on-accent); }
.similar-badge.onlist { background: var(--accent); }
.modal-body {
  padding: 24px;
  margin-top: -120px;
  position: relative; z-index: 2;
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: 24px;
}
.modal-poster {
  aspect-ratio: 2/3;
  border-radius: 12px;
  background-size: cover;
  background-color: var(--surface-2);
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.modal-info h2 {
  font-size: 26px;
  margin-bottom: 6px;
  line-height: 1.2;
}
.modal-meta {
  font-size: 14px; color: var(--text-dim);
  margin-bottom: 14px;
  display: flex; gap: 8px; flex-wrap: wrap;
  align-items: center;
}
.modal-meta-dot { color: var(--text-muted); }
.modal-overview {
  font-size: 14px; line-height: 1.6;
  color: var(--text-dim);
  margin-bottom: 20px;
}

.providers-section { padding: 0 24px 24px; }
.providers-section h3 {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin-bottom: 12px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  display: flex; align-items: center; gap: 8px;
}
.providers-section h3:first-child { padding-top: 0; border-top: none; }
.providers-section h3 .h3-meta {
  color: var(--text-muted);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  font-size: 11px;
}
.providers-section h3 .h3-tag {
  background: var(--success-dim);
  color: var(--success);
  padding: 2px 8px;
  border-radius: 6px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.providers-section h3 .h3-tag.vpn {
  background: var(--vpn-dim);
  color: var(--vpn);
}
.providers-row {
  display: flex; gap: 10px; flex-wrap: wrap;
  margin-bottom: 16px;
}
.providers-row:last-child { margin-bottom: 0; }
.provider-chip {
  display: flex; align-items: center; gap: 8px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 8px 12px;
  border-radius: 10px;
  font-size: 13px;
}
.provider-chip.subscribed {
  border-color: var(--success);
  background: var(--success-dim);
}
.provider-chip.vpn {
  border-color: var(--vpn);
  background: var(--vpn-dim);
}
.provider-chip-logo {
  width: 28px; height: 28px;
  border-radius: 6px;
  background-size: cover;
  flex-shrink: 0;
}
/* Brand-mono variant for trimmed transparent brand SVGs (Steam, PlayStation,
   Apple, Xbox, Nintendo, …): smaller than TMDB logos, no rounded square,
   contain-not-cover so SVGs aren't cropped. Two sources, two markups:
   - simpleicons (colored): rendered as <img>, brand color baked in.
   - bootstrap-icons (monochrome SVG with fill="currentColor"): rendered as
     a <span> with mask-image so we can paint it currentColor / theme-aware. */
.provider-chip-logo.brand-mono {
  width: 22px; height: 22px;
  border-radius: 0;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  display: inline-block;
}
.provider-chip-logo.brand-mono.brand-mask {
  background-color: currentColor;
  background-image: none;
  -webkit-mask: var(--brand-mask) center / contain no-repeat;
          mask: var(--brand-mask) center / contain no-repeat;
}
img.provider-chip-logo.brand-mono { object-fit: contain; }

.modal-actions {
  display: flex; gap: 8px;
  margin-top: 16px;
  flex-wrap: wrap;
}
.modal-actions button {
  padding: 10px 16px;
  font-size: 14px; font-weight: 500;
  border-radius: 8px;
  border: 1px solid var(--border);
  color: var(--text);
  transition: background 0.15s;
  display: flex; align-items: center; gap: 6px;
}
.modal-actions button:hover { background: var(--surface-2); }
.modal-actions button.primary { background: var(--accent); color: white; border-color: var(--accent); }
.modal-actions button.primary:hover { background: var(--accent-hover); }

/* Settings modal */
.settings-modal {
  max-width: 640px;
  padding: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  max-height: 90vh;
}
.settings-modal-scroll {
  padding: 28px 32px 24px;
  overflow-y: auto;
  /* overflow-y: auto implicitly turns overflow-x into auto as well, which lets
     a too-wide flex row inside the modal (e.g. the account email + "Send magic
     link" row in long-string locales) start panning the whole modal sideways.
     Pin overflow-x to hidden so only vertical scrolling is possible. */
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  flex: 1 1 auto;
  min-height: 0;
}
.settings-modal-close {
  position: absolute;
  top: 12px; right: 12px;
  z-index: 11;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background 0.15s, transform 0.1s;
}
.settings-modal-close:hover { background: var(--surface-3); transform: scale(1.06); }
.settings-modal-close:active { transform: scale(0.96); }
.settings-modal-footer {
  flex-shrink: 0;
  padding: 14px 24px;
  border-top: 1px solid var(--border);
  background: var(--surface);
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
}
.settings-modal-footer button.secondary {
  padding: 10px 18px;
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--text);
  font-weight: 500;
  font-size: 14px;
  border: 1px solid var(--border);
  transition: background 0.15s;
}
.settings-modal-footer button.secondary:hover { background: var(--surface-3); }
.settings-modal-footer button.primary {
  padding: 10px 22px;
  border-radius: 10px;
  background: var(--accent);
  color: white;
  font-weight: 600;
  font-size: 14px;
  border: none;
  transition: background 0.15s;
}
.settings-modal-footer button.primary:hover { background: var(--accent-hover); }
.settings-modal h2 {
  font-size: 22px;
  margin-bottom: 10px;
}
/* Settings subtitle (#302): styled as a small info chip rather than a bare
   <p>, since the previous `.settings-modal > p.subtitle` selector never
   matched (the <p> is nested inside `.settings-modal-scroll`, not a direct
   child of `.settings-modal`) so the line was shipping unstyled and
   crammed against the content below. */
.settings-modal-scroll .subtitle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 22px;
  padding: 6px 12px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--text-dim);
  font-size: 12.5px;
  line-height: 1.3;
  max-width: 100%;
}
.settings-modal-scroll .subtitle svg {
  flex-shrink: 0;
  opacity: 0.7;
}
.settings-section {
  border-top: 1px solid var(--border);
  padding-top: 20px;
  margin-top: 20px;
}
.settings-section:first-of-type {
  border-top: none;
  padding-top: 0;
  margin-top: 0;
}
.settings-section h3 {
  font-size: 14px;
  font-weight: 700;
  margin-bottom: 4px;
}
.settings-section p.section-desc {
  color: var(--text-dim);
  font-size: 13px;
  margin-bottom: 14px;
}
.settings-section input[type="text"] {
  width: 100%;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
}
.api-row {
  display: flex; gap: 8px;
  flex-wrap: wrap;
}
.api-row input { flex: 1 1 180px; min-width: 0; }
.api-row button {
  padding: 10px 16px;
  background: var(--accent);
  color: white;
  border-radius: 8px;
  font-weight: 500;
  white-space: nowrap;
  transition: background 0.15s;
}
.api-row button:hover { background: var(--accent-hover); }
.api-row button:disabled { opacity: 0.6; cursor: wait; }
.error-msg { color: #f87171; font-size: 13px; margin-top: 8px; }
.success-msg { color: var(--success); font-size: 13px; margin-top: 8px; }

.settings-filter-input {
  width: 100%;
  padding: 8px 12px;
  font-size: 13px;
  background: var(--surface);
  margin-bottom: 10px;
  position: sticky;
  top: 0;
  z-index: 5;
}
.vpn-regions-mode { margin-bottom: 12px; }
/* Theme picker (#250): 4-option segmented control. With 4 buttons inside the
   ~360–600px Settings content panel — and longer locale labels (es
   "Predeterminado", de "Automatisch") — `flex: 1` alone overflowed because
   each button defaulted to `min-width: auto` (intrinsic content). Allow wrap
   to a 2x2 grid as a safety net, drop `min-width: 0` so each button can
   shrink to its share, and trim horizontal padding so 4 short labels still
   fit on one row in every supported locale. */
.theme-mode { width: 100%; flex-wrap: wrap; }
.theme-mode button {
  flex: 1 1 80px;
  min-width: 0;
  padding: 6px 8px;
  text-align: center;
}
.settings-filter-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  height: 14px; width: 14px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'><path d='M11 3 3 11M3 3l8 8' stroke='%239090a8' stroke-width='1.5' fill='none' stroke-linecap='round'/></svg>");
  cursor: pointer;
}
.provider-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
  padding: 4px 4px 4px 0;
}
/* "Show all (N more)" button that lives inside the grid as a full-width row */
.provider-show-more {
  grid-column: 1 / -1;
  padding: 10px;
  background: transparent;
  border: 1px dashed var(--border);
  border-radius: 6px;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.provider-show-more:hover {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--accent);
}
.picker-bulk-actions {
  display: flex; gap: 8px;
  margin-bottom: 10px;
}
.picker-bulk-actions button {
  padding: 6px 12px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-dim);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.picker-bulk-actions button:hover {
  background: var(--surface-3);
  color: var(--text);
  border-color: var(--accent);
}
.provider-pill {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
  font-size: 13px;
  user-select: none;
}
.provider-pill:hover { border-color: var(--accent); }
.provider-pill.selected {
  background: var(--success-dim);
  border-color: var(--success);
}
.provider-pill .logo {
  width: 28px; height: 28px;
  border-radius: 6px;
  background-size: cover;
  flex-shrink: 0;
}
.provider-pill .name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.region-pill-grid {
  display: flex; flex-wrap: wrap; gap: 6px;
}
.region-pill {
  padding: 6px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
  user-select: none;
}
.region-pill:hover { border-color: var(--accent); }
.region-pill.selected {
  background: var(--vpn-dim);
  border-color: var(--vpn);
  color: var(--vpn);
}
.region-pill.disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.spinner {
  display: inline-block;
  width: 16px; height: 16px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.loading-row {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 40px;
  color: var(--text-dim);
}

/* Skeleton cards — placeholder while a grid is loading. Mirrors .movie-card
   geometry (2:3 poster only, no strip below) so the layout doesn't jump
   when real cards swap in. Themed via existing CSS vars so Watch / Read /
   Play all get a subject-appropriate shimmer. JS-free animation. */
.skeleton-card {
  background: var(--surface);
  border-radius: 18px;
  border: 1px solid var(--border);
  overflow: hidden;
  display: flex; flex-direction: column;
}
.skeleton-poster {
  aspect-ratio: 2/3;
  background: var(--surface-2);
  background-image: linear-gradient(
    90deg,
    var(--surface-2) 0%,
    var(--surface-3) 50%,
    var(--surface-2) 100%
  );
  background-size: 200% 100%;
  animation: skeletonShimmer 1.4s ease-in-out infinite;
}
@keyframes skeletonShimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .skeleton-poster { animation: none; }
}
.loading-inline {
  display: flex; align-items: center; gap: 8px;
  padding: 12px;
  color: var(--text-dim);
  font-size: 13px;
}
/* The HTML `hidden` attribute should always win over class display rules. */
[hidden] { display: none !important; }

.toast {
  position: fixed; bottom: 24px; left: 50%;
  transform: translateX(-50%) translateY(100px);
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 12px 20px;
  border-radius: 10px;
  z-index: 300;
  font-size: 14px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
  opacity: 0;
  transition: transform 0.3s, opacity 0.3s;
}
.toast.visible {
  transform: translateX(-50%) translateY(0);
  opacity: 1;
}
.toast-undo { display: flex; align-items: center; gap: 12px; padding: 10px 14px 10px 16px; }
.toast-undo-btn {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 8px;
  padding: 6px 14px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
}
.toast-undo-btn:hover { filter: brightness(1.1); }

/* TV episode progress (#26) — modal section + per-card progress badge. */
.episodes-section {
  margin-top: 24px;
  padding: 0 24px;
}
.episodes-section h3 {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin: 0 0 8px;
  font-size: 16px;
}
.episodes-section .episodes-progress {
  font-size: 13px;
  color: var(--text-dim);
  font-weight: 500;
}
.episodes-progress-bar {
  height: 4px;
  background: var(--surface-2);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: 12px;
}
.episodes-progress-bar > span {
  display: block;
  height: 100%;
  background: var(--accent);
  transition: width 0.2s;
}
.seasons-list .season {
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: 8px;
  overflow: hidden;
  background: var(--surface);
}
.season-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  cursor: pointer;
  user-select: none;
}
.season-header:hover, .season-header:focus-visible { background: var(--surface-2); }
.season-header:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.season-chevron {
  display: inline-block;
  width: 14px;
  font-size: 12px;
  color: var(--text-dim);
  transition: transform 0.15s;
}
.season-name { flex: 1; font-weight: 500; font-size: 14px; }
.season-count { font-size: 12px; color: var(--text-dim); min-width: 44px; text-align: right; }
.season-mark {
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 12px;
  cursor: pointer;
}
.season-mark:hover { background: var(--surface-2); color: var(--text); }
.season-episodes {
  border-top: 1px solid var(--border);
  padding: 4px 0;
  background: var(--surface-2);
}
.episode-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px 8px 12px;
  cursor: pointer;
  font-size: 13px;
  border-radius: 0;
}
.episode-row:hover { background: var(--surface-3, var(--surface)); }
.episode-row input[type="checkbox"] {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  cursor: pointer;
  accent-color: var(--accent);
}
.episode-row .episode-num {
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  min-width: 32px;
  font-weight: 500;
}
.episode-row .episode-name { flex: 1; color: var(--text); }
.episode-row .episode-aired { font-size: 11px; color: var(--text-muted); }
.episode-row.seen .episode-name { color: var(--text-dim); }
.episodes-empty, .episodes-error {
  padding: 12px;
  font-size: 13px;
  color: var(--text-dim);
}
.episodes-error { color: var(--accent); }

/* Card progress badge — top-right corner of TV poster. Sits above the .right
   corner-status badge by virtue of being rendered later (no z-index war).
   Geometry aligned with the rest of the .poster-badge family in #271
   (padding 4px 8px, border-radius 6px) so it reads as part of the same
   badge system instead of a stray pill. The translucent black background
   stays since the bar visualization needs more visual weight than a tinted
   chrome on top of poster art could provide. */
.poster-badge.tv-progress {
  right: 8px;
  display: flex;
  align-items: center;
  gap: 6px;
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  font-variant-numeric: tabular-nums;
  backdrop-filter: blur(4px);
}
.tv-progress-bar {
  display: inline-block;
  width: 32px;
  height: 3px;
  background: rgba(255, 255, 255, 0.25);
  border-radius: 2px;
  overflow: hidden;
}
.tv-progress-bar > span {
  display: block;
  height: 100%;
  background: var(--accent);
}

/* Rate modal */
.rate-modal {
  max-width: 420px;
  padding: 32px;
  text-align: center;
}
/* Rate modal is short and padded — skip the sticky/negative-margin layout
   the cinematic movie modal needs and just anchor the X to the card corner. */
.rate-modal .modal-close-floating {
  position: absolute;
  top: 14px;
  right: 14px;
  align-self: auto;
  margin: 0;
}
.rate-modal h2 { font-size: 20px; margin-bottom: 6px; line-height: 1.3; }
.rate-modal .subtitle { color: var(--text-dim); font-size: 13px; margin-bottom: 24px; }
.rate-stars {
  display: flex; justify-content: center; gap: 6px;
  margin-bottom: 24px;
  font-size: 40px;
}
.rate-star {
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.1s, transform 0.1s;
  user-select: none;
}
/* Gate :hover behind hover-capable devices — without this, mobile Safari/
   Chrome treat the first tap on a star as "fauxhover" (showing the hover
   state but suppressing the synthetic click), so users have to tap twice
   before pendingRating updates and Save enables. */
@media (hover: hover) and (pointer: fine) {
  .rate-star:hover { color: var(--warning); transform: scale(1.1); }
}
.rate-star.active { color: var(--warning); }
.rate-value-label {
  font-size: 13px;
  color: var(--text-dim);
  margin-bottom: 20px;
  height: 18px;
}
.rate-actions {
  display: flex; gap: 8px; justify-content: center;
}
.rate-actions button {
  padding: 10px 20px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-weight: 500;
  font-size: 14px;
  transition: background 0.15s;
}
.rate-actions button:hover { background: var(--surface-2); }
.rate-actions button.primary { background: var(--accent); color: white; border-color: var(--accent); }
.rate-actions button.primary:hover { background: var(--accent-hover); }
.rate-actions button.primary:disabled { opacity: 0.5; cursor: not-allowed; background: var(--surface-3); border-color: var(--border); }
#rateModal { z-index: 250; }

/* Cold-start quiz (#84). Stacked card list shown on the For You tab the
   first time a fresh user lands there for the active category. */
.cold-start {
  max-width: 640px;
  margin: 0 auto;
}
.cold-start-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 4px;
}
.cold-start-header h2 {
  font-size: 22px;
  font-weight: 700;
  margin: 0;
  line-height: 1.25;
}
.cold-start-header .cold-start-skip {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  padding: 6px 8px;
  flex-shrink: 0;
}
.cold-start-header .cold-start-skip:hover { text-decoration: underline; }
.cold-start-subtitle {
  color: var(--text-dim);
  font-size: 14px;
  margin: 0 0 10px;
}
.cold-start-progress {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 16px;
}
.cold-start-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 18px;
}
.cold-start-card {
  display: grid;
  grid-template-columns: 72px 1fr;
  gap: 14px;
  padding: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  transition: opacity 0.18s, background 0.18s;
}
.cold-start-card.dismissed {
  opacity: 0.5;
}
.cold-start-card.rated {
  background: var(--surface-2);
}
.cold-start-card .cs-poster {
  width: 72px;
  height: 108px;
  border-radius: 8px;
  background: var(--surface-3);
  background-size: cover;
  background-position: center;
  flex-shrink: 0;
}
.cold-start-card .cs-poster.no-art {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  font-size: 22px;
}
.cold-start-card .cs-body {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-width: 0;
}
.cold-start-card .cs-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  margin: 0 0 2px;
  line-height: 1.3;
  word-wrap: break-word;
}
.cold-start-card .cs-meta {
  font-size: 12px;
  color: var(--text-muted);
  margin-bottom: 8px;
}
.cold-start-card .cs-stars {
  display: flex;
  gap: 4px;
  font-size: 28px;
  line-height: 1;
  margin-bottom: 6px;
}
.cold-start-card .cs-star {
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  background: none;
  border: none;
  padding: 4px 2px;
  min-width: 36px;
  min-height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color 0.1s, transform 0.1s;
}
.cold-start-card .cs-star:hover { color: var(--warning); transform: scale(1.08); }
.cold-start-card .cs-star.active { color: var(--warning); }
.cold-start-card .cs-haventseen {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  padding: 6px 0;
  text-align: left;
  align-self: flex-start;
}
.cold-start-card .cs-haventseen:hover { color: var(--text-dim); }
.cold-start-card .cs-status {
  font-size: 13px;
  color: var(--text-dim);
  font-weight: 500;
}
.cold-start-card .cs-status .cs-status-stars { color: var(--warning); }
.cold-start-card .cs-undo {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 12px;
  cursor: pointer;
  padding: 4px 0 0;
}
.cold-start-card .cs-undo:hover { text-decoration: underline; }
.cold-start-done-row {
  display: flex;
  justify-content: center;
  margin: 10px 0 24px;
}
.cold-start-done-btn {
  background: var(--accent);
  color: white;
  border: none;
  padding: 14px 28px;
  border-radius: 10px;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  min-height: 48px;
  transition: background 0.15s;
}
.cold-start-done-btn:hover { background: var(--accent-hover); }
.cold-start-done-btn:disabled {
  background: var(--surface-3);
  color: var(--text-muted);
  cursor: not-allowed;
}
.cold-start-loading,
.cold-start-error {
  text-align: center;
  padding: 40px 20px;
  color: var(--text-dim);
  font-size: 14px;
}
.cold-start-error button {
  margin-top: 14px;
  background: var(--accent);
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
  font-size: 14px;
}

@media (max-width: 720px) {
  .cold-start-header h2 { font-size: 19px; }
  .cold-start-card { grid-template-columns: 60px 1fr; gap: 10px; padding: 10px; }
  .cold-start-card .cs-poster { width: 60px; height: 90px; }
  .cold-start-card .cs-stars { font-size: 26px; gap: 2px; }
  .cold-start-card .cs-star { min-width: 40px; min-height: 40px; }
  .cold-start-card .cs-title { font-size: 14px; }
  .cold-start-done-btn { width: 100%; }

  /* Bottom-sheet treatment for the movie / rate / settings modals (#91).
     The overlay anchors to the bottom edge, the modal becomes full-width
     with rounded top-only corners, and the slide animation runs on
     `transform`. Outside this query the desktop centered-dialog layout
     in `.modal-overlay` / `.modal` is untouched. */
  .modal-overlay.is-bottom-sheet {
    align-items: flex-end;
    padding: 0;
  }
  .modal-overlay.is-bottom-sheet > .modal {
    width: 100%;
    max-width: none;
    border-radius: 16px 16px 0 0;
    max-height: 90vh;
    transform: translateY(100%);
    transition: transform 0.25s cubic-bezier(0.32, 0.72, 0, 1),
                max-height 0.18s ease;
  }
  .modal-overlay.is-bottom-sheet.visible > .modal {
    transform: translateY(0);
  }
  /* While the user is actively dragging the handle, JS writes
     `transform: translateY(<dy>px)` directly on the element — kill the
     CSS transition so the gesture tracks the finger 1:1. */
  .modal-overlay.is-bottom-sheet > .modal.dragging {
    transition: none;
  }
  /* Peek snap-point — half-height resting position (tap-on-handle toggle). */
  .modal-overlay.is-bottom-sheet > .modal.peek {
    max-height: 60vh;
  }

  .modal-overlay.is-bottom-sheet .bottom-sheet-handle {
    display: flex;
    position: sticky;
    top: 0;
    z-index: 12;
    height: 22px;
    margin-bottom: -22px;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    cursor: grab;
    /* The handle owns vertical pan gestures so drags translate to the
       sheet's transform rather than scrolling the inner content. The
       scrollable content area stays at the default `auto`. */
    touch-action: none;
    /* Faint top-fade so the pill is legible over the movie modal's
       backdrop hero image (the rate/settings surfaces don't strictly
       need it, but a barely-there fade reads as the same visual
       affordance across all three sheets). */
    background: linear-gradient(to bottom, rgba(20,20,31,0.18) 0%, rgba(20,20,31,0) 100%);
  }
  .modal-overlay.is-bottom-sheet .bottom-sheet-handle:active { cursor: grabbing; }

  /* Settings modal has its own inner-scroll wrapper (`.settings-modal-scroll`)
     and the outer `.modal` is `overflow: hidden`. The negative-margin
     reclaim trick used on movie/rate isn't needed here — the handle just
     sits as a flex child at the top of the column. */
  .modal-overlay.is-bottom-sheet > .modal.settings-modal > .bottom-sheet-handle {
    margin-bottom: 0;
  }

  /* Respect reduced-motion: skip the slide-up entirely. The sheet
     simply appears at its resting position when `.visible` toggles. */
  @media (prefers-reduced-motion: reduce) {
    .modal-overlay.is-bottom-sheet > .modal {
      transition: none !important;
    }
  }
}

/* Discover */
.section-heading {
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin: 32px 0 14px;
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap;
}
.section-heading:first-child { margin-top: 0; }
.section-heading .meta {
  color: var(--text-muted);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  font-size: 12px;
}
.genre-chips {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-bottom: 20px;
}
.genre-chip {
  padding: 8px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  color: var(--text-dim);
}
.genre-chip:hover { border-color: var(--accent); color: var(--text); }
.genre-chip.active {
  background: var(--accent);
  color: white;
  border-color: var(--accent);
}
/* Mood chips (#87) — visually adjacent to genre chips but lightly tinted so
   the user can tell at a glance the chip strip switched modes. The icon is
   a leading emoji rendered inside .mood-icon for spacing. */
.mood-chip {
  background: color-mix(in srgb, var(--accent) 8%, var(--surface-2));
  border-color: color-mix(in srgb, var(--accent) 22%, var(--border));
}
.mood-chip:hover {
  background: color-mix(in srgb, var(--accent) 14%, var(--surface-2));
}
.mood-chip.active {
  background: var(--accent);
  color: white;
  border-color: var(--accent);
}
.mood-chip .mood-icon {
  margin-right: 6px;
  font-size: 14px;
  line-height: 1;
  display: inline-block;
}
/* Genres / Moods pivot above the chip strip (#87). Uses the same pill
   shape as the segmented controls in .filter-bar but lives in its own row
   to make the toggle feel deliberate. */
.browse-pivot {
  display: inline-flex;
  gap: 4px;
  padding: 3px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  margin-bottom: 12px;
}
.browse-pivot-btn {
  padding: 6px 14px;
  background: transparent;
  border: none;
  border-radius: 999px;
  font-size: 13px;
  color: var(--text-dim);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.browse-pivot-btn:hover { color: var(--text); }
.browse-pivot-btn.active {
  background: var(--accent);
  color: white;
}

/* Annual goals progress strip (#86). Sits above the For You toolbar; one
   row per non-zero goal in the current category. Compact — designed not
   to push the swipe deck below the fold on mobile. The fill is `--accent`
   when on-pace or ahead, `--warning` when behind. .hit adds a soft glow
   for the celebrating-good-job state. The strip itself is JS-built so the
   number of rows tracks state.goals dynamically. */
.goals-strip {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 14px;
}
.goal-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--surface, rgba(255, 255, 255, 0.04));
  border: 1px solid var(--border);
}
.goal-row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  color: var(--text);
  line-height: 1.3;
}
.goal-row-head strong {
  font-weight: 700;
}
.goal-row-pace {
  color: var(--text-dim);
  font-size: 12px;
  white-space: nowrap;
}
.goal-row-pace.behind {
  color: var(--warning, #f59e0b);
}
.goal-row-pace.hit {
  color: var(--accent);
  font-weight: 600;
}
.goal-bar {
  position: relative;
  height: 6px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.08);
}
.goal-bar-fill {
  position: absolute;
  inset: 0;
  width: 0%;
  background: var(--accent);
  border-radius: inherit;
  transition: width 240ms ease-out;
}
.goal-row.behind .goal-bar-fill {
  background: var(--warning, #f59e0b);
}
.goal-row.hit {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 0 24px -6px var(--accent);
}
.goal-row.hit .goal-bar-fill {
  background: var(--accent);
}
@media (max-width: 720px) {
  .goals-strip { gap: 8px; margin-bottom: 10px; }
  .goal-row { padding: 8px 10px; }
  .goal-row-head { font-size: 12px; }
  .goal-row-pace { font-size: 11px; }
}

/* Yearly goals settings (#86) — labelled rows with right-aligned number
   inputs. The .movies-only / .books-only / .games-only modifiers reuse
   the same per-category visibility rules used elsewhere in the settings
   modal so a Watch user doesn't see a Books goal input. */
.goals-grid {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.goals-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 14px;
}
.goals-row-label {
  color: var(--text);
}
.goals-row input[type="number"] {
  width: 96px;
  height: 36px;
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface, transparent);
  color: var(--text);
  font-size: 14px;
  text-align: right;
}
.goals-help {
  margin-top: 12px;
  color: var(--text-dim);
  font-size: 12px;
}

/* Unified tab toolbar (#271) — single-row pattern shared by Discover,
   For You, Watchlist, and Watched. Leftmost slot hosts the sub-mode
   segmented or search bar; the rest of the row holds the + Filter pill,
   inline Sort, and any tab-specific action button (Pick-for-me, +New
   list). Replaces the previous mix of .discover-toolbar / .filter-bar /
   .watchlist-mode-row / .watched-view-toggle as separate row containers.
   Inline-sort labels/selects use the shared .inline-sort-label /
   .inline-sort-select classes so chrome (height, padding, font-size)
   matches across every tab that has a sort. */
.tab-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.tab-toolbar .search-bar {
  flex: 1 1 280px;
  margin-bottom: 0;
  min-width: 200px;
}
/* Push pure-action buttons (Pick-for-me, +New list) to the right edge so
   the row reads `[sub-mode] … [+Filter] [Sort] | [action]`. Only one of
   these should be visible at a time per tab/mode. */
.tab-toolbar .pick-inline,
.tab-toolbar .lists-create-btn {
  margin-left: auto;
}
.tab-toolbar .inline-sort-label {
  font-size: 13px;
  color: var(--text-dim);
}
.tab-toolbar .inline-sort-select,
.tab-toolbar select {
  padding: 0 12px;
  height: 34px;
  font-size: 14px;
}
@media (max-width: 720px) {
  /* On mobile inline sort moves into the filter sheet (mirrored to the
     per-tab Sort dropdown inside that sheet) so the strip doesn't need a
     second row of controls. */
  .tab-toolbar .inline-sort-label,
  .tab-toolbar .inline-sort-select {
    display: none;
  }
  .tab-toolbar { gap: 8px; margin-bottom: 12px; }
  .tab-toolbar .search-bar { flex-basis: 100%; }
}

/* Active-filter chips row (#205, #227, #271) — chips-only after #271 hoisted
   the + Filter pill into .tab-toolbar. Each chip dismisses one filter. JS
   sets [hidden] when no chips render so the row claims no vertical space
   when filters are inactive. .filter-pill-wrap below keeps the first-launch
   hint anchored above the pill in the toolbar. */
.active-filter-chips-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 20px;
}
.filter-pill-wrap {
  position: relative;
  display: inline-flex;
}

/* Mode-gated visibility on Watchlist (Items vs Lists) and Watched (Items
   vs Stats) so the unified toolbar shows the right controls per sub-mode
   without re-rendering. setWatchlistMode toggles data-mode on
   #watchlistPanel; #watchedPanel already carries data-view. */
#watchlistPanel:not([data-mode="items"]) .items-mode-only { display: none !important; }
#watchlistPanel:not([data-mode="lists"]) .lists-mode-only { display: none !important; }
#watchedPanel:not([data-view="items"]) .watched-items-only { display: none !important; }

/* Backwards-compatible aliases so legacy usage of .discover-toolbar still
   gets the same flex behavior without duplicating rules. */
.discover-toolbar { /* shape comes from .tab-toolbar */ }

/* Active-filter chip row + + Filter pill (#205). Lives just below the
   toolbar in idle/browse mode. Each chip dismisses one filter (×); the
   pill at the end opens the bottom sheet. The container is flex-wrap so
   on desktop more chips fit inline before truncation, and on mobile they
   wrap to additional rows naturally. */
.discover-filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 20px;
  position: relative;
}
.active-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 6px 6px 12px;
  background: var(--accent);
  color: white;
  border: 1px solid var(--accent);
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  cursor: default;
}
.active-filter-chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px; height: 20px;
  border-radius: 999px;
  background: rgba(255,255,255,0.18);
  color: inherit;
  font-size: 12px;
  cursor: pointer;
  border: 0;
  padding: 0;
  transition: background 0.12s;
}
.active-filter-chip-remove:hover { background: rgba(255,255,255,0.32); }
.filter-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  background: var(--surface-2);
  border: 1px dashed var(--border);
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.filter-pill:hover {
  border-color: var(--accent);
  color: var(--text);
  background: color-mix(in srgb, var(--accent) 8%, var(--surface-2));
}
.filter-pill svg { flex-shrink: 0; }

/* First-launch hint that floats above the pill on first idle render. Fades
   after the pill is tapped or after 2 page visits (logic in src/main.js).
   prefers-reduced-motion strips the fade. */
.filter-pill-hint {
  position: absolute;
  top: -34px;
  left: 0;
  background: var(--accent);
  color: white;
  padding: 5px 10px;
  border-radius: 8px;
  font-size: 12px;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 4px 12px rgba(0,0,0,0.4);
  animation: filterHintBob 2.4s ease-in-out infinite;
  max-width: calc(100vw - 48px);
  overflow: hidden;
  text-overflow: ellipsis;
}
.filter-pill-hint::after {
  content: '';
  position: absolute;
  bottom: -5px;
  left: 18px;
  width: 0; height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 5px solid var(--accent);
}
@keyframes filterHintBob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
@media (prefers-reduced-motion: reduce) {
  .filter-pill-hint { animation: none; }
}

/* Discover filter sheet body (#205). Reuses .is-bottom-sheet primitive on
   mobile (≤720px); on desktop it falls back to the centered-dialog layout
   shared with the other modals. */
.discover-filter-sheet {
  max-width: 520px;
}
.discover-filter-sheet-scroll {
  overflow-y: auto;
  padding: 24px 24px 16px;
  flex: 1 1 auto;
}
.filter-sheet-title {
  font-size: 22px;
  font-weight: 700;
  margin: 0 0 4px;
}
.filter-sheet-subtitle {
  margin: 0 0 20px;
  font-size: 13px;
  color: var(--text-dim);
}
.filter-sheet-section {
  margin-bottom: 20px;
}
.filter-sheet-section h3 {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin: 0 0 10px;
}
.filter-sheet-section .segmented,
.filter-sheet-section .browse-pivot {
  display: inline-flex;
}
.filter-sheet-section select {
  padding: 8px 12px;
  font-size: 14px;
  width: 100%;
  max-width: 280px;
}
.filter-sheet-meta {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--text-muted);
  min-height: 1em;
}
/* The chip grid now lives inside the sheet — keep its own margin-bottom
   collapsed since the sheet section already provides spacing. */
.filter-sheet-section .genre-chips {
  margin-bottom: 0;
  max-height: none;
}
.filter-sheet-footer {
  display: flex;
  gap: 10px;
  padding: 12px 24px 20px;
  border-top: 1px solid var(--border);
  background: var(--surface);
  flex-shrink: 0;
}
.filter-sheet-footer button {
  flex: 1 1 0;
  padding: 12px 16px;
  font-size: 14px;
  font-weight: 600;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.filter-sheet-footer button:hover {
  border-color: var(--accent);
}
.filter-sheet-footer button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
}
.filter-sheet-footer button.primary:hover {
  background: var(--accent-hover);
}
.discover-filter-sheet { display: flex; flex-direction: column; padding: 0; overflow: hidden; }

/* Mobile-only sheet sections (e.g. sort moves into sheet on mobile only). */
.filter-sheet-section.mobile-only-section { display: none; }
@media (max-width: 720px) {
  .filter-sheet-section.mobile-only-section { display: block; }
}

/* When the discover panel is in idle (browse) mode the sort section inside
   the sheet is irrelevant. JS mirrors the panel mode onto the sheet via
   data-discover-mode so we can hide irrelevant sections cleanly. */
#discoverFilterSheet[data-discover-mode="idle"] .searching-only {
  display: none !important;
}

/* Watchlist + Watched chips rows — chips-only after #271 hoisted their
   + Filter pills into .tab-toolbar. Geometry is shared with
   .active-filter-chips-row above; these aliases let the legacy IDs keep
   working. The desktop sort hide-on-mobile rule moved into the global
   .tab-toolbar block (.inline-sort-select hides ≤720px) so we don't
   need per-id rules anymore. */
.watchlist-filter-row,
.watched-filter-row {
  /* shape comes from .active-filter-chips-row */
}

/* Filter sheet sections that only apply to Watch (movies + TV) — Year, Rating,
   Runtime, Provider, My rating, Year watched, in-sheet Type & Genre (#239).
   Scoped to .filter-sheet-section so we don't collide with Settings's
   .movies-only (the streaming-subs picker, which deliberately stays visible
   in Read so the user can manage subs without category-switching). */
body.cat-books .filter-sheet-section.movies-only,
body.cat-games .filter-sheet-section.movies-only {
  display: none !important;
}
/* Watched filter row + pill — visible in all three categories now. The
   sheet body shows the matching subset via the .movies-only / .books-only
   / .games-only rules. (Pre-#241 it was hidden in Play because Play had
   no Watched filter dimensions; #241 added Genre / Platform / Year /
   My-rating sections so the row is unhidden everywhere.) */

/* Year / rating / runtime / provider / my-rating / year-watched chips reuse
   the .genre-chips visual treatment so the sheet feels coherent across
   sections — same shape, same active state. The trailing modifier classes
   are kept on the markup so JS can target each grid for re-render without
   walking the DOM. */
.filter-sheet-section .year-chips,
.filter-sheet-section .rating-chips,
.filter-sheet-section .runtime-chips,
.filter-sheet-section .provider-chips,
.filter-sheet-section .my-rating-chips,
.filter-sheet-section .year-watched-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* "My ★" chips on Watched (#239) — visually distinct from the TMDB rating
   threshold above so the two are unambiguous. Active state borrows the
   accent fill but with a subtle inner ring to read as "your rating". */
.my-rating-chips .genre-chip.active {
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.32);
}

.liked-chips {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-bottom: 24px;
}
.liked-chip {
  padding: 5px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 12px;
  color: var(--text-dim);
}

/* Clickable provider chips — deep links */
a.provider-chip {
  text-decoration: none;
  color: var(--text);
  cursor: pointer;
  transition: transform 0.1s, border-color 0.15s, background 0.15s;
}
a.provider-chip:hover {
  transform: translateY(-1px);
  border-color: var(--accent);
}
a.provider-chip.subscribed:hover { border-color: var(--success); }
a.provider-chip.vpn:hover { border-color: var(--vpn); }
a.provider-chip .provider-chip-arrow {
  margin-left: 4px;
  color: var(--text-muted);
  font-size: 11px;
  opacity: 0.7;
  transition: opacity 0.15s, transform 0.15s;
}
a.provider-chip:hover .provider-chip-arrow { opacity: 1; transform: translateX(2px); }

/* VPN reliability indicator */
.rel-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  vertical-align: middle;
  margin: 0 2px;
}
.rel-dot.high { background: var(--success); box-shadow: 0 0 0 2px rgba(74,222,128,0.18); }
.rel-dot.medium { background: var(--warning); box-shadow: 0 0 0 2px rgba(251,191,36,0.18); }
.rel-dot.low { background: #ef4444; box-shadow: 0 0 0 2px rgba(239,68,68,0.18); }
.poster-badge .rel-dot { width: 6px; height: 6px; box-shadow: none; margin-left: 4px; }
.h3-tag .rel-dot { margin-left: 4px; vertical-align: 1px; }
.provider-chip .vpn-regions {
  font-size: 13px;
  letter-spacing: 1px;
  color: var(--text-dim);
  margin-left: 2px;
}

/* Available abroad — per-provider rows. Stacks providers vertically so a
   100%-coverage Netflix doesn't blow out into a multi-line wall of flags
   next to a <80% provider. Issue #218. */
.vpn-providers-list { flex-direction: column; align-items: stretch; gap: 8px; }
.vpn-provider-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
}
.vpn-provider-row > .provider-chip { flex: 0 1 auto; min-width: 0; }
.provider-chip .vpn-summary-text {
  font-size: 13px;
  color: var(--text-dim);
  margin-left: 2px;
  white-space: normal;
}
.vpn-show-toggle {
  background: transparent;
  border: 0;
  color: var(--accent);
  font-size: 12px;
  padding: 4px 8px;
  cursor: pointer;
  border-radius: 6px;
  white-space: nowrap;
}
.vpn-show-toggle:hover { background: var(--surface-2); }
.vpn-flags-expand {
  flex-basis: 100%;
  font-size: 13px;
  letter-spacing: 1px;
  color: var(--text-dim);
  padding: 0 4px 4px 40px;
}

/* Availability banner */
.alerts-banner {
  background: linear-gradient(135deg, rgba(74, 222, 128, 0.18), rgba(74, 222, 128, 0.04));
  border: 1px solid var(--success);
  border-radius: 12px;
  margin: 18px 24px 0;
  padding: 16px 20px;
  display: flex; gap: 16px; align-items: flex-start;
  max-width: 1352px; margin-left: auto; margin-right: auto;
}
.alerts-banner-icon { flex-shrink: 0; line-height: 1; color: var(--accent); }
.alerts-banner-icon svg { width: 28px; height: 28px; }
.alerts-banner-content { flex: 1; }
.alerts-banner-title {
  font-weight: 600; margin-bottom: 6px;
  color: var(--success); font-size: 15px;
}
.alerts-banner-list {
  font-size: 13px;
  color: var(--text);
  display: flex; flex-direction: column; gap: 4px;
}
.alerts-banner-row {
  display: flex; align-items: center; gap: 8px;
  cursor: pointer;
  padding: 4px 8px;
  margin: -4px -8px;
  border-radius: 6px;
  transition: background 0.15s;
  /* Element is now a <button> (was a <div>) for keyboard activation —
     reset the default UA button styling so it looks identical to the
     prior row. */
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  background: transparent;
  border: 0;
}
.alerts-banner-row:hover { background: rgba(255,255,255,0.04); }
.alerts-banner-row strong { font-weight: 600; }
.alerts-banner-row .pill {
  font-size: 11px; font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--success);
  color: var(--on-accent);
}
.alerts-banner-dismiss {
  width: 30px; height: 30px;
  border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-dim);
  flex-shrink: 0;
  font-size: 16px;
}
.alerts-banner-dismiss:hover { background: rgba(255,255,255,0.05); color: var(--text); }

/* Upcoming-releases calendar (#90) — agenda-style sectioned list of
   watchlist items by release date, accessed via the "📅 Upcoming"
   toggle above the watchlist filter bar. Reuses .pill / .segmented
   patterns; no new icons. */
.watchlist-view-row {
  margin-bottom: 14px;
  display: flex;
}
.watchlist-view-toggle button {
  font-size: 13px;
  padding: 6px 14px;
}
.upcoming-section {
  margin-bottom: 28px;
}
.upcoming-section-heading {
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin: 0 0 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}
.upcoming-section.upcoming-this-week .upcoming-section-heading {
  color: var(--accent);
  border-bottom-color: rgba(255, 77, 109, 0.35);
}
.upcoming-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.upcoming-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  cursor: pointer;
  text-align: left;
  width: 100%;
  font: inherit;
  color: inherit;
  transition: background 0.15s, border-color 0.15s, transform 0.1s;
}
.upcoming-row:hover {
  background: var(--surface-2);
  border-color: var(--accent);
}
.upcoming-row:active { transform: scale(0.997); }
.upcoming-row-poster {
  width: 44px;
  height: 66px;
  flex-shrink: 0;
  border-radius: 6px;
  overflow: hidden;
  background: var(--surface-2);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
  font-size: 18px;
}
.upcoming-row-poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.upcoming-row-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.upcoming-row-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.upcoming-row-meta {
  font-size: 12px;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.upcoming-row-meta .pill {
  font-size: 10px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-dim);
  border: 1px solid var(--border);
}
.upcoming-countdown {
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 600;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text);
  white-space: nowrap;
}
.upcoming-countdown.is-today {
  background: var(--accent);
  color: white;
}
.upcoming-countdown.is-tomorrow {
  background: rgba(255, 77, 109, 0.18);
  color: var(--accent);
  border: 1px solid rgba(255, 77, 109, 0.4);
}
.upcoming-countdown.is-soon {
  background: rgba(56, 189, 248, 0.14);
  color: #38BDF8;
  border: 1px solid rgba(56, 189, 248, 0.35);
}
.upcoming-empty {
  text-align: center;
  color: var(--text-dim);
  padding: 36px 16px;
  font-size: 14px;
}
@media (max-width: 600px) {
  .upcoming-row { gap: 10px; padding: 8px 10px; }
  .upcoming-row-poster { width: 38px; height: 57px; }
  .upcoming-row-title { font-size: 14px; }
  .upcoming-countdown { font-size: 11px; padding: 5px 10px; }
}

/* ===== Onboarding wizard (#193) =======================================
   First-launch 4-step guided flow. Replaces the old in-Settings welcome
   banner. Centered modal on desktop, full-screen on mobile. The user can
   only exit via Skip All (top-right) or completing step 4 — backdrop
   clicks and Esc are intentionally ignored. Steps 2 + 3 reuse the
   existing region/language/provider pickers wired into dedicated wizard
   slots so changes flow into state via the same code paths Settings uses. */
.wizard-modal {
  max-width: 560px;
  width: 100%;
  padding: 28px 32px 24px;
  position: relative;
}
.wizard-skip-all {
  position: absolute;
  top: 12px;
  right: 14px;
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font-size: 12px;
  padding: 6px 8px;
  cursor: pointer;
  border-radius: 6px;
}
.wizard-skip-all:hover { color: var(--text); background: var(--surface-2); }
.wizard-progress {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin: 4px 0 22px;
}
.wizard-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--surface-3, var(--border));
  transition: background 0.15s, transform 0.15s;
}
.wizard-dot.done { background: var(--accent); opacity: 0.55; }
.wizard-dot.active {
  background: var(--accent);
  transform: scale(1.4);
}
.wizard-step[hidden] { display: none; }
.wizard-step h2 {
  font-size: 22px;
  font-weight: 600;
  margin: 0 0 8px;
  color: var(--text);
  text-align: center;
}
.wizard-step .wizard-tagline {
  text-align: center;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--text-dim);
  margin: 0 0 24px;
}
.wizard-brand {
  text-align: center;
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--accent);
  margin: 22px 0 10px;
}
.wizard-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}
.wizard-field label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.wizard-field select,
.wizard-field input[type="email"] {
  padding: 10px 12px;
  font-size: 14px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
}
.wizard-providers-help {
  font-size: 13px;
  color: var(--text-dim);
  margin: 0 0 10px;
}
.wizard-providers {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  max-height: 320px;
  overflow-y: auto;
  padding: 4px 0 4px;
}
.wizard-providers .provider-pill { margin: 0; }
.wizard-providers-loading,
.wizard-providers-empty {
  font-size: 13px;
  color: var(--text-dim);
  padding: 18px 0;
  text-align: center;
}
.wizard-signin-status {
  font-size: 13px;
  margin: 8px 0 0;
  min-height: 1.4em;
}
.wizard-signin-status.err { color: #f87171; }
.wizard-signin-status.ok { color: #34d399; }
.wizard-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
}
.wizard-actions [data-wizard-back] {
  margin-right: auto;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 14px;
  cursor: pointer;
}
.wizard-actions [data-wizard-skip] {
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font-size: 13px;
  cursor: pointer;
  padding: 8px 10px;
}
.wizard-actions [data-wizard-skip]:hover { color: var(--text); }
.wizard-actions .primary {
  background: var(--accent);
  color: #fff;
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 10px 18px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s;
}
.wizard-actions .primary:hover { background: var(--accent-hover); }
.wizard-actions .primary:disabled { opacity: 0.55; cursor: not-allowed; }
.wizard-step-welcome .wizard-actions {
  border-top: 0;
  justify-content: center;
}
.wizard-step-welcome .wizard-actions .primary { padding: 12px 32px; font-size: 15px; }
@media (prefers-reduced-motion: no-preference) {
  .wizard-step:not([hidden]) {
    animation: wizardFadeIn 180ms ease-out;
  }
  @keyframes wizardFadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}
@media (max-width: 720px) {
  .wizard-modal {
    max-width: 100%;
    width: 100%;
    height: 100vh;
    max-height: 100vh;
    border-radius: 0;
    padding: 24px 20px 20px;
  }
  /* Cancel the centering so the modal really fills the viewport on mobile. */
  .modal-overlay#onboardingWizard { padding: 0; }
}

/* ===== Settings IA structure (#192) ====================================
   The 12 flat sections are wrapped into 5 navigable groups (Preferences /
   Account / Notifications / Data / About). Mobile (≤720px) shows a
   top-level list of group entries and drills into one at a time, driven
   by `data-active-group` on the wrapper. Desktop (>720px) renders the
   same nav as a sticky left sidebar with the active group on the right.
   Hash routing in main.js (#settings/<group>) keeps URL + back-button in
   sync. */
.settings-groups { display: block; }
.settings-group-nav {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 12px;
}
.settings-group-nav button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 14px 16px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.settings-group-nav button:hover {
  background: var(--surface-3);
  border-color: var(--accent);
}
.settings-group-nav-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.settings-group-nav-title {
  font-size: 15px;
  font-weight: 600;
}
.settings-group-nav-desc {
  font-size: 12px;
  color: var(--text-dim);
}
.settings-group-nav-chevron {
  flex-shrink: 0;
  color: var(--text-dim);
}
.settings-group-content { display: block; }
.settings-group { display: none; }
.settings-groups[data-active-group="preferences"] .settings-group[data-group="preferences"],
.settings-groups[data-active-group="account"] .settings-group[data-group="account"],
.settings-groups[data-active-group="notifications"] .settings-group[data-group="notifications"],
.settings-groups[data-active-group="data"] .settings-group[data-group="data"],
.settings-groups[data-active-group="about"] .settings-group[data-group="about"] {
  display: block;
}
/* Mobile top-level (data-active-group=""): nav visible, content hidden */
.settings-groups[data-active-group=""] .settings-group-content { display: none; }
.settings-groups:not([data-active-group=""]) .settings-group-nav { display: none; }

.settings-group-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.settings-group-back {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px 6px 6px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  cursor: pointer;
  transition: background 0.15s;
}
.settings-group-back:hover { background: var(--surface-2); }
.settings-group-title {
  font-size: 18px;
  font-weight: 700;
  margin: 0;
}

/* Advanced disclosure (TMDB key, #192 IA audit). Hosted app uses a
   server-side proxy key, so this surface is legacy/self-hoster only —
   collapsed by default keeps it out of the default Preferences view. */
details.settings-section-advanced {
  border-top: 1px solid var(--border);
  padding-top: 14px;
  margin-top: 24px;
}
details.settings-section-advanced > summary {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-dim);
  cursor: pointer;
  padding: 6px 0;
  list-style: revert;
  user-select: none;
}
details.settings-section-advanced > summary:hover { color: var(--text); }
details.settings-section-advanced[open] > summary { color: var(--text); margin-bottom: 6px; }
details.settings-section-advanced .settings-section.api-section {
  border-top: none;
  padding-top: 8px;
  margin-top: 0;
}
/* #215: Games data source picker — sits below the TMDB key inside the
   Advanced disclosure. Stacks two radio rows with a hairline border between
   them, matching the rest of the Settings panel's quiet density. */
.games-backend-section { margin-top: 12px; }
.games-backend-options {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}
.games-backend-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  cursor: pointer;
  font-size: 14px;
  color: var(--text);
}
.games-backend-option:hover { background: var(--surface-2); }
.games-backend-option input[type="radio"] { accent-color: var(--accent); }

/* Desktop layout (>720px): 200px sidebar + content panel. */
@media (min-width: 721px) {
  .settings-groups {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 24px;
    align-items: start;
  }
  /* Both nav and content always visible on desktop, regardless of active group.
     Selector specificity must match the mobile-drill rule
     `.settings-groups:not([data-active-group=""]) .settings-group-nav { display: none }`
     (0,3,0) — otherwise that rule wins on desktop too and the sidebar
     collapses to zero width inside the grid (#269). `[data-active-group]`
     matches whenever the attribute is present, which is always (set in
     HTML and re-set by setSettingsActiveGroup).
     Sticky pins the sidebar to the top of `.settings-modal-scroll` while the
     right content panel scrolls (#279) — standard desktop settings UX.
     `align-self: start` so the grid row doesn't stretch the sidebar to match
     the (much taller) content column, which would leave nothing for sticky
     to do. No nested scroll on the sidebar itself, preserving the
     one-scroll-axis invariant from #225. */
  .settings-groups[data-active-group] .settings-group-nav {
    display: flex;
    position: sticky;
    top: 0;
    align-self: start;
  }
  .settings-groups[data-active-group=""] .settings-group-content {
    display: block;
  }
  /* Default-empty data-active-group on desktop falls back to Preferences,
     so users without JS (or before hash sync runs) still see content. */
  .settings-groups[data-active-group=""] .settings-group[data-group="preferences"] {
    display: block;
  }
  .settings-groups[data-active-group=""] .settings-group-nav button[data-target="preferences"] {
    background: var(--surface-2);
    border-color: var(--border);
    color: var(--accent);
  }
  /* Sidebar entries: compact, transparent until active/hover */
  .settings-group-nav { gap: 2px; margin-bottom: 0; }
  .settings-group-nav button {
    padding: 10px 12px;
    border-radius: 8px;
    background: transparent;
    border: 1px solid transparent;
  }
  .settings-group-nav button:hover {
    background: var(--surface-2);
    border-color: var(--border);
  }
  .settings-group-nav-desc,
  .settings-group-nav-chevron { display: none; }
  .settings-group-nav-title { font-size: 14px; font-weight: 500; }
  /* Active sidebar entry */
  .settings-groups[data-active-group="preferences"] .settings-group-nav button[data-target="preferences"],
  .settings-groups[data-active-group="account"] .settings-group-nav button[data-target="account"],
  .settings-groups[data-active-group="notifications"] .settings-group-nav button[data-target="notifications"],
  .settings-groups[data-active-group="data"] .settings-group-nav button[data-target="data"],
  .settings-groups[data-active-group="about"] .settings-group-nav button[data-target="about"] {
    background: var(--surface-2);
    border-color: var(--border);
    color: var(--accent);
  }
  /* Sidebar handles navigation on desktop — drop the per-group back button
     and the duplicate group title (the section h3s already title each card). */
  .settings-group-header { display: none; }
}

/* Platform picker: grouped by console family (PlayStation, Xbox, …) so the
   ~30 specific platforms read as a tidy list rather than a wall of pills. */
.platform-group + .platform-group { margin-top: 12px; }
.platform-group-heading {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 6px;
}

/* Game modal: horizontal-scrolling screenshots strip */
.game-screenshots {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-bottom: 4px;
}
.game-screenshots::-webkit-scrollbar { display: none; }
.game-screenshots img {
  flex: 0 0 auto;
  height: 180px;
  width: auto;
  border-radius: 8px;
  background: var(--surface-2);
  object-fit: cover;
}
@media (max-width: 600px) {
  .game-screenshots img { height: 130px; border-radius: 6px; }
}

/* Book / game placeholders when no cover is available */
.movie-card[data-type="book"] .movie-poster.no-image::before {
  content: '📖';
}
.movie-card[data-type="game"] .movie-poster.no-image::before {
  content: '🎮';
}

/* Procedural book-cover placeholder (#146). Used in every book-card
   surface (grid, swipe deck, modal hero, cold-start quiz) when no
   cover image URL can be resolved through any of the OL fallback keys.
   Background gradient is computed in JS from a hash of the book id so
   the same book always gets the same color across reloads.
   Container-query sizing keeps typography legible at every poster
   width — grid thumbnail through modal hero. */
.book-placeholder {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
  padding: 12% 9%;
  color: #F5F2EA;
  font-family: Georgia, 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', serif;
  /* Inner double-ring evokes a printed cover; opaque enough to read on
     every gradient in the palette without dominating it. */
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.10),
    inset 0 0 0 4px rgba(0, 0, 0, 0.14);
  container-type: inline-size;
  overflow: hidden;
}
.book-placeholder-title {
  font-size: clamp(11px, 9cqw, 26px);
  font-weight: 600;
  line-height: 1.18;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-bottom: 0.5em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.28);
  word-break: break-word;
  hyphens: auto;
}
.book-placeholder-author {
  font-size: clamp(9px, 5.5cqw, 15px);
  font-weight: 400;
  line-height: 1.3;
  opacity: 0.82;
  font-style: italic;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}

.modal-poster.book-no-cover {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
}
.modal-poster.book-no-cover::after {
  content: '📖';
  font-size: 72px;
  opacity: 0.28;
}
body.cat-games .modal-poster.book-no-cover::after { content: '🎮'; }

/* In Read and Play modes, hide streaming/movie-specific filters that don't
   apply to books or games. The For You mode toggle (Grid/Swipe) STAYS visible
   — swipe works for books and games too. */
body.cat-books .type-toggle,
body.cat-books #discoverType,
body.cat-books .filter-bar #discoverFilter,
body.cat-books .filter-bar > .meta,
body.cat-games .type-toggle,
body.cat-games #discoverType,
body.cat-games .filter-bar #discoverFilter,
body.cat-games .filter-bar > .meta {
  display: none !important;
}
/* watchlistFilter is also hidden in books (books have no availability concept)
   but in Play mode it stays visible — repurposed as the "On my platforms"
   filter against the user's owned-platforms list in Settings. */
body.cat-books #watchlistFilter { display: none !important; }
/* In books, the Availability section is moot — hide the section header +
   segmented. The + Filter pill stays visible (#240) because the sheet now
   carries Subjects/Year/Pages/Author for the Read category. */
body.cat-books .availability-section { display: none !important; }

/* `.books-only` filter sections appear inside the shared filter sheets
   (#discoverFilterSheet, #watchlistFilterSheet) but should only render
   when the Read category is active. The companion `.movies-only` /
   `.games-only` rules above already handle the inverse (Watch- and Play-
   only sections). #240. */
body:not(.cat-books) .books-only { display: none !important; }

/* Watched-side + Filter pill — books-only in #240, extended to movies in
   #239, and to games in #241. The row's visibility is now unconditional;
   the sheet body shows the right subset of sections via the
   .movies-only / .books-only / .games-only class rules. */
/* The +travel button is meaningless for games (no travel-region concept) */
body.cat-games #watchlistFilter button[data-filter="any"] { display: none !important; }

/* Settings sections that only apply to specific categories. .movies-only is
   the streaming-subs picker — useless in Play mode. .games-only is the new
   owned-platforms picker — only relevant in Play mode. */
body.cat-games .movies-only,
body:not(.cat-games) .games-only {
  display: none !important;
}

/* Discover panel mode driver (#181). The panel root carries data-mode="idle"
   when the search input is empty (browse view) or "searching" when it has a
   query. Use ":is" so a control flagged both .idle-only and .games-only still
   collapses correctly when either rule fires. */
#discoverPanel[data-mode="idle"] .searching-only,
#discoverPanel[data-mode="searching"] .idle-only {
  display: none !important;
}

/* Danger zone */
.danger-section h3 { color: #f87171; }

.app-version {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-dim);
  margin-left: 4px;
}

.update-banner {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--accent);
  padding: 10px 14px;
  border-radius: 10px;
  z-index: 320;
  font-size: 14px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
  max-width: calc(100vw - 32px);
}
.update-banner > span { flex: 1 1 auto; min-width: 0; }
.update-banner-btn {
  padding: 6px 12px;
  background: var(--accent);
  color: #fff;
  border: 0;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}
.update-banner-btn:hover { background: var(--accent-hover); }
.update-banner-close {
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
  flex-shrink: 0;
}
.update-banner-close:hover { color: inherit; }
.danger-btn {
  padding: 10px 20px;
  background: rgba(239, 68, 68, 0.12);
  border: 1px solid rgba(239, 68, 68, 0.4);
  color: #ef4444;
  border-radius: 8px;
  font-weight: 500;
  font-size: 14px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.danger-btn:hover {
  background: rgba(239, 68, 68, 0.22);
  border-color: #ef4444;
}

.account-section .api-row input[type="email"] {
  flex: 1;
  font-family: inherit;
  font-size: 14px;
}
.account-info {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
}
.account-info-label {
  font-size: 12px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.account-info-email {
  font-size: 15px;
  font-weight: 600;
  margin-top: 2px;
  word-break: break-all;
}
.account-info-meta {
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 4px;
}
#accountSignOutBtn {
  padding: 8px 14px;
  background: var(--surface-3);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  font-weight: 500;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
#accountSignOutBtn:hover {
  background: var(--surface);
  border-color: #ef4444;
  color: #ef4444;
}

.backup-actions {
  display: flex; gap: 8px; flex-wrap: wrap;
}
.backup-actions button {
  padding: 10px 18px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  font-weight: 500;
  font-size: 14px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.backup-actions button:hover {
  background: var(--surface-3);
  border-color: var(--accent);
}

/* Account: delete-button row sits below the signed-in info block */
.account-delete-row {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--border);
}

/* Profile: signed-out hint sits in place of the form so the section is
   discoverable even before the user signs in. */
.profile-signin-hint {
  font-size: 13px;
  color: var(--text-dim);
  background: var(--surface-2);
  border: 1px dashed var(--border);
  border-radius: 8px;
  padding: 12px 14px;
}

.profile-form {
  display: flex; flex-direction: column; gap: 14px;
}
.profile-row {
  display: flex; flex-direction: column; gap: 6px;
}
.profile-row label {
  font-size: 12px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.profile-row input[type="text"],
.profile-row textarea,
.profile-row select {
  width: 100%;
  padding: 9px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
}
.profile-row textarea {
  min-height: 72px;
  resize: vertical;
}
.profile-row input:focus,
.profile-row textarea:focus,
.profile-row select:focus {
  outline: none; border-color: var(--accent);
}
.profile-username-row {
  display: flex; align-items: stretch; gap: 0;
}
.profile-username-prefix {
  display: inline-flex; align-items: center;
  padding: 0 10px;
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-right: 0;
  border-radius: 8px 0 0 8px;
  color: var(--text-dim);
  font-size: 13px;
}
.profile-username-row input {
  border-radius: 0 8px 8px 0 !important;
  flex: 1;
}
.profile-field-status {
  font-size: 12px;
  min-height: 14px;
}
.profile-field-status.ok { color: #22c55e; }
.profile-field-status.err { color: #f87171; }
.profile-field-status.pending { color: var(--text-dim); }

.profile-bio-counter {
  font-size: 11px;
  color: var(--text-dim);
  text-align: right;
}

.profile-avatar-row {
  display: flex; align-items: center; gap: 14px;
}
.profile-avatar-preview {
  width: 64px; height: 64px;
  border-radius: 50%;
  background: var(--surface-3);
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  color: var(--text-dim);
  font-size: 22px; font-weight: 600;
  flex-shrink: 0;
}
.profile-avatar-preview img {
  width: 100%; height: 100%; object-fit: cover;
}
.profile-avatar-actions {
  display: flex; gap: 8px; flex-wrap: wrap;
}
.profile-avatar-actions button {
  padding: 8px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  font-size: 13px; font-weight: 500;
  cursor: pointer;
}
.profile-avatar-actions button:hover {
  background: var(--surface-3);
  border-color: var(--accent);
}

.profile-actions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 4px;
}
#profileSaveBtn {
  padding: 9px 16px;
  background: var(--accent);
  border: 1px solid var(--accent);
  color: #fff;
  border-radius: 8px;
  font-weight: 500;
  font-size: 14px;
  cursor: pointer;
}
#profileSaveBtn:disabled {
  opacity: 0.55; cursor: not-allowed;
}
.profile-save-status {
  font-size: 12px;
}
.profile-save-status.ok { color: #22c55e; }
.profile-save-status.err { color: #f87171; }

.profile-public-lists {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px 10px;
  margin: 0;
}
.profile-public-lists legend {
  font-size: 12px; font-weight: 600;
  color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em;
  padding: 0 6px;
}
.profile-public-lists-hint {
  margin: 0 0 8px;
  font-size: 12px; color: var(--text-muted);
}
.profile-toggle {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 0;
  font-size: 13.5px; color: var(--text);
  cursor: pointer;
}
.profile-public-lists.disabled .profile-toggle {
  color: var(--text-dim); cursor: not-allowed;
}
.profile-toggle input[type="checkbox"] {
  width: 16px; height: 16px; cursor: inherit;
}

/* Friends 29a — "Add friend" modal */
.add-friend-modal { max-width: 520px; padding: 24px 28px 20px; }
.add-friend-modal h2 { margin: 0 0 14px; font-size: 20px; }
.add-friend-modal #friendSearchInput {
  width: 100%;
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  margin-bottom: 8px;
}
.add-friend-modal #friendSearchInput:focus {
  outline: none; border-color: var(--accent);
}
.friend-search-status {
  font-size: 12px; color: var(--text-dim);
  min-height: 16px;
  margin-bottom: 8px;
}
.friend-search-status.err { color: #f87171; }
.friend-search-status.pending { color: var(--text-dim); }
.friend-search-results {
  list-style: none; margin: 0; padding: 0;
  max-height: 320px; overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
}
.friend-search-results li {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}
.friend-search-results li:last-child { border-bottom: 0; }
.friend-result-avatar {
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--surface-3); background-size: cover; background-position: center;
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; font-weight: 600; color: var(--text-dim);
  flex-shrink: 0;
}
.friend-result-meta { flex: 1; min-width: 0; }
.friend-result-name {
  font-size: 14px; font-weight: 500; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.friend-result-handle {
  font-size: 12px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.friend-result-action {
  padding: 7px 12px;
  background: var(--accent);
  color: #fff;
  border: 1px solid var(--accent);
  border-radius: 8px;
  font-size: 13px; font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
}
.friend-result-action:hover:not(:disabled) { background: var(--accent-hover); }
.friend-result-action:disabled {
  background: var(--surface-3); border-color: var(--border);
  color: var(--text-dim); cursor: default;
}
.friend-result-action.sent {
  background: var(--surface-3); border-color: var(--border); color: #22c55e;
}

/* Friends 29b (#105) — pending-requests inbox in Settings + banner rows. */
.friends-subhead {
  font-size: 13px; font-weight: 600; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: 0.04em;
  margin: 0 0 6px;
}
.friend-inbox-list {
  list-style: none; margin: 8px 0 12px; padding: 0;
  border: 1px solid var(--border); border-radius: 8px;
}
.friend-inbox-list li {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}
.friend-inbox-list li:last-child { border-bottom: 0; }
.friend-inbox-actions { display: flex; gap: 6px; flex-shrink: 0; }
.friend-inbox-action {
  padding: 6px 10px;
  border-radius: 8px;
  font-size: 13px; font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border);
}
.friend-inbox-action.accept {
  background: var(--accent); color: #fff; border-color: var(--accent);
}
.friend-inbox-action.accept:hover:not(:disabled) { background: var(--accent-hover); }
.friend-inbox-action.decline {
  background: transparent; color: var(--text);
}
.friend-inbox-action.decline:hover:not(:disabled) { background: var(--surface-3); }
.friend-inbox-action:disabled { opacity: 0.6; cursor: default; }
.friend-inbox-empty { font-size: 13px; color: var(--text-dim); margin: 6px 0 12px; }

.friend-banner-list { list-style: none; margin: 4px 0 0; padding: 0; }
.friend-banner-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 6px 0;
  border-top: 1px solid rgba(255,255,255,0.06);
}
.friend-banner-row:first-child { border-top: 0; padding-top: 2px; }
.friend-banner-text { flex: 1 1 auto; min-width: 0; font-size: 13px; }
.friend-banner-action {
  padding: 5px 10px;
  border-radius: 8px;
  font-size: 12px; font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border);
  flex-shrink: 0;
}
.friend-banner-action.accept {
  background: var(--accent); color: #fff; border-color: var(--accent);
}
.friend-banner-action.accept:hover:not(:disabled) { background: var(--accent-hover); }
.friend-banner-action.decline {
  background: transparent; color: var(--text);
}
.friend-banner-action.decline:hover:not(:disabled) { background: rgba(255,255,255,0.05); }
.friend-banner-action:disabled { opacity: 0.6; cursor: default; }

/* Friends 29c (#106) — header icon + dedicated Friends modal. Lives next to
   the avatar slot in the topbar (signed-in only) so the social graph has a
   visible, one-tap entry point instead of being buried in Settings. */
.header-friends-btn {
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-dim);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  border: 2px solid var(--border);
  box-sizing: border-box;
  margin-right: 6px;
  padding: 0;
  transition: transform 0.12s, border-color 0.12s, color 0.12s, background 0.12s;
}
.header-friends-btn:hover {
  transform: scale(1.05);
  border-color: var(--accent);
  color: var(--accent);
}
.header-friends-btn[hidden] { display: none; }
@media (max-width: 600px) {
  .header-friends-btn { width: 28px; height: 28px; margin-right: 4px; }
  .header-friends-btn svg { width: 18px; height: 18px; }
}
@media (max-width: 380px) {
  .header-friends-btn { width: 26px; height: 26px; border-width: 1.5px; }
  .header-friends-btn svg { width: 16px; height: 16px; }
}

/* Friends list modal — list of confirmed friends with View profile + kebab. */
.friends-modal { max-width: 540px; padding: 22px 24px 18px; }
.friends-modal-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 14px;
}
.friends-modal-header h2 { margin: 0; font-size: 20px; }
.friends-modal-add {
  padding: 7px 12px;
  background: var(--accent); color: #fff;
  border: 1px solid var(--accent);
  border-radius: 8px;
  font-size: 13px; font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
}
.friends-modal-add:hover { background: var(--accent-hover); }
.friends-modal-status {
  font-size: 13px; color: var(--text-dim);
  margin: 4px 0 8px;
}
.friends-modal-status.err { color: #f87171; }
.friends-list {
  list-style: none; margin: 0; padding: 0;
  max-height: 60vh; overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
}
.friends-list li {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  position: relative;
}
.friends-list li:last-child { border-bottom: 0; }
.friend-row-link {
  display: flex; align-items: center; gap: 12px;
  flex: 1; min-width: 0;
  text-decoration: none; color: inherit;
  cursor: pointer;
}
.friend-row-link:hover .friend-result-name { color: var(--accent); }
.friend-row-arrow {
  color: var(--text-dim);
  flex-shrink: 0;
  transition: transform 0.12s, color 0.12s;
}
.friend-row-link:hover .friend-row-arrow {
  color: var(--accent);
  transform: translateX(2px);
}
.friend-row-kebab-wrap { position: relative; flex-shrink: 0; }
.friend-row-kebab {
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--text-dim);
  cursor: pointer;
}
.friend-row-kebab:hover, .friend-row-kebab[aria-expanded="true"] {
  background: var(--surface-3);
  color: var(--text);
}
.friend-row-menu {
  position: absolute;
  top: 100%; right: 0;
  margin-top: 4px;
  min-width: 140px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.28);
  z-index: 10;
  padding: 4px;
}
.friend-row-menu[hidden] { display: none; }
.friend-row-menu-item {
  display: block;
  width: 100%;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  color: var(--text);
  font-size: 13px;
  text-align: left;
  border-radius: 6px;
  cursor: pointer;
}
.friend-row-menu-item:hover { background: var(--surface-3); }
.friend-row-menu-item.danger { color: #f87171; }
.friends-empty {
  padding: 28px 16px;
  text-align: center;
  color: var(--text-dim);
  border: 1px dashed var(--border);
  border-radius: 10px;
  background: var(--surface-2);
}
.friends-empty-title {
  display: block;
  color: var(--text); font-weight: 600; font-size: 15px;
  margin-bottom: 4px;
}
.friends-empty-cta {
  margin-top: 12px;
  padding: 8px 14px;
  background: var(--accent); color: #fff;
  border: 1px solid var(--accent);
  border-radius: 8px;
  font-size: 13px; font-weight: 500;
  cursor: pointer;
}
.friends-empty-cta:hover { background: var(--accent-hover); }

/* Unfriend confirm modal — small, two-button. */
.unfriend-confirm-modal { max-width: 420px; padding: 22px 24px; }
.unfriend-confirm-modal h2 { margin: 0 0 10px; font-size: 18px; }
.unfriend-confirm-modal p { margin: 0 0 14px; font-size: 14px; line-height: 1.5; color: var(--text); }

/* Public profile modal (route: /u/<username>) — 28b */
.profile-modal { max-width: 640px; padding: 28px 32px 24px; }
.profile-header {
  display: flex; align-items: center; gap: 16px; margin-bottom: 18px;
}
.profile-avatar {
  width: 88px; height: 88px; border-radius: 50%;
  flex-shrink: 0;
  background: var(--surface-2);
  background-size: cover; background-position: center;
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  font-size: 36px; font-weight: 600;
  color: var(--text-dim);
  overflow: hidden;
}
.profile-avatar.profile-avatar-placeholder {
  background-image: none;
}
.profile-name-block { min-width: 0; flex: 1; }
.profile-display-name {
  font-size: 22px; font-weight: 600; color: var(--text);
  margin: 0 0 2px;
  word-break: break-word;
}
.profile-handle {
  font-size: 13px; color: var(--text-dim);
  word-break: break-all;
}
.profile-bio {
  margin: 14px 0 4px;
  font-size: 14px; line-height: 1.55; color: var(--text);
  white-space: pre-wrap; word-wrap: break-word;
}
.profile-bio-empty {
  margin: 14px 0 4px;
  font-size: 13px; color: var(--text-muted); font-style: italic;
}
.profile-lists-placeholder {
  margin-top: 22px; padding: 18px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--text-dim);
  font-size: 13px;
  text-align: center;
}
.profile-lists-placeholder strong {
  display: block; color: var(--text); font-weight: 600; font-size: 14px; margin-bottom: 4px;
}
.profile-lists { margin-top: 22px; display: flex; flex-direction: column; gap: 22px; }
.profile-list-section h3 {
  margin: 0 0 10px;
  font-size: 14px; font-weight: 600;
  color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em;
}
.profile-list-cat { margin-bottom: 14px; }
.profile-list-cat:last-child { margin-bottom: 0; }
.profile-list-cat-label {
  font-size: 12px; font-weight: 600;
  color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em;
  margin: 0 0 6px;
}
.profile-list-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 10px;
}
.profile-list-card {
  display: flex; flex-direction: column;
  border-radius: 8px; overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--border);
  text-decoration: none; color: var(--text);
  transition: transform 120ms ease, border-color 120ms ease;
}
.profile-list-card:hover, .profile-list-card:focus-visible {
  transform: translateY(-2px);
  border-color: var(--accent);
  outline: none;
}
.profile-list-cover {
  width: 100%; aspect-ratio: 2 / 3;
  background-color: var(--surface-3, var(--surface-2));
  background-size: cover; background-position: center;
}
.profile-list-cat[data-cat="games"] .profile-list-cover { aspect-ratio: 16 / 9; }
.profile-list-card .profile-list-title {
  padding: 6px 8px;
  font-size: 11.5px; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.profile-state {
  text-align: center; padding: 32px 12px;
}
.profile-state h2 {
  font-size: 18px; font-weight: 600; margin: 0 0 6px; color: var(--text);
}
.profile-state p {
  font-size: 13.5px; color: var(--text-dim); margin: 0;
}
.profile-loading {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 40px 0;
  color: var(--text-dim); font-size: 13.5px;
}
.profile-modal-footer {
  margin-top: 24px;
  display: flex; justify-content: flex-end;
}

/* Privacy policy modal */
.privacy-modal { max-width: 720px; padding: 28px 32px 24px; }
.privacy-modal h2 { margin: 0 0 4px; font-size: 22px; font-weight: 600; }
.privacy-modal .privacy-updated {
  font-size: 12px; color: var(--text-dim); margin: 0 0 20px;
}
.privacy-modal h3 {
  margin: 22px 0 6px; font-size: 15px; font-weight: 600; color: var(--text);
}
.privacy-modal p, .privacy-modal li {
  font-size: 13.5px; line-height: 1.55; color: var(--text);
}
.privacy-modal p { margin: 6px 0; }
.privacy-modal a { color: var(--accent); }
.privacy-modal-footer {
  margin-top: 24px;
  display: flex; justify-content: flex-end; align-items: center;
  gap: 12px;
}
.privacy-modal-report-link { margin-right: auto; }

.privacy-link {
  display: inline-block;
  margin-top: 6px;
  color: var(--accent);
  font-size: 12px;
  background: none; border: 0; padding: 0;
  cursor: pointer;
  text-decoration: underline;
}
.privacy-link:hover { color: var(--accent-hover); }
.about-section .report-bug-link { margin-left: 14px; }

/* Delete-account confirm modal */
.delete-account-modal { max-width: 460px; padding: 24px 28px; }
.delete-account-modal h2 {
  margin: 0 0 8px; font-size: 18px; font-weight: 600; color: #f87171;
}
.delete-account-modal p {
  margin: 6px 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--text);
}
.delete-account-modal p.delete-account-export-hint {
  color: var(--text-dim); font-size: 12.5px;
}
.delete-account-modal .email-input {
  width: 100%;
  padding: 9px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
}
.delete-account-modal .email-input:focus {
  outline: none; border-color: var(--accent);
}
.delete-account-modal .modal-actions {
  display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px;
}
.delete-account-modal .modal-actions button {
  padding: 9px 16px;
  border-radius: 8px;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
}
.delete-account-modal .modal-actions button:hover { background: var(--surface-3); }
.delete-account-modal .modal-actions button.danger-btn {
  background: rgba(239, 68, 68, 0.12);
  border-color: rgba(239, 68, 68, 0.4);
  color: #ef4444;
}
.delete-account-modal .modal-actions button.danger-btn:hover {
  background: rgba(239, 68, 68, 0.22);
  border-color: #ef4444;
}
.delete-account-modal .modal-actions button:disabled {
  opacity: 0.5; cursor: not-allowed;
}
/* Reuse delete-account-modal layout for the list create + delete modals
   (#174). They share the same shape (title + body + textfield/inline + actions),
   so applying the class keeps the visual language consistent without forking. */
.delete-account-modal input[type="text"] {
  width: 100%;
  padding: 9px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  box-sizing: border-box;
}
.delete-account-modal input[type="text"]:focus {
  outline: none; border-color: var(--accent);
}
.delete-account-modal .modal-actions button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
}
.delete-account-modal .modal-actions button.primary:hover {
  background: var(--accent-hover);
}
/* Override the destructive-red title coloring for non-destructive modals
   (Create list reuses the layout but isn't a danger surface). */
#listCreateModal .delete-account-modal h2 { color: var(--text); }

/* Per-item "Add to list" picker (#175). Compact modal with a scrollable
   stack of checkbox rows, an inline "+ New list" expander at the bottom,
   and a single Done action. Reuses tokens from delete-account-modal so the
   picker matches the rest of the lists surface (#174). */
.list-picker-modal {
  max-width: 460px;
  padding: 22px 26px 18px;
}
.list-picker-modal h2 {
  margin: 0 0 4px;
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
}
.list-picker-subtitle {
  margin: 0 0 14px;
  font-size: 12.5px;
  color: var(--text-dim);
  /* Single-line item title — long titles ellipsize rather than reflowing the
     header into 3 lines. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.list-picker-subtitle:empty { display: none; margin: 0; }
.list-picker-empty {
  margin: 0 0 12px;
  padding: 14px 16px;
  background: var(--surface-2);
  border: 1px dashed var(--border);
  border-radius: 10px;
  font-size: 13px;
  color: var(--text-dim);
  text-align: center;
}
.list-picker-rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 50vh;
  overflow-y: auto;
  margin: 0 -6px;
  padding: 0 6px;
}
.list-picker-rows:empty { display: none; }
.list-picker-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.list-picker-row:hover { background: var(--surface-3); border-color: var(--accent); }
.list-picker-row.checked { border-color: var(--accent); }
.list-picker-row input[type="checkbox"] {
  width: 18px; height: 18px;
  accent-color: var(--accent);
  cursor: pointer;
  flex-shrink: 0;
}
.list-picker-row-name {
  flex: 1; min-width: 0;
  font-size: 14px;
  color: var(--text);
  word-break: break-word;
}
.list-picker-row-count {
  font-size: 12px;
  color: var(--text-dim);
  flex-shrink: 0;
}
.list-picker-create {
  margin-top: 10px;
}
.list-picker-create-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 12px;
  background: transparent;
  border: 1px dashed var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.list-picker-create-toggle:hover {
  background: var(--surface-2);
  border-color: var(--accent);
  color: var(--accent);
}
.list-picker-create-toggle svg { stroke: currentColor; }
.list-picker-create-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.list-picker-create-form input[type="text"] {
  width: 100%;
  padding: 9px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  box-sizing: border-box;
}
.list-picker-create-form input[type="text"]:focus {
  outline: none; border-color: var(--accent);
}
.list-picker-create-form-actions {
  display: flex; gap: 8px; justify-content: flex-end;
}
.list-picker-create-form-actions button {
  padding: 8px 14px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
.list-picker-create-form-actions button:hover { background: var(--surface-3); }
.list-picker-create-form-actions button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
}
.list-picker-create-form-actions button.primary:hover { background: var(--accent-hover); }
.list-picker-create-form-actions button:disabled {
  opacity: 0.5; cursor: not-allowed;
}
.list-picker-modal .modal-actions {
  display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px;
}
.list-picker-modal .modal-actions button {
  padding: 9px 18px;
  border-radius: 8px;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: white;
}
.list-picker-modal .modal-actions button:hover { background: var(--accent-hover); }

/* Per-item "Added to N lists" hint (#175). Small chip rendered in the modal
   meta line so users see at a glance that the item is on one of their lists.
   Uses the bookmark accent so it visually links to the picker affordance. */
.modal-lists-hint {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 12px;
  color: var(--accent);
  /* Click-through to the picker — handled by data-act="add-to-list" wiring. */
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
}
.modal-lists-hint:hover { text-decoration: underline; }

/* #315 removed the top-left .swipe-card-list-btn ("+ list" bookmark
   shortcut from #175); the slot is now used by the .provider-cluster.
   top-left from #311. Custom-list picker is still reachable via the
   detail modal `i` button. */

@media (max-width: 720px) {
  .list-picker-modal { padding: 20px 18px 16px; }
  .list-picker-row { padding: 11px 12px; }
  .list-picker-row input[type="checkbox"] { width: 20px; height: 20px; }
  .list-picker-rows { max-height: 55vh; }
}

/* Watchlist mode sub-segment (#260, hoisted into .tab-toolbar in #271).
   The toggle still uses .watchlist-mode-toggle so JS can target it; the
   wrapping .watchlist-mode-row was retired with the unified toolbar
   refactor. */
.watchlist-mode-toggle { display: inline-flex; }

/* Custom lists (#174). The Lists tab renders a stack of cards, one per
   list, plus a "+ New list" toolbar button above. Cards reuse the
   surface palette of `.movie-card` / settings sections (border + soft bg)
   without re-implementing the poster/grid layout — these are list-of-lists
   rows, not item cards. The standalone .lists-toolbar wrapper retired in
   #271 — the +New list button now lives directly in the unified
   .tab-toolbar above the panel. */
.lists-create-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px;
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 8px;
  color: white;
  font-size: 13.5px; font-weight: 500;
  cursor: pointer;
  transition: background 0.15s;
}
.lists-create-btn:hover { background: var(--accent-hover); }
.lists-create-btn svg { stroke: white; }
.lists-grid { display: flex; flex-direction: column; gap: 10px; }
.list-card {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px 14px;
  align-items: center;
  padding: 14px 16px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  transition: background 0.15s, border-color 0.15s;
}
.list-card:hover { background: var(--surface-3); border-color: var(--accent); }
.list-card-title {
  font-size: 15px; font-weight: 600; color: var(--text);
  cursor: text;
  padding: 4px 6px;
  margin: -4px -6px;
  border-radius: 6px;
  border: 1px solid transparent;
  min-width: 0;
  word-break: break-word;
}
.list-card-title:hover { background: var(--surface-3); border-color: var(--border); }
.list-card-title-input {
  font: inherit; color: inherit;
  background: var(--surface-3);
  border: 1px solid var(--accent);
  border-radius: 6px;
  padding: 4px 6px;
  margin: -4px -6px;
  width: 100%;
  box-sizing: border-box;
  outline: none;
}
.list-card-meta {
  grid-column: 1 / 2;
  font-size: 12.5px; color: var(--text-dim);
  display: flex; gap: 10px; flex-wrap: wrap;
}
.list-card-meta span + span::before {
  content: '·'; margin-right: 10px; color: var(--text-dim);
}
.list-card-actions {
  grid-row: 1 / span 2;
  grid-column: 2 / 3;
  display: inline-flex; gap: 4px; align-self: start;
}
.list-card-action-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--text-dim);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.list-card-action-btn:hover {
  background: var(--surface-3);
  color: var(--text);
  border-color: var(--border);
}
.list-card-action-btn.danger:hover {
  color: #ef4444;
  border-color: rgba(239, 68, 68, 0.4);
  background: rgba(239, 68, 68, 0.08);
}
@media (max-width: 720px) {
  .lists-create-btn { padding: 9px 14px; font-size: 14px; }
  .list-card { padding: 12px 14px; gap: 6px 10px; }
  .list-card-title { font-size: 14.5px; }
  .list-card-meta { font-size: 12px; gap: 8px; }
  .list-card-action-btn { width: 36px; height: 36px; }
}

/* List detail view (#176). Sits inside #watchlistListsView alongside the
   list-of-lists container; renderListDetail toggles which is visible. The
   header is a 2-row stack: Back button on top, then a baseline-aligned row
   with the (rename-able) list title and the item count. The grid below
   reuses the global .movie-grid layout so cards match Watchlist density. */
.list-detail-header {
  display: flex; flex-direction: column; gap: 8px;
  margin: 4px 0 14px;
}
.list-detail-back {
  display: inline-flex; align-items: center; gap: 4px;
  align-self: flex-start;
  background: transparent;
  border: 1px solid transparent;
  padding: 6px 10px 6px 6px;
  margin-left: -6px;
  border-radius: 8px;
  color: var(--text-dim);
  font-size: 13.5px; font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.list-detail-back:hover, .list-detail-back:focus-visible {
  background: var(--surface-3);
  color: var(--text);
  border-color: var(--border);
}
.list-detail-title-row {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
}
.list-detail-title {
  background: transparent;
  border: 1px solid transparent;
  padding: 4px 8px;
  margin: -4px -8px;
  border-radius: 8px;
  color: var(--text);
  font-size: 22px; font-weight: 700;
  cursor: text;
  text-align: left;
  min-width: 0;
  word-break: break-word;
  transition: background 0.15s, border-color 0.15s;
}
.list-detail-title:hover, .list-detail-title:focus-visible {
  background: var(--surface-2);
  border-color: var(--border);
}
.list-detail-title-input {
  font: 700 22px/1.2 inherit;
  color: var(--text);
  background: var(--surface-3);
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 4px 8px;
  margin: -4px -8px;
  outline: none;
  min-width: 200px;
  max-width: 100%;
}
.list-detail-count {
  font-size: 13px; color: var(--text-dim);
}
@media (max-width: 720px) {
  .list-detail-title { font-size: 19px; }
  .list-detail-title-input { font-size: 19px; }
  .list-detail-header { margin: 0 0 10px; }
}

/* Remove-from-list overlay (#176). Mirrors the watchlist/watched
   .poster-quick-action treatment but uses an x-mark icon and a danger
   tint so the action reads as destructive at a glance. The .remove
   modifier overrides the default green hover with red. */
.poster-quick-action.remove {
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
}
.poster-quick-action.remove:hover, .poster-quick-action.remove:focus-visible {
  background: rgba(239, 68, 68, 0.9);
  color: #fff;
}

/* Inline CTA button on the empty list-detail state. Reuses the .empty-state
   container for icon + title + text but adds a small CTA button below — the
   ticket calls for a "Browse" CTA and the existing emptyState() helper
   doesn't render buttons. */
.empty-state-cta {
  display: inline-block;
  margin-top: 12px;
  padding: 8px 16px;
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 8px;
  font-size: 14px; font-weight: 600;
  cursor: pointer;
  transition: filter 0.15s;
}
.empty-state-cta:hover, .empty-state-cta:focus-visible { filter: brightness(1.1); }

/* Pick-for-me — random watchlist picker (#64).
   Inline button sits in the watchlist filter bar on desktop; the FAB
   replaces it on mobile (bottom-right, above the fixed bottom-tab bar).
   The modal is intentionally narrow + image-forward — the whole point is
   to dramatize a single chosen item, not to look like another grid. */
.pick-inline {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  cursor: pointer;
  font-size: 13.5px; font-weight: 500;
  white-space: nowrap;
}
.pick-inline:hover { background: var(--surface-3); border-color: var(--accent); color: var(--text); }
.pick-inline svg { color: var(--accent); }

.pick-fab {
  position: fixed;
  right: 16px;
  bottom: calc(76px + env(safe-area-inset-bottom));
  width: 56px; height: 56px;
  border-radius: 50%;
  background: var(--accent);
  color: white;
  border: 1px solid var(--accent);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  z-index: 90;
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.12s ease;
}
.pick-fab:hover { filter: brightness(1.08); }
.pick-fab:active { transform: scale(0.94); }
.pick-fab[hidden] { display: none; }
/* FAB only on mobile; desktop uses the inline button instead. */
@media (min-width: 601px) {
  .pick-fab { display: none !important; }
}
/* Inline pick button hides on mobile in favour of the FAB. */
@media (max-width: 600px) {
  .pick-inline { display: none; }
}

.pick-modal {
  max-width: 460px;
  padding: 0;
  overflow: hidden;
}
.pick-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 36px 28px 24px;
}
.pick-poster {
  width: 220px;
  aspect-ratio: 2 / 3;
  border-radius: 14px;
  overflow: hidden;
  background: var(--surface-2);
  position: relative;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
  transition: transform 0.18s ease, filter 0.18s ease;
}
.pick-poster img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pick-poster .pick-poster-placeholder {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-dim);
  font-size: 36px;
}
.pick-stage.rolling .pick-poster {
  animation: pickShake 0.16s ease-in-out infinite alternate;
}
@keyframes pickShake {
  from { transform: translateY(-3px) rotate(-1.6deg) scale(0.97); filter: blur(0.5px) saturate(1.1); }
  to   { transform: translateY(3px)  rotate(1.6deg)  scale(0.97); filter: blur(0.5px) saturate(1.1); }
}
.pick-stage.landed .pick-poster {
  animation: pickLand 0.5s cubic-bezier(0.22, 1.2, 0.36, 1) 1;
}
@keyframes pickLand {
  0%   { transform: scale(0.9); }
  60%  { transform: scale(1.05); }
  100% { transform: scale(1); }
}
.pick-title {
  margin: 20px 0 4px;
  font-size: 22px; font-weight: 700;
  color: var(--text);
  line-height: 1.25;
}
.pick-subtitle {
  font-size: 13px; color: var(--text-muted);
  margin-bottom: 22px;
}
.pick-actions {
  display: flex; gap: 10px;
  width: 100%;
  max-width: 360px;
}
.pick-actions button {
  flex: 1 1 0;
  padding: 12px 14px;
  border-radius: 10px;
  font-size: 14.5px; font-weight: 600;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
}
.pick-actions button:hover { background: var(--surface-3); }
.pick-actions .pick-watch-it {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
}
.pick-actions .pick-watch-it:hover { filter: brightness(1.08); background: var(--accent); }
.pick-actions button svg { width: 16px; height: 16px; }

.pick-empty {
  padding: 44px 28px 32px;
  text-align: center;
}
.pick-empty .empty-state-icon { margin-bottom: 10px; color: var(--text-dim); }
.pick-empty .empty-state-icon svg { width: 48px; height: 48px; }
.pick-empty-title { font-size: 17px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
.pick-empty-text { color: var(--text-muted); font-size: 14px; }

@media (prefers-reduced-motion: reduce) {
  .pick-stage.rolling .pick-poster,
  .pick-stage.landed .pick-poster {
    animation: none !important;
    transform: none !important;
    filter: none !important;
  }
}

@media (max-width: 480px) {
  .pick-modal { max-width: 100%; }
  .pick-stage { padding: 28px 20px 20px; }
  .pick-poster { width: 180px; }
  .pick-title { font-size: 19px; }
}

/* Settings toggle */
.toggle-row {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 0;
}
.toggle-row label { flex: 1; font-size: 13px; color: var(--text); margin: 0; }
.toggle-row label span { display: block; font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.toggle {
  position: relative;
  width: 40px; height: 22px;
  background: var(--surface-3);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s;
  flex-shrink: 0;
}
.toggle::after {
  content: '';
  position: absolute;
  top: 2px; left: 2px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--text);
  transition: transform 0.15s;
}
.toggle.on { background: var(--accent); }
.toggle.on::after { transform: translateX(18px); }

@media (max-width: 600px) {
  /* Belt-and-braces zoom prevention on mobile: the viewport meta blocks
     pinch-zoom; this 16px minimum on form controls blocks iOS Safari's
     auto-zoom on input focus (which fires whenever the focused field is
     under 16px). */
  input, select, textarea { font-size: 16px; }

  /* The `padding:` shorthand re-asserts all four sides, so we have to
     re-apply the safe-area-inset-top trick from the base `header` rule
     above (#283) — otherwise iOS Home Screen PWA loses the inset and the
     status bar overlaps the wordmark again (#293). */
  header {
    padding: 10px 10px;
    padding-top: calc(10px + env(safe-area-inset-top));
    gap: 6px;
  }
  main { padding: 14px; }

  /* Mobile region picker: collapse to a flag-only chip (#162). The native
     <select> sits opacity:0 over the flag span — taps still open the
     OS-rendered dropdown which keeps full "🇺🇸 United States" labels. */
  .region-picker {
    height: 30px;
    padding: 0;
    width: 38px;
    min-width: 38px;
    justify-content: center;
  }
  .region-picker .region-globe { display: none; }
  .region-flag-display {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    font-size: 16px;
  }
  .region-picker select {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    padding: 0;
    cursor: pointer;
  }
  /* Category toggle stays compact in the header on mobile, doesn't stretch.
     min-height (not height) so the touch-target override below can grow the
     wrapper instead of having buttons overflow it. The standalone selectors
     guard the (now-unused) bare-segmented case; the .brand-selector variant
     overrides the inner-pill sizing inside the unified container. */
  .segmented.category-toggle { width: auto; min-height: 30px; }
  .segmented.category-toggle button { flex: initial; padding: 0 8px; font-size: 11px; }
  .brand-selector { height: 34px; padding: 2px 3px 2px 12px; }
  .brand-selector .logo { padding-right: 8px; }
  .brand-selector .segmented.category-toggle button { height: 26px; padding: 0 8px; font-size: 11px; }
  /* Profile button shrinks slightly so the row fits without overflow. */
  .header-avatar { width: 28px; height: 28px; }
  .header-avatar-icon { width: 18px; height: 18px; }
}

@media (max-width: 480px) {
  /* iPhone-class viewports (≤390pt) with longer Romance-language category
     labels ("Guardare / Leggere / Giocare" in Italian, "Regarder / Lire /
     Jouer" in French) push the header past 100vw at the ≤600px values. Pack
     the row tighter so all three tabs stay visible — see issue #118.
     padding-top longhand preserves the safe-area inset (#283/#293) which
     the `padding:` shorthand would otherwise reset to 8px. */
  header {
    padding: 8px 10px;
    padding-top: calc(8px + env(safe-area-inset-top));
    gap: 6px;
  }
  .wordmark { font-size: 19px; }
  .region-picker { height: 28px; width: 34px; min-width: 34px; gap: 0; }
  .region-flag-display { font-size: 15px; }
  .segmented.category-toggle { min-height: 28px; padding: 1px; }
  .segmented.category-toggle button { padding: 0 6px; font-size: 10.5px; }
  /* Brand selector at iPhone-class widths — swap full localized pill labels
     ("Watch" / "Guardare" / "Regarder") for SVG icons so the wordmark stays
     visible alongside the right-side cluster (Region · Avatar). */
  .brand-selector { height: 30px; padding: 2px 2px 2px 10px; gap: 4px; border-radius: 10px; }
  .brand-selector .logo { padding-right: 7px; }
  .brand-selector .segmented.category-toggle { gap: 1px; }
  .brand-selector .segmented.category-toggle button {
    height: 24px;
    padding: 0;
    min-width: 28px;
    justify-content: center;
  }
  .cat-pill-full { display: none; }
  .cat-pill-short { display: inline-flex; align-items: center; }
  /* iPhone-class viewports: shrink the single profile button so the row
     fits in 100vw alongside the long Romance-language category labels. */
  .header-avatar { width: 26px; height: 26px; border-width: 1.5px; }
  .header-avatar-icon { width: 16px; height: 16px; }

  /* Update banner: full-width with edge insets, sat above the bottom
     tabs bar (which is position:fixed at bottom on mobile). */
  .update-banner {
    left: 12px;
    right: 12px;
    bottom: calc(64px + env(safe-area-inset-bottom));
    transform: none;
    max-width: none;
    font-size: 13px;
    gap: 8px;
    padding: 10px 12px;
  }
  .update-banner-btn { padding: 8px 14px; font-size: 13px; }
  .update-banner-close { font-size: 22px; padding: 0 6px; }

  /* Tabs: native mobile pattern — fixed bar at the bottom of the viewport.
     Even though the markup keeps them as a child of #topbar, position:fixed
     pulls them to the viewport edge. Active state uses color (no underline)
     since a border-bottom at the screen edge looks awkward.
     Background is fully opaque + theme-aware (#223): iOS Safari can drop
     backdrop-filter mid-scroll, and the previous hard-coded rgba(20,20,31,0.96)
     was only a partial backdrop AND only matched the Watch-dark theme — so on
     Read/Play/Light Watch the nav also rendered the wrong tint. */
  .tabs {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    background: var(--bg);
    border-top: 1px solid var(--border);
    border-bottom: none;
    padding: 4px 4px;
    padding-bottom: max(6px, env(safe-area-inset-bottom));
    z-index: 100;
    overflow: hidden;
    gap: 0;
    margin: 0;
  }
  .tab {
    flex: 1 1 0;
    min-width: 0;
    flex-direction: column;
    padding: 8px 2px 6px;
    gap: 3px;
    font-size: 11px;
    line-height: 1.2;
    border-bottom: none;
    margin-bottom: 0;
  }
  .tab.active { border-bottom: none; }
  svg.tab-icon { width: 24px; height: 24px; }
  .tab-label {
    font-size: 10.5px;
    gap: 4px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
  }
  /* On mobile, the count is a small floating badge anchored to the tab icon
     instead of taking horizontal space alongside the label. This frees up
     room for longer labels like "Reading list" without truncation. */
  .tab { position: relative; }
  .tab-count {
    position: absolute;
    top: 4px;
    left: calc(50% + 8px);
    background: var(--accent);
    color: white;
    font-size: 9px;
    font-weight: 700;
    padding: 0 4px;
    border-radius: 999px;
    line-height: 14px;
    height: 14px;
    min-width: 14px;
    text-align: center;
    box-sizing: border-box;
  }
  .tab.active .tab-count { background: white; color: var(--accent); }
  /* Reserve room at the bottom of scrollable content so the tab bar
     doesn't cover the last cards / actions. */
  main {
    padding-bottom: calc(80px + env(safe-area-inset-bottom));
  }
  /* Alerts banner sits below header — give it a little less side margin */
  .alerts-banner { margin: 12px 14px 0; }

  .modal-body {
    grid-template-columns: 110px 1fr;
    gap: 14px;
    padding: 16px;
    margin-top: -70px;
    align-items: start;
  }
  .modal-poster {
    width: 110px;
    margin: 0;
    grid-column: 1;
    grid-row: 1 / span 2;
  }
  .modal-info h2 {
    font-size: 18px;
    line-height: 1.25;
    margin-bottom: 6px;
    text-align: left;
  }
  .modal-meta {
    font-size: 12px;
    margin-bottom: 0;
    gap: 4px;
    line-height: 1.4;
    align-self: start;
  }
  .modal-overview {
    font-size: 13px;
    line-height: 1.55;
    margin-top: 14px;
    margin-bottom: 8px;
    grid-column: 1 / -1;
  }
  .modal-overview.clamp {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .read-more-toggle {
    background: none;
    border: none;
    color: var(--accent);
    font-size: 13px;
    font-weight: 600;
    padding: 2px 0 8px;
    cursor: pointer;
    grid-column: 1 / -1;
  }
  .modal-actions {
    grid-column: 1 / -1;
    flex-wrap: nowrap;
    gap: 6px;
    margin-top: 12px;
  }
  .modal-actions button {
    flex: 1 1 0;
    min-width: 0;
    padding: 11px 6px;
    font-size: 12px;
    font-weight: 600;
    justify-content: center;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* The info column needs to allow .modal-overview/.read-more-toggle/.modal-actions
     to span the full row — flatten the info container into the body grid */
  .modal-info {
    display: contents;
  }
  .modal-info h2,
  .modal-meta {
    grid-column: 2;
  }
  .modal-backdrop { height: 200px; }
  .movie-grid {
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 14px;
  }
  .logo-text { display: none; }
  /* Tighten the inner scroll padding on phones; modal itself stays padding:0
     so the scroll wrapper can fill the modal and scrolling works properly */
  .settings-modal-scroll { padding: 22px 18px 18px; }

  /* Filter bar layout on mobile: 2-col grid where segmented toggles and
     the sort select are first-class peers. The "Sort" label is hidden
     since the dropdown values are self-explanatory.
     - 1 toggle + sort  → toggle | sort           (one row)
     - 2 toggles + sort → toggle | toggle / sort  (two rows; sort spans)
     - 2 toggles only   → toggle | toggle         (one row)
     All controls share the same 36px height for a unified look. The
     search-bar and tab-bar are also slimmed so the filter strip eats
     less of the viewport. */
  .filter-bar {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-bottom: 14px;
    align-items: stretch;
  }
  .filter-bar > label { display: none; }
  .filter-bar:has(.segmented + .segmented) > select { grid-column: 1 / -1; }
  /* In Read and Play modes the segmented toggles are display:none, so the
     sort select is the only visible control — span it full-width so widths
     match across the watchlist and watched tabs. */
  body.cat-books .filter-bar > select,
  body.cat-games .filter-bar > select { grid-column: 1 / -1; }
  /* Browse in games mode: only #discoverPlatformFilter is visible in the
     filter-bar (siblings are display:none). Span full width so it doesn't
     sit alone in the left column with empty space to the right. */
  body.cat-games #discoverPlatformFilter { grid-column: 1 / -1; }
  .filter-bar > select {
    width: 100%;
    height: 36px;
    padding: 0 32px 0 12px;
  }
  .segmented { width: 100%; padding: 2px; }
  /* For You toolbar packs two segmenteds — [Swipe|Grid] + [All|Movies|TV] —
     and the default `.segmented { width: 100% }` above stacks them onto two
     rows, pushing the swipe action buttons below the viewport on iPhone-class
     widths (#322 follow-up). Override so they share one row, weighted 2:3 by
     button count. */
  #foryouPanel .tab-toolbar .segmented { width: auto; min-width: 0; }
  #foryouPanel .tab-toolbar #foryouMode { flex: 2 1 0; }
  #foryouPanel .tab-toolbar #foryouType { flex: 3 1 0; }
  /* min-height (not height) so the 36px touch-target override below can take
     effect without buttons overflowing — keeps active and unselected pills
     the same height inside the wrapper. */
  .segmented button {
    flex: 1;
    font-size: 12px;
    padding: 0 6px;
    min-height: 32px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
  }
  /* Trim the search bar's margins so the filter strip below it sits
     closer to the content. */
  .search-bar { margin-bottom: 12px; }
  .search-bar input { padding: 11px 14px 11px 40px; }
  .search-icon { left: 12px; }

  /* Movie modal padding tighter */
  .providers-section { padding: 0 16px 20px; }
  .similar-section { padding: 16px 16px 20px; }
  .similar-grid {
    grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
    gap: 10px;
  }

  /* Bigger, more prominent close button on phones — easier to tap */
  .modal-close-floating {
    top: 12px;
    margin: 12px 12px -56px 0;
    width: 44px; height: 44px;
    font-size: 22px;
  }

  /* Genre chips and liked-movie chips: horizontal scroll instead of wrapping
     so they take one row instead of five on small screens */
  .genre-chips,
  .liked-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 6px;
    margin-left: -14px;
    margin-right: -14px;
    padding-left: 14px;
    padding-right: 14px;
  }
  .genre-chips::-webkit-scrollbar,
  .liked-chips::-webkit-scrollbar { display: none; }
  .genre-chip {
    flex-shrink: 0;
    padding: 6px 12px;
    font-size: 12px;
  }
  .liked-chip {
    flex-shrink: 0;
    font-size: 11px;
    padding: 4px 9px;
  }
}

/* ===== Accessibility (WCAG 2.1 AA) =====
   Centralised so a11y wins are easy to find and review. Source order is
   late-on-purpose: focus-visible / reduced-motion overrides need to win
   against the per-component transitions defined above. */

/* Visually-hidden text for screen readers — used by aria-labels we don't
   want sighted users to see (e.g. landmark headings, nav alt-text). */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Skip-to-content link — first tab stop for keyboard users. Hidden until
   focused. WCAG 2.4.1 Bypass Blocks. */
.skip-link {
  position: absolute;
  top: 0; left: 12px;
  z-index: 1000;
  padding: 10px 16px;
  background: var(--accent);
  color: white;
  border-radius: 0 0 8px 8px;
  font-size: 14px;
  font-weight: 600;
  transform: translateY(-110%);
  transition: transform 0.15s;
}
.skip-link:focus {
  transform: translateY(0);
  outline: 3px solid var(--text);
  outline-offset: 2px;
}

/* Visible keyboard focus on every interactive element. We use
   :focus-visible so mouse-clicks don't get a ring, but keyboard
   navigation always does. WCAG 2.4.7. */
button:focus-visible,
a:focus-visible,
[role="button"]:focus-visible,
.tab:focus-visible,
.segmented button:focus-visible,
.swipe-btn:focus-visible,
.modal-close:focus-visible,
.modal-close-floating:focus-visible,
.settings-modal-close:focus-visible,
.movie-card:focus-visible,
.alerts-banner-row:focus-visible,
.rate-star:focus-visible,
.update-banner-btn:focus-visible,
.update-banner-close:focus-visible,
.alerts-banner-dismiss:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Touch-target sizing: enforce 44×44 minimum on devices without a fine
   pointer (phones / tablets). Desktop hover layouts stay unchanged so
   dense filter rows don't reflow. WCAG 2.5.5 (AAA) but recommended for
   AA compliance on mobile. */
@media (hover: none) and (pointer: coarse) {
  .modal-close,
  .modal-close-floating,
  .settings-modal-close,
  .alerts-banner-dismiss,
  .update-banner-close {
    min-width: 44px;
    min-height: 44px;
  }
  .segmented button,
  .tab,
  .rate-star {
    min-height: 36px;
  }
}

/* ===== Personal stats dashboard (#65) — sub-view of the Watched tab.
   The Watched panel carries data-view="items"|"stats"; the rules below
   hide whichever sub-view is inactive. Stats charts use plain divs +
   SVG with theme colors via var(--accent), so the per-category palette
   flows through automatically. The .watched-view-toggle used to be
   width: 100% with flex: 1 buttons, which made it visibly oversized vs.
   every other segmented in the app — #271 dropped that override so
   it inherits the standard inline-flex segmented sizing. */
#watchedPanel[data-view="stats"] .watched-items-view { display: none; }
#watchedPanel[data-view="items"] .watched-stats-view { display: none; }

.watched-stats-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 18px;
}
.watched-stats-toolbar .segmented { flex: 1 1 auto; }
@media (max-width: 520px) {
  .watched-stats-toolbar { flex-direction: column; gap: 8px; }
  .watched-stats-toolbar .segmented { width: 100%; }
}

#watchedStats { display: flex; flex-direction: column; gap: 18px; }

.stat-headline {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 22px 20px;
  text-align: center;
}
.stat-headline-big {
  font-size: 22px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.35;
}
.stat-headline-sub {
  margin-top: 8px;
  font-size: 14px;
  color: var(--text-dim);
}
@media (max-width: 520px) {
  .stat-headline-big { font-size: 18px; }
}

.stat-section {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 18px 18px 16px;
}
.stat-section-title {
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin: 0 0 14px;
}
.stat-section-meta {
  margin: -6px 0 12px;
  font-size: 13px;
  color: var(--text-dim);
}
.stat-section-note {
  margin: 12px 0 0;
  font-size: 11px;
  color: var(--text-muted);
  font-style: italic;
}
.stat-empty-line {
  margin: 0;
  font-size: 13px;
  color: var(--text-muted);
}

/* Stacked horizontal hours bar with a per-type swatch legend underneath.
   Segments are typed (.seg-movie / -tv / -book / -game) so each gets its
   own tonal shade off the active accent — keeping the chart readable
   without diverging from the per-category palette. */
.stat-hours-bar {
  display: flex;
  height: 14px;
  width: 100%;
  border-radius: 7px;
  overflow: hidden;
  background: var(--bg);
  border: 1px solid var(--border);
}
.stat-hours-seg {
  height: 100%;
  transition: width 700ms cubic-bezier(0.2, 0, 0.1, 1);
}
.stat-hours-seg.seg-movie { background: var(--accent); }
.stat-hours-seg.seg-tv    { background: var(--accent); filter: brightness(0.78); }
.stat-hours-seg.seg-book  { background: var(--accent); filter: brightness(1.18) saturate(0.85); }
.stat-hours-seg.seg-game  { background: var(--accent); filter: brightness(0.6) saturate(1.2); }
.stat-hours-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
  margin-top: 12px;
  font-size: 12px;
  color: var(--text-dim);
}
.stat-hours-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.stat-hours-swatch {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  display: inline-block;
}
.stat-hours-swatch.seg-movie { background: var(--accent); }
.stat-hours-swatch.seg-tv    { background: var(--accent); filter: brightness(0.78); }
.stat-hours-swatch.seg-book  { background: var(--accent); filter: brightness(1.18) saturate(0.85); }
.stat-hours-swatch.seg-game  { background: var(--accent); filter: brightness(0.6) saturate(1.2); }
.stat-hours-legend-val { color: var(--text); font-weight: 600; margin-left: 2px; }

/* Horizontal bars (genres + ratings). Track + fill, with a label cell on
   the left and a count cell on the right. Fill animates from 0 width on
   first paint via a CSS keyframe — global @prefers-reduced-motion zeroes
   the transition-duration. */
.stat-bars {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.stat-bar-row {
  display: grid;
  grid-template-columns: minmax(70px, 100px) 1fr 32px;
  align-items: center;
  gap: 10px;
  font-size: 13px;
}
.stat-bar-label {
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.stat-bar-track {
  height: 8px;
  background: var(--bg);
  border-radius: 4px;
  overflow: hidden;
  border: 1px solid var(--border);
}
.stat-bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 4px;
  /* Inline width sets the *final* size; we animate via scaleX so the bar
     appears to grow from the left. transform-origin keeps the growth
     anchored to the start of the track. */
  transform-origin: left center;
  animation: statBarGrow 700ms cubic-bezier(0.2, 0, 0.1, 1) backwards;
}
.stat-bar-value {
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--text-dim);
  text-align: right;
}
.stat-rating-stars {
  letter-spacing: 1px;
  color: var(--accent);
  font-size: 14px;
}
.stat-rating-empty { color: var(--border); }

@keyframes statBarGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* Vertical bars (decades + monthly cadence). The SVG holds only the bars
   and uses preserveAspectRatio="none" so they stretch full-width on any
   viewport. Labels live in a sibling HTML row because that same non-uniform
   stretch was blowing up SVG <text> glyphs by the X-scale ratio (#272). */
.stat-vbar-chart {
  display: block;
}
.stat-vbars {
  width: 100%;
  height: 120px;
  display: block;
  overflow: visible;
}
.stat-vbar rect {
  transform-origin: bottom;
  animation: statVbarGrow 700ms cubic-bezier(0.2, 0, 0.1, 1) backwards;
}
.stat-vbar-labels {
  display: flex;
  margin-top: 6px;
}
.stat-vbar-labels > .stat-vbar-label {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
@keyframes statVbarGrow { from { transform: scaleY(0); } to { transform: scaleY(1); } }

/* Empty state — fewer than ~5 watched items in the current scope. */
.stats-empty {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 32px 24px;
  text-align: center;
}
.stats-empty-headline {
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 10px;
}
.stats-empty-body {
  margin: 0;
  font-size: 14px;
  color: var(--text-dim);
  line-height: 1.5;
}

/* Honour prefers-reduced-motion across the whole app. Strips out any
   transitions / animations / smooth-scroll for users who've asked the OS
   to minimise motion. Skeleton shimmer was already covered above; this
   block catches everything else (modal fades, swipe-deck transforms,
   hover lifts, segmented sliders). WCAG 2.3.3. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .movie-card:hover { transform: none; }
}

/* Bumped --text-muted on each theme so secondary copy (movie-year, byline,
   meta) clears 4.5:1 against the surface. Values were too dim before and
   failed Lighthouse contrast in three places (.movie-year on .movie-card,
   .tab-count, .poster-badge.unavailable). */
:root { --text-muted: #8C8473; }
body.cat-books { --text-muted: #6F5E48; }
body.cat-games { --text-muted: #8B7BBE; }
