/* ===========================================================================
   Spot! — chrome de l'application installée.

   TOUT ce fichier est sous « .is-app » : sur le site web, il ne pose pas une
   seule règle. La classe est posée avant le premier paint par app-mode.js
   (script synchrone), quand la PWA tourne en display-mode: standalone — ou par
   ?app=1 pour la démo dans un navigateur ordinaire.

   Ce qu'on remplace, et pourquoi :
   un site web mobile a une nav en haut, un pied de page en bas et le bouton
   « précédent » du navigateur. Une app installée n'a AUCUN de ces trois : pas
   de barre Safari, donc pas de retour, et un pied de page de site n'a pas de
   sens quand l'écran fait 6 pouces. Sans chrome de remplacement, on est
   littéralement coincé sur la fiche d'un live, sans moyen de revenir.
   D'où : barre de titre avec retour en haut, barre d'onglets en bas.

   Références de conception : barre d'onglets flottante en verre d'iOS 26
   (« Liquid Glass »), transitions push/pop d'un UINavigationController,
   réponse tactile immédiate. Les tokens restent ceux du brandkit — c'est
   l'ossature qui devient une app, pas la marque.
   =========================================================================== */

.is-app {
  /* Gouttière unique : la SEULE unité d'espacement du mode app, horizontale
     comme verticale. Trois valeurs coexistaient (11, 12, 18 px) et aucun bord
     ne tombait sur le même axe — c'est ce désalignement, plus qu'un espacement
     pris isolément, qui fait qu'un écran « ne tombe pas juste ». */
  --app-gutter: 16px;
  /* Mesures du chrome, une seule source. Le padding des pages s'en déduit :
     changer la hauteur d'une barre ne laisse pas de contenu sous une autre. */
  /* 64 px : exactement l'encombrement de la zone basse (54 px de capsule +
     10 px d'air sous elle). Le haut et le bas de l'écran pèsent donc le même
     poids, ce qui est la seule façon de rendre l'en-tête « cohérent avec le
     reste de la navigation ».

     L'audit qui a mené là : à 52 px, le haut occupait 12 px de moins que le
     bas, et le logo disposait de 14 px d'air contre une capsule basse qui, elle,
     flotte avec 16 px de dégagement latéral et 10 px sous elle. Le logo était
     pincé entre la barre d'état du système et le contenu, sans détachement —
     d'où la sensation d'étouffement, qui ne venait pas d'un défaut de centrage
     (il était parfait) mais d'un manque d'air absolu. À 64 px, il respire de
     20 px de chaque côté. */
  --app-top-h: 64px;          /* hauteur utile de la barre de titre */
  --app-tabs-h: 54px;         /* hauteur utile de la barre d'onglets */
  --app-tabs-gap: 10px;       /* air entre la barre flottante et le bas */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  /* Verre : c'est le fond translucide qui fait « surface système » plutôt que
     « div blanche ». La saturation compense le délavage du flou. */
  --app-glass: rgba(250, 247, 254, .72);
  --app-glass-strong: rgba(250, 247, 254, .92);
  --app-blur: saturate(180%) blur(20px);
  /* Le bord du verre, et la SEULE chose qui dise où la barre commence quand le
     fond est clair.

     Mesuré au pixel : sur un fond à l'encre, c'est la surface claire qui détache
     la barre (15,2:1) et tout va bien. Sur du papier, la surface composée vaut
     EXACTEMENT le fond — 1,00:1 sur /marques, 1,05:1 sur une carte blanche. Il
     ne reste alors que ce bord, et à 7 % d'encre il rendait 1,17:1 : la barre
     n'avait plus de limites, elle se dissolvait dans la carte blanche du Club.

     Aucune opacité de fond ne peut rattraper ça : du papier sur du blanc reste
     du blanc. La séparation se joue donc au bord, comme dans toute barre
     flottante iOS — dont les filets sont eux aussi à ~36 % d'opacité, et non à
     quelques pour cent. À 45 %, le filet rend 3,3:1 sur blanc et 3,1:1 sur
     papier, au-dessus du seuil de 3:1 que WCAG demande pour la limite d'un
     composant. 38 % avait été essayé d'abord et mesuré à 2,5:1 : sous le seuil,
     donc insuffisant — la valeur ne se devine pas, elle se mesure. */
  --app-edge: rgba(26, 7, 40, .45);
  /* Le filet d'un bandeau PLEINE LARGEUR (la barre de titre) traverse l'écran
     de bord à bord, là où celui de la pilule n'en dessine que le pourtour : à
     45 % il barrait l'écran. Il n'identifie d'ailleurs aucun contrôle — le titre
     et le chevron portent leur propre contraste — il dit seulement que le
     contenu passe DESSOUS. 32 % rend 2,1:1 sur blanc : un filet franc, pas une
     frontière. À 7 % il rendait 1,17:1, et un titre de page pouvait se glisser
     sous la barre sans que rien ne le montre. */
  --app-edge-soft: rgba(26, 7, 40, .32);
  /* Le filet de l'en-tête au repos : il sépare, il ne souligne pas. Mesuré à
     1,36:1 sur papier — perceptible sans être une barrière, dans la gamme des
     séparateurs système d'iOS. À .32, la valeur de l'état défilé, il rendrait
     2,12:1 : un trait qu'on lit avant le titre qu'il est censé détacher. */
  --app-edge-hair: rgba(26, 7, 40, .14);
}

/* --- Racine -------------------------------------------------------------- */

/* overscroll-behavior: none coupe le rebond élastique du document. Dans un
   onglet Safari ce rebond est un repère ; dans une app installée il découvre
   une bande de fond sous la barre d'onglets flottante et casse l'illusion.
   Il reste actif DANS les zones scrollables horizontales (rails de cartes). */
html.is-app,
html.is-app body {
  overscroll-behavior-y: none;
  /* La police ne doit pas être regrossie par iOS en paysage : une barre
     d'onglets dont les libellés grossissent tout seuls se voit immédiatement. */
  -webkit-text-size-adjust: 100%;
}

html.is-app body {
  /* Place pour les deux barres. Sans ça, la dernière carte d'une liste vit
     sous la barre d'onglets et reste inatteignable — le défaut le plus
     fréquent des PWA converties à la hâte. */
  padding-top: calc(var(--safe-t) + var(--app-top-h));
  padding-bottom: calc(var(--safe-b) + var(--app-tabs-h) + var(--app-tabs-gap) * 2);
  padding-left: var(--safe-l);
  padding-right: var(--safe-r);
}

/* Le grain en overlay est un effet d'affiche : sur un écran de téléphone tenu à
   30 cm, il ajoute du bruit de compression et coûte un paint plein écran à
   chaque frame de transition. */
html.is-app body::after { display: none; }

/* --- Écran d'ouverture --------------------------------------------------- */

/* Le hero remonte SOUS la barre de titre au lieu de commencer en dessous. Sans
   ce rattrapage, le padding du body découvre une bande de papier clair au-dessus
   d'un hero à l'encre : une frise pâle collée sous l'heure, qui signe la page
   web habillée en app. Ici le visuel touche le haut de l'écran et la barre
   flotte par-dessus, transparente tant qu'on n'a pas défilé — le comportement
   d'une vue à défilement iOS.

   --chrome-h est mesurée par spot.js sur la nav du site, que le mode app
   masque : elle vaudrait 0 et le hero occuperait tout l'écran, barre d'onglets
   comprise. On la remplace par la vraie hauteur du chrome de l'app. */
.is-app .hero {
  margin-top: calc(-1 * (var(--safe-t) + var(--app-top-h)));
  /* L'air PERÇU sous la barre de titre doit valoir celui du bas : la part qui
     compense la barre ne se voit pas, seule la gouttière se voit. Sans ce
     calcul, on lisait 12 px en haut contre 22 en bas — assez pour que le bloc
     paraisse posé de travers sans qu'on sache dire pourquoi. */
  padding-top: calc(var(--safe-t) + var(--app-top-h));
  padding-bottom: var(--app-gutter);
  min-height: calc(100svh - var(--safe-b) - var(--app-tabs-h) - var(--app-tabs-gap) * 2);
}

/* --- Consentement cookies ------------------------------------------------ */

/* Collée en bas d'écran, la bannière recouvre la barre d'onglets : on ne peut
   plus naviguer tant qu'on n'a pas répondu, ce qui transforme une obligation
   légale en impasse. Elle passe donc AU-DESSUS de la barre, en feuille
   flottante, et laisse l'app utilisable pendant qu'on décide. */
.is-app .cc-banner {
  left: max(12px, var(--safe-l));
  right: max(12px, var(--safe-r));
  bottom: calc(var(--safe-b) + var(--app-tabs-h) + var(--app-tabs-gap) * 2);
  z-index: 970;
  border: 1px solid rgba(26, 7, 40, .08);
  border-radius: var(--r-surface);
  box-shadow: var(--sh-lift);
}
.is-app .cc-banner__in { padding: 14px 15px; gap: .7rem; }
.is-app .cc-banner__txt { font-size: .78rem; min-width: 0; }

/* Le chrome de l'app ne se sélectionne pas, ne surligne pas au toucher et
   n'ouvre pas de menu contextuel sur appui long : trois réflexes de page web
   qui n'existent nulle part dans une app. Le CONTENU, lui, reste sélectionnable
   (un code promo doit pouvoir se copier). */
.is-app .app-top,
.is-app .app-tabs,
.is-app .app-sheet__grip {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.is-app { -webkit-tap-highlight-color: transparent; }

/* --- Ce que l'app n'affiche pas ------------------------------------------ */

/* La nav du site (logo + onglets d'ancres + burger) est remplacée par les deux
   barres. Le pied de page aussi : ses liens légaux vivent désormais dans
   Mon Spot!. Le bandeau promo passe à la trappe parce qu'il doublonnerait la
   barre de titre — son information utile (un live en cours) est portée par la
   pastille de l'onglet Lives, qui la montre depuis n'importe quel écran. */
.is-app .nav,
.is-app .promo-bar,
.is-app .site-footer,
.is-app footer.footer,
.is-app .skip-link { display: none !important; }

/* --- Barre de titre ------------------------------------------------------ */

.app-top {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 900;
  display: none;
  align-items: center;
  gap: 4px;
  height: calc(var(--safe-t) + var(--app-top-h));
  padding: var(--safe-t) max(10px, var(--safe-r)) 0 max(10px, var(--safe-l));
  /* Transparente tant qu'on est en haut : le visuel de la page monte jusque
     sous l'heure, comme une vue à défilement iOS. Le verre n'arrive qu'au
     défilement (.is-scrolled), posé par spot-app.js. */
  /* Aplat de papier ombré plutôt que transparent (choix du 30/09/2026). Un
     en-tête transparent sur un fond clair n'a aucune matière : le logo flottait
     au-dessus du contenu sans que rien ne dise où commençait l'écran. Le papier
     ombré est déjà la couleur des sections secondaires du site — l'en-tête
     devient une surface sans qu'on introduise quoi que ce soit.

     Plus de backdrop-filter ici : un flou sous un aplat opaque ne se voit pas et
     coûte une passe de composition à chaque frame de défilement. */
  background: var(--paper-2);
  /* Un filet permanent sous l'en-tête : c'est lui qui dit où finit le chrome et
     où commence le contenu. Sans lui, le logo flottait au-dessus du titre de
     l'écran sans rien pour les séparer, et les deux se lisaient comme un seul
     bloc. Il est plus léger que celui de l'état défilé (--app-edge-soft, qui
     s'ajoute au verre) : ici il n'a rien à décoller, seulement à marquer une
     limite. Posé en box-shadow et non en border, pour ne pas ajouter un pixel à
     la hauteur de la barre — dont dépend le calage de tout le contenu. */
  box-shadow: 0 1px 0 var(--app-edge-hair);
  transition: background .22s var(--ease-std), box-shadow .22s var(--ease-std),
              transform .26s var(--ease-std);
}

/* Descendre, c'est vouloir voir le contenu : l'en-tête se retire vers le haut
   et rend sa hauteur à l'écran. Il revient au moindre retour vers le haut, et
   il est toujours là en tête de page. pointer-events coupé pendant qu'il est
   escamoté : une barre invisible qui intercepte encore les touchers est le
   défaut le plus déroutant qui soit. */
.app-top.is-hidden {
  transform: translateY(-100%);
  pointer-events: none;
}
.is-app .app-top { display: flex; }

/* Au défilement, l'aplat ne change pas — il est déjà opaque. Seul le filet se
   renforce, pour dire que du contenu passe DESSOUS. */
.app-top.is-scrolled {
  background: var(--paper-2);
  /* Même panne que la barre d'onglets, même cause : une fois défilé, le verre
     de l'en-tête se compose à 1,03:1 sur une page claire et le filet à 7 %
     rendait 1,17:1. On ne voyait donc pas où le contenu passait SOUS la barre,
     et un titre de page pouvait se glisser derrière elle sans qu'on le sache. */
  box-shadow: 0 1px 0 var(--app-edge-soft);
}

/* Bouton retour et action : cibles de 44 px, le minimum tactile d'Apple. Un
   chevron de 17 px dans une zone de 30 px se rate une fois sur trois. */
.app-top__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  border: 0;
  background: none;
  color: var(--ink);
  cursor: pointer;
  border-radius: var(--r-action);
  transition: transform .12s var(--ease-std), opacity .12s linear;
}
.app-top__btn:active { transform: scale(.88); opacity: .55; }

/* Le retour n'apparaît que sur un écran où l'on est ENTRÉ. Sur une destination
   d'onglet, il n'a nulle part où revenir : afficher un chevron inerte est pire
   que ne rien afficher. La profondeur est posée sur <html> par app-mode.js,
   avant le premier paint — le bouton ne clignote donc jamais. */
.app-top__btn { display: none; }
html[data-depth="deep"] .app-top__btn { display: inline-flex; }

/* Le titre est lisible dès l'arrivée sur l'écran : c'est lui qui dit où l'on
   est, et le découvrir seulement après avoir défilé n'aide personne. Il n'entre
   pas en concurrence avec le grand titre de la page, puisque l'en-tête s'efface
   dès qu'on descend (voir .is-hidden plus bas). */
.app-top__title {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  font-family: var(--fz-display);
  font-weight: 800;
  font-size: .98rem;
  letter-spacing: var(--track-titre);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Réserve la place du bouton retour à droite pour que le titre reste
   optiquement centré, comme une barre de navigation iOS. */
.app-top__spacer { width: 44px; flex: 0 0 auto; }

/* --- Barre d'onglets ----------------------------------------------------- */

.app-tabs {
  position: fixed;
  z-index: 950;
  left: max(12px, var(--safe-l));
  right: max(12px, var(--safe-r));
  bottom: calc(var(--safe-b) + var(--app-tabs-gap));
  display: none;
  align-items: stretch;
  height: var(--app-tabs-h);
  padding: 4px;
  border-radius: var(--r-action);
  /* Barre à l'encre plutôt qu'en verre clair (choix du 30/09/2026). Le verre
     réglait la visibilité par un filet bien calibré ; l'encre la règle par
     construction — une barre sombre se détache du blanc, du papier et d'une
     photo sans dépendre d'aucun réglage. C'est aussi ce qui a permis de retirer
     le flou : plus rien à laisser transparaître, donc plus de passe de
     composition à chaque frame. */
  background: var(--ink);
  border: 1px solid rgba(255, 255, 255, .12);
  /* Trois ombres, trois rôles, et aucune n'est décorative.

     1. Le halo intérieur clair : une arête de lumière en haut de la pilule, qui
        fait lire le bord comme une épaisseur de verre et non comme un trait
        dessiné. C'est lui qui empêche le filet à 45 % de paraître dur.
     2. L'ombre de contact, courte et dense : elle décolle la barre du fond
        immédiatement sous elle. L'ancienne valeur (0 1px 2px à 5 %) rendait
        1,11:1 entre le dessous de la barre et le fond, c'est-à-dire rien.
     3. L'ombre portée, longue : elle dit la hauteur de vol. Elle était déjà là
        mais trop diffuse (-18px d'étalement) pour se voir sur du blanc. */
  box-shadow:
    /* L'arête de lumière passe de 85 % à 10 % : sur l'encre, un liseré blanc
       franc dessinerait un trait au lieu de suggérer une épaisseur. */
    inset 0 1px 0 rgba(255, 255, 255, .10),
    0 2px 6px rgba(26, 7, 40, .22),
    0 14px 30px -12px rgba(26, 7, 40, .55);
  /* Elle ne se range PAS au défilement, contrairement à ce que fait iOS 26.
     C'est le point de repère permanent de l'application : savoir à tout moment
     où l'on est et pouvoir changer d'écran sans avoir à remonter d'abord. Seul
     l'en-tête s'efface, parce que lui n'offre aucune destination. */
}
.is-app .app-tabs { display: flex; }

.app-tab {
  position: relative;
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  border-radius: var(--r-action);
  /* Blanc atténué sur l'encre : 7,4:1, bien au-dessus de AA. L'encre douce des
     textes secondaires, elle, aurait disparu sur ce fond. */
  color: rgba(255, 255, 255, .62);
  font-family: var(--fz-body);
  font-size: .625rem;
  font-weight: 700;
  letter-spacing: .01em;
  text-decoration: none;
  transition: color .18s var(--ease-std), transform .12s var(--ease-std);
}
.app-tab:active { transform: scale(.93); }
.app-tab svg {
  width: 22px; height: 22px; display: block;
  fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
  transition: stroke-width .18s var(--ease-std);
}

/* L'onglet actif porte une « lentille » pleine, à l'encre de la marque. Le
   choix de l'encre plutôt que de la menthe n'est pas esthétique : la menthe est
   la couleur d'action du kit, elle appelle un geste. Un onglet actif ne demande
   rien, il situe.

   L'état est croisé entre <html data-tab> et l'onglet : quatre paires écrites à
   la main, faute de sélecteur capable de comparer deux attributs. En échange,
   aucun JavaScript ne touche la barre au chargement. */
:is(
  html[data-tab="/"] .app-tab[data-tab="/"],
  html[data-tab="/lives"] .app-tab[data-tab="/lives"],
  html[data-tab="/club"] .app-tab[data-tab="/club"],
  html[data-tab="/mon-spot"] .app-tab[data-tab="/mon-spot"]
) {
  /* Encre sur la lentille menthe : 12,2:1. En blanc — l'état de la maquette —
     le libellé rendait 1,55:1, soit illisible, très en dessous des 4,5:1 exigés
     pour du texte. La menthe est une couleur claire : c'est l'encre qui se pose
     dessus, jamais le blanc. C'est d'ailleurs la règle du brandkit. */
  color: var(--ink);
}

/* Le trait épaissit sur l'onglet actif, comme une icône pleine face à une
   icône creuse dans une barre d'onglets iOS : le contraste ne repose pas que
   sur la couleur, donc il tient aussi en niveaux de gris. */
:is(
  html[data-tab="/"] .app-tab[data-tab="/"],
  html[data-tab="/lives"] .app-tab[data-tab="/lives"],
  html[data-tab="/club"] .app-tab[data-tab="/club"],
  html[data-tab="/mon-spot"] .app-tab[data-tab="/mon-spot"]
) svg { stroke-width: 2.35; }

/* La lentille de l'onglet actif est un ÉLÉMENT, pas un ::before, et c'est la
   seule raison de sa présence dans le HTML : view-transition-name n'a aucun
   effet sur un pseudo-élément. Portée par un ::before, la lentille
   réapparaissait bêtement sur le nouvel onglet au lieu d'y glisser — l'animation
   était écrite, elle ne pouvait simplement pas s'appliquer.

   D'un écran à l'autre, elle glisse donc de l'onglet quitté vers l'onglet
   rejoint. C'est ce mouvement, plus que le flou du verre, qui fait lire les
   écrans comme une seule application plutôt que comme des pages successives. */
.app-tab__lens {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: var(--r-action);
  background: var(--mint);
  display: none;
}
:is(
  html[data-tab="/"] .app-tab[data-tab="/"],
  html[data-tab="/lives"] .app-tab[data-tab="/lives"],
  html[data-tab="/club"] .app-tab[data-tab="/club"],
  html[data-tab="/mon-spot"] .app-tab[data-tab="/mon-spot"]
) .app-tab__lens {
  display: block;
  view-transition-name: app-tab-lens;
}

/* Pastille d'état : rouge Direct quand un live est en cours (la seule chose qui
   mérite d'interrompre, et le rouge du kit lui est réservé), menthe sinon. */
.app-tab__dot {
  position: absolute;
  top: 6px;
  left: 50%;
  margin-left: 7px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--mint);
  box-shadow: 0 0 0 2px var(--ink);
}
.app-tab__dot--live {
  background: var(--live);
  animation: app-pulse var(--pulse) ease-in-out infinite;
}
:is(
  html[data-tab="/"] .app-tab[data-tab="/"],
  html[data-tab="/lives"] .app-tab[data-tab="/lives"],
  html[data-tab="/club"] .app-tab[data-tab="/club"],
  html[data-tab="/mon-spot"] .app-tab[data-tab="/mon-spot"]
) .app-tab__dot { box-shadow: 0 0 0 2px var(--mint); }
@keyframes app-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .55; transform: scale(.82); }
}

/* --- Réponse au toucher -------------------------------------------------- */

/* Une app native répond AVANT que le doigt se lève. Sur le web, l'état :active
   n'est appliqué par WebKit que si un gestionnaire tactile existe — d'où
   l'écouteur vide posé par spot-app.js. Sans ces deux moitiés, les cartes
   restent inertes au doigt et tout paraît lent, même à 60 fps. */
.is-app .card:active,
.is-app .lv-card:active,
.is-app .spot-card:active,
.is-app .btn:active { transform: scale(.975); }
.is-app .card,
.is-app .lv-card,
.is-app .spot-card,
.is-app .btn { transition: transform .12s var(--ease-std); }

/* Les rails horizontaux gardent leur inertie et leur accroche, mais ne
   transmettent plus leur rebond au document (sinon toute la page tremble
   quand on arrive au bout d'un rail). */
.is-app [class*="rail"],
.is-app .scroller { overscroll-behavior-x: contain; }

/* --- Tirer pour rafraîchir ----------------------------------------------- */

/* iOS ne donne AUCUN geste de rafraîchissement à une app installée : pas de
   barre d'adresse à tirer, pas de bouton recharger. Une page figée sur un live
   terminé n'a alors littéralement aucun moyen de se mettre à jour, à part
   fermer l'app. C'est la fonction manquante la plus coûteuse du mode
   standalone, et la seule réponse est de la réimplémenter. */
.app-pull {
  position: fixed;
  top: calc(var(--safe-t) + 6px);
  left: 50%;
  z-index: 880;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  margin-left: -15px;
  border-radius: 50%;
  background: var(--app-glass-strong);
  -webkit-backdrop-filter: var(--app-blur);
  backdrop-filter: var(--app-blur);
  box-shadow: 0 6px 18px -8px rgba(26, 7, 40, .5);
  color: var(--ink);
  opacity: 0;
  transform: translateY(-12px) scale(.7);
  pointer-events: none;
}
.app-pull svg { width: 16px; height: 16px; }
.app-pull.is-armed svg { color: var(--mint-shadow); }
.app-pull.is-spinning svg { animation: app-spin .7s linear infinite; }
@keyframes app-spin { to { transform: rotate(360deg); } }

/* --- Hors ligne ---------------------------------------------------------- */

/* Bandeau, pas modale : perdre le réseau ne doit pas arrêter la lecture de ce
   qui est déjà à l'écran. Il se place au-dessus de la barre d'onglets. */
.app-offline {
  position: fixed;
  z-index: 960;
  left: max(12px, var(--safe-l));
  right: max(12px, var(--safe-r));
  bottom: calc(var(--safe-b) + var(--app-tabs-h) + var(--app-tabs-gap) * 2);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 9px 14px;
  border-radius: var(--r-action);
  background: var(--ink);
  color: #fff;
  font-size: .8rem;
  font-weight: 600;
  box-shadow: var(--sh-lift);
  transform: translateY(20px);
  opacity: 0;
  pointer-events: none;
  transition: transform .3s var(--ease-pop), opacity .2s linear;
}
.app-offline.is-on { transform: none; opacity: 1; }
.app-offline__dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--live); flex: 0 0 auto;
}

/* --- Feuille « Ajouter à l'écran d'accueil » ----------------------------- */

/* iOS ne déclenche jamais beforeinstallprompt : l'installation passe par
   Partager → Sur l'écran d'accueil, un geste que personne ne trouve seul.
   D'où cette feuille, montrée UNE fois, seulement sur Safari iOS et seulement
   hors de l'app. Elle est refermable et ne revient pas : une invite qu'on
   revoit à chaque visite fait fuir plus qu'elle n'installe. */
.app-install {
  position: fixed;
  z-index: 1000;
  left: max(12px, env(safe-area-inset-left, 0px));
  right: max(12px, env(safe-area-inset-right, 0px));
  /* Au-dessus de la bannière cookies, qui occupe le bas de l'écran et porte une
     obligation légale : l'invite ne doit jamais la recouvrir. */
  bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);
  padding: 16px 16px 14px;
  border-radius: var(--r-surface);
  /* Couleur littérale, et c'est tout l'enjeu : cette feuille déclare ses
     variables dans le bloc .is-app, alors que l'invite s'adresse par définition
     aux visiteurs du SITE, hors de ce bloc. La variable n'y existait pas, la
     déclaration devenait invalide au calcul, et le fond retombait sur
     transparent — une invite sans fond se superposait à la bannière cookies,
     les deux textes illisibles. C'est la seule famille de règles de ce fichier
     qui vit hors de .is-app : elle ne doit dépendre d'aucun de ses tokens. */
  background: rgba(250, 247, 254, .97);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border: 1px solid rgba(26, 7, 40, .10);
  /* Sur un écran large, « left/right: 12px » l'étirait sur 1416 px. */
  max-width: 420px;
  margin-inline: auto;
  box-shadow: var(--sh-lift);
  transform: translateY(140%);
  transition: transform .42s var(--ease-pop);
}
.app-install.is-on { transform: none; }
.app-install__head { display: flex; align-items: center; gap: 12px; }
.app-install__icon { width: 44px; height: 44px; border-radius: 11px; flex: 0 0 auto; box-shadow: var(--sh-sm); }
.app-install__t { font-family: var(--fz-display); font-weight: 900; font-size: 1rem; letter-spacing: var(--track-titre); }
.app-install__s { font-size: .8rem; color: var(--ink-soft); margin-top: 1px; }
.app-install__steps { margin: 12px 0 0; padding: 0; list-style: none; display: grid; gap: 7px; font-size: .84rem; }
.app-install__steps li { display: flex; align-items: center; gap: 8px; }
.app-install__steps svg { width: 17px; height: 17px; flex: 0 0 auto; color: var(--ink-soft); }
.app-install__x {
  position: absolute; top: 10px; right: 10px;
  width: 30px; height: 30px; display: grid; place-items: center;
  border: 0; background: rgba(26, 7, 40, .06); color: var(--ink);
  border-radius: 50%; cursor: pointer;
}

/* ===========================================================================
   Transitions entre écrans
   ---------------------------------------------------------------------------
   Le point qui sépare « site web en plein écran » de « application » : sur le
   web, une page apparaît d'un bloc après un blanc ; dans une app, un écran
   arrive PAR UN CÔTÉ et celui qu'on quitte recule. Les transitions de vue
   entre documents (Safari 18.2+, Chrome 126+) donnent exactement ça sur un
   site multi-pages rendu par le serveur, sans transformer le site en SPA.

   Le sens est décidé par spot-app.js, qui pose data-vt sur <html> :
   push (on entre dans un détail), pop (on revient), tab (on change d'onglet).
   Sans lui, on retombe sur un fondu : jamais de régression.
   =========================================================================== */

/* Même restriction que app-mode.js : pas de fullscreen, qui est vrai pour
   un navigateur ordinaire en plein écran. */
@media (display-mode: standalone), (display-mode: minimal-ui) {
  @view-transition { navigation: auto; }
}

/* Les deux barres ne participent pas à la transition de la racine : elles sont
   nommées, donc capturées à part et laissées immobiles pendant que le contenu
   glisse. Une barre d'onglets qui glisse avec la page est le défaut n°1 des
   premières intégrations de cette API. */
.is-app .app-top { view-transition-name: app-top; }
.is-app .app-tabs { view-transition-name: app-tabs; }

/* Figer le GROUPE ne suffit pas, et c'est le piège de cette API : le groupe
   porte le déplacement et la taille, mais les deux images capturées — l'ancienne
   et la nouvelle — gardent par défaut leur fondu croisé de 250 ms. Les barres
   s'effaçaient donc puis revenaient à chaque changement d'onglet, et l'écran
   paraissait se casser une demi-seconde : ni titre, ni onglets, sur un contenu
   à moitié transparent.

   Pour un élément qui doit rester IDENTIQUE d'un écran à l'autre, la marche à
   suivre est de ne rien animer du tout : on retire l'ancienne image, on montre
   la nouvelle pleinement opaque, et on désactive l'isolation qui sert au
   mélange des deux. La barre ne bouge plus, ne clignote plus : elle est
   simplement là, avant comme après — ce qu'on attend d'une barre d'application. */
::view-transition-group(app-top),
::view-transition-group(app-tabs) { animation: none; }

/* isolation n'accepte que auto ou isolate : « none » était silencieusement
   rejeté par le navigateur, et la règle restait vide. Vérifié en relisant les
   règles telles que le navigateur les a analysées — une déclaration invalide ne
   prévient jamais, elle disparaît. */
::view-transition-image-pair(app-top),
::view-transition-image-pair(app-tabs) { isolation: auto; }

::view-transition-old(app-top),
::view-transition-old(app-tabs) { display: none; }

::view-transition-new(app-top),
::view-transition-new(app-tabs) { animation: none; opacity: 1; mix-blend-mode: normal; }

::view-transition-group(app-tab-lens) {
  animation-duration: .34s;
  animation-timing-function: var(--ease-pop);
}

/* Entrer dans un écran : le nouveau vient de la droite, l'ancien recule d'un
   tiers et s'assombrit — la parallaxe d'un UINavigationController. */
html[data-vt="push"]::view-transition-old(root) {
  animation: .3s var(--ease-std) both app-out-left;
}
html[data-vt="push"]::view-transition-new(root) {
  animation: .3s var(--ease-std) both app-in-right;
}
html[data-vt="pop"]::view-transition-old(root) {
  animation: .28s var(--ease-std) both app-out-right;
}
html[data-vt="pop"]::view-transition-new(root) {
  animation: .28s var(--ease-std) both app-in-left;
}
/* Changer d'onglet n'est pas entrer dans une profondeur : pas de glissement
   latéral, un fondu court avec une échelle presque nulle. */
html[data-vt="tab"]::view-transition-old(root) { animation: .12s linear both app-fade-out; }
html[data-vt="tab"]::view-transition-new(root) { animation: .16s var(--ease-std) both app-fade-in; }

@keyframes app-in-right { from { transform: translateX(100%); } to { transform: none; } }
@keyframes app-out-left { from { transform: none; opacity: 1; } to { transform: translateX(-32%); opacity: .55; } }
@keyframes app-in-left { from { transform: translateX(-32%); opacity: .55; } to { transform: none; opacity: 1; } }
@keyframes app-out-right { from { transform: none; } to { transform: translateX(100%); } }
@keyframes app-fade-out { to { opacity: 0; } }
@keyframes app-fade-in { from { opacity: 0; transform: scale(.985); } to { opacity: 1; transform: none; } }

/* Le visuel du live suit la carte sur laquelle on a appuyé : il grandit de sa
   vignette jusqu'à l'affiche de la fiche. spot-app.js pose ce nom au moment du
   clic, sur la SEULE carte touchée — deux éléments portant le même nom dans un
   document annulent toute la transition. */
.is-app [data-vt-live] { view-transition-name: app-live-visuel; }
::view-transition-group(app-live-visuel) {
  animation-duration: .36s;
  animation-timing-function: var(--ease-std);
}
::view-transition-image-pair(app-live-visuel) { isolation: auto; }
::view-transition-old(app-live-visuel),
::view-transition-new(app-live-visuel) {
  /* Sans cela, deux images de rapports d'aspect différents se déforment l'une
     dans l'autre au lieu de se fondre : le défaut se voit énormément sur une
     vignette paysage qui devient une affiche portrait. */
  animation: none;
  mix-blend-mode: normal;
  height: 100%;
  object-fit: cover;
}

/* Mouvement réduit : on garde le repère (un fondu très court) et on retire tout
   déplacement. Supprimer la transition entière ferait clignoter l'écran, ce qui
   est pire pour les personnes concernées. */
@media (prefers-reduced-motion: reduce) {
  html[data-vt]::view-transition-old(root),
  html[data-vt]::view-transition-new(root) {
    animation-duration: .12s !important;
    animation-name: app-fade-in !important;
  }
  ::view-transition-group(app-tab-lens),
  ::view-transition-group(app-live-visuel) { animation-duration: .01ms !important; }
  .app-tab__dot--live { animation: none; }
}

/* --- Écran hors ligne ---------------------------------------------------- */

.offline-screen {
  min-height: 100svh;
  display: grid;
  place-content: center;
  justify-items: center;
  text-align: center;
  gap: 14px;
  padding: 32px 26px calc(env(safe-area-inset-bottom, 0px) + 32px);
}
.offline-screen__ic {
  width: 66px; height: 66px; display: grid; place-items: center;
  border-radius: 50%; background: var(--paper-2); color: var(--ink-soft);
}
.offline-screen h1 {
  font-family: var(--fz-display); font-weight: var(--w-affiche);
  font-size: 1.6rem; letter-spacing: var(--track-affiche);
}
.offline-screen p { color: var(--ink-soft); max-width: 30ch; }

/* --- Repli sans flou d'arrière-plan -------------------------------------- */

/* Les surfaces de verre ne tiennent QUE par le flou : sans lui, il ne reste
   qu'un fond à 72 % d'opacité, et le contenu qui défile dessous se lit au
   travers du titre. Là où la propriété est inconnue, on rend donc les fonds
   opaques : on perd l'effet, jamais la lisibilité.

   Portée exacte, pour ne pas s'y tromper : @supports teste si le navigateur
   CONNAÎT la propriété, pas s'il la DESSINE. Un Chrome en rendu logiciel — celui
   des captures de recette — la déclare supportée et ne l'applique pas : les
   aperçus montrent donc un verre trop transparent qu'aucun appareil réel
   n'affiche. Ce repli ne couvre pas ce cas et n'a pas à le couvrir ; il protège
   des navigateurs qui ignorent backdrop-filter, et c'est tout. */
@supports not ((-webkit-backdrop-filter: blur(2px)) or (backdrop-filter: blur(2px))) {
  .is-app {
    --app-glass: var(--paper);
    --app-glass-strong: var(--paper);
  }
  .app-top.is-scrolled { box-shadow: 0 1px 0 rgba(26, 7, 40, .12); }
  .app-tabs { border-color: rgba(26, 7, 40, .12); }
}

/* ===========================================================================
   L'ACCUEIL COMME ÉCRAN D'APPLICATION
   ---------------------------------------------------------------------------
   Poser une barre d'onglets sur une page web ne fait pas une application : on
   obtient une interface mobile, et ça se voit tout de suite. La différence
   n'est pas dans le chrome, elle est dans CE QUI EST À L'ÉCRAN.

   Une page d'accueil de site s'adresse à quelqu'un qu'il faut convaincre :
   accroche plein écran, argumentaire, formulaire d'inscription, logos de
   marques en gage de sérieux. Quelqu'un qui a installé l'app est déjà
   convaincu — il l'a prouvé en l'installant. Lui reservir la promesse à chaque
   ouverture, c'est lui faire défiler un écran entier avant d'atteindre ce
   pour quoi il vient : les lives.

   On retire donc le discours et on garde le contenu. Rien n'est supprimé du
   site : ces blocs restent intacts pour les visiteurs du web, qui eux ont
   besoin d'être convaincus.
   =========================================================================== */

/* Les logos de marques en défilement sont une preuve sociale : ils répondent à
   « est-ce sérieux ? », une question que personne ne se pose dans une app
   qu'il a lui-même installée. */
.is-app .logos-section,
/* Le pied de page d'un site (plan du site, mentions, réseaux) n'a pas
   d'équivalent dans une application : ses liens vivent dans Mon Spot!. */
.is-app body > footer { display: none !important; }

/* Le hero cesse d'occuper la hauteur d'écran : en application, on doit voir
   qu'il y a quelque chose EN DESSOUS. Un premier écran qui remplit exactement
   la fenêtre donne l'impression d'être arrivé au bout. */
.is-app .hero {
  min-height: 0;
  justify-content: flex-start;
}
.is-app .hero__grid { gap: 0; }

/* Densité. Les respirations d'une page web — pensée pour un écran large et un
   défilement lent — coûtent ici des écrans entiers. On resserre les sections
   et les titres sans toucher aux cartes, qui restent des cibles tactiles. */
/* Le rythme vertical suit la même unité que la gouttière, en multiples entiers :
   24 px de respiration par section (soit 48 px entre deux sections) et 16 px
   sous un titre. Avant, ces valeurs étaient en rem — 27,2 px — et ne tombaient
   sur aucun multiple : l'œil ne peut pas nommer l'écart, mais il le voit. */
.is-app section { padding: 24px 0; }
.is-app .sec-head { margin-bottom: 16px; }
.is-app .sec-head h2 { font-size: 1.32rem; }
.is-app .sec-head p { font-size: .87rem; margin-top: .3rem; }
.is-app .cadeau-sec { padding: 24px 0; }

/* Le halo du hero est calibré pour une accroche qui n'est plus là : sans elle,
   il éclaire du vide au-dessus du visuel. */
.is-app .hero__glow { opacity: .5; }

/* Le visuel d'ouverture est une affiche verticale : à sa taille de site, il
   remplit l'écran à lui seul et on ne devine rien de ce qui suit. Une app
   montre toujours le haut du bloc suivant — c'est ce qui donne envie de
   défiler, et ce qui distingue un écran d'une page. Plafonné à 46 % de la
   hauteur, le live garde sa présence et laisse voir l'agenda commencer. */
.is-app .hero .player,
.is-app .hero .stage__halo { max-height: 46svh; }
.is-app .hero .player img,
.is-app .hero .player video { height: 100%; object-fit: cover; }

/* Les sous-titres de section expliquent au visiteur ce qu'il va trouver et
   pourquoi il devrait rester. Dans l'app, la réponse est sous ses yeux : les
   cartes sont là, le geste est sur les cartes. Le texte ne fait plus que
   repousser le contenu. */
.is-app .sec-head p { display: none; }

/* Les titres de section sont écrits pour le web : un surtitre, puis une phrase
   complète — « La sélection des Lives à venir disponibles sur Spot! » — puis une
   explication. Trois étages avant la première carte. Une application titre en
   deux mots, parce que l'écran ne contient qu'une chose et qu'on sait déjà où
   l'on est.

   Le surtitre DIT DÉJÀ cette chose : « L'agenda des lives », « À voir & revoir »,
   « À gagner ». On le promeut donc en titre, et la phrase disparaît. Rien n'est
   réécrit, aucun texte n'est inventé : le bon libellé était là, sous le mauvais.

   :has() garde ce remplacement sûr — une section sans surtitre conserve son
   titre, au lieu de se retrouver sans aucun. */
.is-app .sec-head div:has(> .kicker) h2,
.is-app .lots-head:has(> .kicker) .lots-head__ttl { display: none; }

.is-app .sec-head .kicker,
.is-app .lots-head .kicker {
  font-family: var(--fz-display);
  font-size: 1.32rem;
  font-weight: var(--w-affiche);
  letter-spacing: var(--track-titre);
  text-transform: none;
  color: var(--ink);
  margin-bottom: .2rem;
  line-height: 1.15;
}


/* --- Le hero selon qu'il y a un live, ou non ----------------------------- */

/* Le discours du hero ne s'efface QUE s'il y a un live à montrer à sa place.
   La distinction n'est pas cosmétique : sans elle, une journée sans live ouvre
   l'application sur 80 px de bande vide — le halo du hero, et rien d'autre.
   C'est arrivé pour de bon, et longtemps : le partage d'un live vers Spot! est
   un geste manuel, et l'agenda est resté vide 52 jours d'affilée à l'été 2026.
   L'état « rien en ce moment » n'est donc pas un cas limite, c'est un état
   ordinaire, qui doit être tenu aussi bien que les autres. */
.is-app .hero:has(.stage, .sc-brand) .hero__copy { display: none; }

/* Sans live, il reste le formulaire de rappel — et lui seul. L'accroche et
   l'argumentaire s'adressent toujours à un visiteur à convaincre, mais « Sois
   prévenu·e des prochains lives » est exactement ce qu'il y a à faire quand il
   n'y a rien à regarder. L'écran vide se répare tout seul, avec un texte déjà
   écrit et une action déjà branchée. */
.is-app .hero:not(:has(.stage, .sc-brand)) .hero__copy > :is(.eyebrow, h1, .lead) { display: none; }

/* Et le hero se comporte alors comme un simple bloc de tête : pas de halo à
   éclairer du vide, pas de hauteur réservée à une affiche absente. */
.is-app .hero:not(:has(.stage, .sc-brand)) {
  padding-top: calc(var(--safe-t) + var(--app-top-h) + var(--app-gutter));
  padding-bottom: var(--app-gutter);
}
.is-app .hero:not(:has(.stage, .sc-brand)) .hero__glow { display: none; }

/* ===========================================================================
   ÉQUILIBRE : une seule gouttière, des cibles à la taille du doigt
   ---------------------------------------------------------------------------
   Audit du 30 septembre 2026, mesuré en navigateur. Trois défauts, invisibles
   un par un et très visibles ensemble.
   =========================================================================== */

/* 1. TROIS GOUTTIÈRES COEXISTAIENT — 18 px pour les titres, 11 px pour les
   rails de cartes, 12 px pour la barre d'onglets. Aucun bord vertical ne
   tombait sur le même axe, et c'est ce désalignement, plus que n'importe quel
   espacement pris isolément, qui fait qu'un écran « ne tombe pas juste ».
   Une valeur unique, et tous les bords s'alignent (--app-gutter, déclarée avec
   les autres mesures du chrome en tête de fichier). */
.is-app .container,
.is-app .container--wide { padding-inline: var(--app-gutter); }

/* Le rail de cartes débordait de son conteneur de 3 px pour ne pas rogner les
   ombres. Le prix était que la première carte ne s'alignait pas sur le titre
   qui la surmonte — un décalage de 3 px, assez petit pour ne pas se nommer et
   assez grand pour se voir. En application, l'alignement prime : l'ombre d'une
   carte en bord d'écran n'est de toute façon pas visible. */
.is-app .lc-rail { margin-inline: 0; padding-inline: 0; }

/* Les deux barres flottantes reprennent la même gouttière. */
.is-app .app-tabs,
.is-app .cc-banner {
  left: max(var(--app-gutter), var(--safe-l));
  right: max(var(--app-gutter), var(--safe-r));
}

/* 2. LE BLOC DE TÊTE ÉTAIT DÉCENTRÉ de 6 px (18 à gauche, 12 à droite) : la
   carte blanche du rappel paraissait poussée contre le bord droit, ce qui se
   voit immédiatement sur fond sombre. */
.is-app .hero__grid { margin-inline: 0; }
.is-app .hero__copy {
  margin-inline: 0;
  /* Les 6 px venaient de là : un élément de grille a min-width:auto, donc il
     refuse de passer sous la largeur minimale de son contenu — ici le champ et
     le bouton « M'inscrire » côte à côte. Il débordait de sa colonne par la
     droite, sans marge ni padding pour le trahir, et la carte blanche paraissait
     poussée contre le bord. min-width:0 rend la colonne maîtresse de sa
     largeur ; c'est la correction canonique du débordement de grille, et elle
     ne change rien là où le contenu tient déjà. */
  min-width: 0;
}

/* 3. CIBLES TACTILES. Apple fixe le minimum à 44 px, et ce n'est pas une
   convention d'esthétique : en dessous, on rate la cible une fois sur trois,
   en marchant ou d'une seule main. Les libellés restaient à leur taille, seule
   la zone sensible grandit — l'écran ne change pas, il devient atteignable. */
.is-app :is(.sec-link, .sel-boost__link, .seg button) {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
/* La zone tactile a grandi, pas le texte : en gardant son border-bottom, le
   trait tombait 18 px sous la ligne de base et lisait comme un filet posé en
   travers de l'en-tête. Le soulignement est redessiné au ras du texte. */
.is-app .sec-link { border-bottom: 0; position: relative; }
.is-app .sec-link::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: calc(50% - .78em);
  height: 2px;
  background: rgba(26, 7, 40, .16);
}
/* Le segment SMS/Email garde sa forme de pilule : on rend la hauteur au
   bouton, pas au cadre, sinon la pilule devient un pavé. */
.is-app .seg { padding: 4px; }
.is-app .seg button { padding-block: 0; }

/* Les liens de section sont des cibles isolées en bout de ligne : sans marge
   propre, leurs 44 px mordent sur le titre voisin. */
.is-app .sec-head { align-items: center; }


/* La croix de l'invite d'installation faisait 30 px. C'est mon propre bouton :
   il n'a aucune excuse à passer sous le minimum que le reste respecte. La zone
   sensible est étendue sous le rond visible, qui garde sa taille. */
.is-app .app-install__x,
.app-install__x { width: 36px; height: 36px; }
.app-install__x::after {
  content: "";
  position: absolute;
  inset: -4px;
}

/* ===========================================================================
   Corrections relevées sur iPhone, 30 septembre 2026
   =========================================================================== */

/* 1. LE TITRE DE LA BARRE N'ÉTAIT PAS CENTRÉ, de 22 px exactement. La barre
   réserve 44 px à droite pour faire contrepoids au bouton retour ; sur une
   destination d'onglet, le bouton est masqué et la réserve, elle, restait. Le
   titre se centrait donc dans un espace amputé d'un seul côté.
   La réserve suit désormais le bouton : présents tous les deux, ou aucun. */
.app-top__spacer { display: none; }
html[data-depth="deep"] .app-top__spacer { display: block; }

/* 2. UN VIDE DE 120 PX OUVRAIT LES PAGES À EN-TÊTE. Le corps réserve déjà la
   hauteur de la barre de titre ; l'en-tête de page ajoutait par-dessus son
   propre padding de site (32 px et plus), et les deux se cumulaient sous
   l'encoche. Sur un écran de 6 pouces, le premier mot arrivait au tiers de la
   hauteur. L'en-tête n'a plus à compenser quoi que ce soit : la gouttière
   suffit. */
.is-app .acct-head { padding-top: var(--app-gutter); }

/* 3. LE TITRE DE PAGE DOUBLONNAIT LA BARRE. « Programme / Tous les lives / Les
   lives shopping de tes marques, en direct et en replay. » sous une barre qui
   affiche déjà « Lives » : trois formulations pour une seule information, et
   deux écrans de défilement avant le premier live.
   Le grand titre reste — c'est le motif iOS du titre qui se réduit dans la
   barre au défilement, et il porte la recherche en cours quand il y en a une.
   Le surtitre et la phrase d'explication, eux, s'en vont. */
.is-app .acct-head .kicker,
.is-app .acct-sub { display: none; }
.is-app .acct-hi { font-size: 1.75rem; }

/* 4. LES FILTRES OCCUPAIENT UN ÉCRAN ENTIER avant la première carte : quatre
   contrôles empilés sur toute la largeur. Les deux listes déroulantes tiennent
   côte à côte, et l'ensemble passe de quatre lignes à trois — sans descendre
   sous les 44 px de hauteur tactile. */
.is-app .lv-filters {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: var(--app-gutter);
}
.is-app .lv-filters__search,
.is-app .lv-filters .btn { grid-column: 1 / -1; }
.is-app .lv-filters__select { min-width: 0; width: 100%; }


/* --- La marque sur l'écran racine --------------------------------------- */

/* Le logo ne paraît que sur l'accueil, à la place du titre : « Accueil » sous
   un onglet Accueil déjà surligné ne dit rien que l'écran ne dise déjà, alors
   que la marque, elle, manque. Sur les autres onglets c'est l'inverse, et le
   titre reste. */
/* Le logo occupe la place du titre, donc il se centre comme lui : d'un onglet à
   l'autre, le repère reste au même endroit de l'écran, et seul son contenu
   change. Calé à gauche, il faisait sautiller le point de lecture à chaque
   changement d'onglet. */
.app-top__logo { display: none; align-items: center; justify-content: center; height: 44px; flex: 1 1 auto; min-width: 0; }
/* .is-app et data-depth sont portés par le MÊME élément, <html> : ils se
   chaînent sans espace. Écrits comme deux sélecteurs descendants, ils ne
   correspondaient à rien. */
html.is-app[data-depth="root"] .app-top__logo { display: flex; }
html.is-app[data-depth="root"] .app-top__title { display: none; }

.app-top__logo img { height: 24px; width: auto; display: block; }

/* Le logo est à l'ENCRE, toujours. Il a été blanc tant que l'app s'ouvrait sur
   le hero sombre ; depuis que l'accueil commence par l'agenda, le fond est du
   papier clair et le logo blanc y était illisible — on ne voyait plus que le
   point rose. La bascule au défilement ne pouvait pas le rattraper : le défaut
   était là dès l'arrivée, avant tout défilement.

   Plutôt que de basculer entre deux versions selon ce qui se trouve derrière —
   ce qu'un sélecteur CSS ne peut pas savoir ici, `:has()` ne s'imbriquant pas —
   on rend le fond uniformément clair (voir le hero sans visuel, plus bas). Une
   seule version du logo, lisible partout, et aucun état à tenir.

   Le sélecteur porte « img » parce que la règle de dimension au-dessus vise
   « .app-top__logo img » : plus spécifique, elle gardait display:block sur les
   deux versions, qui s'affichaient côte à côte — 134 px de large au lieu de 67. */
.app-top__logo img.app-top__logo--clair { display: none; }
.app-top__logo img.app-top__logo--encre { display: block; }

/* La signature « Le Club » en tête de page répétait mot pour mot le titre de la
   barre, qui affiche déjà « Le Club » : deux étiquettes identiques à 40 px
   d'écart, plus le nom repris dans le grand titre juste en dessous. Sur le web
   elle a du sens — aucune barre ne nomme l'écran — mais dans l'app c'est un
   doublon strict. Même raisonnement que pour les surtitres de .acct-head.

   Le grand titre, lui, reste : « Le Club, ton espace entre deux lives » est une
   phrase qui dit ce qu'on y fait, pas une étiquette qui se répète. */
.is-app .club-badge { display: none; }

/* ===========================================================================
   L'ACCUEIL DE L'APP N'A PAS L'ORDRE DE L'ACCUEIL DU SITE
   ---------------------------------------------------------------------------
   Le site s'adresse à quelqu'un qu'il faut convaincre : il ouvre sur une
   vitrine — la mosaïque de lives — qui montre la variété et la qualité de ce
   qu'on trouve ici. L'app s'adresse à quelqu'un qui est déjà venu la chercher
   et qui vient pour une raison précise : voir ce qui passe.

   La mosaïque est donc retirée, et les sections reprennent l'ordre d'usage :
   l'agenda d'abord, puis ce qu'on gagne, puis les vidéos, puis les replays.
   Rien n'est supprimé du site, qui garde sa vitrine et son ordre.
   =========================================================================== */

/* La vitrine part, et le hero avec elle quand il ne portait qu'elle ou une
   affiche : les lives en cours ouvrent désormais l'agenda, juste en dessous,
   avec leur bouton. Le hero SURVIT dans un seul cas, celui où il ne contient ni
   vitrine ni affiche — il porte alors le formulaire de rappel, qui est la seule
   chose utile un jour sans live. */
/* La vitrine part toujours : c'est une mosaïque de démonstration, elle répond à
   « qu'est-ce qu'on trouve ici ? », une question qui ne se pose plus dans l'app. */
.is-app .hero:has(.showcase) { display: none !important; }

/* L'affiche part AUSSI — sauf quand elle porte un live EN COURS.
   La règle précédente la masquait dans tous les cas, au motif que « les lives en
   cours ouvrent l'agenda juste en dessous ». C'était faux : l'agenda ne liste
   que les lives À VENIR, il n'a pas de rubrique « en cours ». L'app affichait
   donc une pastille rouge et un compteur sur son icône pour annoncer un direct
   que l'accueil ne permettait pas de rejoindre. Le seul élément qui le portait
   était justement celui qu'on masquait. */
.is-app .hero:has(.stage):not(:has(.pc-tag--live)) { display: none !important; }

/* L'ordre d'usage. `order` plutôt qu'un remaniement du gabarit : le site garde
   son ordre, l'app impose le sien, et une seule source de HTML sert les deux. */
.is-app main#main { display: flex; flex-direction: column; }

/* Un temps fort passe devant TOUT. C'est une opération exceptionnelle, avec un
   gros lot et une durée courte : la placer après les replays la rendrait
   pratiquement invisible dans l'app. Elle ne coûte rien le reste du temps,
   puisque la section n'est rendue que lorsqu'une opération est active — et il
   n'y en a aucune la plupart des semaines. */
.is-app .cadeau-sec.is-special { order: 0; }

.is-app .hero            { order: 1; } /* formulaire de rappel, jour sans live */
.is-app #agenda          { order: 2; } /* L'agenda des lives */
.is-app .cadeau-sec.sel  { order: 3; } /* Privilège membre */
.is-app #questions       { order: 4; } /* Spot! en 30 secondes */
.is-app #replays         { order: 5; } /* Replays */

/* Les lots ferment la marche : ils redisent, en liste, ce que les cartes de
   l'agenda portent déjà live par live. */
.is-app #jeu                   { order: 6; }


/* Le hero des jours sans live passe au papier. Son fond à l'encre existait pour
   faire valoir une affiche de live ; sans affiche il ne reste qu'un formulaire
   de rappel, et l'encre ne fait plus qu'assombrir l'écran d'ouverture — tout en
   obligeant le logo à changer de version selon ce qu'il surmonte. Un fond clair
   partout, c'est un logo unique et un écran cohérent avec le reste de l'app. */
.is-app .hero:not(:has(.showcase, .stage)) {
  background: var(--paper);
  color: var(--ink);
}


/* Les trois étapes décrivent le menu Partager de Safari : elles n'ont aucun sens
   ailleurs. Sur Android et sur ordinateur, l'installation passe par l'invite
   native du navigateur, et c'est le bouton « Installer » qui la déclenche —
   montrer « Touche Partager en bas de Safari » à quelqu'un sur Mac était une
   consigne impossible à suivre. */
html:not(.is-ios) .app-install__steps { display: none; }
html:not(.is-ios) .app-install__head { margin-bottom: 4px; }
