/* ─────────────────────────────────────────
   Xtream-Masters WebPlayer - shared tokens + base
───────────────────────────────────────── */
:root{
  --bg-primary:#0b0e14;
  --bg-secondary:#111520;
  --bg-panel:#111520;
  --bg-card:#161b28;
  --bg-hover:#1c2235;
  --bg-input:#1c2235;
  --bg-input-focus:#212840;
  --accent:#3b82f6;
  --accent-hover:#2563eb;
  --accent-dim:rgba(59,130,246,0.15);
  --accent-glow:rgba(59,130,246,0.2);
  --text-primary:#f0f2f5;
  --text-secondary:#8b90a0;
  --text-muted:#505672;
  --border:rgba(255,255,255,0.06);
  --border-strong:rgba(255,255,255,0.12);
  --border-focus:rgba(59,130,246,0.5);
  --success:#22c55e;
  --success-dim:rgba(34,197,94,0.12);
  --error:#ef4444;
  --error-dim:rgba(239,68,68,0.12);
  --warning:#f59e0b;
  --warning-dim:rgba(245,158,11,0.12);
  --purple:#a78bfa;
  --purple-dim:rgba(167,139,250,0.12);
  --star:#fbbf24;
  --live-red:#ef4444;
  --cyan:#22d3ee;
  --cyan-dim:rgba(34,211,238,0.12);
  --pink:#f472b6;
  --transition:0.18s cubic-bezier(0.4,0,0.2,1);
  --transition-slow:0.25s cubic-bezier(0.4,0,0.2,1);
}

*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%}
body{
  font-family:'DM Sans','Segoe UI',Tahoma,sans-serif;
  background:var(--bg-primary);
  color:var(--text-primary);
  overflow:hidden;
  -webkit-font-smoothing:antialiased;
  font-size:14px;
}
a{color:inherit;text-decoration:none}
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
input,select,textarea{font-family:inherit}
img{display:block;max-width:100%}
svg{display:inline-block;vertical-align:middle}

/* scrollbars */
::-webkit-scrollbar{width:6px;height:6px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.08);border-radius:4px}
::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.18)}

/* ─────────────────────── APP SHELL ─────────────────────── */
.app{display:flex;flex-direction:column;height:100vh;overflow:hidden}

/* top navbar */
.top-nav{
  height:56px;flex-shrink:0;
  background:var(--bg-secondary);
  border-bottom:1px solid var(--border);
  display:flex;align-items:center;
  padding:0 20px;gap:18px;
  position:relative;z-index:50;
}
.brand{
  font-family:'Outfit',sans-serif;
  font-weight:800;font-size:1.2rem;
  letter-spacing:-0.3px;display:flex;align-items:center;gap:10px;
}
.brand span{color:#c1c1c1}
.brand-icon{
  width:32px;height:32px;border-radius:8px;
  display:flex;align-items:center;justify-content:center;
  overflow:hidden;flex-shrink:0;
}
.brand-icon img{width:100%;height:100%;object-fit:contain;display:block}
.brand-icon svg{width:18px;height:18px;color:#fff}

.nav-tabs{display:flex;gap:4px;flex:1;overflow-x:auto;scrollbar-width:none}
.nav-tabs::-webkit-scrollbar{display:none}
.nav-tab{
  padding:8px 14px;border-radius:8px;
  font-size:0.85rem;font-weight:600;
  color:var(--text-secondary);
  display:flex;align-items:center;gap:7px;
  transition:all var(--transition);
  white-space:nowrap;
}
.nav-tab svg{width:15px;height:15px;opacity:0.8}
.nav-tab:hover{color:var(--text-primary);background:var(--bg-hover)}
.nav-tab.active{
  background:var(--accent-dim);
  color:var(--accent);
}
.nav-tab.active svg{opacity:1}

.nav-actions{display:flex;gap:8px;align-items:center}
.nav-icon-btn{
  width:36px;height:36px;border-radius:8px;
  background:var(--bg-card);border:1px solid var(--border);
  display:flex;align-items:center;justify-content:center;
  color:var(--text-secondary);transition:all var(--transition);
}
.nav-icon-btn:hover{background:var(--bg-hover);color:var(--text-primary);border-color:var(--border-strong)}
.nav-icon-btn svg{width:16px;height:16px}

.profile-chip{
  display:flex;align-items:center;gap:8px;
  padding:5px 10px 5px 5px;border-radius:20px;
  background:var(--bg-card);border:1px solid var(--border);
  transition:all var(--transition);
}
.profile-chip:hover{border-color:var(--border-strong)}
.profile-chip .avatar{
  width:28px;height:28px;border-radius:50%;
  background:linear-gradient(135deg,var(--accent),#a78bfa);
  display:flex;align-items:center;justify-content:center;
  font-weight:700;font-size:0.75rem;color:#fff;
}
.profile-chip .name{font-size:0.78rem;font-weight:600;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* content area */
.content{flex:1;overflow:hidden;position:relative}
.view{position:absolute;inset:0;display:flex;overflow:hidden;animation:fadeIn .18s ease}
@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}

/* ─────────────────────── PANELS / SIDEBAR ─────────────────────── */
.side-panel{
  width:280px;flex-shrink:0;
  background:var(--bg-secondary);
  border-right:1px solid var(--border);
  display:flex;flex-direction:column;overflow:hidden;
}
.side-header{
  padding:16px;border-bottom:1px solid var(--border);
  display:flex;flex-direction:column;gap:10px;
}
/* One line, always. The channel panel's heading is the chosen category name,
   and a long one wrapped onto a second line - which pushed that panel's search
   box below the neighbouring one, so the two headers no longer lined up. It is
   trimmed with an ellipsis instead, and the count keeps its place on the right
   with room reserved for it. */
.side-title{
  position:relative;display:block;
  font-family:'Outfit',sans-serif;font-size:0.95rem;font-weight:700;
  line-height:1.5;
  padding-right:52px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.side-title .count{
  position:absolute;right:0;top:50%;transform:translateY(-50%);
  font-size:0.7rem;padding:2px 8px;border-radius:10px;
  background:var(--bg-card);color:var(--text-secondary);font-weight:600;
}
.side-search{position:relative;display:flex;align-items:center}
.side-search input{
  width:100%;padding:9px 12px 9px 34px;
  background:var(--bg-input);border:1px solid var(--border);
  border-radius:8px;color:var(--text-primary);
  font-size:0.82rem;outline:none;transition:all var(--transition);
}
.side-search input:focus{border-color:var(--border-focus);box-shadow:0 0 0 3px var(--accent-glow)}
.side-search svg{position:absolute;left:11px;width:15px;height:15px;color:var(--text-muted);pointer-events:none}

.side-list{flex:1;overflow-y:auto;padding:8px}
.side-item{
  display:flex;align-items:center;gap:10px;
  padding:9px 10px;border-radius:7px;
  font-size:0.82rem;color:var(--text-primary);
  cursor:pointer;transition:all var(--transition);
  border:1px solid transparent;
}
/* Hover only where there is a pointer. On a touch screen :hover sticks to the
   last thing tapped, so a random row sat highlighted as though it were the
   selected one. */
@media (hover:hover){
  .side-item:hover{background:var(--bg-hover);color:var(--text-primary)}
}
.side-item.active{
  background:var(--accent-dim);color:var(--accent);
  border-color:rgba(59,130,246,0.25);
}
.side-item .label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.side-item .badge{
  font-size:0.68rem;padding:1px 7px;border-radius:9px;
  background:var(--bg-card);color:#6d738f;font-weight:600;
}
.side-item.active .badge{background:rgba(59,130,246,0.2);color:var(--accent)}

/* ─────────────────────── MAIN AREA ─────────────────────── */
.main-area{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--bg-primary)}
.main-header{
  padding:16px 22px;border-bottom:1px solid var(--border);
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  background:var(--bg-primary);
}
.main-title{font-family:'Outfit',sans-serif;font-size:1.2rem;font-weight:700;flex:1;min-width:120px}
.main-sub{font-size:0.78rem;color:var(--text-secondary);margin-top:2px}
.main-body{flex:1;overflow-y:auto;padding:20px 22px}

.toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.toolbar-search{position:relative;min-width:220px}
.toolbar-search input{
  width:100%;padding:8px 12px 8px 34px;
  background:var(--bg-input);border:1px solid var(--border);
  border-radius:8px;color:var(--text-primary);font-size:0.82rem;outline:none;
}
.toolbar-search input:focus{border-color:var(--border-focus);box-shadow:0 0 0 3px var(--accent-glow)}
.toolbar-search svg{position:absolute;left:11px;top:50%;transform:translateY(-50%);width:15px;height:15px;color:var(--text-muted)}
.btn{
  padding:8px 14px;border-radius:8px;font-size:0.82rem;font-weight:600;
  display:inline-flex;align-items:center;gap:7px;transition:all var(--transition);
  border:1px solid transparent;
}
.btn svg{width:15px;height:15px}
.btn-primary{background:var(--accent);color:#fff}
.btn-primary:hover{background:var(--accent-hover);transform:translateY(-1px);box-shadow:0 4px 14px rgba(59,130,246,0.3)}
.btn-ghost{background:var(--bg-card);color:var(--text-secondary);border-color:var(--border)}
.btn-ghost:hover{background:var(--bg-hover);color:var(--text-primary);border-color:var(--border-strong)}
.btn-danger{background:var(--error-dim);color:var(--error)}
.btn-icon{width:36px;height:36px;padding:0;justify-content:center}

/* ─────────────────────── CARD GRIDS ─────────────────────── */
.grid{display:grid;gap:14px}
.grid-posters{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));grid-auto-rows:1fr}
.grid-channels{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}

.poster-card{
  background:var(--bg-card);border:1px solid var(--border);
  border-radius:10px;overflow:hidden;
  cursor:pointer;transition:all var(--transition);
  display:flex;flex-direction:column;
  position:relative;
}
@media (hover:hover){
  .poster-card:hover{
    border-color:var(--border-strong);
    transform:translateY(-2px);
    box-shadow:0 10px 30px rgba(0,0,0,0.4);
  }
}
/* Every card is the same height, whatever its title and metadata.
   The artwork keeps a fixed 2:3 box, the title occupies EXACTLY two lines
   whether it needs one or three, and the meta row is reserved even when a
   title carries no year or rating. Before this a one-line title, or a missing
   rating, made its card shorter than its neighbours and the grid stepped up
   and down as you scrolled. */
.poster-img{
  aspect-ratio:2/3;width:100%;
  position:relative;flex:0 0 auto;
  /* A film-strip glyph sits under the artwork. Most of this catalogue has no
     poster, and a grid of empty tinted rectangles reads as a broken page
     rather than as titles without cover art. */
  background-color:var(--bg-hover);
  background-image:
    url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23505672' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2' y='4' width='20' height='16' rx='2'/%3E%3Cpath d='M7 4v16M17 4v16M2 9h5M2 15h5M17 9h5M17 15h5'/%3E%3C/svg%3E"),
    linear-gradient(135deg,#1c2235,#0b0e14);
  background-repeat:no-repeat,no-repeat;
  background-position:center,center;
  background-size:38px,cover;
  /* min-height:0 and the absolutely positioned image together are what make
     the 2:3 box actually hold.
     .poster-card is a column flex container, so .poster-img is a FLEX ITEM,
     and a flex item's automatic minimum size is content-based: an in-flow
     <img> taller than the ratio (a 630x1200 poster in a 172px column wants
     328px, not 258px) overrode aspect-ratio and stretched that one card - and
     with equal grid rows, every card in the grid with it. Out of flow, the
     artwork cannot influence the box: it fills it and is cropped. */
  min-height:0;
}
.poster-img img{
  position:absolute;inset:0;
  width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .18s ease;
}
.poster-img img.is-loaded{opacity:1}
.poster-body{padding:9px 10px}
.poster-title{
  font-size:0.82rem;font-weight:600;color:var(--text-primary);
  line-height:1.25;max-height:2.5em;
  overflow:hidden;text-overflow:ellipsis;display:-webkit-box;
  -webkit-line-clamp:2;-webkit-box-orient:vertical;
}
.poster-meta{
  font-size:0.68rem;color:var(--text-muted);margin-top:2px;
  display:flex;gap:6px;align-items:center;
}
.poster-rating{display:inline-flex;align-items:center;gap:3px;color:var(--star)}
.poster-rating svg{width:11px;height:11px}
.poster-fav{
  /* Top LEFT: the rating pill owns the opposite corner. */
  position:absolute;top:5px;left:5px;
  width:30px;height:30px;border-radius:50%;
  background:rgba(0,0,0,0.6);backdrop-filter:blur(8px);
  display:flex;align-items:center;justify-content:center;
  color:#fff;opacity:0;transition:all var(--transition);z-index:2;
}
@media (hover:hover){
  .poster-card:hover .poster-fav{opacity:1}
}
/* There is no hover on a touch screen, so a control that only appears on
   hover never appears at all. Reveal it permanently there - the same reason
   the channel rows show their heart outright on a phone. */
@media (hover:none){
  .poster-fav{opacity:1}
}
.poster-fav.active{opacity:1;color:var(--error)}
.poster-fav svg{width:15px;height:15px}

.channel-card{
  background:var(--bg-card);border:1px solid var(--border);
  border-radius:10px;padding:12px;display:flex;gap:12px;align-items:center;
  cursor:pointer;transition:all var(--transition);
  margin:4px;
}
@media (hover:hover){
  .channel-card:hover{border-color:var(--accent);background:var(--bg-hover);transform:translateY(-1px)}
}
.channel-logo{
  width:52px;height:52px;border-radius:8px;flex-shrink:0;
  background:var(--bg-hover) center/contain no-repeat;
  display:flex;align-items:center;justify-content:center;
  border:1px solid var(--border);overflow:hidden;
}
.channel-logo img{max-width:100%;max-height:100%;object-fit:contain}
.channel-info{flex:1;min-width:0}
.channel-name{font-size:0.88rem;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.channel-prog{font-size:0.72rem;color:var(--text-muted);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.live-badge{
  display:inline-flex;align-items:center;gap:4px;
  font-size:0.62rem;font-weight:700;padding:2px 7px;border-radius:4px;
  background:var(--live-red);color:#fff;letter-spacing:0.5px;
}
.live-badge::before{content:'';width:6px;height:6px;border-radius:50%;background:#fff;animation:pulse 1.4s ease infinite}
@keyframes pulse{50%{opacity:0.4}}

/* EPG on-air progress: bar + "43% · 26m in · 34m left" on one line */
.epg-prog{display:flex;align-items:center;gap:10px;margin-top:6px;min-width:0}
.epg-prog-bar{flex:1;height:4px;border-radius:2px;background:rgba(255,255,255,0.08);overflow:hidden}
.epg-prog-bar i{display:block;height:100%;background:var(--accent);border-radius:2px;transition:width .4s}
.epg-prog-txt{flex:none;font-size:0.68rem;color:var(--text-muted);white-space:nowrap;font-variant-numeric:tabular-nums}
.epg-prog-txt b{color:var(--accent);font-weight:700}
.epg-prog-txt em{font-style:normal;margin:0 5px;opacity:.6}

/* ─────────────────────── EMPTY / LOADING ─────────────────────── */
.empty{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:60px 20px;color:var(--text-muted);text-align:center;
}
.empty svg{width:48px;height:48px;margin-bottom:14px;opacity:0.5}
.empty h3{font-family:'Outfit',sans-serif;font-size:1.05rem;color:var(--text-secondary);margin-bottom:4px}
.empty p{font-size:0.82rem}

.loader{
  width:28px;height:28px;border-radius:50%;
  border:3px solid var(--border);border-top-color:var(--accent);
  animation:spin 0.8s linear infinite;margin:40px auto;
}
@keyframes spin{to{transform:rotate(360deg)}}

/* ─────────────────────── SKELETON LOADERS (shimmer) ─────────────────────── */
/* Used while waiting for movies / series / channel lists - instead of a tiny
   off-center spinner we paint ghost cards with a sliding shimmer highlight.   */
.skeleton{
  background:linear-gradient(
    90deg,
    rgba(255,255,255,0.04) 0%,
    rgba(255,255,255,0.08) 40%,
    rgba(255,255,255,0.14) 50%,
    rgba(255,255,255,0.08) 60%,
    rgba(255,255,255,0.04) 100%
  );
  background-size:200% 100%;
  animation:shimmer 1.4s ease-in-out infinite;
  border-radius:6px;
}
@keyframes shimmer{
  0%{background-position:200% 0}
  100%{background-position:-200% 0}
}

/* Poster skeleton card (movies, series, latest, search posters) */
.sk-poster{
  background:var(--bg-card);
  border:1px solid var(--border);
  border-radius:10px;overflow:hidden;
  display:flex;flex-direction:column;
  pointer-events:none;
}
.sk-poster .sk-img{aspect-ratio:2/3;width:100%}
.sk-poster .sk-body{padding:10px}
.sk-poster .sk-line{height:10px;border-radius:4px;margin-bottom:6px}
.sk-poster .sk-line.w-90{width:90%}
.sk-poster .sk-line.w-60{width:60%;height:8px;margin-bottom:0}

/* Channel row skeleton (live, favourites, epg, catchup) */
.sk-channel{
  background:var(--bg-card);
  border:1px solid var(--border);
  border-radius:10px;padding:12px;
  display:flex;gap:12px;align-items:center;
  pointer-events:none;
}
.sk-channel .sk-logo{width:52px;height:52px;border-radius:8px;flex-shrink:0}
.sk-channel .sk-info{flex:1;min-width:0}
.sk-channel .sk-info .sk-line{height:10px;border-radius:4px;margin-bottom:6px}
.sk-channel .sk-info .sk-line.w-70{width:70%}
.sk-channel .sk-info .sk-line.w-40{width:40%;height:8px;margin-bottom:0}

/* Sidebar row skeleton (categories, channels) */
.sk-side-row{
  display:flex;gap:10px;align-items:center;
  padding:9px 10px;border-radius:7px;margin-bottom:4px;
  pointer-events:none;
}
.sk-side-row .sk-dot{width:20px;height:20px;border-radius:5px;flex-shrink:0}
.sk-side-row .sk-bar{height:10px;border-radius:4px;flex:1}
.sk-side-row .sk-badge{width:28px;height:14px;border-radius:10px;flex-shrink:0}

/* Text-only skeleton block (details, info) */
.sk-block{
  background:var(--bg-card);
  border:1px solid var(--border);
  border-radius:10px;padding:16px;
}
.sk-block .sk-line{height:12px;border-radius:4px;margin-bottom:10px}
.sk-block .sk-line:last-child{margin-bottom:0}
.sk-block .sk-line.full{width:100%}
.sk-block .sk-line.w-80{width:80%}
.sk-block .sk-line.w-50{width:50%}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce){
  .skeleton{animation:none;opacity:0.5}
}

/* ─────────────────────── MODAL ─────────────────────── */
.modal-backdrop{
  position:fixed;inset:0;background:rgba(0,0,0,0.75);backdrop-filter:blur(6px);
  display:flex;align-items:center;justify-content:center;z-index:200;
  animation:fadeIn .15s ease;padding:20px;
}
.modal{
  background:var(--bg-card);border-radius:14px;border:1px solid var(--border);
  width:100%;max-width:900px;max-height:90vh;overflow:hidden;
  display:flex;flex-direction:column;
  box-shadow:0 30px 80px rgba(0,0,0,0.6);
}
.modal-head{
  padding:14px 18px;border-bottom:1px solid var(--border);
  display:flex;align-items:center;justify-content:space-between;gap:10px;
}
.modal-head h3{font-family:'Outfit',sans-serif;font-size:1rem;font-weight:700}
.modal-body{flex:1;overflow-y:auto;padding:16px 18px}
.modal-close{
  width:32px;height:32px;border-radius:8px;display:flex;
  align-items:center;justify-content:center;color:var(--text-secondary);
  transition:all var(--transition);
}
.modal-close:hover{background:var(--bg-hover);color:var(--text-primary)}
.modal-close svg{width:18px;height:18px}

/* ─────────────────────── FULLSCREEN PLAYER VIEW ─────────────────────── */
/* Dedicated "now playing" layer - takes over the viewport when a movie or
   episode starts. Player is the primary focus; info/episodes sit in a side
   rail, not crammed below the video inside a modal. */
.pv-backdrop{
  position:fixed;inset:0;z-index:300;
  background:#05070c;
  display:flex;flex-direction:column;
  animation:pvIn 0.22s cubic-bezier(0.4,0,0.2,1);
}
@keyframes pvIn{
  from{opacity:0;transform:scale(0.985)}
  to{opacity:1;transform:scale(1)}
}

.pv-topbar{
  height:52px;flex-shrink:0;
  display:flex;align-items:center;gap:14px;
  padding:0 18px;
  background:linear-gradient(180deg, rgba(0,0,0,0.7), rgba(0,0,0,0));
  position:relative;z-index:2;
}
.pv-back{
  width:36px;height:36px;border-radius:10px;
  display:inline-flex;align-items:center;justify-content:center;
  color:var(--text-primary);background:rgba(255,255,255,0.06);
  transition:all var(--transition);flex-shrink:0;
}
.pv-back:hover{background:rgba(255,255,255,0.12)}
.pv-back svg{width:18px;height:18px}
.pv-title-wrap{flex:1;min-width:0}
.pv-title{
  font-family:'Outfit',sans-serif;font-size:1rem;font-weight:700;
  color:var(--text-primary);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.pv-sub{font-size:0.74rem;color:var(--text-secondary);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pv-actions{display:flex;gap:6px;flex-shrink:0}

/* Main stage */
.pv-stage{flex:1;display:flex;min-height:0;overflow:hidden}
.pv-main{
  flex:1;min-width:0;
  display:flex;align-items:center;justify-content:center;
  padding:14px 18px;position:relative;overflow:hidden;background:#000;
}
.pv-player{
  width:100%;max-width:100%;max-height:100%;
  aspect-ratio:16/9;
  background:#000;border-radius:12px;overflow:hidden;
  position:relative;
  box-shadow:0 20px 60px rgba(0,0,0,0.6);
}
@supports not (aspect-ratio: 16/9){
  .pv-player{height:0;padding-bottom:56.25%}
}
.pv-player video{width:100%;height:100%;display:block;object-fit:contain;background:#000}
.pv-player .plyr,
.pv-player .plyr--video,
.pv-player .plyr__video-wrapper{position:absolute;inset:0;width:100%;height:100%}
.pv-player .plyr video{width:100%;height:100%;object-fit:contain}
.pv-player .plyr__controls{position:absolute;left:0;right:0;bottom:0;z-index:3}
.pv-player .plyr{border-radius:12px}

/* Right rail: episodes (series) or info (movies) */
.pv-rail{
  width:360px;flex-shrink:0;
  background:var(--bg-secondary);border-left:1px solid var(--border);
  display:flex;flex-direction:column;overflow:hidden;
}
.pv-rail-head{
  padding:14px 16px;border-bottom:1px solid var(--border);
  display:flex;align-items:center;justify-content:space-between;gap:8px;
}
.pv-rail-title{font-family:'Outfit',sans-serif;font-size:0.9rem;font-weight:700}
.pv-rail-sub{font-size:0.7rem;color:var(--text-muted);margin-top:2px}
.pv-rail-tabs{display:flex;gap:4px;padding:8px 10px 0;flex-shrink:0}
.pv-rail-tabs button{
  flex:1;padding:7px 8px;border-radius:7px;
  font-size:0.74rem;font-weight:600;color:var(--text-secondary);
  background:var(--bg-card);border:1px solid var(--border);
  transition:all var(--transition);
}
.pv-rail-tabs button.active{background:var(--accent);color:#fff;border-color:var(--accent)}
.pv-rail-body{flex:1;overflow-y:auto;padding:10px}

/* Episode row */
.pv-ep{
  display:flex;gap:10px;padding:10px;border-radius:9px;
  cursor:pointer;transition:all var(--transition);margin-bottom:6px;
  border:1px solid transparent;
}
.pv-ep:hover{background:var(--bg-hover)}
.pv-ep.active{background:var(--accent-dim);border-color:rgba(59,130,246,0.3)}
.pv-ep-still{
  width:96px;height:54px;border-radius:6px;flex-shrink:0;
  background:var(--bg-hover) center/cover no-repeat;
  display:flex;align-items:center;justify-content:center;
  font-size:0.7rem;font-weight:700;color:var(--text-muted);
  position:relative;overflow:hidden;
}
.pv-ep-still img{width:100%;height:100%;object-fit:cover}
.pv-ep-playing{
  position:absolute;inset:0;background:rgba(0,0,0,0.55);
  display:flex;align-items:center;justify-content:center;color:#fff;
}
.pv-ep-playing svg{width:22px;height:22px}
.pv-ep-info{flex:1;min-width:0}
.pv-ep-num{font-size:0.64rem;font-weight:700;color:var(--accent);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:2px}
.pv-ep-title{font-size:0.82rem;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:2px}
.pv-ep-desc{font-size:0.7rem;color:var(--text-muted);line-height:1.35;max-height:2.7em;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}

/* Movie rail info */
.pv-movie-poster{
  width:100%;aspect-ratio:2/3;border-radius:10px;overflow:hidden;
  background:var(--bg-hover);margin-bottom:12px;
}
.pv-movie-poster img{width:100%;height:100%;object-fit:cover}
.pv-movie-meta{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px}
.pv-movie-plot{font-size:0.82rem;line-height:1.55;color:var(--text-secondary);margin-bottom:14px}
.pv-movie-kv{font-size:0.78rem;margin-bottom:6px}
.pv-movie-kv strong{color:var(--text-secondary);font-weight:600;margin-right:6px}

.pv-loading-ep{padding:32px 12px;text-align:center;color:var(--text-muted);font-size:0.8rem}

/* Mobile: stack vertically, rail becomes a bottom sheet */
@media (max-width: 900px){
  .pv-stage{flex-direction:column}
  .pv-rail{width:100%;flex:none;max-height:45vh;border-left:none;border-top:1px solid var(--border)}
  .pv-main{padding:8px 10px}
}
@media (max-width: 640px){
  .pv-topbar{height:48px;padding:0 12px;gap:8px}
  .pv-title{font-size:0.88rem}
  .pv-sub{font-size:0.68rem}
  .pv-back{width:32px;height:32px}
}

/* The phone tab strip. Declared hidden HERE, at the base, because a rule that
   only exists inside a max-width media query leaves the element at its default
   display everywhere else - which put "CategoriesChannelsPlayer" across the top
   of the desktop layout and shifted every pane sideways. */
.mtabs{display:none}

/* ─────────────────────── PLAYER ─────────────────────── */
.player-wrap{
  width:100%;background:#000;border-radius:10px;overflow:hidden;
  aspect-ratio:16/9;position:relative;
  /* Fallback for very old browsers without aspect-ratio support */
  max-width:100%;
  /* Use the height the screen actually has. The frame stayed 16:9 inside a
     1000px cap, so on a desktop the picture occupied the top third and the
     rest of the window was empty. Bounded by the viewport so the Now & Next
     strip below it stays on screen without scrolling. */
  max-height:calc(100vh - 260px);
  margin-inline:auto;
}
/* Fallback: for browsers lacking aspect-ratio, use padding trick */
@supports not (aspect-ratio: 16/9){
  .player-wrap{height:0;padding-bottom:56.25%}
}
.player-wrap video{width:100%;height:100%;display:block;object-fit:contain;background:#000}

/* Plyr overrides - force the Plyr container to fill the 16:9 box at all times.
   Before the stream loads the video has no intrinsic size, so Plyr would
   otherwise collapse and its control bar would float up to the top.
   Pinning Plyr to the full container keeps controls anchored to the bottom. */
.plyr{--plyr-color-main:var(--accent);border-radius:10px}
.plyr--video{background:#000}
.player-wrap .plyr,
.player-wrap .plyr--video,
.player-wrap .plyr__video-wrapper{
  position:absolute;inset:0;
  width:100%;height:100%;
}
.player-wrap .plyr video{
  width:100%;height:100%;object-fit:contain;
}
/* Keep the control bar glued to the bottom edge of the player box */
.player-wrap .plyr__controls{
  position:absolute;left:0;right:0;bottom:0;z-index:3;
}
/* While buffering / before metadata, show a subtle spinner fallback */
.player-wrap .plyr__poster{background-color:#000}

/* Responsive: smaller radius on phones, no horizontal overflow */
@media (max-width: 640px){
  .player-wrap{border-radius:8px}
  .player-wrap .plyr{border-radius:8px}
}

/* ─────────────────────── TRACK SELECTOR (Plyr settings injection) ─────────
   Audio + Subtitles items injected as siblings of Plyr's "Speed" entry in
   the settings dropdown (gear icon). On click each opens a submenu pane
   styled to match Plyr's own back-button + radio-list pattern. Hidden when
   the source has nothing selectable. */
.xm-tm-item .xm-tm-value{margin-left:8px;color:rgba(255,255,255,0.55)}

.xm-tm-pane{padding:0}
.xm-tm-pane .xm-tm-list{display:flex;flex-direction:column;padding:4px 0}
.xm-tm-opt{
  display:flex;align-items:center;gap:8px;
  width:100%;padding:8px 14px;
  font-size:13px;line-height:1.4;
  color:#fff;background:none;border:none;
  cursor:pointer;text-align:left;font-family:inherit;
  transition:background 0.12s ease;
}
.xm-tm-opt:hover,.xm-tm-opt:focus{background:rgba(255,255,255,0.08);outline:none}
.xm-tm-opt.is-active{background:rgba(59,130,246,0.18);color:#fff;font-weight:600}
.xm-tm-opt.is-active::before{
  content:'';display:inline-block;
  width:8px;height:8px;border-radius:50%;
  background:#3b82f6;flex-shrink:0;
  box-shadow:0 0 0 2px rgba(59,130,246,0.28);
}
.xm-tm-opt:not(.is-active)::before{
  content:'';display:inline-block;
  width:8px;height:8px;border-radius:50%;
  border:1.5px solid rgba(255,255,255,0.3);flex-shrink:0;
  background:transparent;
}
.xm-tm-empty{
  padding:10px 14px;font-size:12px;
  color:rgba(255,255,255,0.45);font-style:italic;
}
/* Make Plyr's settings dropdown wider so long audio/track names fit */
.plyr__menu__container{min-width:200px}

/* ─────────────────────── INPUT / FORMS ─────────────────────── */
.form-row{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
.form-label{font-size:0.76rem;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:0.5px}
.input-wrap{position:relative;display:flex;align-items:center}
.input-wrap input,.input-wrap select,.input-wrap textarea{
  width:100%;padding:11px 12px 11px 38px;
  background:var(--bg-input);border:1.5px solid var(--border);
  border-radius:9px;color:var(--text-primary);font-size:0.88rem;
  transition:all var(--transition);outline:none;
}
.input-wrap input:focus,.input-wrap select:focus{border-color:var(--border-focus);background:var(--bg-input-focus);box-shadow:0 0 0 3px var(--accent-glow)}
.input-wrap .ic{position:absolute;left:12px;width:17px;height:17px;color:var(--text-muted);pointer-events:none}
.input-wrap.plain input{padding-left:12px}

/* toast */
.toast{
  position:fixed;bottom:20px;right:20px;z-index:500;
  padding:11px 16px;border-radius:10px;
  background:var(--bg-card);border:1px solid var(--border);
  box-shadow:0 12px 30px rgba(0,0,0,0.5);
  display:flex;align-items:center;gap:9px;font-size:0.85rem;
  animation:slideUp .25s ease;
}
.toast.success{border-color:rgba(34,197,94,0.4)}
.toast.error{border-color:rgba(239,68,68,0.4)}
@keyframes slideUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}

/* tag row */
.tag{
  display:inline-flex;align-items:center;gap:4px;padding:3px 9px;
  border-radius:14px;font-size:0.7rem;font-weight:600;
  background:var(--bg-hover);color:var(--text-secondary);
}
.tag.accent{background:var(--accent-dim);color:var(--accent)}
.tag.success{background:var(--success-dim);color:var(--success)}
.tag.warn{background:var(--warning-dim);color:var(--warning)}

/* ─────────────────────── MOBILE DRAWER + BOTTOM NAV ───────────────────────
   Hamburger toggle (in top-nav) and bottom-nav are hidden on desktop and
   shown only on small screens. The drawer is a slide-in panel that wraps
   any .side-panel inside the active .view. The overlay dims content behind
   it and closes on tap.

   Two source-of-truth toggles for redundancy: <body class="drawer-open">
   and <aside class="side-panel open"> set by JS. Either one opening the
   drawer is enough - defensive against any single rule failing.
*/
.sidebar-toggle{
  display:none;position:relative;z-index:65;
  /* Stand out from the other nav-icon-btns - accent color so it's clearly
     the way to open the menu, not just another action button. */
  background:var(--accent) !important;color:#fff !important;
  border-color:transparent !important;
}
.sidebar-toggle:hover{background:var(--accent-hover) !important}
.sidebar-toggle.is-hidden{display:none !important}
body.drawer-open{overflow:hidden}
body.drawer-open .top-nav{z-index:60}
.side-overlay{
  display:none;position:fixed;inset:0;z-index:55;
  background:rgba(0,0,0,0.55);
  -webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
  opacity:0;transition:opacity .22s ease;
}
body.drawer-open .side-overlay{display:block;opacity:1}
.bottom-nav{display:none}

/* responsive */
@media (max-width: 900px){
  .side-panel{width:240px}
  .main-title{font-size:1rem}
  .grid-posters{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}
  /* Stack any nested main-area that was forced to row layout via inline style */
  .view .main-area[style*="row"],
  .view .main-area[style*="ROW"]{flex-direction:column !important}
}

@media (max-width: 768px){
  /* Show the hamburger toggle by default; hidden state is opt-in via class */
  .sidebar-toggle{display:inline-flex !important;flex-shrink:0}
  .sidebar-toggle.is-hidden{display:none !important}

  /* Bottom nav visible - primary destinations as big touch targets */
  .bottom-nav{
    display:flex;position:fixed;left:0;right:0;bottom:0;z-index:45;
    background:var(--bg-secondary);border-top:1px solid var(--border-strong);
    padding:6px 4px calc(env(safe-area-inset-bottom,0px) + 6px);
    justify-content:space-around;align-items:stretch;gap:2px;
    box-shadow:0 -10px 24px rgba(0,0,0,0.45);
  }
  .bn-tab{
    flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:3px;padding:6px 4px;border-radius:8px;color:var(--text-secondary);
    font-size:0.62rem;font-weight:600;letter-spacing:0.2px;
    transition:all var(--transition);min-width:0;
    background:none;border:none;cursor:pointer;font-family:inherit;
  }
  .bn-tab svg{width:20px;height:20px;display:block}
  .bn-tab.active{color:var(--accent);background:var(--accent-dim)}
  .bn-tab:active{transform:scale(0.95)}
  /* Reserve space at the bottom so content isn't covered. Account for safe-area */
  .content{padding-bottom:calc(64px + env(safe-area-inset-bottom,0px))}

  /* Top nav: tighten + collapse to icon-only scrollable strip. Top tabs
     still give access to every view (EPG, Catch Up, Favourites, Latest)
     beyond the 5 in the bottom-nav. */
  .top-nav{padding:0 10px;gap:8px;height:54px}
  /* The top tab strip is hidden on a phone: collapsed to icons it clipped
     EPG, Catch Up, Favourites and Latest off the right edge, so those screens
     had no route at all. The bottom bar and its More sheet carry navigation
     here, and the brand gets the room back. */
  .nav-tabs{display:none}
  .profile-chip .name{display:none}
  .nav-tab{padding:7px 9px}
  .nav-tab span{display:none}
  .nav-tab svg{width:18px;height:18px;opacity:0.9}
  .nav-tabs{gap:2px}

  /* View becomes a vertical column; main-area scrolls in normal flow */
  .view{flex-direction:column;overflow-y:auto;-webkit-overflow-scrolling:touch}
  .main-area{width:100%;min-width:0;height:auto;flex:1 1 auto;overflow:visible}
  .main-body{overflow:visible;flex:0 1 auto}

  /* DRAWER: every .side-panel becomes a slide-in panel from the left edge.
     Hidden by default off-screen via transform. Either body.drawer-open
     OR a direct .open class on the panel reveals it (redundant for
     reliability - different mobile JS paths can use either). */
  body:not(.has-mtabs) .view .side-panel,
  body:not(.has-mtabs) .view .main-area .side-panel{
    position:fixed;top:54px;bottom:0;left:0;
    width:86vw;max-width:340px;
    z-index:60;
    transform:translateX(-105%);
    transition:transform .26s cubic-bezier(0.4,0,0.2,1);
    box-shadow:18px 0 40px rgba(0,0,0,0.6);
    border-right:1px solid var(--border-strong);
    /* Make sure the panel is never clipped by an ancestor's overflow */
    will-change:transform;
  }
  body:not(.has-mtabs).drawer-open .view .side-panel,
  body:not(.has-mtabs).drawer-open .view .main-area .side-panel,
  body:not(.has-mtabs) .view .side-panel.open,
  body:not(.has-mtabs) .view .main-area .side-panel.open{transform:translateX(0)}

  /* DUAL SIDEBAR (live/epg/catchup): two side-panels stacked in the same
     drawer column. JS adds .has-dual-sidebar to body when the active view
     contains more than one .side-panel. Avoids :has() compatibility issues. */
  body:not(.has-mtabs).has-dual-sidebar .view > .side-panel{height:50vh;bottom:auto}
  body:not(.has-mtabs).has-dual-sidebar .view .main-area > .side-panel{
    top:auto;bottom:0;height:50vh;
    border-top:1px solid var(--border-strong);border-right:1px solid var(--border-strong);
  }

  /* ── The phone layout: panes as tabs ────────────────────────────────────
     Category -> channel -> watch is a drill-down, so on a phone it is one
     pane at a time with a tab strip that is both the map and the way back.
     See App.buildPanes. Above this breakpoint none of it applies: the panes
     are already side by side. */
  .mtabs{
    display:flex;gap:6px;flex:0 0 auto;
    padding:8px 10px calc(8px + env(safe-area-inset-left,0px));
    background:var(--bg-secondary);
    border-bottom:1px solid var(--border-strong);
    overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;
  }
  .mtabs::-webkit-scrollbar{display:none}
  .mtab{
    flex:1 1 0;min-width:max-content;
    padding:10px 12px;border-radius:10px;
    border:1px solid var(--border);background:var(--bg-card);
    color:var(--text-secondary);
    font-family:'DM Sans',system-ui,sans-serif;font-size:0.8rem;font-weight:600;
    white-space:nowrap;cursor:pointer;transition:all var(--transition);
  }
  .mtab.active{background:var(--accent);border-color:var(--accent);color:#fff}
  .mtab:active{transform:scale(0.97)}

  /* The view is a column: the tab strip, then exactly one pane filling the
     rest. The PANE scrolls, not the view, so the tabs stay put. */
  body.has-mtabs .view{flex-direction:column;overflow:hidden;height:100%}
  /* The dual-panel views set display:flex INLINE on this wrapper, and an
     inline style beats any stylesheet rule without !important - so without it
     the wrapper stayed a flex item holding only hidden panes, and rendered as
     an empty band above the bottom bar. */
  body.has-mtabs .view .main-area{display:contents !important}
  body.has-mtabs .view[data-mtab] .mpane{display:none !important}
  body.has-mtabs .view[data-mtab="0"] .mpane[data-p="0"],
  body.has-mtabs .view[data-mtab="1"] .mpane[data-p="1"],
  body.has-mtabs .view[data-mtab="2"] .mpane[data-p="2"],
  body.has-mtabs .view[data-mtab="3"] .mpane[data-p="3"]{
    display:flex !important;flex:1 1 auto;min-height:0;
    width:100%;max-width:none;
  }
  /* Panels are ordinary blocks here, never drawers. */
  body.has-mtabs .view .side-panel,
  body.has-mtabs .view .main-area .side-panel{
    position:static;transform:none;box-shadow:none;
    width:100%;max-width:none;height:auto;
    border-right:none;border-top:none;z-index:auto;
  }
  body.has-mtabs .mpane .side-list,
  body.has-mtabs .mpane .main-body{overflow-y:auto;flex:1 1 auto;min-height:0}
  body.has-mtabs .mpane .side-header{flex:0 0 auto}
  /* Rows are touch targets, not pointer targets. */
  body.has-mtabs .side-item{padding-top:12px;padding-bottom:12px}
  body.has-mtabs .live-ch-row .ch-fav{opacity:1;width:34px;height:34px}
  body.has-mtabs .live-ch-row{padding-right:44px}
  /* The tab strip replaces the hamburger entirely. */
  body.has-mtabs .sidebar-toggle{display:none !important}

  /* The pane runs to the bottom bar and its own scroller clears it, instead
     of the content area reserving a strip that showed as a seam under every
     short list. */
  body.has-mtabs .content{padding-bottom:0}
  body.has-mtabs .mpane .side-list,
  body.has-mtabs .mpane .main-body{
    padding-bottom:calc(76px + env(safe-area-inset-bottom,0px));
  }

  /* The top bar keeps the brand and the account; Settings and Sign out live
     in the More sheet, so three icon buttons competing for a phone's width
     is duplication, not access. */
  .top-nav .nav-icon-btn[data-view],
  .top-nav #logout-btn{display:none}

  /* Plyr ships desktop-sized controls; on a phone they crowded the frame. */
  .plyr{
    --plyr-control-icon-size:16px;
    --plyr-control-spacing:8px;
    --plyr-font-size-time:12px;
    --plyr-range-thumb-height:12px;
  }

  .main-body{padding:14px}
  .main-header{padding:14px 14px;gap:10px}
  .toolbar-search{min-width:0;flex:1}
  .grid-posters{grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px}
  .grid-channels{grid-template-columns:1fr;gap:10px}
}

@media (max-width: 640px){
  .top-nav{padding:0 8px;gap:6px;height:52px}
  .brand{font-size:1.05rem}
  .brand-icon{width:28px;height:28px}
  .nav-icon-btn{width:34px;height:34px}
  .nav-icon-btn svg{width:15px;height:15px}
  .sidebar-toggle{width:38px;height:38px}
  .sidebar-toggle svg{width:18px;height:18px}
  .profile-chip{padding:3px 5px}
  .profile-chip .avatar{width:26px;height:26px;font-size:0.7rem}
  .view .side-panel,
  .view .main-area .side-panel{top:52px;width:90vw}
  .main-body{padding:12px}
  .main-header{padding:12px 12px}
  .grid-posters{grid-template-columns:repeat(auto-fill,minmax(108px,1fr));gap:8px}
  .poster-title{font-size:0.78rem}
  .poster-meta{font-size:0.64rem}
  .modal-backdrop{padding:8px}
  .modal{max-height:94vh}
  .modal-head{padding:12px 14px}
  .modal-body{padding:14px}
  .toast{left:12px;right:12px;bottom:calc(74px + env(safe-area-inset-bottom,0px))}
  .channel-card{padding:10px;gap:10px}
  .channel-logo{width:46px;height:46px}
  .bn-tab span{font-size:0.58rem}
}

/* Tiny screens */
@media (max-width: 380px){
  .grid-posters{grid-template-columns:repeat(2,1fr);gap:8px}
  .nav-icon-btn{width:32px;height:32px}
  .sidebar-toggle{width:36px;height:36px}
  .brand-text{display:none}
}

/* ═══════════════════════════════════════════════════════════════════════════
   GATE PAGES - sign in, and the add-on notice
   ═══════════════════════════════════════════════════════════════════════════
   These are the only two screens a viewer can reach before the app shell, so
   they carry the whole first impression. They reuse the app's own tokens and
   its Outfit/DM Sans pairing rather than introducing a second look: a login
   page that does not resemble the player it opens reads as a different
   product, or as a phishing page.

   The one deliberate flourish is the backdrop - a faint grid of channel tiles
   behind the card, the wall of screens the product is actually about. It is
   drawn in the accent colour at a few percent, so it reads as texture at a
   glance and resolves into tiles only if you look. Everything else is quiet.
   ═════════════════════════════════════════════════════════════════════════ */

.gate-body{
  margin:0;
  min-height:100vh;
  background:var(--bg-primary);
  color:var(--text-primary);
  font-family:'DM Sans',system-ui,-apple-system,'Segoe UI',sans-serif;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:24px;
  position:relative;
  overflow:hidden;
}

/* The channel wall. Two layers: the tile grid, and a soft accent bloom that
   lifts the centre so the card sits in light rather than on a flat field. */
.gate-body::before{
  content:'';
  position:absolute;
  inset:-10%;
  background-image:
    linear-gradient(var(--border-strong) 1px,transparent 1px),
    linear-gradient(90deg,var(--border-strong) 1px,transparent 1px);
  background-size:112px 63px,112px 63px;   /* 16:9 tiles */
  opacity:.5;
  mask-image:radial-gradient(ellipse 80% 70% at 50% 45%,#000 20%,transparent 78%);
  -webkit-mask-image:radial-gradient(ellipse 80% 70% at 50% 45%,#000 20%,transparent 78%);
  pointer-events:none;
}
.gate-body::after{
  content:'';
  position:absolute;
  left:50%;top:38%;
  width:min(760px,110vw);aspect-ratio:1;
  transform:translate(-50%,-50%);
  background:radial-gradient(circle,var(--accent-dim) 0%,transparent 62%);
  pointer-events:none;
}

.gate{
  position:relative;
  z-index:1;
  width:100%;
  max-width:420px;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:20px;
}

.gate-card{
  width:100%;
  background:var(--bg-card);
  border:1px solid var(--border);
  border-radius:16px;
  padding:36px 32px 32px;
  box-shadow:0 24px 60px rgba(0,0,0,.45);
  text-align:center;
}

/* Centred explicitly rather than relying on the card's text-align: the logo is
   a replaced element whose box can be as wide as the card, and the operator's
   image sits wherever it likes inside that box. */
.gate-brand{
  margin-bottom:26px;
  display:flex;align-items:center;justify-content:center;
}
.gate-logo{display:block;max-height:44px;max-width:100%;object-fit:contain;margin:0 auto}
.gate-wordmark{
  font-family:'Outfit',system-ui,sans-serif;
  font-weight:800;
  font-size:24px;
  letter-spacing:-.02em;
  color:var(--text-primary);
}
.gate-wordmark span{color:var(--accent)}

.gate-icon{
  width:52px;height:52px;
  margin:0 auto 22px;
  display:grid;place-items:center;
  border-radius:14px;
  background:var(--accent-dim);
  color:var(--accent);
}
.gate-icon svg{width:26px;height:26px}

.gate-title{
  font-family:'Outfit',system-ui,sans-serif;
  font-size:26px;
  font-weight:700;
  letter-spacing:-.02em;
  margin:0 0 8px;
}
.gate-sub{
  margin:0 0 24px;
  font-size:14px;
  line-height:1.55;
  color:var(--text-secondary);
}

.gate-error{
  margin:0 0 20px;
  padding:11px 14px;
  border-radius:10px;
  background:rgba(239,68,68,.12);
  border:1px solid rgba(239,68,68,.3);
  color:#fca5a5;
  font-size:13.5px;
  line-height:1.45;
  text-align:left;
}

.gate-form{display:flex;flex-direction:column;gap:14px;text-align:left}
.gate-field{display:flex;flex-direction:column;gap:7px}
.gate-field span{
  font-size:12px;
  font-weight:600;
  letter-spacing:.04em;
  text-transform:uppercase;
  color:var(--text-muted);
}
.gate-field input{
  width:100%;
  box-sizing:border-box;
  padding:12px 14px;
  border-radius:10px;
  border:1px solid var(--border-strong);
  background:var(--bg-input);
  color:var(--text-primary);
  font-family:inherit;
  font-size:15px;
  transition:border-color var(--transition),box-shadow var(--transition);
}
.gate-field input::placeholder{color:var(--text-muted)}
.gate-field input:focus{
  outline:none;
  border-color:var(--border-focus);
  box-shadow:0 0 0 3px var(--accent-dim);
}

.gate-captcha{display:grid;grid-template-columns:160px 36px 1fr;gap:8px;align-items:center}
.gate-captcha img{display:block;width:160px;height:52px;border-radius:10px;border:1px solid var(--border-strong);background:#0f1420}
.gate-captcha-refresh{
  width:36px;height:36px;border-radius:9px;border:1px solid var(--border-strong);
  background:var(--bg-input);color:var(--text-secondary);font-size:18px;line-height:1;cursor:pointer;
  transition:border-color var(--transition),color var(--transition);
}
.gate-captcha-refresh:hover{border-color:var(--accent);color:var(--text-primary)}
.gate-captcha-refresh:disabled{opacity:.5;cursor:default}
.gate-captcha input{letter-spacing:.18em;text-transform:uppercase;font-weight:600}
@media (max-width:420px){
  .gate-captcha{grid-template-columns:130px 36px 1fr}
  .gate-captcha img{width:130px;height:44px}
}

.gate-btn{
  display:block;
  width:100%;
  box-sizing:border-box;
  margin-top:6px;
  padding:13px 18px;
  border:none;
  border-radius:10px;
  background:var(--accent);
  color:#fff;
  font-family:'Outfit',system-ui,sans-serif;
  font-size:15px;
  font-weight:600;
  letter-spacing:.01em;
  cursor:pointer;
  text-align:center;
  text-decoration:none;
  transition:background var(--transition),transform var(--transition);
}
.gate-btn:hover{background:var(--accent-hover)}
.gate-btn:active{transform:translateY(1px)}

.gate-link{
  display:inline-block;
  margin-top:20px;
  font-size:13.5px;
  color:var(--text-secondary);
  text-decoration:none;
  border-bottom:1px solid var(--border-strong);
  padding-bottom:2px;
  transition:color var(--transition),border-color var(--transition);
}
.gate-link:hover{color:var(--text-primary);border-color:var(--accent)}


/* The quality floor, stated rather than assumed. */
.gate-btn:focus-visible,
.gate-link:focus-visible,
.gate-field input:focus-visible{
  outline:2px solid var(--accent);
  outline-offset:2px;
}

@media (max-width:420px){
  .gate-card{padding:28px 22px 26px;border-radius:14px}
  .gate-title{font-size:22px}
}

/* A logo in the app's own top bar, when the operator has uploaded one. */
.brand-logo{max-height:30px;max-width:190px;object-fit:contain;display:block}

/* A list that had to be capped says so. Quiet, but never absent: a truncated
   list that looks complete is how a viewer concludes a channel is missing. */
.more-note{
  margin:0 0 12px;
  padding:9px 13px;
  border-radius:9px;
  background:var(--bg-hover);
  border:1px solid var(--border);
  color:var(--text-secondary);
  font-size:12.5px;
  line-height:1.5;
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE "MORE" SHEET  (phones)
   A bottom sheet for the destinations the five-slot bottom bar cannot hold.
   ═══════════════════════════════════════════════════════════════════════════ */
.sheet-backdrop{
  position:fixed;inset:0;z-index:80;
  background:rgba(0,0,0,0.55);
  display:flex;align-items:flex-end;
  opacity:0;transition:opacity .18s ease;
}
.sheet-backdrop.open{opacity:1}
.sheet{
  width:100%;
  background:var(--bg-secondary);
  border-top:1px solid var(--border-strong);
  border-radius:18px 18px 0 0;
  padding:10px 14px calc(16px + env(safe-area-inset-bottom,0px));
  transform:translateY(14px);transition:transform .2s cubic-bezier(0.4,0,0.2,1);
  box-shadow:0 -18px 40px rgba(0,0,0,0.5);
}
.sheet-backdrop.open .sheet{transform:translateY(0)}
.sheet-grip{
  width:38px;height:4px;border-radius:2px;
  background:var(--border-strong);margin:2px auto 12px;
}
.sheet-items{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.sheet-item{
  display:block;width:100%;box-sizing:border-box;
  padding:14px 14px;border-radius:11px;
  background:var(--bg-card);border:1px solid var(--border);
  color:var(--text-primary);
  font-family:'DM Sans',system-ui,sans-serif;font-size:0.88rem;font-weight:600;
  text-align:left;text-decoration:none;cursor:pointer;
  transition:all var(--transition);
}
.sheet-item:active{transform:scale(0.98);background:var(--bg-hover)}
.sheet-signout{margin-top:8px;text-align:center;color:var(--error);background:transparent}
@media (min-width:769px){ .sheet-backdrop{display:none} }

/* The player frame before a channel is chosen.
   It used to be an empty <video> with the browser's own controls sitting in a
   black rectangle, which reads as a broken player rather than a waiting one. */
.player-idle{
  position:absolute;inset:0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:14px;text-align:center;padding:24px;
  color:var(--text-muted);
  background:radial-gradient(ellipse 60% 60% at 50% 45%,rgba(59,130,246,0.07),transparent 70%);
}
.player-idle svg{width:44px;height:44px;opacity:0.55}
.player-idle p{
  margin:0;font-family:'Outfit',system-ui,sans-serif;
  font-size:0.95rem;font-weight:500;color:var(--text-secondary);
}

/* Display preferences, applied from the viewer's settings (App.applyDisplayPrefs).
   In the reference player these two controls saved a value nothing ever read. */
:root[data-grid="compact"] .grid-posters{grid-template-columns:repeat(auto-fill,minmax(120px,1fr))}
:root[data-grid="large"]   .grid-posters{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}
:root[data-ratings="0"] .poster-rating{display:none}

/* ─────────────────── CHANNEL LOGO SLOT ───────────────────
   The slot is always drawn: a faint aerial glyph on the card colour. The image
   lies on top and is revealed only once it has decoded, so a logo that is
   still loading, missing, or broken all look the same - and none of them shows
   the browser's empty-image box. */
.ch-logo{
  position:relative;flex-shrink:0;
  width:var(--ch-logo-size,24px);height:var(--ch-logo-size,24px);
  border-radius:5px;overflow:hidden;
  background:var(--bg-card);
  display:inline-flex;align-items:center;justify-content:center;
  color:var(--text-muted);
}
.ch-logo > svg{width:58%;height:58%;opacity:0.45}
.ch-logo-img{
  position:absolute;inset:0;
  width:100%;height:100%;object-fit:contain;
  background:var(--bg-card);
  opacity:0;transition:opacity .18s ease;
}
.ch-logo-img.is-loaded{opacity:1}

/* The bigger card variant used by Favourites and Search. Same slot, no white
   edge - the border read as a frame around an empty box while loading. */
.channel-logo{border:none}

/* ─────────────── SIGN-IN PROMPT ───────────────
   Shown where a guest opens a screen that only means something for a
   subscription. It replaces a plain-text "Login required." that the app
   injected raw into the view - a bare sentence in the browser's default font
   in the middle of the player. An empty screen is an invitation to act. */
.signin-prompt{
  height:100%;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:14px;padding:48px 24px;text-align:center;
  color:var(--text-muted);
}
.signin-prompt svg{width:46px;height:46px;opacity:0.5;color:var(--accent)}
.signin-prompt h3{
  font-family:'Outfit',system-ui,sans-serif;
  font-size:1.15rem;font-weight:700;color:var(--text-primary);margin:0;
}
.signin-prompt p{
  margin:0;max-width:380px;
  font-size:0.86rem;line-height:1.6;color:var(--text-secondary);
}
.signin-btn{
  margin-top:6px;
  display:inline-block;padding:11px 26px;border-radius:10px;
  background:var(--accent);color:#fff;
  font-family:'Outfit',system-ui,sans-serif;font-size:0.92rem;font-weight:600;
  text-decoration:none;transition:background var(--transition),transform var(--transition);
}
.signin-btn:hover{background:var(--accent-hover)}
.signin-btn:active{transform:translateY(1px)}
.signin-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* The rating, on the artwork rather than under it - top right, clear of both
   edges. The favourite heart moves to the opposite corner so the two controls
   never sit on top of each other. */
.poster-rate{
  position:absolute;top:5px;right:5px;z-index:2;
  display:inline-flex;align-items:center;gap:3px;
  padding:3px 7px;border-radius:20px;
  background:rgba(0,0,0,0.66);
  /* The pill sits on artwork of any colour, so it carries its own contrast. */
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  color:#fff;font-size:0.68rem;font-weight:700;line-height:1;
  letter-spacing:.01em;pointer-events:none;
}
.poster-rate svg{width:10px;height:10px;color:var(--star)}

/* ─────────────── PLAYBACK STATE ───────────────
   Sits over the video frame and says what playback is doing: connecting,
   retrying, or stopped with the reason. A player that shows controls over a
   black rectangle and waits forever tells the viewer nothing they can act on. */
.pb-host{position:relative}
.pb-state{
  position:absolute;inset:0;z-index:3;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:9px;padding:22px;text-align:center;
  background:rgba(11,14,20,0.82);
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  color:var(--text-secondary);
}
.pb-state[data-state="connecting"]{background:rgba(11,14,20,0.55)}
.pb-icon{width:38px;height:38px;color:var(--warning);opacity:.9}
.pb-title{
  margin:0;font-family:'Outfit',system-ui,sans-serif;
  font-size:1rem;font-weight:700;color:var(--text-primary);
}
.pb-msg{margin:0;font-size:0.84rem;line-height:1.5;max-width:420px}
.pb-note{margin:0;font-size:0.74rem;color:var(--text-muted)}
.pb-retry{
  margin-top:6px;padding:9px 20px;border-radius:9px;border:none;
  background:var(--accent);color:#fff;
  font-family:'Outfit',system-ui,sans-serif;font-size:0.86rem;font-weight:600;
  cursor:pointer;transition:background var(--transition),transform var(--transition);
}
.pb-retry:hover{background:var(--accent-hover)}
.pb-retry:active{transform:translateY(1px)}
.pb-retry:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.pb-spin{
  width:30px;height:30px;border-radius:50%;
  border:3px solid var(--border-strong);border-top-color:var(--accent);
  animation:spin .8s linear infinite;
}
@media (prefers-reduced-motion:reduce){ .pb-spin{animation-duration:2.4s} }
