/* ═══════════════════════════════════════════════════════════════
   Nova IPTV — web build

   The palette, type and component shapes are ported from the Android app
   (ui/theme/Theme.kt and the embedded panel in data/web/WebAdminServer.kt) so
   the two builds read as the same product. Colour names match the Kotlin ones.
   ═══════════════════════════════════════════════════════════════ */

:root {
  /* ── Nova industrial dark palette (Theme.kt) ── */
  --bg: #0A0E14;
  --surface: #111722;
  --surface2: #182030;
  --surface3: #1F2A3D;
  --accent: #00E5A0;
  --accent-dim: #00B27D;
  --blue: #2D8CFF;
  --amber: #FFB02E;
  --red: #FF4D5E;
  --text: #E8EDF4;
  --dim: #8A95A8;
  --border: #253047;

  --sans: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --r-sm: 8px;
  --r-md: 10px;
  --r-lg: 16px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Registered so the conic-gradient rings can animate their angle — an
   unregistered custom property is untyped and cannot be interpolated, so
   without this the rings sit still. Browsers without @property render a
   static ring, which is a perfectly good resting state. */
@property --nv-ang {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
@keyframes nvSpin { to { --nv-ang: 360deg; } }

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0; height: 100%;
  background: var(--bg); color: var(--text);
  font-family: var(--sans); line-height: 1.5;
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
}

/* ── Themed scrollbars ────────────────────────────
   The default light-grey scrollbar is the one piece of chrome that gave the
   dark UI away, and on the browse panes there are three of them on screen at
   once. Firefox gets the two-colour form it supports; Chromium gets the full
   treatment — a thumb drawn inside a transparent border, so it reads as a
   floating capsule rather than filling the gutter edge to edge.

   The two forms are mutually exclusive, not complementary: naming
   scrollbar-color anywhere makes Chromium ignore ::-webkit-scrollbar for that
   scroller and draw a plain default-width bar instead. scrollbar-color is an
   inherited property, so setting it on html alone was enough to disable every
   rule below it. Hence the feature query: Firefox, which has no
   ::-webkit-scrollbar to match, is the only engine that takes that branch. */
@supports not selector(::-webkit-scrollbar) {
  html { scrollbar-width: thin; scrollbar-color: var(--accent-dim) var(--surface); }
}

*::-webkit-scrollbar { width: 12px; height: 12px; }
*::-webkit-scrollbar-track {
  background: linear-gradient(180deg, rgba(10,14,20,.92), rgba(17,23,34,.92));
  border-radius: 99px;
}
*::-webkit-scrollbar-thumb {
  border-radius: 99px;
  border: 3px solid transparent;
  background: linear-gradient(180deg, var(--accent), var(--accent-dim)) padding-box;
  box-shadow: inset 0 0 8px rgba(0,0,0,.45);
}
*::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, #6BFFD0, var(--accent)) padding-box;
}
*::-webkit-scrollbar-corner { background: transparent; }

/* ── Atmosphere ───────────────────────────────────
   Two inert full-viewport layers behind everything: coloured pools in the
   corners that keep the large flat darks from reading as a void, and a fine
   grain that stops those same darks banding on cheap panels. Fixed to the
   viewport, so they stay put while a screen scrolls. Attached to body rather
   than #app because #app is the scrolling flex column. */
body::before, body::after {
  content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
}
body::before {
  background:
    radial-gradient(circle at 8% 8%,   rgba(0,229,160,.13), transparent 26%),
    radial-gradient(circle at 92% 4%,  rgba(45,140,255,.11), transparent 24%),
    radial-gradient(circle at 78% 96%, rgba(255,176,46,.08), transparent 26%),
    radial-gradient(circle at 18% 92%, rgba(255,77,94,.07),  transparent 24%);
}
body::after {
  opacity: .045; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* NovaGradient — Brush.verticalGradient(#0D1320 → NovaBg), now translucent so
   the atmosphere behind it shows through instead of being painted over. */
/* A hard 100% height, not min-height. With min-height the column grew to fit
   its contents, so #screen-browse became as tall as its channel list (7,700px
   for one page of rows) and the window scrolled as a whole — the head and the
   category sidebar scrolled away with it, and .cats/.chans never used the
   overflow-y:auto they each declare. Capping the column to the viewport is
   what makes those panes scroll independently, as the three-pane layout
   intends. Screens that legitimately run long opt back in below. */
#app {
  position: relative; z-index: 1;
  height: 100%;
  background: linear-gradient(180deg, rgba(13,19,32,.82), rgba(10,14,20,.90));
  display: flex; flex-direction: column;
}

button { font-family: inherit; cursor: pointer; }
img { max-width: 100%; }

.hidden { display: none !important; }

/* ── Screens ──────────────────────────────────── */
.screen { display: none; flex: 1; min-height: 0; flex-direction: column; }
.screen.active { display: flex; }
/* The one screen that is a document rather than a pane layout: its form is
   taller than a short window, so it scrolls itself now that #app cannot. */
#screen-sources { overflow-y: auto; }

/* ── Top bar ──────────────────────────────────── */
.topbar {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 28px; flex-wrap: wrap;
  position: relative;
}
/* A hairline of accent light along the bottom of the bar, brightest under the
   wordmark and fading out to nothing at both ends so it never terminates on a
   hard edge. */
.topbar::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: linear-gradient(90deg,
    transparent, color-mix(in srgb, var(--accent) 55%, transparent) 18%,
    color-mix(in srgb, var(--blue) 35%, transparent) 60%, transparent);
  opacity: .7; pointer-events: none;
}
.wordmark { font-family: var(--mono); font-size: 24px; letter-spacing: -0.5px; white-space: nowrap; }
.wordmark b {
  color: var(--accent); font-weight: 900;
  text-shadow: 0 0 18px rgba(0,229,160,.55), 0 0 42px rgba(0,229,160,.25);
}
.wordmark span { color: var(--text); font-weight: 300; }

/* Playlist chip — the "switch source" affordance from the Compose top bar. */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: 20px; padding: 5px 12px;
  color: var(--dim); font-size: 12px; cursor: pointer;
  transition: border-color .2s, color .2s;
  max-width: 260px;
}
.chip:hover { border-color: var(--accent); color: var(--text); }
.chip svg { width: 14px; height: 14px; fill: var(--accent); flex: 0 0 auto; }
.chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.spacer { flex: 1; }
.clock {
  font-family: var(--mono); font-size: 14px; letter-spacing: .8px;
  color: var(--dim); white-space: nowrap;
}

.icon-btn {
  width: 40px; height: 40px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: none; color: var(--dim);
  transition: background .2s, color .2s;
}
.icon-btn:hover { background: var(--surface2); color: var(--text); }
.icon-btn svg { width: 20px; height: 20px; fill: currentColor; }

/* ── Dashboard ────────────────────────────────────
   Was an inline style on the element. It lives here now because the rails
   below rely on this being the flex column they can push themselves to the
   foot of, and because a short window needs somewhere to scroll: without
   overflow, a laptop screen clipped the second rail with no way to reach it. */
#home-body { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto; }

/* ── Dashboard tiles ──────────────────────────── */
/* The Compose tiles take the dashboard's leftover height via weight(1f), which
   suits a TV. Here they grow the same way but stop at max-height, so on a tall
   desktop window they read as four generous panels rather than four near-full
   -height columns — and whatever is left over goes to the gap above the docked
   rails instead of stranding the tiles at a fixed height with a void below. */
/* The top padding is structural, not spacing: #home-body scrolls, so it clips,
   and a hovered tile lifts 6px and scales past its own box by another ~6px.
   Without room above, the top edge of the hovered card was sliced off. The
   28px sides already cover the horizontal growth. */
.tiles {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
  padding: 16px 28px 0; flex: 1 1 auto; min-height: 216px; max-height: 436px;
}
.tile {
  position: relative; overflow: hidden; text-align: left; isolation: isolate;
  /* Content sits top-left, as Alignment.TopStart does in the Kotlin tile — a
     button would otherwise centre it. */
  display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start;
  border-radius: var(--r-lg); padding: 22px;
  /* TileGradient — Brush.linearGradient(NovaSurface2 → NovaSurface), with the
     tile's own colour pooled into the top-left corner so the four tiles read
     as four different things at a glance rather than four grey boxes. */
  background:
    radial-gradient(280px circle at var(--mx, 12%) var(--my, 10%), color-mix(in srgb, var(--tint) 20%, transparent), transparent 66%),
    radial-gradient(120% 110% at 0% 0%, color-mix(in srgb, var(--tint) 12%, transparent), transparent 62%),
    linear-gradient(135deg, var(--surface2), var(--surface));
  border: 1px solid color-mix(in srgb, var(--tint) 45%, transparent);
  box-shadow: 0 12px 30px rgba(0,0,0,.45);
  transition: transform .35s var(--spring), border-color .25s, box-shadow .35s var(--ease);
  color: inherit;
}
/* Lift and light, without the old border-width swap: growing the border from
   1px to 3px and shrinking the padding to compensate made the whole card's
   contents twitch on hover. A ring drawn as a shadow costs no layout. */
.tile:hover, .tile:focus-visible {
  transform: translateY(-6px) scale(1.03); outline: none;
  border-color: color-mix(in srgb, var(--tint) 85%, transparent);
  box-shadow:
    0 26px 54px rgba(0,0,0,.60),
    0 0 40px color-mix(in srgb, var(--tint) 32%, transparent),
    inset 0 0 70px color-mix(in srgb, var(--tint) 10%, transparent);
}

/* A rotating conic ring, drawn as a border-thick frame by masking the middle
   out of a full-bleed gradient. It only spins while hovered — four of these
   turning at once on an idle dashboard would be noise, not atmosphere. */
.tile::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1.5px; pointer-events: none;
  background: conic-gradient(from var(--nv-ang),
    transparent 0deg, color-mix(in srgb, var(--tint) 70%, transparent) 60deg,
    #FFFFFF 100deg, color-mix(in srgb, var(--tint) 70%, transparent) 140deg,
    transparent 210deg, transparent 360deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0; transition: opacity .3s var(--ease);
}
.tile:hover::before, .tile:focus-visible::before {
  opacity: 1; animation: nvSpin 4.5s linear infinite;
}

/* One pass of light across the card as the pointer arrives. */
.tile::after {
  content: ''; position: absolute; top: -20%; bottom: -20%; left: -70%; width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.10), transparent);
  transform: skewX(-18deg); pointer-events: none; opacity: 0;
}
.tile:hover::after { opacity: 1; animation: nvSheen 1s var(--ease); }
@keyframes nvSheen { to { left: 135%; } }

/* The oversized ghost glyph in the bottom corner of each Compose tile.
   It used to be pushed past the tile's edges (right:-10px, bottom:-18px) and
   the tile clips its overflow, so every glyph was sliced off mid-stroke —
   four half-drawn icons that read as broken artwork rather than watermarks.
   It now sits wholly inside the card, and is faded out toward its top-left so
   it dissolves into the tile instead of ending on a hard outline. */
.tile .ghost {
  position: absolute; right: 16px; bottom: 14px;
  width: 104px; height: 104px; fill: var(--tint); opacity: .18;
  pointer-events: none;
  filter: drop-shadow(0 0 16px color-mix(in srgb, var(--tint) 45%, transparent));
  -webkit-mask-image: radial-gradient(115% 115% at 85% 85%, #000 42%, rgba(0,0,0,.35) 78%, transparent 100%);
          mask-image: radial-gradient(115% 115% at 85% 85%, #000 42%, rgba(0,0,0,.35) 78%, transparent 100%);
  transition: transform .45s var(--spring), opacity .3s var(--ease);
}
.tile:hover .ghost, .tile:focus-visible .ghost {
  transform: translateY(-4px) scale(1.07) rotate(-4deg); opacity: .3;
}

.tile .badge {
  width: 46px; height: 46px; border-radius: var(--r-md);
  background: color-mix(in srgb, var(--tint) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--tint) 30%, transparent);
  display: flex; align-items: center; justify-content: center; margin-bottom: 14px;
  transition: transform .4s var(--spring), box-shadow .3s var(--ease);
}
.tile:hover .badge, .tile:focus-visible .badge {
  transform: translateY(-2px) scale(1.06);
  box-shadow: 0 0 22px color-mix(in srgb, var(--tint) 45%, transparent);
}
.tile .badge svg {
  width: 26px; height: 26px; fill: var(--tint);
  filter: drop-shadow(0 0 8px color-mix(in srgb, var(--tint) 60%, transparent));
}
.tile h2 {
  font-family: var(--mono); font-weight: 700; font-size: 20px;
  margin: 0 0 2px; color: var(--text); white-space: nowrap;
  letter-spacing: .04em;
  text-shadow: 0 0 26px color-mix(in srgb, var(--tint) 45%, transparent);
}
.tile p {
  margin: 0; font-size: 12px; color: var(--dim);
  font-family: var(--mono); letter-spacing: .05em;
}

/* ── Reorder handle ───────────────────────────────
   touch-action:none is load-bearing, not a nicety: without it a drag that
   starts on the handle is claimed by the page's own scrolling and the tile
   never moves on a phone at all. */
.tile .grip {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  width: 30px; height: 30px; border-radius: var(--r-md);
  display: flex; align-items: center; justify-content: center;
  cursor: grab; touch-action: none;
  background: color-mix(in srgb, var(--tint) 12%, rgba(0,0,0,.42));
  border: 1px solid color-mix(in srgb, var(--tint) 32%, transparent);
  opacity: 0;
  transition: opacity .25s var(--ease), background .2s var(--ease), border-color .2s var(--ease);
}
/* Hidden until the tile is hovered or focused — four permanent handles on an
   idle dashboard read as clutter on what is meant to be four clean panels. */
.tile:hover .grip, .tile:focus-visible .grip, .tile.nv-drag .grip { opacity: 1; }
.tile .grip:hover {
  background: color-mix(in srgb, var(--tint) 26%, rgba(0,0,0,.42));
  border-color: color-mix(in srgb, var(--tint) 65%, transparent);
}
.tile .grip svg { width: 15px; height: 15px; fill: var(--tint); opacity: .9; }
/* No hover to reveal them on a touch screen, so they simply stay visible. */
@media (hover: none) { .tile .grip { opacity: .85; } }

.tiles.nv-dragging { cursor: grabbing; }
.tile.nv-drag {
  z-index: 5; cursor: grabbing;
  /* The pointer sets this tile's transform on every move; a transition on it
     would chase the pointer a third of a second behind. */
  transition: none;
  border-color: color-mix(in srgb, var(--tint) 90%, transparent);
  box-shadow:
    0 34px 70px rgba(0,0,0,.70),
    0 0 48px color-mix(in srgb, var(--tint) 38%, transparent);
}
.tile.nv-drag .grip { cursor: grabbing; }
/* The hover lift is a transform too, so on the tiles being displaced it would
   fight the FLIP animation that shows them moving aside. */
.tiles.nv-dragging .tile:not(.nv-drag):hover { transform: none; }
.tiles.nv-dragging .tile::after { display: none; }
.tiles.nv-dragging .tile:not(.nv-drag):hover::before { opacity: 0; animation: none; }

/* ── Recents rails ────────────────────────────────
   `margin-top:auto` inside #home-body's flex column is what drops the whole
   recents block — CONTINUE WATCHING and RECENTLY WATCHED — to the foot of the
   screen. Before this the rails sat directly under the tiles with the empty
   remainder of the page below them, which left the dashboard bottom-heavy in
   content and top-heavy in ink. They now read as a dock: the tiles own the
   upper half, the rails are pinned to the floor. */
.rails {
  margin-top: auto; padding: 18px 28px 22px; position: relative;
}
/* Fades in from the page rather than sitting on a drawn edge. */
.rails:not(:empty)::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px;
  background: linear-gradient(90deg, transparent,
    color-mix(in srgb, var(--accent) 40%, transparent) 30%,
    color-mix(in srgb, var(--blue) 28%, transparent) 70%, transparent);
  pointer-events: none;
}
.rails:not(:empty) {
  background: linear-gradient(180deg, transparent, rgba(0,0,0,.28));
}
.rail-title {
  font-family: var(--mono); font-size: 12px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--dim); margin: 14px 0 0;
  display: flex; align-items: center; gap: 10px;
}
/* A short lit bar in place of a bullet, so each rail has a visible anchor. */
.rail-title::before {
  content: ''; width: 16px; height: 2px; border-radius: 2px; flex: 0 0 auto;
  background: var(--accent);
  box-shadow: 0 0 10px rgba(0,229,160,.75);
}
.rail-title::after {
  content: ''; flex: 1; height: 1px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--border) 90%, transparent), transparent);
}
/* No scrollbar-width here. It is the same trap as scrollbar-color: setting
   either one on a scroller makes Chromium drop ::-webkit-scrollbar for it and
   draw a plain default bar — which is why these rails, the most visible
   scrollbars in the app, stayed unthemed while the browse panes did not.
   Firefox still gets `thin` from the feature query at the top of the file. */
/* The top padding is structural, like the one on .tiles. overflow-x:auto makes
   overflow-y computed `auto` as well, so the rail clips vertically too, and a
   card that lifts 6px on hover had its top edge sliced off against a box that
   was only 2px taller than the card. The rail-title above gives the space back
   by dropping its own bottom margin. */
.rail { display: flex; gap: 12px; overflow-x: auto; padding: 12px 0 10px; }
.rail::-webkit-scrollbar { height: 10px; }
.rail::-webkit-scrollbar-thumb { border-width: 2px; }

.rcard {
  position: relative; overflow: hidden; isolation: isolate;
  flex: 0 0 auto; width: 150px; padding: 11px; text-align: center;
  background: linear-gradient(160deg, var(--surface2), var(--surface));
  border: 1px solid var(--border); border-radius: var(--r-md);
  color: inherit;
  transition: border-color .2s, transform .35s var(--spring), box-shadow .35s var(--ease);
}
.rcard:hover, .rcard:focus-visible {
  outline: none;
  border-color: var(--accent); transform: translateY(-6px);
  box-shadow: 0 16px 30px rgba(0,0,0,.5), 0 0 24px rgba(0,229,160,.28);
}
.rcard .logo {
  width: 76px; height: 76px; margin: 0 auto; border-radius: var(--r-sm);
  background: var(--surface2); object-fit: contain; display: block;
  transition: transform .35s var(--spring), filter .3s var(--ease);
}
.rcard:hover .logo { transform: scale(1.07); filter: drop-shadow(0 0 12px rgba(0,229,160,.45)); }
.rcard .nm {
  font-size: 11px; color: var(--text); margin-top: 7px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Reserved even at zero, so cards never change height as the rail scrolls. */
.rcard .bar {
  height: 3px; margin-top: 7px; border-radius: 2px;
  background: var(--border); overflow: hidden;
}
.rcard .bar i {
  display: block; height: 100%; border-radius: 2px;
  background: linear-gradient(90deg, var(--accent-dim), var(--accent));
  box-shadow: 0 0 8px rgba(0,229,160,.7);
}

/* ── States ───────────────────────────────────── */
.state { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 40px; text-align: center; }
.state svg { width: 56px; height: 56px; fill: var(--dim); }
.state.err svg { fill: var(--red); }
.state.err p { color: var(--red); }
.state p { margin: 0; color: var(--dim); max-width: 40rem; }
.state .label { font-family: var(--mono); font-size: 13px; letter-spacing: .8px; }

.spinner {
  width: 44px; height: 44px; border-radius: 50%;
  border: 3px solid var(--border); border-top-color: var(--accent);
  animation: spin .9s linear infinite;
  box-shadow: 0 0 24px rgba(0,229,160,.30);
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Buttons ──────────────────────────────────── */
.btn {
  padding: 12px 22px; border-radius: var(--r-md); border: none;
  background: var(--accent); color: #000; font-weight: 700; font-size: 14px;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  transition: background .2s, transform .2s;
}
.btn:hover {
  background: var(--accent-dim); transform: translateY(-1px);
  box-shadow: 0 8px 22px rgba(0,0,0,.45), 0 0 22px rgba(0,229,160,.45);
}
.btn svg { width: 18px; height: 18px; fill: currentColor; }
.btn.ghost { background: var(--surface2); color: var(--text); border: 1.5px solid var(--border); }
.btn.ghost:hover { background: var(--surface3); border-color: var(--dim); }
.btn.danger { background: transparent; color: var(--red); border: 1.5px solid color-mix(in srgb, var(--red) 45%, transparent); }
.btn.danger:hover { background: color-mix(in srgb, var(--red) 12%, transparent); }
.btn[disabled] { opacity: .5; pointer-events: none; }

/* ── Forms ────────────────────────────────────── */
.field { margin-bottom: 14px; }
.field label {
  display: block; font-family: var(--mono); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--dim); margin-bottom: 6px;
}
input, select, textarea {
  width: 100%; padding: 12px 14px; border-radius: var(--r-md);
  background: var(--surface2); color: var(--text);
  border: 1.5px solid var(--border); font-family: inherit; font-size: 14px;
  outline: none; transition: border-color .2s, background .2s, box-shadow .2s;
}
input:focus, select:focus, textarea:focus {
  border-color: var(--accent); background: var(--surface3);
  box-shadow: 0 0 0 3px rgba(0, 229, 160, .15);
}
input::placeholder { color: var(--dim); }
select { color-scheme: dark; }

/* ── Source picker / playlists ────────────────── */
.wrap { max-width: 760px; margin: 0 auto; padding: 28px; width: 100%; }
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 24px; margin-bottom: 16px;
}
.card h3 { margin: 0 0 4px; font-family: var(--mono); font-size: 16px; color: var(--text); }
.card .hint { margin: 0 0 18px; font-size: 13px; color: var(--dim); }

.tabs { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; }
.tab {
  padding: 9px 16px; border-radius: 999px; font-size: 13px; font-weight: 600;
  background: var(--surface2); color: var(--dim); border: 1.5px solid var(--border);
  transition: all .2s;
}
.tab.active { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); border-color: var(--accent); }

.plist { list-style: none; margin: 0; padding: 0; }
.plist li {
  display: flex; align-items: center; gap: 12px; padding: 14px 16px;
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: var(--r-md); margin-bottom: 10px;
}
.plist li.active { border-color: var(--accent); }
.plist .meta { flex: 1; min-width: 0; }
.plist .meta b { display: block; font-size: 14px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plist .meta small { font-family: var(--mono); font-size: 11px; color: var(--dim); }
.kindtag {
  font-family: var(--mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 5px; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
}

/* ── Browse: three panes ──────────────────────── */
.browse-head {
  display: flex; align-items: center; gap: 10px;
  background: linear-gradient(180deg, var(--surface2), var(--surface));
  padding: 8px 12px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
  position: relative; z-index: 1;
}
.browse-head h2 {
  font-family: var(--mono); font-size: 20px; color: var(--accent); margin: 0; white-space: nowrap;
  letter-spacing: .05em; text-shadow: 0 0 22px rgba(0,229,160,.45);
}
.browse-head .count { color: var(--dim); font-size: 13px; }
.search { width: 260px; max-width: 45vw; }

.panes { display: flex; flex: 1; min-height: 0; }
.cats {
  width: 230px; flex: 0 0 auto; background: var(--surface);
  overflow-y: auto; padding: 8px 0; border-right: 1px solid var(--border);
}
.cat {
  display: block; width: 100%; text-align: left; border: none; background: transparent;
  padding: 11px 16px; color: var(--dim); font-size: 13px;
  border-left: 3px solid transparent;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cat:hover { background: var(--surface2); color: var(--text); }
.cat.sel {
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 14%, transparent), var(--surface3) 65%);
  color: var(--accent); border-left-color: var(--accent);
  box-shadow: inset 6px 0 14px -8px rgba(0,229,160,.9);
}

.chans { flex: 1; min-width: 0; overflow-y: auto; padding: 8px; }
.chans.grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px; align-content: start;
}

.chan {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 10px 12px; border-radius: var(--r-md);
  background: transparent; border: 1px solid transparent; color: inherit; text-align: left;
  transition: background .15s, border-color .15s;
}
.chan:hover, .chan:focus-visible {
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 11%, transparent), var(--surface2) 32%);
  border-color: color-mix(in srgb, var(--accent) 38%, transparent); outline: none;
}
.chan .num {
  font-family: var(--mono); font-size: 12px; color: var(--dim);
  min-width: 42px; text-align: right; flex: 0 0 auto;
}
.chan .logo {
  width: 42px; height: 42px; flex: 0 0 auto; border-radius: var(--r-sm);
  background: var(--surface2); object-fit: contain;
}
.chan .info { flex: 1; min-width: 0; }
.chan .info b { display: block; font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chan .info small { font-size: 11px; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.star { background: none; border: none; padding: 6px; color: var(--border); flex: 0 0 auto; }
.star svg { width: 18px; height: 18px; fill: currentColor; }
.star.on { color: var(--amber); }
.star:hover { color: var(--amber); }

/* Grid variant — poster-ish cards for movies and series. */
.chans.grid .chan {
  flex-direction: column; align-items: stretch; text-align: center;
  background: var(--surface); border-color: var(--border); padding: 12px;
}
.chans.grid .chan .num { display: none; }
.chans.grid .chan .logo { width: 100%; height: 110px; }
.chans.grid .chan .info b { white-space: normal; font-size: 13px; }
.chans.grid .star { position: absolute; }

/* ── Player ───────────────────────────────────── */
#screen-player { background: #000; }
.player-stage { position: relative; flex: 1; min-height: 0; background: #000; overflow: hidden; }
.player-stage video { width: 100%; height: 100%; display: block; background: #000; }
/* Aspect modes mirror the app's fit / fill / zoom toggle. */
.player-stage video[data-fit="fit"]  { object-fit: contain; }
.player-stage video[data-fit="fill"] { object-fit: fill; }
.player-stage video[data-fit="zoom"] { object-fit: cover; }

.player-overlay {
  position: absolute; inset: 0; z-index: 3; display: flex; flex-direction: column;
  justify-content: space-between; pointer-events: none;
  opacity: 1; transition: opacity .35s var(--ease);
}
.player-overlay.idle { opacity: 0; }
.player-overlay > * { pointer-events: auto; }

.pl-top, .pl-bottom {
  display: flex; align-items: center; gap: 12px; padding: 16px 20px;
}
.pl-top { background: linear-gradient(180deg, rgba(3,6,11,.92), transparent); }
.pl-bottom { background: linear-gradient(0deg, rgba(3,6,11,.92), transparent); flex-wrap: wrap; }
.pl-title { font-family: var(--mono); font-size: 16px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-group { font-size: 12px; color: var(--dim); }
.pl-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 14px; border-radius: var(--r-md);
  background: rgba(24,32,48,.85); border: 1px solid var(--border); color: var(--text);
  font-size: 12px; font-family: var(--mono); letter-spacing: .04em;
  transition: background .2s, border-color .2s;
}
.pl-btn:hover { background: var(--surface3); border-color: var(--accent); }
.pl-btn svg { width: 16px; height: 16px; fill: currentColor; }
/* Targeted by id rather than as .pl-btn.on: #pl-fav is also a .pl-btn and
   carries .on for "favourited", where the amber from .star.on is correct. */
#pl-pip.on {
  color: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 16px color-mix(in srgb, var(--accent) 35%, transparent);
}

/* Sits under the control bars and passes clicks through to them. A failed
   channel used to be a trap: the scrim covered BACK, PREV and NEXT, so there
   was no way out of a dead stream except the browser's own back button. */
.player-busy {
  position: absolute; inset: 0; z-index: 2; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 14px;
  background: rgba(3,6,11,.72); text-align: center; padding: 24px;
  pointer-events: none;
}
.player-busy .btn { pointer-events: auto; }
.player-busy p { margin: 0; color: var(--dim); max-width: 34rem; }
.player-busy .label { font-family: var(--mono); font-size: 13px; letter-spacing: .8px; }

/* In-player channel drawer */
.drawer {
  position: absolute; top: 0; right: 0; bottom: 0; width: 340px; max-width: 86vw;
  background: rgba(10,14,20,.97); border-left: 1px solid var(--border);
  transform: translateX(100%); transition: transform .3s var(--ease);
  display: flex; flex-direction: column; z-index: 5;
}
.drawer.open { transform: none; }
.drawer header {
  display: flex; align-items: center; gap: 10px; padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
.drawer header b { font-family: var(--mono); font-size: 13px; letter-spacing: .08em; color: var(--accent); }
.drawer .list { flex: 1; overflow-y: auto; padding: 8px; }
.drawer .chan .logo { width: 34px; height: 34px; }
.drawer .chan.playing { background: var(--surface3); }

/* ── Toast ────────────────────────────────────── */
#toast {
  position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%) translateY(20px);
  background: var(--surface3); border: 1px solid var(--border); color: var(--text);
  padding: 12px 20px; border-radius: var(--r-md); font-size: 14px;
  opacity: 0; pointer-events: none; transition: opacity .25s, transform .25s var(--ease);
  z-index: 100; max-width: 90vw; text-align: center;
}
#toast.show { opacity: 1; transform: translateX(-50%); }
#toast.err { border-color: var(--red); color: #ffd7db; }

/* ── Narrow screens ───────────────────────────── */
@media (max-width: 900px) {
  /* Two rows of tiles need the height the four-across cap denied them. */
  .tiles { grid-template-columns: repeat(2, 1fr); min-height: 340px; max-height: none; }
}
@media (max-width: 700px) {
  .topbar { padding: 12px 16px; gap: 8px; }
  .wordmark { font-size: 19px; }
  .clock { display: none; }
  .tiles { padding: 12px 16px 0; gap: 10px; }
  .tile { padding: 16px; }
  .tile h2 { font-size: 16px; }
  .tile .badge { width: 38px; height: 38px; margin-bottom: 10px; }
  .tile .ghost { width: 76px; height: 76px; right: 10px; bottom: 10px; }
  .rails { padding: 14px 16px 18px; }
  .cats { width: 132px; }
  .cat { padding: 10px 10px; font-size: 12px; }
  .search { width: 100%; max-width: none; order: 10; }
  .wrap { padding: 16px; }
  .card { padding: 18px; }
  .drawer { width: 300px; }
}

@media (prefers-reduced-motion: reduce) {
  .tile, .rcard, .drawer, .player-overlay, #toast,
  .tile .ghost, .tile .badge, .rcard .logo { transition: none; }
  .tile:hover, .tile:focus-visible, .rcard:hover,
  .tile:hover .ghost, .tile:focus-visible .ghost,
  .tile:hover .badge, .tile:focus-visible .badge,
  .rcard:hover .logo { transform: none; }
  /* The ring still appears on hover, it just stops turning; the sheen is a
     pure motion effect and has nothing to show when held still, so it goes. */
  .tile:hover::before, .tile:focus-visible::before { animation: none; }
  .tile::after, .tile:hover::after { display: none; animation: none; }
  .spinner { animation-duration: 2s; }
}
