/* Interface TV 1920x1080.
 *
 * Contraintes propres au support :
 *  - lecture a ~3 m : rien sous 20 px, contrastes appuyes ;
 *  - overscan : 48 px de marge de securite sur les bords ;
 *  - le focus doit etre identifiable sans ambiguite (contour + echelle) ;
 *  - jamais de filter ni de blur : le compositeur d'une TV s'ecroule dessus.
 *
 * Sur l'animation : transform et opacity sont compositables, donc gratuits, et
 * ce sont les seuls utilises pour tout ce qui se repete (entrees en scene, onde
 * des squelettes, echelle du focus). box-shadow est en revanche une operation de
 * peinture ; elle n'est transitionnee que sur l'anneau de focus, ou deux
 * elements au plus changent a la fois. Le fond dynamique repeint un degrade
 * plein ecran : c'est pourquoi UI.backdrop() est debounce cote JS.
 */

:root {
  /* --- Palette ---
   *
   * Relevee au pixel sur les captures de Zen Player, pas estimee a l'oeil :
   *   fond #121528, surface #1c1f32, violet #8168fe, menthe #a5ece8,
   *   cyan de la pastille de source #00bcd5, rouge destructif ~#ef4056.
   *
   * Repartition des roles, telle qu'elle ressort de leurs ecrans :
   *   violet  = marque et actions (bordures de pilules, barres d'avancement,
   *             icones, nom de la source) ;
   *   menthe  = etat actif ou selectionne (soulignement d'onglet, segment
   *             courant, avancement d'un contenu commence) ;
   *   blanc   = focus. Non negociable sur TV : un focus uniquement colore
   *             disparait sur une dalle mal calibree.
   */
  --bg: #121528;
  --bg-alt: #171b30;
  --panel: #1c1f32;
  --panel-hi: #252943;
  --panel-top: #2e3350;      /* haut du degrade des panneaux */
  --line: #2b3050;

  --fg: #f4f5fb;
  --fg-dim: #9aa2c0;
  --fg-faint: #6b7396;

  --accent: #8168fe;         /* violet : marque et actions */
  --accent-soft: #9c88ff;
  --accent-deep: #6a4ef5;
  --accent-fg: #ffffff;

  --mint: #a5ece8;           /* menthe : etat actif */
  --mint-deep: #5fcfc8;
  --cyan: #00bcd5;           /* pastille de source */

  --danger: #ef4056;
  --live: #22c55e;
  --warn: #eab308;

  --focus: #ffffff;

  --safe: 48px;
  /* Place reservee a l anneau de focus (3 px) et a son halo (9 px) : sans
     cette marge, overflow:auto rogne le focus du premier element de chaque
     liste. */
  --ring: 14px;
  /* Largeur du rail replie. Les sections reservent cette bande a gauche ; le
     rail ne s'elargit qu'en recouvrement, donc cette valeur ne bouge jamais.
     108px laissait ~74px de vide entre l'icone (34px) et le contenu -- signale
     par l'utilisateur ("espace enorme a droite du menu"), corrige une premiere
     fois a 72px. Le contenu restait pourtant visiblement plus indente a gauche
     qu'a droite (retour direct) : normal, .catalog-body ajoute encore --safe
     (48px) de padding a l'interieur de cette bande, cote gauche seulement --
     mais l'ecart perçu justifiait de resserrer encore un peu. 56px laisse tout
     juste l'icone (34px) plus une marge de 11px de chaque cote. */
  --rail-w: 56px;

  /* --- Rayons ---
     Echelle unique : des rayons incoherents sont ce qui fait le plus
     "bricole" sur un grand ecran. */
  --r-sm: 8px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-pill: 999px;
  --radius: var(--r-md);      /* conserve pour les regles existantes */

  /* --- Mouvement ---
     Le ressort de Zen Player (spring_animation) n'existe pas en CSS ; une
     Bezier avec depassement en est l'equivalent atteignable, et elle reste
     compositable donc gratuite pour le GPU de la TV. */
  --ease-spring: cubic-bezier(0.34, 1.42, 0.64, 1);
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --t-fast: 130ms;
  --t-mid: 240ms;
  --t-slow: 420ms;

  /* Teinte du fond dynamique, pilotee par le focus depuis UI.backdrop(). */
  --backdrop-hue: 220;
}

* { box-sizing: border-box; }

/* [hidden] doit gagner sur tout.
   La regle `display:none` de l'attribut hidden vient de la feuille UA, donc une
   simple classe comme `.modal{display:flex}` la surclasse : la modale et le
   spinner restaient affiches en permanence, voiles compris. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  width: 1920px;
  height: 1080px;
  overflow: hidden;
  background: var(--bg);
  color: var(--fg);
  font-family: 'Golos Text', 'LG Smart UI', 'Segoe UI', system-ui, sans-serif;
  font-size: 20px;
  line-height: 1.45;
  /* Le curseur du Magic Remote reste utile, mais on ne selectionne rien. */
  user-select: none;
  -webkit-user-select: none;
  /* Golos est une police geometrique : sans lissage explicite, les graisses
     lourdes bavent sur certaines dalles. */
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

button, input, textarea { font-family: inherit; color: inherit; }
button { border: 0; background: none; cursor: pointer; }
p { margin: 0 0 12px; }

/* --- Echelle typographique ---
   Titres serres et lourds, corps de texte aere : c'est ce contraste qui donne
   l'impression de densite maitrisee plutot que de texte etale. */
h1, h2, h3 { margin: 0; font-weight: 700; }
h1 { font-weight: 800; letter-spacing: -0.02em; }
h2 { letter-spacing: -0.01em; }

.view-host { width: 1920px; height: 1080px; position: relative; }

/* Marge anti-surbalayage, sur le CONTENU seulement -- et JAMAIS d'office.
   Elle etait codee en dur a 95 % sur la coque native : sur un televiseur qui
   ne rogne pas, cela se voyait comme un liseré parasite a gauche et en haut.
   C'est donc le reglage « Taille de l'image » qui la pilote, plein cadre par
   defaut. Applique ici et non sur la WebView, sinon le lecteur -- frere de
   .view-host, pas enfant -- serait en retrait lui aussi. */
.view-host {
  transform: scale(var(--ui-scale, 1));
  transform-origin: 50% 50%;
}

/* ------------------------------------------------------------------ *
 * Fond dynamique
 *
 * Zen Player teinte le fond d'apres l'affiche en cours. On obtient le meme
 * effet sans lire les pixels de l'image : la teinte est derivee du nom de
 * l'element focalise (meme calcul que les vignettes d'initiales), donc stable
 * d'un lancement a l'autre et sans acces reseau.
 *
 * Un seul calque, teinte changee via une variable CSS et transition lente :
 * repeindre un degrade plein ecran a chaque deplacement du focus serait trop
 * couteux, d'ou le lissage cote JS (UI.backdrop est debounce).
 * ------------------------------------------------------------------ */

.backdrop {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  z-index: 0;
  pointer-events: none;
  /* PAS de transition sur `background`.
   *
   * Elle faisait repeindre 1920x1080 de degrades a chaque image pendant toute
   * sa duree, a chaque deplacement du focus stabilise : c'est un travail non
   * composite, et la navigation saccadait sur le SoC du televiseur. La teinte
   * ne bougeant que dans une bande indigo -> magenta, le changement sec ne se
   * remarque pas -- l'a-coup, lui, se remarquait. */
  /* Trois foyers : un dominant en haut a gauche, un complementaire en bas a
     droite, et un voile sombre pour que le texte garde son contraste au centre.
     Les luminosites sont volontairement basses -- sur une dalle OLED, un fond
     trop clair mange le contraste du focus. */
  background:
    radial-gradient(1400px 900px at 10% -12%,
      hsl(var(--backdrop-hue) 52% 26%) 0%, transparent 60%),
    radial-gradient(1000px 700px at 98% 110%,
      hsl(calc(var(--backdrop-hue) + 42) 46% 20%) 0%, transparent 56%),
    linear-gradient(180deg, transparent 30%, rgba(18, 21, 40, 0.55) 100%);
}

/* Le contenu passe au-dessus du fond. */
#view, .home, .live, .guide, .catalog, .detail, .search, .settings, .organise, .login,
.profiles {
  position: relative;
  z-index: 1;
}

/* ------------------------------------------------------------------ *
 * Squelettes de chargement
 *
 * Remplacent le mot « Chargement... ». L'onde est un pseudo-element deplace en
 * transform : c'est compositable, donc sans cout de peinture, contrairement a
 * une animation de background-position.
 * ------------------------------------------------------------------ */

.sk {
  position: relative;
  overflow: hidden;
  background: var(--panel);
  border-radius: var(--r-sm);
}
.sk::after {
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg,
    transparent 0%, rgba(255, 255, 255, 0.07) 45%,
    rgba(255, 255, 255, 0.11) 50%, rgba(255, 255, 255, 0.07) 55%, transparent 100%);
  animation: sk-wave 1500ms ease-in-out infinite;
}
@keyframes sk-wave { to { transform: translateX(100%); } }

/* Titre defilant (marquee) sur un focus prolonge -- voir Nav.armMarquee().
 * Repos : troncature normale par ellipsis. En defilement, on desactive
 * l'ellipsis (elle coupait la fin du texte que l'animation doit justement
 * reveler) et on glisse le contenu de droite a gauche, avec une pause nette a
 * chaque bout pour laisser le temps de lire le debut et la fin. La duree
 * depend de la longueur reelle du texte (--marquee-distance, pose en JS) :
 * un titre a peine plus long que sa boite ne doit pas defiler aussi longtemps
 * qu'un titre trois fois trop long. */
.marquee-text {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}
/* .tile-title clampe sur 2 lignes au repos (voir sa regle) -- ce
   contrat-la reste intact tant qu'on ne defile pas. Le passage a une
   seule ligne ne doit arriver qu'EN DEFILEMENT, sinon toute tuile
   perdrait sa deuxieme ligne des l'affichage, focus ou non. */
.tile-title.marquee-text {
  display: -webkit-box;
  white-space: normal;
}
.marquee-text.is-scrolling {
  display: inline-block;
  /* Annule explicitement le clamp 2 lignes de .tile-title : -webkit-line-clamp
     restait actif visuellement sur certains rendus meme apres le passage a
     inline-block, ce qui coupait le texte AVANT la fin reelle du defilement --
     on voyait une portion deja lisible sans jamais atteindre la fin du titre. */
  -webkit-line-clamp: unset;
  overflow: visible;
  white-space: nowrap;
  text-overflow: clip;
  /* 2 passages, pas infinite : en boucle, un titre trop long qui reste
     focalise longtemps (l'utilisateur consulte la fiche, hesite...) defilait
     indefiniment -- signale a l'usage comme genant plutot qu'utile passe les
     deux premieres lectures. Le titre reste ensuite au repos (immobile a son
     origine, la derniere etape des keyframes revient a translateX(0)) plutot
     que de disparaitre ou de rester coupe en cours de course. */
  animation: marquee-scroll var(--marquee-duration, 8s) linear 2;
}
@keyframes marquee-scroll {
  0%, 8%   { transform: translateX(0); }
  46%, 54% { transform: translateX(calc(-1 * var(--marquee-distance, 0px))); }
  92%, 100% { transform: translateX(0); }
}

/* Decalage par element : une vague unique sur toute la liste ferait « bloc ». */
.sk-stagger > *:nth-child(2n)::after { animation-delay: 140ms; }
.sk-stagger > *:nth-child(3n)::after { animation-delay: 280ms; }

.sk-row {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 12px 18px;
  margin-bottom: 6px;
}
.sk-logo { flex: 0 0 84px; height: 50px; border-radius: 6px; }
.sk-lines { flex: 1; }
.sk-line { height: 17px; margin-bottom: 9px; }
.sk-line.is-short { width: 32%; }
.sk-line.is-mid { width: 58%; }

.sk-tile { width: 240px; height: 300px; border-radius: var(--r-lg); }
.sk-poster { width: 100%; height: 380px; border-radius: var(--r-lg); }

/* ------------------------------------------------------------------ *
 * Entree en scene
 *
 * Une apparition decalee remplace l'affichage sec. Uniquement opacity et
 * transform, et l'animation ne joue qu'une fois : pas de cout continu.
 * ------------------------------------------------------------------ */

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}
.rise { animation: rise var(--t-mid) var(--ease-out) both; }
.rise-1 { animation-delay: 40ms; }
.rise-2 { animation-delay: 90ms; }
.rise-3 { animation-delay: 140ms; }
.rise-4 { animation-delay: 190ms; }

/* Le lecteur masque l'interface sans la demonter : revenir au meme etat de
   defilement apres un zapping evite de tout recharger. */
body.is-playing .view-host { visibility: hidden; }

/* Lecture par le pont natif (Android TV) : ExoPlayer dessine la video SOUS la
   WebView. Tout fond opaque au-dessus la masquerait -- on entendrait le son
   sur un ecran noir, ce qui s'est produit au premier essai reel. Pendant une
   lecture native, la pile entiere devient donc transparente ; les degrades du
   lecteur restent lisibles (rgba). La classe est posee par player.js. */
html.native-playing,
html.native-playing body { background: transparent; }
html.native-playing #backdrop { display: none; }
html.native-playing .player { background: transparent; }

/* ------------------------------------------------------------------ *
 * Focus
 * ------------------------------------------------------------------ */

[data-focusable] {
  outline: none;
  /* Courbe a depassement : l'element « arrive » au lieu de glisser. C'est le
     detail qui donne le plus de vie pour le moins de cout.
     box-shadow N'EST PLUS anime : contrairement a transform (compose par le
     GPU, quasi gratuit), animer une box-shadow force un repaint CPU complet de
     l'element a chaque frame. Sur une liste dense, avec le deplacement rapide
     du focus a la telecommande, ce repaint s'accumulait et se lisait comme un
     retard de l'anneau blanc par rapport au fond violet -- les deux
     changeaient au meme instant, mais l'un se peint plus vite que l'autre.
     Constate sur la TV. L'anneau apparait maintenant instantanement. */
  transition:
    transform var(--t-fast) var(--ease-spring),
    background-color var(--t-fast) linear;
}

[data-focusable].is-focused {
  position: relative;
  z-index: 5;
}

/* Anneau de focus en box-shadow : pas de reflow, contrairement a une bordure.
   Double couche : un halo accent diffus pour la presence, un anneau blanc net
   pour la lisibilite a trois metres. L'anneau blanc n'est pas negociable --
   sur une dalle mal calibree, un focus uniquement colore devient invisible. */
[data-focusable].is-focused::after {
  content: '';
  position: absolute;
  top: -4px; right: -4px; bottom: -4px; left: -4px;
  border-radius: inherit;
  box-shadow:
    0 0 0 3px var(--focus),
    0 0 0 9px rgba(129, 104, 254, 0.30),
    0 18px 44px rgba(0, 0, 0, 0.55);
  pointer-events: none;
}

/* Les elements pleine largeur (lignes de liste, reglages) n'ont pas besoin du
   halo : il deborde et alourdit. Anneau seul. */
.set-row.is-focused::after,
.org-row.is-focused::after,
.chan.is-focused::after,
.ep.is-focused::after,
.picker-row.is-focused::after,
.cat.is-focused::after,
.row-btn.is-focused::after {
  box-shadow: 0 0 0 3px var(--focus), 0 10px 26px rgba(0, 0, 0, 0.45);
}

/* Le fond de .cat.is-active etait peint dans le border-box exact du bouton,
   sans debordement -- l'anneau du focus, lui, deborde de -4px sur les 4
   cotes comme partout ailleurs. Resserrer l'anneau contre le texte (essaye
   d'abord) a ete rejete par l'utilisateur : les deux colles au bord du texte
   ne respirait plus et rendait moins bien. La bonne direction est l'inverse
   -- elargir le fond actif vers la gauche pour retrouver la meme respiration
   que l'anneau, plutot que resserrer l'anneau contre le texte.
   Un pseudo-element evite de toucher au texte : margin/width sur .cat
   deplaceraient aussi .cat-name via son propre padding relatif au bouton, ce
   qui redecalerait le texte par rapport au titre de section (deja corrige
   une fois). Le fond visuel devient donc ce ::before, positionne en dehors
   du flux et debordant de 4px a gauche -- meme valeur que l'anneau -- sans
   jamais deplacer le contenu texte. */
.cat.is-active::before {
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: -4px;
  background: rgba(165, 236, 232, 0.09);
  border-radius: inherit;
  z-index: -1;
}

/* ------------------------------------------------------------------ *
 * Elements communs
 * ------------------------------------------------------------------ */

.btn {
  display: inline-block;
  padding: 14px 30px;
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, var(--panel-top) 0%, var(--panel-hi) 100%);
  color: var(--fg);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.btn.is-focused { background: linear-gradient(180deg, #3a3f68 0%, #2e3352 100%); transform: scale(1.05); }
.btn-primary {
  background: linear-gradient(180deg, var(--accent-soft) 0%, var(--accent) 100%);
  color: var(--accent-fg);
  font-weight: 700;
}
.btn-primary.is-focused { background: linear-gradient(180deg, #fb5a72 0%, var(--accent-soft) 100%); }

.input {
  display: block;
  width: 100%;
  padding: 14px 18px;
  border: 2px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-alt);
  font-size: 22px;
}
.input.is-focused { border-color: var(--focus); background: #0d1119; }

.field { display: block; margin-bottom: 20px; }
.field-label {
  display: block;
  margin-bottom: 6px;
  color: var(--fg-dim);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.hint { color: var(--fg-dim); font-size: 17px; line-height: 1.5; max-width: 900px; }
.mono { font-family: 'Consolas', 'Menlo', monospace; font-size: 16px; word-break: break-all; }

/* Sortie du banc de mesure : lue depuis un canape, donc alignee en colonnes et
   nettement plus grande que le reste des indications. white-space: pre-wrap
   plutot que pre, sinon une ligne longue deborderait hors de l'ecran d'une TV
   sans que rien ne le laisse voir. */
.bench-out {
  white-space: pre-wrap;
  word-break: normal;
  font-size: 18px;
  line-height: 1.6;
  max-width: 1100px;
  margin: 12px 0 0;
  padding: 16px 20px;
  background: rgba(0, 0, 0, 0.35);
  border-radius: 10px;
  color: var(--fg);
}
.bench-out:empty { display: none; }

.chip {
  display: inline-block;
  padding: 4px 12px;
  margin: 0 8px 8px 0;
  border-radius: 999px;
  background: var(--panel-hi);
  color: var(--fg-dim);
  font-size: 16px;
}

.tag {
  display: inline-block;
  margin-left: 10px;
  padding: 2px 8px;
  border-radius: 4px;
  background: var(--accent);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  vertical-align: middle;
}
/* Archive disponible : même code couleur que le badge CATCH-UP du lecteur. */
.tag-catchup { background: rgba(234, 179, 8, 0.20); color: #fde047; }

.loading, .empty {
  padding: 60px;
  color: var(--fg-dim);
  font-size: 22px;
  text-align: center;
}
.empty .btn { margin-top: 20px; }

.fatal { padding: var(--safe); color: var(--fg); }
.fatal h1 { margin-bottom: 16px; color: var(--accent); }

/* Legendes des touches couleur */
.key-red,  .key-green, .key-yellow, .key-blue { position: relative; padding-left: 22px; }
.key-red::before, .key-green::before, .key-yellow::before, .key-blue::before {
  content: '';
  position: absolute;
  left: 0; top: 50%;
  width: 14px; height: 14px;
  margin-top: -7px;
  border-radius: 50%;
}
.key-red::before    { background: #ef4444; }
.key-green::before  { background: #22c55e; }
.key-yellow::before { background: #eab308; }
.key-blue::before   { background: #3b82f6; }

/* ------------------------------------------------------------------ *
 * En-tetes
 * ------------------------------------------------------------------ */

.sub-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: var(--safe) var(--safe) 20px;
}
/* Vues montees a cote du rail (.shell, voir Rail.mount) : celui-ci a deja
   recule tout son contenu de calc(--rail-w + --safe), donc re-appliquer
   --safe a gauche ici double-comptait la marge -- meme raison que
   .live-body/.catalog-body plus bas. Portee a .shell uniquement : les vues
   qui n'ont pas de rail visible (fiche detaillee, organiser...) gardent le
   padding par defaut ci-dessus, leur bord gauche etant le vrai bord d'ecran. */
.shell .sub-head { padding-left: 0; }
/* Films/Series ET TV : leur corps (.catalog-grid / .chan-pane) ajoute --ring
   de padding-right par-dessus --safe pour loger l'anneau de focus de la
   derniere colonne/ligne (voir le commentaire sur --ring plus haut). Sans
   compenser ici, le texte d'en-tete ("XXX titres", "Chiffres : numero de
   chaine") atterrissait --ring plus pres du bord que le contenu lui-meme --
   signale par l'utilisateur pour les deux. Recherche et Reglages n'ont pas
   ce --ring supplementaire et gardent donc le padding par defaut ci-dessus. */
.catalog .sub-head, .live .sub-head { padding-right: calc(var(--safe) + var(--ring)); }
.sub-head h1 { font-size: 42px; font-weight: 800; letter-spacing: -0.025em; }
.sub-hints { display: flex; align-items: center; gap: 28px; color: var(--fg-dim); font-size: 17px; }

/* Puce d'action dans un en-tete de section : double une touche de couleur pour
   qui ne connait pas la telecommande par coeur. */
.btn-chip {
  padding: 10px 22px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.07);
  color: var(--fg);
  font-size: 18px;
  font-weight: 600;
}
.btn-chip.is-focused {
  background: linear-gradient(180deg, var(--accent-soft) 0%, var(--accent) 100%);
  color: var(--accent-fg);
  transform: scale(1.05);
}

/* Indice de retour : un rappel, pas un bouton. Discret par construction --
   il doit se remarquer quand on le cherche et disparaitre sinon. */
.back-hint {
  position: absolute;
  top: 22px;
  right: var(--safe);
  z-index: 25;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 18px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.05);
  color: var(--fg-dim);
  font-size: 17px;
  font-weight: 600;
  pointer-events: auto;
}
.back-hint-key {
  font-size: 20px;
  line-height: 1;
  color: var(--mint);
}

/* ------------------------------------------------------------------ *
 * Accueil
 * ------------------------------------------------------------------ */

.home { height: 1080px; display: flex; flex-direction: column; }

/* Le bas doit degager le titre de la premiere rangee. C'est l'ancienne barre de
   sections qui fournissait cet air ; en la retirant, l'en-tete s'est retrouvee
   collee au premier titre. */
.home-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 40px var(--safe) 34px;
}
.home-brand { display: flex; align-items: center; }
.home-brand h1 { font-size: 40px; font-weight: 800; letter-spacing: -0.03em; }
.home-source { color: var(--fg-dim); font-size: 20px; }
.home-clock { color: var(--fg-dim); font-size: 22px; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ *
 * Rail de navigation vertical
 *
 * Replie il ne montre que les pictogrammes ; il s'ouvre au focus et recouvre le
 * contenu au lieu de le pousser. C'est ce qui evite le defaut classique du rail
 * extensible : une grille entiere qui se recompose des qu'on effleure le menu.
 *
 * Les elements font toujours 320px de large et c'est le rail qui les rogne
 * (overflow: hidden). Aucun texte ne se remet donc en page a l'ouverture, seule
 * la fenetre de decoupe change -- une seule propriete animee, donc gratuite.
 * ------------------------------------------------------------------ */

/* Zone sure : les FONDS vont jusqu'au bord, les CONTENUS restent en retrait.
 *
 * C'est le motif des interfaces TV, et c'est le seul qui ne casse jamais. Les
 * autres approches essayees rentraient toutes de la meme facon : elles
 * retrecissaient quelque chose -- la WebView, ou la scene par un transform --
 * et il restait toujours un cas ou le decalage se voyait (liseré autour du
 * lecteur, image gelee de la surface video, rendu fantome au retour).
 *
 * Ici rien n'est mis a l'echelle : le rail garde `left: 0`, son fond touche le
 * bord de l'ecran, et ce sont ses icones et ses libelles qu'on rentre de
 * --safe. Toutes les autres vues ont deja ce retrait (padding: var(--safe)).
 * Consequence directe : le lecteur est plein cadre par construction, sur les
 * trois plateformes, sans un seul reglage propre a l'une d'elles. */
.shell { position: relative; height: 1080px; padding-left: calc(var(--rail-w) + var(--safe)); }
.shell-main { position: relative; height: 1080px; min-width: 0; }

/* Accueil sans compte ni source : le menu lateral n'aurait rien a ouvrir (TV,
   Films et Series y seraient vides), on le retire et le contenu reprend toute la
   largeur. Voir syncRail() dans views/account.js. */
.shell.no-rail { padding-left: 0; }
.shell.no-rail .rail { display: none; }

/* Voile sous le rail ouvert : le contenu recule d'un plan, et les libelles que
   le panneau rogne a moitie cessent d'accrocher l'oeil. Purement visuel
   (pointer-events none), une seule propriete animee (opacity). */
/* Les arrets sont en PIXELS, pas en pourcentage : .shell-main commence apres
   le rail replie, le rail ouvert s'arrete 336 px plus loin et la colonne des
   categories s'etend jusqu'a ~430 px. Le voile est donc OPAQUE sur toute cette
   zone : a 97 % il restait des bribes de libelles lisibles a droite du rail,
   qui se lisaient comme des fragments flottants. Constate sur la TV. */
.shell-main::after {
  content: '';
  position: absolute;
  left: 0; top: 0; right: 0; bottom: 0;
  background: linear-gradient(90deg,
    rgba(8, 10, 20, 1) 0px,
    rgba(8, 10, 20, 1) 430px,
    rgba(8, 10, 20, 0.72) 620px,
    rgba(8, 10, 20, 0.50) 100%);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-mid) var(--ease-out);
  z-index: 15;   /* au-dessus du contenu, sous le rail (20) */
}
.rail:focus-within + .shell-main::after,
.rail.is-open + .shell-main::after { opacity: 1; }

.rail {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  /* Le fond va jusqu'au bord (left: 0), le contenu est rentre de --safe :
     aucun televiseur ne peut donc rogner une icone, et il n'y a jamais
     d'espace visible a gauche du menu.
     Une marge superieure a --safe a ete essayee puis abandonnee sur retour
     direct de l'utilisateur ("ça ne rend pas bien") : le vrai probleme
     n'etait pas l'espace au bord mais la TAILLE des icones/texte, corrigee
     plus bas plutot qu'en reculant tout le menu du bord. */
  width: calc(var(--rail-w) + var(--safe));
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* Padding vertical reduit a --ring (14px, le plus petit espacement
     standard du projet) : sans lui du tout, Recherche et Principal
     touchaient le bord au point d'etre partiellement rognes sur un vrai
     televiseur -- avec 34px/48px (essais precedents), la marge redevenait
     trop genereuse. --ring est deja le token utilise ailleurs pour ce
     niveau de respiration minimale (voir sa definition), pas une vraie
     zone de securite de bord d'ecran comme --safe. */
  padding: var(--ring) 0;
  overflow: hidden;
  z-index: 20;
  background: linear-gradient(90deg, rgba(10, 12, 24, 0.55) 0%, rgba(10, 12, 24, 0) 100%);
  /* AUCUNE transition sur `width`.
     Animer une largeur relaie une mise en page complete a chaque image, pour
     le rail ET tout ce qu'il contient : l'ouverture du menu trainait
     visiblement sur la TV. Un menu qui s'ouvre net parait plus rapide qu'un
     menu qui glisse mal. */
}
/* Ouverture au focus. :focus-within suffit : Nav.focus() appelle bien
   node.focus(), donc l'etat est atteint aussi bien a la telecommande qu'au
   pointeur de la Magic Remote. */
/* `.rail.is-open` double `:focus-within`, qui n'arrive qu'en Chromium 60 et manque
   donc sur webOS 4.x. La classe est posee par Rail.mount() sur nav:focus, ce qui
   fonctionne sur tous les moteurs. Les deux selecteurs coexistent sans conflit. */
/* Ouvert, les elements s'elargissent avec le rail : leur rectangle reste alors
   coherent avec ce qu'on voit, et la navigation spatiale ne s'y trompe pas. */
.rail:focus-within .rail-item,
.rail.is-open .rail-item { width: calc(336px + var(--safe)); }

/* Fond PLEIN, et non un degrade qui s'eteint sur les 10 derniers pourcents :
   ce fondu laissait voir le contenu au travers du bord droit du rail. L'ombre
   est resserree pour marquer l'arete au lieu d'etaler un halo. */
.rail:focus-within,
.rail.is-open {
  width: calc(336px + var(--safe));
  background: #191d33;
  box-shadow: 12px 0 28px rgba(0, 0, 0, 0.55);
}

.rail-sep { flex: 0 0 26px; }
.rail-foot { margin-top: auto; display: flex; flex-direction: column; gap: 4px; }

.rail-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 20px;
  flex: 0 0 auto;
  /* Largeur SUIVANT l'etat du rail, et non fixee a 336 px.
   *
   * Une largeur fixe de 336 px faisait deborder les elements du rail sur le
   * contenu : visuellement ils etaient rognes, mais geometriquement leur
   * rectangle couvrait la colonne des categories. La navigation spatiale,
   * elle, raisonne sur les rectangles reels -- deux consequences :
   *
   *   - depuis une categorie, Gauche n'atteignait PAS le rail (l'element
   *     n'etait pas « a gauche », il chevauchait) ;
   *   - dans le rail, Bas sautait sur une categorie au lieu de descendre a la
   *     section suivante.
   *
   * Autrement dit le rail etait inatteignable, et inutilisable, depuis l'onglet
   * TV. La largeur fixe visait a eviter une repagination du texte : c'est
   * `white-space: nowrap` sur le libelle qui s'en charge, pas la largeur. */
  width: calc(var(--rail-w) + var(--safe));
  height: 68px;
  /* La zone sure (--safe) est ici, sur le bouton, pas sur .rail : le fond du
     focus (.is-focused, plus bas) est peint sur .rail-item et doit donc
     couvrir TOUTE sa largeur -- y compris ce qui etait avant un padding du
     conteneur .rail. Avec le padding sur .rail, le focus s'arretait net au
     bord de ce padding, laissant une bande non couverte entre le bord de
     l'ecran et l'icone (signale par l'utilisateur : "le focus ne prend pas
     toute la bande"). Ici l'icone tombe toujours exactement a --safe, mais
     le fond du bouton, lui, va jusqu'au bord. */
  padding: 0 30px 0 var(--safe);
  background: transparent;
  color: var(--fg-dim);
  /* Icone et texte agrandis sur retour direct ("trop petit pour de la
     television") : vus a distance de canape, 26px/21px se distinguaient
     mal. Le canevas logique de l'app est fixe (1920x1080, voir
     index.html) et redimensionne uniformement par la plateforme -- ces
     valeurs en px suivent donc deja l'ecran reel (TV, mobile, PC) sans
     media query a ecrire, exactement comme le logo de l'ecran de
     demarrage. */
  font-size: 26px;
  font-weight: 600;
  text-align: left;
  border-radius: 0;
}
.rail-icon {
  flex: 0 0 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
}
/* Le SVG (voir Icons.svg()) porte width/height="24" en HTML fixe : sans
   cette regle, agrandir .rail-icon laisse le glyphe minuscule au milieu
   d'une boite plus grande au lieu de remplir l'espace gagne. */
.rail-icon svg { display: block; width: 34px; height: 34px; }
.rail-label { white-space: nowrap; opacity: 0; }
.rail:focus-within .rail-label,
.rail.is-open .rail-label { opacity: 1; }

/* Grille des catalogues sans CSS Grid.
   Grid arrive en Chromium 57, donc absente de webOS 4.x (Chromium 53). Le repli en
   flex reproduit six colonnes ; la largeur est calculee pour retrancher les cinq
   gouttieres. Aucun effet sur les moteurs recents : la classe n'y est pas posee. */
.no-css-grid .catalog-grid {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
}
.no-css-grid .catalog-grid > * {
  width: calc((100% - 5 * 22px) / 6);
  margin: 0 22px 22px 0;
}
.no-css-grid .catalog-grid > *:nth-child(6n) { margin-right: 0; }
/* Les elements pleine largeur de la grille le restent. */
.no-css-grid .catalog-grid > .catalog-more,
.no-css-grid .catalog-grid > .empty,
.no-css-grid .catalog-grid > .loading { width: 100%; margin-right: 0; }

/* Actif = texte menthe seul, sans liseré ; focus = violet plein.
 *
 * Le liseré etait pose sur .is-active ET sur .is-focused. Retire du focus au
 * premier passage, il restait visible sur l'onglet actif meme SANS le focus
 * dessus -- l'utilisateur l'a signale comme le meme indicateur superflu. La
 * couleur du texte (var(--mint)) suffit a distinguer la section courante ;
 * elle ne rivalise pas avec le fond violet du focus comme le faisait le
 * liseré. */
.rail-item.is-active { color: var(--mint); }
.rail-item.is-focused {
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-soft) 100%);
  color: var(--accent-fg);
}

/* L'anneau de focus habituel est exterieur (inset: -4px) ; le rail rognant tout
   ce qui depasse, il n'en resterait que les bords haut et bas -- deux traits
   parasites en travers du menu, d'ou l'idee d'un liseré blanc. Sur demande de
   l'utilisateur : rien que la surbrillance violette de .is-focused, sans
   liseré ni anneau -- le rail n'a pas besoin d'un second signal, il n'affiche
   qu'un element a la fois par ligne. */
.rail-item.is-focused::after { display: none; }

/* Pastille de profil : porte l'initiale, donc pas de SVG. */
.rail-dot {
  border-radius: 50%;
  font-size: 15px;
  font-weight: 800;
  color: #10131f;
}

.home-rows { flex: 1; overflow-y: auto; padding: 0 0 var(--safe); }

/* ------------------------------------------------------------------ *
 * Rangees et tuiles
 * ------------------------------------------------------------------ */

.row { margin-bottom: 42px; }
.row-title {
  padding: 0 var(--safe) 16px;
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.row-strip {
  display: flex;
  gap: 18px;
  /* La marge verticale laisse passer l anneau de focus et le leger soulevement
     de la tuile focalisee. */
  padding: var(--ring) var(--safe) 22px;
  overflow-x: auto;
  /* Barres masquees : elles n'ont aucun sens sans pointeur. */
  scrollbar-width: none;
}
/* Vues montees a cote du rail (.shell) : celui-ci a deja recule tout son
   contenu de calc(--rail-w + --safe) -- meme raison que .shell .sub-head et
   .catalog-body plus haut/bas. Sans ce correctif, "Reprendre la lecture" et
   ses tuiles (catalog-resume, Films/Series) ainsi que les rangees de
   Recherche demarraient --safe plus a droite que le titre de la page et la
   grille, un decalage visible signale par l'utilisateur. */
.shell .row-title, .shell .row-strip { padding-left: 0; }
.row-strip::-webkit-scrollbar, .home-rows::-webkit-scrollbar,
.cat-pane::-webkit-scrollbar, .chan-pane::-webkit-scrollbar,
.catalog-grid::-webkit-scrollbar, .settings-body::-webkit-scrollbar,
.guide-scroll::-webkit-scrollbar, .ep-list::-webkit-scrollbar,
.search-results::-webkit-scrollbar, .login-form::-webkit-scrollbar { display: none; }

/* La fiche detail fait defiler la page elle-meme : sans cette regle, la barre
   native apparait sur le bord droit -- visible aussi sur la dalle. Le
   defilement au D-pad n'en a pas besoin. */
html::-webkit-scrollbar, body::-webkit-scrollbar { display: none; }

.tile {
  position: relative;
  flex: 0 0 240px;
  width: 240px;
  height: 300px;
  border-radius: var(--r-lg);
  background: var(--panel);
  overflow: hidden;
}
.tile.is-focused { transform: scale(1.07) translateY(-6px); }

/* L affiche remplit la carte : c est elle qu on vient voir. */
.tile-poster { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; }
/* Format paysage, pour « Continuer la lecture ». */
.row-strip.is-wide .tile { flex-basis: 420px; width: 420px; height: 236px; }

/* Voile degrade : sans lui, un titre blanc sur une affiche claire devient
   illisible, et on ne controle pas les affiches du fournisseur. */
.tile-scrim {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  pointer-events: none;
  background: linear-gradient(180deg,
    transparent 38%, rgba(10, 12, 24, 0.55) 68%, rgba(10, 12, 24, 0.94) 100%);
}

.tile-meta {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 16px 16px 18px;
}
/* Les tuiles ont desormais une hauteur fixe et le texte est ancre en bas :
   plus besoin de reserver deux lignes pour aligner les rangees. */
.tile-title {
  font-size: 20px;
  font-weight: 700;
  line-height: 1.3;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.tile-sub {
  margin-top: 4px;
  color: rgba(255, 255, 255, 0.72);
  font-size: 17px;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.7);
}

.tile-badge {
  position: absolute;
  top: 8px; left: 8px;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 20px;
  font-weight: 700;
  line-height: 30px;
  text-align: center;
}
.tile-fav { position: absolute; top: 8px; right: 10px; color: #fbbf24; font-size: 22px; }

.tile-progress {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 6px;
  background: rgba(255, 255, 255, 0.18);
  z-index: 2;
}
.tile-progress i { display: block; height: 100%; background: var(--mint); }

/* Affiches et logos */
.poster {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-alt);
  overflow: hidden;
}
/* opacity:0 jusqu'a has-img (posee par poster() au load de l'image, voir
   ui.js) : entre la pose de data-src et la fin du decodage, un <img> qu'on
   vient d'assigner peut afficher un rendu natif transitoire (contour clair,
   image "cassee" un instant) selon le moteur -- signale a l'usage comme un
   leger clignotement en bordure de carte pendant un defilement rapide, la ou
   le fond sombre de .poster suffisait deja a masquer l'attente si l'image
   elle-meme restait invisible jusqu'a avoir vraiment quelque chose a montrer.
   transition courte pour ne pas remplacer un clignotement par un fondu trop
   lent qui parait, lui, comme un retard de chargement. */
.poster img { width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 120ms ease-out; }
.poster.has-img img { opacity: 1; }
/* Logos de chaine : contain, pas cover. Une affiche de film/serie remplit
   sa carte par design (cover est correct) mais un logo de chaine a son
   propre cadrage -- souvent carre ou avec de la marge -- que cover
   rognait sur les bords des qu'il ne correspondait pas exactement au ratio
   84x50 de la case. Vu sur l'appareil : TF1, France 2/3/4/5 tronques. */
.chan-logo img, .np-logo img, .org-logo img, .guide-logo img { object-fit: contain; }
.avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%; height: 100%;
  color: rgba(255, 255, 255, 0.55);
  font-size: 64px;
  font-weight: 800;
}

/* ------------------------------------------------------------------ *
 * Direct
 * ------------------------------------------------------------------ */

.live { height: 1080px; display: flex; flex-direction: column; }
/* Padding gauche a 0, pas --safe : .shell (voir plus haut) a DEJA recule tout
   le contenu de calc(--rail-w + --safe) pour le rail. Rajouter --safe ici
   comptait la marge de securite deux fois sur ce bord precis -- mesure sur
   l'appareil (CDP) : 152px avant le texte des categories au lieu des 104px
   du rail lui-meme. Signale par l'utilisateur ("l'espace entre la liste de
   categorie et le menu vertical"). Les autres bords (droite, bas) touchent
   eux le vrai bord d'ecran et gardent donc --safe. */
.live-body { flex: 1; display: flex; gap: 0; min-height: 0; padding: 0 var(--safe) var(--safe) 0; }

.cat-pane {
  /* La colonne DEBORDE a gauche de --ring, et le recupere en padding.
   *
   * Il lui faut cette place a gauche, sinon `overflow-y: auto` rogne l'anneau
   * de focus et la pastille selectionnee parait coupee. Le debord annule ce
   * decalage : le padding-left de .cat-pane (--ring) compense exactement son
   * propre margin-left negatif, donc le texte des categories tombe a
   * --safe -- A CONDITION que .cat lui-meme n'ajoute aucun padding-left (voir
   * plus bas). Mesure sur l'appareil (CDP getBoundingClientRect) le
   * 2026-08-09 : avec .cat en padding-left 18px, le texte etait decale de
   * ~12px de trop par rapport au titre de section ; le retirer aligne les
   * deux exactement. */
  flex: 0 0 calc(380px + var(--ring));
  margin-left: calc(-1 * var(--ring));
  overflow-y: auto;
  padding: var(--ring) 18px var(--ring) var(--ring);
  border-right: 1px solid var(--line);
}
.cat {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 14px 18px 14px 0;
  border-radius: var(--radius);
  font-size: 20px;
  text-align: left;
}
/* Actif = menthe, focus = blanc. Deux signaux distincts, jamais confondus :
   on peut avoir le focus sur une categorie sans qu elle soit la categorie
   affichee, et l inverse.
   Le fond lui-meme est un ::before separe (voir plus bas, pres du commentaire
   sur l'anneau de focus) : il deborde de 4px a gauche pour respirer comme
   l'anneau, sans deplacer le texte -- un simple background ici, dans le
   border-box de .cat, redecalerait le texte de cette ligne par rapport aux
   autres si on tentait de le faire deborder via padding/margin. */
.cat.is-active {
  color: var(--mint);
  font-weight: 700;
}
/* Meme raison que .chan.is-focused : le fond plein derriere l'anneau se
   peignait avec un leger retard, perceptible en descendant la colonne vite.
   L'anneau seul suffit desormais a marquer le focus. */
.cat.is-focused { color: var(--fg); }
/* flex:1 + min-width:0 : sans ce dernier, un enfant flex ne se contraint
   jamais en dessous de la largeur de son contenu -- le texte deborderait du
   bouton entier au lieu de se tronquer dedans. Piege flexbox classique. */
.cat-name { flex: 1 1 auto; min-width: 0; }
.cat-count { flex: 0 0 auto; color: var(--fg-faint); font-size: 16px; }
.cat.is-focused .cat-count { color: rgba(255,255,255,0.8); }

.chan-pane {
  /* flex:1 tout court laissait cette colonne absorber TOUT l'espace restant
     apres les categories, au detriment du panneau Maintenant/Ensuite --
     signale par l'utilisateur ("le panneau est trop serre a cause de la
     liste qui s'etale trop sur la droite"). Un premier correctif la
     plafonnait a 760px et laissait .next-pane s'etirer dans l'espace libere
     -- mais l'etirement rendait CE panneau trop large a son tour (logo+texte
     colles a gauche, grand vide a droite, signale ensuite). Les DEUX
     panneaux laterales (categories, maintenant/ensuite) sont maintenant des
     largeurs FIXES, dimensionnees a leur propre contenu ; seule la liste de
     chaines, qui est la vraie liste a faire defiler, absorbe l'espace
     restant. C'est la liste qui doit s'adapter a l'espace disponible, pas
     l'inverse. */
  flex: 1 1 auto;
  overflow-y: auto;
  /* Padding droit remonte de --ring (14px) a 24px, pour egaler le padding
     gauche : mesure sur l'appareil (CDP), la tuile focalisee laissait 18,7px
     jusqu'au delimiteur des categories a gauche, mais seulement 8,7px
     jusqu'au delimiteur de Maintenant/Ensuite a droite -- asymetrie visible
     signalee par l'utilisateur, sans raison d'etre puisque les DEUX bords
     bordent un panneau fixe et ont donc les memes besoins de respiration. */
  padding: var(--ring) 24px var(--ring) 24px;
  min-width: 0;
}

/* Panneau Maintenant / Ensuite : informatif, jamais focalisable. Il occupe le
   tiers droit qui restait vide et repond a « qu'est-ce qui suit ? » sans
   zapper. Marges plutot que gap : pas de nouvelle regle flex-gap a generer. */
.next-pane {
  /* Largeur FIXE, proche de .cat-pane en face (394px) pour une colonne
     laterale visuellement coherente des deux cotes de la liste de chaines.
     420px etait trop juste une fois un vrai programme affiche : le titre
     (24px gras) et surtout la description (18px, jusqu'a 6 lignes) talonnent
     le bord au lieu de respirer -- constate sur l'appareil avec un programme
     reel, pas seulement l'etat vide. 460px donne a la description la meme
     marge que .cat-pane a son propre texte, sans revenir a un etirement
     libre qui grandissait bien au-dela de son contenu. */
  flex: 0 0 460px;
  overflow: hidden;
  padding: var(--ring) 0 var(--ring) 28px;
  border-left: 1px solid var(--line);
}
.np-chan { display: flex; align-items: center; margin-bottom: 26px; }
.np-logo { flex: 0 0 96px; width: 96px; height: 58px; border-radius: 6px; }
.np-chan-name { min-width: 0; margin-left: 18px; font-size: 24px; font-weight: 700; }
.np-chan-num { margin-top: 2px; color: var(--fg-dim); font-size: 17px; font-weight: 600; }
.np-section {
  margin: 22px 0 8px;
  color: var(--mint);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.np-title { font-size: 24px; font-weight: 700; line-height: 1.25; }
.np-times { margin-top: 6px; color: var(--fg-dim); font-size: 18px; font-weight: 600; }
.np-bar {
  height: 5px;
  margin-top: 12px;
  border-radius: 3px;
  background: var(--line);
  overflow: hidden;
}
.np-bar i { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
.np-desc {
  margin-top: 14px;
  color: var(--fg-dim);
  font-size: 18px;
  line-height: 1.5;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 6;
  -webkit-box-orient: vertical;
}
.np-next { font-size: 20px; font-weight: 600; }
.np-empty { margin-top: 18px; color: var(--fg-dim); font-size: 18px; }

/* Lignes sans carte.
 *
 * L'app de reference ne dessine aucun fond sous ses lignes : le rythme vient des
 * blancs, et la surface n'apparait qu'a l'interaction. C'est nettement plus
 * aere. Transposition sur TV : le focus doit alors porter toute la charge de
 * lisibilite, d'ou un fond ET un anneau au focus, la ou le mobile se contente
 * d'un effleurement. */
.chan {
  display: flex;
  align-items: center;
  gap: 22px;
  padding: 16px 20px;
  margin-bottom: 10px;
  border-radius: var(--r-md);
  background: transparent;
}
/* Le fond degrade au focus paraissait toujours en retard sur l'anneau blanc,
   quel que soit le reglage des transitions -- deux signaux distincts qui ne
   peuvent pas se peindre au meme instant sur ce moteur. L'anneau seul
   (box-shadow, voir [data-focusable].is-focused::after) porte desormais
   toute la charge : un seul signal, jamais desynchronise. */
.chan.is-focused {
  transform: scale(1.012);
}
.chan.is-focused .chan-prog { color: var(--fg); }
.chan.is-focused .chan-time { color: var(--fg-dim); }

/* ------------------------------------------------------------------ *
 * Composants reperes sur l'app de reference
 * ------------------------------------------------------------------ */

/* Pilule contouree : leur « Modifier l'ordre ». Action secondaire pleine
   largeur, bordure et texte violets, fond transparent. */
.btn-outline {
  display: block;
  width: 100%;
  padding: 18px 30px;
  border: 2px solid var(--accent);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--accent-soft);
  font-size: 22px;
  font-weight: 700;
  text-align: center;
}
.btn-outline.is-focused {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
  transform: scale(1.02);
}

/* Controle segmente : leur « Chaines | EPG colonnes | EPG grille ».
   Conteneur en pilule sombre, segment actif en pilule claire, texte menthe. */
.segmented {
  display: inline-flex;
  gap: 4px;
  padding: 5px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.05);
}
.segment {
  padding: 11px 30px;
  border-radius: var(--r-pill);
  color: var(--fg-dim);
  font-size: 20px;
  font-weight: 600;
  white-space: nowrap;
}
.segment.is-active { background: rgba(165, 236, 232, 0.12); color: var(--mint); }
.segment.is-focused { background: var(--accent); color: var(--accent-fg); }

/* Onglets soulignes en menthe, comme leur barre de categories. */
.uline-tabs { display: flex; gap: 40px; }
.uline-tab {
  position: relative;
  padding: 8px 2px 16px;
  color: var(--fg-dim);
  font-size: 24px;
  font-weight: 700;
}
.uline-tab.is-active { color: var(--fg); }
.uline-tab.is-active::before {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 4px;
  height: 5px;
  border-radius: 3px;
  background: var(--mint);
}
.uline-tab.is-focused { color: var(--accent-soft); }

/* Pastille de source : le point cyan devant le nom du fournisseur. */
.source-dot {
  display: inline-block;
  width: 18px; height: 18px;
  margin-right: 14px;
  border-radius: 50%;
  background: var(--cyan);
  vertical-align: middle;
}
.source-name {
  color: var(--accent-soft);
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.01em;
  vertical-align: middle;
}
.chan-num {
  flex: 0 0 62px;
  color: var(--fg-faint);
  font-size: 22px;
  font-weight: 700;
  text-align: right;
}
.chan-logo { flex: 0 0 84px; width: 84px; height: 50px; border-radius: 6px; }
.chan-info { flex: 1; min-width: 0; }

.chan-name { display: flex; align-items: center; }
.chan-label {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.005em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Titre du programme en cours, sur sa propre ligne. */
.chan-prog {
  margin-top: 5px;
  color: var(--fg-dim);
  font-size: 18px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Ligne temporelle : debut - avancement - fin. */
.chan-time {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 8px;
  color: var(--fg-faint);
  font-size: 17px;
  font-variant-numeric: tabular-nums;
}
.chan-bar {
  flex: 0 0 260px;
  height: 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.13);
  overflow: hidden;
}
.chan-bar i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--accent);
}
.chan-fav { flex: 0 0 34px; color: #fbbf24; font-size: 22px; text-align: center; }

.chan-more, .catalog-more, .guide-more {
  display: block;
  width: 100%;
  padding: 18px;
  margin: 10px 0 20px;
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--fg-dim);
  font-size: 19px;
}
.chan-more.is-focused, .catalog-more.is-focused, .guide-more.is-focused {
  background: var(--accent); color: var(--accent-fg);
}

/* Action de reordonnancement, en tete de la liste des chaines.
   Menthe, comme tout ce qui releve de l'ordre choisi par l'utilisateur, et
   affichee seulement la ou un tel ordre existe. */
.chan-action {
  display: block;
  width: 100%;
  padding: 15px 22px;
  margin: 0 0 14px;
  border-radius: var(--radius);
  background: rgba(165, 236, 232, 0.10);
  color: var(--mint);
  font-size: 19px;
  font-weight: 600;
  text-align: left;
}
.chan-action.is-focused {
  background: var(--mint);
  color: #10131f;
}

/* Saisie d'un numero de chaine */
.digit-box {
  position: absolute;
  top: 120px; right: var(--safe);
  padding: 18px 40px;
  border-radius: var(--radius);
  background: rgba(0,0,0,0.85);
  font-size: 60px;
  font-weight: 700;
  letter-spacing: 0.08em;
}

/* Separateur de section dans la colonne des categories */
.cat-sep {
  padding: 18px 18px 6px;
  color: var(--fg-faint);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.cat-action { color: var(--fg-dim); font-style: italic; }

/* ------------------------------------------------------------------ *
 * Compte
 * ------------------------------------------------------------------ */

.account-actions { display: flex; gap: 16px; margin: 8px 0 26px; }

/* Premier lancement : le formulaire de compte devient une page d'accueil.
   Colonne centree comme l'ecran d'ajout de source, marque en tete, et le
   jargon (backend, serveur) relegue tout en bas. */
.settings.is-welcome .sub-head { display: none; }
.settings.is-welcome .settings-body {
  max-width: 720px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
}
/* Centrage vertical par entretoises : si le contenu depasse (champs du harnais),
   elles s'ecrasent et le defilement normal reprend. */
.settings.is-welcome .settings-body::before,
.settings.is-welcome .settings-body::after { content: ''; flex: 1; }
.account-brand { text-align: center; margin: 26px 0 34px; }
.account-brand h1 { font-size: 58px; font-weight: 900; letter-spacing: -0.035em; }
.account-brand p { margin-top: 12px; color: var(--fg-dim); font-size: 22px; }
.settings.is-welcome .account-actions { justify-content: center; margin-top: 26px; }
.account-recover { text-align: center; }
.btn-link {
  padding: 12px 22px;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--accent-soft);
  font-size: 20px;
  font-weight: 700;
}
.btn-link.is-focused {
  background: var(--accent);
  color: var(--accent-fg);
}
.account-footnote { text-align: center; margin-top: 30px; }

/* Cle de recuperation : affichee une seule fois, destinee a etre recopiee a la
   main depuis un canape. Grande, monospace, groupee, tres contrastee. */
.recovery-key {
  margin: 22px 0 8px;
  padding: 28px 24px;
  border: 2px dashed var(--accent);
  border-radius: var(--r-md);
  background: rgba(129, 104, 254, 0.10);
  color: var(--fg);
  font-family: 'Consolas', 'Menlo', monospace;
  font-size: 34px;
  font-weight: 700;
  letter-spacing: 0.06em;
  line-height: 1.6;
  text-align: center;
  word-break: break-all;
  /* Seule zone de l'application ou la selection au pointeur a un sens. */
  user-select: text;
  -webkit-user-select: text;
}

/* Action destructive : rouge, et seulement celle-la. */
.set-row.is-danger .set-label { color: var(--danger); }

/* ------------------------------------------------------------------ *
 * Profils
 * ------------------------------------------------------------------ */

.profiles {
  height: 1080px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--safe);
}
.profiles-head { margin-bottom: 60px; text-align: center; }
.profiles-head h1 {
  margin-bottom: 18px;
  font-size: 54px;
  font-weight: 900;
  letter-spacing: -0.035em;
}
.profiles-head .hint { max-width: 900px; margin: 0 auto; font-size: 19px; }

.profiles-grid {
  display: flex;
  gap: 56px;
  flex-wrap: wrap;
  justify-content: center;
  padding: var(--ring);
}

.profile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  padding: 12px;
  border-radius: var(--r-lg);
  background: transparent;
}
.profile.is-focused { transform: scale(1.06); }

.profile-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 176px; height: 176px;
  border-radius: 50%;
  /* Texte sombre : les teintes de profil sont pastel, un libelle blanc dessus
     serait illisible. */
  color: rgba(20, 22, 40, 0.72);
  font-size: 74px;
  font-weight: 800;
}
/* Profil courant : anneau violet, comme dans l'app de reference. */
.profile.is-current .profile-avatar { box-shadow: 0 0 0 6px var(--accent); }
.profile-avatar.is-add {
  background: var(--panel-hi);
  color: var(--fg-dim);
  font-weight: 400;
}

.profile-name { color: var(--fg-dim); font-size: 24px; font-weight: 600; }
.profile.is-focused .profile-name { color: var(--fg); }
.profile.is-current .profile-name { color: var(--fg); }

/* Nuancier de couleurs de profil */
.color-grid { display: flex; gap: 18px; flex-wrap: wrap; padding: var(--ring); }
.color-swatch {
  width: 92px; height: 92px;
  border-radius: 50%;
}
.color-swatch.is-current { box-shadow: 0 0 0 5px var(--accent); }

/* En-tete de l'accueil : profil courant a droite de l'heure */
.home-head-right { display: flex; align-items: center; gap: 26px; }
.home-profile { color: var(--fg-dim); font-size: 21px; font-weight: 600; }

/* ------------------------------------------------------------------ *
 * Organisation
 * ------------------------------------------------------------------ */

.organise { height: 1080px; display: flex; flex-direction: column; }
.org-tabs { display: flex; gap: 14px; padding: 0 var(--safe) 20px; }
.org-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--ring) var(--safe) var(--safe);
  max-width: 1500px;
}
.org-body .hint { margin-bottom: 18px; }
.org-body .btn { margin-bottom: 18px; }

.org-crumb { display: flex; align-items: center; gap: 24px; margin-bottom: 22px; }
.org-crumb h2 { font-size: 30px; }

.org-row {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 14px 20px;
  margin-bottom: 8px;
  border-radius: var(--radius);
  background: var(--panel);
}
.org-row.is-focused { background: var(--panel-hi); }

/* Element saisi : il doit etre impossible de confondre « je navigue » et
   « je deplace ». D'ou le liseré lateral et le fond accentue. */
.org-row.is-grabbed {
  background: #2a2454;
  box-shadow: inset 5px 0 0 var(--accent);
}
.org-row.is-grabbed.is-focused { background: #332c66; }

/* Chaine masquee : grisee, sans disparaitre de cette liste-ci. */
.org-row.is-off { opacity: 0.45; }

.org-handle {
  flex: 0 0 30px;
  color: var(--mint);
  font-size: 24px;
  text-align: center;
}
.org-logo { flex: 0 0 76px; width: 76px; height: 46px; border-radius: 6px; }
.org-info { flex: 1; min-width: 0; }
.org-title {
  font-size: 21px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.org-sub { margin-top: 3px; color: var(--fg-dim); font-size: 17px; }
.org-badge {
  flex: 0 0 auto;
  padding: 4px 14px;
  border-radius: 999px;
  background: var(--panel-hi);
  color: var(--fg-dim);
  font-size: 17px;
  font-weight: 700;
}

/* Selecteur de chaines (modale) */
.picker { max-height: 520px; overflow-y: auto; padding: var(--ring) var(--ring) var(--ring) 0; }
.picker::-webkit-scrollbar { display: none; }
.picker-row {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: 12px 16px;
  margin-bottom: 4px;
  border-radius: 8px;
  background: var(--panel);
  text-align: left;
}
.picker-row.is-focused { background: var(--accent); color: var(--accent-fg); }
.picker-row.is-on { background: rgba(165, 236, 232, 0.10); }
.picker-row.is-on.is-focused { background: var(--accent); }
.picker-mark { flex: 0 0 28px; color: var(--mint); font-size: 20px; font-weight: 700; }
.picker-row.is-focused .picker-mark { color: var(--accent-fg); }
.picker-num { flex: 0 0 60px; color: var(--fg-faint); font-size: 17px; text-align: right; }
.picker-name {
  flex: 1;
  font-size: 19px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.backup-box {
  height: 320px;
  font-family: 'Consolas', monospace;
  font-size: 14px;
  resize: none;
}

/* ------------------------------------------------------------------ *
 * Guide
 * ------------------------------------------------------------------ */

.guide { height: 1080px; display: flex; flex-direction: column; }
.guide-scroll {
  flex: 1;
  overflow: auto;
  padding: 0 var(--safe) var(--safe);
  min-height: 0;
}

.guide-times { display: flex; position: sticky; top: 0; z-index: 3; background: var(--bg); }
.guide-corner {
  flex: 0 0 300px;
  padding: 10px 14px;
  color: var(--fg-dim);
  font-size: 17px;
  font-weight: 700;
  text-transform: uppercase;
}
.guide-times-strip { display: flex; }
.guide-tick {
  flex: 0 0 auto;
  padding: 10px 0 10px 10px;
  border-left: 1px solid var(--line);
  color: var(--fg-dim);
  font-size: 17px;
}

.guide-row { display: flex; margin-bottom: 6px; }

.guide-chan {
  display: flex;
  flex: 0 0 300px;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border-radius: 8px;
  background: var(--panel);
  text-align: left;
}
.guide-chan.is-focused { background: var(--accent); color: var(--accent-fg); }
.guide-logo { flex: 0 0 56px; width: 56px; height: 34px; border-radius: 4px; }
.guide-chan-name {
  font-size: 18px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.guide-strip { position: relative; display: flex; }

.guide-prog {
  flex: 0 0 auto;
  overflow: hidden;
  padding: 8px 12px;
  margin-right: 3px;
  border-radius: 8px;
  background: var(--panel);
  text-align: left;
}
.guide-prog.is-live { background: #2b3160; }

/* Passé : atténué, pour que le présent et le futur ressortent. */
.guide-prog.is-past { opacity: 0.45; }

/* Rejouable : liseré qui distingue « passé consultable » de « passé perdu ». */
.guide-prog.is-replayable {
  opacity: 1;
  border-left: 3px solid #eab308;
}
.guide-replay { color: #eab308; font-weight: 700; }
.guide-prog.is-focused .guide-replay { color: var(--accent-fg); }

.guide-prog.is-focused { background: var(--accent); color: var(--accent-fg); z-index: 6; opacity: 1; }
.guide-prog-title {
  display: block;
  font-size: 18px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Sans nowrap, un bloc etroit (programme tronque par le bord de la fenetre)
   renvoie l'horaire a la ligne et fait grandir toute la rangee. */
.guide-prog-time {
  display: block;
  color: var(--fg-dim);
  font-size: 15px;
  white-space: nowrap;
  overflow: hidden;
}
.guide-prog.is-focused .guide-prog-time { color: rgba(255,255,255,0.85); }

.guide-gap {
  flex: 0 0 auto;
  padding: 8px 12px;
  margin-right: 3px;
  border-radius: 8px;
  background: rgba(255,255,255,0.04);
  color: var(--fg-faint);
  font-size: 15px;
  overflow: hidden;
  white-space: nowrap;
}

/* Repere de l'heure courante */
.guide-now {
  position: absolute;
  top: -4px; bottom: -4px;
  width: 3px;
  background: var(--mint);
  pointer-events: none;
  z-index: 4;
}

.prog-when { color: var(--fg-dim); font-size: 18px; }

/* ------------------------------------------------------------------ *
 * Catalogue (films / series)
 * ------------------------------------------------------------------ */

.catalog { height: 1080px; display: flex; flex-direction: column; }

/* ------------------------------------------------------------------ *
 * Bandeau d'avertissement
 *
 * Sert au cas ou une source ne peut plus lire ses identifiants. Il doit se
 * remarquer sans hurler : le probleme est reparable en trente secondes, il ne
 * merite pas un ecran d'erreur pleine page.
 * ------------------------------------------------------------------ */

.banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 32px;
  margin: 0 var(--safe) 22px;
  padding: 22px 28px;
  border-radius: var(--r-md);
  background: rgba(234, 179, 8, 0.10);
  border-left: 5px solid var(--warn);
}
.banner strong { display: block; margin-bottom: 6px; font-size: 22px; color: var(--warn); }
.banner p { color: var(--fg-dim); font-size: 18px; line-height: 1.45; max-width: 1000px; }
.banner .btn { flex: 0 0 auto; }

/* ------------------------------------------------------------------ *
 * Sous-titres
 *
 * ::cue n'accepte qu'une liste courte de proprietes -- ni marge, ni position,
 * ni bordure. La position se regle donc dans le VTT lui-meme (voir
 * Subtitles.withCueLine) ; il ne reste ici que la lisibilite.
 *
 * A trois metres, un fond translucide vaut mieux qu'un simple contour : sur une
 * image claire, du blanc sur du blanc disparait quel que soit le liseré.
 * ------------------------------------------------------------------ */

video::cue {
  font-family: 'Golos Text', system-ui, sans-serif;
  /* 24px de base (comme .pl-subtitle, le bandeau EPG) : 30px paraissait trop
     gros a l'usage sur un grand ecran de TV. --subtitle-size est pilotee par
     Subtitles.setScale() (bouton +/- du menu), voir player.js. */
  font-size: var(--subtitle-size, 24px);
  font-weight: 600;
  line-height: 1.3;
  color: #ffffff;
  background: rgba(8, 10, 20, 0.72);
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.9);
}

/* Bandeau des reprises : hauteur dictee par son contenu, et absent du flux
   quand il est vide -- une rangee vide coute plus cher qu'aucune rangee. */
.catalog-resume { flex: 0 0 auto; }
.catalog-resume:empty { display: none; }
.catalog-resume .row { margin-bottom: 18px; }
.catalog-resume .tile { height: 210px; }

/* Meme raison que .live-body : .shell a deja recule le contenu pour le rail,
   pas besoin de re-appliquer --safe a gauche ici. */
.catalog-body { flex: 1; display: flex; min-height: 0; padding: 0 var(--safe) var(--safe) 0; }

.catalog-grid {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 22px;
  align-content: start;
  overflow-y: auto;
  padding: var(--ring) var(--ring) var(--ring) 24px;
  min-width: 0;
}
.catalog-grid .tile { flex: none; width: auto; height: 380px; }
.catalog-grid .catalog-more, .catalog-grid .empty, .catalog-grid .loading { grid-column: 1 / -1; }

/* ------------------------------------------------------------------ *
 * Fiche detaillee
 * ------------------------------------------------------------------ */

.detail { position: relative; height: 1080px; overflow-y: auto; padding: var(--safe); }
/* Image de couverture (backdrop_path, format large TMDB) en arriere-plan
   plein cadre, comme Netflix/Disney+/Plex/Jellyfin -- verifie en pratique
   avant d'implementer (recherche web) : la 3e colonne a cote du texte n'est
   plus le motif dominant, le hero backdrop derriere un voile degrade l'est.
   position:fixed (pas absolute) : .detail defile (overflow-y:auto) mais le
   backdrop doit rester immobile derriere le contenu qui defile, pas scroller
   avec lui. z-index:0 le place sous .detail-head (position:relative,
   z-index implicite superieur car apres dans l'ordre du document et sans
   z-index neutralisant -- confirmer visuellement plutot que supposer). */
.detail-backdrop {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 400ms ease-out;
}
.detail-backdrop.is-loaded { opacity: 1; }
.detail-backdrop img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* Le sujet principal d'un backdrop TMDB est souvent cadre plus haut que le
     centre geometrique (visages, scene) : object-position remonte le point
     de focale au lieu de risquer de couper les tetes sur un ecran large. */
  object-position: center 22%;
}
/* Voile en trois zones : sombre a gauche (sous le texte, qui doit rester
   lisible), degrade au centre, quasi transparent a droite (l'image reste
   visible) -- puis un fondu vers le bas pour que le contenu qui defile sous
   le pli ne se batte jamais avec l'image. Memes tons que --bg pour que la
   transition avec le reste de l'interface soit invisible.
   Deux essais precedents (degrade horizontal seul, puis + un radial ancre au
   coin) laissaient encore le texte illisible sur un backdrop clair (ciel,
   scene en plein jour) -- constate sur l'appareil a chaque fois : le bloc de
   texte s'etend sur ~1500px de large (titre, puces, synopsis), largement
   au-dela de ce qu'un degrade centre au coin haut-gauche couvre reellement.
   Un bloc quasi opaque, dimensionne sur la largeur ET la hauteur reelles du
   texte (pas juste ancre a son coin), regle le probleme a la racine plutot
   que d'ajouter un troisieme calque en esperant compenser. */
.detail-backdrop::after {
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  background:
    linear-gradient(100deg, rgba(15, 17, 32, 0.98) 0%, rgba(15, 17, 32, 0.98) 46%,
      rgba(15, 17, 32, 0.55) 62%, transparent 80%),
    linear-gradient(180deg, rgba(15, 17, 32, 0.98) 0%, rgba(15, 17, 32, 0.7) 32%, transparent 42%),
    linear-gradient(180deg, transparent 55%, rgba(18, 21, 40, 0.85) 88%, var(--bg) 100%);
}

.detail-head { position: relative; z-index: 1; display: flex; gap: 40px; margin-bottom: 34px; }
.detail-poster { flex: 0 0 320px; width: 320px; height: 460px; border-radius: var(--radius); }
.detail-meta { flex: 1; min-width: 0; }
.detail-meta h1 { margin-bottom: 16px; font-size: 50px; font-weight: 800; letter-spacing: -0.03em; }
.detail-chips { margin-bottom: 18px; }
.detail-plot { max-width: 1100px; color: var(--fg-dim); font-size: 21px; line-height: 1.6; }
.detail-cast { color: var(--fg-faint); font-size: 18px; }
.detail-actions { display: flex; gap: 16px; margin-top: 26px; flex-wrap: wrap; }

.season-bar { position: relative; z-index: 1; display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap; }
.season {
  padding: 12px 26px;
  border-radius: 999px;
  background: var(--panel);
  font-size: 20px;
  font-weight: 600;
}
.season.is-active { background: var(--panel-hi); }
.season.is-focused { background: var(--accent); color: var(--accent-fg); }

.ep-list { position: relative; z-index: 1; max-width: 1500px; }
.ep {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 16px 20px;
  margin-bottom: 8px;
  border-radius: var(--radius);
  background: var(--panel);
}
.ep.is-focused { background: var(--panel-hi); transform: scale(1.01); }
.ep-num {
  flex: 0 0 56px;
  color: var(--fg-faint);
  font-size: 26px;
  font-weight: 700;
  text-align: center;
}
.ep-info { flex: 1; min-width: 0; }
.ep-name { font-size: 21px; font-weight: 600; }
.ep-plot {
  margin-top: 4px;
  color: var(--fg-dim);
  font-size: 17px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ep-dur { flex: 0 0 auto; color: var(--fg-dim); font-size: 18px; }
.ep-seen { flex: 0 0 auto; color: var(--mint); font-size: 17px; font-weight: 700; }

/* ------------------------------------------------------------------ *
 * Recherche
 * ------------------------------------------------------------------ */

.search { height: 1080px; display: flex; flex-direction: column; }
/* Padding gauche a 0 : meme redondance que .live-body/.catalog-body -- .shell
   a deja recule le contenu pour le rail. */
.search-bar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 var(--safe) 24px 0;
  max-width: 1000px;
}
.search-bar .field { flex: 1; }
.search-results { flex: 1; overflow-y: auto; padding-bottom: var(--safe); }

/* Bouton micro : rond, meme gabarit que les autres actions rondes de l'app
   (voir .pl-t-icon) plutot qu'un nouveau format. Etat is-listening pulse en
   couleur d'accent pour signaler que l'ecoute est active -- sans texte,
   l'icone seule suffit ici, contrairement aux boutons du lecteur qui gardent
   toujours un libelle (distance/contexte differents). */
.btn-mic {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  color: var(--fg-dim);
  background: var(--panel);
  border: 1px solid var(--line);
}
.btn-mic svg { width: 26px; height: 26px; }
.btn-mic.is-focused { color: var(--accent-fg); background: var(--accent); border-color: var(--accent); }
.btn-mic.is-listening {
  color: var(--accent-fg);
  background: var(--danger);
  border-color: var(--danger);
  animation: mic-pulse 1.2s ease-in-out infinite;
}
/* Variante mise en avant (comme TiviMate) : point d'entree principal de
   l'ecran, pas une icone secondaire a cote du champ -- nettement plus
   grande, en tete de .search-bar (voir search.js). */
.btn-mic-primary { width: 96px; height: 96px; }
.btn-mic-primary svg { width: 44px; height: 44px; }
@keyframes mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 64, 86, 0.5); }
  50% { box-shadow: 0 0 0 10px rgba(239, 64, 86, 0); }
}

/* ------------------------------------------------------------------ *
 * Connexion
 * ------------------------------------------------------------------ */

.login { height: 1080px; display: flex; flex-direction: column; align-items: center; padding-top: 90px; }
.login-brand { margin-bottom: 40px; text-align: center; }
.login-brand h1 { font-size: 58px; font-weight: 900; letter-spacing: -0.035em; }
.login-brand p { margin-top: 10px; color: var(--fg-dim); font-size: 22px; }

.login-tabs { display: flex; gap: 14px; margin-bottom: 32px; }
.tab {
  padding: 13px 32px;
  border-radius: 999px;
  background: var(--panel);
  font-size: 20px;
  font-weight: 600;
}
.tab.is-active { background: var(--panel-hi); }
.tab.is-focused { background: var(--accent); color: var(--accent-fg); }

.login-form { width: 760px; max-height: 620px; overflow-y: auto; padding: var(--ring) var(--ring); }
.login-actions { display: flex; gap: 16px; margin-top: 30px; }
.login-account-link { width: 760px; margin-bottom: 30px; }

/* ------------------------------------------------------------------ *
 * Reglages
 * ------------------------------------------------------------------ */

.settings { height: 1080px; display: flex; flex-direction: column; }
/* Le contour de focus deborde de 4px a gauche de chaque .set-row (voir
   [data-focusable].is-focused::after). Avec un padding-left a 0 pur, ce
   debord tombait dans la bande occupee par le rail (position:absolute,
   z-index superieur), qui le recouvrait -- signale a l'usage : contour
   coupe sur les premiers boutons de Reglages/Compte. Meme schema que
   .cat-pane : margin-left negatif de --ring, compense par un padding-left
   egal, pour donner au focus la place de deborder sans deplacer le texte. */
.settings-body {
  flex: 1;
  overflow-y: auto;
  margin-left: calc(-1 * var(--ring));
  padding: var(--ring) var(--safe) var(--safe) var(--ring);
  max-width: calc(1300px + var(--ring));
}
.settings-section {
  margin: 34px 0 14px;
  color: var(--mint);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.settings-section:first-child { margin-top: 0; }

.set-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  width: 100%;
  padding: 18px 24px;
  margin-bottom: 8px;
  border-radius: var(--radius);
  background: var(--panel);
  text-align: left;
}
.set-row.is-focused { background: var(--panel-hi); }
.set-label { flex: 1 1 auto; font-size: 21px; }
.set-value { flex: 0 0 auto; color: var(--fg-dim); font-size: 19px; font-weight: 600; }
.set-row.is-focused .set-value { color: var(--fg); }

/* Le chevron marque qu'OK agit sur la ligne (changer la valeur, ouvrir). */
.set-chevron {
  flex: 0 0 auto;
  color: var(--fg-dim);
  font-size: 26px;
  font-weight: 700;
  line-height: 1;
  opacity: 0.55;
}
.set-row.is-focused .set-chevron { color: var(--fg); opacity: 1; }

/* ------------------------------------------------------------------ *
 * Lecteur
 * ------------------------------------------------------------------ */

.player { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 100; background: #000; }
.pl-video { width: 100%; height: 100%; object-fit: contain; background: #000; }

/* Barre d'actions sous la timeline, modele YouTube : Recommencer SEUL a
   gauche, le cluster Precedent/Lecture-Pause/Suivant VRAIMENT au centre de
   la rangee, le groupe contextuel (Sous-titres/Audio/Guide/Favoris/Retour
   direct) aligne a droite. Grille a 3 colonnes (gauche / centre / droite),
   chaque colonne 1fr de largeur EGALE : avec deux colonnes 1fr de part et
   d'autre de la colonne centrale, le contenu de la colonne centrale reste
   au milieu EXACT de la rangee quel que soit ce qui occupe les cotes --
   contrairement a un flex + space-between (le premier groupe se retrouve
   pousse a gauche, pas au centre) ou a une seule colonne "auto" centrale
   flanquee de deux 1fr de largeurs DIFFERENTES (le centre glisse vers le
   cote le plus charge). Recommencer vit dans SA PROPRE colonne (justify-
   self: start) : dans le meme groupe flex que le cluster, justify-self ne
   peut centrer que l'ensemble, pas isoler Recommencer a gauche -- constate
   a l'usage, Recommencer restait colle au cluster au lieu d'etre decale.
   `.pl-ui` est en pointer-events: none pour laisser passer la souris ; les
   boutons, eux, doivent rester cliquables au pointeur de la Magic Remote. */
.pl-actions {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  align-items: center;
  margin-top: 20px;
  pointer-events: auto;
}
.pl-restart-group { grid-column: 1; justify-self: start; }
.pl-transport-group { grid-column: 2; justify-self: center; }
.pl-context-group { grid-column: 3; justify-self: end; }
.pl-restart-group, .pl-transport-group, .pl-context-group {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Icone SEULE, pas de label a cote : sur une TV pilotee a la telecommande,
   le label texte alourdissait la rangee sans rien apporter que l'icone ne
   dise deja (Play/Pause, sous-titres, etc. sont des symboles universels) --
   voir transportButton() dans player.js, le label devient un aria-label,
   jamais affiche. Au focus, un rond plein de la couleur du theme entoure
   l'icone : SEUL signal de focus, pas de cadre sur tout le bouton. */
.pl-transport-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.85);
}
/* vmin plutot qu'un px fixe, meme raisonnement que .boot-logo (voir plus
   bas dans ce fichier) : sur le canevas logique 1920x1080 de webOS/TV,
   4.1vmin est visuellement identique a 44px, mais la MEME regle reste juste
   sur une fenetre de navigateur etroite ou large, ou un ecran TV plus petit
   ou plus grand -- vmin suit toujours la plus petite dimension reelle,
   contrairement a un px fixe identique pour toutes les tailles d'ecran.
   width+max-width (PAS la fonction CSS max(), non fiable sur Chromium 79 --
   voir CLAUDE.md) borne les extremes : un tres grand ecran ne doit pas
   produire des boutons demesures, une fenetre minuscule ne doit pas les
   faire disparaitre. */
.pl-t-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 5.2vmin; height: 5.2vmin;
  max-width: 68px; max-height: 68px;
  min-width: 30px; min-height: 30px;
  border-radius: 50%;
  flex: 0 0 auto;
}
.pl-t-icon svg { width: 55%; height: 55%; }
/* Precedent/Lecture-Pause/Suivant : coeur du transport, nettement plus gros
   que le reste (Recommencer, Sous-titres, Audio...) -- demande a l'usage,
   comme YouTube ou ces trois-la dominent visuellement la rangee. Plusieurs
   corrections successives (44->58->80->124px, la derniere jugee trop
   grosse) -- valeur actuelle a mi-chemin entre les deux derniers passages. */
.pl-t-icon--major {
  width: 7.2vmin; height: 7.2vmin;
  max-width: 96px; max-height: 96px;
  min-width: 42px; min-height: 42px;
}
/* "Precedent" reutilise le trace de skipForward (voir TRANSPORT_ICON_KEY
   dans player.js) retourne a l'horizontale : aucun second trace SVG a
   maintenir pour un simple miroir. */
.pl-t-icon--flip { transform: scaleX(-1); }
.pl-transport-btn.is-focused { color: var(--accent); }
.pl-transport-btn.is-focused .pl-t-icon {
  background: var(--accent);
  color: var(--accent-fg);
  box-shadow: 0 0 0 3px var(--focus), 0 10px 26px rgba(0, 0, 0, 0.5);
}
.pl-transport-btn.is-disabled { color: rgba(255, 255, 255, 0.3); }

/* Navigateur (PC/mobile) reste regarde de pres, au clavier/tactile/souris :
   memes proportions vmin, bornes max/min plus basses -- responsive a la
   fois a l'ecran (TV grande/petite) ET a la fenetre (desktop large, mobile
   etroit), au lieu d'un simple choix binaire TV/navigateur en px fixes.
   `is-tv-platform` est posee une seule fois au demarrage (js/app.js), pas
   par vue -- voir sa definition pour la detection exacte. */
:root:not(.is-tv-platform) .pl-t-icon {
  max-width: 34px; max-height: 34px;
  min-width: 22px; min-height: 22px;
}
:root:not(.is-tv-platform) .pl-t-icon--major {
  max-width: 48px; max-height: 48px;
  min-width: 30px; min-height: 30px;
}
:root:not(.is-tv-platform) .pl-restart-group,
:root:not(.is-tv-platform) .pl-transport-group,
:root:not(.is-tv-platform) .pl-context-group { gap: 4px; }

/* Degrades hauts et bas : le texte doit rester lisible sur une image claire. */
.pl-shade {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms ease;
  background:
    linear-gradient(to bottom, rgba(0,0,0,0.85) 0%, transparent 22%),
    linear-gradient(to top, rgba(0,0,0,0.9) 0%, transparent 30%);
}
.player.ui-visible .pl-shade { opacity: 1; }

.pl-ui {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: var(--safe);
  pointer-events: none;
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 200ms ease, transform 200ms ease;
}
.player.ui-visible .pl-ui { opacity: 1; transform: translateY(0); }

.pl-title { font-size: 46px; font-weight: 700; text-shadow: 0 2px 12px rgba(0,0,0,0.8); }
.pl-subtitle { margin-top: 8px; color: rgba(255,255,255,0.85); font-size: 24px; }

.pl-head-top { display: flex; align-items: center; gap: 20px; }

/* Badge DIRECT / CATCH-UP : l'utilisateur doit savoir en un coup d'oeil s'il
   regarde le flux vivant ou une archive. */
.pl-badge {
  flex: 0 0 auto;
  padding: 6px 18px;
  border-radius: 999px;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.08em;
}
.pl-badge.is-live { background: var(--live); color: #04210f; }
.pl-badge.is-catchup { background: #eab308; color: #2a1f00; }

.pl-foot { display: flex; flex-direction: column; gap: 18px; }
.pl-progress { display: flex; align-items: center; gap: 20px; }

.pl-t {
  flex: 0 0 auto;
  min-width: 74px;
  font-size: 21px;
  font-variant-numeric: tabular-nums;
}
.pl-t:last-of-type { text-align: right; }

.pl-bar {
  position: relative;
  flex: 1;
  height: 7px;
  border-radius: 4px;
  background: rgba(255,255,255,0.25);
}
.pl-bar i { display: block; height: 100%; border-radius: 4px; background: var(--accent); }

/* Curseur : sur une timeline d'heure murale, la position compte plus que le
   remplissage — il faut un repère net. */
.pl-cursor {
  position: absolute;
  top: 50%;
  width: 20px; height: 20px;
  margin: -10px 0 0 -10px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 4px rgba(0,0,0,0.45);
}

/* ------------------------------------------------------------------ *
 * Curseur de reperage
 *
 * Deuxieme curseur, distinct de celui de lecture : violet contre blanc, et une
 * etiquette qui nomme l'heure et le programme vises. Toute la valeur du mode est
 * la -- on voit ou l'on va AVANT de valider, au lieu de tatonner par sauts.
 *
 * La barre s'epaissit pendant le reperage : le mode doit se voir de trois metres,
 * sinon on ne sait pas si les fleches deplacent le curseur ou zappent.
 * ------------------------------------------------------------------ */

.pl-scrub {
  position: absolute;
  top: 50%;
  width: 26px; height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 4px rgba(8, 10, 20, 0.6), 0 0 24px rgba(129, 104, 254, 0.7);
  transition: left var(--t-fast) linear;
}

.pl-scrub-label {
  position: absolute;
  bottom: 34px;
  left: 50%;
  transform: translateX(-50%);
  padding: 8px 18px;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 19px;
  font-weight: 600;
  white-space: nowrap;
}

/* Le recentrage aux extremites est calcule en JS (renderScrub) : un selecteur
   d'attribut sur la valeur inline attraperait aussi bien « left: 1% » que
   « left: 15% », et deplacerait l'etiquette au mauvais moment. */

/* Message d'absence d'image. Centre, sobre, et affiche UNIQUEMENT quand le flux
   n'a pas de piste video : sur un televiseur qui decode, on ne le voit jamais. */
.pl-nopicture {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  max-width: 820px;
  padding: 30px 40px;
  border-radius: var(--r-lg);
  background: rgba(28, 31, 50, 0.9);
  border-left: 5px solid var(--warn);
  text-align: center;
  z-index: 3;
}
.pl-nopicture strong { display: block; margin-bottom: 10px; font-size: 26px; color: var(--warn); }
.pl-nopicture p { margin: 0; color: var(--fg-dim); font-size: 19px; line-height: 1.5; }

.row-timeline .pl-bar { height: 12px; }
.row-timeline .pl-cursor { opacity: 0.45; }

.pl-delay {
  flex: 0 0 auto;
  padding: 5px 14px;
  border-radius: 6px;
  background: rgba(234,179,8,0.2);
  color: #fde047;
  font-size: 19px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.pl-hints { display: flex; gap: 32px; color: rgba(255,255,255,0.7); font-size: 18px; }

.pl-status {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  padding: 20px 40px;
  border-radius: var(--radius);
  background: rgba(0,0,0,0.8);
  font-size: 26px;
}

.track-menu h3 { margin: 18px 0 10px; color: var(--fg-dim); font-size: 18px; text-transform: uppercase; }
.track-menu h3:first-child { margin-top: 0; }
.row-btn {
  display: block;
  width: 100%;
  padding: 14px 20px;
  margin-bottom: 6px;
  border-radius: 8px;
  background: var(--panel);
  font-size: 20px;
  text-align: left;
}
.row-btn.is-active { color: var(--mint); font-weight: 700; }
/* Programme hors fenetre d'archive dans la liste "Revoir sur ..." (voir
   openRecentProgrammes, live.js) : visible pour situer le programme dans le
   temps, mais pas actionnable -- ni focalisable (pas de data-focusable),
   ni surligne comme les lignes valides. */
.row-btn.is-disabled { opacity: 0.4; }
/* Pas de fond violet plein au focus : l'anneau global (voir
   [data-focusable].is-focused::after plus haut) suffit deja, un
   remplissage en plus faisait double emploi -- signale a l'usage. Meme
   traitement "anneau seul" que .set-row/.chan/.cat ci-dessus. */

/* Menu Sous-titres, disposition a deux colonnes (repere Stremio, demande a
   l'usage) : la colonne de gauche liste TOUTES les langues disponibles --
   integrees et externes melangees, une ligne par langue -- la colonne de
   droite detaille la langue ACTIVE (etiquette de source, reglages). Sur un
   ecran de television au clavier/telecommande (pas de survol), "active" est
   la seule notion fiable sans plomberie de focus supplementaire dans Nav. */
.subtitle-track-layout {
  display: flex;
  gap: 24px;
  align-items: flex-start;
}
.subtitle-track-langs {
  flex: 1 1 42%;
  min-width: 0;
}
.subtitle-track-detail {
  flex: 1 1 58%;
  min-width: 0;
  padding: 18px 22px;
  border-radius: 10px;
  background: var(--panel);
}
.subtitle-track-detail h3 { margin-top: 0; }
.subtitle-track-tag {
  display: inline-block;
  margin-bottom: 14px;
  padding: 4px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.1);
  color: var(--fg-dim);
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Reglage de taille des sous-titres : deux boutons ronds encadrant la valeur
   courante, meme gabarit que les icones du transport (.pl-t-icon) pour rester
   dans la meme famille visuelle malgre le changement de contexte (modale). */
.subtitle-size-row { margin-bottom: 18px; }
.subtitle-size-controls {
  display: flex;
  align-items: center;
  gap: 20px;
}
.icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--panel);
  color: rgba(255, 255, 255, 0.85);
  flex: 0 0 auto;
}
.icon-btn.is-focused { background: var(--accent); color: var(--accent-fg); }
.subtitle-size-label {
  min-width: 64px;
  text-align: center;
  font-size: 20px;
  font-weight: 600;
}

/* ------------------------------------------------------------------ *
 * Toast, modale, spinner
 * ------------------------------------------------------------------ */

.toast {
  position: fixed;
  bottom: var(--safe); left: 50%;
  transform: translateX(-50%);
  z-index: 300;
  max-width: 1200px;
  padding: 18px 34px;
  border-radius: var(--radius);
  background: rgba(20,25,36,0.96);
  box-shadow: 0 8px 30px rgba(0,0,0,0.5);
  font-size: 21px;
}

.modal {
  position: fixed; top: 0; right: 0; bottom: 0; left: 0;
  z-index: 400;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.72);
}
.modal-box {
  width: 900px;
  max-height: 780px;
  overflow-y: auto;
  padding: 44px;
  border-radius: 16px;
  background: var(--bg-alt);
  box-shadow: 0 20px 60px rgba(0,0,0,0.6);
}
.modal-box h2 { margin-bottom: 20px; font-size: 34px; }
.modal-box p { color: var(--fg-dim); font-size: 21px; line-height: 1.6; white-space: pre-line; }
.modal-actions { display: flex; gap: 16px; margin-top: 30px; }

/* Barre de decalage des sous-titres (voir openDelayBar() dans player.js) :
   ancree en HAUT expres. Le sous-titre est TOUJOURS en bas de l'image --
   une barre en haut ne le recouvre jamais, quel que soit le decalage en
   cours de reglage. Repere direct sur Stremio (capture fournie a l'usage) :
   petite pastille flottante, pas une modale plein ecran. */
.pl-delaybar {
  position: absolute;
  top: var(--safe);
  left: 50%;
  transform: translateX(-50%);
  z-index: 250;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 18px;
  border-radius: 999px;
  background: rgba(15,18,28,0.92);
  box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.pl-delaybar-value {
  min-width: 76px;
  text-align: center;
  font-size: 20px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.pl-delaybar .icon-btn { width: 40px; height: 40px; }
.pl-delaybar-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: transparent;
  color: var(--fg-dim);
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
}
.pl-delaybar-close.is-focused,
.pl-delaybar .icon-btn.is-focused { background: var(--accent); color: var(--accent-fg); }

.spinner {
  position: fixed; top: 0; right: 0; bottom: 0; left: 0;
  z-index: 350;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 26px;
  background: rgba(11,14,20,0.82);
}
.spinner-ring {
  width: 74px; height: 74px;
  border: 6px solid rgba(255,255,255,0.18);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 900ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.spinner-label { color: var(--fg-dim); font-size: 22px; }

/* Ecran de demarrage : voir index.html pour pourquoi il vit hors de #app.
   Fond de marque uni (#0b0e14, deja la couleur de splash.png/l'iconColor
   webOS) -- le standard des apps TV (Netflix, Disney+, lanceurs Android TV) :
   un logo centre, rien d'autre, jusqu'a ce que la vraie interface soit prete.
   z-index au-dessus de tout, y compris le spinner generique (350). */
.boot {
  position: fixed; top: 0; right: 0; bottom: 0; left: 0;
  z-index: 500;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 40px;
  background: #0b0e14;
  transition: opacity 300ms ease;
}
.boot.is-hidden { opacity: 0; pointer-events: none; }
/* vmin plutot qu'un px fixe : sur le canevas logique 1920x1080 de webOS/TV
   c'est visuellement identique a 140px, mais la MEME regle reste juste en
   navigateur reel (fenetre etroite en portrait mobile, large en desktop) --
   vmin suit toujours la plus petite dimension de la fenetre. Largeur
   contrainte seule, hauteur en auto : le logo n'est pas carre (1536x1024),
   fixer les deux l'aurait deforme. */
.boot-logo { width: 13vmin; height: auto; max-width: 220px; }
/* Sur une vraie TV, 140px se perd dans un ecran de 1920x1080 vu a distance
   de canape -- confirme par capture reelle sur l'appareil : un point minuscule
   perdu au centre d'un vide noir, pas un ecran de marque. is-tv-platform est
   posee au tout premier paint (voir index.html, duplicat de la detection de
   app.js) pour que CE choix s'applique des le boot, pas seulement une fois
   l'app demarree. */
.is-tv-platform .boot-logo { width: 22vmin; max-width: 340px; }
/* Legere pulsation : un logo parfaitement immobile plusieurs secondes lit
   comme un gel plutot que comme un chargement en cours. */
.boot-logo { animation: boot-pulse 2400ms ease-in-out infinite; }
@keyframes boot-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.72; }
}
/* Uniquement pose au tout premier lancement (aucun cache local) : voir
   Boot.progress() dans app.js. Absent sinon -- pas de barre qui n'avancerait
   pas vraiment, ce serait pire que rien. */
.boot-progress {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: 420px;
}
.boot-bar {
  width: 100%;
  height: 6px;
  border-radius: 3px;
  background: rgba(255,255,255,0.14);
  overflow: hidden;
}
.boot-bar i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--accent);
  width: 0%;
  transition: width 200ms ease;
}
.boot-label { color: var(--fg-dim); font-size: 18px; }

/* === DEBUT PARADE FLEX-GAP (genere) === */
/*
 * NE PAS MODIFIER A LA MAIN : produit par tools/gen-flexgap-fallback.js.
 *
 * Retablit l'espacement des conteneurs flex quand `gap` ne s'y applique pas,
 * c'est-a-dire en dessous de Chromium 84 -- donc sur webOS 6.0, qui est en 79.
 * La classe no-flex-gap est posee par U.flexGapSupported() au demarrage.
 *
 * 43 regles generees.
 */
.no-flex-gap .sk-row > * + * { margin-left: 18px; }
.no-flex-gap .sub-hints > * + * { margin-left: 28px; }
.no-flex-gap .back-hint > * + * { margin-left: 10px; }
.no-flex-gap .rail > * + * { margin-top: 4px; }
.no-flex-gap .rail-foot > * + * { margin-top: 4px; }
.no-flex-gap .rail-item > * + * { margin-left: 20px; }
.no-flex-gap .row-strip > * + * { margin-left: 18px; }
.no-flex-gap .cat > * + * { margin-left: 10px; }
.no-flex-gap .chan > * + * { margin-left: 22px; }
.no-flex-gap .segmented > * + * { margin-left: 4px; }
.no-flex-gap .uline-tabs > * + * { margin-left: 40px; }
.no-flex-gap .chan-time > * + * { margin-left: 16px; }
.no-flex-gap .account-actions > * + * { margin-left: 16px; }
.no-flex-gap .profiles-grid > * { margin-right: 56px; margin-bottom: 56px; }
.no-flex-gap .profile > * + * { margin-top: 20px; }
.no-flex-gap .color-grid > * { margin-right: 18px; margin-bottom: 18px; }
.no-flex-gap .home-head-right > * + * { margin-left: 26px; }
.no-flex-gap .org-tabs > * + * { margin-left: 14px; }
.no-flex-gap .org-crumb > * + * { margin-left: 24px; }
.no-flex-gap .org-row > * + * { margin-left: 18px; }
.no-flex-gap .picker-row > * + * { margin-left: 16px; }
.no-flex-gap .guide-chan > * + * { margin-left: 12px; }
.no-flex-gap .banner > * + * { margin-left: 32px; }
.no-flex-gap .detail-head > * + * { margin-left: 40px; }
.no-flex-gap .detail-actions > * { margin-right: 16px; margin-bottom: 16px; }
.no-flex-gap .season-bar > * { margin-right: 12px; margin-bottom: 12px; }
.no-flex-gap .ep > * + * { margin-left: 20px; }
.no-flex-gap .search-bar > * + * { margin-left: 16px; }
.no-flex-gap .login-tabs > * + * { margin-left: 14px; }
.no-flex-gap .login-actions > * + * { margin-left: 16px; }
.no-flex-gap .set-row > * + * { margin-left: 24px; }
.no-flex-gap .pl-restart-group, .pl-transport-group, .pl-context-group > * + * { margin-left: 8px; }
.no-flex-gap .pl-head-top > * + * { margin-left: 20px; }
.no-flex-gap .pl-foot > * + * { margin-top: 18px; }
.no-flex-gap .pl-progress > * + * { margin-left: 20px; }
.no-flex-gap .pl-hints > * + * { margin-left: 32px; }
.no-flex-gap .subtitle-track-layout > * + * { margin-left: 24px; }
.no-flex-gap .subtitle-size-controls > * + * { margin-left: 20px; }
.no-flex-gap .modal-actions > * + * { margin-left: 16px; }
.no-flex-gap .pl-delaybar > * + * { margin-left: 14px; }
.no-flex-gap .spinner > * + * { margin-top: 26px; }
.no-flex-gap .boot > * + * { margin-top: 40px; }
.no-flex-gap .boot-progress > * + * { margin-top: 14px; }
/* === FIN PARADE FLEX-GAP (genere) === */
