/* ------------------------------------------------------------------
   layout.css — the app shell and screen scaffolding.
   ------------------------------------------------------------------ */

body {
  background: var(--c-bg);
  color: var(--c-text);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
}

/* ---- the single reused video surface ---------------------------- */

.player-surface {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  background: #000;
  object-fit: contain;
  z-index: var(--z-player);
  display: none;              /* Phase 8 reveals it via .is-active */
}
.player-surface.is-active { display: block; }

/* ---- picture modes ------------------------------------------------
   Set by js/player/video-modes.js. `fit` is the default and the only one
   that shows the whole frame; the rest trade picture for a filled screen,
   which is a real choice on a 2.40:1 film and never one to make for the
   viewer. There is no fullscreen mode because the element is already
   fixed at inset 0 — a toggle would change nothing. */

.player-surface.vmode-fit     { object-fit: contain; transform: none; }
.player-surface.vmode-fill    { object-fit: cover;   transform: none; }
.player-surface.vmode-stretch { object-fit: fill;    transform: none; }
.player-surface.vmode-zoom    { object-fit: contain; transform: scale(1.1); }
.player-surface.vmode-w16x9   { object-fit: fill; aspect-ratio: 16 / 9;
                                width: auto; height: 100%; margin: 0 auto; }
.player-surface.vmode-w4x3    { object-fit: fill; aspect-ratio: 4 / 3;
                                width: auto; height: 100%; margin: 0 auto; }

/* ---- app root ---------------------------------------------------- */

.app {
  position: fixed;
  inset: 0;
  z-index: var(--z-app);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* While the player is visible the shell must not paint over the video. */
.app.is-transparent { background: transparent; pointer-events: none; }

/* ---- screens ----------------------------------------------------- */

.screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  opacity: 0;
  transition: opacity var(--t-base) var(--ease);
}
.screen.is-mounted { opacity: 1; }

.screen__body {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--sp-5) var(--safe-x) var(--safe-y);
  /* Plain hidden. An earlier attempt used `overflow: hidden visible` to let
     a scaled item bleed sideways; per spec that computes to `hidden auto`,
     which quietly made the body itself scrollable. The inset focus ring
     removes the need for the trick entirely. */
  overflow: hidden;
}

/* Spacing has to be restated here because the geometry changed under it.

   With a top bar, `.screen__body` sat below 96px of chrome, so a small
   --sp-5 top padding was all it needed and the real top safe area was the
   nav's own padding. With the nav down the left edge there is nothing above
   the content any more, so the body has to own the top safe area itself —
   otherwise every screen's title sat 24px from the panel edge, inside the
   overscan region on a TV.

   The left is the other way round: the rail already insets that edge, and a
   full --safe-x on top of it pushed content in by nearly 150px.

   Screens without the rail — login, boot, error, PIN — keep the original
   padding, which is why this is a modifier and not a change to
   `.screen__body`. */
.screen__body--railed {
  min-width: 0;
  /* The rail floats above this, so the margin is what keeps content clear
     of it — and what gives the width back when it retreats. Transitioned to
     match the rail's own slide, so the two read as one movement rather than
     a panel leaving and a page jumping.

     Both virtualizers carry a ResizeObserver, so a grid re-measures as this
     runs; the column count only changes at thresholds, so in practice that
     is a cheap no-op on all but one or two frames. */
  margin-left: var(--nav-w);
  transition: margin-left var(--t-base) var(--ease);
  /* Tight. The rail already holds the content off the left edge and the
     screens below fill the space themselves, so the old full safe area on
     every side just pushed everything into the middle of the panel and left
     a wide empty border. --safe-x is kept only on the right, where nothing
     else protects against overscan. */
  padding: var(--sp-5) var(--sp-6) var(--sp-4) var(--sp-5);
}

.screen__title {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: -0.02em;
  margin-bottom: var(--sp-2);
}

.screen__subtitle {
  font-size: var(--fs-sm);
  color: var(--c-text-dim);
  margin-bottom: var(--sp-6);
}

/* ---- top navigation ---------------------------------------------- */

/* The shell: rail on the left, screen body filling the rest.

   Not `.row`, which sets align-items:center — that centred the rail
   vertically and left it floating as a panel in the middle of the screen
   instead of running the full height of the edge. */
.shell {
  position: relative;          /* the rail anchors to this when it retreats */
  display: flex;
  align-items: stretch;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
}

/* A vertical rail, not a top bar.
   On a 16:9 panel, horizontal space is the thing there is most of and
   vertical space is what content actually needs — a 96px top bar cost 9% of
   the height of every screen, where a rail costs 4% of the width nothing was
   using. It also gives the remote a simpler job: UP/DOWN walks the sections,
   RIGHT enters the content, which is one axis per decision.

   overflow is VISIBLE on purpose: the focused item flies its name out to the
   right as a floating pill, and any clipping on this element would cut it in
   half. Eight icon-only items, the mark and the clock come to roughly 700px,
   so there is nothing to scroll at any size this app is used at. The one
   exception is handled at the short-viewport breakpoint. */
.nav {
  /* Always out of the layout, so nothing reflows when it comes and goes.
     The content's left margin is what actually moves, and a margin can be
     transitioned where `position` cannot — switching the rail between
     static and absolute made the content snap to its new width in one
     frame. */
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: var(--nav-w);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--sp-3);
  padding: var(--safe-y) var(--sp-2);
  z-index: var(--z-nav);
  overflow: visible;
  background: var(--c-bg-elevated);
  border-right: 1px solid var(--c-line);

  /* It gets out of the way when you are not using it.
     ------------------------------------------------
     The rail retreats to a sliver once focus has been in the content for a
     few seconds, and comes back the instant focus returns to it.

     Driven by a CLASS that nav.js toggles, not by :focus-within. That was
     the first attempt and it could not work here: this app deliberately
     withholds native DOM focus from buttons — see the comment in
     js/input/focus.js, "Native focus only for text entry" — and actively
     blurs them, so :focus-within only ever matched a mouse hover. Arrowing
     into the menu left it hidden. The app's focus lives in the .is-focused
     class, so the rail has to listen to that.

     It leaves as soon as focus does, rather than lingering on a timer. A
     menu that hangs about for a few seconds after you have moved on is a
     menu you watch disappear, which draws the eye to exactly the thing you
     stopped caring about.

     It keeps its width in the layout on purpose. Collapsing it to zero
     would reflow every screen behind it, and the poster grids recompute
     their column count from the container width — so the whole grid would
     re-lay-out twice every time you glanced at the menu. */
  opacity: 1;
  transform: none;
  transition: opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease);
}

/* Retreating takes the rail OUT OF FLOW, so the content gets its width
   back rather than sitting next to an empty 84px gutter.

   Absolute rather than a width animation, because the rail must stay the
   size it is: its items keep their real geometry and stay focusable, which
   is what lets LEFT walk back into a rail that is currently invisible. A
   rail collapsed to zero width has zero-width children, and the focus
   engine skips anything with no box — the menu would become unreachable by
   remote, which is the bug this whole behaviour already had once.

   The screens behind it reflow, and that is fine: both virtualizers carry a
   ResizeObserver and recompute their column count, so the poster grid
   re-lays-out properly instead of being left at the old width. */
.nav.is-idle {
  opacity: 0.16;
  transform: translateX(calc(var(--nav-w) * -0.55));
}

/* The body is the rail's immediate next sibling — see screens/_shell.js —
   so it can follow the rail's state without any JavaScript of its own. */
.nav.is-idle + .screen__body--railed {
  margin-left: 0;
}

/* A mouse still reveals it, and must beat .is-idle — equal specificity, so
   it goes after. */
.nav:hover {
  opacity: 1;
  transform: none;
  transition-delay: 0ms;
}

/* Someone who has asked for less motion gets the rail simply present. */
@media (prefers-reduced-motion: reduce) {
  .nav, .nav.is-idle { opacity: 1; transform: none; transition: none; }
  .screen__body--railed,
  .nav.is-idle + .screen__body--railed {
    margin-left: var(--nav-w);
    transition: none;
  }
}}

.nav__items {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--sp-1);
  flex: 1 1 auto;
  min-height: 0;
}


/* Stacked, because "NoZ IPTV" on one line sets the rail's minimum width and
   the rail is the one place where width is the scarce dimension. */
.nav__brand {
  flex: 0 0 auto;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: 0.04em;
  line-height: 1.05;
  color: var(--c-text);
  text-align: center;
  padding: var(--sp-1) 0 var(--sp-2);
}
.nav__brand b { color: var(--c-primary); font-weight: var(--fw-bold); display: block; }


/* ---- generic panes ----------------------------------------------- */

.pane-split {
  display: flex;
  gap: var(--sp-6);
  height: 100%;
  min-height: 0;
}
.pane-split__side { flex: 0 0 380px; min-width: 0; display: flex; flex-direction: column; }
.pane-split__main { flex: 1 1 auto;  min-width: 0; display: flex; flex-direction: column; }

/* The only scrollable elements in the app. Never the document.
   The padding/negative-margin pair is not decoration: a focused item is
   scaled up and carries a ring, and without that reserved bleed the ring
   is clipped flat against the container edge. The negative margin keeps
   the visual alignment identical to a container with no padding at all. */
.scroll-y {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding: var(--focus-bleed);
  margin: calc(var(--focus-bleed) * -1);
  scroll-padding: var(--focus-bleed);
}
.scroll-y::-webkit-scrollbar { width: 0; height: 0; display: none; }

.scroll-x {
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  /* nowrap keeps the rail itself on one line. It is inherited, though, so
     anything inside a rail that should wrap has to opt back in — see
     .scroll-x > * below. Tracking this down was the real cause of tile
     text spilling past its card. */
  white-space: nowrap;
  padding: var(--focus-bleed);
  margin: calc(var(--focus-bleed) * -1);
  scroll-padding: var(--focus-bleed);
}
.scroll-x::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* Children of a rail lay out normally inside themselves. */
.scroll-x > * { white-space: normal; }

/* Opt-out for containers that must sit flush. */
.scroll-y.no-bleed, .scroll-x.no-bleed { padding: 0; margin: 0; }

/* ---- overlays ---------------------------------------------------- */

.overlay-root {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  pointer-events: none;
}
.overlay-root > * { pointer-events: auto; }

/* ---- pre-boot splash --------------------------------------------- */

.preboot {
  position: fixed;
  inset: 0;
  z-index: var(--z-dialog);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-6);
  background: var(--c-bg);
  color: var(--c-text);
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.preboot__mark {
  font-size: var(--fs-2xl, 56px);
  font-weight: 700;
  letter-spacing: 0.08em;
}
.preboot__bar {
  width: 280px;
  height: 4px;
  border-radius: 2px;
  background: var(--c-surface-2, #1D2631);
  overflow: hidden;
}
.preboot__bar i {
  display: block;
  width: 40%;
  height: 100%;
  background: var(--c-primary, #E50914);
  animation: preboot-slide 1.1s var(--ease, ease) infinite;
}
@keyframes preboot-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}

/* ---- utility ----------------------------------------------------- */

.stack   { display: flex; flex-direction: column; }
.row     { display: flex; align-items: center; }
.gap-2   { gap: var(--sp-2); }
.gap-4   { gap: var(--sp-4); }
.gap-5   { gap: var(--sp-5); }
.grow    { flex: 1 1 auto; min-width: 0; min-height: 0; }
.center  { align-items: center; justify-content: center; }
.dim     { color: var(--c-text-dim); }
.faint   { color: var(--c-text-faint); }
.mono    { font-family: "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.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;
}
