/* Genre home: dark streaming-style hero over horizontal poster rows.
   Kept deliberately cheap for webOS — no keyframe animation, no filters, and
   the only transforms are whole-strip translations plus a small focus scale. */

.genre-home {
  position: relative;
  height: 100%;
  overflow: hidden;
  background: var(--bg-primary);
  color: var(--text-primary);
}

/* Hero and rows move as one piece so the hero can retract off the top and
   the rows can take the whole screen (three rails instead of two) once
   focus leaves the first row. The stage carries the row-0 step as well,
   which is why leaving row 0 reads as one glide rather than a step plus a
   slide (see GenreHome.stageOffset). Both this and .gh-rows below animate
   by default; GenreHome adds .gh-holding to drop both to instant jumps the
   moment it detects the D-pad is actually held (repeats arriving faster
   than a human re-presses), so a long hold never falls behind waiting on
   an animation that a single tap still gets. */
.gh-stage {
  position: relative;
  transition: transform 200ms ease-out;
}

.genre-home.gh-holding .gh-stage {
  transition: none;
}

/* The rows start 460px down (HERO_HEIGHT_PX in genre-home.ts, which needs
   it to size the retraction — same arrangement as .gh-row and
   ROW_STRIDE_PX). The backdrop runs 72px further, behind the
   first rail's heading, whose strip is see-through there (.gh-rows): the
   owner found the black band between picture and rail too wide. The 72px
   overlap appears three times (here, .gh-hero-body, .gh-rows); plain
   numbers, not a variable in calc(), for webOS 4's engine. */
.gh-hero {
  position: relative;
  height: 532px;
  background-size: cover;
  background-position: center 20%;
  background-repeat: no-repeat;
  background-color: var(--catalog-surface);
}

.gh-hero-scrim {
  position: absolute;
  /* Not `inset` — Chromium 53 (webOS 4) has no logical properties. */
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  /* Two layers: a left-to-right fade so the text side is always readable
     regardless of what the backdrop shows there, plus a bottom-heavy vertical
     fade so the text block itself always sits over a solidly dark strip
     instead of directly on whatever is in the photo at that height. */
  /* The vertical fade's last stop is fully opaque, not 0.96 — this color is
     exactly --bg-primary (see --catalog-hero-scrim-rgb), the same solid
     color everything below the hero sits on, so ending short of 1.0 left a
     thin sliver of the actual backdrop image peeking through right at the
     hero's bottom edge — a visible hard seam against the genuinely solid
     area immediately after it, not a fade. Five stops, not three: the image
     is still mostly readable through the top third, then darkens gradually
     the rest of the way down, rather than sitting near-flat until a late,
     steep dive to solid right at the edge — the earlier three-stop curve
     read as an abrupt cutoff even with the seam itself fixed. */
  /* The owner: less black between the backdrop and the first rail. The
     picture now carries further down (dark only in the last tenth); the
     text side stays readable through the left-to-right layer. */
  background:
    linear-gradient(180deg,
      rgba(var(--catalog-hero-scrim-rgb-legacy), 0.1) 0%,
      rgba(var(--catalog-hero-scrim-rgb-legacy), 0.2) 50%,
      rgba(var(--catalog-hero-scrim-rgb-legacy), 0.35) 75%,
      rgba(var(--catalog-hero-scrim-rgb-legacy), 0.65) 90%,
      rgba(var(--catalog-hero-scrim-rgb-legacy), 1) 100%),
    linear-gradient(90deg, rgba(var(--catalog-hero-scrim-rgb-legacy), 0.97) 0, rgba(var(--catalog-hero-scrim-rgb-legacy), 0.65) 45%, rgba(var(--catalog-hero-scrim-rgb-legacy), 0.2) 75%);
}

.gh-hero-body {
  position: absolute;
  /* 12px inside the clip below for the focus glow (3px outline, 2px
     offset, on a button scaled 1.03 — main.css): at the old 80/56 without
     padding, Play's glow was cut off on its left and bottom. */
  left: 68px;
  bottom: 100px; /* 28px above the rows, as the backdrop runs 72px under them */
  padding: 12px;
  max-width: 924px;
  /* Hard clip: the content here is sized to fit comfortably above this anchor,
     but a title with an unusually long wrap, or a webOS font-metric quirk this
     stylesheet did not account for, must never be able to spill past the hero
     into the rows below it — this guarantees that regardless of cause. */
  max-height: 420px;
  overflow: hidden;
}
/* The overview unfolded: a wider block reaching up to the hero's top (Play
   stays put, the logo keeps its size — the owner). */
.gh-hero-body.overview-expanded {
  max-width: 1124px;
  max-height: 432px;
}

.gh-hero-title {
  margin: 0 0 12px;
  font-size: calc(64px * var(--font-scale));
  font-weight: 700;
  line-height: 1.05;
}

/* TMDB's own clear-art logo in place of the plain text title, when the
   Popular-row prefetch already has one — see GenreHome.heroLogoUrl. Kept as
   a sibling of .gh-hero-title (hidden via CSS, not swapped node-for-node)
   so a title with no logo, or whose logo 404s (.is-broken), falls straight
   back to text without a render path of its own. */
.gh-hero-title-wrap.has-logo .gh-hero-title { display: none; }
.gh-hero-title-wrap.has-logo .gh-hero-logo.is-broken { display: none; }
.gh-hero-title-wrap.has-logo .gh-hero-logo.is-broken ~ .gh-hero-title { display: block; }
.gh-hero-logo {
  display: block;
  max-width: 640px;
  max-height: 140px;
  width: auto;
  height: auto;
  object-fit: contain;
  object-position: left center;
  margin: 0 0 12px;
  /* No drop-shadow: the logo sits on the scrim's near-solid left side, and
     the filter's blur cost a TV rendering at 4K a fifth of its frames on
     the top row, as each settled hero redrew it (Follow-up 148). */
}

/* Always in the DOM, blanked rather than removed, so moving focus is a
   textContent write instead of a node insertion — see GenreHome.renderFocus. */
.gh-hero-overview-wrap.is-empty,
.gh-hero-episode.is-empty,
.gh-btn-meta.is-empty { display: none; }

/* Which episode Resume continues, for a title with a Continue Watching
   entry — sits between the title and the synopsis, since it answers "what
   happens if I press Resume" more directly than either does. */
.gh-hero-episode {
  margin: 0 0 12px;
  font-size: calc(22px * var(--font-scale));
  font-weight: 600;
  color: var(--accent);
}

/* Three lines, cut at a word with "(...)" after it when longer (GenreHome
   foldedOverviews); OK on it unfolds it, as a person's biography does.
   Unfolded, it gets the room left beside the logo and Play — five lines —
   and Down/Up scroll the rest, a fade showing there is more. The border is
   the focus ring, so the text sits where it did before it had one. */
.gh-hero-overview-wrap {
  margin: 0 0 21px -11px;
  padding: 4px 8px;
  border: 3px solid transparent;
  border-radius: 10px;
}
.gh-hero-overview-wrap.focused { border-color: var(--catalog-focus); }
.gh-hero-overview {
  margin: 0;
  font-size: calc(22px * var(--font-scale));
  line-height: 1.4;
  color: var(--text-secondary);
  max-height: 93px;
  overflow: hidden;
}
.gh-hero-overview-wrap.is-expanded .gh-hero-overview { max-height: 154px; }
.gh-hero-overview-wrap.has-more .gh-hero-overview {
  /* Prefixed gradient masks are Chromium 53's own; a fade is all this is. */
  /* stylelint-disable-next-line plugin/no-unsupported-browser-features */
  -webkit-mask-image: linear-gradient(180deg, #000 75%, transparent);
}

/* Quieter than the hero's primary action — these navigate, they do not play. */
.gh-btn-nav {
  height: 48px;
  padding: 0 20px;
  font-size: calc(20px * var(--font-scale));
  font-weight: 500;
  background: var(--glass-tint);
  backdrop-filter: var(--glass-filter);
  color: var(--text-primary);
}

.gh-hero-actions { display: flex; }
.gh-hero-actions > * + * { margin-left: 16px; }

.gh-btn {
  display: flex;
  align-items: center;
  height: 60px;
  padding: 0 28px;
  border: 3px solid transparent;
  border-radius: 8px;
  background: var(--glass-tint);
  backdrop-filter: var(--glass-filter);
  box-shadow: inset 0 1px 0 var(--glass-highlight);
  color: var(--text-primary);
  font-size: calc(22px * var(--font-scale));
  font-weight: 600;
  cursor: pointer;
  box-sizing: border-box;
}
.gh-btn > * + * { margin-left: 10px; }

/* A TV drawing the app at 4K (devicePixelRatio 2): the hero's buttons keep
   their tint but not the blur, which had to be redone over every new
   backdrop and halved the top row's frames there (Follow-up 148). They sit
   on the scrim's dark side, where the blur showed little. Marked by
   theme-service's markHighDensity: a resolution query answers 1x there. */
html[data-density="2x"] .gh-btn,
html[data-density="2x"] .gh-btn-nav { backdrop-filter: none; }
.gh-btn-primary {
  background: var(--glass-accent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.28);
  color: #fff;
}
.gh-btn-icon { display: flex; width: 24px; height: 24px; }
.gh-btn-icon svg { width: 24px; height: 24px; }
.gh-btn-meta { font-size: calc(18px * var(--font-scale)); font-weight: 400; opacity: 0.75; }

.gh-btn.focused {
  border-color: var(--catalog-focus);
  outline: 3px solid var(--catalog-focus);
  outline-offset: 2px;
}
/* As the title page's: the accent-filled Play turns white when focused. */
.gh-btn-primary.focused {
  background: #fff;
  color: #111;
  border-color: #fff;
  outline-color: rgba(255, 255, 255, 0.45);
}

.gh-notice {
  margin: 0;
  padding: 12px 80px;
  font-size: calc(20px * var(--font-scale));
  color: var(--text-secondary);
}

/* The strip translates as a whole rather than each row scrolling, so a
   vertical move is one composited move instead of N layout changes. The
   opaque background is on THIS element (the one that translates) so the
   strip covers the hero title/overview as you scroll down — moving only an
   inner spacer left the hero text showing through gaps between posters. */
.gh-rows {
  position: relative;
  margin-top: -72px;
  padding: 24px 0 0 80px;
  /* Fast out of the press, a gentle landing (the cards' curve). */
  transition: transform 220ms cubic-bezier(0.2, 0, 0, 1);
  /* Kept on its own compositor layer between moves, so each press only
     re-composites it instead of re-promoting and re-rasterizing it at the
     start of every transition (measured faster on-device). */
  will-change: transform;
}

/* See-through where the backdrop runs under the first heading, opaque from
   the posters down: the strip covers the hero title as it scrolls. Two
   boxes, not one gradient over the whole strip: Chromium skips rastering a
   tile of plain colour but not of a gradient, and the strip is several
   screens tall; on a TV drawing at 4K each Up/Down re-rastered it, ~2-3
   frames shown in the 300ms after a press against ~8-9 now (Follow-up 148). */
.gh-rows::before,
.gh-rows::after {
  content: "";
  position: absolute;
  z-index: -1;
  left: 0;
  right: 0;
  pointer-events: none;
}
.gh-rows::before {
  top: 0;
  height: 72px;
  background: linear-gradient(180deg,
    rgba(var(--catalog-hero-scrim-rgb-legacy), 0) 0,
    rgba(var(--catalog-hero-scrim-rgb-legacy), 0) 48px,
    var(--bg-primary) 72px);
}
.gh-rows::after {
  top: 72px;
  bottom: 0;
  background: var(--bg-primary);
}

.genre-home.gh-holding .gh-rows {
  transition: none;
}

/* A window remount shifts the rows inside the strip and moves the transform
   by the same amount in the same frame; that pair must not animate
   (GenreHome.remountRows). */
.gh-rows.gh-rows--snap {
  transition: none;
}

/* No separate fade above the strip any more (it was a 140px, then 72px,
   ::before): the strip's own background ramps from see-through to opaque
   just where the backdrop's scrim reaches solid, so neither the resting
   home nor the slide to row 1 shows a hard edge, and nothing darkens the
   picture above the first heading. */


/* At 150% text size: heading + margin (~65px), track (366px), then a gap. */
/* A layer per row: a row mounting on Down then rasterizes alone, not the
   whole strip's layer again. Measured on a TV rendering at 4K (QNED91,
   devicePixelRatio 2): Down 384-447ms to 127-165ms median; the C3 (1080p)
   unchanged at ~70ms (Follow-up 145). */
.gh-row { height: 444px; will-change: transform; }
.gh-row-heading { display: flex; align-items: baseline; }
.gh-row-heading > * + * { margin-left: 24px; }
.gh-row-title {
  margin: 0 0 10px;
  font-size: calc(26px * var(--font-scale));
  font-weight: 600;
  color: var(--text-primary);
}

/* The clip window and the thing that moves must be DIFFERENT elements.
   Transforming an element that owns `overflow: hidden` drags its clipping
   rectangle along with it, so scrolling right slid the visible window off the
   left of the screen: cards near the edge were sliced in half and, far enough
   along, the whole row went blank while still accepting more Right presses.
   The track stays put and clips; the spacer inside it is what translates. */
/* Clear 280px artwork, 6px label margin and two 34px lines at 150%. */
/* 12px of padding on the clip window's leading (top/left) edges. A focused
   card's art scales up from its own center, so its top edge reaches above
   row 0's y:0 and its left edge reaches left of column 0's x:0 by half the
   scale delta (11px of the 280px art at 1.08). With no room to give, `overflow: hidden` clipped the
   glow — and the card itself — on exactly those two edges, on every row, for
   the first card only.
   Every element in the app is `box-sizing: border-box` (see main.css), so
   `height` here already means the TOTAL box the padding comes out of, not
   the content alone, so it is the 354px content (280 art + 6 + two 150%-scale
   label lines, which the `genre titles fit two lines` e2e spec checks) plus
   the 12px of headroom (444 total - 65 heading - 366 track leaves ~13px).
   `.gh-row-track`'s width is `auto`, so `padding-left` needs no matching
   width bump: for an auto-width border-box block the browser narrows the
   content area to absorb the padding while the box's own outer edges (and
   this clip window) stay pinned to the parent's bounds. */
.gh-row-track { position: relative; overflow: hidden; height: 366px; padding: 12px 0 0 12px; }
/* Right and Left glide the row too, on the cards' curve; the row keeps 3
   cards mounted past each edge (ROW overscan), so none pops in mid-glide. */
.gh-row-spacer {
  position: relative;
  height: 354px;
  transition: transform 200ms cubic-bezier(0.2, 0, 0, 1);
}

.gh-card {
  position: absolute;
  top: 0;
  width: 196px;
  cursor: pointer;
}

.gh-card-art {
  position: relative;
  width: 196px;
  height: 280px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--catalog-surface);
  border: 3px solid transparent;
  box-sizing: border-box;
}

.gh-card-img { width: 100%; height: 100%; object-fit: cover; display: block; }

.gh-resume-progress {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 5px;
  background: var(--accent);
}

.gh-card-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  font-size: calc(48px * var(--font-scale));
  color: var(--text-secondary);
}

.gh-card-versions {
  position: absolute;
  right: 4px;
  bottom: 4px;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgb(0 0 0 / 0.7);
  font-size: calc(16px * var(--font-scale));
  color: #fff;
}

/* Top-left is the one corner the rating (bottom-left), version count
   (bottom-right) and remove button (top-right) all leave free. */
.gh-card-episode {
  position: absolute;
  left: 4px;
  top: 4px;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgb(0 0 0 / 0.7);
  font-size: calc(16px * var(--font-scale));
  color: #fff;
}

.gh-card-name {
  margin-top: 6px;
  font-size: calc(18px * var(--font-scale));
  line-height: 1.25;
  /* Two lines, in em so it tracks --font-scale — a px cap here re-clipped the
     second line the moment the font scale went above 1. */
  max-height: 2.5em;
  overflow: hidden;
  color: var(--text-secondary);
}

/* The trailing card into a row's full grid. Sized exactly like a poster card
   so it sits on the same stride as everything else in the rail. */
.gh-more-art {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-secondary);
  border-style: dashed;
  border-color: var(--text-secondary);
}

.gh-more-label {
  padding: 0 10px;
  font-size: calc(20px * var(--font-scale));
  font-weight: 600;
  text-align: center;
  color: var(--text-primary);
}

.gh-card-more.focused .gh-more-art {
  border-style: solid;
  background: var(--catalog-surface);
}

/* The IMDb position, drawn ahead of the title on that row's cards only. */
.gh-card-rank {
  margin-right: 5px;
  font-weight: 700;
  color: var(--catalog-focus);
}

/* Focus: a ring with its shadow and glow, and a small scale, on the art only
   so the label below does not shift. Only transform and opacity change on a
   press: the ring is its own box, painted once and then faded and scaled,
   where a border, shadow and glow changing on the card repainted it every
   frame of the move (costly on a TV rendering at 4K, Follow-up 147). */
.gh-card.focused .gh-card-art { transform: scale(1.08); }
.gh-card::before {
  content: "";
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  width: 196px;
  height: 280px;
  box-sizing: border-box;
  border: 3px solid var(--catalog-focus);
  border-radius: 8px;
  box-shadow: 0 6px 18px rgb(0 0 0 / 0.6), 0 0 20px var(--accent-glow);
  pointer-events: none;
  opacity: 0;
}
.gh-card.focused::before {
  opacity: 1;
  transform: scale(1.08);
}
.gh-card.focused .gh-card-name { color: var(--text-primary); }
/* The generic ring around the whole card box (navigation.css) would double
   the card's own, and it animates a box-shadow. */
.gh-card[data-focusable] { transition: none; }
.gh-card[data-focusable].focused { box-shadow: none; }

/* Short, eased in fast and out gently: a press answers at once and still
   glides. Stopped while the strip snaps a remount (gh-rows--snap). */
.gh-card-art,
.gh-card::before {
  transition: transform 120ms cubic-bezier(0.2, 0, 0, 1), opacity 120ms cubic-bezier(0.2, 0, 0, 1);
}

@media (prefers-reduced-motion: reduce) {
  .gh-card-art,
  .gh-card::before,
  .gh-row-spacer, .gh-grid-spacer, .gh-grid-scrollbar-thumb { transition: none; }
}

/* The genre chooser: a panel over the rows, for a viewer who already knows
   what they want. Deliberately a plain vertical list — it is walked with the
   D-pad, so anything grid-shaped would only add a second axis to get lost on. */
.gh-picker {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  /* rgba(), not the space/slash rgb() used elsewhere in this file: that
     syntax postdates Chromium 53, and a dropped declaration here would leave
     the panel sitting on the rows with no scrim behind it at all. */
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
}

.gh-picker-panel {
  width: 668px;
  max-height: 80%;
  padding: 32px;
  border-radius: 12px;
  background: var(--glass-tint);
  backdrop-filter: var(--glass-filter);
  border: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--glass-highlight), var(--glass-shadow);
  box-sizing: border-box;
  overflow: hidden;
}

.gh-picker-title {
  margin: 0 0 20px;
  font-size: calc(30px * var(--font-scale));
  font-weight: 600;
  color: var(--text-primary);
}

/* `position: relative` is load-bearing, not decoration: syncPickerScroll reads
   the focused option's `offsetTop`, which is measured from its offsetParent.
   Without a position here the nearest positioned ancestor is `.gh-picker`
   itself, so every reading carried the panel's own offset from the screen edge
   and the list under-scrolled by that constant — the focused genre walked out
   of view moving back up the list. */
.gh-picker-list {
  position: relative;
  max-height: 560px;
  overflow-x: hidden;
  overflow-y: auto;
  /* Room for the focused option's glow (navigation.css [data-focusable]
     .focused): the list clips, so without it the glow was cut at both
     sides, and the top/bottom options' at the list's ends. Pulled back out
     sideways so the options keep their width; not upward, where the list
     would slide under the title. */
  padding: 16px 24px;
  margin: 0 -24px;
}

.gh-picker-option {
  display: block;
  width: 100%;
  padding: 12px 18px;
  border: 3px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: calc(24px * var(--font-scale));
  text-align: left;
  cursor: pointer;
  box-sizing: border-box;
}

.gh-picker-option.focused {
  border-color: var(--catalog-focus);
  background: var(--catalog-surface);
  color: var(--text-primary);
}

/* One genre's full catalog. Same card as the rows, laid out on a vertical
   grid; the spacer translates while the track clips, exactly as the rows do. */
.gh-grid-view { padding: 24px 0 0 80px; box-sizing: border-box; }

.gh-grid-title {
  margin: 0 0 20px;
  font-size: calc(40px * var(--font-scale));
  font-weight: 700;
  color: var(--text-primary);
}

/* Same 12px clip-window inset as the rows, and for the same reason: a
   focused card's scale-up reaches above row 0 and left of column 0. `height`
   is raised by the padding amount, same as `.gh-row-track` above, so the
   visible content area (what GRID_VIEWPORT_HEIGHT in genre-home.ts windows
   against) stays the original 820. */
.gh-grid-track { position: relative; overflow: hidden; height: 832px; padding: 12px 0 0 12px; flex: 1; min-width: 0; }
.gh-grid-spacer {
  position: relative;
  /* The grid glides between rows as the home's rows do (gh-row-spacer),
     on the same curve; two rows stay mounted past each edge (overscan),
     so none pops in mid-glide. */
  transition: transform 200ms cubic-bezier(0.2, 0, 0, 1);
}

.gh-grid-body {
  position: relative;
  display: flex;
  padding-right: 48px;
}

.gh-grid-scrollbar {
  position: relative;
  flex: 0 0 6px;
  width: 6px;
  height: 832px;
  margin-left: 12px;
  border-radius: 3px;
  background: rgb(255 255 255 / 0.08);
}

.gh-grid-scrollbar-thumb {
  position: absolute;
  transition: top 200ms cubic-bezier(0.2, 0, 0, 1);
  left: 0;
  right: 0;
  border-radius: 3px;
  background: rgb(255 255 255 / 0.35);
}

.catalog-rating {
  position: absolute;
  left: 4px;
  bottom: 4px;
  padding: 2px 5px;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.8);
  color: #ffd36a;
  font-size: calc(16px * var(--font-scale));
  line-height: 20px;
}

.catalog-rating svg { width: 14px; height: 14px; margin-left: 3px; }
.gh-grid-heading { display: flex; align-items: center; justify-content: space-between; padding-right: 80px; }
.gh-grid-heading .gh-btn { margin-bottom: 20px; }

/* Continue/Details/Close choice on a Continue Watching card select
   (ResumeChoiceMenu) — a right-click-style context menu that opens beside
   the card it came from, so the layer itself only carries the coordinate
   space: transparent, no dim, and pointer-transparent except over the panel
   (which turns pointer events back on). Nothing about this screen is
   blocked; it is a menu, not a dialog. */
.resume-choice-menu {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  pointer-events: none;
  z-index: 10000;
}

.resume-choice-menu.hidden {
  display: none;
}

/* Positioned from JS against the anchor — see applyPosition. */
.resume-choice-panel {
  position: absolute;
  min-width: 260px;
  padding: 6px;
  pointer-events: auto;
  background: var(--glass-tint);
  backdrop-filter: var(--glass-filter);
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius);
  box-shadow: inset 0 1px 0 var(--glass-highlight), var(--glass-shadow);
}

.resume-choice-option {
  display: block;
  width: 100%;
  padding: 10px 16px;
  font-size: calc(20px * var(--font-scale));
  text-align: left;
  color: var(--text-primary);
  background: transparent;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

.resume-choice-option.focused {
  background: var(--accent-dim);
  color: #fff;
}

/* Close is a way out, not a third thing to do to the title — split off
   from the two that act on it, the way a desktop menu groups its items. */
.resume-choice-sep {
  height: 1px;
  margin: 6px 8px;
  background: var(--border);
}

/* The button column down the left edge (genre-home.ts renderColumn), drawn
   with Live's column styles (channel-list.css .live-actions). Tucked off the
   edge until used, leaving a small tab to show it is there; it slides in over
   the page (never pushing it aside) while focus is in it or the pointer is
   over the edge. `.gh-side` is the pointer's hover zone: the column is its
   child, so moving onto the opened column keeps it open. Hover counts only
   while the pointer is in use: html.keys-active (key-handler.ts) is set by
   a remote button, so a cursor left over the column (after clicking one of
   its buttons, say) no longer holds it open while the D-pad browses. */
.gh-side {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 28px;
  z-index: 20;
}

.gh-side-tab {
  position: absolute;
  top: 50%;
  left: 0;
  /* As Live's: wide enough to survive a TV's overscan (channel-list.css). */
  width: 16px;
  height: 88px;
  margin-top: -44px;
  border-radius: 0 6px 6px 0;
  background: var(--accent);
  opacity: 0.55;
  -webkit-transition: opacity 180ms ease-out;
  transition: opacity 180ms ease-out;
}

.gh-actions {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  box-sizing: border-box;
  -webkit-transform: translateX(-100%);
  transform: translateX(-100%);
  -webkit-transition: -webkit-transform 200ms ease-out, box-shadow 200ms ease-out;
  transition: transform 200ms ease-out, box-shadow 200ms ease-out;
}

.gh-side.is-open .gh-actions,
html:not(.keys-active) .gh-side:hover .gh-actions {
  -webkit-transform: translateX(0);
  transform: translateX(0);
  box-shadow: 12px 0 32px rgba(0, 0, 0, 0.6);
}

.gh-side.is-open .gh-side-tab,
html:not(.keys-active) .gh-side:hover .gh-side-tab {
  opacity: 0;
}

/* On the watchlist: a bookmark in the card's top-right corner. */
.gh-card-listed {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 26px;
  height: 26px;
  padding: 3px;
  border-radius: 4px;
  box-sizing: border-box;
  background: rgb(0 0 0 / 0.7);
  color: var(--accent);
}
.gh-card-listed svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: currentColor;
}

/* What is switched on (genre-home.ts statusChips), top left of the hero
   where the column's state would otherwise be out of sight. */
.gh-status {
  position: absolute;
  top: 24px;
  left: 80px;
  display: flex;
}
.gh-status > * + * { margin-left: 12px; }
.gh-status-chip {
  display: flex;
  align-items: center;
  height: 40px;
  padding: 0 16px 0 12px;
  border-radius: 20px;
  font-size: calc(20px * var(--font-scale));
  font-weight: 600;
  box-sizing: border-box;
}
.gh-status-chip svg {
  width: 24px;
  height: 24px;
  margin-right: 8px;
}
.gh-status-fourk { background: var(--remote-key-yellow); color: #1b1b1b; }
.gh-status-watchlist { background: var(--remote-key-green); color: #fff; }

/* A switched-on column button says so on its face as well: a check badge. */
.gh-actions .live-action.is-on::after {
  content: '';
  position: absolute;
  right: -4px;
  bottom: -4px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid var(--bg-primary);
  box-sizing: border-box;
  background: var(--success) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4L19 7'/%3E%3C/svg%3E") center / 14px 14px no-repeat;
}

/* A card left focused a moment opens out over its row (GenreHome.openPeek):
   a larger copy of the poster with the year and genres beneath, grown from
   the card's own size. Transform and opacity only. */
.gh-peek {
  position: absolute;
  z-index: 10;
  width: 235px;
  border: 3px solid var(--catalog-focus);
  border-radius: 10px;
  overflow: hidden;
  background: var(--bg-secondary);
  box-shadow: 0 14px 36px rgb(0 0 0 / 0.65);
  pointer-events: none;
  opacity: 0;
  transform: scale(0.9);
  transform-origin: 50% 168px;
  transition: transform 200ms ease-out, opacity 200ms ease-out;
}

.gh-peek.is-open {
  opacity: 1;
  transform: scale(1);
}

.gh-peek-art {
  position: relative;
  height: 330px;
  background: var(--catalog-surface);
}

.gh-peek-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gh-peek-info {
  padding: 8px 10px 10px;
}

.gh-peek-title {
  font-size: calc(18px * var(--font-scale));
  font-weight: 600;
  line-height: 1.25;
  max-height: 2.5em;
  overflow: hidden;
  color: var(--text-primary);
}

.gh-peek-meta {
  margin-top: 4px;
  font-size: calc(16px * var(--font-scale));
  line-height: 1.3;
  color: var(--text-secondary);
}

@media (prefers-reduced-motion: reduce) {
  .gh-peek { transition: none; }
}
