/* ============================================================
   HÏGHT CARNAC, application clients.
   Jetons de style repris de style.css du site : mêmes couleurs,
   mêmes polices, mêmes courbes. Ce qui change tient aux codes
   d'application, pas à la direction artistique.
   ============================================================ */
:root{
  color-scheme: dark;
  --bg-primary:#050508; --bg-secondary:#0c0c14; --bg-tertiary:#111119;
  --bg-glass:rgba(255,255,255,.03); --bg-glass-hover:rgba(255,255,255,.06);
  --bg-glass-strong:rgba(255,255,255,.08);
  --accent-primary:#9333ea; --accent-secondary:#a855f7; --accent-light:#c084fc; --accent-ultra:#d8b4fe;
  --fidelite:#a855f7; --fidelite-clair:#d8b4fe; --fidelite-fonce:#9333ea;
  --text-primary:#fff; --text-secondary:rgba(255,255,255,.70);
  --text-muted:rgba(255,255,255,.40); --text-faint:rgba(255,255,255,.20);
  --border-subtle:rgba(255,255,255,.08); --border-light:rgba(255,255,255,.12);
  --border-accent:rgba(147,51,234,.40);
  --font-heading:'Cormorant Garamond',Georgia,serif;
  --font-body:'Inter','Helvetica Neue',Arial,sans-serif;
  --ease-luxury:cubic-bezier(.25,.46,.45,.94);
  --ease-spring:cubic-bezier(.34,1.56,.64,1);
  --radius-md:8px; --radius-lg:16px; --radius-xl:24px;
  --shadow-btn:0 4px 20px rgba(147,51,234,.35);
  --tabbar-h:72px;
}
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
[hidden] { display: none !important; }
html{background:#08080d;-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased;height:100%}
body{
  font-family:var(--font-body); font-weight:300; color:var(--text-secondary);
  background:#08080d; height:100%; overflow:hidden;
}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;-webkit-tap-highlight-color:transparent}
img{max-width:100%;display:block}
::selection{background:var(--accent-primary);color:#fff}

/* ---------- Coquille de l'application ---------- */
body{display:flex;justify-content:center;background:#08080d}
.app{position:relative;width:100%;max-width:480px;height:100%;display:flex;
  flex-direction:column;overflow:hidden;background:var(--bg-primary)}
@media (min-width:900px){
  body{padding:24px 0;background:radial-gradient(1200px 700px at 50% -10%,
       rgba(147,51,234,.13),transparent 60%),#08080d}
  .app{max-width:430px;height:calc(100dvh - 48px);border:1px solid var(--border-light);
    border-radius:38px;box-shadow:0 40px 120px rgba(0,0,0,.7),0 0 60px rgba(147,51,234,.10)}
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;transition-duration:.01ms !important}
}
:focus-visible{outline:2px solid var(--accent-light);outline-offset:2px;border-radius:4px}

/* ---------- Écrans ---------- */
/* `overflow-x:hidden` et pas seulement `overflow-y:auto`.
   ---------------------------------------------------------------
   Des qu'un axe n'est plus `visible`, l'autre passe a `auto` : la vue
   gagnait donc un defilement horizontal qu'on ne lui avait pas demande.
   Les marges negatives du rail de la galerie le remplissaient, et toute
   la page se mettait a deriver de cote sous le doigt — c'est ce qui
   rendait le defilement « bizarre » bien au-dela de la galerie. */
.view{display:none;flex:1;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;
  padding:env(safe-area-inset-top, 0px) 20px calc(var(--tabbar-h) + 28px + env(safe-area-inset-bottom, 0px));-webkit-overflow-scrolling:touch}
/* Le fondu garde son opacite mais perd sa translation, et raccourcit.
   Une vue qui remonte de huit pixels a chaque changement d'onglet se
   voit surtout quand on revient sur un ecran deja fait defiler : le
   contenu glisse une fois de plus, sans raison. */
.view.on{display:block;animation:fade .22s var(--ease-luxury)}
@keyframes fade{from{opacity:0}to{opacity:1}}
.view::-webkit-scrollbar{width:0}

/* ---------- Connexion ----------

   Pas de photo en fond : le formulaire d'inscription fait six champs, et
   du texte clair sur un sous-bois n'est pas lisible. On garde la lueur
   violette de la marque, qui ne gêne aucun texte.

   Le défilement n'est pas un détail. En justify-content:flex-end, un
   contenu plus haut que l'écran déborde vers le haut et devient
   inatteignable, sans barre de défilement : le premier champ du
   formulaire disparaissait purement et simplement. Le margin-top:auto
   sur le premier enfant donne le même alignement en bas, tout en
   laissant le débordement défiler normalement.

   Ce qui est bloqué, c'est le REBOND, pas le défilement. Mesuré : le
   contenu fait 850 px. Sur un écran de 915 px il tient tout entier et
   rien ne bouge — mais dès qu'un champ reçoit le focus, le clavier
   virtuel prend la moitié de la hauteur, et il reste 350 px de contenu
   sous la ligne de flottaison, bouton « Créer mon compte » compris.
   `overflow:hidden` ici rendrait ce bouton inatteignable au moment
   précis où l'on en a besoin.

   `overscroll-behavior:contain` retire l'élastique de fin de course et
   empêche le geste de se propager à la page derrière : plus rien ne
   bouge quand il n'y a rien à faire défiler, et le défilement reste là
   quand il sert. */
#login{position:absolute;inset:0;z-index:60;display:flex;flex-direction:column;
  align-items:center;justify-content:flex-start;overflow-y:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  background:#08080d url('/img/poster2.webp') center/cover no-repeat;scrollbar-width:none}
#login::-webkit-scrollbar{display:none}
#login.gone{display:none}
/* opacity:0 par defaut : le temps que la lecture demarre vraiment, l'ecran
   reste sur le fond sombre de #login plutot que de laisser voir le flash
   du bouton lecture natif le temps que le flux se decode. Le script en
   bas de index.html bascule .pret des que la video joue reellement. */
.login-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;pointer-events:none;
  opacity:0;transition:opacity .5s ease}
.login-video.pret{opacity:1}
.login-video::-webkit-media-controls { display: none !important; }
.login-video::-webkit-media-controls-start-playback-button { display: none !important; }
.login-video::-webkit-media-controls-overlay-play-button { display: none !important; }
.login-video::-webkit-media-controls-panel { display: none !important; }
.login-overlay{position:absolute;inset:0;z-index:1;
  background:linear-gradient(to bottom,
    rgba(5,5,8,.30) 0%,
    rgba(5,5,8,.55) 50%,
    rgba(5,5,8,.90) 100%)}
/* Le padding du bas prend env(safe-area-inset-bottom) comme celui du haut
   prend l'inset du haut. Sans lui, « Créer mon compte » et « J'ai déjà un
   compte » — poussés en bas de carte par le margin-top:auto de
   #etapeAccueil — finissaient sous la barre de gestes Android, où le
   système avale le toucher : les boutons paraissaient coupés et ne
   répondaient pas. */
.login-content{position:relative;z-index:2;width:100%;max-width:400px;
  padding:calc(clamp(20px, 5vh, 40px) + env(safe-area-inset-top, 0px)) clamp(18px, 6vw, 28px)
          calc(clamp(24px, 5vh, 44px) + env(safe-area-inset-bottom, 0px));
  display:flex;flex-direction:column;align-items:center;
  text-align:center;margin-top:clamp(0px, 5vh, 44px);margin-bottom:auto;flex:1}
.login-content .login-brand,.login-content .login-sub{text-align:center}
/* flex column + min-height:0 : #etapeAccueil peut s'y pousser vers le bas
   avec margin-top:auto (voir index.html) sans jamais dépasser l'écran —
   contrairement à un margin-top en vh fixe, l'auto-margin flexbox se
   réduit tout seul si le contenu (le formulaire d'inscription, par ex.)
   est déjà trop grand, et #login reste alors scrollable comme prévu. */
.login-card{width:100%;margin-top:clamp(14px, 3vh, 24px);text-align:left;flex:1;display:flex;flex-direction:column;min-height:0}
/* Le nom de la marque passe sur deux lignes et porte l'écran : à taille
   fixe, il débordait en largeur sous 360 px et flottait perdu au-dessus
   de 430 px. Le clamp le garde proportionné à la largeur réelle, entre
   les deux bornes où il reste lisible. */
.login-brand{font-family:var(--font-heading);font-size:clamp(2.3rem, 11vw, 3.4rem);font-weight:400;color:#fff;
  letter-spacing:.1em;line-height:1;margin:0 0 6px}
.login-sub{color:var(--text-muted);font-size:clamp(.72rem, 2.6vw, .82rem);letter-spacing:.16em;
  text-transform:uppercase;margin-bottom:clamp(16px, 4.5vh, 34px)}
/* Écrans courts (petits téléphones, et surtout clavier ouvert sur l'étape
   inscription) : la marque mange la place des six champs. On la réduit
   plutôt que de laisser le formulaire sortir de l'écran. */
@media (max-height:700px){
  .login-brand{font-size:clamp(1.9rem, 8vw, 2.6rem)}
  .login-sub{margin-bottom:14px}
}
/* Paysage : la hauteur disponible tombe sous 400 px, le bloc de marque
   n'a plus lieu d'être aussi haut. */
@media (orientation:landscape) and (max-height:520px){
  .login-content{margin-top:0;padding-top:calc(12px + env(safe-area-inset-top, 0px))}
  .login-brand{font-size:1.8rem;margin-bottom:2px}
  .login-sub{margin-bottom:10px}
  .login-card{margin-top:10px}
}
.login-pitch{color:var(--text-secondary);font-size:.94rem;line-height:1.7;margin-bottom:26px}
.btn{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;
  padding:16px 18px;border-radius:var(--radius-md);font-size:.88rem;font-weight:500;
  letter-spacing:.04em;text-decoration:none;transition:transform .18s var(--ease-luxury),opacity .18s}
.btn:active{transform:scale(.975)}
.btn-primary{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));
  color:#fff;box-shadow:var(--shadow-btn)}
.btn-ghost{background:var(--bg-glass);border:1px solid var(--border-light);color:var(--text-primary)}
.btn-fidelite{background:linear-gradient(135deg,var(--fidelite),var(--fidelite-fonce));color:#fff;font-weight:600}
.btn-danger{background:rgba(239,68,68,.16);border:1px solid rgba(239,68,68,.5);color:#fca5a5;font-weight:600}
/* Le pendant de btn-danger, pour le geste qui rouvre une porte plutot
   que d'en fermer une. Sur la fiche membre, un seul bouton bascule entre
   « Suspendre » et « Reactiver » : la couleur dit lequel des deux est
   sous le pouce, avant meme d'avoir lu le libelle. */
.btn-ouvrir{background:rgba(34,197,94,.16);border:1px solid rgba(34,197,94,.5);color:#86efac;font-weight:600}
.btn:disabled{opacity:.38;cursor:not-allowed}
.btn--sm{padding:11px 16px;font-size:.78rem;width:auto}
.sep{display:flex;align-items:center;gap:14px;margin:18px 0;color:var(--text-faint);
  font-size:.7rem;letter-spacing:.2em;text-transform:uppercase}
.sep::before,.sep::after{content:"";flex:1;height:1px;background:var(--border-subtle)}
.field{width:100%;padding:15px 16px;background:rgba(5,5,8,0.85);border:1px solid var(--border-light);
  border-radius:var(--radius-md);color:#fff;font-size:.88rem;margin-bottom:12px;font-family:inherit}
.field:focus{outline:none;border-color:var(--border-accent);background:var(--bg-glass-hover)}
.field::placeholder{color:var(--text-faint)}
/* iOS : un input date garde une largeur minimale propre au WebKit et
   deborde de la colonne. On le ramene a la largeur du champ. */
input.field[type="date"]{-webkit-appearance:none;appearance:none;display:block;box-sizing:border-box;min-width:0;max-width:100%;min-height:52px}
input.field[type="date"]::-webkit-date-and-time-value{text-align:left}
/* Téléphone : l'indicatif est posé, pas saisi. Une seule forme entre en
   base, et personne ne se demande s'il faut mettre le 0. */
.tel-groupe{display:flex;align-items:stretch;margin-bottom:12px}
.tel-groupe .tel-prefixe{display:flex;align-items:center;padding:0 14px;
  background:rgba(5,5,8,0.85);border:1px solid var(--border-light);border-right:0;
  border-radius:var(--radius-md) 0 0 var(--radius-md);color:var(--text-secondary);
  font-size:.88rem;font-variant-numeric:tabular-nums;user-select:none}
.tel-groupe .field{margin-bottom:0;border-radius:0 var(--radius-md) var(--radius-md) 0}
.tel-groupe:focus-within .tel-prefixe{border-color:var(--border-accent)}
/* Six chiffres lus dans le noir, à bout de bras : gros, espacés, centrés. */
.champ-code{text-align:center;font-size:1.9rem;letter-spacing:.42em;text-indent:.42em;
  padding:18px 16px;font-variant-numeric:tabular-nums}
.champ-titre{display:block;font-size:.72rem;text-transform:uppercase;letter-spacing:.12em;
  color:#fff;margin:6px 0 7px}
/* Cases à cocher : la zone tactile couvre tout le texte, pas seulement
   le petit carré. Dans le noir et en marchant, viser 16 px est perdu. */
.case{display:flex;gap:11px;align-items:flex-start;margin:12px 0;cursor:pointer;
  font-size:.76rem;line-height:1.6;color:var(--text-muted)}
.case input{flex-shrink:0;width:19px;height:19px;margin-top:1px;accent-color:#9333ea}
.login-legal{margin-top:22px;font-size:.68rem;line-height:1.7;color:var(--text-faint);text-align:center}
.lien-legal{color:var(--accent-ultra);text-decoration:underline;text-underline-offset:2px}

/* ---------- Bandeau hors ligne ----------
   Au dessus de la topbar, donc il prend le padding de l'encoche a sa
   place : sans ca, le bandeau passerait sous la barre d'etat du telephone.
   La topbar garde le sien, elle sert seule quand le reseau va bien. */
.bandeau-reseau{display:flex;align-items:center;gap:9px;flex-shrink:0;z-index:21;
  padding:calc(9px + env(safe-area-inset-top, 0px)) 20px 9px;
  background:rgba(234,179,8,.13);border-bottom:1px solid rgba(234,179,8,.32);
  color:#fde68a;font-size:.78rem;font-weight:500;line-height:1.35}
.bandeau-reseau[hidden]{display:none}
.bandeau-reseau svg{width:16px;height:16px;flex-shrink:0}
/* Quand le bandeau est la, la topbar n'a plus a redoubler l'encoche. */
.bandeau-reseau:not([hidden]) + .topbar{padding-top:12px}

/* ---------- Mode carte seule ----------
   Sans reseau, l'app se reduit a ce qui marche encore : le QR, les points
   et la jauge, tous calcules en local depuis le cache.

   Ce qui disparait disparait pour une raison, pas pour faire propre :
   « Ce soir » et « Agenda » afficheraient une programmation peut-etre
   perimee, « Profil » ne peut rien enregistrer, « Mes recompenses »
   promet un bouton qui ne partira pas, et l'historique date de la
   derniere synchro. Mieux vaut ne rien montrer que montrer faux.

   Tout est en CSS, sans toucher au DOM : au retour du reseau, retirer la
   classe suffit a tout ramener, sans rien avoir a reconstruire. */
.note-carte-seule{display:none}
body.mode-carte .note-carte-seule{display:block;margin:0 0 14px;
  padding:11px 14px;border-radius:var(--radius-md);
  background:rgba(234,179,8,.10);border:1px solid rgba(234,179,8,.28);
  color:#fde68a;font-size:.8rem;line-height:1.5}

/* Un seul onglet reste : il s'etale, `flex:1` s'en charge tout seul. */
body.mode-carte .tab:not([data-tab="card"]){display:none}

body.mode-carte [data-view="card"] .sec-title,
body.mode-carte [data-view="card"] .sec-sub,
body.mode-carte [data-view="card"] .earn-list,
body.mode-carte #rewards,
body.mode-carte #history{display:none}

/* ---------- Barre du haut ---------- */
.topbar{display:flex;align-items:center;justify-content:space-between;
  padding:calc(14px + env(safe-area-inset-top, 0px)) 20px 12px;border-bottom:1px solid var(--border-subtle);
  /* Fond plein, sans `backdrop-filter`.
     -------------------------------------------------------------------
     Un flou d'arriere-plan oblige le WebView a relire tout ce qui passe
     dessous, image par image, pendant que la page defile : c'est lui qui
     retardait le dessin des vignettes, qui apparaissaient donc une
     fraction de seconde apres le doigt. A 86 % d'opacite il ne se voyait
     de toute facon presque pas — le fond opaque rend la meme barre, sans
     la relecture. */
  background:#050508;z-index:20;flex-shrink:0}
.topbar.hide{display:none}
.tb-logo{font-family:var(--font-heading);font-size:1.42rem;color:#fff;letter-spacing:.09em;text-transform:uppercase}
.pts-pill{display:flex;align-items:center;gap:7px;padding:7px 13px;border-radius:999px;
  background:rgba(168,85,247,.10);border:1px solid rgba(168,85,247,.32);
  color:var(--fidelite-clair);font-size:.76rem;font-weight:600;letter-spacing:.03em}
.pts-pill svg{width:13px;height:13px}

/* ---------- Blocs communs ---------- */
.sec-title{font-family:var(--font-heading);font-size:1.55rem;color:#fff;font-weight:400;
  letter-spacing:.02em;margin:26px 0 4px}
.sec-sub{font-size:.76rem;color:var(--text-muted);letter-spacing:.13em;text-transform:uppercase;margin-bottom:16px}
/* Même en-tête de section, mais cliquable : le chevron dit qu'il y a une
   suite, et le titre garde exactement la typographie des autres sections. */
.sec-link{display:flex;align-items:center;justify-content:space-between;gap:12px;
  width:100%;background:none;border:0;padding:0;text-align:left;color:inherit}
.sec-link .sec-title,.sec-link .sec-sub{display:block}
.sec-link .chev{flex-shrink:0;margin-top:6px}
.card{background:var(--bg-glass);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);
  overflow:hidden;transition:border-color .3s var(--ease-luxury)}
.pad{padding:18px}
.muted{color:var(--text-muted);font-size:.8rem;line-height:1.65}
.tiny{font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;color:var(--text-muted)}

/* ---------- Ce soir ---------- */
/* Affiche entière, texte à la suite, rien par-dessus.

   Toutes les variantes superposées ont échoué pour la même raison : quoi
   qu'on mette sur l'affiche — dégradé translucide ou panneau opaque — on
   lui coupe le bas, et elle se lit comme une image tranchée en deux avec
   du noir dessous. Le bloc de texte est donc dans le flux normal : la
   carte est une affiche, puis une légende, et l'affiche est intacte.

   Effet de bord bienvenu : plus rien ne dépend de la hauteur du bloc de
   texte, donc plus rien à ajuster quand un titre passe sur deux lignes. */
.hero{border-radius:var(--radius-xl);overflow:hidden;margin-top:18px;
  border:1px solid var(--border-light);background:var(--bg-primary)}
/* Pas de recadrage : l'affiche garde ses proportions d'origine. Les visuels
   de la billetterie sont carrés (2000x2000, vérifié sur les fichiers servis),
   et c'est src/xceed.js qui garantit qu'on stocke bien le recadrage 1:1 et
   non la bannière 16:9. contain le garantit même si la source change.

   « aspect-ratio: auto 1/1 » se lit « le ratio naturel s'il est connu, 1/1
   sinon » : sans ce repli, la carte se replie sur elle-même le temps que
   l'affiche arrive, puis tout saute d'un coup à son arrivée.

   max-height n'est qu'un garde-fou pour une affiche anormalement haute. */
.hero img{width:100%;height:auto;aspect-ratio:auto 1/1;max-height:72vh;object-fit:contain}
.hero-body{padding:18px 20px}
/* Mêmes 20 px que .hero-body : le bouton s'aligne sur le titre au pixel.
   padding-top réduit, les 18 px du bas de .hero-body s'y ajoutant déjà. */
.hero-cta{padding:0 20px 18px}
.badge-live{display:inline-flex;align-items:center;gap:7px;padding:6px 12px;border-radius:999px;
  background:rgba(147,51,234,.22);border:1px solid var(--border-accent);
  color:var(--accent-ultra);font-size:.66rem;font-weight:600;letter-spacing:.18em;text-transform:uppercase}
.dot{width:6px;height:6px;border-radius:50%;background:var(--accent-light);animation:pulse 2.6s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.85)}}
/* Le point respire, il ne clignote pas : coupé net pour qui a désactivé les
   animations dans les réglages du téléphone. */
@media (prefers-reduced-motion:reduce){.dot{animation:none}}
.hero-title{font-family:var(--font-heading);font-size:clamp(1.6rem,7.5vw,2.5rem);line-height:1.08;color:#fff;
  font-weight:400;margin:12px 0 6px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden}
.hero-meta{font-size:.78rem;color:var(--text-secondary);letter-spacing:.05em;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  text-wrap:balance}
.earn-strip{display:flex;align-items:center;gap:9px;margin-top:12px;padding:13px 13px;
  border-radius:var(--radius-md);background:rgba(168,85,247,.07);border:1px solid rgba(168,85,247,.22)}
.earn-strip b{color:var(--fidelite-clair);font-weight:600}
.earn-suite{white-space:nowrap}
/* Une seule ligne, sur toute la largeur d'écran.

   La phrase demandait 276 px pour 273 disponibles sur un écran de 375 :
   trois pixels de trop. `.earn-suite` insécable renvoyait donc la seconde
   moitié entière à la ligne, au niveau de la barre verticale — deux
   lignes propres, mais deux lignes.

   Les trois pixels se récupèrent sans toucher au texte : la gouttière
   passe de 11 à 9 px et le rembourrage horizontal de 15 à 13, ce qui rend
   6 px. De quoi tenir à 375 px sans rien réduire.

   En dessous, la largeur manque quand même, d'où le `min()` : la police
   suit la largeur de l'écran (3,05 vw ≈ 11 px sur un 360) tant que
   celle-ci est inférieure à la taille nominale, et s'y arrête au delà.
   Un écran large garde donc exactement le rendu d'avant.

   `nowrap` sur le conteneur et non plus sur la seule seconde moitié :
   c'est l'espace entre « points » et « | » qui cassait la ligne, et lui
   seul restait sécable. */
.earn-strip span{font-size:.76rem;font-size:min(.76rem, 3.05vw);
  line-height:1.55;color:var(--text-secondary);white-space:nowrap}
.rowlink{display:flex;align-items:center;justify-content:space-between;gap:14px;width:100%;
  padding:16px 18px;text-align:left;transition:background .25s}
.rowlink:active{background:var(--bg-glass-hover)}
.rowlink + .rowlink{border-top:1px solid var(--border-subtle)}
.chev{width:16px;height:16px;flex-shrink:0;stroke:var(--text-faint);fill:none;stroke-width:2}
.rail{display:flex;gap:12px;overflow-x:auto;margin:0 -20px;padding:2px 20px 6px;scrollbar-width:none}
.rail::-webkit-scrollbar{height:0}
/* Une carte du carrousel est la carte du haut en plus petit : mêmes bordure,
   arrondi et fond, affiche entière (contain, jamais rognée) puis le texte en
   dessous dans le flux. Seule l'échelle typographique change. */
/* display:flex plutôt que block : le carrousel étire les cartes à la hauteur
   de la plus haute, et un <button> en bloc recentre alors son contenu — les
   affiches se retrouvaient décalées vers le bas, chacune d'un cran différent. */
.mini{width:min(74vw,268px);flex-shrink:0;display:flex;flex-direction:column;
  text-align:left;padding:0;
  border-radius:var(--radius-xl);overflow:hidden;background:var(--bg-primary);
  border:1px solid var(--border-light);transition:border-color .3s var(--ease-luxury)}
.mini:active{border-color:var(--border-accent)}
.mini img,.mini-vide{display:block;flex:none;width:100%;height:auto;aspect-ratio:auto 1/1;object-fit:contain}
.mini-vide{background:var(--bg-tertiary)}
.mini-body{display:block;padding:13px 15px 15px}
.mini-d{display:block;font-size:.66rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--accent-light);margin-bottom:5px}
.mini-t{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  font-family:var(--font-heading);font-size:1.18rem;line-height:1.14;color:#fff;font-weight:400}
.mini-c{display:block;font-size:.72rem;color:var(--text-secondary);letter-spacing:.05em;margin-top:5px}
.mini-c:empty{display:none}

/* ---------- Agenda ---------- */
.chips{display:flex;gap:8px;overflow-x:auto;margin:16px -20px 6px;padding:0 20px 4px;scrollbar-width:none}
.chips::-webkit-scrollbar{height:0}
.chip{flex-shrink:0;padding:8px 15px;border-radius:999px;border:1px solid var(--border-light);
  background:var(--bg-glass);font-size:.75rem;color:var(--text-secondary);white-space:nowrap;transition:all .25s}
.chip.on{background:rgba(147,51,234,.18);border-color:var(--border-accent);color:var(--accent-ultra)}
.month{font-size:.7rem;letter-spacing:.22em;text-transform:uppercase;color:var(--text-faint);
  margin:24px 0 12px;padding-bottom:9px;border-bottom:1px solid var(--border-subtle)}
.ev{display:flex;align-items:center;gap:14px;width:100%;padding:11px 0;text-align:left}
.ev:active{opacity:.6}
.ev-date{width:50px;flex-shrink:0;text-align:center}
.ev-day{font-family:var(--font-body);font-weight:700;font-variant-numeric:tabular-nums;font-size:1.5rem;color:#fff;line-height:1}
.ev-mon{font-size:.6rem;letter-spacing:.16em;text-transform:uppercase;color:var(--accent-light);margin-top:2px}
.ev img{width:58px;height:58px;object-fit:cover;border-radius:var(--radius-md);flex-shrink:0}
.ev-txt{flex:1;min-width:0}
.ev-t{font-size:.94rem;color:#fff;font-weight:500;margin-bottom:3px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ev-c{font-size:.7rem;color:var(--text-muted);letter-spacing:.05em}

/* ---------- Ma carte ---------- */
.mcard{margin-top:18px;border-radius:var(--radius-xl);padding:22px;position:relative;overflow:hidden;
  background:linear-gradient(145deg,#1b1230 0%,#130f22 46%,#0b0912 100%);
  border:1px solid rgba(168,85,247,.34);box-shadow:0 0 50px rgba(168,85,247,.10)}
.mcard::after{content:"";position:absolute;top:-60%;right:-30%;width:220px;height:220px;
  background:radial-gradient(circle,rgba(168,85,247,.16),transparent 70%);pointer-events:none}
.mcard-head{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:18px;
  position:relative;z-index:1}
.mcard-head>div:first-child{flex:1;min-width:0;container-type:inline-size}
.mcard-name{font-family:var(--font-heading);font-size:1.5rem;color:#fff;line-height:1.15;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
/* « Membre depuis septembre 2026 » tient sur une ligne, toujours.

   Elle passait a la ligne des que le mois etait long — septembre,
   novembre, decembre — et la carte se retrouvait avec un nom sur une
   ligne et sa date sur deux. Le coupable etait surtout le
   letter-spacing de .14em : sur 28 caracteres, il ajoutait a lui seul
   un cinquieme de la largeur.

   La taille se mesure en cqw, soit la largeur de la colonne de gauche
   (celle qui partage la ligne avec la pastille de palier), et non en vw
   : c'est cette colonne qui contraint le texte, pas l'ecran. Le `min`
   garde .62rem comme plafond — la ligne ne grossit pas sur grand
   ecran, elle se contente de ne jamais deborder sur petit.

   Le `font-size` juste au-dessus est le repli des moteurs qui ignorent
   les unites de conteneur : une ligne un peu serree vaut mieux qu'une
   valeur ignoree et un texte rendu a la taille heritee. */
.mcard-since{font-size:.58rem;font-size:min(.62rem,5cqw);letter-spacing:.07em;
  text-transform:uppercase;color:rgba(168,85,247,.72);margin-top:3px;white-space:nowrap}
.tier-tag{padding:5px 11px;border-radius:999px;border:1px solid rgba(168,85,247,.42);
  background:rgba(168,85,247,.10);color:var(--fidelite-clair);font-size:.62rem;
  letter-spacing:.15em;text-transform:uppercase;font-weight:600;white-space:nowrap;flex-shrink:0;margin-left:10px}
/* La carte saisonnière ou étudiante, sur la carte de fidélité. Ni l'un
   ni l'autre en violet : le palier se gagne, celle-ci se prouve à
   l'accueil, et les deux ne doivent pas se ressembler. Une couleur par
   type, la même qu'au poste de l'équipe. */
.mcard-carte{display:inline-block;margin-top:8px;padding:4px 10px;border-radius:999px;
  border:1px solid rgba(56,189,248,.42);background:rgba(56,189,248,.12);color:#7dd3fc;
  font-size:.62rem;letter-spacing:.14em;text-transform:uppercase;font-weight:600}
.mcard-carte.type-etudiant{border-color:rgba(52,199,89,.45);background:rgba(52,199,89,.14);color:#7ee29a}
.mcard-carte.type-vip{border-color:rgba(234,179,8,.48);background:rgba(234,179,8,.15);color:#fde047}
.mcard-carte.type-admin{border-color:rgba(239,68,68,.5);background:rgba(239,68,68,.16);color:#fca5a5}
/* Le statut de carte sur le QR plein ecran.

   Les couleurs de .mcard-carte sont calibrees pour le fond noir de
   l'app : posees sur le blanc du plein ecran, un jaune a 15 % et un
   vert clair deviennent illisibles. On reprend donc les memes teintes,
   assombries jusqu'a tenir sur blanc — c'est la meme information, lue
   dans l'autre sens. */
.qrplein-carte{display:inline-block;margin-top:10px;padding:5px 14px;border-radius:999px;
  border:1px solid rgba(2,132,199,.45);background:rgba(2,132,199,.12);color:#0369a1;
  font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;font-weight:700}
.qrplein-carte.type-etudiant{border-color:rgba(21,128,61,.45);background:rgba(21,128,61,.12);color:#15803d}
.qrplein-carte.type-vip{border-color:rgba(161,98,7,.5);background:rgba(202,138,4,.16);color:#a16207}
.qrplein-carte.type-admin{border-color:rgba(185,28,28,.5);background:rgba(220,38,38,.12);color:#b91c1c}

/* Le choix du genre, a l'inscription.

   Les trois tiennent sur UNE ligne, a parts egales : c'est un choix
   unique entre trois options, et les repartir sur deux lignes donnait
   l'impression que la troisieme appartenait a autre chose.

   Le texte est le contrainte : « Non-binaire » est deux fois plus long
   que « Femme ». D'ou la police reduite, le bouton radio ramene a 13 px
   et les marges serrees — le tout calibre pour tenir a 360 px de large,
   le plus petit ecran courant. */
.genre{border:0;padding:0;margin:0 0 4px;display:flex;gap:6px}
.genre-choix{flex:1;display:inline-flex;align-items:center;justify-content:center;
  gap:5px;margin-top:8px;padding:9px 6px;border-radius:999px;cursor:pointer;
  background:var(--bg-glass);border:1px solid var(--border-subtle);
  color:var(--text-secondary);font-size:.76rem;white-space:nowrap;
  transition:border-color .18s,color .18s,background .18s}
.genre-choix:hover{color:#fff}
.genre-choix input{accent-color:var(--accent-primary);margin:0;flex:none;
  width:13px;height:13px}
.genre-choix:has(input:checked){border-color:var(--border-accent);color:#fff;
  background:rgba(147,51,234,.14)}

.qr-box{width:100%;border:0;cursor:pointer;background:#fff;border-radius:var(--radius-lg);padding:15px;display:flex;justify-content:center;
  position:relative;z-index:1}
.qr-box canvas{width:100%;max-width:196px;height:auto;image-rendering:pixelated}
/* ---------- Le bon de récompense en attente ----------

   Il ne s'affiche plus sous la carte : la même récompense porte déjà son
   bouton « Voir le code » dans la liste plus bas, et deux entrées pour
   un seul bon encombraient le premier écran.

   Une exception, et une seule : le mode hors ligne masque cette liste
   (voir body.mode-carte plus haut). Au sous-sol, sans réseau, c'est le
   seul endroit qui reste pour sortir son bon — il réapparaît donc là,
   et nulle part ailleurs. */
#pendingBox{display:none}
body.mode-carte #pendingBox{display:block}
.bon-ligne{display:flex;align-items:center;gap:12px;margin-top:14px;padding:14px 16px;
  border-radius:var(--radius-lg);background:var(--bg-glass);
  border:1px solid var(--border-accent)}
.bon-txt{flex:1;min-width:0}
.bon-t{color:#fff;font-size:.92rem;font-weight:500;line-height:1.3}
.bon-d{color:var(--text-muted);font-size:.72rem;margin-top:4px;line-height:1.45}
.bon-ligne .btn{flex:none}

.qr-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:14px;
  position:relative;z-index:1}
/* Le code est le seul des deux qu'on ne peut pas couper : il se lit à voix
   haute au bar. C'est donc la légende qui passe à la ligne si la place
   manque, pas lui — l'inverse coupait « HN-4F2A9C » en deux sous 340 px. */
.qr-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.74rem;
  letter-spacing:.18em;color:rgba(168,85,247,.85);white-space:nowrap;flex-shrink:0}
.qr-timer{font-size:.68rem;color:var(--text-muted);text-align:right;min-width:0}
.qr-bar{height:2px;background:rgba(168,85,247,.16);border-radius:2px;overflow:hidden;margin-top:9px;
  position:relative;z-index:1}
.qr-bar i{display:block;height:100%;background:linear-gradient(90deg,var(--fidelite-fonce),var(--fidelite-clair));
  transition:width 1s linear}

.balance{text-align:center;padding:30px 0 24px}
.balance-n{font-family:var(--font-body);font-weight:600;font-variant-numeric:tabular-nums;font-size:3.5rem;line-height:1;color:#fff}
.balance-l{font-size:.68rem;letter-spacing:.24em;text-transform:uppercase;color:var(--text-muted);margin-top:6px}
.gauge{height:7px;border-radius:999px;background:rgba(255,255,255,.07);overflow:hidden;margin:20px 0 10px}
.gauge i{display:block;height:100%;border-radius:999px;
  background:linear-gradient(90deg,var(--fidelite-fonce),var(--fidelite-clair));
  transition:width .9s var(--ease-luxury)}
.gauge-lbl{display:flex;justify-content:space-between;font-size:.72rem;color:var(--text-muted)}
.gauge-lbl b{color:var(--fidelite-clair);font-weight:600}

.earn-list{display:grid;gap:1px;background:var(--border-subtle);border-radius:var(--radius-lg);overflow:hidden}
.earn-item{display:flex;gap:14px;align-items:center;padding:15px 17px;background:var(--bg-secondary)}
.earn-ico{width:36px;height:36px;border-radius:10px;flex-shrink:0;display:flex;align-items:center;
  justify-content:center;background:rgba(168,85,247,.10);border:1px solid rgba(168,85,247,.24)}
.earn-ico svg{width:17px;height:17px;stroke:var(--fidelite-clair);fill:none;stroke-width:1.6}
.earn-item strong{display:block;color:#fff;font-size:.86rem;font-weight:500;margin-bottom:2px}
.earn-item span{font-size:.73rem;color:var(--text-muted)}
/* Ticket de caisse SumUp */
.btn-ticket{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;margin-top:14px}
.btn-ticket svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.ticket-aide{text-align:center;margin-top:8px;color:var(--text-muted)}
body.mode-carte .btn-ticket,body.mode-carte .ticket-aide{display:none}
.ticket-photo{display:flex;align-items:center;justify-content:center;gap:10px;cursor:pointer}
.ticket-ou{text-align:center;color:var(--text-faint);font-size:.75rem;margin:14px 0 10px;letter-spacing:.12em;text-transform:uppercase}

.rw{display:flex;align-items:center;gap:15px;padding:16px 17px;border-radius:var(--radius-lg);
  border:1px solid var(--border-subtle);background:var(--bg-glass);margin-bottom:11px;transition:all .3s}
.rw.open{border-color:rgba(168,85,247,.38);background:rgba(168,85,247,.05)}
.rw-ico{width:44px;height:44px;border-radius:12px;flex-shrink:0;display:flex;align-items:center;
  justify-content:center;background:rgba(255,255,255,.04);border:1px solid var(--border-subtle);font-size:1.15rem}
.rw.open .rw-ico{background:rgba(168,85,247,.13);border-color:rgba(168,85,247,.3)}
.rw-txt{flex:1;min-width:0}
.rw-t{font-size:.92rem;color:#fff;font-weight:500;margin-bottom:3px}
.rw.locked .rw-t{color:var(--text-secondary)}
.rw-c{font-size:.72rem;color:var(--text-muted)}
.rw.open .rw-c{color:var(--fidelite)}
/* Jauge par récompense : le solde face au prix. */
.rw-bar{height:5px;border-radius:999px;background:rgba(255,255,255,.07);overflow:hidden;margin-top:9px}
.rw-bar i{display:block;height:100%;border-radius:999px;
  background:linear-gradient(90deg,var(--accent-primary),var(--fidelite-clair));transition:width .6s var(--ease-luxury)}
.rw.locked .rw-bar i{opacity:.75}
.rw-bar-lbl{font-size:.64rem;color:var(--text-faint);margin-top:4px}
.rw-bar.pleine + .rw-bar-lbl{color:var(--fidelite-clair)}
#rewards.charge .rw-bar i{animation:rw-charge 1.1s var(--ease-luxury) both}
#rewards.charge .rw:nth-child(2) .rw-bar i{animation-delay:.08s}
#rewards.charge .rw:nth-child(3) .rw-bar i{animation-delay:.16s}
#rewards.charge .rw:nth-child(4) .rw-bar i{animation-delay:.24s}
#rewards.charge .rw:nth-child(5) .rw-bar i{animation-delay:.32s}
@keyframes rw-charge{from{width:0}}
@media (prefers-reduced-motion:reduce){#rewards.charge .rw-bar i{animation:none}}

.mv{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:13px 0;
  border-bottom:1px solid var(--border-subtle)}
.mv:last-child{border-bottom:0}
/* Un enfant flex ne descend pas sous sa largeur de contenu sans min-width:0.
   Le libellé d'un retrait (« 🍾 Bouteille offerte (HGT-1234) ») et la ligne
   de date qui le suit poussaient donc la colonne de gauche à sa largeur
   naturelle, et le montant à droite sortait de la carte sous 400 px. */
.mv > div:first-child{min-width:0;flex:1}
.mv-t{font-size:.85rem;color:var(--text-primary);font-weight:400;margin-bottom:2px;overflow-wrap:anywhere}
.mv-d{font-size:.7rem;color:var(--text-faint);overflow-wrap:anywhere}
.mv-p{font-size:.9rem;font-weight:600;color:var(--fidelite-clair);white-space:nowrap}
.mv-p.neg{color:var(--accent-light)}

/* Le bon de retrait en attente : un QR à faire scanner au bar. */
.bon{padding:16px 17px 17px;border-radius:var(--radius-lg);margin-top:18px;
  background:rgba(147,51,234,.10);border:1px solid var(--border-accent)}
.bon-head{display:flex;align-items:center;gap:13px;margin-bottom:15px}
.bon-head strong{display:block;color:#fff;font-size:.9rem;font-weight:500;margin-bottom:2px}
.bon-head span{font-size:.73rem;color:var(--accent-ultra)}

.pending{display:flex;align-items:center;gap:13px;padding:15px 17px;border-radius:var(--radius-lg);
  background:rgba(147,51,234,.10);border:1px solid var(--border-accent);margin-top:18px}
.pending strong{display:block;color:#fff;font-size:.86rem;font-weight:500;margin-bottom:2px}
.pending span{font-size:.73rem;color:var(--accent-ultra)}
.pending .code{font-family:ui-monospace,Menlo,monospace;letter-spacing:.14em;font-size:.92rem;
  color:#fff;font-weight:600}

/* ---------- Profil ---------- */
.prof-head{display:flex;align-items:center;gap:16px;padding:26px 0 22px}
.prof-head>div:last-child{min-width:0;flex:1}
/* Les deux raccourcis WhatsApp de l'accueil. Deux colonnes egales : ils
   se valent, et l'un ne doit pas paraitre plus urgent que l'autre. Ils
   passent l'un sous l'autre sur les tres petits ecrans, ou deux libelles
   cote a cote se couperaient en plein mot. */
.wa-duo{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:12px}
@media (max-width:340px){.wa-duo{grid-template-columns:1fr}}
.wa-btn{display:flex;align-items:center;justify-content:center;gap:9px;
  padding:14px 10px;border-radius:14px;text-decoration:none;
  font-size:.82rem;font-weight:600;color:#fff;
  /* Le vert de WhatsApp, pose en fond discret plutot qu'en aplat : la
     couleur dit ou mene le bouton sans prendre le pas sur le violet du
     club, qui reste la couleur des actions de l'app. */
  background:rgba(37,211,102,.14);border:1px solid rgba(37,211,102,.38);
  transition:background .2s}
/* Le logo WhatsApp est un trace plein, la navette un trace au contour :
   `:not([fill])` ne teinte que le premier, sinon la regle CSS l'emporte
   sur l'attribut `fill="none"` et la navette devient un bloc vert. */
.wa-btn svg{flex-shrink:0;color:#25d366}
.wa-btn svg:not([fill]){fill:#25d366}
.wa-btn:active{background:rgba(37,211,102,.24)}
/* Une seule ligne, jamais coupee. Le libelle retrecit plutot que de
   passer a la ligne : « Reserver la navette » sur deux lignes decalait
   le second bouton et donnait deux hauteurs differentes. */
.wa-btn span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
@media (max-width:380px){.wa-btn{font-size:.74rem;padding:13px 6px;gap:6px}}

.avatar{width:62px;height:62px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;
  justify-content:center;font-family:var(--font-heading);font-size:1.7rem;color:#fff;
  background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));
  border:0;padding:0;position:relative;overflow:hidden;flex-shrink:0}
/* La photo remplit le disque quelle que soit sa forme : `cover` recadre
   au centre plutot que d'ecraser un portrait dans un carre. */
.avatar img{width:100%;height:100%;object-fit:cover;display:block}
/* L'apercu dans Parametres : le meme disque, en plus petit, a droite de
   la ligne. Le badge appareil photo n'est que la — c'est cette ligne qui
   se touche, et l'avatar de l'entete n'est plus qu'un affichage. */
/* Un champ que l'oeil ne voit pas mais que le navigateur considere
   comme affiche. `hidden` et `display:none` font refuser l'ouverture du
   selecteur de fichiers a certains WebView Android — c'est precisement
   ce qui empechait la photo de profil de fonctionner. */
.hors-ecran{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

.avatar-mini{width:40px;height:40px;font-size:1.05rem}
.avatar-mini::after{content:'';position:absolute;right:-1px;bottom:-1px;width:16px;height:16px;
  border-radius:50%;background:var(--bg-secondary,#1a1a1f) center/10px 10px no-repeat;
  box-shadow:0 0 0 2px var(--bg-secondary,#0a0a0f);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z'/%3E%3Ccircle cx='12' cy='13' r='4'/%3E%3C/svg%3E")}
/* L'avatar de l'entete se touche pour changer la photo : il porte donc
   le meme badge appareil photo que l'apercu des reglages, sinon rien ne
   dit qu'il est autre chose qu'une vignette. `overflow` revient a
   `visible` pour que le badge deborde du disque ; la photo, elle, reste
   ronde par son propre `border-radius`. */
#avatar{cursor:pointer;-webkit-tap-highlight-color:transparent;overflow:visible}
#avatar img{border-radius:50%}
#avatar::after{content:'';position:absolute;right:-1px;bottom:-1px;width:20px;height:20px;
  border-radius:50%;background:var(--bg-secondary,#1a1a1f) center/12px 12px no-repeat;
  box-shadow:0 0 0 2px var(--bg-primary,#050508);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z'/%3E%3Ccircle cx='12' cy='13' r='4'/%3E%3C/svg%3E")}
#avatar.occupe{opacity:.55;pointer-events:none}

/* Pendant l'envoi : la ligne entiere se fige, pas seulement le disque. */
#photoBtn{cursor:pointer;-webkit-tap-highlight-color:transparent}
#photoBtn.occupe{opacity:.55;pointer-events:none}
#photoBtn.occupe .avatar-mini::after{display:none}
.prof-n{font-family:var(--font-body);font-weight:600;font-variant-numeric:tabular-nums;font-size:1.4rem;color:#fff;line-height:1.2;
  overflow-wrap:break-word}
.prof-e{font-size:.78rem;color:var(--text-muted);margin-top:2px;overflow-wrap:break-word}
.gbadge{display:inline-flex;align-items:center;gap:6px;margin-top:7px;padding:4px 10px;border-radius:999px;
  background:var(--bg-glass);border:1px solid var(--border-subtle);font-size:.65rem;color:var(--text-secondary)}
.switch{width:42px;height:24px;border-radius:999px;background:rgba(255,255,255,.13);position:relative;
  flex-shrink:0;transition:background .3s}
.switch i{position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;
  transition:transform .3s var(--ease-spring)}
.switch.on{background:var(--accent-primary)}
.switch.on i{transform:translateX(18px)}
.danger{color:#ff6b6b}

/* ---------- Barre d'onglets ---------- */
.tabbar{position:absolute;left:0;right:0;bottom:0;height:calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));display:flex;
  padding-bottom:env(safe-area-inset-bottom, 0px);
  /* Opaque pour la meme raison que .topbar : la barre est posee par
     dessus la liste qui defile, et son flou faisait relire l'ecran a
     chaque image. */
  background:#090a0e;border-top:1px solid var(--border-subtle);z-index:30}
.tabbar.hide{display:none}
.tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;
  color:var(--text-faint);transition:color .25s}
.tab svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round}
/* Six onglets depuis l'arrivee de « Récompenses ». `min-width:0` autorise
   le retrecissement sous la largeur du texte, sans quoi la barre deborde
   sur un ecran de 320 px ; la lettre-espacement tombe pour la meme
   raison, et « Ma carte » reste sur une ligne. */
.tab{min-width:0}
.tab span{font-size:.62rem;letter-spacing:.04em;white-space:nowrap;
  max-width:100%;overflow:hidden;text-overflow:ellipsis}
@media (max-width:340px){.tab span{font-size:.58rem;letter-spacing:0}}
.tab.on{color:var(--accent-light)}

/* ---------- Feuille du bas ---------- */
.backdrop{position:absolute;inset:0;background:rgba(5,5,8,.72);backdrop-filter:blur(3px);
  z-index:70;opacity:0;pointer-events:none;transition:opacity .3s}
.backdrop.on{opacity:1;pointer-events:auto}
.sheet{position:absolute;left:0;right:0;bottom:0;z-index:80;background:var(--bg-secondary);
  border-top:1px solid var(--border-light);border-radius:26px 26px 0 0;padding:10px 22px calc(30px + env(safe-area-inset-bottom, 0px));
  transform:translateY(101%);transition:transform .42s var(--ease-luxury);max-height:88%;overflow-y:auto}
.sheet.on{transform:none}
.grip{width:38px;height:4px;border-radius:99px;background:var(--border-light);margin:0 auto 20px}
.sheet h3{font-family:var(--font-heading);font-size:1.75rem;color:#fff;font-weight:400;margin-bottom:8px}
/* Même règle que .hero img : l'affiche est carrée, un cadrage 16/10 en cover
   lui coupait le haut et le bas (visages tranchés, mentions d'entrée perdues).
   max-height garde le bouton « Réserver » à portée sur les petits écrans. */
.sheet-img{display:block;width:100%;height:auto;aspect-ratio:auto 1/1;max-height:52vh;
  object-fit:contain;border-radius:var(--radius-lg);margin-bottom:18px}
.bigcode{text-align:center;padding:24px;border-radius:var(--radius-lg);margin:18px 0;
  background:rgba(168,85,247,.07);border:1px dashed rgba(168,85,247,.4)}
.bigcode b{display:block;font-family:ui-monospace,Menlo,monospace;font-size:2.05rem;
  letter-spacing:.22em;color:var(--fidelite-clair);font-weight:600}
.bigcode small{display:block;margin-top:8px;font-size:.7rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--text-muted)}

/* ---------- Espace admin ---------- */
.admin{position:absolute;inset:0;z-index:50;background:var(--bg-primary);display:none;flex-direction:column}
.admin.on{display:flex}
.ad-top{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;
  border-bottom:1px solid var(--border-subtle);flex-shrink:0}
.ad-title{font-size:.74rem;letter-spacing:.2em;text-transform:uppercase;color:var(--text-secondary)}
.ad-title b{color:#fff;font-weight:600}
.ad-tabs{display:flex;gap:8px;padding:14px 20px 4px;flex-shrink:0;overflow-x:auto;-webkit-overflow-scrolling:touch;
  /* Un dégradé aux deux bords plutôt qu'un défilement qui a l'air d'une
     liste coupée court : sur un écran étroit, six onglets ne tiennent
     jamais tous, mais on doit voir qu'il y en a d'autres à droite. */
  -webkit-mask-image:linear-gradient(to right,transparent 0,#000 20px,#000 calc(100% - 20px),transparent 100%);
  mask-image:linear-gradient(to right,transparent 0,#000 20px,#000 calc(100% - 20px),transparent 100%)}
.ad-tabs::-webkit-scrollbar{display:none}
.ad-tab{white-space:nowrap;padding:8px 14px;border-radius:999px;border:1px solid var(--border-light);background:var(--bg-glass);
  font-size:.84rem;font-weight:600;color:var(--text-secondary);transition:all .2s var(--ease-luxury)}
.ad-tab.on{background:rgba(147,51,234,.18);border-color:var(--border-accent);color:var(--accent-ultra)}
.ad-body{flex:1;overflow-y:auto;padding:12px 20px 30px}
.ad-pane{display:none}.ad-pane.on{display:block}
.req{border:1px solid var(--border-subtle);border-radius:var(--radius-lg);padding:17px;margin-bottom:12px;
  background:var(--bg-glass)}
.req.fresh{border-color:var(--border-accent);background:rgba(147,51,234,.08);
  animation:glow 2s var(--ease-luxury) 2}
@keyframes glow{0%,100%{box-shadow:0 0 0 rgba(147,51,234,0)}50%{box-shadow:0 0 26px rgba(147,51,234,.35)}}
.req-h{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:14px}
/* Un nom + e-mail long (courant en vrai, rare dans les essais avec des
   comptes de test) ne réduisait pas sa boîte — flexbox laisse un enfant
   texte à sa largeur naturelle par défaut — et poussait tout .req-h en
   dehors de la carte. min-width:0 l'autorise à rétrécir, overflow-wrap
   coupe un mot trop long (une adresse sans espace) plutôt que déborder. */
.req-h > div:first-child{min-width:0;flex:1}
.req-n{font-size:1rem;color:#fff;font-weight:500;overflow-wrap:break-word}
.req-m{font-size:.72rem;color:var(--text-muted);margin-top:3px}
.req-code{font-family:ui-monospace,Menlo,monospace;font-size:.82rem;letter-spacing:.12em;
  color:var(--fidelite-clair);padding:5px 10px;border-radius:6px;background:rgba(168,85,247,.10);
  border:1px solid rgba(168,85,247,.26);white-space:nowrap;flex-shrink:0}
.req-r{display:flex;align-items:center;gap:10px;padding:12px 14px;border-radius:var(--radius-md);
  background:rgba(255,255,255,.03);margin-bottom:14px}
.req-r b{color:#fff;font-weight:500;font-size:.86rem}
.req-r span{margin-left:auto;font-size:.78rem;color:var(--fidelite);font-weight:600;white-space:nowrap}
.req-btns{display:flex;gap:9px}
.req-btns .btn{flex:1}
.btn-ok{background:rgba(52,199,89,.14);border:1px solid rgba(52,199,89,.42);color:#7ee29a;font-weight:600}
.btn-no{background:var(--bg-glass);border:1px solid var(--border-light);color:var(--text-muted)}
.stat3{display:grid;grid-template-columns:repeat(3,1fr);gap:9px;margin-bottom:20px}
.stat{padding:15px 12px;border-radius:var(--radius-lg);background:var(--bg-glass);
  border:1px solid var(--border-subtle);text-align:center}
.stat b{display:block;font-family:var(--font-body);font-weight:600;font-variant-numeric:tabular-nums;font-size:1.5rem;color:#fff;line-height:1.1}
.stat span{font-size:.6rem;letter-spacing:.1em;text-transform:uppercase;color:var(--text-muted);
  display:block;margin-top:5px;line-height:1.4}
.mem{display:flex;align-items:center;gap:13px;padding:13px 0;border-bottom:1px solid var(--border-subtle)}
.mem-av{width:38px;height:38px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;
  justify-content:center;font-size:.86rem;color:#fff;font-weight:500;background:var(--bg-glass-strong)}
.mem-n{font-size:.88rem;color:#fff;flex:1;min-width:0;overflow-wrap:break-word}
.mem-p{font-size:.8rem;color:var(--fidelite-clair);font-weight:600;flex-shrink:0}
.credit{border:1px solid var(--border-subtle);border-radius:var(--radius-lg);padding:17px;
  background:var(--bg-glass);margin-bottom:22px}
.credit label{display:block;font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-muted);margin-bottom:8px}
select.field{appearance:none;background-image:none}
.calc{text-align:center;padding:13px;border-radius:var(--radius-md);background:rgba(168,85,247,.07);
  border:1px solid rgba(168,85,247,.22);margin-bottom:13px;color:var(--fidelite-clair);
  font-size:.86rem;font-weight:600}

/* ---------- Notification passagère ---------- */
.toast{position:absolute;left:20px;right:20px;bottom:calc(var(--tabbar-h) + 18px + env(safe-area-inset-bottom, 0px));z-index:95;
  padding:15px 18px;border-radius:var(--radius-md);background:rgba(20,20,28,.97);
  border:1px solid var(--border-light);color:#fff;font-size:.82rem;line-height:1.5;
  box-shadow:0 12px 40px rgba(0,0,0,.6);opacity:0;transform:translateY(14px);
  pointer-events:none;transition:all .34s var(--ease-luxury)}
.toast.on{opacity:1;transform:none}
.toast b{color:var(--fidelite-clair)}

/* ---------- Ajouts propres à l'application ---------- */
/* Champ refusé à la validation : un message seul ne dit pas lequel. */
.field.faux{border-color:rgba(239,68,68,.7);background:rgba(239,68,68,.1)}
.case input.faux{outline:2px solid rgba(239,68,68,.75);outline-offset:3px}
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
a.rowlink{text-decoration:none;color:inherit;display:flex}
.switch{border:0;padding:0;cursor:pointer}
.vide{padding:34px 0;text-align:center;color:var(--text-muted);font-size:.84rem;line-height:1.7}
.squelette{border-radius:var(--radius-lg);background:linear-gradient(90deg,
  rgba(255,255,255,.03),rgba(255,255,255,.07),rgba(255,255,255,.03));
  background-size:200% 100%;animation:glisse 1.4s linear infinite}
@keyframes glisse{from{background-position:200% 0}to{background-position:-200% 0}}
.horsligne{display:flex;align-items:center;gap:9px;margin-top:14px;padding:11px 14px;
  border-radius:var(--radius-md);background:rgba(255,255,255,.04);
  border:1px solid var(--border-subtle);font-size:.76rem;color:var(--text-muted)}
.horsligne i{width:7px;height:7px;border-radius:50%;background:var(--fidelite);flex-shrink:0}

/* ---------- Bandeau du mode démonstration ---------- */
.bandeau-demo{margin-top:16px;padding:12px 15px;border-radius:var(--radius-md);
  background:rgba(168,85,247,.09);border:1px solid rgba(168,85,247,.3);
  color:var(--fidelite-clair);font-size:.76rem;line-height:1.55;text-align:center}

/* ---------- Le code en grand, pour que le scanner l'attrape ---------- */
.voile-qr{position:absolute;inset:0;z-index:90;background:#fff;display:flex;
  flex-direction:column;align-items:center;justify-content:center;gap:24px;padding:24px}
.voile-qr[hidden]{display:none}
.voile-qr canvas{max-width:100%;height:auto;image-rendering:pixelated}
.voile-qr p{color:#5b5b66;font-size:.72rem;letter-spacing:.16em;text-transform:uppercase}

/* La galerie : une liste d'albums, et dans l'album ouvert un rail qui
   se parcourt du doigt, de droite a gauche. Un rail plutot qu'une
   grille — trente photos en grille font une page qu'on deroule sans
   fin, la ou le rail garde chaque soiree a sa place. */
.gal-grille{margin-top:14px}
.album{border-bottom:1px solid var(--border-subtle)}
.album:last-child{border-bottom:0}
.album-tete{display:flex;align-items:center;gap:12px;width:100%;padding:12px 2px;
  background:none;border:0;text-align:left;cursor:pointer;
  -webkit-tap-highlight-color:transparent}
.album-couv{width:52px;height:52px;border-radius:10px;object-fit:cover;flex-shrink:0;
  background:var(--bg-tertiary)}
.album-txt{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}
.album-t{color:#fff;font-size:.92rem;font-weight:500;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.album-d{color:var(--text-muted);font-size:.76rem}
/* Le chevron pivote quand l'album est ouvert : c'est le seul signe que
   le rail dessous appartient a cette ligne-la. */
.album.on .album-tete .chev{transform:rotate(90deg)}
.album-tete .chev{transition:transform .25s}

/* Le rail. Les marges negatives le font filer jusqu'aux bords, sinon il
   a l'air de s'arreter avant.

   Plus d'accrochage du tout : meme en `proximity`, le calage reprenait
   la main au relachement et retenait le rail — c'est ce qui donnait ce
   defilement qui « colle ». Le doigt decide seul, du debut a la fin.

   `overscroll-behavior-x:contain` : arrive au bout du rail, le geste ne
   se propage plus a la page ni au retour arriere du systeme. Sans lui,
   un balayage un peu long dans l'album quittait l'ecran.

   `touch-action:pan-x pan-y` : le rail prend l'horizontale, la page
   garde la verticale. Un geste vertical parti sur une photo fait donc
   defiler la page, au lieu d'etre avale par le rail. */
.album-rail{display:flex;gap:8px;overflow-x:auto;
  overscroll-behavior-x:contain;touch-action:pan-x pan-y;
  padding:2px 20px 16px;margin:0 -20px;-webkit-overflow-scrolling:touch;
  scrollbar-width:none}
.album-rail::-webkit-scrollbar{display:none}
.album-rail .gal-case{flex:0 0 42%;max-width:190px}
.album-rail .vide{padding:18px 0}
.gal-case{position:relative;aspect-ratio:1;border:0;padding:0;border-radius:12px;
  overflow:hidden;background:var(--bg-tertiary);cursor:pointer;
  -webkit-tap-highlight-color:transparent}
.gal-case img{width:100%;height:100%;object-fit:cover;display:block}
.gal-case:active{opacity:.75}
/* Le mot « aucune photo » est un enfant de la grille : sans cela il se
   rangerait dans la premiere colonne, sur un tiers de la largeur. */
.gal-grille .vide{grid-column:1/-1}

/* Le plein ecran. Fond noir : c'est la photo qu'on regarde. `contain`
   et non `cover` — une photo tronquee en plein ecran n'a plus de
   raison d'etre agrandie. */
/* Le plein ecran, et plein pour de bon : ni marge, ni hauteur bridee.
   L'image occupait 82 % de la hauteur dans une boite a rembourrage, ce
   qui laissait des bandes noires en haut et en bas sur une photo au
   format portrait — la photo etait affichee plus petite que l'ecran qui
   la montrait. */
.voile-photo{position:absolute;inset:0;z-index:95;background:#000;
  display:flex;align-items:center;justify-content:center;overflow:hidden;
  /* Le navigateur ne doit pas interpreter les gestes a notre place :
     c'est le code qui gere le glissement. */
  touch-action:none}
.voile-photo[hidden]{display:none}
/* La piste : trois images cote a cote, celle du milieu a l'ecran. Le
   decalage de depart (-100 %) est pose par le code, en pixels.
   `will-change` previent le compositeur : le glissement se fait alors
   sans repeindre quoi que ce soit. */
.vp-piste{position:absolute;inset:0;display:flex;align-items:stretch;
  will-change:transform}
/* Une courbe qui freine, et qui ne depasse pas.
   ---------------------------------------------------------------------
   C'etait `--ease-spring`, un ressort qui va 56 % au-dela de sa cible
   avant de revenir : sur un carrousel, la photo suivante passait donc le
   bord de l'ecran et repartait en arriere, laissant entrevoir la case
   d'apres. D'ou ce mouvement bref et bizarre.

   Ici la piste ralentit jusqu'a sa place et s'y arrete. 420 ms et une
   courbe qui part vite puis freine longuement (.16,.84,.44,1) : c'est
   la sensation d'inertie d'une galerie de telephone — la photo est
   lancee, elle glisse, elle se pose. En dessous de 350 ms le mouvement
   se lit comme un saut plutot que comme un glissement. */
.vp-piste.pose{transition:transform .42s cubic-bezier(.16,.84,.44,1)}

/* Une case, une image : la pleine taille, plein cadre.
   ---------------------------------------------------------------------
   Plus d'apercu pose dessous. La vignette est un carre recadre au
   centre : affichee en attendant, elle n'avait pas le format de la
   photo, et celle-ci semblait donc s'agrandir jusqu'au plein cadre a
   chaque changement — c'est ce qui hachait le defilement.

   L'image reste transparente tant qu'elle n'est pas decodee, et se
   revele d'un fondu. Sa geometrie, elle, ne bouge jamais : ce qui
   apparait est deja pleine page. */
.vp-case{flex:0 0 100%;position:relative;height:100%}
.vp-net{position:absolute;inset:0;width:100%;height:100%;display:block;
  object-fit:contain;opacity:0;transition:opacity .18s ease-out}
.vp-net.prete{opacity:1}
/* Le compteur passe par-dessus la photo au lieu de lui prendre sa
   hauteur. */
.voile-photo p{position:absolute;left:0;right:0;z-index:2;text-align:center;
  bottom:calc(16px + env(safe-area-inset-bottom, 0px));
  color:rgba(255,255,255,.55);font-size:.72rem;letter-spacing:.16em;
  text-transform:uppercase;font-variant-numeric:tabular-nums;
  text-shadow:0 1px 6px rgba(0,0,0,.9);transition:opacity .2s}
/* Un appui simple efface les commandes : sur un portrait plein cadre,
   le compteur, la croix et l'enregistrement mordent sur l'image. Un
   second appui les ramene. */
.vp-btn,.vp-nav,.voile-photo p{transition:opacity .2s}
.voile-photo.nu p,.voile-photo.nu .vp-btn,.voile-photo.nu .vp-nav{
  opacity:0;pointer-events:none}

/* Les commandes du plein ecran. Fond translucide et non plein : sur une
   photo sombre il faut les voir, sur une photo claire elles ne doivent
   pas faire tache. */
.vp-btn,.vp-nav{position:absolute;z-index:2;display:flex;align-items:center;
  justify-content:center;border:0;color:#fff;background:rgba(0,0,0,.45);
  backdrop-filter:blur(6px);cursor:pointer;-webkit-tap-highlight-color:transparent}
.vp-btn{top:calc(14px + env(safe-area-inset-top, 0px));width:40px;height:40px;
  border-radius:50%;font-size:1.05rem;line-height:1}
.vp-fermer{left:14px}
.vp-dl{right:14px}
.vp-nav{top:50%;transform:translateY(-50%);width:44px;height:44px;border-radius:50%}
.vp-prec{left:8px}
.vp-suiv{right:8px}
.vp-btn:active,.vp-nav:active{background:rgba(0,0,0,.7)}
/* Sur un telephone on glisse le doigt ; les fleches encombrent l'image
   plus qu'elles ne servent. Elles restent pour la souris. */
@media (hover:none){.vp-nav{display:none}}

/* Onglet Prévente : QR Xceed, fond blanc pour que le lecteur le trouve. */
.tab[hidden]{display:none}
.prevente{background:var(--card,#16161d);border-radius:16px;padding:18px;margin:14px 0;text-align:center}
.prevente h3{margin:0 0 4px;font-size:1.05rem}
.prevente .sec-sub{margin-bottom:12px}
.prevente-qr{width:240px;max-width:100%;height:auto;aspect-ratio:1;border-radius:10px;background:#fff;cursor:pointer}
.prevente-info{margin:12px 0 0;font-size:.85rem;color:var(--text-muted)}
.prevente.scanne .prevente-qr{opacity:.35}
