/* ==========================================================================
   FirasMovies — design system.

   One stylesheet for the whole site, built around a single idea: the layout,
   the type and the spacing are identical on every device and at every
   capability tier. Only the *decoration* changes.

   TIERS

   js/perf.js stamps `t-full` or `t-lite` on <html> before the first paint.

     t-full   blur, depth, hover lift, cross-fades, large artwork
     t-lite   the same layout, drawn flat — no blur, no shadow, no transform
              animation, shorter transitions, smaller artwork

   Everything expensive is written ONCE, inside a `.t-full` selector, so the
   reduced tier is the default and the rich one is the addition. Written the
   other way round — effects everywhere, overridden in `.t-lite` — every new
   effect would silently ship to weak devices until someone remembered to
   exclude it.

   WHAT COUNTS AS EXPENSIVE

   backdrop-filter and filter above all; then large or animated box-shadows;
   then anything animating a property that is not transform or opacity, since
   those are the only two the compositor can handle without repainting.
   ========================================================================== */

:root {
  --bg:        #08080c;
  --surface:   #101016;
  --raised:    #17171f;
  --raised-2:  #1f1f2a;
  --line:      rgba(255, 255, 255, 0.09);
  --line-soft: rgba(255, 255, 255, 0.055);

  --text:      #f5f5f7;
  --dim:       #b4b4c2;
  --muted:     #8a8a9c;

  --brand:     #e50914;
  --brand-2:   #ff3b6b;
  --brand-ink: #ffffff;

  --ok:        #3ddc84;
  --warn:      #ffb020;
  --bad:       #ff5a62;

  --r-sm: 8px;
  --r:    14px;
  --r-lg: 20px;
  --r-pill: 999px;

  /* One rhythm for the whole site. Section padding, gaps and gutters all
     come from here so nothing drifts. */
  --gutter: clamp(16px, 4vw, 56px);
  --row-gap: clamp(28px, 4vw, 48px);
  --card-w: clamp(126px, 15vw, 186px);

  --speed: 220ms;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  --topbar-h: 64px;
  --tabbar-h: 62px;

  /*
   * iPhone safe areas.
   *
   * The pages declare viewport-fit=cover so artwork can run edge to edge and
   * fill the rounded corners. That also puts the page UNDER the status bar
   * and the camera cutout — the fixed top bar was drawing behind the clock
   * and the battery icon.
   *
   * --topbar-total is the bar's real occupied height: its own height plus
   * whatever the device reserves at the top. Anything positioning itself
   * below the bar must use this, never --topbar-h. The fallback of 0px keeps
   * it exact on every device that has no inset.
   */
  --safe-top: env(safe-area-inset-top, 0px);
  --topbar-total: calc(var(--topbar-h) + var(--safe-top));
}

/* The reduced tier keeps the layout and drops the time. */
.t-lite { --speed: 110ms; }

@media (prefers-reduced-motion: reduce) {
  :root { --speed: 1ms; }
  * { animation-duration: 1ms !important; animation-iteration-count: 1 !important; }
}

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }

html { background: var(--bg); -webkit-text-size-adjust: 100%; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img, svg, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button, input, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }

/* Visible focus for keyboard users, invisible for mouse users. */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--brand-2); outline-offset: 3px; border-radius: 4px; }

h1, h2, h3 { line-height: 1.14; letter-spacing: -0.022em; font-weight: 800; }

.wrap { padding-inline: var(--gutter); }
.hide { display: none !important; }

/* Screen-reader-only, still focusable — used by the skip link. */
.sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip:focus {
  position: fixed; top: 12px; left: 12px; z-index: 200;
  width: auto; height: auto; clip: auto; margin: 0;
  padding: 12px 20px; background: var(--raised);
  border: 1px solid var(--line); border-radius: var(--r-sm);
}

/* ==========================================================================
   Top bar
   ========================================================================== */
.top {
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--topbar-total);
  padding-top: var(--safe-top);
  z-index: 100;
  display: flex;
  align-items: center;
  gap: 12px;
  padding-inline: var(--gutter);
  /*
   * Promote to its own compositor layer. iOS Safari repaints a backdrop-filter
   * element against the scrolling content underneath it, which flickers badly
   * during momentum scrolling; giving it a layer of its own stops that.
   */
  transform: translateZ(0);
  /* Transparent over the hero, solid once scrolled. Only the background and
     the border change, so nothing reflows. */
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background var(--speed) var(--ease), border-color var(--speed) var(--ease);
}

.top.solid { background: rgba(8, 8, 12, 0.92); border-bottom-color: var(--line-soft); }
.t-full .top.solid {
  background: rgba(8, 8, 12, 0.72);
  backdrop-filter: saturate(160%) blur(16px);
  -webkit-backdrop-filter: saturate(160%) blur(16px);
}

.logo { font-size: 1.16rem; font-weight: 900; letter-spacing: -0.03em; white-space: nowrap; }
.logo i { font-style: normal; color: var(--text); }
.logo b {
  font-weight: 900;
  background: linear-gradient(95deg, var(--brand-2), var(--brand));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* A gradient that fails to clip renders as a solid block over the word. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .logo b { background: none; color: var(--brand-2); }
}

.nav { display: flex; align-items: center; gap: 4px; margin-left: 18px; }
.nav a {
  position: relative;
  padding: 9px 14px;
  border-radius: var(--r-pill);
  font-size: 0.93rem;
  font-weight: 600;
  color: var(--dim);
  white-space: nowrap;
  transition: color var(--speed) var(--ease), background var(--speed) var(--ease);
}
.nav a:hover { color: var(--text); }
.nav a.on { color: var(--text); background: rgba(255, 255, 255, 0.08); }

.top .spacer { flex: 1; }
.top-actions { display: flex; align-items: center; gap: 10px; }

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  height: 46px;
  padding: 0 22px;
  border-radius: var(--r-pill);
  font-size: 0.95rem;
  font-weight: 700;
  white-space: nowrap;
  background: var(--raised-2);
  color: var(--text);
  border: 1px solid var(--line);
  transition: background var(--speed) var(--ease), border-color var(--speed) var(--ease);
}
.btn:hover { background: #2a2a38; }

.btn-primary {
  background: linear-gradient(95deg, var(--brand-2), var(--brand));
  color: var(--brand-ink);
  border-color: transparent;
}
.t-lite .btn-primary { background: var(--brand); }
.btn-primary:hover { background: linear-gradient(95deg, #ff5480, #ff1524); }
.t-lite .btn-primary:hover { background: #ff1524; }

.btn-ghost { background: rgba(255, 255, 255, 0.07); border-color: var(--line); }
.btn-sm { height: 38px; padding: 0 16px; font-size: 0.88rem; }
.btn-lg { height: 54px; padding: 0 30px; font-size: 1rem; }

/* Depth belongs to the capable tier only. */
.t-full .btn-primary { box-shadow: 0 6px 22px rgba(229, 9, 20, 0.28); }

/* ==========================================================================
   Hero
   ========================================================================== */
.hero {
  position: relative;
  min-height: min(74vh, 640px);
  display: flex;
  align-items: flex-end;
  padding: calc(var(--topbar-total) + 40px) var(--gutter) clamp(32px, 5vw, 64px);
  overflow: hidden;
  /* Reserves the space before the artwork loads, so the rows below never
     jump down the page when it arrives. */
  background: var(--surface);
}

.hero-art {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 28%;   /* faces sit high in a film still */
  opacity: 0;
  transition: opacity 600ms var(--ease);
}
.hero-art.in { opacity: 1; }
.t-lite .hero-art { transition: none; }

/*
 * Two scrims: one vertical so the text has a floor, one horizontal so it has
 * a left edge on wide screens. Static, so they are painted once and never
 * again — a gradient that never moves costs nothing.
 */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to top, var(--bg) 2%, rgba(8, 8, 12, 0.72) 34%, rgba(8, 8, 12, 0.12) 68%),
    linear-gradient(to right, rgba(8, 8, 12, 0.88), rgba(8, 8, 12, 0.12) 62%);
  pointer-events: none;
}
.t-lite .hero::after {
  background: linear-gradient(to top, var(--bg) 4%, rgba(8, 8, 12, 0.82) 46%, rgba(8, 8, 12, 0.45));
}

.hero-in { position: relative; z-index: 2; max-width: 660px; width: 100%; }

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brand-2);
  margin-bottom: 14px;
}

.hero h1 {
  font-size: clamp(2.1rem, 6.2vw, 4rem);
  margin-bottom: 14px;
  text-wrap: balance;
}

.meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  color: var(--dim);
  font-size: 0.92rem;
  margin-bottom: 16px;
}
.meta .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--muted); }
.pill {
  padding: 3px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-size: 0.78rem;
  font-weight: 700;
}
.pill.score { color: var(--ok); border-color: rgba(61, 220, 132, 0.34); }

.hero p {
  color: var(--dim);
  font-size: clamp(0.95rem, 1.6vw, 1.05rem);
  margin-bottom: 26px;
  max-width: 56ch;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.hero-btns { display: flex; flex-wrap: wrap; gap: 12px; }

/* Which hero slide is showing. Tap targets are the full height of the strip
   so they are usable on a phone. */
.hero-dots { position: absolute; z-index: 3; left: var(--gutter); bottom: 18px; display: flex; gap: 8px; }
.hero-dots button {
  width: 26px; height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.25);
  transition: background var(--speed) var(--ease);
}
.hero-dots button.on { background: var(--brand-2); }

/* ==========================================================================
   Rows
   ========================================================================== */
.rows { padding-block: clamp(22px, 4vw, 38px) 40px; position: relative; z-index: 3; }
.row { margin-bottom: var(--row-gap); }

.row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding-inline: var(--gutter);
  margin-bottom: 14px;
}
.row-head h2 { font-size: clamp(1.05rem, 2.2vw, 1.34rem); }
.row-head a { font-size: 0.86rem; font-weight: 700; color: var(--muted); white-space: nowrap; }
.row-head a:hover { color: var(--text); }

.rail-wrap { position: relative; }

/*
 * Native horizontal scrolling with snap.
 *
 * Deliberately not a JS-driven transform carousel: native scrolling gives
 * momentum, trackpad gestures, touch drag and keyboard scrolling for free,
 * and the browser runs it off the main thread. A hand-rolled one gets none
 * of that and janks the moment JavaScript is busy.
 */
.rail {
  display: flex;
  gap: 12px;
  padding-inline: var(--gutter);
  overflow-x: auto;
  overflow-y: hidden;
  /*
   * `proximity`, not `mandatory`, and scroll-padding to match the gutter.
   *
   * Mandatory snapping aligns the first card to the scrollport's start edge
   * the moment the rail is laid out, which swallowed the left padding: every
   * row's first poster sat flush against the window while its heading was
   * correctly indented. scroll-padding tells snapping where the content
   * really begins, and proximity stops it fighting a deliberate scroll.
   */
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--gutter);
  scroll-behavior: smooth;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Stops a vertical swipe on a phone being stolen by the rail. */
  touch-action: pan-x pan-y;
}
.rail::-webkit-scrollbar { display: none; }
@media (prefers-reduced-motion: reduce) { .rail { scroll-behavior: auto; } }

.rail > * { scroll-snap-align: start; }

/* Desktop arrows. Hidden from touch devices, which do not need them. */
.rail-btn {
  position: absolute;
  top: 0; bottom: 0;
  width: 56px;
  z-index: 4;
  display: none;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  color: var(--text);
  background: linear-gradient(to right, rgba(8, 8, 12, 0.92), rgba(8, 8, 12, 0));
  opacity: 0;
  transition: opacity var(--speed) var(--ease);
}
.rail-btn.next { right: 0; background: linear-gradient(to left, rgba(8, 8, 12, 0.92), rgba(8, 8, 12, 0)); }
.rail-btn.prev { left: 0; }
@media (hover: hover) and (min-width: 900px) {
  .rail-btn { display: flex; }
  .rail-wrap:hover .rail-btn { opacity: 1; }
  .rail-btn[disabled] { opacity: 0 !important; pointer-events: none; }
}

/* ==========================================================================
   Card
   ========================================================================== */
.card {
  flex: 0 0 var(--card-w);
  width: var(--card-w);
  position: relative;
}

.art {
  position: relative;
  aspect-ratio: 2 / 3;
  border-radius: var(--r);
  overflow: hidden;
  background: var(--raised);
  border: 1px solid var(--line-soft);
}
/* aspect-ratio is well supported now, but a browser without it would render
   zero-height artwork, which is a blank page rather than a rough one. */
@supports not (aspect-ratio: 2 / 3) {
  .art { height: 0; padding-bottom: 150%; }
}

.art img {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 320ms var(--ease);
}
.art img.in { opacity: 1; }
.t-lite .art img { transition: none; }

.card-t {
  margin-top: 9px;
  font-size: 0.87rem;
  font-weight: 600;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card-m { font-size: 0.78rem; color: var(--muted); margin-top: 2px; }

/* Hover/focus treatment. Transform and opacity only. */
.t-full .card .art { transition: transform var(--speed) var(--ease), box-shadow var(--speed) var(--ease); }
.t-full .card:hover .art,
.t-full .card:focus-visible .art { transform: translateY(-6px) scale(1.03); box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55); }
.t-lite .card:hover .art { border-color: rgba(255, 255, 255, 0.3); }

.card .play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.6rem;
  color: #fff;
  background: rgba(8, 8, 12, 0.5);
  opacity: 0;
  transition: opacity var(--speed) var(--ease);
}
@media (hover: hover) {
  .t-full .card:hover .play, .t-full .card:focus-visible .play { opacity: 1; }
}

.badge {
  position: absolute;
  top: 8px; left: 8px;
  padding: 3px 8px;
  border-radius: var(--r-sm);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  background: rgba(8, 8, 12, 0.82);
  color: var(--text);
}
.badge.hot { background: var(--brand); }

/* Continue-watching progress. */
.bar { position: absolute; left: 0; right: 0; bottom: 0; height: 4px; background: rgba(255, 255, 255, 0.22); }
.bar i { display: block; height: 100%; background: var(--brand); }

/* ==========================================================================
   Grid (library / search results)
   ========================================================================== */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--card-w), 1fr));
  gap: clamp(14px, 2vw, 22px);
  padding-inline: var(--gutter);
}
.grid .card { flex: none; width: auto; }

/* ==========================================================================
   Chips (filters)
   ========================================================================== */
.chips {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-inline: var(--gutter);
  scrollbar-width: none;
}
.chips::-webkit-scrollbar { display: none; }
.chip {
  flex: 0 0 auto;
  padding: 9px 16px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--raised);
  font-size: 0.87rem;
  font-weight: 600;
  color: var(--dim);
  white-space: nowrap;
  transition: background var(--speed) var(--ease), color var(--speed) var(--ease);
}
.chip:hover { color: var(--text); }
.chip.on { background: var(--text); color: #0b0b10; border-color: transparent; }

/* ==========================================================================
   Search
   ========================================================================== */
.search {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 46px;
  padding: 0 16px;
  border-radius: var(--r-pill);
  background: var(--raised);
  border: 1px solid var(--line);
}
.search input { flex: 1; background: none; border: 0; font-size: 0.95rem; min-width: 0; }
.search input::placeholder { color: var(--muted); }
.search:focus-within { border-color: rgba(255, 255, 255, 0.28); }

/* ==========================================================================
   Stats strip
   ========================================================================== */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1px;
  margin-inline: var(--gutter);
  background: var(--line-soft);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.stats div { background: var(--surface); padding: 22px 18px; text-align: center; }
.stats b { display: block; font-size: clamp(1.3rem, 3vw, 1.8rem); font-weight: 800; letter-spacing: -0.02em; }
.stats span { font-size: 0.8rem; color: var(--muted); }

/* ==========================================================================
   Skeletons
   ========================================================================== */
.sk { background: var(--raised); border-radius: var(--r-sm); position: relative; overflow: hidden; }
.sk.art-sk { aspect-ratio: 2 / 3; border-radius: var(--r); }
.sk.line { height: 11px; margin-top: 9px; }
.sk.line.short { width: 55%; }

/* The shimmer is a single transform animation on one pseudo-element, and it
   only exists on the capable tier. On a weak device a static grey box is
   better than an animation competing with the page it is waiting for. */
.t-full .sk::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.06), transparent);
  transform: translateX(-100%);
  animation: sweep 1.35s infinite;
}
@keyframes sweep { to { transform: translateX(100%); } }

/* ==========================================================================
   Reveal on scroll (full tier only)
   ========================================================================== */
.t-full .reveal { opacity: 0; transform: translateY(16px); transition: opacity 520ms var(--ease), transform 520ms var(--ease); }
.t-full .reveal.in { opacity: 1; transform: none; }

/* ==========================================================================
   Empty / error states
   ========================================================================== */
.empty { padding: clamp(40px, 8vw, 80px) var(--gutter); text-align: center; color: var(--muted); }
.empty .ico { font-size: 2.6rem; margin-bottom: 12px; }
.empty h3 { color: var(--text); font-size: 1.15rem; margin-bottom: 8px; }

/* ==========================================================================
   Footer
   ========================================================================== */
.foot {
  border-top: 1px solid var(--line-soft);
  padding: 40px var(--gutter) calc(40px + env(safe-area-inset-bottom));
  color: var(--muted);
  font-size: 0.86rem;
}
.foot-in { display: flex; flex-wrap: wrap; gap: 18px 32px; align-items: center; justify-content: space-between; }
.foot nav { display: flex; flex-wrap: wrap; gap: 8px 20px; }
.foot a:hover { color: var(--text); }

/* ==========================================================================
   Mobile navigation

   A website, not an app shell.

   This used to be a fixed bottom tab bar. That is an app convention, and on a
   site it costs a permanent strip of the screen, hides the footer behind
   itself, and reads as a wrapped native app rather than a page you landed on.

   What replaces it is ordinary web navigation: the sections live in the
   header, in a scrollable row of pills, always visible. Nothing is hidden
   behind a hamburger, because with only three sections a menu would add a tap
   to reach something that fits on screen anyway. Hidden navigation is the
   thing people actually complain about.
   ========================================================================== */
.tabbar { display: none !important; }

/* The section row. Desktop keeps them inline in .nav; narrow screens get
   their own row underneath so the logo and account controls keep their space. */
.navrow { display: none; }

@media (max-width: 860px) {
  .nav { display: none; }

  .navrow {
    position: fixed;
    top: var(--topbar-total);
    left: 0; right: 0;
    z-index: 99;
    display: flex;
    gap: 8px;
    padding: 9px var(--gutter) 11px;
    overflow-x: auto;
    scrollbar-width: none;
    background: rgba(8, 8, 12, 0.94);
    border-bottom: 1px solid var(--line-soft);
    /* Its own layer: it sits above scrolling content and, on the full tier,
       carries a backdrop-filter that would otherwise repaint constantly. */
    transform: translateZ(0);
  }
  .navrow::-webkit-scrollbar { display: none; }

  .navrow a {
    flex: 0 0 auto;
    padding: 9px 16px;
    border-radius: var(--r-pill);
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--dim);
    background: var(--raised);
    border: 1px solid var(--line);
    white-space: nowrap;
  }
  .navrow a.on {
    color: var(--brand-ink);
    background: var(--brand-grad);
    border-color: transparent;
  }
  .t-lite .navrow a.on { background: var(--brand); }

  /* Content clears the header AND the section row. */
  body { --nav-total: calc(var(--topbar-total) + 52px); }
  main { padding-top: 52px; }
  .hero { padding-top: calc(var(--topbar-total) + 52px + 150px); }
  .filters { top: calc(var(--topbar-total) + 52px) !important; }

  /* No fixed bar at the bottom any more, so the page ends where it ends. */
  body { padding-bottom: 0; }
}

@media (max-width: 560px) {
  :root {
    --topbar-h: 58px;
    /*
     * Three posters across on a phone instead of two.
     *
     * The desktop minimum of 126px only fits twice in a 375px viewport, which
     * wastes a third of the screen and halves how much of the catalogue is
     * visible per scroll. 104px gives three columns at 375px and four on a
     * larger phone, which is what every streaming app does — and in a rail it
     * leaves part of a fourth card peeking, so it reads as scrollable without
     * needing an arrow.
     */
    --card-w: 104px;
  }
  .hero { min-height: 0; padding-top: calc(var(--topbar-total) + 150px); }
  .hero h1 { font-size: clamp(1.8rem, 8vw, 2.4rem); }
  .hero p { -webkit-line-clamp: 2; font-size: 0.93rem; }
  .hero-btns .btn { flex: 1 1 auto; }
}

/* ==========================================================================
   Components owned by app.js

   These are injected from JavaScript (the verify-email modal, the guest
   playback gate, toasts, the install banner, the top loading bar). They used
   to be styled by styles.css/ui.css, which this stylesheet replaces, so their
   styles live here now — otherwise the first time a viewer hits the verify
   modal they would get an unstyled stack of text.
   ========================================================================== */

/* Top loading bar */
.tk-loadbar {
  position: fixed; left: 0; top: 0; height: 3px; z-index: 300;
  background: linear-gradient(90deg, var(--brand-2), var(--brand));
  transition: width 220ms var(--ease), opacity 220ms var(--ease);
}
.t-lite .tk-loadbar { background: var(--brand); }

/* Toasts */
.toast, .gate-toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 300; max-width: min(92vw, 520px);
  padding: 13px 22px; border-radius: var(--r-pill);
  background: var(--raised-2); border: 1px solid var(--line);
  color: var(--text); font-size: 0.9rem; text-align: center;
}
.t-full .toast, .t-full .gate-toast { box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5); }
@media (max-width: 860px) {
  .toast, .gate-toast { bottom: calc(var(--tabbar-h) + 20px + env(safe-area-inset-bottom)); }
}

/* Modal shell — used by the verify-email dialog and the guest gate */
.vmodal-bg {
  position: fixed; inset: 0; z-index: 280;
  display: flex; align-items: center; justify-content: center;
  padding: 20px; background: rgba(4, 4, 8, 0.74);
  opacity: 0; transition: opacity 200ms var(--ease);
}
.vmodal-bg.open { opacity: 1; }
.t-full .vmodal-bg { backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); }

.vmodal, .gate-card {
  width: 100%; max-width: 440px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: clamp(24px, 5vw, 36px);
  text-align: center;
}
.t-full .vmodal, .t-full .gate-card { box-shadow: 0 28px 80px rgba(0, 0, 0, 0.6); }

.btn-block { display: flex; width: 100%; }

.gate-benefits { list-style: none; text-align: left; margin: 18px 0; display: grid; gap: 9px; }
.gate-benefits li { color: var(--dim); font-size: 0.91rem; }
.gate-note { color: var(--muted); font-size: 0.8rem; margin-top: 14px; }

/* "Verify your email" strip */
.resume-note {
  display: flex; gap: 10px; align-items: center;
  margin: 0 var(--gutter) 20px; padding: 13px 18px;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface); color: var(--dim); font-size: 0.88rem;
}
.resume-note a { color: var(--brand-2); font-weight: 700; }

/* Install banner */
.pwa-banner {
  position: fixed; left: 12px; right: 12px; bottom: 12px; z-index: 250;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px; border-radius: var(--r);
  background: var(--raised); border: 1px solid var(--line);
}
.t-full .pwa-banner { box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55); }
@media (max-width: 860px) {
  .pwa-banner { bottom: calc(var(--tabbar-h) + 12px + env(safe-area-inset-bottom)); }
}

/* ==========================================================================
   Per-section identity

   The wordmark — and the accent that follows it through buttons, chips and
   focus rings — takes on the section you are in. Movies keep the house red,
   Live TV goes blue, Sports goes green.

   It is done by re-pointing the brand tokens on a `data-section` attribute on
   <body>, so ONE declaration per section retints everything downstream. No
   component needs to know which section it is in.
   ========================================================================== */

:root { --brand-grad: linear-gradient(95deg, var(--brand-2), var(--brand)); }

[data-section="channels"] {
  --brand:   #2f6bff;
  --brand-2: #35a7ff;
  --brand-grad: linear-gradient(95deg, #35a7ff, #6b5cff 62%, #8b5cf6);
}
[data-section="sports"] {
  --brand:   #17a94b;
  --brand-2: #52d17a;
  --brand-grad: linear-gradient(95deg, #52d17a, #17a94b 58%, #0f8f3d);
}

/*
 * background-IMAGE, not the background shorthand.
 *
 * The shorthand resets every background-* longhand it does not mention, which
 * includes the -webkit-background-clip: text set on .logo b further up. Using
 * it here wiped the clip and the wordmark rendered as a solid coloured block
 * over the text instead of gradient-filled letters.
 */
.logo b { background-image: var(--brand-grad); }
.btn-primary { background: var(--brand-grad); }
.t-full .btn-primary { box-shadow: 0 6px 22px color-mix(in srgb, var(--brand) 34%, transparent); }
/* color-mix is recent; a browser without it simply gets no glow, which is the
   same thing the reduced tier gets. */
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  .t-full .btn-primary { box-shadow: 0 6px 22px rgba(0, 0, 0, 0.4); }
}
.tk-loadbar { background: var(--brand-grad); }
.eyebrow { color: var(--brand-2); }
.bar i { background: var(--brand); }

/* The mark beside the wordmark. A single element, no image request. */
.logo .mark {
  display: inline-block;
  width: 15px; height: 15px;
  margin-right: 8px;
  border-radius: 4px;
  background: var(--brand-grad);
  vertical-align: -2px;
}
[data-section="sports"] .logo .mark { border-radius: 50%; }

/* The sports mark turns, slowly, and only where that is free:
   compositor-only rotation, full tier, motion not suppressed. */
.t-full [data-section="sports"] .logo .mark { animation: mark-spin 14s linear infinite; }
@keyframes mark-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .logo .mark { animation: none !important; } }
.t-lite .logo .mark { animation: none !important; }

/* A section-tinted wash behind the hero text. Static, painted once. */
.t-full .hero::before {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: min(62%, 780px); height: 78%;
  background: radial-gradient(120% 100% at 0% 100%,
              color-mix(in srgb, var(--brand) 16%, transparent), transparent 72%);
  pointer-events: none;
  z-index: 1;
}

/*
 * Section-aware hover.
 *
 * The base .btn-primary:hover further up hardcodes the red gradient, and
 * being a :hover rule it outranks the section tokens — so Live TV and Sports
 * buttons flashed red on hover. Re-stating it here in terms of the section
 * gradient, with a cheap inset highlight instead of a filter, keeps the tint
 * correct without a repaint-heavy effect.
 */
.btn-primary:hover,
.t-lite .btn-primary:hover {
  background: var(--brand-grad);
  box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.13);
}
.t-lite .btn-primary { background: var(--brand); }
.t-lite .btn-primary:hover { background: var(--brand); }

/* Section tint on the things that should follow the wordmark. */
.chip.on { background: var(--text); }
.nav a.on { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--brand) 40%, transparent); }
.pill.score { color: var(--ok); }

/*
 * The scroll sentinel.
 *
 * Given a real height and taken out of the flow, it becomes a small buffer
 * zone rather than a zero-height line. On iOS the rubber-band overscroll at
 * the top of a page can cross a zero-height boundary repeatedly during a
 * single flick, toggling the bar between transparent and solid several times
 * — read as flickering. A few pixels of hysteresis removes that, and being
 * absolutely positioned it adds nothing to the layout.
 */
#top-sentinel {
  position: absolute;
  top: 0; left: 0;
  width: 1px; height: 10px;
  pointer-events: none;
}

/* ==========================================================================
   Liquid glass

   The web answer to what expo-glass-effect does natively on iOS 26. That
   package binds to UIGlassEffect through React Native and cannot run in a
   browser, so this is built from the parts the platform actually gives us.

   Real glass is not just a blur. Four things together sell it:

     1. blur, for the refraction
     2. saturate + brightness, because glass concentrates the colour behind it
        rather than washing it out. A plain blur looks like frosted plastic.
     3. a specular highlight along the top edge, which is the single cue that
        reads as "lit from above"
     4. a hairline border slightly brighter than the fill, for the cut edge

   All four are paint-time only and never animate, so the cost is one
   composite. Full tier only: backdrop-filter is the most expensive thing on
   this site, and the reduced tier gets the same shapes drawn opaque.
   ========================================================================== */
.t-full .glass,
.t-full .top.solid,
.t-full .navrow,
.t-full .filters {
  background:
    linear-gradient(to bottom, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.025));
  background-color: rgba(10, 10, 16, 0.55);
  backdrop-filter: blur(26px) saturate(190%) brightness(1.06);
  -webkit-backdrop-filter: blur(26px) saturate(190%) brightness(1.06);
  border-color: rgba(255, 255, 255, 0.11);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.20),   /* the lit top edge */
    inset 0 -1px 0 rgba(0, 0, 0, 0.30),
    0 10px 34px rgba(0, 0, 0, 0.34);
}

/* Browsers without backdrop-filter would get a nearly transparent bar over
   scrolling content, which is unreadable. Give them the solid fill instead. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .t-full .glass,
  .t-full .top.solid,
  .t-full .navrow,
  .t-full .filters { background-color: rgba(8, 8, 12, 0.96); }
}

/* A glass panel for surfaces that sit over artwork. */
.t-full .glass-card {
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.03));
  background-color: rgba(16, 16, 22, 0.5);
  backdrop-filter: blur(22px) saturate(180%);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 16px 44px rgba(0, 0, 0, 0.42);
}

/* ==========================================================================
   Page transitions

   Clicking a poster should carry that poster into the next page rather than
   blanking the screen and redrawing it.

   This is the View Transitions API doing cross-document transitions, which is
   the whole feature in about twenty lines and, crucially, costs nothing when
   unsupported: a browser that does not implement it simply navigates, exactly
   as before. No polyfill, no JS-driven FLIP animation to fall out of sync.

   js/fm.js stamps view-transition-name on the poster that was actually
   clicked, and the watch page puts the same name on its player. The browser
   matches them across the navigation and tweens between the two positions.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
  html.t-full {
    view-transition-name: none;   /* the root itself must not animate */
  }
  /* Opt this document in to cross-document transitions. */
  @view-transition { navigation: auto; }
}

/* The reduced tier opts out entirely: a full-screen cross-fade of two
   documents is exactly the kind of work a weak device cannot absorb. */
.t-lite { view-transition-name: none; }

::view-transition-group(fm-art) { animation-duration: 420ms; }
::view-transition-old(fm-art),
::view-transition-new(fm-art) {
  /* The poster and the player are different shapes, so let the browser scale
     rather than cross-fade two mismatched boxes. */
  animation: none;
  mix-blend-mode: normal;
  height: 100%;
  object-fit: cover;
}

/* Everything that is not the shared poster fades, quickly, so the page swap
   reads as one movement instead of two competing ones. */
::view-transition-old(root) { animation: fm-out 180ms cubic-bezier(0.4, 0, 1, 1) both; }
::view-transition-new(root) { animation: fm-in 260ms cubic-bezier(0, 0, 0.2, 1) both; }
@keyframes fm-out { to { opacity: 0; } }
@keyframes fm-in { from { opacity: 0; } }

/* ==========================================================================
   Entrance motion

   Rows lift in as they are scrolled to, and the cards inside them stagger.
   Transform and opacity only, so each one is a compositor job.
   ========================================================================== */
.t-full .row.in .card {
  animation: fm-rise 520ms var(--ease) both;
}
.t-full .row.in .card:nth-child(1) { animation-delay: 0ms; }
.t-full .row.in .card:nth-child(2) { animation-delay: 40ms; }
.t-full .row.in .card:nth-child(3) { animation-delay: 80ms; }
.t-full .row.in .card:nth-child(4) { animation-delay: 120ms; }
.t-full .row.in .card:nth-child(5) { animation-delay: 160ms; }
.t-full .row.in .card:nth-child(6) { animation-delay: 200ms; }
.t-full .row.in .card:nth-child(n+7) { animation-delay: 240ms; }

@keyframes fm-rise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

/* The hero copy arrives a beat after the artwork. */
.t-full .hero-in > * { animation: fm-rise 620ms var(--ease) both; }
.t-full .hero-in > *:nth-child(2) { animation-delay: 60ms; }
.t-full .hero-in > *:nth-child(3) { animation-delay: 110ms; }
.t-full .hero-in > *:nth-child(4) { animation-delay: 155ms; }
.t-full .hero-in > *:nth-child(5) { animation-delay: 195ms; }

/* Pressing anything should feel physical. */
.t-full .btn:active,
.t-full .chip:active,
.t-full .navrow a:active { transform: scale(0.96); }
.t-full .btn, .t-full .chip, .t-full .navrow a { transition: transform 120ms var(--ease), background var(--speed) var(--ease), border-color var(--speed) var(--ease); }

@media (prefers-reduced-motion: reduce) {
  .t-full .row.in .card,
  .t-full .hero-in > * { animation: none !important; }
}
