/* ============================================================
   style.css — Style neutre et fonctionnel (mobile-first)
   La charte graphique définitive sera appliquée ultérieurement.
   ============================================================ */

:root {
  --color-primary: #191557;
  --color-primary-dark: #120f3d;
  --color-accent: #e8a13a;
  --color-bg: #f5f6f5;
  --color-surface: #ffffff;
  /* Textes rubriques/contenus/libellés (console > Réglages > Couleurs > Environnement ICILAGO,
     demande explicite du 2026-07-31) : 3 variables pilotables à distance via
     appliquerCouleursEnvironnementDepuisApi() (main.js), valeurs par défaut ci-dessous identiques
     à l'ancien --color-text figé, donc aucun changement visuel tant que la console n'a rien
     modifié. --color-text reste défini mais redirigé vers --color-texte-contenu (indirection) :
     tout le reste de la feuille de style qui référence encore var(--color-text) directement
     (badges, popups, filtres, pendule...) suit donc automatiquement "Textes contenus" sans qu'il
     ait fallu réécrire chaque règle une par une — seuls .section-title (rubriques) et .field
     label (libellés) ont été isolés sur leur propre variable, voir plus bas. */
  --color-texte-rubrique: #6b7570;
  --color-texte-contenu: #1f2420;
  --color-texte-libelle: #1f2420;
  --color-text: var(--color-texte-contenu);
  --color-text-muted: #6b7570;
  --color-border: #e1e4e1;
  --color-input-bg: #f5f5f4;
  --color-danger: #c04848;
  --color-success: #2f8f5b;
  --radius: 12px;
  --shadow: 0 2px 10px rgba(0,0,0,0.07);
  --nav-height: 62px;
  --header-height: 70px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  -webkit-tap-highlight-color: transparent;
  /* Empêche le "pull-to-refresh"/rebond natif du navigateur de se déclencher EN PLUS du geste
     personnalisé sur "Près de vous" (voir #pull-refresh-indicator plus bas + initPullToRefreshVisiteur()
     dans main.js, 2026-08-29) — sans ça, sur Chrome Android notamment, les deux pouvaient se
     chevaucher (rechargement complet de la page ET notre propre rafraîchissement). */
  overscroll-behavior-y: contain;
}
/* height:100% seulement sur html (base du calcul de position:fixed du verrou de défilement,
   voir ci-dessous), JAMAIS sur body — corrigé le 2026-07-27 : body avait aussi height:100% ici
   à la base (donc en permanence, pas seulement pendant le verrou), ce qui causait une bande
   blanche en bas d'écran mobile dès qu'une pop-up était ouverte sur une page déjà scrollée. En
   cause : le verrou JS (actualiserVerrouillageDefilementModales) passe body en position:fixed
   avec top:-Y (Y = scroll au moment de l'ouverture) pour recréer la position visuelle sans
   autoriser le défilement ; si la hauteur de body est en plus plafonnée à 100% (= une seule
   hauteur d'écran), son bord bas se retrouve mécaniquement Y pixels au-dessus du bas réel de
   l'écran, démasquant Y pixels du fond de la page en dessous. */
html { height: 100%; }
/* Verrou de défilement pendant qu'une pop-up est ouverte (voir actualiserVerrouillageDefilementModales
   dans main.js) : position:fixed sur body seul ne suffit pas sur tous les navigateurs mobiles à
   empêcher le "scroll chaining" vers <html> — on bloque donc aussi explicitement l'overflow des
   deux éléments, en complément (demande explicite du 2026-07-27, le premier essai avec
   position:fixed seul n'a pas suffi). */
html.defilement-verrouille,
html.defilement-verrouille body {
  overflow: hidden !important;
}
/* Bloque le clic sur le contenu flouté derrière une pop-up ouverte (demande de Christophe,
   2026-08-21, capture à l'appui : "Changer d'accès à ICILAGO" laissait deviner/atteindre les
   boutons et filtres de l'écran "Près de vous" en dessous). Le voile (.modal-overlay,
   position:fixed inset:0, z-index:3000) suffit en théorie à intercepter tout clic par
   empilement natif, mais cette app a déjà eu des cas de clic/tap qui atteint quand même l'élément
   du dessous sur mobile (voir icilago-events-header-sous-titre-compteur-jour) — verrouillage
   explicite ici en ceinture-et-bretelles, réutilisant la même classe globale
   .defilement-verrouille (posée/retirée exactement quand une .modal-overlay est ouverte, voir
   actualiserVerrouillageDefilementModales dans main.js) plutôt que d'ajouter un mécanisme dédié.
   Coupe TOUT le contenu de #app (donc aussi #toast-container si présent dedans), puis rétablit
   explicitement les clics sur la pop-up elle-même (pointer-events hérite sur les descendants sauf
   override). #splash-screen/#ecran-rotation-requise sont hors #app (avant dans le DOM) donc jamais
   affectés. */
html.defilement-verrouille #app {
  pointer-events: none;
}
html.defilement-verrouille #app .modal-overlay:not(.hidden) {
  pointer-events: auto;
}
/* Régression du 2026-08-21 (même jour) : la "Flèche retour" de la fiche évènement (#btn-retour-fiche)
   vit dans le header (barre bleue, #app-header), pas dans #modal-event-detail lui-même. Le
   blocage général ci-dessus la neutralisait aussi (elle fait partie de #app), rendant "retour"
   inopérant — signalé par Christophe. 1er correctif limité à .au-dessus-fiche (affichage fiche
   seule) jugé insuffisant par Christophe : "il faut pas que ce bouton soit bloqué pour toutes les
   applications" — le header doit rester cliquable dans TOUS les cas, y compris quand une autre
   pop-up (#modal-abonnement, etc.) s'ouvre par-dessus une fiche déjà affichée. #app-header exempté
   du blocage sans condition. */
html.defilement-verrouille #app-header {
  pointer-events: auto;
}
/* Écran d'accueil : .hero et .accueil-bottom sont déjà position:fixed (immobiles), mais tant que
   <body>/<html> restent défilables, un doigt posé sur l'écran déclenche le rebond élastique
   ("rubber-band") d'iOS Safari, qui entraîne tout — y compris le fond d'écran — avec lui, même
   avec background-attachment:fixed (Christophe, 2026-08-11 : "l'image des fond bouge toujours si
   on la touche"). Verrouille le défilement le temps que l'accueil est affiché, même principe que
   le verrou pop-up ci-dessus. Même sélecteur :has() qu'ailleurs dans ce fichier (voir
   #app:has(#view-accueil.active)). */
html:has(#view-accueil.active),
html:has(#view-accueil.active) body {
  overflow: hidden;
}

button, input, select, textarea { font-family: inherit; font-size: 16px; }
/* Sans ça, glisser un curseur horizontal (ex. rayon de recherche) sur mobile peut être
   interprété par le navigateur comme un geste de défilement de la page, qui bouge alors
   verticalement pendant le glissement. */
/* Curseur "Rayon de recherche" (et tout autre input[type=range] de l'app) : portion à gauche du
   rond en bleu ICILAGO, portion à droite en gris — demande de Christophe, 2026-08-13.
   accent-color colore automatiquement la partie déjà parcourue et laisse le reste du rail dans
   la couleur neutre par défaut du navigateur (déjà un gris proche de --color-border sur
   Chrome/Safari récents), sans avoir à redessiner tout le composant piste/curseur à la main. */
input[type="range"] { touch-action: none; accent-color: var(--color-bleu-icilago, #1e3a8a); }

/* Écran de démarrage (couleur unie), voir main.js → afficherEcranDemarrage(). Couleur par
   défaut ci-dessous volontairement neutre (identique au fond de l'appli) plutôt qu'une couleur
   de marque figée : sinon, si la couleur réglée dans la console diffère de cette valeur par
   défaut, on voit un flash de la mauvaise couleur le temps que l'API réponde. La transition sur
   background-color fait apparaître la vraie couleur en douceur une fois connue, sans à-coup. */
#splash-screen {
  position: fixed;
  /* RATCHET --h-max RETIRÉ le 2026-09-04 (voir icilago-guide-bande-blanche-fond-accueil-paint-bug
     en mémoire) : figeait la mise en page sur la plus grande hauteur jamais vue, ce qui cachait
     le contenu sous les barres du navigateur quand celles-ci restent réellement affichées.
     Retour à inset:0 en attendant une version avec debounce, validée sur la page de test isolée
     avant tout nouveau report ici. */
  inset: 0;
  z-index: 9999;
  background: var(--color-bg);
  /* Fondu de sortie remonté à .35s (demande de Christophe, 2026-08-30 : "un fondu entre l'écran
     splash et la page d'accueil" — les .167s précédents (~10 trames, réduits le 2026-08-11 car le
     splash total était perçu trop long) étaient devenus trop brefs pour être perçus comme un
     vrai fondu). Voir main.js → afficherEcranDemarrage() pour le délai avant retrait du DOM,
     ajusté en conséquence (doit rester ≥ cette durée, sinon le retrait coupe le fondu en cours). */
  transition: opacity .35s ease, background-color .4s ease;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* 20px entre le logo symbole (.splash-logo-wrap) et le logo texte (.splash-logo-txt) —
     demande de Christophe, 2026-08-19. */
  gap: 20px;
}
#splash-screen.splash-sortie { opacity: 0; pointer-events: none; }

/* Écran "Tournez votre appareil" — bloque l'usage de l'application en mode paysage sur
   smartphones/tablettes (demande de Christophe, 2026-08-13). Règle dupliquée en CSS critique
   inline dans index.html (voir commentaire là-bas) pour être active dès le premier paint. Cible
   uniquement le tactile via hover:none + pointer:coarse — jamais un écran d'ordinateur, qui est
   intrinsèquement "paysage" mais piloté à la souris. z-index 10000 : au-dessus de #splash-screen
   (9999) et de tout le reste (.modal-overlay à 3000). */
#ecran-rotation-requise {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: #191557;
  color: #fff;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 24px;
  text-align: center;
}
#ecran-rotation-requise p {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  max-width: 280px;
  line-height: 1.5;
}
@media (orientation: landscape) and (hover: none) and (pointer: coarse) {
  #ecran-rotation-requise { display: flex; }
}
.splash-logo-wrap {
  position: relative;
  width: 82.5%;
  max-width: 330px;
}
.splash-logo {
  display: block;
  width: 100%;
  height: auto;
}
.splash-logo-teinte {
  position: absolute;
  inset: 0;
  display: none;
  /* Non utilisé sur guide.icilago.eu (logo affiché sans teinte, voir main.js) — mask-image
     laissé sans URL en dur, logo désormais configurable en console (Réglages > Paramètres >
     ÉCRAN SPLASH > "Choisir le logo :", demande de Christophe, 2026-08-19). */
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}
/* Remplace le texte "ICILAGO GUIDE" (ancien .splash-titre, <p> retiré du HTML) — logo texte
   propre à cette application (console > Applications > fiche > Informations > "Choisir le logo
   txt app"), affiché SOUS le logo symbole (.splash-logo-wrap), sans masque/teinte — demande de
   Christophe, 2026-08-19. */
.splash-logo-txt {
  display: block;
  width: 60%;
  max-width: 220px;
  height: auto;
}

#app {
  max-width: 480px;
  margin: 0 auto;
  min-height: 100vh;
  /* 100dvh (hauteur de viewport DYNAMIQUE) en repli : sur Safari mobile/tablette, 100vh ne tient
     pas compte du rétrécissement de la zone visible quand la barre d'adresse/outils du navigateur
     est affichée — le contenu du bas de page peut alors se retrouver sous cette barre, invisible
     et impossible à atteindre en scroll dans les vues où le défilement est verrouillé (signalé
     sur tablette par Christophe, capture iPad, 2026-08-19). Propriété redéclarée : les
     navigateurs qui ne connaissent pas dvh ignorent cette ligne et gardent la valeur 100vh
     ci-dessus.
     RATCHET --h-max RETIRÉ le 2026-09-04 (voir icilago-guide-bande-blanche-fond-accueil-paint-bug
     en mémoire) : figeait #app sur la plus grande hauteur jamais vue, cachant le contenu sous les
     barres du navigateur quand celles-ci restent réellement affichées. Retour à 100vh/100dvh en
     attendant une version avec debounce. */
  min-height: 100dvh;
  background: var(--color-bg);
  display: flex;
  flex-direction: column;
  position: relative;
}

/* Regroupe tout ce qui doit "glisser" quand le menu latéral s'ouvre (en-tête, vues,
   navigation basse) — reproduit ici la même disposition flex/colonne que #app appliquait
   directement à ces éléments auparavant, une fois déplacés d'un niveau. Le panneau du menu
   et son fond assombri restent en dehors, donc immobiles.
   padding-bottom (comme padding-top) vit ici et pas sur #app, pour la même raison : dès que
   la poussée du menu applique un transform à #app-contenu, celui-ci devient le bloc de
   confinement des éléments position:fixed ancrés en bas (bottom-nav, cartes "Accès visiteur"
   en bottom:50px…). Si le padding restait sur #app, le bord bas de #app-contenu remonterait
   de cette hauteur et ces éléments "bottom:X" se retrouveraient décalés vers le haut à
   l'ouverture du menu — bug observé sur les blocs Accès libre / Accès connecté.
   #panel-raccourcis-interets (voir plus bas) a été sorti d'#app-contenu le 2026-09-01 pour la
   même famille de raisons : bien que #app-contenu n'ait pas de transform actif hors `.pousse`,
   sa `transition: transform` permanente semble suffire, dans certains moteurs, à perturber
   l'ordre d'empilement de ses descendants position:fixed (le panneau restait visuellement
   par-dessus #app-header malgré z-index:18 < 20). Sorti du DOM d'#app-contenu, dans index.html,
   au même niveau que le menu latéral. */
#app-contenu {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  padding-bottom: var(--nav-height);
  transition: transform 0.25s ease;
}
#app-contenu.pousse { transform: translateX(135px); }
/* Sur desktop/tablette large, #app est centré (max-width:480px; margin:0 auto) avec de la marge
   blanche des 2 côtés — sans ceci, la translateX(135px) de #app-contenu.pousse ci-dessus pousse
   visuellement le bord droit du contenu (header/vues/nav) à DÉBORDER de #app dans cette marge, où
   rien ne le recouvre plus (le voile #menu-lateral-backdrop reste, lui, calé sur la largeur de
   #app) : une bande de contenu net/non flouté apparaissait à droite de la colonne floutée
   (2026-09-01, capture Christophe à l'appui). Scopé à l'état ouvert via :has() (pas un
   overflow:hidden permanent sur #app) pour ne rien changer au repos. */
#app:has(#app-contenu.pousse) { overflow-x: hidden; }

/* ---------- Header ---------- */
.app-header {
  height: var(--header-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 14px;
  background: var(--color-primary);
  color: #fff;
  position: sticky;
  top: 0;
  z-index: 20;
}
/* Quand la fiche détail est ouverte, le header (z-index:20) passe SOUS le voile/flou de
   #modal-event-detail (.modal-overlay, z-index:3000, plein écran) : la "Flèche retour" y devenait
   visible-mais-floutée et non cliquable — signalé par Christophe, captures à l'appui, 2026-08-20.
   1er correctif via :has() resté sans effet (toujours flouté/non cliquable, screenshot de
   confirmation) : repli sur un pilotage JS explicite (classe posée/retirée par
   actualiserMasquageContenuSousFiche(), voir main.js), plus robuste/direct qu'un sélecteur
   :has() imbriqué. Bien en-dessous de #splash-screen/l'écran de rotation forcée (z-index
   9999/10000), qui doivent rester au-dessus de tout dans tous les cas. */
.app-header.au-dessus-fiche {
  z-index: 3001;
}
/* Titre/sous-titre du header remplacés par la ville + l'accroche du flyer tant que la fiche est
   ouverte (texte posé en JS, voir appliquerTitreHeaderFiche() dans main.js) — mêmes tailles que
   sous la photo de la fiche (organisePar 20px / evt.titre 16px, voir openEventDetail()), demande
   de Christophe, 2026-08-30. */
#app-header.au-dessus-fiche h1 { font-size: 20px; text-align: right; }
/* Phrase d'accroche (evt.titre, sous-titre du header pendant que la fiche est ouverte) : 14px
   repassée à 13px — demande de Christophe, 2026-08-31. */
#app-header.au-dessus-fiche .header-sous-titre { font-size: 13px; text-align: right; }
/* Texte aligné à droite (pas centré) tant que la fiche est ouverte — demande de Christophe,
   2026-08-30. Repositionné en absolute, ancré par "right" (PAS "margin-left:auto" dans le flux
   flex, essayé d'abord) : #btn-filtres reste réservé dans le flex (visibility:hidden, voir plus
   bas) pour ne pas casser le CENTRAGE des autres écrans (voir icilago-guide-header-titre-centrage-
   absolu), donc un positionnement flex "flush contre le voisin de droite" collait le texte contre
   le bord GAUCHE de cette réserve invisible, pas contre le bord réel du bandeau bleu. Demande de
   Christophe (mesure exacte prise sur events.icilago.eu, même fonctionnalité) : marge de 24px
   entre le texte et le bord droit RÉEL, quelle que soit la largeur de l'icône filtres réservée.
   "right:24px" ignore totalement cette réserve — écart garanti exact. left/transform remis à
   "auto"/"none" pour annuler le centrage horizontal de la règle de base (.header-titre-groupe),
   PAS "top" : cf. la même leçon que le centrage vertical du titre (icilago-guide-header-titre-
   centrage-absolu) — top explicite cassait le rendu sur smartphone (zone de sécurité iOS), donc
   toujours laissé à sa valeur par défaut ici aussi (position statique verticale, pilotée par le
   flex du header). align-items:flex-end aligne le bloc lui-même à droite ; text-align:right
   (ci-dessus, sur h1/.header-sous-titre) aligne en plus le TEXTE dans les lignes qui passent à la
   ligne (le sous-titre est plus long que le header, voir capture de Christophe). */
#app-header.au-dessus-fiche .header-titre-groupe {
  left: auto;
  right: 24px;
  transform: none;
  align-items: flex-end;
}
/* Icône filtres masquée tant qu'une fiche est ouverte (demande de Christophe, 2026-08-30) —
   visibility:hidden (PAS display:none/.hidden) pour que le bouton conserve sa place dans le flex
   du header : #app-header utilise justify-content:space-between entre la flèche retour (gauche)
   et cette icône (droite), avec le titre centré entre les deux. En display:none, il ne reste plus
   que 2 enfants et le titre "Autour de vous" se retrouve poussé tout à droite au lieu de rester
   centré (régression repérée par Christophe sur PC, capture à l'appui, corrigée le même jour). */
#app-header.au-dessus-fiche #btn-filtres {
  visibility: hidden;
  pointer-events: none;
}
/* Groupe titre + sous-titre : centré HORIZONTALEMENT par position absolue (left:50% +
   translateX), PAS par le flex/justify-content:space-between du header — demande de Christophe,
   2026-08-30 : l'ancien décalage fixe "-10px" (compensait la différence de largeur entre le
   bouton hamburger et le bouton filtres) donnait un centrage correct UNIQUEMENT quand ces 2
   boutons précis étaient ceux affichés de part et d'autre. Dès que le bouton de gauche change
   (#btn-retour-fiche, plus étroit que le hamburger, affiché pendant qu'une fiche est ouverte), le
   même "-10px" ne compense plus correctement et le titre se retrouve légèrement décalé par
   rapport à la page principale (signalé par Christophe, captures comparatives à l'appui). Le
   centrage horizontal absolu ignore totalement la largeur des boutons de chaque côté : la
   position du titre est désormais IDENTIQUE partout, quel que soit le bouton affiché à
   gauche/masqué à droite.
   VOLONTAIREMENT PAS de "top"/transform vertical ici (top reste "auto") : un enfant position:
   absolute d'un conteneur flex SANS top/bottom explicites garde sa "position statique" pour l'axe
   vertical, càd exactement la position qu'il aurait eue en flux normal (centrée par le
   align-items:center de #app-header) — comportement standard du modèle Flexbox. Un essai
   précédent avec top:50%+translateY(-50%) cassait ce centrage vertical SPÉCIFIQUEMENT sur
   smartphone (≤1024px) : à cette largeur, #app-header passe en box-sizing:content-box avec un
   padding-top ajouté pour la zone de sécurité iOS (encoche/barre de statut, voir plus bas dans ce
   fichier) — top:50% se résout alors par rapport à la boîte de PADDING (donc en incluant cette
   zone), tandis que les boutons/icônes (height:100%, alignés par le flex normal) restent, eux,
   centrés dans la seule boîte de CONTENU (sous le padding) — d'où un titre remonté visuellement
   dans la zone de l'encoche, capture à l'appui. Laisser "top" à sa valeur par défaut élimine ce
   problème : la position verticale suit alors la même logique flex que les icônes, sur PC, tablette
   ET smartphone. #app-header sert de bloc de confinement pour l'axe horizontal (position:sticky
   sur PC, position:fixed sur ≤1024px — les deux valent "relative" pour un enfant en absolute). */
.header-titre-groupe {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.app-header h1 {
  font-size: 17px;
  font-weight: 600;
  margin: 0;
}
/* Sous-titre "xx Évènement(s) aujourd'hui" sous le titre "Près de vous" (en-tête bleue) —
   demande explicite du 2026-07-31. Masqué par défaut (.hidden), affiché/rempli uniquement par
   showView()/renderVisitorList() quand #view-visiteur-liste est la vue active : ce texte n'a de
   sens que pour "Près de vous" (compte des évènements du jour dans le rayon/filtres courants).
   La hauteur supplémentaire nécessaire est ajoutée à --header-height UNIQUEMENT sur cette vue
   (voir #app:has(#view-visiteur-liste.active) ci-dessous) : comme tout le reste de l'app (contenu,
   panneau filtres, fiche évènement) se positionne via cette même variable, la bascule se propage
   automatiquement sans toucher chaque règle une par une, et redevient 70px partout ailleurs. */
.header-sous-titre {
  margin: 0;
  font-size: 11px;
  font-weight: 500;
  color: rgba(255,255,255,0.82);
  text-align: center;
}
.header-sous-titre.hidden { display: none; }
/* --header-height déclaré sur BODY (pas #app) depuis le 2026-08-20 : #modal-filtres/
   #modal-filtres-carte sont des enfants directs de <body> (pas de #app), donc une variable CSS
   redéfinie seulement sur #app ne leur était jamais transmise (les propriétés custom suivent
   l'arbre du DOM, pas une simple portée globale) — le panneau Filtres restait donc positionné
   sur l'ancien --header-height: 70px même sur les vues à en-tête 2 lignes (88px), chevauchant le
   bas du bandeau bleu (signalé par Christophe, captures guide + events, 2026-08-20). body:has()
   voit n'importe quel descendant quelle que soit sa profondeur, donc ce même sélecteur couvre à
   la fois #app (contenu) et #modal-filtres (popup) sans dupliquer la règle. */
body:has(#view-visiteur-liste.active) { --header-height: 88px; }
/* Fiche ouverte (#modal-event-detail) : header toujours 2 lignes (ville + accroche, voir
   appliquerTitreHeaderFiche() dans main.js), quelle que soit la vue en dessous (Carte/Favoris
   n'ont normalement qu'une ligne) — même bump 88px que "Près de vous"/"Mon compte" ci-dessus,
   pour ne jamais risquer un débordement/chevauchement avec des caractères plus grands (20px/16px,
   voir .au-dessus-fiche h1/.header-sous-titre) — demande de Christophe, 2026-08-30. */
body:has(#modal-event-detail:not(.hidden)) { --header-height: 88px; }
/* "Mon compte" : titre + palier actuel (sous-titre) alignés à droite plutôt que centrés — demande
   de Christophe, 2026-08-18. Scopé à cette vue uniquement (:has()), le reste (ex. "Près de vous")
   garde le centrage existant. */
#app:has(#view-visiteur-compte.active) .header-titre-groupe {
  align-items: flex-end;
  /* Décalé à 20px du bord droit (au lieu du centrage horizontal par défaut) — demande de
     Christophe, 2026-08-31. Même technique que #app-header.au-dessus-fiche (fiche évènement) :
     left:auto/transform:none annulent le centrage par position absolue de la règle de base
     (.header-titre-groupe), remplacés par un positionnement direct depuis la droite. */
  left: auto;
  right: 20px;
  transform: none;
}
#app:has(#view-visiteur-compte.active) .header-sous-titre { text-align: right; }
body:has(#view-visiteur-compte.active) { --header-height: 88px; }
.app-header .back-btn, .app-header .icon-btn {
  background: none;
  border: none;
  color: #fff;
  font-size: 20px;
  padding: 6px;
  cursor: pointer;
  line-height: 1;
}
/* "Flèche retour" clignotante (Icone_0206 <-> Icone_0198) : occupe la même place que le bouton
   hamburger dans le header pendant que la fiche détail est ouverte — déplacée du dessous de la
   photo (rangée d'icônes) vers la barre bleue, demande de Christophe, 2026-08-20. Affichage/
   masquage piloté par actualiserMasquageContenuSousFiche() (main.js), pas par openEventDetail :
   bouton statique désormais, plus recréé à chaque ouverture. Animation clignotante reprise à
   l'identique de l'ancien emplacement (2 images superposées, opacité en miroir, 1 clignotement/s). */
#btn-retour-fiche {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  padding: 0 6px;
}
#btn-retour-fiche img { width: 32px; height: 32px; }
#btn-retour-fiche img.icone-retour-frame-b { position: absolute; inset: 0; margin: auto; }
#btn-retour-fiche img.icone-retour-frame-a { animation: clignoter-icone-retour-a 1s steps(1) infinite; }
#btn-retour-fiche img.icone-retour-frame-b { animation: clignoter-icone-retour-b 1s steps(1) infinite; }
@keyframes clignoter-icone-retour-a { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
@keyframes clignoter-icone-retour-b { 0%, 49% { opacity: 0; } 50%, 100% { opacity: 1; } }
/* Icône "Retour" fixe (Icone_0217, sans clignotement) réservée au smartphone/tablette — masquée
   par défaut (PC), affichée à la place des 2 images clignotantes via le média query ≤1024px plus
   bas dans ce fichier — demande de Christophe, 2026-08-20. */
#btn-retour-fiche img.icone-retour-mobile { display: none; }
/* Icône hamburger blanche, agrandie à 3x (200% de plus que la taille de base des icônes
   de l'en-tête, 20px → 60px). display:flex + align-items:center pour centrer verticalement
   le glyphe dans le bouton (sinon les métriques de la police le décalent visuellement). */
#btn-menu-hamburger {
  color: #fff;
  font-size: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

/* Icône filtres (3 ronds, en-tête "Près de vous" + "Carte") : masquée par défaut, visible
   uniquement quand la vue active est #view-visiteur-liste OU #view-visiteur-carte — via :has()
   plutôt qu'un style inline en JS, pour ne jamais entrer en conflit de priorité CSS avec cette
   règle. Même hauteur pleine que le hamburger, pour un alignement visuel identique. Sur "Carte",
   ouvre #modal-filtres-carte (Type uniquement) au lieu de #modal-filtres — voir
   panneauFiltresVisiteurActifId() dans main.js, même principe que panneauFiltresActifId() côté
   annonceurs.icilago.eu ("Autour de moi") : demande de Christophe (2026-08-05), configurer
   Carte à l'identique de "Autour de moi". */
#btn-filtres {
  color: #fff;
  display: none;
  align-items: center;
  justify-content: center;
  height: 100%;
}
#btn-filtres svg { width: 47.5px; height: 47.5px; transform: translateY(8px); }
#app:has(#view-visiteur-liste.active) #btn-filtres,
#app:has(#view-visiteur-carte.active) #btn-filtres { display: flex; }

/* Bascule "Liste"/"Icônes" de "Mes favoris", déplacée dans la barre bleue du header — demande de
   Christophe, 2026-08-31 ("comme sur Autour de vous"), 20px du bord droit (10px puis 20px, même
   jour), centrée verticalement.
   CORRIGÉ le même jour (bug identique remonté côté events.icilago.eu, "le texte et le nombre ne
   sont pas centrés horizontalement", capture à l'appui — même code copié ici, même défaut) : la
   1re version en position:absolute sortait ce bouton du flux flex de .app-header. Sur "Mes
   favoris", #btn-filtres est display:none (masqué SANS réserver sa place, contrairement au cas
   #app-header.au-dessus-fiche qui utilise visibility:hidden pour ça) : il ne restait donc plus
   que 2 enfants flex visibles (hamburger + groupe titre), et justify-content:space-between
   poussait le groupe titre tout à droite du header — pile sous ce bouton posé en absolute au même
   endroit, d'où le chevauchement/texte tronqué (visible dès que le titre "Mes XX favoris" est
   devenu plus long que "Mes favoris"). Fix : bouton réintégré au flux flex normal (plus de
   position/top/transform), au même titre que #btn-filtres — la ligne redevient un vrai triptyque
   à 3 enfants (hamburger / titre centré / ce bouton) quel que soit l'écran, exactement comme
   #btn-filtres le fait déjà sur Près de vous/Carte. `margin-right:6px` (+ le padding 0 14px de
   .app-header) pour retrouver les ~20px du bord demandés. Centrage vertical hérité de
   `.app-header { align-items:center }`, pas besoin de height:100%/transform ici. Fond blanc +
   cadre blanc de 2px (même badge circulaire que .fav-toggle sur les vignettes) : icône
   Icone_0302/0304.png laissée dans sa couleur d'origine (PAS de filter:brightness(0) invert(1),
   retiré ici — nécessaire tant que le bouton était nu sur le bleu, devenu inutile/contre-productif
   une fois posé sur fond blanc). */
#btn-toggle-affichage-favoris {
  display: none;
  align-items: center;
  justify-content: center;
  margin-right: 6px;
  padding: 4px;
  background: #fff;
  border: 2px solid #fff;
  border-radius: 50%;
}
#btn-toggle-affichage-favoris img { width: 24px; height: 24px; display: block; }
#app:has(#view-visiteur-favoris.active) #btn-toggle-affichage-favoris { display: flex; }

/* Bouton menu (☰) désactivé sur l'écran "Créer un compte visiteur" (demande du 2026-07-28) : le
   menu latéral n'a pas de sens avant d'avoir un compte créé. Même technique :has() que
   #btn-filtres ci-dessus. */
#app:has(#view-visiteur-register.active) #btn-menu-hamburger { display: none; }
/* Une fois le hamburger masqué ci-dessus, le titre devient le seul enfant visible de la barre :
   justify-content:space-between le pousserait alors tout à gauche (au lieu de rester centré
   entre deux boutons). margin:auto sur l'axe flex le recentre ; position:static annule le
   centrage horizontal par position absolue de .header-titre-groupe (inutile/redondant ici
   puisqu'il n'y a plus qu'un seul enfant visible dans le header). transform:none obligatoire en
   plus de position:static : "transform" s'applique indépendamment de la valeur de "position" —
   sans cette ligne, le translateX(-50%) de .header-titre-groupe continuait à décaler le titre
   même après repassage en position:static, le recentrant deux fois (margin:auto + translateX). */
#app:has(#view-visiteur-register.active) #header-titre-groupe {
  position: static;
  transform: none;
  margin: 0 auto;
}

/* Panneau de filtres : ancré sous l'en-tête (à droite, y compris sur PC — le "transform" sur
   l'overlay crée un nouveau bloc de confinement pour sa feuille en position fixed, qui
   s'aligne donc sur la colonne centrée de 480px, comme le header, pas sur le bord réel de
   l'écran). Fond blanc, 200px de large, hauteur qui suit son contenu (pas de bottom:0) avec
   30px d'espace sous le dernier texte plutôt que d'occuper toute la hauteur de l'écran.
   #modal-filtres-carte (Type uniquement, vue "Carte") partage exactement ce même habillage —
   demande de Christophe (2026-08-05) : configurer Carte à l'identique de "Autour de moi". */
#modal-filtres, #modal-filtres-carte {
  top: 0;
  right: auto;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  align-items: flex-start;
  justify-content: flex-end;
  /* Fond assombri en fondu enchaîné plutôt qu'en instantané : transparent au repos, la
     classe .filtres-visible (ajoutée en JS un cran après le retrait de .hidden, pour laisser
     le temps au navigateur de peindre l'état de départ) l'amène à la même valeur sombre que
     les autres modales (voir --modal-overlay-opacite, règle juste en dessous). */
  background: rgba(0,0,0,0);
  transition: background 0.25s ease;
}
/* Fond assombri + flou UNIFIÉS avec toutes les autres popups (2026-09-01) — reviennent sur le
   réglage fin scopé à ces 2 panneaux uniquement (blur 4px / rgba 0.02) fait plus tôt le même jour :
   Christophe a explicitement demandé ensuite "Toutes les applications auront la meme valeurs (1
   seul réglage)" pour le blur/fond de TOUTES les popups (console > Réglages > Paramètres >
   "FLOUTAGE ET FOND PANNEAU", voir --modal-blur-px/--modal-overlay-opacite ci-dessous et
   [[icilago-console-floutage-fond-panneau-global]]) — ces 2 panneaux suivent donc maintenant la
   même variable globale que .modal-overlay, au lieu d'une valeur fixe qui leur était propre. */
#modal-filtres.filtres-visible, #modal-filtres-carte.filtres-visible { background: rgba(0,0,0,var(--modal-overlay-opacite, 0.5)); }
#modal-filtres, #modal-filtres-carte { backdrop-filter: blur(var(--modal-blur-px, 6px)); -webkit-backdrop-filter: blur(var(--modal-blur-px, 6px)); }
/* z-index sous le header (20), PAS 3000 hérité de .modal-overlay — demande de Christophe,
   2026-09-01 : "je veux que le panneau apparaisse depuis le dessous du header pas par dessus,
   juste l'effet d'apparition". Même technique que #panel-raccourcis-interets (voir plus bas dans
   ce fichier) : contenu inchangé, seul l'empilement change, pour donner l'impression que le
   panneau sort de sous la barre bleue au lieu de glisser par-dessus.
   #modal-filtres-carte INCLUS depuis le 2026-09-01 ("applique le meme traitement") : utilisé
   au-dessus de la carte Leaflet (vue "Carte"), dont les contrôles internes montent jusqu'à
   z-index:~1000 (voir commentaire sur .modal-overlay plus haut) — un z-index:18 aurait dû se faire
   recouvrir par la carte, résolu en isolant #map-container dans son propre contexte d'empilement
   (position:relative + z-index:0, voir ce sélecteur plus haut dans ce fichier) : les panes
   Leaflet ne rivalisent plus avec le z-index de la page. */
#modal-filtres, #modal-filtres-carte { z-index: 18; }
#modal-filtres .modal-sheet, #modal-filtres-carte .modal-sheet {
  position: fixed;
  /* + env(safe-area-inset-top) (2026-07-31, même correction que #modal-event-detail, voir
     [[icilago-events-fiche-evenement-safe-area-top]]) : sans elle, le panneau mordait sur la
     barre bleue d'en-tête sur les écrans avec encoche/Dynamic Island. 0 sans encoche : aucun
     effet sur les autres écrans. */
  top: calc(env(safe-area-inset-top, 0px) + var(--header-height));
  right: 0;
  left: auto;
  bottom: auto;
  width: 200px;
  max-width: 200px;
  max-height: none;
  border-radius: 0;
  margin: 0;
  padding: 14px 14px 30px;
  background: #fff;
  color: var(--color-text);
  overflow-y: auto;
  /* Déroulé du haut vers le bas : replié au-dessus de l'en-tête (translateY(-100%) de sa
     propre hauteur), déplié pile sous l'en-tête (translateY(0), position définie par le top
     ci-dessus). */
  transform: translateY(-100%);
  transition: transform 0.25s ease;
  /* Signalé par Christophe, 2026-08-20 : sur smartphone/tablette (PAS sur PC), taper sur "Tous"
     ne fait rien. Cause probable — déjà rencontrée sur #modal-event-detail pour un symptôme
     voisin sur tablette, voir plus bas dans ce fichier — : Safari mobile "avale" le 1er tap sur
     un élément tout en haut d'une zone tout juste devenue défilante (overflow-y:auto) pour
     distinguer tap/scroll. Mêmes 2 propriétés ajoutées ici par précaution (non confirmé sans
     test sur device réel, à valider par Christophe). */
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
#modal-filtres.filtres-visible .modal-sheet, #modal-filtres-carte.filtres-visible .modal-sheet { transform: translateY(0); }
/* Ombre blanche (flou 10px, .modal-sheet de base) retirée sur ce panneau — demande de
   Christophe, 2026-08-20. Ne garde que l'ombre de profondeur ; le liseret coloré défilant
   (border/background/animation, base .modal-sheet) n'est PAS concerné, laissé inchangé. */
#modal-filtres .modal-sheet, #modal-filtres-carte .modal-sheet { box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
/* cursor:pointer (2026-09-01) : ce texte ferme désormais le panneau au clic (voir
   cablerFermetureClicPanneauFiltres() dans main.js) — demande de Christophe, "Ajoute l'icone main
   aussi pour le clic sur le texte 'centres d'intérets'". */
#modal-filtres .hint, #modal-filtres-carte .hint { color: var(--color-text-muted); font-size: 11px; margin: 14px 0 4px; cursor: pointer; }
/* Textes agrandis d'une taille (2026-08-21) PUIS de 2 tailles supplémentaires (même jour,
   2e demande) — guide UNIQUEMENT : .hint 11->12->14px, .filter-pill 11.5->12.5->14.5px,
   .filtre-groupe-classe summary 12->13->15px. */
#modal-filtres .hint, #modal-filtres-carte .hint { font-size: 14px; }
#modal-filtres .hint:first-child, #modal-filtres-carte .hint:first-child { margin-top: 0; }
/* Ligne de séparation sous le titre "Centres d'intérêts" ajoutée le 2026-08-20 puis retirée le
   même jour (demande de Christophe : "enleve le traits sous centres d'intérets") — border-bottom
   supprimé, seul le majuscule forcé (demande suivante) reste. */
#modal-filtres .hint, #modal-filtres-carte .hint { text-transform: uppercase; }
/* Titre en rouge (demande de Christophe, 2026-08-20) : couvre les 3 états du titre dynamique
   (racine "Centres d'intérêts", nom du centre d'intérêt, nom du thème sélectionné — voir
   renderInteretFilterBar() dans main.js), un seul <p class="hint"> à la fois. Plus en gras
   (font-weight normal) depuis le 2026-08-21 : "Les centres d'intérêts, thèmes et sujets doivent
   être en régular". L'exception "PAS de rouge" pour le niveau 2/thème (class "hint-sujet",
   demande du 2026-08-20) a été ANNULÉE le 2026-08-21 ("met en rouge le thème") — le thème est
   donc à nouveau rouge comme les 2 autres niveaux, .hint-sujet ne sert plus qu'à documenter ce
   niveau dans le DOM (aucun style propre restant). */
#modal-filtres .hint, #modal-filtres-carte .hint { color: var(--color-danger, #c04848); font-weight: 400; }
/* Icône retour (Icone_0198.png) à gauche du titre dynamique, demande de Christophe, 2026-08-21 :
   clic = remonter d'un niveau (voir renderInteretFilterBar() dans main.js). Absente au niveau
   racine (pas de niveau au-dessus) : .hint garde alors sa marge habituelle, .hint-row se comporte
   comme un simple conteneur transparent autour d'elle. Titre collé au plus proche à droite de
   l'icône (3e demande du 2026-08-21, "déplace au plus proche à droite de l'icone le texte" —
   remplace le centrage flex:1/text-align:center essayé juste avant, abandonné) : .hint reprend sa
   largeur naturelle, justify-content:flex-start (comportement par défaut) le colle juste après
   l'icône, à 15px d'écart (gap ci-dessous). */
/* gap 6px -> 15px (demande de Christophe, 2026-08-21, "Déplace le texte à 15 px de l'icone"). */
#modal-filtres .hint-row, #modal-filtres-carte .hint-row { display: flex; align-items: center; gap: 15px; }
/* Espace supplémentaire entre "Tous" et la ligne icône/texte en dessous (demande de Christophe,
   2026-08-21) : s'ajoute au gap de 10px déjà posé par .filter-bar (règle plus haut) — scopé via
   :has() à la ligne AVEC icône uniquement (pas la racine, où .hint-row n'a pas "Tous" au-dessus
   d'elle mais en dessous, voir renderInteretFilterBar()). */
#modal-filtres .hint-row:has(.hint-icone-retour), #modal-filtres-carte .hint-row:has(.hint-icone-retour) { margin-top: 10px; }
/* margin: 0 UNIQUEMENT quand l'icône retour est présente (demande de Christophe, 2026-08-21,
   "centre le texte verticalement par rapport à l'axe de l'icone") : .hint garde par défaut une
   marge haut/bas ASYMÉTRIQUE (14px/4px, héritée de la règle de base ci-dessus) — avec
   align-items:center sur .hint-row, cette asymétrie décale le texte vers le bas par rapport au
   centre réel de l'icône (le centrage flexbox porte sur la boîte AVEC marges, pas sur le seul
   contenu). Scopé via :has() pour ne PAS retirer la marge au niveau racine (pas d'icône, .hint
   garde alors sa marge habituelle pour l'espacement avec "Tous" en dessous). */
#modal-filtres .hint-row:has(.hint-icone-retour) .hint, #modal-filtres-carte .hint-row:has(.hint-icone-retour) .hint { margin: 0; }
#modal-filtres .hint-icone-retour, #modal-filtres-carte .hint-icone-retour {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}
#modal-filtres .hint-icone-retour img, #modal-filtres-carte .hint-icone-retour img { width: 100%; height: 100%; display: block; }
/* Filtres en texte seul, sans badge/pastille : liste verticale simple. Espace entre les items
   (2px -> 10px, demande de Christophe, 2026-08-21 "Ajoute un espace entre les items") — nouvelle
   règle scopée #modal-filtres UNIQUEMENT ci-dessous, la base ci-dessus (partagée avec
   #modal-filtres-carte) reste à 2px pour ne pas toucher le filtre Type de la vue Carte. */
#modal-filtres .filter-bar, #modal-filtres-carte .filter-bar { display: flex; flex-direction: column; overflow: visible; gap: 2px; }
#modal-filtres .filter-bar, #modal-filtres-carte .filter-bar { gap: 10px; }
#modal-filtres .filter-pill, #modal-filtres-carte .filter-pill {
  flex-shrink: 0;
  background: none;
  border: none;
  border-radius: 0;
  padding: 4px 0;
  font-size: 11.5px;
  color: var(--color-text);
  text-align: left;
  white-space: normal;
  /* Ajouté avec le correctif ci-dessus (voir #modal-filtres .modal-sheet) pour le même
     signalement "Tous" muet sur mobile/tablette : supprime le délai ~300ms de détection
     double-tap-zoom sur ces pastilles tactiles, seconde piste possible en plus du scroll. */
  touch-action: manipulation;
  cursor: pointer;
}
#modal-filtres .filter-pill.active, #modal-filtres-carte .filter-pill.active { background: none; color: var(--color-primary); font-weight: 700; text-decoration: underline; }
/* Sujet sélectionné en rouge (demande de Christophe, 2026-08-21, "Met en rouge l'item sujet") :
   seul cas restant de .filter-pill.active dans #modal-filtres (les pastilles "Tous les thèmes de
   X"/"Tous les sujets de Y" ont été supprimées) — cohérent avec le rouge déjà utilisé pour le
   centre d'intérêt/thème sélectionné (.hint). #modal-filtres-carte concerné depuis le 2026-09-01
   (même filtre Intérêt/Thème/Sujet qu'#modal-filtres, voir #filter-interet-bar-carte). */
#modal-filtres .filter-pill.active, #modal-filtres-carte .filter-pill.active { color: var(--color-danger, #c04848); }
/* Texte agrandi d'une taille (2026-08-21) PUIS de 2 tailles supplémentaires (même jour, 2e
   demande) — étendu à #modal-filtres-carte le 2026-09-01 (même filtre, même contenu). Sujets déjà
   en régular par défaut (pas de font-weight ici), rien à changer pour la demande "régular". */
#modal-filtres .filter-pill, #modal-filtres-carte .filter-pill { font-size: 14.5px; }
/* Pastille "Tous" (texte fixe, pas une valeur console) forcée en majuscules — demande de
   Christophe, 2026-08-20, cohérence visuelle avec les noms de Centres d'intérêt. */
#modal-filtres .filter-pill-tous, #modal-filtres-carte .filter-pill-tous { text-transform: uppercase; }
/* Traits de séparation entre sujets (demande de Christophe, 2026-08-21, "ajoute les traits dans
   le filtre 'sujets'") : même trait que .filtre-groupe-classe (centres d'intérêt/thèmes) juste en
   dessous, absent sur le 1er sujet de la liste. Classe dédiée .filter-bar-sujets (voir main.js)
   pour ne pas ajouter ce trait sur "Tous" (aussi un .filter-pill direct de la .filter-bar
   externe). */
#modal-filtres .filter-bar-sujets .filter-pill, #modal-filtres-carte .filter-bar-sujets .filter-pill { border-top: 1px solid var(--color-border, #e5e7eb); padding-top: 10px; }
/* padding-top du 1er sujet aligné sur celui de .filtre-groupe-classe summary (8px, "padding: 8px
   0") — demande de Christophe, 2026-08-21 : le même espace Centre d'intérêt->Thème (10px de gap
   + 8px de padding du 1er summary = 18px) se retrouve entre Thème->Sujet. Avant ce correctif,
   c'était 4px ici (padding d'origine de .filter-pill), donnant un espace visiblement plus court. */
#modal-filtres .filter-bar-sujets .filter-pill:first-child, #modal-filtres-carte .filter-bar-sujets .filter-pill:first-child { border-top: none; padding-top: 8px; }

/* Accordéon Centres d'intérêts > Thèmes (.filtre-groupe-classe, nom hérité de l'ancien
   regroupement "Types évènments par Classe", retiré depuis — voir renderInteretFilterBar() dans
   main.js). Les pastilles restent stylées par les règles ci-dessus (#modal-filtres
   .filter-bar/.filter-pill), simplement imbriquées un niveau plus profond — les sélecteurs
   descendants s'appliquent sans changement. Étendu à #modal-filtres-carte le 2026-09-01 (même
   filtre Intérêt/Thème/Sujet, même contenu). */
#modal-filtres .filtre-groupe-classe, #modal-filtres-carte .filtre-groupe-classe { border-top: 1px solid var(--color-border, #e5e7eb); }
#modal-filtres .filtre-groupe-classe:first-of-type, #modal-filtres-carte .filtre-groupe-classe:first-of-type { border-top: none; }
#modal-filtres .filtre-groupe-classe summary, #modal-filtres-carte .filtre-groupe-classe summary {
  cursor: pointer;
  list-style: none;
  padding: 8px 0;
  /* Agrandi d'une taille (2026-08-21) PUIS de 2 tailles supplémentaires (même jour) : 12->13->15px.
     Plus en gras (font-weight normal) depuis le 2026-08-21 : "Les centres d'intérêts, thèmes et
     sujets doivent être en régular". */
  font-size: 15px;
  font-weight: 400;
  color: var(--color-text);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
#modal-filtres .filtre-groupe-classe summary::-webkit-details-marker, #modal-filtres-carte .filtre-groupe-classe summary::-webkit-details-marker { display: none; }
/* Flèche ▾ en CSS content::after REMPLACÉE par une vraie icône (Icone_0214.png, demande de
   Christophe, 2026-08-21) : voir remplirSummaryAvecIconeFleche() dans main.js — span (texte) +
   img (.icone-fleche-groupe) ajoutés comme vrais enfants du <summary>, justify-content:
   space-between (ci-dessus) les écarte comme avant. La rotation au clic ne s'applique plus (les
   groupes ne se déplient plus en place depuis la navigation par niveaux, voir plus haut). */
#modal-filtres .icone-fleche-groupe, #modal-filtres-carte .icone-fleche-groupe { margin-left: 8px; flex-shrink: 0; width: 13px; height: 13px; }
/* Conteneur gauche du <summary> (pastille couleur + texte, voir remplirSummaryAvecIconeFleche()
   dans main.js) — regroupe ces 2 éléments pour que le <summary> (justify-content:space-between)
   garde exactement 2 enfants directs (ce conteneur + la flèche), sans écarter la pastille du
   texte. */
#modal-filtres .summary-groupe-conteneur-gauche, #modal-filtres-carte .summary-groupe-conteneur-gauche { display: flex; align-items: center; min-width: 0; }
/* Pastille couleur du centre d'intérêt (Niveau 1 des filtres, demande de Christophe 2026-08-28 :
   "ajoute un rond à la couleur du centre d'intérêt à gauche du nom"), même couleur que le repère
   sur la carte (getInteretColor(), voir eventMarkerColor() dans main.js). Absente pour les
   thèmes/sujets (pas de couleur en console à ce niveau, voir call site). Étendu à
   #modal-filtres-carte le 2026-09-01. */
#modal-filtres .filtre-pastille-couleur-interet, #modal-filtres-carte .filtre-pastille-couleur-interet { width: 10px; height: 10px; border-radius: 50%; margin-right: 8px; flex-shrink: 0; }
#modal-filtres .filtre-groupe-classe .filter-bar, #modal-filtres-carte .filtre-groupe-classe .filter-bar { padding-bottom: 10px; }

/* Menu latéral coulissant, ouvert depuis l'icône hamburger de l'en-tête. Ombre légère
   uniquement sur le côté droit du panneau (décalage horizontal positif, pas de flou vertical).
   L'ancre est positionnée via --menu-anchor-left, une variable CSS mise à jour en JS
   (syncMenuLateralAnchor() dans main.js) à partir de la position RÉELLE de l'en-tête
   (getBoundingClientRect), plutôt que recalculée indépendamment en CSS — deux calculs
   séparés avec la même formule (position:fixed + left:50% + transform + max-width:480px)
   pouvaient diverger de quelques pixels selon la largeur de fenêtre. Ainsi le panneau est
   toujours pile aligné sur le bord gauche réel de l'en-tête. Largeur = celle du panneau
   (135px, portée à cette valeur le 2026-09-01, était 125px avant) avec overflow:hidden : replié
   (translateX(-100%) du panneau), il sort entièrement de cette bande et devient invisible ;
   déplié, il la remplit exactement. */
.menu-lateral-anchor {
  position: fixed;
  top: 0;
  left: var(--menu-anchor-left, 0px);
  bottom: 0;
  width: 135px;
  z-index: 500;
  pointer-events: none;
  overflow: hidden;
  /* transform:translateZ(0) retiré de la règle de base et SCOPÉ au-delà de 480px ci-dessous
     (2026-09-02, suite à une régression) : établit un bloc de confinement pour les descendants
     position:fixed (voir le commentaire de .menu-lateral juste en dessous — sans ça, overflow:
     hidden ci-dessus ne clippait RIEN). Nouveau bug découvert avec ce correctif : le panneau
     FERMÉ (translateX(-100%), .menu-lateral ci-dessous) se décale de sa propre largeur par
     rapport à --menu-anchor-left — sur mobile (--menu-anchor-left≈0) ce décalage sort bien du
     VRAI bord gauche du navigateur, invisible SANS AUCUN besoin de confinement ; le confinement
     n'est donc utile QUE sur desktop large (#app centré, --menu-anchor-left grand), où le panneau
     fermé retombait sinon dans la marge grise à gauche de l'app (encore DANS le viewport) au lieu
     de sortir de l'écran. Or transform (même translateZ(0)) crée aussi un nouveau contexte de
     composition GPU : suspecté d'être la cause d'une "bande blanche" persistante en bas de
     l'accueil, constatée UNIQUEMENT sur iPhone 15 Pro Max (pas sur un iPhone 16e), signalée par
     Christophe le 2026-09-02 — cet élément est position:fixed/top:0/bottom:0 (pleine hauteur) et
     donc TOUJOURS présent dans le DOM dès le premier écran, y compris sur mobile où il n'apportait
     jusqu'ici aucun bénéfice fonctionnel. Restreint donc à la largeur où il sert réellement (voir
     @media plus bas) — 3 correctifs différents tentés côté #app/.hero sans succès avant de
     remonter à cette cause. Voir aussi icilago-annonceurs-2-menus-lateraux-simultanes.md (mémoire
     projet, bug de confinement découvert sur annonceurs.icilago.eu). */
}
@media (min-width: 481px) {
  .menu-lateral-anchor { transform: translateZ(0); }
}
.menu-lateral {
  position: fixed;
  top: 0;
  /* left:0, RELATIF à .menu-lateral-anchor (voir son commentaire ci-dessus, transform:translateZ(0)
     en fait le bloc de confinement de ce panneau) — plus relatif à la fenêtre comme avant. */
  left: 0;
  bottom: 0;
  width: 135px;
  background: #fff;
  transform: translateX(-100%);
  transition: transform .25s ease;
  overflow-y: auto;
  pointer-events: auto;
  /* 10px sous la barre de statut du smartphone (heure/encoche), pas juste sous le haut de
     l'écran : le logo doit rester à distance constante de l'heure sur tous les appareils. */
  padding-top: calc(env(safe-area-inset-top, 0px) + 10px);
  display: flex;
  flex-direction: column;
  align-items: center;
}
.menu-lateral-logo-btn { background: none; border: none; padding: 0; cursor: pointer; display: block; text-align: center; }
.menu-lateral-logo { width: 70px; height: auto; }
.menu-lateral-logo-label { display: block; margin-top: 4px; font-size: 12px; font-weight: 700; color: var(--color-text-muted); }
/* Trait entre le logo et l'icône : 20px sous le logo, 5px avant le haut de l'icône suivante
   (espacement volontairement asymétrique), largeur 115px = 135px du panneau - 10px - 10px,
   centré par le align-items:center du parent flex. */
.menu-lateral-separateur {
  width: 115px;
  height: 2px;
  margin-top: 20px;
  margin-bottom: 5px;
  background: #191557;
  border: none;
}
/* Icône d'accès au profil visiteur, sous le trait (espacement porté par la marge basse du
   trait ci-dessus, pas de marge supplémentaire ici). */
.menu-lateral-icone {
  margin-top: 0;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.menu-lateral-icone img { width: 60px; height: 60px; }
/* 5px entre l'icône et le texte ; 5px sous le texte avant le trait qui suit. */
.menu-lateral-icone-label { margin-top: 0; margin-bottom: 10px; font-size: 12px; color: var(--color-text-muted); }
/* Un trait après une icône (ex. sous "Mon compte") n'a pas sa propre marge du dessus : l'espace
   de 5px est déjà porté par la marge basse du texte ci-dessus. Sa marge du dessous (10px,
   héritée de .menu-lateral-separateur) reste inchangée pour espacer l'icône suivante. */
.menu-lateral-icone + .menu-lateral-separateur { margin-top: 0; }
/* L'ombre n'est appliquée qu'une fois le panneau ouvert : repliée, même clippée par
   overflow:hidden sur l'ancre, elle déborderait de ~11px (décalage 3px + flou 8px) dans la
   zone visible juste à côté de l'en-tête, car ce débordement reste À L'INTÉRIEUR des bornes
   de l'ancre et n'est donc jamais coupé — c'était la bande grise visible à tort. */
.menu-lateral.ouvert {
  transform: translateX(0);
  box-shadow: 3px 0 8px rgba(0,0,0,0.15);
}
/* Voile flouté à l'ouverture du menu latéral, même traitement que l'ouverture d'une popup (voir
   .modal-overlay : rgba(0,0,0,0.5) + blur(6px)) — demande de Christophe, 2026-08-20, toutes
   applications (events/guide/annonceurs). La transition existante sur opacity (0→1) fait
   apparaître voile ET flou ensemble en fondu, pas d'un coup.
   Valeurs pilotées par console > Réglages > Paramètres > "FLOUTAGE ET FOND PANNEAU" (variables
   --modal-blur-px/--modal-overlay-opacite posées par appliquerCouleursEnvironnementDepuisApi() au
   démarrage, voir js/main.js) — demande de Christophe, 2026-09-01. Fallback = valeurs historiques. */
.menu-lateral-backdrop {
  position: fixed;
  /* top/right/bottom/left/width/max-width AU LIEU DE inset:0 (2026-09-01) : même correctif que
     .modal-overlay plus bas (voir son commentaire pour le détail) — le voile ne doit couvrir que
     la colonne de l'application (480px), pas toute la largeur du navigateur. */
  top: 0;
  right: auto;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  background: rgba(0,0,0,var(--modal-overlay-opacite, 0.5));
  backdrop-filter: blur(var(--modal-blur-px, 6px));
  -webkit-backdrop-filter: blur(var(--modal-blur-px, 6px));
  z-index: 499;
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s ease;
}
.menu-lateral-backdrop.visible { opacity: 1; pointer-events: auto; }

/* "À propos" : modale classique (#modal-apropos, voir .modal-overlay/.modal-sheet), plus de
   cadre latéral dédié. Centrée à l'écran (RÈGLE POUR TOUS LES POPUPS de l'app, faits et à
   venir — voir mémoire) plutôt qu'en feuille du bas, même technique de centrage indépendant
   du flex que les autres modales ci-dessous. */
#modal-apropos .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
#modal-apropos p { margin: 0 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--color-text); }
/* Popup "Rechercher" (icône navigation basse) : même centrage indépendant du flex que
   #modal-apropos ci-dessus — demande de Christophe, 2026-08-31. */
#modal-recherche .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
/* Icône 29.png à droite de "Service clients" (À propos), tooltip au survol/clic — demande de
   Christophe, 2026-08-21, guide ET events. Même recette CSS que .icone-sous-photo/.detail-icone-
   legende[data-tooltip] déjà utilisées ailleurs dans l'app (voir plus haut) : bouton (focusable au
   tap, contrairement à un <span>, donc :focus déclenche aussi le tooltip sur mobile/tactile, pas
   seulement :hover). Pas de transition (affichage immédiat, demande explicite). */
.icone-service-clients-tooltip {
  position: relative;
  display: inline-flex;
  vertical-align: middle;
  border: none;
  background: none;
  padding: 0;
  margin-left: 4px;
  cursor: pointer;
}
.icone-service-clients-tooltip img { width: 30px; height: 30px; display: block; }
/* Disparition automatique de TOUTES les tooltips CSS pures de ce fichier après 5s MAXIMUM, même si
   le survol/focus/appui persiste au-delà — demande de Christophe, 2026-08-30, "pour toutes les
   applications" (voir aussi events/annonceurs/console). Sans ça, un tooltip pouvait rester affiché
   indéfiniment (souris immobile dessus, ou :focus qui persiste sur mobile après un tap tant que
   l'utilisateur ne touche pas ailleurs). Deux variantes selon que l'apparition était déjà
   INSTANTANÉE (.icone-service-clients-tooltip, .detail-icone-legende — pas de transition) ou EN
   FONDU ~150ms (.icone-sous-photo — transition:opacity .15s déjà posée) : reproduit l'apparition
   d'origine à l'identique, seule la disparition à 5s est nouvelle. Si le survol/focus se termine
   AVANT 5s (cas normal), l'animation est retirée avec le sélecteur :hover/:focus/:active et la
   disparition reprend l'ancien comportement (transition 150ms ou instantané) — inchangé. */
@keyframes icilago-tooltip-auto-hide-instant {
  0%, 98% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes icilago-tooltip-auto-hide-fade {
  0% { opacity: 0; }
  3% { opacity: 1; }
  98% { opacity: 1; }
  100% { opacity: 0; }
}
.icone-service-clients-tooltip[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 100%;
  left: 0;
  margin-bottom: 8px;
  background: var(--color-primary);
  color: #fff;
  font-size: 12px;
  line-height: 1.3;
  padding: 6px 10px;
  border-radius: 6px;
  white-space: normal;
  width: 220px;
  opacity: 0;
  pointer-events: none;
  z-index: 20;
}
.icone-service-clients-tooltip[data-tooltip]:hover::after,
.icone-service-clients-tooltip[data-tooltip]:focus::after,
.icone-service-clients-tooltip[data-tooltip]:active::after {
  animation: icilago-tooltip-auto-hide-instant 5s ease forwards;
}
#modal-apropos a { color: var(--color-primary); }

/* "Installer l'application" (PWA) : même règle de centrage que toutes les popups de l'app. */
#modal-installer-pwa .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 360px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
#modal-installer-pwa p { margin: 0 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--color-text); }

/* Détail d'une mention légale (contenu chargé depuis /mentions-legales, propre à l'application
   courante), ouvert depuis la liste de #modal-apropos. Même règle de centrage. */
#modal-mention-legale .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  overflow-y: auto;
  border-radius: 16px;
  margin: 0;
}
#mention-legale-contenu { font-size: 13.5px; line-height: 1.5; color: var(--color-text); }

/* Popups restants qui n'avaient pas encore été centrés (suppression de compte, abonnement,
   position manuelle, réservation, paiement) — même règle : tous les popups doivent être
   centrés à l'écran, pas en feuille du bas. */
#modal-suppr-etape1 .modal-sheet,
#modal-suppr-etape2 .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 360px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
#modal-abonnement .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
/* "Type d'accès" (après vérification e-mail), même technique de centrage. Peut désormais s'ouvrir
   PAR-DESSUS d'autres pop-ups déjà affichées (#modal-abonnement, #modal-apropos — voir
   [[icilago-apropos-voir-differents-acces]]) : toutes les .modal-overlay partagent le même
   z-index:3000, donc c'est l'ORDRE DANS LE DOM qui décide laquelle passe au-dessus en cas
   d'égalité. #modal-type-acces est plus TÔT dans le DOM que #modal-apropos (index.html) — sans
   z-index dédié, elle s'ouvrait donc DERRIÈRE "À propos" au lieu de passer devant, signalé par
   Christophe, 2026-08-21. z-index:3001 la fait désormais toujours passer au-dessus, quel que soit
   son ordre dans le DOM par rapport à la pop-up depuis laquelle elle est ouverte. */
#modal-type-acces { z-index: 3001; }
#modal-type-acces .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
/* Détail d'un palier ("!"/"Actuel", ouvert PAR-DESSUS #modal-type-acces sans le refermer) : 10px
   de moins de large de chaque côté que son popup parent ci-dessus (demande de Christophe,
   2026-08-13, "sous popup" 10px plus étroite à droite et à gauche — se répète à chaque fois
   qu'une popup s'ouvre par-dessus une autre déjà ouverte, voir aussi #modal-fil-messagerie-visiteur
   et #modal-voir-affiche plus bas). z-index:3002 (1 de plus que #modal-type-acces, voir juste
   au-dessus) pour rester par-dessus même maintenant que #modal-type-acces a son propre z-index
   dédié — sinon ce sous-popup se retrouverait en dessous de son propre parent. */
#modal-detail-palier { z-index: 3002; }
#modal-detail-palier .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 60px);
  max-width: 400px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
/* Popup "informations" (icône à côté d'un service/info perso dans le détail palier) — même
   centrage que ci-dessus mais en propre (demande explicite du 2026-07-28) : largeur réduite de
   20% (calc(80% - 32px)/336px = 0.8 * calc(100% - 40px)/420px), fond rouge très clair, position
   relative pour ancrer l'icône de fermeture (193.png) en absolu à 5px du haut et de la droite. */
#modal-commentaire-service-visiteur .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(80% - 32px);
  max-width: 336px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
  background: #fff0f0;
}
/* Une ligne par palier = une carte colorée (niveaux_acces.couleur, réglé en console) contenant
   le nom du palier + un bouton par durée (mois/année, prix HT — demande explicite du
   2026-07-28, plus de conversion de devise réelle : voir mémoire) + un bouton rond "!" ouvrant
   le détail, à côté de la carte. */
.type-acces-ligne { display: flex; align-items: stretch; gap: 8px; margin-bottom: 10px; }
.type-acces-carte {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 12px;
  border-radius: 10px;
  color: #fff; /* repli : la couleur réelle est posée en inline par renderTypeAccesModal(),
                  calculée pour contraster avec la couleur de fond (niveau.couleur, réglable en
                  console) — voir couleurTexteContrastee() dans main.js. */
}
/* Nom du palier forcé en majuscules (demande de Christophe, 2026-08-14) : le nom vient désormais
   d'un champ de saisie libre en console (renommage des paliers), la casse tapée par l'admin ne
   doit pas rendre l'affichage incohérent d'une carte à l'autre ("Accès libre" / "BASIC" /
   "Premium"...). Forcé ici en CSS plutôt qu'en JS pour rester correct avec les accents. */
.type-acces-carte strong { font-size: 15px; text-transform: uppercase; }
.type-acces-prix { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px; }
.type-acces-choix {
  border: none;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.92);
  color: #222;
  font-size: 12.5px;
  font-weight: 600;
  padding: 5px 9px;
  cursor: pointer;
}
/* Hérite de la couleur de texte posée sur .type-acces-carte (contraste calculé) plutôt qu'un
   blanc fixe, sinon illisible sur les couleurs de palier claires (ex. jaune/or). */
.type-acces-tva { width: 100%; text-align: center; font-size: 10.5px; color: inherit; opacity: 0.85; }
/* Badge "Actuel" (palier réellement actif du visiteur) — remplace le prix/bouton "Gratuit" sur
   la carte correspondante, clignote en vert — demande de Christophe, 2026-08-13. Même forme que
   .type-acces-choix pour rester aligné visuellement avec les autres cartes. C'est un vrai
   <button> (cliquable, ouvre la fiche détail du palier en consultation — demande du même jour),
   d'où le reset border/cursor/font qu'un <span> n'aurait pas eu besoin. */
.type-acces-actuel-badge {
  display: inline-block;
  border: none;
  border-radius: 8px;
  /* Vert plus soutenu que #2ecc71 : texte blanc lisible même en pleine opacité (contraste ~4.8:1,
     demande de Christophe, 2026-08-13 — l'ancien fond trop clair rendait le texte foncé peu
     contrasté une fois en clignotement). */
  background: #1e8449;
  color: #fff;
  font-size: 12.5px;
  font-weight: 700;
  font-family: inherit;
  padding: 5px 12px;
  cursor: pointer;
}
@keyframes clignoter-type-acces-actuel { 0%, 49% { opacity: 1; } 50%, 100% { opacity: .35; } }
.type-acces-actuel-badge.clignote { animation: clignoter-type-acces-actuel 1s steps(1) infinite; }
.type-acces-info {
  flex: 0 0 auto;
  width: 40px;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: #fff;
  font-weight: 700;
  font-size: 16px;
  color: var(--color-text);
  cursor: pointer;
}
/* Fond coloré du titre = couleur du palier réglée en console (Tarifs > Abonnements visiteurs),
   posée en JS via ouvrirDetailPalier() — demande de Christophe, 2026-08-13. */
#detail-palier-titre { padding: 10px 14px; border-radius: 10px; }
.detail-palier-liste { margin: 10px 0 0; padding-left: 20px; }
.detail-palier-liste li { margin-bottom: 6px; font-size: 14px; }
/* Icône "en savoir plus" par ligne de service/info perso — uniquement présente si un commentaire
   existe pour ce champ (voir sectionDetailPalier() dans main.js), ouvre #modal-commentaire-service-visiteur
   au clic (mobile-friendly : pas de survol requis, contrairement à un title natif). */
.detail-palier-icone-commentaire {
  margin-left: 6px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  vertical-align: middle;
}
.detail-palier-icone-commentaire img { width: 18px; height: 18px; vertical-align: middle; display: inline-block; }
/* Icône de fermeture de la popup "informations" (193.png), en haut à droite — remplace le bouton
   "Fermer" texte (demande explicite du 2026-07-28). Ancrée sur #modal-commentaire-service-visiteur
   .modal-sheet (position:fixed, sert aussi de contexte de positionnement pour l'absolu ici). */
.commentaire-service-visiteur-fermer {
  position: absolute;
  top: 5px;
  right: 5px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}
.commentaire-service-visiteur-fermer img { width: 22px; height: 22px; display: block; }
#modal-position .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 480px;
  max-height: 85vh;
  max-height: 85dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
#modal-ma-position .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 480px;
  max-height: 85vh;
  max-height: 85dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  overflow-y: auto;
  border-radius: 16px;
  margin: 0;
}
#modal-reservation .modal-sheet,
#modal-paiement .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 85vh;
  max-height: 85dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}

/* Sur smartphones/tablettes, la barre du haut est fixée à l'écran (au lieu de "sticky") :
   plus robuste face aux comportements de la barre d'adresse mobile (Safari/Chrome) qui
   pouvaient la faire disparaître ou sauter pendant le défilement. */
@media (max-width: 1024px) {
  .app-header {
    position: fixed;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 100%;
    max-width: 480px;
    /* Pousse le contenu de la barre sous la barre de statut iOS (celle-ci est rendue
       transparente par apple-mobile-web-app-status-bar-style=black-translucent en mode
       "ajouté à l'écran d'accueil", donc sans ce padding le titre/boutons seraient masqués
       par l'horloge/la batterie). Sans encoche/safe-area, env() vaut 0 : aucun effet. */
    padding-top: env(safe-area-inset-top, 0px);
    box-sizing: content-box;
  }
  /* Ce padding doit vivre sur #app-contenu (pas sur #app) : #app-contenu est le bloc de
     confinement de l'en-tête (position:fixed) une fois que la poussée du menu latéral lui
     applique un transform. Si le padding était sur #app, le bord haut de #app-contenu se
     retrouverait décalé de cette hauteur, et l'en-tête (top:0 relatif à ce bloc) descendrait
     d'autant à l'ouverture du menu — bug observé : l'en-tête "tombe" quand le menu s'ouvre. En
     le mettant directement sur #app-contenu, le padding pousse son CONTENU en flux normal
     (les vues) vers le bas, mais pas l'en-tête, qui est hors flux (position:fixed) et reste
     ancré au bord haut réel de #app-contenu (top:0), quel que soit l'état du menu. */
  #app-contenu { padding-top: calc(var(--header-height) + env(safe-area-inset-top, 0px)); }
  /* "Flèche retour" du header sur smartphone/tablette : remplace les 2 images clignotantes
     (Icone_0206/0198, conservées telles quelles sur PC — demande explicite de Christophe) par
     Icone_0217 FIXE, sans clignotement — même correctif que events.icilago.eu, demande de
     Christophe, 2026-08-20. Egalement agrandie (32px → 48px, plus proche de l'échelle du
     hamburger/filtres qu'elle remplace) suite à un 1er signalement le même jour. */
  #btn-retour-fiche img.icone-retour-frame-a,
  #btn-retour-fiche img.icone-retour-frame-b { display: none; }
  #btn-retour-fiche img.icone-retour-mobile { display: block; width: 48px; height: 48px; }
  /* Menu bas, smartphone/tablette : voir le média query DÉDIÉ juste après la règle de base
     .bottom-nav plus bas dans ce fichier (padding-bottom:10px) — PAS ici. Une 1ère tentative
     avait placé cette même règle ICI, AVANT la déclaration de base .bottom-nav (plus loin dans
     le fichier) : à spécificité égale, la règle la PLUS TARDIVE dans le fichier l'emporte,
     donc la base (padding-bottom: env(safe-area-inset-bottom)) écrasait silencieusement ce
     correctif — le trait redevenait visible malgré le "correctif" appliqué (signalé par
     Christophe : "le trait est réapparu... donc mal dimensionné"). Règle déplacée après la
     base pour que l'ordre du fichier joue en sa faveur. */
}

/* ---------- Main content ---------- */
.view {
  flex: 1;
  padding: 14px;
  padding-bottom: 24px;
  display: none;
}
.view.active { display: block; }
/* Masque le contenu de la vue derrière la fiche détaillée d'un évènement (#modal-event-detail) —
   voir actualiserMasquageContenuSousFiche() dans main.js. visibility:hidden (pas display:none) :
   la vue garde sa place/sa hauteur, aucun saut visuel/scroll à la réouverture. */
.view.active.contenu-masque-par-fiche { visibility: hidden; }
#view-visiteur-auth.active { padding-top: 50px; }
/* "Accès libre" et "Accès connecté" : même cadre (rôle-card et card-connexion partagent le
   même fond/arrondi/ombre/padding), ancrés ensemble à 50px du bas de l'écran réel — même
   technique que les boutons de l'accueil (position fixe indépendante du flux/scroll). */
.card-connexion { padding: 18px; }
/* En-tête icône + titre + description du bloc "Accès connecté", même agencement que
   l'icône/titre/texte du role-card "ACCÈS LIBRE" juste au-dessus. */
.connexion-entete { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.connexion-entete .emoji { font-size: 30px; }
.connexion-entete h3 { margin: 0 0 3px; font-size: 15px; }
.connexion-entete p { margin: 0; font-size: 12.5px; color: var(--color-text-muted); }
#view-visiteur-auth.active .visiteur-auth-blocs {
  position: fixed;
  left: 50%;
  bottom: 50px;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  padding: 0 14px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 30px;
  z-index: 15;
}

/* ---------- Bottom nav ---------- */
.bottom-nav {
  position: fixed;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  height: var(--nav-height);
  /* Réserve la zone de sécurité basse (barre gesture iPhone/iPad récents sans bouton Home) à
     l'INTÉRIEUR de cette hauteur fixe (box-sizing: border-box, règle globale en haut du fichier)
     — les icônes se recentrent donc dans l'espace restant, au lieu de déborder dans cette zone
     où elles peuvent être masquées par l'UI système ou la barre du bas de Safari (signalé sur
     tablette par Christophe, capture iPad, 2026-08-19 : "le bas des pages n'est pas visible").
     0px de repli sur les navigateurs qui ne supportent pas env(). */
  padding-bottom: env(safe-area-inset-bottom, 0px);
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  display: flex;
  justify-content: space-evenly; /* même espace entre les icônes ET entre les bords et les icônes de chaque extrémité */
  z-index: 30;
}
/* Sur smartphone/tablette, env(safe-area-inset-bottom) peut monter jusqu'à ~34px sur les
   appareils à encoche/barre gesture — avec box-sizing:border-box (règle globale, hauteur totale
   fixée à 62px), ça ne laissait que 28px pour les icônes+libellés, qui chevauchaient alors le
   trait du haut (signalé par Christophe, captures à l'appui, 2026-08-20). padding-bottom passe
   à une valeur FIXE de 10px sur cette largeur d'écran (au lieu du env() variable) : 52px restent
   disponibles pour le contenu, largement suffisant, tout en gardant le "10px du bas de l'écran"
   demandé. DOIT rester APRÈS la règle de base .bottom-nav ci-dessus (spécificité égale, c'est
   l'ordre dans le fichier qui décide) — un 1er essai placé plus haut dans le média query partagé
   du header était silencieusement écrasé par cette base, d'où la réapparition du bug malgré le
   "correctif". box-sizing reste border-box (ne pas remettre content-box : ça agrandit la barre
   et la fait remonter, régression déjà rencontrée le même jour). */
@media (max-width: 1024px) {
  .bottom-nav { padding-bottom: 10px; }
}
/* Classe utilitaire générique "masqué" — utilisée par de nombreux classList.toggle('hidden', …)
   dans main.js (cartes "Mes centres d'intérêt"/"Mes recherches prioritaires", options de
   notifications, etc.) qui n'avaient jusqu'ici PAS d'effet visuel réel : seules deux règles plus
   spécifiques (.bottom-nav.hidden, .modal-overlay.hidden ci-dessous) existaient, laissant tous
   les autres éléments "hidden" affichés quand même. Découvert en ajoutant le masquage conditionnel
   des cartes de "Mes paramètres" (2026-07-26). */
.hidden { display: none !important; }
.bottom-nav.hidden { display: none; }
/* Bouton "Remonter en haut" (icône 211), vue "Autour de vous" (guide uniquement) — demande de
   Christophe, 2026-08-31. Centré horizontalement dans la colonne de l'appli via left:50% +
   transform:translateX(-50%), APPLIQUÉ SANS condition de largeur (même technique que .bottom-nav
   ci-dessus) pour rester centré même en desktop où l'appli elle-même est centrée par ce même
   procédé. Positionné 10px sous la barre bleue via --header-height (variable dynamique posée sur
   body, 88px sur cette vue à 2 lignes). Masqué par défaut (display:none), affiché en ajoutant
   .visible — géré par actualiserBtnRemonterListe() dans main.js, au-delà de la 6ème
   vignette/carte (3ème ligne en Icônes ou 6ème ligne en Liste, même indice dans les deux cas).
   z-index:15, sous le header (z-index:20) mais au-dessus du contenu de la liste. Fond/ombre
   circulaire retirés le 2026-08-31 (demande de Christophe). Contour blanc de 2px ajouté le même
   jour, PAS directement en `border` sur le bouton (essai initial) : le PNG source 211 (350x351)
   contient un padding transparent important autour du rond bleu marine dessiné dedans (rond
   opaque mesuré à 260x261px sur les 350x351, soit ~74.3% — le reste est transparent), donc un
   contour calé sur la boîte pleine de l'image (40x40px) restait visiblement décollé du bord du
   rond (signalé par Christophe, capture à l'appui). Corrigé avec un ::before positionné en retrait
   de 5px de chaque côté (40 × (1 - 260/350) / 2 ≈ 5.14px, arrondi) — un cercle de ~30px qui vient
   se caler exactement sur le bord du rond bleu marine visible dans l'icône, indépendamment de la
   boîte de l'image elle-même. */
#btn-remonter-liste {
  display: none;
  position: fixed;
  /* env(safe-area-inset-top) ajouté le 2026-09-08 (signalé par Christophe : icône invisible en
     PWA installée/standalone, fonctionnait en onglet navigateur classique) — en standalone,
     .app-header gagne un padding-top: env(safe-area-inset-top) pour se pousser sous la barre de
     statut (voir apple-mobile-web-app-status-bar-style: black-translucent), donc sa hauteur
     réelle dépasse --header-height seul. Sans ce terme, le bouton se positionnait DANS la zone du
     header (recouvert par son fond opaque, z-index supérieur) en standalone uniquement, alors que
     env(safe-area-inset-top) vaut 0 dans un onglet classique (d'où le fonctionnement correct là). */
  top: calc(env(safe-area-inset-top, 0px) + var(--header-height, 70px) + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 15;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
#btn-remonter-liste.visible { display: flex; }
#btn-remonter-liste::before {
  content: '';
  position: absolute;
  top: 5px;
  left: 5px;
  right: 5px;
  bottom: 5px;
  border: 2px solid #fff;
  border-radius: 50%;
  pointer-events: none;
}
#btn-remonter-liste img { width: 40px; height: 40px; display: block; }
.nav-btn {
  flex: 0 0 auto;
  border: none;
  background: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  color: var(--color-text-muted);
  font-family: inherit;
  font-size: 11px;
  font-weight: normal;
  cursor: pointer;
  padding: 4px;
}
.nav-btn .nav-icon { position: relative; font-size: 20px; display: flex; align-items: center; justify-content: center; }
.nav-btn .nav-icon img { width: 22px; height: 22px; object-fit: contain; display: block; }
/* Pastille "messages à traiter" (icône Messagerie, menu bas visiteur) : affiche le nombre de fils
   avec au moins une réponse ICILAGO non lue — même principe que côté annonceurs.icilago.eu et
   console.icilago.eu (demande de Christophe, 2026-08-04). Se réduit au fil de la lecture des fils
   (Api.Visiteur.messagesMarquerLu) et disparaît à 0. */
.pastille-nav-btn-nombre {
  position: absolute;
  top: -6px;
  right: -8px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--color-danger, #c0392b);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--color-surface);
}
.pastille-nav-btn-nombre.hidden { display: none; }

/* ---------- Cards / lists ---------- */
.card {
  background: var(--color-surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px;
  margin-bottom: 12px;
}
/* Liseret bleu ICILAGO autour d'une carte précise (ex. "Mes informations", Mon compte) — même
   correctif que events.icilago.eu, demande de Christophe, 2026-08-21. Simple bordure statique, à
   ne pas confondre avec le liseret DÉFILANT des popups (.modal-sheet, voir
   [[icilago-popups-liseret-defilant]]) qui reste réservé aux modales. */
.card-liseret-bleu { border: 2px solid var(--color-primary); }
/* Fond rouge + textes blancs pour "Zone de danger" (Mon compte) — même correctif que
   events.icilago.eu, demande de Christophe, 2026-08-21. */
.card-fond-danger { background: var(--color-danger); }
.card-fond-danger .section-title, .card-fond-danger .hint { color: #fff; }
/* Bouton "Enregistrer" (Mon compte > Infos) clignotant en vert tant qu'une modification n'est
   pas enregistrée — même correctif que events.icilago.eu, demande de Christophe, 2026-08-21.
   Classe togglée en JS (signalerModificationCompte / masquerBanniereModificationsCompte). */
@keyframes clignoter-btn-enregistrer-vert { 0%, 49% { background: #2ecc71; color: #fff; border-color: #2ecc71; } 50%, 100% { background: #fff; color: var(--color-primary); border-color: var(--color-primary); } }
/* "Envoyer" (modale "Message à ICILAGO", #modal-messagerie-visiteur) : même clignotement vert,
   mais TOUJOURS actif dès l'ouverture (pas de condition de modification) — demande de Christophe,
   2026-09-11. :not([disabled]) coupe l'animation le temps du bref disabled posé pendant l'envoi. */
#btn-envoyer-messagerie-visiteur:not([disabled]) { animation: clignoter-btn-enregistrer-vert 1s steps(1) infinite; }
#btn-enregistrer-compte-profil.clignote-vert { animation: clignoter-btn-enregistrer-vert 1s steps(1) infinite; }
/* La photo de la liste des événements (visiteur) est agrandie, carrée, avec une marge fixe
   de 5px de tous les côtés (taille figée en dur — les approches dynamiques précédentes,
   aspect-ratio/align-self:stretch, ne s'affichaient pas de façon fiable). Le padding de
   .card est entièrement délégué à .info (le texte) : le conteneur flex lui-même n'a plus de
   padding, ce qui laisse la marge de la photo (5px) seule responsable de l'espacement. */
.event-card {
  display: flex;
  gap: 0;
  cursor: pointer;
  padding: 0;
  /* min-height: 200px retiré (demande de Christophe, 2026-08-23) : c'était lui qui forçait le
     cadre à rester haut même quand le contenu de .info (dernier élément = badges ville/distance/
     vues) est plus court que 200px, laissant un grand espace blanc en bas de la carte (padding-
     bottom de .info seul ne suffisait pas puisque .info s'étire par défaut, align-items:stretch,
     pour combler ce min-height). Sans min-height, le cadre suit la hauteur réelle du contenu — le
     bas du cadre se retrouve bien à 5px (padding-bottom de .info) sous le dernier élément. */
  width: 95%;
  margin-left: auto;
  margin-right: auto;
}
.event-card .thumb {
  width: 100px;
  height: 100px;
  flex-shrink: 0;
  margin: 5px;
}
/* Réserve la place du badge "Annonceur certifié" (position:absolute, top:100%, voir
   .badge-annonceur-certifie-sous-image) dans la hauteur réelle de la carte — sans ce
   margin-bottom agrandi, le badge (40px + 5px d'écart) déborde sur la carte suivante quand
   .info n'est pas assez haute pour compenser (demande de Christophe, 2026-08-31). */
.event-card .thumb.avec-badge-certifie {
  margin-bottom: 50px;
}
/* Évènement verrouillé (au-delà de l'horizon du palier réel, ex. 1 jour en Accès libre — voir
   eventCardHtml/data-verrouille dans main.js) : flou sur TOUTE la carte (texte, badges, image),
   en plus du grisage général (.is-grise, opacity 0.4) — demande explicite du 2026-07-27. filter:
   blur() n'accepte pas les pourcentages en CSS ; 30% a été traduit en une valeur de flou modérée
   (3px, sur toute la carte via l'élément parent), à ajuster si trop fort/faible. */
.event-card.is-grise {
  filter: blur(3px);
}
.event-card .info {
  padding: 5px 14px 5px 10px;
}
/* Icônes des lignes date/heure façon carte annonceur ("Du : ... au ...", "De : ... à ..."),
   reprises telles quelles ici (voir .icone-meta dans annonceurs.icilago.eu/css/style-3.css). */
.icone-meta { width: 15px; height: 15px; object-fit: contain; vertical-align: -2px; margin-right: 4px; }
/* Badge "✏️ Modifié" (voir badgeEvenementModifieHtml) déplacé sous la photo, centré sur son
   axe horizontal, à 5px de son bord bas — plutôt que dans la ligne de badges texte. */
.badge-modifie-sous-image {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-top: 5px;
  white-space: nowrap;
  z-index: 1;
}
/* Icône "Annonceur certifié" (evt.annonceurCertifie, ajoutée 2026-08-31) — même principe que
   .badge-modifie-sous-image ci-dessus (sous la photo, mode Liste uniquement) mais alignée à
   gauche (left:0, pas de transform) plutôt que centrée. */
.badge-annonceur-certifie-sous-image {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 5px;
  width: 40px;
  height: 40px;
  object-fit: contain;
  z-index: 1;
}
.thumb {
  width: 64px;
  height: 64px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--color-primary), var(--color-accent));
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 22px;
  position: relative;
}
.event-card .info { flex: 1; min-width: 0; }
/* Titre de la carte "liste" (eventCardHtml, .info h3) tronqué avec "…" sur une seule ligne
   plutôt que de passer sur 2 lignes et pousser le reste de la carte — demande de Christophe,
   2026-08-06. Nécessite min-width:0 sur .info ci-dessus pour que la troncature s'applique
   réellement dans un conteneur flex. */
.event-card .info h3 {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* Pendule (anneau de temps restant, voir choisirPenduleHtml/eventCardHtml) déplacée à droite des
   lignes "Du : ... au ..." / "De : ... à ..." — demande de Christophe, 2026-08-06 : axée
   verticalement sur ces deux lignes (align-items:center), à 10px du bord droit du cadre (.info a
   déjà 14px de padding-right, d'où le margin-right:-4px sur l'anneau pour retomber sur 10px). */
.ligne-dates-pendule {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ligne-dates-pendule-textes { flex: 1; min-width: 0; }
.ligne-dates-pendule .pendule-vignette-anneau { margin-right: -4px; }

/* Numéro d'événement (PAYS + département + 8 chiffres, ex. FR8400000000), affiché à droite
   du titre — carte de la liste (h3), voir eventCardHtml. */
.numero-evenement {
  float: right;
  font-size: 11px;
  font-weight: 400;
  color: var(--color-text-muted);
  margin-left: 8px;
  white-space: nowrap;
}
/* Fiche détail (openEventDetail) : icônes "Favoris", "Partager" (Icone_0293) et "Signalement"
   (Icone_0295) sous la photo, alignées à droite — demande de Christophe, 2026-08-17. Favoris était
   auparavant seul en superposition sur la photo (haut-gauche, 2026-08-15), déplacé dans cette
   rangée à gauche de "Partager" le même jour que ce commentaire (2026-08-17). Icône "Retour"
   (Icone_0206) qui occupait le côté gauche de cette rangée (depuis 2026-08-17) déplacée dans la
   barre bleue du header le 2026-08-20 (voir #btn-retour-fiche plus bas dans ce fichier).
   Référence de l'annonce (numero-evenement-sur-photo) réintégrée comme 1er groupe (gauche) de
   cette rangée le 2026-08-30 (demande de Christophe : "même ligne et centré horizontalement" avec
   les icônes, à 10px sous la photo) — d'où le retour à space-between (2 groupes désormais).
   Classe .sans-numero (posée en JS quand evt.numeroEvenement est vide, anciens évènements) repasse
   en flex-end pour que les icônes restent à droite même sans 1er groupe. */
.rangee-icones-sous-photo {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 10px 0 12px;
}
.rangee-icones-sous-photo.sans-numero {
  justify-content: flex-end;
}
.rangee-icones-sous-photo-droite {
  display: flex;
  align-items: center;
  gap: 10px;
}
.icone-sous-photo {
  position: relative;
  padding: 0;
  background: none;
  border: none;
  box-shadow: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.icone-sous-photo img { width: 40px; height: 40px; object-fit: contain; }
/* Tooltips des icônes sous la photo (Retour, Favoris, Partager, Signalement) : affichés au-dessus
   de l'icône, centrés dans son axe vertical — demande de Christophe, 2026-08-17. Remplace les
   info-bulles natives du navigateur (attribut title). */
.icone-sous-photo[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 8px;
  background: var(--color-primary);
  color: #fff;
  font-size: 12px;
  line-height: 1.2;
  padding: 4px 8px;
  border-radius: 6px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s ease;
  z-index: 20;
}
.icone-sous-photo[data-tooltip]:hover::after,
.icone-sous-photo[data-tooltip]:focus::after,
.icone-sous-photo[data-tooltip]:active::after {
  animation: icilago-tooltip-auto-hide-fade 5s ease forwards;
}
/* Fiche détail (openEventDetail) : déplacé en badge au-dessus de la photo, en haut à droite
   (demande de Christophe, 2026-08-05) — plus à côté du titre (h2). */
/* Référence de l'annonce : déplacée de la superposition sur la photo (haut-droite) vers le bas de
   l'image, à gauche — demande de Christophe, 2026-08-21, guide ET events. Plus de position:absolute
   (elle vivait dans le conteneur position:relative de la photo, superposée dessus). Fond
   translucide blanc retiré (pensé pour contraster sur une photo, plus adapté une fois sous l'image
   sur fond blanc). Réintégrée dans .rangee-icones-sous-photo comme 1er groupe (demande de
   Christophe, 2026-08-30, "même ligne et centré horizontalement" que les icônes) : n'est donc plus
   un bloc pleine largeur sur sa propre ligne — display/margin retirés (align-items:center du
   parent flex gère le centrage vertical avec les icônes). */
.numero-evenement-sur-photo {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-text-muted, #666);
  white-space: nowrap;
}
/* Fiche détail (openEventDetail) : nombre de vues, en badge sur la photo, en bas à droite
   (demande de Christophe, 2026-08-05). */
.vues-evenement-sur-photo {
  position: absolute;
  bottom: 8px;
  right: 8px;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 11px;
  font-weight: 600;
  color: #000;
  background: rgba(255,255,255,0.5);
  padding: 1px 8px;
  border-radius: 6px;
  white-space: nowrap;
}
.vues-evenement-sur-photo img {
  width: 30px;
  height: 30px;
  object-fit: contain;
}
/* Fiche détail (openEventDetail) : logo du Centre d'intérêt de l'évènement (console > Données >
   CITS, cits.icone_url), en bas à gauche de la photo, symétrique de .vues-evenement-sur-photo
   (en bas à droite) — demande de Christophe, 2026-09-09. Rond blanc de 50px en fond, centré par
   rapport à l'icône de 40px (demande complémentaire du même jour) : le rond est positionné en
   absolute (10px des bords - (50-40)/2 = 5px, pour que l'icône de 40px à l'intérieur reste bien à
   10px des bords de la photo une fois centrée dans les 50px du rond) ; l'icône elle-même n'est
   plus positionnée en absolute, elle est centrée dans le rond via flexbox. */
.fiche-flyer-icone-interet-fond {
  position: absolute;
  bottom: 5px;
  left: 5px;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
.fiche-flyer-icone-interet {
  width: 40px;
  height: 40px;
  object-fit: contain;
}

/* Couleur d'état (rond "Près de vous", .point-etat plus bas) selon le statut de l'événement,
   clignotante à l'approche du début (vert) ou de la fin (rouge) — voir
   calculerEtatBarreEvenement() dans main.js. La vitesse de clignotement est réglée par
   variable CSS --barre-intervalle, injectée en style inline par événement. */
.barre-etat-vert { background: #27ae60; }
.barre-etat-orange { background: #e67e22; }
.barre-etat-jaune { background: #f1c40f; }
.barre-etat-rouge { background: #c0392b; }
.barre-etat-neutre { background: #bdc3c7; }
@keyframes clignoter-barre-etat { 0%, 49% { opacity: 1; } 50%, 100% { opacity: .15; } }
.barre-etat.clignote { animation: clignoter-barre-etat var(--barre-intervalle, 1s) steps(1) infinite; }
/* Même animation que .barre-etat.clignote, mais applicable au rond .point-etat (Près de vous),
   qui n'a jamais la classe "barre-etat" — sans cette règle le clignotement ne s'affichait pas
   sur les fiches visiteur. */
/* Correctif iOS Safari (signalé par Christophe, 2026-08-11 : le voyant clignote sur PC mais pas
   sur iPhone, y compris un iPhone neuf jamais installé — donc pas un souci de cache PWA).
   Différence structurelle identifiée avec .vignette-evenement-pro-dot-ouverture côté annonceurs
   (qui clignote bien sur iOS) : là-bas le rond est en flux normal, ici .point-etat est un enfant
   position:static d'un .point-etat-groupe en position:absolute, superposé à la photo/miniature —
   bug WebKit connu où les animations d'opacité sur un élément imbriqué dans un conteneur en
   position absolue ne sont pas toujours repeintes de façon fiable. will-change/transform forcent
   son propre calque de composition (sans effet visuel sur les autres navigateurs) pour contourner
   ce cas. */
.point-etat.clignote {
  animation: clignoter-barre-etat var(--barre-intervalle, 1s) steps(1) infinite;
  will-change: opacity;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}
.point-etat-groupe { -webkit-transform: translateZ(0); transform: translateZ(0); }
/* Voyant "OUVERTURE" (voir console > Réglages > Infos temps > rubrique OUVERTURE), couleurs
   identiques à la légende console : remplace le vert de .barre-etat-vert sur les fiches
   "Aujourd'hui" quand l'évènement est en cours au sens large (voir calculerEtatOuverture). */
/* Couleurs pilotées par variables CSS (posées dans appliquerCouleursEnvironnementDepuisApi(),
   voir main.js), lues en direct depuis console > Réglages > Infos temps > OUVERTURE — la valeur
   après la virgule est le repli si l'API n'a pas encore répondu / est indisponible. */
.voyant-ouverture-ouvert { background: var(--color-ouverture-ouvert, #27ae60); }
/* "Bientôt" (dernière heure avant l'ouverture du jour, remplace "Fermé") — demande de
   Christophe, 2026-08-09. */
.voyant-ouverture-bientot { background: var(--color-ouverture-bientot, #27ae60); }
.voyant-ouverture-pause { background: var(--color-ouverture-pause, #4aa3df); }
.voyant-ouverture-ferme { background: var(--color-ouverture-ferme, #d94f4f); }
/* Légende OUVERTURE sous le rayon de recherche (Près de vous) : 4 voyants (Fermé/Ouvert/En
   pause/Fermé ouvre demain, repris de la console > Réglages > Infos temps) répartis
   équitablement, texte minuscule en dessous de chacun. */
.legende-ouverture { display: flex; justify-content: space-between; margin-top: 10px; gap: 4px; }
.legende-ouverture-item { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 1; min-width: 0; }
.legende-ouverture-voyant { width: 14px; height: 14px; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,.15); flex-shrink: 0; }
.legende-ouverture-voyant.clignote { animation: clignoter-barre-etat var(--barre-intervalle, 1s) steps(1) infinite; }
.legende-ouverture-texte { font-size: 9px; color: var(--color-text-muted); text-align: center; line-height: 1.2; white-space: nowrap; }
/* Légende OUVERTURE compacte, sous le bandeau "Aujourd'hui" de "Près de vous" (voir index.html)
   — layout horizontal voyant + texte sur chaque ligne, reprise à l'identique de
   .legende-ouverture-dashboard dans annonceurs.icilago.eu (Tableau de bord / "Mes annonces") —
   demande de Christophe, 2026-08-06. */
.legende-ouverture-dashboard { margin-top: 0; padding-top: 0; border-top: none; display: flex; flex-direction: column; gap: 6px; }
.legende-ouverture-dashboard-titre { font-size: 11px; font-weight: 700; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: .3px; margin-bottom: 2px; }
.legende-ouverture-dashboard-ligne { display: flex; flex-wrap: nowrap; justify-content: space-between; gap: 4px; }
.legende-ouverture-dashboard-item { display: flex; align-items: center; gap: 3px; font-size: 10px; color: var(--color-text); white-space: nowrap; }
.legende-ouverture-dashboard-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; box-shadow: 0 1px 2px rgba(0,0,0,.2); }
.legende-ouverture-dashboard-dot.clignote { animation: clignoter-barre-etat var(--barre-intervalle, 1s) steps(1) infinite; }
/* Barre de progression "avant l'évènement" (voir console > Réglages > Infos temps > EVENEMENTS),
   en bas des fiches "À venir" — même dégradé/repères que la console, largeur = celle de la
   fiche (pas une valeur fixe en pixels), positionnée à 10px du bas de la fiche (position
   absolue : la fiche event-card est déjà en position:relative). L'espace nécessaire est
   réservé en bas de .info via .avec-barre-progression, sinon le texte chevaucherait la barre. */
.event-card.avec-barre-progression .info { padding-bottom: 44px; }
/* Grille 2 colonnes (texte | barre) x 2 lignes (piste | repères) : le texte n'occupe que la
   1ère ligne, donc align-items:center ne le centre que par rapport à la piste — les repères,
   sur la 2e ligne, n'entrent pas dans ce calcul (alignement "à la ligne de progression
   uniquement", indépendant de la présence des repères en dessous). */
.barre-progression-carte-conteneur {
  position: absolute; left: 14px; right: 14px; bottom: 10px;
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 6px;
  align-items: center;
}
/* Texte "Commence dans :" à gauche de la barre — même taille/couleur que le compte à rebours
   ".meta.compte-a-rebours" (12.5px) pour rester cohérent avec le reste de la fiche. */
.label-barre-progression { grid-column: 1; grid-row: 1; font-size: 12.5px; color: var(--color-text-muted); white-space: nowrap; }
/* "Se termine bientôt" (barre "avant et pendant l'heure de l'évènement", dernière heure avant
   la fermeture du jour) : même animation que les voyants d'état (.barre-etat.clignote). */
.label-barre-progression.clignote { animation: clignoter-barre-etat 1s steps(1) infinite; }
/* Enveloppe positionnée autour de la piste, pour pouvoir y superposer le curseur noir en
   position absolue (curseur commun aux deux barres, voir .curseur-progression-carte). */
.barre-progression-carte-piste-wrap { grid-column: 2; grid-row: 1; position: relative; }
.barre-progression-carte-piste { width: 100%; height: 8px; border-radius: 0; background: #e5e7eb; overflow: hidden; }
.barre-progression-carte-remplissage { height: 100%; border-radius: 0; background: linear-gradient(to right, var(--color-infos-temps-debut, #0b5c0b), var(--color-infos-temps-fin, #00ff00)); }
/* Curseur noir (trait simple) marquant la position actuelle sur la barre — commun aux barres
   "avant l'évènement" (jours, "À venir") et "avant l'heure de l'évènement" (heures, "Aujourd'hui"). */
.curseur-progression-carte { position: absolute; top: 50%; width: 2px; height: 14px; background: #000; transform: translate(-50%, -50%); pointer-events: none; }
.reperes-progression-carte { grid-column: 2; grid-row: 2; position: relative; height: 13px; margin-top: 3px; }
.reperes-progression-carte span { position: absolute; top: 0; transform: translateX(-50%); font-size: 8px; color: var(--color-text-muted); white-space: nowrap; }
.reperes-progression-carte span:first-child { transform: translateX(0); }
.reperes-progression-carte span:last-child { transform: translateX(-100%); }
/* Barre "avant l'heure de l'évènement" (voir console > Réglages > Infos temps > EVENEMENTS
   "Information avant l'heure de l'évènement"), échelle 24h→0h, en bas des fiches "Aujourd'hui" —
   même principe que la barre en jours ci-dessus, même positionnement à 10px du bas de la fiche,
   avec le même curseur noir. */
.event-card.avec-barre-heure .info { padding-bottom: 44px; }
.barre-progression-carte-heure-conteneur {
  position: absolute; left: 14px; right: 14px; bottom: 10px;
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 6px;
  align-items: center;
}
.barre-progression-carte-remplissage-heure { height: 100%; border-radius: 0; background: linear-gradient(to right, var(--color-infos-temps-heures-debut, #0b5c0b), var(--color-infos-temps-heures-fin, #00ff00)); }
/* Barre "avant et pendant l'heure de l'évènement" (voir console > Réglages > Infos temps >
   EVENEMENTS, 50px au-dessus de la barre ci-dessus) : remplace la barre 24h→0h dans les 5
   dernières heures avant le début et pendant l'évènement — voir barreProgressionAvantPendantHtml
   dans main.js. Même positionnement/curseur que les 2 barres ci-dessus. */
.event-card.avec-barre-avant-pendant .info { padding-bottom: 44px; }
.barre-progression-carte-avant-pendant-conteneur {
  position: absolute; left: 14px; right: 14px; bottom: 10px;
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 6px;
  align-items: center;
}
/* Dégradé fixé sur la PISTE entière (échelle réelle 5h→Début→Fin, indépendante du niveau
   d'avancement), et non sur le remplissage (qui rétrécit avec le temps restant) — sinon le
   repère "Début" (50%) dérivait : recalculé à 50% de la largeur du remplissage à chaque instant
   au lieu de 50% de la barre entière. Le "remplissage" est maintenant un masque gris qui se
   rétracte depuis la droite, révélant le dégradé fixe en dessous. Dégradé uniquement sur la
   phase "avant" (5h → Début, 0%-50%) ; à partir de "Début de l'évènement" (50%), couleur unie
   ("Durée de l'évènement") sur toute la plage d'ouverture (50%-100%), sans dégradé — voir
   console > Réglages > Infos temps > EVENEMENTS. */
.barre-progression-carte-piste-avant-pendant {
  width: 100%;
  height: 8px;
  border-radius: 0;
  overflow: hidden;
  position: relative;
  background: linear-gradient(to right, var(--color-infos-temps-avant-pendant-5h, #0b5c0b) 0%, var(--color-infos-temps-avant-pendant-2h, #4a8f3c) 50%, var(--color-infos-temps-avant-pendant-fin, #00ff00) 50%, var(--color-infos-temps-avant-pendant-fin, #00ff00) 100%);
}
.barre-progression-carte-masque-avant-pendant { position: absolute; top: 0; right: 0; bottom: 0; background: #e5e7eb; }
/* Phase "pendant" (modèle repris de la console > Réglages > Infos temps > BARRE DE PROGRESSION
   N3) : remplace le masque neutre ci-dessus par 2 segments colorés distincts — "Temps écoulé"
   (depuis "Début", 50%, jusqu'à maintenant) et "Temps restant" (de maintenant jusqu'à "Fin",
   100%). Pas de border-radius propre : la piste parente (overflow:hidden, border-radius:4px)
   suffit à arrondir les extrémités visibles. Voir barreProgressionAvantPendantHtml. */
.segment-ecoule-carte-avant-pendant { position: absolute; top: 0; bottom: 0; background: var(--color-infos-temps-avant-pendant-ecoule, #1f6fb2); }
.segment-restant-carte-avant-pendant { position: absolute; top: 0; bottom: 0; background: var(--color-infos-temps-avant-pendant-fin, #00ff00); }
/* Anneau PENDULE compact (vignettes "Aujourd'hui", voir console > Réglages > Infos temps >
   PENDULE, modèle repris ici) : sous la distance, durée de l'évènement affichée au centre.
   Couleurs pilotées par variables CSS, lues en direct (voir appliquerCouleursEnvironnementDepuisApi).
   Diamètre +10% (44px, était 40px) — demande de Christophe, 2026-08-11. */
.pendule-vignette-anneau {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  flex-shrink: 0;
  background: conic-gradient(var(--pendule-couleur-ecoule, #111111) 0% var(--pendule-pourcentage, 0%), var(--pendule-couleur-restant, #d9d9d9) var(--pendule-pourcentage, 0%) 100%);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Cercle central +10% (33px, était 30px) ; empilement vertical texte + unité (voir
   .pendule-vignette-unite ci-dessous, déplacée sous le temps restant à l'intérieur du cercle —
   demande de Christophe, 2026-08-11, remplace l'ancien positionnement sous l'anneau). */
.pendule-vignette-centre {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.pendule-vignette-texte { margin-top: 0px; font-size: 9.5px; font-weight: 700; color: var(--color-text); white-space: nowrap; text-align: center; line-height: 1; }
.pendule-vignette-unite {
  margin-top: -1px;
  font-size: 6.5px;
  font-weight: 600;
  color: var(--color-text-muted);
  white-space: nowrap;
  text-align: center;
  line-height: 1;
}
/* Alerte < 60 min restantes (voir console > Réglages > Infos temps > PENDULE N4 et
   penduleFrequenceAlerte dans main.js) : le cercle central clignote rouge, texte ET unité en
   blanc pendant le flash — fréquence (--pendule-alerte-intervalle) posée en inline par
   évènement, accélère à mesure que le temps restant diminue (paliers 60/45/30/15 min). */
@keyframes clignoter-pendule-alerte { 0%, 49% { background: #c0392b; } 50%, 100% { background: #fff; } }
.pendule-vignette-centre.clignote-alerte { animation: clignoter-pendule-alerte var(--pendule-alerte-intervalle, 1s) steps(1) infinite; }
.pendule-vignette-centre.clignote-alerte .pendule-vignette-texte,
.pendule-vignette-centre.clignote-alerte .pendule-vignette-unite { animation: clignoter-pendule-alerte-texte var(--pendule-alerte-intervalle, 1s) steps(1) infinite; }
@keyframes clignoter-pendule-alerte-texte { 0%, 49% { color: #fff; } 50%, 100% { color: var(--color-text); } }
/* Compteur à 0 (voir penduleFrequenceAlerte) : cercle rouge FIXE, plus de clignotement — reste
   ainsi jusqu'à la prochaine ouverture, prise en charge par les paliers N1/N2/N3. */
.pendule-vignette-centre.pendule-alerte-fixe { background: #c0392b; }
.pendule-vignette-centre.pendule-alerte-fixe .pendule-vignette-texte,
.pendule-vignette-centre.pendule-alerte-fixe .pendule-vignette-unite { color: #fff; }

/* Gabarit de base (cartes LISTE uniquement désormais) : ramené à sa taille d'origine le 2026-08-15
   — les essais à 30px puis 54px/50px ont été retirés d'ici et déplacés sur une règle scopée
   .vignette-evenement-visiteur-fav (cartes "Icônes" seulement, voir plus bas), suite à la demande
   de Christophe explicitement restreinte : "Pour les cartes icones Guide et Events". */
.fav-toggle {
  position: absolute;
  top: 5px;
  left: 5px;
  background: rgba(255,255,255,0.9);
  border: none;
  border-radius: 50%;
  width: 28px;
  height: 28px;
  font-size: 14px;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0,0,0,0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Icônes favori (Icone_0015 = sélectionné, Icone_0016 = non sélectionné), remplacent les
   emojis ❤️/🤍 — utilisées sur .fav-toggle (cartes), #btn-toggle-favori (fiche détail) et
   [data-unfav] (Mon compte > Favoris). */
.icone-favori { width: 16px; height: 16px; display: inline-block; vertical-align: middle; }
/* Cœur du cercle "favori" sur les fiches/cartes LISTE (.fav-toggle), pas les boutons texte (fiche
   détail, Mon compte > Favoris) qui gardent la taille de base ci-dessus. */
.fav-toggle .icone-favori { width: 32px; height: 32px; }
/* Fiche détail (openEventDetail) : bouton "Ajouter aux favoris" (demande de Christophe, 2026-08-05) */
#btn-toggle-favori .icone-favori { width: 25px; height: 25px; }

/* Bascule "Liste" / "Icônes" des cartes évènement visiteur ("Près de vous" + "Mes favoris") —
   demande de Christophe (2026-08-04), même mécanisme et mêmes styles que
   .toggle-affichage-evenements-pro dans annonceurs.icilago.eu. Bouton actif mis en évidence par
   une couleur pleine, comme les autres pastilles de sélection de l'app. */
.toggle-affichage-evenements-visiteur { justify-content: flex-end; gap: 6px; margin-bottom: 10px; }
.toggle-affichage-evenements-visiteur .btn { flex: 0 0 auto; }
.btn-toggle-affichage-evenements-visiteur.actif {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
}
/* Icône du bouton "Icônes" : vraie grille 2x2 de 4 carrés (currentColor : suit automatiquement
   la couleur du texte du bouton, y compris à l'état .actif) — voir annonceurs.icilago.eu. */
.icone-grille-2x2 {
  display: inline-grid;
  grid-template-columns: repeat(2, 1fr);
  grid-template-rows: repeat(2, 1fr);
  gap: 2px;
  width: 12px;
  height: 12px;
  vertical-align: middle;
  margin-right: 3px;
}
.icone-grille-2x2 span { background: currentColor; border-radius: 1px; }
/* Grille de vignettes (affichage "Icônes") : type + titre en surimpression d'une case photo
   carrée, puis date/heure début→fin et ville/distance en dessous — même principe que
   .grille-vignettes-evenements-pro dans annonceurs.icilago.eu. */
.grille-vignettes-evenements-visiteur {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin-bottom: 14px;
}
.vignette-evenement-visiteur {
  cursor: pointer;
  /* padding-top réduit à 2px le 2026-08-15 (demande de Christophe : "remonte les items en haut
     du cadre") — le type/sujet/raison sociale démarraient avec 10px de vide au-dessus, dans une
     carte déjà espacée de ses voisines par le gap:12px de .grille-vignettes-evenements-visiteur.
     padding-bottom inchangé (10px) pour ne pas resserrer la photo/les infos en bas du cadre. */
  padding: 2px 0 10px;
  margin-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0;
  /* min-width: 0 : évite qu'un texte de type trop long (white-space:nowrap) ne force la colonne
     de grille à s'élargir et casse l'égalité des deux vignettes d'une même ligne — voir le même
     correctif sur .vignette-evenement-pro dans annonceurs.icilago.eu (2026-08-04). */
  min-width: 0;
  /* position:relative ajouté le 2026-09-08 pour ancrer .vignette-evenement-visiteur-nouveau
     (icône 332 "Logo nouvelle annonce") en haut à droite du CADRE ENTIER — contrairement aux
     icônes 331/333 (certifié/prémium), ancrées sur .vignette-evenement-visiteur-photo ci-dessous. */
  position: relative;
}
/* Icône "Logo nouvelle annonce" (evt.logoNouvelleAnnonce, ajoutée 2026-09-08) — haut DROIT du
   CADRE entier de la vignette (pas de la photo) — demande de Christophe, 2026-09-08. */
.vignette-evenement-visiteur-nouveau {
  position: absolute;
  /* Valeurs négatives (pas 0) : le fichier Icone_0332.png a une marge transparente intégrée
     autour du ruban — à top:0/right:0, ce vide laissait un écart visible avant l'angle réel du
     cadre. Décalé vers l'extérieur pour que le ruban VISIBLE (pas le bord du fichier) coïncide
     avec l'angle — demande de Christophe, 2026-09-08. Valeur à affiner si besoin. */
  top: -19px;
  right: -19px;
  width: 100px;
  height: 100px;
  object-fit: contain;
  z-index: 2;
}
/* Cette vignette porte aussi la classe .event-card (voir eventVignetteHtml), réutilisée
   uniquement pour que bindEventCardClicks() la reconnaisse au clic — mais .event-card impose par
   ailleurs une mise en page "liste" (display:flex en ligne, height:200px fixe, width:95%,
   marges auto) pensée pour la carte détaillée, pas pour une vignette en grille 2 colonnes. Sans
   cette surcharge, le type/la photo/les infos s'alignaient horizontalement au lieu de s'empiler,
   et la photo carrée devenait une bande verticale étirée sur 200px (bug signalé par Christophe,
   2026-08-04, capture d'écran à l'appui). Spécificité .event-card.vignette-evenement-visiteur
   (deux classes) suffit à l'emporter sur .event-card seul. */
.event-card.vignette-evenement-visiteur {
  display: block;
  height: auto;
  width: auto;
  margin-left: 0;
  margin-right: 0;
}
/* Mise en avant (evt.miseEnAvantCouleur, evenements_guide.mise_en_avant_couleur choisie par
   l'annonceur via la case OPTIONS dédiée), couleur posée en variable CSS inline
   (--couleur-mise-en-avant, voir eventVignetteHtml() dans main.js) — demande de Christophe,
   2026-08-30. D'abord un liseret 5px inset via pseudo-élément ::after (même technique que côté
   annonceur), PUIS remplacé le même jour ("Enleve le cadre et applique la couleur sur toute la
   carte de la vignette") : la couleur devient le fond de la carte ENTIÈRE (.card, normalement
   var(--color-surface)) — visible autour des zones sans fond opaque propre (le texte
   sujet/raison sociale/type en haut, le bloc "infos" en bas), la photo (fond opaque, prend
   100% de la largeur, sans padding horizontal) recouvre entièrement la couleur là où elle
   s'affiche. Pas de pseudo-élément, pas de z-index, pas de risque d'interception de clic. */
.vignette-mise-en-avant-visiteur {
  background: var(--couleur-mise-en-avant);
}
/* Contraste automatique texte/fond sur la vignette "Mise en avant" (--couleur-texte-mise-en-avant,
   calculée en JS via couleurTexteContrastee() dans eventVignetteHtml — luminance YIQ, noir ou
   blanc selon la couleur choisie par l'annonceur) — demande de Christophe, 2026-08-30. Ne
   concerne QUE les zones où le fond de la carte est visible (texte au-dessus/en dessous de la
   photo) : le titre affiché SUR la photo garde sa couleur blanche fixe existante (fond photo
   opaque, pas la couleur de mise en avant). */
.vignette-mise-en-avant-visiteur .vignette-evenement-visiteur-sujet,
.vignette-mise-en-avant-visiteur .vignette-evenement-visiteur-raison-sociale,
.vignette-mise-en-avant-visiteur .vignette-evenement-visiteur-type,
.vignette-mise-en-avant-visiteur .vignette-evenement-visiteur-infos,
.vignette-mise-en-avant-visiteur .vignette-evenement-visiteur-avis-moyenne {
  color: var(--couleur-texte-mise-en-avant);
}
/* Même réglage typographique que .vignette-evenement-visiteur-sujet (écriture fine, sans
   majuscules) — demande de Christophe, 2026-08-16. Taille +1px par rapport à l'ancien réglage
   (9.5px → 10.5px). */
.vignette-evenement-visiteur-type {
  font-size: 10.5px;
  font-weight: 300;
  line-height: 1.2;
  color: var(--color-text-muted);
  margin-bottom: 2px;
  padding: 0 10px;
  min-height: 11.4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Sujet(s) — demande de Christophe (2026-08-15) : nouvelle ligne au-dessus de la raison sociale,
   "police de texte en écriture fine" → font-weight léger (300), même famille/taille que le type
   juste au-dessus mais sans majuscules ni graisse. evt.sujets (tableau, voir sujetLabel côté JS
   dans eventVignetteHtml) est joint par ", " ; "&nbsp;" en repli si vide pour réserver la place
   (même logique que la raison sociale ci-dessous). */
.vignette-evenement-visiteur-sujet {
  font-size: 9.5px;
  font-weight: 300;
  line-height: 1.2;
  color: var(--color-text-muted);
  margin-bottom: 2px;
  padding: 0 10px;
  min-height: 11.4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Raison sociale du professionnel organisateur, sous le type — demande de Christophe
   (2026-08-04). evt.raisonSociale vient de la colonne evenements.raison_sociale (déjà renvoyée
   par la recherche/les favoris, pas d'appel API supplémentaire nécessaire), absente si l'auto-
   entrepreneur n'en a pas renseigné une à la création. Le bloc est TOUJOURS rendu (avec un
   "&nbsp;" en repli côté JS si vide, voir eventVignetteHtml) — sinon une vignette sans raison
   sociale avait une ligne de moins que sa voisine, ce qui désalignait horizontalement les deux
   photos carrées d'une même ligne de grille (demande de Christophe, 2026-08-04). min-height
   réserve la place même quand le contenu est vide.
   Repassé à 2 lignes le 2026-09-02 (demande de Christophe) : ne tronquer ("…") que si le texte
   dépasse la 2e ligne, pas dès qu'il dépasse la 1re — ex. "COMMUNE DE ROUSSILLON" tient sur 1
   ligne, doit s'afficher en entier. Troncature JS repassée à maxLength=36 (identique au titre,
   voir eventVignetteHtml) + `-webkit-line-clamp: 2` ci-dessous, même paire fiable que le titre
   (.vignette-evenement-visiteur-titre) — le JS gère la fiabilité multi-navigateurs, le clamp CSS
   est la limite visuelle. min-height passe à 2 × line-height (1.2 × 12px × 2 = 28.8px) pour
   réserver la place sur 2 lignes même à vide (voir explication ci-dessus). */
.vignette-evenement-visiteur-raison-sociale {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.2;
  color: var(--color-text);
  margin: -1px 0 2px;
  padding: 0 10px;
  min-height: 28.8px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}
.vignette-evenement-visiteur-photo {
  width: 100%;
  padding-top: 100%;
  height: 0;
  border-radius: 10px 10px 0 0;
  overflow: hidden;
  position: relative;
  background-size: cover;
  background-position: center;
  background-color: var(--color-bg, #f0f1f3);
  /* margin-bottom retiré le 2026-08-20 (demande de Christophe : "deplace sous la photo la barre
     de progression, ne laisse pas d'espace") : la photo est désormais TOUJOURS suivie de la
     barre PENDULE N5 (.vignette-evenement-visiteur-barre-n5-piste, voir eventVignetteHtml), qui
     porte elle-même margin-bottom:8px pour l'espacement avant .vignette-evenement-visiteur-infos
     — un margin-bottom ici en plus créait un espace visible entre la photo et la barre. */
}
.vignette-evenement-visiteur-emoji {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 40px;
}
/* Cartes "Icônes" UNIQUEMENT — demande de Christophe, 2026-08-15, dernier réglage ("Tailles
   icones 30x30 px - Retire le contour des icones - Pour les cartes icones Guide et Events") :
   icône 30x30px, SANS le cercle blanc de fond/ombre (contour retiré), position 5px/5px des bords
   inchangée. Volontairement distinct de la base .fav-toggle (cartes LISTE, restée à son gabarit
   d'origine 28px/32px avec cercle) — les essais précédents (30px puis 54px/50px avec cercle)
   avaient été faits à tort sur la règle de base partagée, ce qui affectait aussi les cartes liste
   par erreur. Icône repassée de 30px à 50px le 2026-08-15 (demande de Christophe : "taille de
   l'icone favoris 50x50 px pour toutes les applications"). */
.vignette-evenement-visiteur-fav {
  top: 5px;
  left: 5px;
  width: 50px;
  height: 50px;
  background: none;
  border-radius: 0;
  box-shadow: none;
}
.vignette-evenement-visiteur-fav .icone-favori { width: 50px; height: 50px; }
/* Icône "Annonceur certifié" (evt.annonceurCertifie, ajoutée 2026-08-31) sur la vignette
   "Icônes" — sous l'icône favoris, centrée sur le même axe vertical qu'elle : le cœur fait
   50px de large avec left:5px (centre à 5+25=30px), ce badge fait 60px de large donc
   left:0px pour un centre identique (0+30=30px) — un left:5px identique aux deux, comme
   avant ce correctif, décalait le badge de 5px vers la droite car les largeurs diffèrent.
   Bug remonté par Christophe, 2026-09-08 ("Centre l'icone premium dans l'axe vertical de
   l'icone favoris"). Écart vertical sous le cœur ramené à 0px (top: 5+50) — demande de
   Christophe, 2026-09-08 ("espace entre l'icone favori et l'icone premium 0px"), remplace
   les 5px d'écart précédents. */
.vignette-evenement-visiteur-certifie {
  position: absolute;
  top: 55px;
  left: 0px;
  width: 60px;
  height: 60px;
  object-fit: contain;
  z-index: 1;
}
/* Icône "Annonceur Prémium" (evt.annonceurPremium, ajoutée 2026-09-08) sur la vignette "Icônes" —
   juste sous l'icône "Annonceur certifié" ci-dessus, même largeur/centrage (left:0px pour un
   centre identique à l'icône favoris), 0px d'écart vertical (top: 55+60) — demande de Christophe,
   2026-09-08. */
.vignette-evenement-visiteur-premium {
  position: absolute;
  top: 115px;
  left: 0px;
  width: 60px;
  height: 60px;
  object-fit: contain;
  z-index: 1;
}
/* Tailles réduites des icônes Favoris/Certifié/Prémium sur vignette "Icônes" en smartphone/tablette
   uniquement — demande de Christophe, 2026-09-08 ("en version smartphone ou tablette tailles des
   icones Favoris 40px, Certifié et prémium 50px. Ne touche pas à la version pc"). Breakpoint
   @media (max-width:1024px) : convention déjà établie dans ce fichier pour "smartphones/tablettes"
   (voir .app-header ci-dessous). Version PC (règles de base ci-dessus) inchangée. left:0px conservé
   pour certifie/premium : leur centre horizontal (left + moitié largeur) reste aligné sur le centre
   de favoris (left:5px + 40/2 = 25px = 0 + 50/2) malgré les nouvelles tailles.
   top recalculé une 2e fois le même jour (demande "Reduit les espaces entre les icones Favoris,
   Certifié et prémium pour pas que le texte se trouve sous l'icone premium" — capture d'écran
   montrant la colonne des 3 icônes empilées avec 0px d'écart qui débordait sur le titre affiché
   plus bas dans la photo, la carte "Icônes" étant proportionnellement plus petite en mobile) :
   passage d'un écart de 0px (bord à bord) à un chevauchement, ramené à 5px le même jour (demande
   "passe le chevauchement à 5px", après un essai à 15px jugé excessif) : certifie top:45→40,
   premium top:95→85. Ne concerne QUE le bloc mobile/tablette ci-dessous ; les règles PC ci-dessus
   n'ont jamais été modifiées (top:55/115, écart 0px inchangé depuis leur création) — si le PC
   affichait le rendu "mobile", c'est que la fenêtre du navigateur PC était sous 1024px de large
   (le breakpoint réagit à la largeur de la fenêtre, pas au type d'appareil).
   certifie top recalculé une 3e fois le même jour (demande "Centre verticalement l'icone 333
   entre les icones 331 et favoris") : centre vertical de favoris = 5+40/2=25, centre vertical de
   prémium = 85+50/2=110, milieu des deux = 67.5 → top certifie = 67.5-50/2 = 42.5px (au lieu de
   40px, qui centrait un peu trop haut). Favoris et prémium inchangés, seul certifie recalé. */
@media (max-width: 1024px) {
  .vignette-evenement-visiteur-fav {
    width: 40px;
    height: 40px;
  }
  .vignette-evenement-visiteur-fav .icone-favori { width: 40px; height: 40px; }
  .vignette-evenement-visiteur-certifie {
    top: 42.5px;
    width: 50px;
    height: 50px;
  }
  .vignette-evenement-visiteur-premium {
    top: 85px;
    width: 50px;
    height: 50px;
  }
}
/* Voyant OUVERTURE (.point-etat, voir eventVignetteHtml) sur la vignette "Icônes" — repositionné
   le 2026-08-20 (demande de Christophe : "deplace le voyant à droite de la photo dans l'axe
   horizontal de l'icone favoris à 10px du bord droit"), remplace le positionnement "sous
   Favoris" du 2026-08-15. Corrigé le même jour ("le voyant n'est pas sur l'axe horizontal de
   l'icone favoris") : un même "top" ne suffit pas à aligner les CENTRES verticaux de deux
   éléments de hauteurs différentes (Favoris fait 50px de haut, le voyant ~14-20px selon le
   libellé "Bientôt"). "top:30px" cible le centre vertical de Favoris (5 + 50/2), et
   "transform:translateY(-50%)" centre le voyant sur ce point quelle que soit sa propre hauteur —
   même technique que le centrage horizontal déjà utilisé ailleurs (left:30px +
   translateX(-50%)), appliquée ici à l'axe vertical. Axe horizontal du voyant recalé le même
   jour à 30px du bord droit (demande de Christophe) : "right:30px" + "transform:translateX(50%)"
   centre l'axe vertical PROPRE du voyant (son propre milieu gauche-droite) exactement à 30px du
   bord droit, quelle que soit sa largeur — translateX(50%) décale l'élément vers la droite de la
   moitié de sa propre largeur, symétrique de la technique translateX(-50%) utilisée côté gauche
   pour Favoris. */
/* .vignette-evenement-visiteur-photo .point-etat-groupe (top:30px/right:30px sur la photo) retiré
   le 2026-09-08 : le voyant a été déplacé sur la ligne horaire de .vignette-evenement-visiteur-infos
   (voir .vignette-evenement-visiteur-horaire-ligne plus bas) — demande de Christophe. */
/* Badge temps restant (anneau PENDULE, voir choisirPenduleHtml) sur la vignette "Icônes" — coin
   haut-droit de la photo à 5px du bord, demande de Christophe 2026-08-11. */
.vignette-evenement-visiteur-pendule { position: absolute; top: 5px; right: 5px; }
/* Barre sous la photo des vignettes "Icônes", pleine largeur, 10px de haut — demande de
   Christophe, 2026-08-20. Pas de margin-top : .vignette-evenement-visiteur-photo porte déjà
   margin-bottom:8px, qui espace la photo de cette barre.
   Remplacée le 2026-08-28 (demande de Christophe) : n'affiche plus la progression du temps
   (PENDULE N5, voir mémoire projet) mais une couleur UNIE reprenant celle du centre d'intérêt du
   flyer (eventMarkerColor() dans main.js, même couleur que le repère sur la carte) — même
   dimensions qu'avant (10px, pleine largeur), plus de remplissage à 2 tons ni de mise à jour au
   tick. Couleur posée en style inline par eventVignetteHtml (une valeur par évènement). */
.vignette-evenement-visiteur-barre-n5-piste { width: 100%; height: 10px; border-radius: 0; overflow: hidden; margin-bottom: 8px; }
.vignette-evenement-visiteur-titre {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.25;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,.6);
  padding: 16px 10px 8px;
  background: linear-gradient(to top, rgba(0,0,0,.7), rgba(0,0,0,0));
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Filet de sécurité si line-clamp n'est pas honoré (bug signalé par Christophe, 2026-08-30 :
     une 3e ligne restait visible, débordant du cadre de la photo au lieu d'être proprement
     tronquée) : height FIXE (pas max-height, moins fiable dans ce cas précis — le contenu
     dépassait sans être coupé) = 2 lignes exactes + le padding vertical du bloc, combiné à
     overflow:hidden pour garantir qu'AUCUN contenu ne puisse jamais dépasser cette hauteur, même
     si line-clamp échoue. tronquerTitreVignette (maxLength=28 côté JS) reste la 1ère ligne de
     défense, ce height est la garantie absolue. */
  height: 56.5px;
  overflow-wrap: break-word;
}
/* Taille augmentée le 2026-08-15 (demande de Christophe : "Texte taille supérieure pour tous les
   cartes en version icone de toutes les applications"). Réduite à 11px sur smartphone (max-width:
   499px, même seuil que le @media (min-width:500px) plus bas dans ce fichier) le 2026-08-30. */
.vignette-evenement-visiteur-infos {
  font-size: 13px;
  font-weight: 500;
  color: var(--color-text-muted);
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 0 10px;
}
.vignette-evenement-visiteur-infos div {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Distance sous la ville (pas sur la même ligne) — demande de Christophe, 2026-08-16. Plus
   d'indentation : icônes horaires/ville supprimées le même jour. */
.vignette-evenement-visiteur-distance { padding-left: 0; }
/* Icône 40 "Établissement recommandé" (cumul des avis, voir eventVignetteHtml/evt.avisRecommande) —
   déplacée sur la ligne de distance, à droite — demande de Christophe, 2026-08-17 (était en
   bas-droite de la photo). Icône positionnée en absolu (pas en flex) : sinon sa hauteur (24px, plus
   grande que le texte) agrandit la ligne et casse l'espacement régulier avec les 2 lignes du
   dessus (demande de Christophe, 2026-08-17 : "Met le meme espace que l'horaire et la ville entre
   la ville et la distance") — en absolu, la ligne garde exactement la hauteur du texte, comme ses
   voisines. */
/* Ligne horaire ("24h/24" ou plage horaire), même colonne droite que ville-ligne/distance-ligne
   ci-dessous (position:relative + padding-right:30px, élément marqueur en position:absolute;
   right:0) — accueille désormais le voyant OUVERTURE (point-etat-groupe), déplacé depuis la photo
   le 2026-09-08 (demande de Christophe : "Déplace les voyants à droite de la ligne horaire, aligné
   verticalement au texte avis et recommandé"). */
.vignette-evenement-visiteur-horaire-ligne { position: relative; padding-right: 30px; }
.vignette-evenement-visiteur-horaire-ligne .point-etat-groupe { position: absolute; top: 50%; right: 0; transform: translateY(-50%); }
.vignette-evenement-visiteur-distance-ligne { position: relative; padding-right: 30px; }
.vignette-evenement-visiteur-avis-recommande { position: absolute; top: 50%; right: 0; transform: translateY(-50%); width: 24px; height: 24px; }
/* Écran Tendances (menu latéral, copie indépendante de events.icilago.eu, 2026-09-12) —
   evt.favorisNb/partagesNb/itineraireNb ne sont renvoyés QUE par /guide/tendances-visiteur. Ligne
   sous la distance affichant les 3 compteurs + les vues, une icône par compteur, espacés
   régulièrement. */
.vignette-evenement-visiteur-stats-tendances {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 12px;
  color: var(--color-text-muted);
  white-space: normal;
}
.vignette-evenement-visiteur-stats-tendances span {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  white-space: nowrap;
}
.vignette-evenement-visiteur-stats-icone {
  width: 21px;
  height: 21px;
  flex-shrink: 0;
}
/* Moyenne des avis + icône 31, sur la ligne "ville" — demande de Christophe, 2026-08-17, au-dessus
   de l'icône 40 ci-dessus (même règle position:absolute + right:0, pour rester alignées entre
   elles sans agrandir la hauteur de la ligne texte). */
.vignette-evenement-visiteur-ville-ligne { position: relative; padding-right: 30px; }
.vignette-evenement-visiteur-avis-moyenne {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 2px;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-text);
}
/* Étoile générique (SVG inline, fill:currentColor) — remplace l'icône Icone_0031.png fournie par
   Christophe (retirée de cet affichage seulement, PAS supprimée du disque) : une image PNG ne
   peut pas suivre la couleur de contraste automatique --couleur-texte-mise-en-avant, alors qu'un
   SVG en currentColor hérite du color: déjà posé sur .vignette-evenement-visiteur-avis-moyenne
   (var(--color-text) par défaut, var(--couleur-texte-mise-en-avant) sur la vignette mise en
   avant) — demande de Christophe, 2026-08-30. */
.vignette-evenement-visiteur-avis-etoile { width: 14px; height: 14px; flex-shrink: 0; }
/* Pas de règle .is-grise dédiée ici : .event-card.is-grise (blur) et la règle générique
   .is-grise (opacity: 0.4) s'appliquent déjà, comme pour toutes les autres cartes verrouillées
   de l'app — cohérence demandée implicitement (même traitement partout). */

/* Rond d'état (liste "Près de vous") : même code couleur que la barre d'état du tableau de
   bord annonceur (voir calculerEtatBarreEvenement), coin haut-droit du cadre photo, centré
   verticalement sur l'icône favori (top:5px, height:28px → centre à 19px ; ici height:14px →
   top:12px pour aligner les deux centres). */
.point-etat {
  position: absolute;
  top: 12px;
  right: 5px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  box-shadow: 0 1px 4px rgba(0,0,0,0.15);
}
/* .point-etat-groupe : enveloppe systématiquement le rond d'état (voir eventCardHtml/
   eventVignetteHtml), même quand aucun libellé n'est affiché — permet au tick de
   rafraîchissement (actualiserDegradesVisiteurAujourdhui) d'ajouter/retirer le compte à rebours
   "Bientôt" sans reconstruire la carte. Reprend exactement les coordonnées de .point-etat seul ;
   le rond redevient position:static à l'intérieur (mêmes dimensions/couleur, inchangées). Mode
   Liste (.thumb) et mode Icônes (.vignette-evenement-visiteur-photo) ont des coordonnées
   différentes, comme .point-etat avant ce correctif — demande de Christophe, 2026-08-09. */
.point-etat-groupe {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 5px;
}
.point-etat-groupe .point-etat { position: static; box-shadow: 0 1px 4px rgba(0,0,0,0.15); }
.thumb .point-etat-groupe { top: 12px; right: 5px; }
/* Libellé compte à rebours "Bientôt" (ex. "42min"), à gauche du rond — même style de pastille
   que .vues-evenement-sur-photo (fond blanc translucide, lisible sur toute photo). */
.point-etat-label-bientot {
  font-size: 11px;
  font-weight: 600;
  color: #000;
  background: rgba(255,255,255,0.7);
  padding: 1px 6px;
  border-radius: 6px;
  white-space: nowrap;
}

.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid #eceff1;
}
.toggle-row-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.toggle-row-text span { font-size: 14.5px; color: var(--color-text); }
.toggle-row-text em { font-size: 12px; color: var(--color-text-muted); font-style: italic; }
.toggle-switch {
  position: relative;
  display: inline-block;
  width: 46px;
  height: 26px;
  flex-shrink: 0;
}
.toggle-switch input {
  opacity: 0;
  width: 0;
  height: 0;
}
.toggle-slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background-color: #d9dcdf;
  border-radius: 26px;
  transition: background-color .2s ease;
}
.toggle-slider::before {
  content: "";
  position: absolute;
  height: 22px;
  width: 22px;
  left: 2px;
  top: 2px;
  background-color: #fff;
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
  transition: transform .2s ease;
}
.toggle-switch input:checked + .toggle-slider { background-color: var(--color-primary); }
.toggle-switch input:checked + .toggle-slider::before { transform: translateX(20px); }
.toggle-row-option {
  padding: 4px 14px 12px;
  border-bottom: 1px solid #eceff1;
  background: #fafbfc;
}
.toggle-row-option label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--color-text-muted);
}
.toggle-row-option input[type="number"] {
  width: 80px;
  padding: 5px 8px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  font-size: 13px;
}
.toggle-row-option .chip-group { margin-top: 2px; }
.event-card h3 { margin: 0 0 10px; font-size: 15px; }
.event-card .meta {
  font-size: 12.5px;
  color: var(--color-text-muted);
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 20px;
  background: var(--color-border);
  color: var(--color-text);
}
.badge.free { background: #e2f3ea; color: var(--color-success); }
.badge.paid { background: #fdecd6; color: #a86a1d; }
.badge.locked { background: #f3e2e2; color: var(--color-danger); }
.badge.attente-validation { background: var(--color-danger); color: #fff; }

/* ---------- Forms ---------- */
.field { margin-bottom: 14px; }
.field label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 6px;
  color: var(--color-texte-libelle);
}
.field .hint { font-size: 12px; color: var(--color-text-muted); margin-top: 4px; }
input[type="text"], input[type="email"], input[type="tel"], input[type="password"],
input[type="number"], input[type="date"], input[type="time"], input[type="file"],
input[type="search"],
select, textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-input-bg);
  color: var(--color-text);
}
textarea { min-height: 80px; resize: vertical; }

/* Bouton "œil" afficher/masquer, ajouté automatiquement sur tous les champs
   mot de passe (voir activerAfficherMotDePasse() dans main.js). */
.password-field-wrapper { position: relative; }
.password-field-wrapper input { padding-right: 42px; }
.password-toggle-btn {
  position: absolute;
  right: 2px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  padding: 8px;
  color: var(--color-text-muted);
}
.password-toggle-btn:hover { color: var(--color-text); }
.chip-group { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  padding: 6px 12px;
  border-radius: 20px;
  border: 1px solid var(--color-border);
  background: #fff;
  font-size: 13px;
  cursor: pointer;
  color: var(--color-text-muted);
}
.chip.selected {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
}
/* "Mes centres d'intérêt" (Mes paramètres, réintroduit 2026-09-13) — badges avec icône/pastille,
   même base visuelle que .chip mais en flex pour aligner l'icône devant le texte. */
.chip-badge-interet {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 20px;
  border: 1px solid var(--color-border);
  background: #fff;
  font-size: 13px;
  cursor: pointer;
  color: var(--color-text-muted);
}
.chip-badge-interet.selected {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
}
.chip-badge-interet-icone { width: 18px; height: 18px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.chip-badge-interet-pastille { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
/* Séparateur entre les badges et les colonnes de thèmes ci-dessous — demande explicite de
   Christophe ("Traits de séparation"). */
.separateur-centres-interet-themes { border-top: 1px solid var(--color-border); margin: 14px 0; }
/* X colonnes (X = console > Tarifs visiteurs > "Mes centres d'intérêts") : une par centre
   d'intérêt sélectionné, dans l'ordre de sélection ; vides tant qu'un centre d'intérêt
   correspondant n'a pas été choisi. Traits de séparation verticaux entre colonnes adjacentes. */
.colonnes-themes-centres-interet { display: flex; gap: 0; }
.colonne-theme-centre-interet {
  flex: 1 1 0;
  min-width: 0;
  padding: 0 12px;
  border-left: 1px solid var(--color-border);
}
.colonne-theme-centre-interet:first-child { border-left: none; padding-left: 0; }
.colonne-theme-centre-interet-titre {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  font-size: 13px;
  color: var(--color-text);
  margin-bottom: 6px;
}
.colonne-theme-centre-interet-vide { font-size: 12px; color: var(--color-text-muted); font-style: italic; }
.colonne-theme-centre-interet-liste { font-size: 12px; color: var(--color-text-muted); line-height: 1.7; }
.radio-group { display: flex; gap: 8px; flex-wrap: wrap; }
.radio-group label {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  font-size: 13px;
  font-weight: 400;
  cursor: pointer;
}
.checkbox-field {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  cursor: pointer;
}
.checkbox-field input[type="checkbox"] { width: 18px; height: 18px; }

.info-tip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-border);
  color: var(--color-text-muted);
  font-size: 11px;
  cursor: help;
  margin-left: 4px;
  vertical-align: middle;
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  padding: 12px 16px;
  border-radius: 10px;
  border: none;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  /* Légère ombre portée sur tous les boutons de l'application. */
  box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
.btn-primary { background: var(--color-primary); color: #fff; }
.btn-primary:active { background: var(--color-primary-dark); }
.btn-secondary { background: #fff; color: var(--color-primary); border: 1px solid var(--color-primary); }
.btn-accent { background: var(--color-accent); color: #fff; }
.btn-danger { background: var(--color-danger); color: #fff; }
.btn-ghost { background: none; color: var(--color-text-muted); }
.btn-sm { padding: 8px 12px; font-size: 13px; width: auto; }
/* Fiche évènement (events) : bouton "GO !" agrandi de 25% par rapport à .btn-sm (demande de
   Christophe, 2026-08-05). */
#btn-go-itineraire { padding: 10px 15px; font-size: 16px; }
/* Bouton "GO !" clignotant orange (texte blanc) / vert (texte noir), demande de Christophe,
   2026-08-05. */
@keyframes clignoter-btn-go-itineraire { 0%, 49% { background: #ff8c00; color: #fff; } 50%, 100% { background: #2ecc71; color: #000; } }
#btn-go-itineraire { animation: clignoter-btn-go-itineraire 1s steps(1) infinite; border: none; }
.btn[disabled] { opacity: 0.5; }
/* Survol bleu icilago (100%) sur TOUS les boutons — demande de Christophe, 2026-09-11, "pour
   toutes les applications, quand on survole un bouton, le bouton passe en bleu icilago (100%),
   sauf pour les boutons déjà en verts". ":not(.clignote-vert)" exclut #btn-enregistrer-compte-profil
   pendant son clignotement (voir plus haut) — #btn-go-itineraire (vert/orange en continu, sans
   classe .clignote-vert) reste protégé de fait : son animation continue écrase la couleur de
   survol tant qu'elle tourne. La spécificité ".btn:hover" (2 classes) l'emporte déjà sur
   ".btn-primary"/"-secondaire"/"-accent"/"-danger"/"-ghost" (1 classe chacune) sans !important. */
.btn:hover:not([disabled]):not(.clignote-vert) {
  background: var(--color-bleu-icilago, #1e3a8a);
  color: #fff;
  border-color: var(--color-bleu-icilago, #1e3a8a);
}
/* Exclusion du survol bleu icilago pour les 2 boutons icônes de "Près de vous" (raccourci Carte +
   bascule Liste/Icônes) — demande de Christophe, 2026-09-12 : "les 2 icones ne doivent pas devenir
   bleu lors du survol. Ils gardent leurs comportements normal". Sélecteur ID + spécificité égale
   (2 classes) que la règle ci-dessus mais placé APRÈS dans la cascade, donc l'emporte. */
#btn-raccourci-carte-pres-de-vous.btn-secondary:hover,
#btn-toggle-affichage-pres-de-vous.btn-secondary:hover {
  background: #fff;
  color: var(--color-primary);
  border-color: var(--color-primary);
}

/* Fonctionnalité non incluse dans le palier d'accès actuel : reste cliquable (un message
   d'invitation à changer de palier s'affiche), mais visuellement estompée. */
.is-grise { opacity: 0.4; }
.is-grise:hover, .is-grise:focus { opacity: 0.55; }
input.is-grise, select.is-grise { background: var(--color-bg); cursor: not-allowed; }
/* "Mon compte" (menu latéral + navigation basse) en ACCÈS LIBRE : is-grise + disabled natif
   (voir appliquerGrisageBoutonCompte()) — celui-ci n'est PAS cliquable, contrairement au cas
   général ci-dessus. */
button.is-grise:disabled:hover { opacity: 0.4; }
/* Pas d'icône "sens interdit" au survol des boutons désactivés (demande explicite) : gagne sur
   toute règle plus spécifique ci-dessus/ailleurs qui poserait cursor:not-allowed sur un bouton. */
button:disabled { cursor: default !important; }

/* ---------- Home / role picker ---------- */
/* Sur l'accueil, la barre du haut (bandeau bleu "ICILAGO Events") est masquée : le vrai logo
   remplace le bandeau + le logo emoji + le titre texte. */
#app:has(#view-accueil.active) .app-header { display: none; }
#app-contenu:has(#view-accueil.active) { padding-top: 0; }
/* Image de fond plein écran de l'accueil (croppée pour couvrir tout l'écran, sans déformation). */
#view-accueil.active {
  padding-top: 0;
  /* absolute (pas fixed) : sa boîte de référence devient #app (position:relative, déjà
     centré/limité à 480px) au lieu du viewport. Corrigé le 2026-07-28 (vidéo à l'appui, côté
     annonceurs, même code ici) : en position:fixed avec top:0/bottom:0, la hauteur de cette
     boîte est recalculée par rapport au viewport — et l'ouverture d'une pop-up (qui ajoute
     overflow:hidden sur html/body) fait légèrement varier ce calcul sous iOS/PWA installée, ce
     qui recadre différemment l'image de fond en background-size:cover (donc un "saut" visible du
     fond d'écran, pas du contenu). En absolute, la boîte est ancrée sur #app dont la hauteur ne
     bouge jamais pendant ce verrou. */
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1;
  background-image: url('https://api.icilago.eu/picts/Fonds_ecrans/events_image_spash.jpg');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  /* background-attachment: fixed — revenu à "fixed" le 2026-09-04 (était passé à "scroll" en
     accompagnement du ratchet --h-max, lui-même retiré suite à une régression de cadrage — voir
     icilago-guide-bande-blanche-fond-accueil-paint-bug en mémoire). Sans le ratchet, plus besoin
     de cette précaution. Fixe l'image de fond par rapport à l'écran, indépendamment d'un
     éventuel défilement du contenu (Christophe, 2026-08-11 : "le fond d'écran de la page
     d'accueil n'est pas fixe") — sans toucher à `position` ci-dessus (resté `absolute`, voir
     commentaire), pour ne pas réintroduire le bug du 2026-07-28 (saut du fond au verrou de
     défilement d'une pop-up). */
  background-attachment: fixed;
}
.hero {
  text-align: center;
  padding: 10px 20px 20px;
}
/* Le bloc logo est ancré en position fixe (comme .accueil-bottom) pour ne plus bouger du tout
   au toucher/glissement — il ne fait plus partie du flux défilable de la vue. */
#view-accueil.active .hero {
  position: fixed;
  /* + safe-area-inset-top : garde 10px sous l'encoche/barre de statut plutôt que sous le
     tout premier pixel de l'écran (utile en mode "ajouté à l'écran d'accueil"). */
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  padding: 0 20px;
  box-sizing: border-box;
  z-index: 15;
}
/* Recouvrement du haut du .hero (logo/message rotatif) par la grande barre d'adresse flottante de
   Safari iOS (constaté par Christophe, 2026-09-02, lien ouvert depuis un QR code scanné à la
   caméra — capture montrant le texte à moitié caché sous "guide.icilago.eu") : env(safe-area-
   inset-top) ne couvre que l'encoche/la barre de statut, PAS cette barre d'adresse en plus, qui
   n'existe que dans un onglet Safari normal — absente en mode "Ajouté à l'écran d'accueil"
   (standalone), donc pas de marge supplémentaire dans ce cas via ce media query dédié. */
@media not all and (display-mode: standalone) {
  #view-accueil.active .hero {
    top: calc(env(safe-area-inset-top, 0px) + 60px);
  }
}
.hero-logo {
  display: block;
  width: 70%;
  max-width: 260px;
  height: auto;
  margin: 0 auto 14px;
}
.hero p { color: #fff; font-size: 14px; margin: 0; }
/* Fondu léger lors du changement de message rotatif sous le logo (voir chargerMessageAccueilRotatifDepuisApi). */
/* Blanc avec halo noir en text-shadow, 2026-09-09 — demande de Christophe : "Applique sur les
   messages d'accueil les memes contours de texte que pour le texte 'Ajouter à l'écran
   d'accueil'". Message rotatif configuré dans icilagoconsole → Applications → Messages d'accueil
   (voir chargerMessageAccueilRotatifDepuisApi()) : contenu variable, affiché sur le fond hero qui
   peut être clair ou sombre selon l'app — même halo que .lien-partage-app/.accueil-note pour
   rester lisible dans tous les cas, voir mémoire icilago-accueil-texte-lisibilite-halo. */
#accueil-message-rotatif {
  color: #fff;
  text-shadow: 0 0 4px rgba(0,0,0,.85), 0 0 8px rgba(0,0,0,.7), 0 1px 2px rgba(0,0,0,.9);
  transition: opacity .25s ease;
}
#accueil-message-rotatif.message-accueil-fondu { opacity: 0; }
/* Bandeau "Astuces" sous la barre bleue (console > Applications > fiche > "ASTUCES") — une
   astuce à la fois, en rotation avec fondu enchaîné (même transition que
   #accueil-message-rotatif ci-dessus). Masqué par défaut (classe .hidden posée dans le HTML) :
   réapparaît seulement si mettreAJourBandeauAstuces() trouve au moins une astuce ciblant la vue
   affichée. Demande de Christophe, 2026-09-12. */
#astuce-bandeau {
  background: transparent;
  color: var(--color-text);
  font-size: 13px;
  line-height: 1.4;
  padding: 8px 16px;
  text-align: center;
  transition: opacity .25s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Réserve la hauteur de 2 lignes (line-height 1.4 × font-size 13px × 2 + padding vertical
     16px) pour que le contenu affiché EN DESSOUS du bandeau ne descende/remonte pas quand une
     astuce passe de 1 à 2 lignes ou inversement (modes Fixe et Glissant) — demande de Christophe,
     2026-09-12. Sans effet sur "Déroulant" (toujours 1 seule ligne, white-space:nowrap), qui reste
     simplement centré verticalement dans cet espace réservé. */
  min-height: calc(13px * 1.4 * 2 + 16px);
}
#astuce-bandeau.astuce-bandeau-fondu { opacity: 0; }
/* MODE "Déroulant" (console > Réglages > Paramètres > ASTUCES > MODE, demande de Christophe,
   2026-09-12) : toutes les astuces de la page concaténées en un seul texte qui défile en continu
   de droite à gauche, comme un bandeau d'actualités — remplace la rotation avec fondu ci-dessus
   quand ce mode est actif (voir mettreAJourBandeauAstuces()). padding-left:100% place le texte
   hors écran à droite avant que l'animation ne le fasse glisser jusqu'à sa sortie complète à
   gauche (translateX(-100%)), pour un défilement continu sans saut visible en boucle. */
#astuce-bandeau.astuce-bandeau-defilant {
  overflow: hidden;
  white-space: nowrap;
  text-align: left;
  /* Contrebalance justify-content:center posé sur #astuce-bandeau (voir plus haut) : le texte du
     défilement doit démarrer collé au bord gauche du conteneur (padding-left:100% sur le span
     s'appuie dessus pour partir hors écran à droite), pas être centré comme les autres modes. */
  justify-content: flex-start;
}
#astuce-bandeau.astuce-bandeau-defilant .astuce-bandeau-texte-defilant {
  display: inline-block;
  padding-left: 100%;
  animation-name: astuce-bandeau-defilement;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
@keyframes astuce-bandeau-defilement {
  from { transform: translateX(0); }
  to { transform: translateX(-100%); }
}
/* MODE "Glissant" (console > Réglages > Paramètres > ASTUCES > MODE, demande de Christophe,
   2026-09-12) : une astuce à la fois (comme "Fixe"), mais glisse depuis la droite vers la gauche
   au lieu d'un fondu enchaîné — voir mettreAJourBandeauAstuces(). overflow:hidden sur le bandeau
   masque le texte tant qu'il est hors de la zone visible ; le span interne fait toute la largeur
   du bandeau (width:100%) pour que translateX(±100%) corresponde à "une largeur de bandeau
   entière" (et non la largeur du texte lui-même, plus courte) — le texte est donc toujours
   complètement hors écran avant/après chaque glissement, quelle que soit sa longueur.
   ".astuce-bandeau-glissant-sans-transition" repositionne le texte suivant hors écran à droite
   INSTANTANÉMENT (sans transition visible) avant de la relancer, même technique que
   "sauterSansAnimation()" du carrousel de langues de l'accueil.
   IMPORTANT (bug corrigé le 2026-09-12, "il reste un bout de texte à gauche") : translateX(N%)
   se calcule par rapport à la largeur PROPRE de l'élément transformé, PAS par rapport au
   conteneur. Avec le padding horizontal de #astuce-bandeau (16px de chaque côté) resté sur le
   conteneur, le span (width:100% du contenu, donc plus étroit que la boîte de padding du
   conteneur, qui est la zone réellement découpée par overflow:hidden) ne parcourait qu'une
   distance égale à sa propre largeur — plus courte que la largeur totale à dégager — laissant un
   reliquat de texte visible dans la bande de padding. Fix : le padding horizontal est retiré du
   conteneur en mode "Glissant" et reporté sur le span lui-même en box-sizing:border-box, pour que
   la largeur du span (base du calcul de translateX) corresponde exactement à la largeur totale
   découpée par overflow:hidden. */
#astuce-bandeau.astuce-bandeau-glissant { overflow: hidden; padding-left: 0; padding-right: 0; }
.astuce-bandeau-texte-glissant {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 0 16px;
  transform: translateX(0);
  transition: transform .4s ease;
}
.astuce-bandeau-texte-glissant.astuce-bandeau-glissant-sans-transition { transition: none; }
.role-cards { display: flex; flex-direction: column; gap: 12px; margin-top: 20px; }
/* Sur l'accueil, les boutons + le texte de consentement position sont ancrés ensemble en bas
   de l'écran réel (et non du bas de la vue, qui laisse un espace mort réservé à la barre de
   nav masquée sur cet écran). Le gap de 50px place les boutons exactement 50px au-dessus du
   texte, qui reste lui juste au-dessus du bord de l'écran. */
#view-accueil.active .accueil-bottom {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  padding: 0 14px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 50px;
  z-index: 15;
}
#view-accueil.active .accueil-bottom .role-cards { margin-top: 0; }
/* .hero et .accueil-bottom sont en position:fixed (ci-dessus) : ils restent donc visibles en
   permanence, même derrière une pop-up ouverte — or .modal-overlay n'a plus de voile sombre
   (fond transparent, voir icilago-modal-overlay-fond-transparent), donc la carte blanche
   "Accès visiteur" (.role-card, fond blanc) restait visible À TRAVERS, juste sous une pop-up
   ouverte depuis l'accueil (ex. "Partager ICILAGO Events"), créant une zone blanche confuse en
   bas d'écran (signalé le 2026-07-27, confirmé par capture d'écran côté annonceurs — même
   structure ici). Masqués tant qu'une pop-up est ouverte, via la même classe globale que le
   verrou de défilement — visibility (pas display) pour ne pas perturber leur propre mise en page
   fixe. */
html.defilement-verrouille #view-accueil.active .hero,
html.defilement-verrouille #view-accueil.active .accueil-bottom {
  visibility: hidden;
}
/* Sélecteur de langue de l'accueil — ROUE TOURNANTE (demande de Christophe, 2026-09-14, remplace
   la ligne de drapeaux défilante ci-dessus, GUIDE UNIQUEMENT). Reprend le principe de la roue de
   traduction de annonceurs.icilago.eu (repeating-conic-gradient pour les portions égales, trou
   central via ::after, drapeaux positionnés en JS sur un cercle) à une échelle réduite (200px au
   lieu de 280px, 6 langues au lieu de 5) pour ne pas trop pousser vers le haut la carte "Accès
   visiteur" au-dessus (.accueil-bottom est position:fixed, ancré par le bas — voir commentaire sur
   .accueil-bottom plus haut). Voir initRoueLanguesAccueil() dans main.js pour tout le calcul de
   placement/rotation/aimantation (même math que annonceurs, voir
   icilago-annonceurs-flyer-traduction-manuelle en mémoire pour le détail des formules).
   RESTYLÉ le 2026-09-14 (2e correctif du même jour, capture d'une "roue d'arme" de jeu vidéo à
   l'appui — "essaye de me faire le même type de roue") : portions SOMBRES et SEMI-TRANSPARENTES
   (au lieu du blanc plein posé au 1er jet) + trou central TRANSPARENT (au lieu du disque blanc
   "bombé" du correctif précédent, qui devient sans objet — la référence n'a pas de disque plein au
   centre, juste le décor qui se voit À TRAVERS) + liserés fins clairs entre les portions, façon
   roue de sélection d'arme dans un jeu (HUD translucide). Portion du haut (sous la flèche)
   légèrement éclaircie par un halo FIXE (.accueil-roue-langues-spot, ne tourne PAS avec la roue)
   pour rappeler la portion mise en valeur dans la capture de référence, sans avoir à recalculer un
   dégradé différent à chaque rotation. */
.accueil-roue-langues-conteneur {
  position: relative;
  width: 200px;
  margin: 0 auto;
  touch-action: pan-y;
}
.accueil-roue-langues {
  position: relative;
  width: 200px;
  height: 200px;
  border-radius: 50%;
  box-shadow: 0 4px 14px rgba(0,0,0,.45), inset 0 0 0 1px rgba(255,255,255,.12);
  cursor: grab;
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
  /* "from -31deg" : même raisonnement que .grille-drapeaux-traduction (annonceurs, style-3.css) —
     conic-gradient() place l'angle 0 au NORD et tourne dans le sens HORAIRE, alors que le
     positionnement JS des drapeaux (Math.cos/Math.sin) utilise le repère écran classique
     (0°=EST). Les 6 drapeaux sont posés à angle_JS = -90+i×60 (départ nord, horaire), donc
     angle_CSS = i×60. Centre d'une portion (séparateur 2deg) = F+2+(60-2)/2 = F+31 ; pour que ce
     centre tombe sur i×60, il faut F+31=0, donc F=-31. Portions sombres semi-transparentes
     (rgba(15,23,42,.6), teinte proche du bleu marine ICILAGO plutôt qu'un noir neutre) + liseré
     clair fin (rgba(255,255,255,.5)) entre chaque portion, façon roue d'arme de jeu vidéo.
     CORRECTIF 2026-09-14 (3e passage, "les tranches doivent avoir un effet bombé, regarde bien mon
     modèle") : 1re couche radiale ajoutée PAR-DESSUS le conic-gradient (plusieurs backgrounds
     superposés, la 1re déclarée est peinte au-dessus) — varie uniquement selon la DISTANCE au
     centre (pas l'angle, donc affecte TOUTES les tranches de façon identique à rayon égal) :
     sombre près du trou (39px, effet de bord creusé), clair vers le milieu de l'anneau (62px,
     "bosse" centrale), ombré à nouveau vers le bord extérieur (100px) — simule une coupe
     transversale arrondie/bombée façon tube, comme sur la roue de référence, sans avoir à générer
     un dégradé par tranche individuellement (impossible avec un seul conic-gradient, qui ne varie
     que par angle). */
  /* Reflet blanc rapproché du bord du trou central (2026-09-14, 4e passage : "le reflet blanc
     approche le [sic] du bord centre") — pic de clarté déplacé de 62px à 47px (juste après le bord
     du trou à 39px, au lieu du milieu de l'anneau), reste de la coupe transversale inchangé
     (assombrissement progressif vers le bord extérieur à 100px). */
  background:
    radial-gradient(circle, rgba(0,0,0,.4) 39px, rgba(255,255,255,.35) 47px, rgba(0,0,0,.15) 75px, rgba(0,0,0,.45) 100px),
    repeating-conic-gradient(
      from -31deg,
      rgba(255,255,255,.5) 0deg 1.5deg,
      rgba(15,23,42,.6) 1.5deg 60deg
    );
  /* Trou central VRAIMENT transparent (2026-09-14, 3e passage : "le rond intérieur doit etre
     transparent" — le ::after ci-dessous, à lui seul transparent, ne suffisait PAS : il se
     contente de ne rien AJOUTER par-dessus le fond de .accueil-roue-langues, qui lui peint le
     conic-gradient sur TOUT le disque (y compris sous le trou), donc le centre affichait encore
     la couleur de la tranche à cet endroit, pas le décor de la page. Un masque CSS découpe
     réellement un disque troué dans le fond ci-dessus : transparent jusqu'à 39px du centre (rayon
     du trou, identique à la taille du ::after), opaque au-delà — ce qui laisse voir à travers tout
     ce qu'il y a DERRIÈRE tout le composant (la photo de fond de l'accueil), pas seulement "pas de
     couleur ajoutée". Préfixe -webkit- nécessaire pour Safari/iOS. */
  -webkit-mask-image: radial-gradient(circle, transparent 39px, black 40px);
  mask-image: radial-gradient(circle, transparent 39px, black 40px);
}
.accueil-roue-langues:active { cursor: grabbing; }
/* Cerclage clair + ombre intérieure autour du trou (désormais VRAIMENT transparent, voir le masque
   ci-dessus) — purement décoratif, définit un bord net à la découpe du masque. */
.accueil-roue-langues::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 78px;
  height: 78px;
  border-radius: 50%;
  background: transparent;
  border: 1.5px solid rgba(255,255,255,.55);
  box-shadow: inset 0 0 10px rgba(0,0,0,.35);
}
.accueil-roue-langue-item {
  position: absolute;
  width: 56px;
  height: 56px;
  font-size: 34px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  cursor: pointer;
  padding: 0;
  /* Ombre légère sous chaque drapeau — les emojis plats se détachaient peu du fond sombre sans
     ça, contrairement au fond blanc du 1er correctif. */
  filter: drop-shadow(0 1px 3px rgba(0,0,0,.6));
}
.accueil-roue-langue-drapeau { line-height: 1; }
/* Halo FIXE (ne tourne PAS avec .accueil-roue-langues, posé à côté dans le DOM, pas dedans) qui
   éclaircit en permanence la portion du haut — sous la flèche — pour rappeler la portion mise en
   valeur de la capture de référence (roue d'arme), sans recalculer un dégradé dépendant de
   l'angle courant à chaque frame de rotation : un simple conic-gradient statique centré au nord
   suffit, puisque la flèche pointe toujours au même endroit. */
.accueil-roue-langues-spot {
  position: absolute;
  top: 0;
  left: 0;
  width: 200px;
  height: 200px;
  border-radius: 50%;
  background: conic-gradient(from -30deg, rgba(255,255,255,.22) 0deg 60deg, transparent 60deg 360deg);
  /* Même masque que .accueil-roue-langues (voir plus haut) : sans lui, ce halo peindrait un léger
     voile blanc jusque dans le trou central, qui doit rester entièrement transparent. */
  -webkit-mask-image: radial-gradient(circle, transparent 39px, black 40px);
  mask-image: radial-gradient(circle, transparent 39px, black 40px);
  pointer-events: none;
  z-index: 2;
}
/* Triangle indicateur, POINTE VERS LE BAS — indique la portion actuellement en haut, sous lui. Ne
   tourne jamais avec la roue (position fixe dans .accueil-roue-langues-conteneur).
   CORRECTIF 2026-09-14 (6e passage : "la fleche n'est pas belle graphiquement, applique le meme
   effet que pour la roue") : technique changée de la bordure triangulaire (border-left/right
   transparent + border-top coloré) à `clip-path: polygon(...)` sur un `div` classique — LA
   BORDURE NE PEUT PAS PORTER UN DÉGRADÉ (border-color n'accepte qu'une couleur unie), donc
   impossible d'y appliquer le même style "bombé" (radial-gradient) que la roue avec l'ancienne
   technique. `clip-path` découpe un `background` normal (dégradé compris) à la forme du triangle,
   permettant enfin d'y poser un `radial-gradient` clair en haut → sombre en bas, exactement le
   même principe visuel que le dégradé radial des tranches de la roue (voir plus haut). Mêmes
   dimensions/position qu'avant (32×28px pour le bleu, décalé de -34px, pointe à -6px) — seule la
   technique de rendu change, pas la taille ni l'emplacement.
   CORRECTIF 2026-09-14 (7e passage : "la fleche met la de la meme couleur que la roue") : dégradé
   passé du bleu ICILAGO (#1e3a8a) à la même teinte ardoise/marine sombre que les tranches de la
   roue (rgb(15,23,42), voir .accueil-roue-langues plus haut) — même famille de dégradé "lumière
   décentrée en haut" (radial-gradient), juste recoloré pour matcher exactement.
   CORRECTIF 2026-09-14 (8e passage : "enleve le bord blanc") : le 2e triangle blanc plus grand
   (`.accueil-roue-langues-triangle-bord`, ajouté au 5e passage) RETIRÉ — du HTML (div supprimée)
   ET de ce fichier CSS (règle retirée, plus d'élément à styler).
   CORRECTIF 2026-09-14 (9e passage : "baisse la fleche de 40px / La fleche doit etre sur la
   roue") : `top` remonté de -34px à -34+40 = 6px — la flèche n'est plus au-dessus de la roue mais
   chevauche désormais son bord supérieur (z-index:4, au-dessus de la roue z-index par défaut et du
   halo statique z-index:2), conformément à la demande.
   CORRECTIF 2026-09-14 (10e passage : "la fleche doit être a cheval, remonte là pour que 1/3 de la
   fleche soit sur la roue") : le tour précédent (top:6px) plaçait la flèche ENTIÈREMENT sur la
   roue (0 à 6px + hauteur 28px = de 6 à 34px, tout est ≥0 donc tout est déjà dans le disque) — pas
   de "à cheval" du tout. Recalculé pour que seul le TIERS BAS de la flèche (hauteur 28px, pointe en
   bas) dépasse sous le bord supérieur de la roue (y=0 dans le conteneur) : bas de la flèche à
   28/3≈9px, donc top = 9-28 = -19px (1/3 de 28px ≈ 9px sur la roue, 2/3 ≈ 19px au-dessus, sur la
   photo de fond).
   CORRECTIF 2026-09-14 (19e passage : "supprime le contour blanc") : le contour non clippé du 17e/
   18e passage (`.accueil-roue-langues-triangle-contour`, double-triangle t=1px sur toute la
   hauteur) RETIRÉ — du HTML (div supprimée) ET de ce fichier CSS (règle retirée). Le bord épais de
   5px du 16e passage (`.accueil-roue-langues-triangle-bord`, confiné à la portion sur la roue)
   n'est PAS concerné, reste en place.
   CORRECTIF 2026-09-14 (19e passage, suite : "Déplace le reflet blanc vers le bas de la fleche") :
   le point clair du dégradé radial (`circle at 50% 15%`, proche du HAUT du triangle depuis le 6e
   passage) déplacé vers le BAS, proche de la pointe : `circle at 50% 85%`.
   CORRECTIF 2026-09-14 (21e passage : "baisse la fleche de 4px") : `top` descendu de -19px à
   -15px (+4px). Pointe déplacée de 9px à 13px — la fenêtre de clipping du bord
   (.accueil-roue-langues-triangle-bord-zone, voir plus bas) et la position du bord lui-même
   (.accueil-roue-langues-triangle-bord) recalculées en conséquence pour rester alignées sur la
   nouvelle pointe. */
.accueil-roue-langues-triangle {
  position: absolute;
  top: -15px;
  left: 50%;
  transform: translateX(-50%);
  width: 32px;
  height: 28px;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  -webkit-clip-path: polygon(0 0, 100% 0, 50% 100%);
  background: radial-gradient(circle at 50% 85%, #4b5568 0%, #1e293b 55%, #0f172a 100%);
  z-index: 4;
  pointer-events: none;
}
/* CORRECTIF 2026-09-14 (11e passage : "ajoute dans la partie de la fleche qui est sur la roue, un
   bord blanc") : reprend la technique du 5e passage (2e triangle blanc plus grand posé derrière,
   mêmes calculs de coïncidence de la pointe : outerTop = innerTop - t, outerHeight = innerHeight +
   t, outerWidth = innerWidth + 2t) — mais cette fois le bord blanc ne doit apparaître QUE sur le
   tiers bas de la flèche (la partie physiquement posée sur la roue, y∈[0,9] dans le conteneur, voir
   10e passage), pas sur toute la flèche. Solution : une "fenêtre" de clipping,
   `.accueil-roue-langues-triangle-bord-zone`, positionnée exactement sur cette plage (top:0,
   height:9px = la portion sur la roue) avec `overflow:hidden` — tout ce qui dépasse de cette
   fenêtre (donc la partie du triangle-bord située au-dessus de y=0, sur la photo de fond) est
   masqué. Le triangle blanc plus grand est un enfant positionné normalement (comme si la fenêtre
   n'existait pas) ; seul le morceau qui tombe dans les 9px visibles de la fenêtre (les côtés/la
   pointe, juste sous le bord de la roue) apparaît réellement à l'écran, créant l'effet de bord
   blanc uniquement sur la portion "à cheval" sur la roue. z-index intercalé entre le halo statique
   (.accueil-roue-langues-spot, 2) et le triangle principal (4).
   CORRECTIF 2026-09-14 (12e passage : "agrandi le bord blanc pour un effet plus visible") :
   épaisseur t doublée de 3px à 6px.
   CORRECTIF 2026-09-14 (13e passage, capture d'écran à l'appui : "le bord blanc doit etre de 2 px
   sur la le bout de la fleche") : le 12e passage (t=6px) était trop épais/imprécis d'après la
   capture — épaisseur ramenée à un liseré fin et net de t=2px, précisément sur la pointe de la
   flèche (seule partie visible du bord, la fenêtre de clipping de 9px ne change pas) : outerTop =
   -19-2 = -21px, outerHeight = 28+2 = 30px, outerWidth = 32+4 = 36px (pointe toujours alignée :
   innerTip = -19+28 = 9, outerTip = -21+30 = 9, identique).
   CORRECTIF 2026-09-14 (15e passage : "la partie de la fleche qui est sur la roue doit avoir un
   bord blanc de 5px") : épaisseur remontée de t=2px à t=5px — outerTop = -19-5 = -24px,
   outerHeight = 28+5 = 33px, outerWidth = 32+10 = 42px (pointe toujours alignée : innerTip = 9,
   outerTip = -24+33 = 9, identique). Fenêtre de clipping (9px, plage sur la roue) inchangée.
   CORRECTIF 2026-09-14 (20e passage, 2 captures à l'appui : "l'échancrure est plus prononcée sur
   la photo 2") : la profondeur visible de l'échancrure en V (voir confirmation précédente) dépend
   directement de l'épaisseur t — plus t est grand, plus le triangle blanc dépasse largement du
   triangle sombre à chaque hauteur, donc plus les 2 bras du V sont larges/visibles. Remonté de
   t=5px à t=10px : outerTop = -19-10 = -29px, outerHeight = 28+10 = 38px, outerWidth = 32+20 = 52px
   (pointe toujours alignée : innerTip = 9, outerTip = -29+38 = 9, identique). Fenêtre de clipping
   (9px, plage sur la roue) inchangée.
   CORRECTIF 2026-09-14 (21e passage : "baisse la fleche de 4px") : la flèche principale descendue
   de 4px (top -19→-15, voir plus haut) déplace sa pointe de 9px à 13px — fenêtre de clipping
   remontée de 9px à 13px pour couvrir exactement la nouvelle portion sur la roue, et bord blanc
   redescendu en conséquence : outerTop = -29+4 = -25px (mêmes outerHeight/outerWidth, seule la
   position bouge avec la flèche ; outerTip = -25+38 = 13, identique à la nouvelle pointe). */
.accueil-roue-langues-triangle-bord-zone {
  position: absolute;
  top: 0;
  left: 0;
  width: 200px;
  height: 13px;
  overflow: hidden;
  z-index: 3;
  pointer-events: none;
}
.accueil-roue-langues-triangle-bord {
  position: absolute;
  top: -25px;
  left: 50%;
  transform: translateX(-50%);
  width: 52px;
  height: 38px;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  -webkit-clip-path: polygon(0 0, 100% 0, 50% 100%);
  background: #fff;
  pointer-events: none;
}
/* Code de la langue actuellement en haut (FR/EN/DE/...), affiché au centre du trou — mis à jour en
   continu pendant le glissement ET à l'arrêt, voir appliquerRotationRoueLanguesAccueil() dans
   main.js (même principe que .roue-langue-centre sur annonceurs). Blanc + ombre noire (pas la
   couleur primaire pleine du 1er correctif, devenue peu lisible sur fond transparent variable
   plutôt que sur le disque blanc plein retiré ci-dessus) — même convention de lisibilité que le
   reste de l'accueil, voir icilago-accueil-texte-lisibilite-halo en mémoire. */
.accueil-roue-langues-centre {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-weight: 700;
  font-size: 16px;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0,0,0,.9), 0 0 6px rgba(0,0,0,.6);
  z-index: 1;
  pointer-events: none;
}
.accueil-bas-texte { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.lien-partage-app {
  background: none;
  border: none;
  color: #fff;
  /* Halo noir en text-shadow (2026-09-04, appliqué depuis events.icilago.eu — demande de
     Christophe : texte blanc illisible sur certaines photos de fond claires). Invisible sur un
     fond déjà sombre (le halo se fond dedans), assure un contraste suffisant sur un fond
     clair/coloré. */
  text-shadow: 0 0 4px rgba(0,0,0,.85), 0 0 8px rgba(0,0,0,.7), 0 1px 2px rgba(0,0,0,.9);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  padding: 4px;
}
/* Filet de sécurité indépendant du JS/localStorage : si l'app tourne réellement en mode
   standalone (installée), le navigateur évalue cette media query en direct, sans dépendre
   d'un cache ou d'un timing d'exécution JS. Masque "Ajouter à l'écran d'accueil" même si la
   détection JS (appDejaInstalleeEnPwa) échouait pour une raison quelconque sur un appareil
   donné (voir mémoire projet icilago-pwa-detection-standalone-fiable, 2026-08-11). */
@media (display-mode: standalone) {
  #btn-ajouter-ecran-accueil { display: none !important; }
}
.accueil-note {
  margin: 0;
  text-align: center;
  font-size: 12.5px;
  line-height: 1.4;
  color: #fff;
  /* Halo noir en text-shadow (2026-09-04, appliqué depuis events.icilago.eu) — voir commentaire
     identique sur .lien-partage-app ci-dessus. */
  text-shadow: 0 0 4px rgba(0,0,0,.85), 0 0 8px rgba(0,0,0,.7), 0 1px 2px rgba(0,0,0,.9);
}
/* Même liseret bleu ICILAGO + rouge défilant que les popups (.modal-sheet, voir @property
   --liseret-angle et @keyframes liseret-popup-defilant plus haut, réutilisés tels quels) —
   demande de Christophe, 2026-08-13, sur la carte "Accès visiteur"/"Accès libre" de l'écran
   d'accueil : même technique "gradient border" à 2 fonds superposés (le fond de la carte habille
   l'intérieur en padding-box, le dégradé habille l'anneau de 2px en border-box). */
.role-card {
  background:
    linear-gradient(var(--color-surface), var(--color-surface)) padding-box,
    conic-gradient(from var(--liseret-angle), var(--color-bleu-icilago, #1e3a8a) 0deg, var(--color-bleu-icilago, #1e3a8a) 70deg, var(--color-danger, #c04848) 160deg, var(--color-danger, #c04848) 200deg, var(--color-bleu-icilago, #1e3a8a) 290deg, var(--color-bleu-icilago, #1e3a8a) 360deg) border-box;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px;
  cursor: pointer;
  border: 2px solid transparent;
  display: flex;
  align-items: center;
  gap: 14px;
  animation: liseret-popup-defilant 3s linear infinite;
}
.role-card:active { border-color: var(--color-primary); }
/* Bloc "Je donne mon avis" (fiche évènement/flyer, sous la map Trajet) — demande de Christophe,
   2026-08-20. Liseret animé retiré le même jour ("enleve le cadre, juste un trait fin de 2
   pixels entre la map et le texte") : simple séparateur 2px (distinct du .divider générique de
   1px), texte au-dessus, icône 80x80px en dessous, le tout centré horizontalement sans fond ni
   bordure. */
.divider-avis-map { height: 2px; background: var(--color-border); margin: 16px 0; }
/* Élément <button> (ouvre #modal-avis, voir main.js) plutôt que <div> depuis le 2026-08-20 :
   reset des styles natifs de bouton (bordure/fond/police) pour garder le même rendu visuel. */
.carte-avis-liseret {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 100%;
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.carte-avis-texte { font-size: 14px; font-weight: 700; color: var(--color-text); text-align: center; }
.carte-avis-icone { width: 80px; height: 80px; }
/* Popup AVIS (#modal-avis) : 6 rubriques de note + 5 étoiles cliquables chacune — demande de
   Christophe, 2026-08-20. Étoiles non sélectionnées en gris clair, sélectionnées (valeur ≤ note
   choisie) en doré, même principe qu'un rating classique. */
.avis-item { margin-top: 18px; }
.avis-item-label { margin: 0 0 8px; font-size: 14px; font-weight: 600; color: var(--color-text); }
.avis-etoiles { display: flex; justify-content: center; gap: 6px; }
.avis-etoile {
  border: none;
  background: none;
  padding: 0;
  font-size: 32px;
  line-height: 1;
  color: #d9d9d9;
  cursor: pointer;
  transition: color .15s ease;
}
.avis-etoile.actif { color: #f5b301; }
/* "Avis global" : simple texte (moyenne des 6 notes, arrondie à l'étoile supérieure, calculée
   côté JS — voir mettreAJourEtoilesAvis()), pas des boutons cliquables — doré comme les étoiles
   actives ci-dessus pour rester lisible sur fond gris. */
.avis-etoiles-lecture-seule { font-size: 32px; line-height: 1; color: #f5b301; letter-spacing: 4px; align-items: baseline; }
/* Valeur chiffrée à 1 décimale à côté des étoiles "Avis global" — demande de Christophe,
   2026-08-17, "pour toutes les applications" (voir aussi console.icilago.eu et la moyenne agrégée
   avis_moyenne de la vignette). Taille réduite : ne pas hériter du 32px des étoiles. */
.avis-global-moyenne { font-size: 16px; font-weight: 600; letter-spacing: normal; color: var(--color-text-muted); }
/* "Je recommande ce lieu" : 2 boutons Oui/Non (icônes 40/41), sélection exclusive — même principe
   visuel que .avis-etoile (gris par défaut, doré une fois choisi). */
.avis-recommande-boutons { display: flex; justify-content: center; gap: 24px; }
.avis-recommande-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
  color: var(--color-text-muted);
  opacity: .45;
  transition: opacity .15s ease;
}
.avis-recommande-btn img { width: 40px; height: 40px; }
.avis-recommande-btn.actif { opacity: 1; color: var(--color-text); }
/* Centré verticalement (pas en bas d'écran comme le comportement par défaut de .modal-overlay,
   align-items:flex-end) — demande de Christophe, 2026-08-20. Scopé à cette seule popup pour ne
   pas modifier les autres (bottom-sheet), inchangées. */
#modal-avis { align-items: center; }
/* Popup "AVIS DÉJÀ DONNÉ" (INFORMATION) — centrée verticalement, demande de Christophe 2026-08-17.
   Largeur réduite de 50px de chaque côté (donc 100px au total) par rapport à la largeur pleine par
   défaut de .modal-sheet — demande de Christophe le même jour. */
#modal-avis-deja-donne { align-items: center; }
/* width seul ne suffisait pas : .modal-sheet plafonne à max-width:480px, qui restait donc le
   facteur limitant sur les écrans où 100% dépasse 480px (le calc() sur width n'avait alors aucun
   effet visible). max-width réduit de 100px pour la même raison. */
#modal-avis-deja-donne .modal-sheet { width: calc(100% - 100px); max-width: 380px; border-radius: 16px; }
/* Popup "ONGLET DÉTAILS VERROUILLÉ" (INFORMATION) — même gabarit que #modal-avis-deja-donne
   ci-dessus, demande de Christophe 2026-09-08. */
#modal-onglet-detaille-verrouille { align-items: center; }
#modal-onglet-detaille-verrouille .modal-sheet { width: calc(100% - 100px); max-width: 380px; border-radius: 16px; }
/* "Établissement recommandé" (fiche évènement, sous le bouton "Je donne mon avis") — icône 40
   affichée seulement si le cumul des avis existants est OUI (voir chargerAvisResume() dans
   main.js) — demande de Christophe, 2026-08-20. */
.avis-resume-recommande { margin-top: 10px; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.avis-resume-recommande-texte { font-size: 13px; font-weight: 600; color: var(--color-text); text-align: center; }
.avis-resume-recommande-icone { width: 60px; height: 60px; }
.role-card .emoji { font-size: 30px; }
.role-card h3 { margin: 0 0 3px; font-size: 15px; }
/* CORRECTIF 2026-09-14 : "modifie la hauteur du bouton pour pas qu'il change de hauteur en
   fonction du nombre de ligne (suivant la traduction) / Prévoit que la traduction soit sur 2
   lignes / Garde cette hauteur" — la carte (.role-card, ex. "Accès visiteur") change de hauteur
   selon que sa traduction tient sur 1 ou 2 lignes (l'allemand "Melden Sie sich an..." en fait 2,
   contre 1 seule en français) : capture à l'appui, hauteur variable indésirable. `line-height`
   fixé explicitement (nécessaire pour calculer un nombre de lignes exact en px) + `min-height`
   réservant TOUJOURS l'espace de 2 lignes, même si le texte réel n'en tient qu'une — la carte ne
   rétrécit plus jamais, quelle que soit la langue. */
.role-card p {
  margin: 0;
  font-size: 12.5px;
  color: var(--color-text-muted);
  line-height: 1.3;
  min-height: calc(1.3em * 2);
}

/* ---------- Utility ---------- */
.section-title { font-size: 14px; font-weight: 700; margin: 18px 0 10px; text-transform: uppercase; letter-spacing: .03em; color: var(--color-texte-rubrique); }
.row { display: flex; gap: 10px; }
.row > * { flex: 1; }
.row > .field-compact { flex: 0 0 auto; width: 90px; }
/* .row > * { flex:1 } est pensé pour des boutons CÔTE À CÔTE (flex-direction:row, répartit la
   largeur) — appliqué tel quel à une .row en colonne (#modal-abonnement, "RESTER ACCÈS
   LIBRE"/"CHANGER DE FORMULE"), il étirait les boutons pour remplir toute la hauteur restante du
   panneau (flex-grow sans base définie). Corrigé le 2026-08-21 : hauteur des boutons ramenée à
   celle de leur contenu (padding de .btn), comme les cartes .type-acces-carte (#modal-type-acces,
   demande de Christophe "utiliser la hauteur identique aux 3 boutons"). */
/* Hauteur fixée à 70px (demande de Christophe, 2026-08-21, "ajuste la hauteur de chaque bouton à
   70 px") : flex:none seul laissait la hauteur basée sur le padding de .btn (~48px), encore jugé
   trop haut. Sans effet une 1ère fois ("la hauteur des boutons n'a pas changé") : la règle
   ".row:has(> button.btn) > button.btn { flex: 1 1 140px; ... }" (empilement boutons écran
   étroit, plus bas dans ce fichier) est PLUS SPÉCIFIQUE (:has() compte comme une classe) et
   l'emportait malgré height:70px — en flexbox colonne, flex-basis/flex-grow prime sur height
   pour l'axe principal (vertical ici), donc height était calculé mais jamais appliqué en
   pratique. Corrigé en préfixant #modal-abonnement (spécificité ID, imbat quel que soit l'ordre
   des règles dans le fichier). */
#modal-abonnement .row-hauteur-boutons-fixe > .btn { flex: none; height: 70px; }
/* Rangée de boutons de popup (Annuler/Valider, Oui/Non, etc.) : passe en 2 lignes empilées quand
   l'écran n'est pas assez large pour les afficher côte à côte, plutôt que de les écraser/déborder.
   Scopé via :has(> button.btn) pour ne toucher QUE les rangées de boutons (jamais les rangées de
   champs de formulaire, qui gardent leur comportement .row > * existant) — règle à appliquer sur
   TOUTES les applications ICILAGO existantes et à venir, demande de Christophe, 2026-08-20. */
.row:has(> button.btn) { flex-wrap: wrap; }
.row:has(> button.btn) > button.btn { flex: 1 1 140px; min-width: 140px; }
.empty-state { text-align: center; padding: 40px 12px; color: var(--color-text-muted); }
.empty-state .emoji { font-size: 34px; display: block; margin-bottom: 10px; }
.divider { height: 1px; background: var(--color-border); margin: 16px 0; }
.stat-row { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--color-border); }
.stat-row:last-child { border-bottom: none; }
.stat-value { font-weight: 700; font-size: 16px; }
/* Fiche évènement (events) : valeur de chaque item en gras (titre à gauche inchangé) */
#modal-event-content .stat-row > span:last-child { font-weight: 700; }

/* Fiche évènement (events) : nouvelle présentation des items (icône 50x50 à gauche, nom de
   l'item en haut à droite aligné avec le haut de l'icône, valeur en dessous) — en cours de
   déploiement item par item à partir de "Début" (demande de Christophe, 2026-08-05). */
/* Onglets PRÉSENTATION / DÉTAILS de la fiche flyer (guide.icilago.eu, openEventDetail) — demande
   de Christophe, 2026-08-15. Onglet actif : fond bleu ICILAGO / texte blanc, comme l'exemple
   fourni ; onglet inactif : fond blanc / texte bleu ICILAGO, même trait de soulignement continu
   (border-bottom) que l'onglet actif pour un alignement propre entre les deux. */
/* Cadre 2px complet autour des 2 onglets (au lieu d'un simple border-bottom) — demande de
   Christophe, 2026-08-21 (fait suite à un 1er essai border-bottom posé sur l'onglet blanc,
   retiré le même jour). Invisible sur les 3 côtés qui touchent l'onglet actif (même couleur que
   son fond bleu déjà plein), bien visible sur les 3 côtés de l'onglet blanc. */
.onglets-fiche-flyer { display: flex; margin: 14px 0 0; border: 2px solid var(--color-primary); }
.onglet-fiche-flyer {
  flex: 1; padding: 12px 8px; border: none; background: #fff; color: var(--color-primary);
  font-weight: 700; font-size: 14px; letter-spacing: 0.5px; cursor: pointer;
}
.onglet-fiche-flyer.actif { background: var(--color-primary); color: #fff; }
/* Onglet DÉTAILS : icônes de repère (Site Web/Téléphone/Messagerie de l'évènement, puis
   Facebook/Instagram/TikTok du professionnel) — demande de Christophe, 2026-08-17. 2 blocs de 3
   icônes (voir eventVignetteHtml/openEventDetail dans main.js : .detail-icones-groupe). Sur 1
   seule ligne quand la largeur le permet (flex-wrap:wrap sur le conteneur ne bascule que si les 2
   BLOCS entiers ne tiennent pas côte à côte — jamais de coupure au milieu d'un bloc), sinon
   passage automatique sur 2 lignes (Web/Tél/Mail puis Facebook/Instagram/TikTok) SANS toucher à la
   taille des icônes — demande de Christophe, 2026-08-17, en remplacement du gap minimal (2px)
   utilisé juste avant, qui ne suffisait pas à garantir l'absence de débordement sur les téléphones
   les plus étroits (6×50px = 300px, largeur disponible ~295px sur un écran de 375px). */
.onglet-details-icones-attente { display: flex; flex-wrap: wrap; justify-content: center; row-gap: 20px; column-gap: 12px; padding: 30px 0; }
/* Espacement de 50px entre les 4 icônes (Web/Téléphone/E-mail/Réseaux sociaux) — demande de
   Christophe, 2026-08-19 (remplace le 12px précédent). */
.detail-icones-groupe { display: flex; gap: 50px; flex: 0 0 auto; }
.detail-icone-legende { display: flex; flex-direction: column; align-items: center; gap: 6px; position: relative; }
/* Tooltips des icônes Web/Téléphone/E-mail/Réseaux sociaux/Facebook/Instagram/TikTok, centrés
   au-dessus de chaque icône — demande de Christophe, 2026-08-19. Affichage INSTANTANÉ au survol
   (pas de transition, contrairement à .icone-sous-photo[data-tooltip] qui a un fondu 150ms). */
.detail-icone-legende[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 8px;
  background: var(--color-primary);
  color: #fff;
  font-size: 12px;
  line-height: 1.2;
  padding: 4px 8px;
  border-radius: 6px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  z-index: 20;
}
.detail-icone-legende[data-tooltip]:hover::after {
  animation: icilago-tooltip-auto-hide-instant 5s ease forwards;
}

/* Facebook/Instagram/TikTok regroupés sous un seul bouton (296.png "Réseaux sociaux"), ajouté en
   4e icône de la ligne Web/Téléphone/E-mail (.detail-icones-groupe). Au clic, une 2e ligne
   (.reseaux-sociaux-deploye) apparaît juste en dessous avec les 3 icônes réseaux — colonne
   partagée par les 2 lignes (.groupe-icones-contact-reseaux) pour que la 2e ligne hérite la
   largeur de la 1re (plus large, 4 icônes) et que les 3 icônes s'y répartissent uniformément
   (justify-content:space-between) sur toute cette largeur — demande de Christophe, 2026-08-19. */
.groupe-icones-contact-reseaux { display: flex; flex-direction: column; gap: 12px; }
.icone-item-v2-btn { background: none; border: none; padding: 0; cursor: pointer; display: flex; }
/* Cadre autour des 3 icônes déployées : fond bleu ICILAGO très clair, sans contour — demande de
   Christophe, 2026-08-19. */
.reseaux-sociaux-deploye { display: flex; justify-content: space-between; align-items: center; width: 100%; background: rgba(25, 21, 87, 0.06); border: none; border-radius: 12px; padding: 12px 16px; box-sizing: border-box; }
.reseaux-sociaux-deploye.hidden { display: none; }
/* Avec 1 ou 2 icônes réseaux seulement (classe posée en JS selon evt.lienFacebook/Instagram/Tiktok
   renseignés) : le cadre pleine largeur + space-between écartait trop les icônes (ex. Facebook à
   gauche, Instagram tout à droite). Cadre resserré autour de son contenu (au lieu de width:100%
   hérité de la ligne des 4 icônes du dessus) et centré dans cette ligne, icônes rapprochées avec
   40px d'espace entre elles — demande de Christophe, 2026-08-30. Cas à 3 icônes (le plus courant)
   INCHANGÉ : reste sur .reseaux-sociaux-deploye seul (pleine largeur, space-between). */
.reseaux-sociaux-deploye.reseaux-compact { width: auto; align-self: center; justify-content: center; gap: 40px; }

/* Onglet DÉTAILS : 12 cadres annonceurs en lecture seule, boutons cliquables en accordéon
   exclusif (un seul cadre ouvert à la fois — demande de Christophe, 2026-08-16). Voir
   detailsCadresHtml()/formaterXxxGuide() dans main.js. */
/* 50px sous les onglets PRÉSENTATION/DÉTAILS, pour que le premier bouton ("Public") ne colle pas
   à la barre d'onglets (demande de Christophe, 2026-08-16). */
.details-cadres-liste { margin-top: 50px; }
.details-cadre-btn {
  width: 100%; display: flex; justify-content: space-between; align-items: center;
  padding: 11px 14px; margin-bottom: 6px; border: 1px solid var(--color-primary);
  border-radius: 8px; background: #fff; color: var(--color-primary);
  font-weight: 700; font-size: 13px; letter-spacing: 0.3px; cursor: pointer;
}
/* Flèche Icone_0214.png (20x20px, demande de Christophe 2026-08-16 — remplace l'ancien chevron
   texte "▾") : pivote à l'ouverture, comme .icone-accordeon-acces-consultation côté
   annonceurs.icilago.eu (même icône, même comportement visuel entre les 2 apps). */
.details-cadre-chevron { width: 20px; height: 20px; flex-shrink: 0; transition: transform .2s; }
.details-cadre-btn.actif { background: var(--color-primary); color: #fff; margin-bottom: 0; border-radius: 8px 8px 0 0; }
.details-cadre-btn.actif .details-cadre-chevron { transform: rotate(180deg); }
/* Nombre d'items du cadre (demande de Christophe 2026-08-16, ex. "ACCÈS 3") : regroupé avec la
   flèche à droite du bouton — voir compterVrais()/CADRES_DETAILS_GUIDE dans main.js. */
.details-cadre-droite { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.details-cadre-nombre {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: 9px; background: rgba(255, 255, 255, 0.25);
  font-size: 11px; font-weight: 700;
}
.details-cadre-btn:not(.actif) .details-cadre-nombre { background: var(--color-primary); color: #fff; }
/* Icône à gauche du libellé "PUBLIC" (Icone_0087.png, voir CADRES_DETAILS_GUIDE dans main.js) :
   couleur d'origine sur fond blanc (bouton fermé), passe en blanc sur fond bleu ICILAGO
   (.details-cadre-btn.actif, rubrique dépliée) pour rester lisible — demande de Christophe,
   2026-08-20. filter:brightness(0) invert(1) ne touche jamais le canal alpha (contrairement à
   opacity(0) drop-shadow(...), essai invalidé sur le logo splash) : silhouette blanche exacte,
   sans mask-image ni CORS à gérer. 30x30px (demande de Christophe, 2026-08-20). */
.details-cadre-icone { width: 30px; height: 30px; object-fit: contain; vertical-align: -8px; margin-right: 6px; }
.details-cadre-btn.actif .details-cadre-icone { filter: brightness(0) invert(1); }
.details-cadre-panneau { display: none; padding: 10px 14px 14px; margin: 0 0 6px; border: 1px solid var(--color-border); border-top: none; border-radius: 0 0 8px 8px; background: var(--color-bg); }
.details-cadre-panneau.ouvert { display: block; }
.detail-guide-ligne { font-size: 13px; color: var(--color-text); padding: 5px 0; border-bottom: 1px solid var(--color-border); }
.detail-guide-ligne:last-child { border-bottom: none; }
.detail-guide-ligne strong { color: var(--color-text-muted); font-weight: 600; }
.detail-guide-ligne a { color: var(--color-primary); font-weight: 600; }
.detail-guide-vide { font-size: 12.5px; color: var(--color-text-muted); font-style: italic; padding: 6px 0; }

.stat-row-v2 { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--color-border); }
.stat-row-v2:last-child { border-bottom: none; }
.icone-item-v2 { width: 50px; height: 50px; object-fit: contain; flex-shrink: 0; }
span.icone-item-v2 { display: flex; align-items: center; justify-content: center; font-size: 30px; }
.item-contenu-v2 { display: flex; flex-direction: column; min-width: 0; }
.item-nom-v2 { font-size: 14px; color: var(--color-text); line-height: 1.2; }
.item-valeur-v2 { font-size: 14px; font-weight: 700; margin-top: 4px; }
/* 2 items sur la même ligne de .stat-row-v2 (Du/Au, puis Heures d'ouvertures/Durée visite moy —
   rubrique "Période d'ouverture" de la fiche visiteur GUIDE) — demande de Christophe, 2026-08-15.
   .stat-item-v2-demi reprend la mise en page icône+texte de .stat-row-v2 (même gap:10px, même
   icône 50px) pour que les icônes ET les textes de la 1ère colonne restent alignés verticalement
   avec ceux de "Adresse" juste en dessous (demande de Christophe, même jour : "aligne
   verticalemet les icones calendrier et horloge avec l'icone position [...] décale si tu veux les
   icones et texte de la seconde colonne") — un essai précédent réduisait l'icône à 36px pour
   gagner de la place, ce qui désalignait tout ; on préfère décaler la 2e colonne (Au/Durée visite
   moy) plutôt que rétrécir la 1ère. */
.stat-row-v2-double { gap: 16px; }
.stat-item-v2-demi { display: flex; align-items: center; gap: 10px; flex: 1 1 50%; min-width: 0; }
.bar-row { margin-bottom: 8px; }
.bar-label { display: flex; justify-content: space-between; font-size: 12.5px; margin-bottom: 3px; }
.bar-track { height: 8px; background: var(--color-border); border-radius: 6px; overflow: hidden; }
.bar-fill { height: 100%; background: var(--color-primary); }

/* ---------- Admin ---------- */
.admin-taxo-list { display: flex; flex-direction: column; }
.admin-taxo-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid var(--color-border);
  font-size: 13.5px;
  background: var(--color-surface);
}
.admin-taxo-row:last-child { border-bottom: none; }
.admin-taxo-row.dragging { opacity: 0.5; background: var(--color-bg); border-radius: 8px; }
.admin-taxo-text { flex: 1; min-width: 0; }
.admin-taxo-actions { display: flex; gap: 4px; align-items: center; flex-shrink: 0; }
.admin-taxo-actions button {
  background: none;
  border: none;
  font-size: 15px;
  padding: 4px 6px;
  cursor: pointer;
}
.admin-color-input { width: 30px; height: 26px; padding: 0; border: 1px solid var(--color-border); border-radius: 6px; cursor: pointer; }
.color-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 4px; vertical-align: middle; }
.drag-handle {
  flex-shrink: 0;
  cursor: grab;
  touch-action: none;
  padding: 4px 6px;
  font-size: 15px;
  color: var(--color-text-muted);
  user-select: none;
}
.drag-handle:active { cursor: grabbing; }

/* ---------- Adresse structurée / téléphone avec indicatif ---------- */
.phone-field { display: flex; gap: 8px; }
.phone-field select { flex: 0 0 118px; }
.phone-field input { flex: 1; }

/* ---------- Profil pro : icône + champ lien (Web et réseaux) ---------- */
.field-icone { display: flex; align-items: center; gap: 10px; }
.field-icone img { width: 32px; height: 32px; object-fit: contain; flex: none; }
.field-icone input { flex: 1; }
.toast {
  position: fixed;
  bottom: calc(var(--nav-height) + 10px);
  left: 50%;
  transform: translateX(-50%);
  max-width: 440px;
  width: calc(100% - 28px);
  background: var(--color-text);
  color: #fff;
  padding: 12px 16px;
  border-radius: 10px;
  font-size: 13.5px;
  /* Au-dessus de .modal-overlay (z-index:3000) — un toast déclenché depuis un bouton DANS une
     popup (ex. "Je suis PARRAIN" dans #modal-parrainage-scan) apparaissait auparavant SOUS le
     voile flouté de la popup, donc invisible/illisible. Signalé par Christophe, 2026-09-04. */
  z-index: 3100;
  box-shadow: var(--shadow);
}
.lock-banner {
  background: #fff8ea;
  border: 1px solid #f0d8a4;
  border-radius: 10px;
  padding: 12px 14px;
  font-size: 13px;
  margin-bottom: 14px;
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
/* Bandeau "navigateur obsolète" (#bandeau-navigateur-obsolete) — demande de Christophe,
   2026-08-13 : bandeau fermable, jamais bloquant, flottant au-dessus du contenu sans pousser la
   mise en page. */
.bandeau-navigateur-obsolete {
  position: fixed;
  top: max(8px, env(safe-area-inset-top));
  left: 8px;
  right: 8px;
  z-index: 9999;
  display: flex;
  align-items: center;
  gap: 8px;
  background: #fff3cd;
  color: #7a5b00;
  border: 1px solid #f5dd8d;
  border-radius: 10px;
  padding: 8px 10px;
  font-size: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
}
.bandeau-navigateur-obsolete.hidden { display: none; }
.bandeau-navigateur-obsolete span { flex: 1; }
.bandeau-navigateur-obsolete button {
  background: none;
  border: none;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  color: inherit;
  padding: 4px 6px;
  flex-shrink: 0;
}

/* ---------- Modal ---------- */
.modal-overlay {
  position: fixed;
  /* top/right/bottom/left/width/max-width AU LIEU DE inset:0 (2026-09-01) : position:fixed échappe
     à la contrainte max-width:480px de #app (position:relative ne confine pas les descendants
     position:fixed) — avec inset:0, le voile + flou couvraient TOUTE la largeur du navigateur sur
     desktop/tablette large au lieu de rester sur la colonne de l'application, contrairement à
     #modal-filtres qui avait déjà cette contrainte. Signalé par Christophe (captures guide "Ma
     position" pleine largeur vs events "Types évènements" bien cadré) : "corrige ton code pour que
     tout se trouve uniquement sur la fenetre de l'application pour toutes les applications sauf la
     console". Même technique que #modal-filtres/.menu-lateral-backdrop/#panel-raccourcis-interets
     (voir plus bas). */
  top: 0;
  right: auto;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  /* Voile sombre + flou rétablis sur TOUTES les popups (demande de Christophe, 2026-08-14) —
     revient sur la décision du 2026-07-27 (voile transparent). Mêmes valeurs que console.icilago.eu
     et annonceurs.icilago.eu pour rester cohérent entre applications.
     Valeurs pilotées par console > Réglages > Paramètres > "FLOUTAGE ET FOND PANNEAU" (variables
     --modal-blur-px/--modal-overlay-opacite, voir .menu-lateral-backdrop plus haut pour le détail
     du mécanisme) — demande de Christophe, 2026-09-01. Fallback = valeurs historiques (0.5/6px). */
  background: rgba(0, 0, 0, var(--modal-overlay-opacite, 0.5));
  backdrop-filter: blur(var(--modal-blur-px, 6px));
  -webkit-backdrop-filter: blur(var(--modal-blur-px, 6px));
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* Doit rester au-dessus des cartes Leaflet, dont les panneaux/contrôles internes
     utilisent des z-index allant jusqu'à ~1000 (sinon la carte s'affiche par-dessus
     la fiche, ex. dans Statistiques). */
  z-index: 3000;
  /* Empêche un geste tactile démarré sur le voile de faire défiler la page derrière — complète le
     verrou JS (actualiserVerrouillageDefilementModales). Le contenu de .modal-sheet retrouve son
     propre défilement normal via sa règle overflow-y:auto, non affectée par touch-action ici. */
  touch-action: none;
}
.modal-overlay.hidden { display: none; }
/* Angle du liseret défilant (voir .modal-sheet ci-dessous) — enregistré via @property pour que
   le navigateur puisse l'interpoler en douceur pendant l'animation (un simple custom property
   CSS non enregistré ne s'anime pas, il "saute" d'une valeur à l'autre). Support large
   (Chrome/Edge 85+, Safari 16.4+) ; sur les très anciens navigateurs sans @property, le contour
   reste simplement statique (dégradé figé, pas d'erreur ni de contour manquant). */
@property --liseret-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
.modal-sheet {
  width: 100%;
  max-width: 480px;
  border-radius: 16px 16px 0 0;
  padding: 20px;
  max-height: 88vh;
  max-height: 88dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  overflow-y: auto;
  /* Bug identifié le 2026-08-17 (captures à l'appui, reproductible aussi bien sur PC que mobile) :
     avec overflow-y:auto seul, overflow-x calcule implicitement à "auto" (règle CSS standard :
     dès qu'un axe n'est pas "visible", l'autre axe "visible" devient "auto") — le moindre
     débordement horizontal (arrondi de calcul, ombre, image) rendait donc la fiche défilable
     horizontalement, et la moindre composante horizontale d'un geste de scroll (molette, trackpad,
     doigt) la faisait glisser sur le côté, révélant du contenu tronqué à gauche avant qu'elle ne
     se recale. overflow-x:hidden explicite bloque ce débordement sans toucher au défilement
     vertical normal. */
  overflow-x: hidden;
  /* Rétablit le défilement tactile normal À L'INTÉRIEUR de la fiche — touch-action:none posé sur
     .modal-overlay (le voile parent) bloquerait sinon aussi le contenu scrollable de la pop-up
     elle-même, pas seulement l'arrière-plan derrière. */
  touch-action: auto;
  /* Ombre légère pour détacher visuellement la pop-up de l'arrière-plan (le voile sombre de
     .modal-overlay, rétabli le 2026-08-14, aide déjà à cette séparation, mais cette ombre reste
     utile pour le détachement fin du bord). Toujours appliquer cette règle à toute nouvelle pop-up
     de l'app. */
  /* Flou blanc de 10px du côté EXTÉRIEUR du liseret (demande de Christophe, 2026-08-13, portée à
     10px le même jour — était 5px) : box-shadow avec flou (10px) et SANS spread (0), posé AVANT
     l'ombre de profondeur dans la liste (les box-shadow listés en premier sont les plus proches de
     l'élément) — fait ressortir la popup du fond sans créer de bordure nette. */
  box-shadow: 0 0 10px #fff, 0 10px 40px rgba(0,0,0,0.2);
  /* Liseret défilant bleu ICILAGO + rouge (demande de Christophe, 2026-08-13, "comme un groupe de
     LED qui s'allument en défilant" — 2 "comètes" colorées qui tournent, pas de segment
     transparent) : bordure de 2px transparente qui réserve l'espace, remplie par un dégradé
     conique tournant en continu tant que la popup est ouverte — technique CSS "gradient border" à
     2 fonds superposés (le blanc habille l'intérieur en padding-box, le dégradé habille
     uniquement l'anneau de 2px en border-box), donc aucun élément enfant ajouté qui pourrait être
     rogné par overflow-y:auto ci-dessus. S'applique automatiquement à TOUTES les popups de l'app
     (base .modal-sheet), sans toucher au HTML de chacune. */
  border: 2px solid transparent;
  background:
    linear-gradient(#fff, #fff) padding-box,
    conic-gradient(from var(--liseret-angle), var(--color-bleu-icilago, #1e3a8a) 0deg, var(--color-bleu-icilago, #1e3a8a) 70deg, var(--color-danger, #c04848) 160deg, var(--color-danger, #c04848) 200deg, var(--color-bleu-icilago, #1e3a8a) 290deg, var(--color-bleu-icilago, #1e3a8a) 360deg) border-box;
  animation: liseret-popup-defilant 3s linear infinite;
}
@keyframes liseret-popup-defilant {
  to { --liseret-angle: 360deg; }
}
.modal-sheet h2 { margin-top: 0; font-size: 17px; }
/* Barre grisée de "poignée" en haut des popups — retirée sur toutes les popups de l'app (demande
   explicite du 2026-07-28, events ET annonceurs) : display:none plutôt que de supprimer chaque
   <div class="modal-handle"> du HTML, qui reste présent partout mais n'a plus d'effet visuel. */
.modal-handle { display: none; }
/* Bouton fermer (croix blanche sur rond semi-transparent) posé sur un flux caméra en direct —
   voir #modal-scan-qr-parrain, même style que .icone-fermer-camera côté annonceurs.icilago.eu. */
.icone-fermer-camera {
  position: absolute;
  top: 8px;
  right: 8px;
  padding: 4px;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.5);
  cursor: pointer;
  line-height: 0;
}
.icone-fermer-camera img { width: 22px; height: 22px; display: block; }
/* Bouton "scanner un QR code" à droite du champ "Code de parrainage" — icône seule, largeur
   réduite au strict nécessaire (pas un bouton texte comme .btn), demande de Christophe,
   2026-08-11 (remplace l'émoji 📷 par Icone_0171.png, 50×50px). */
.btn-icone-scan-qr-parrain {
  flex-shrink: 0;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  line-height: 0;
}
.btn-icone-scan-qr-parrain img { width: 50px; height: 50px; display: block; }
/* Popup "nouvelle version" : centrée (comme toutes les popups de l'app), taille fixe 400×300,
   contenu centré verticalement dedans, titre AVERTISSEMENT clignotant 1×/s. */
#modal-nouvelle-version .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 400px;
  height: 300px;
  max-width: calc(100% - 40px);
  max-height: calc(100% - 40px);
  border-radius: 16px;
  margin: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
#modal-nouvelle-version .titre-avertissement-version {
  font-weight: 700;
  font-size: 17px;
  letter-spacing: 1px;
  color: var(--color-danger);
  animation: clignoter-avertissement-version 1s steps(1) infinite;
}
@keyframes clignoter-avertissement-version { 0%, 49% { opacity: 1; } 50%, 100% { opacity: .2; } }

/* La fiche événement (ouverte depuis "Près de vous") s'affiche centrée horizontalement, ancrée à
   une distance fixe SOUS la barre bleue d'en-tête (plutôt que centrée verticalement à l'écran,
   qui la faisait chevaucher l'en-tête sur les écrans pas assez hauts pour ses 85vh max — demande
   explicite du 2026-07-27, deux itérations : d'abord décalage de 50px depuis le centre, puis
   ancrage direct à 25px sous l'en-tête).
   + env(safe-area-inset-top) (2026-07-31) : sur mobile/tablette, la barre d'en-tête réelle
   commence à safe-area-inset-top + descend sur var(--header-height) (voir .app-header /
   #app-contenu ci-dessus, media query max-width:1024px). L'ancrage top ici ne comptait QUE
   var(--header-height), donc sur les écrans avec encoche/Dynamic Island la fiche démarrait trop
   haut et mordait sur la barre de statut + le bas de l'en-tête bleu — corrigé en ajoutant le même
   env(safe-area-inset-top, 0px) que l'en-tête (0 sans encoche : aucun effet sur les autres
   écrans). */
/* Plus de voile sombre/flouté derrière la fiche (demande de Christophe, 2026-08-21) : devenue une
   vraie page pleine largeur (voir .modal-sheet ci-dessous) et non plus un panneau flottant, le
   voile ne sert plus à rien ("ça ne sert plus à rien vu que c'est devenu une vraie page"). Scopé à
   #modal-event-detail uniquement : la règle de base .modal-overlay (voile+flou) reste inchangée
   pour toutes les autres pop-ups de l'app. */
#modal-event-detail {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
#modal-event-detail .modal-sheet {
  position: fixed;
  /* Plus de marge de 25px sous l'en-tête (demande de Christophe, 2026-08-21, "supprime la marge
     au dessus de l'image, marge 0") : ancrage directement sous la barre bleue, cohérent avec le
     retrait du cadre/voile — voir aussi margin-top:-20px sur .fiche-flyer-image-pleine-largeur
     ci-dessous (annule le padding-top:20px hérité de .modal-sheet, sinon l'image resterait quand
     même décalée vers le bas malgré cet ancrage à 0). */
  top: calc(env(safe-area-inset-top, 0px) + var(--header-height));
  /* Centrage horizontal SANS transform (2026-08-17, 2e correctif) : le 1er essai
     (left:50%+transform:translateX(-50%)+translateZ(0)) n'a pas résolu le bug signalé par
     Christophe captures à l'appui — pendant le défilement tactile à l'intérieur de la fiche, tout
     le contenu se décalait horizontalement vers la gauche (texte tronqué, icône Retour disparue),
     avant de se recadrer correctement une fois le scroll stabilisé. Cause identifiée : sur iOS
     Safari, un élément position:fixed centré par transform ET contenant lui-même une zone
     scrollable peut se désynchroniser de la position "fixed" pendant le scroll momentum interne
     (bug connu de iOS). left:0/right:0/margin:0 auto centre SANS transform : plus fiable dans ce
     cas précis, la fiche reste ancrée pendant le scroll interne. */
  left: 0;
  right: 0;
  margin: 0 auto;
  /* Plus de largeur "carte" avec marges de chaque côté (calc(100% - 40px)) : demande de
     Christophe, 2026-08-21 — "le contenu doit être sur la page et non dans un cadre". Largeur
     pleine SOUS l'en-tête, mais toujours plafonnée à 480px comme .app-header/#app (même demande,
     complétée le même jour : "identique à la largeur de la bande bleue") — .modal-overlay,
     position:fixed, échappe sinon à la contrainte max-width:480px de #app (position:relative ne
     confine PAS les descendants position:fixed, contrairement à transform/filter), d'où le
     débordement plein viewport constaté sur desktop/tablette large. */
  width: 100%;
  max-width: 480px;
  /* Recalculée à partir du nouvel ancrage top (au lieu du centrage vertical d'avant) : sans ça,
     la fiche pourrait dépasser le bas de l'écran et rendre son bouton "Fermer" inatteignable
     (le défilement de la page est verrouillé pendant qu'une pop-up est ouverte). */
  max-height: calc(100vh - env(safe-area-inset-top, 0px) - var(--header-height));
  /* 100dvh en repli (voir #app plus haut pour l'explication complète) : sur Safari
     mobile/tablette, 100vh peut dépasser la hauteur réellement visible (barre d'outils du
     navigateur affichée), poussant le bouton "Fermer" hors champ malgré le commentaire
     ci-dessus — signalé sur tablette par Christophe, capture iPad, 2026-08-19. */
  max-height: calc(100dvh - env(safe-area-inset-top, 0px) - var(--header-height));
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* Barre de défilement verticale masquée (demande de Christophe, 2026-08-21, visible sur
     desktop/tablette large où overflow-y:auto affiche une vraie scrollbar) : le défilement
     lui-même reste actif, seul le rendu visuel de la scrollbar disparaît. Même principe que
     .filter-bar::-webkit-scrollbar plus bas dans ce fichier, complété ici de scrollbar-width
     (Firefox) et -ms-overflow-style (Edge/IE legacy) pour une couverture multi-navigateurs
     complète. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* "Retire le cadre blanc et le liseret bleu" (Christophe, 2026-08-21) : cette fiche n'est plus
     une pop-up flottante mais une vraie page pleine largeur — neutralise ici le "cadre" hérité de
     la base .modal-sheet (liseret conique animé, ombre/glow, coins arrondis), sans toucher à
     .modal-sheet elle-même (toujours utilisée telle quelle par toutes les autres pop-ups). */
  border-radius: 0;
  border: none;
  background: #fff;
  box-shadow: none;
  animation: none;
  /* Alignement avec le bandeau bleu (demande de Christophe, 2026-08-21, capture à l'appui) :
     .modal-sheet hérite padding:20px de tous les côtés (règle de base, pensée pour un panneau
     flottant) tandis que .app-header n'a que 14px (padding: 0 14px) — l'image/le titre/les
     onglets se retrouvaient donc décalés de 6px vers l'intérieur par rapport au bord du bandeau.
     Repris ici à 14px, seulement en horizontal (le padding vertical 20px de la base reste utile
     pour l'espacement haut/bas du contenu). */
  padding-left: 14px;
  padding-right: 14px;
}
#modal-event-detail .modal-sheet::-webkit-scrollbar { display: none; }
/* Photo + onglets PRÉSENTATION/DÉTAILS touchent les bords gauche/droit de l'écran, marge 0,
   alignés au bandeau bleu (demande de Christophe, 2026-08-21, capture de référence "Pont Julien")
   — seuls ces 2 blocs "débordent" du padding de 14px de .modal-sheet (titre/texte/icônes restent
   en retrait, lisibles). Marge négative = largeur du padding parent, technique de bleed standard
   (pas besoin de recalcul de width : un div en display:block s'étend automatiquement dans l'espace
   libéré par des marges négatives). Coins de la photo carrés (plus de border-radius, incohérent en
   plein bord) — voir aussi js/main.js (inline style de l'<img>, border-radius retiré). */
#modal-event-detail .fiche-flyer-image-pleine-largeur {
  margin-left: -14px;
  margin-right: -14px;
  /* Annule le padding-top:20px hérité de .modal-sheet (demande de Christophe, 2026-08-21,
     "marge 0" au dessus de l'image) — sans ça l'image resterait décalée vers le bas malgré la
     suppression du gap externe de 25px ci-dessus (top: sur .modal-sheet). */
  margin-top: -20px;
}
#modal-event-detail .onglets-fiche-flyer {
  margin-left: -14px;
  margin-right: -14px;
}

/* "Voir l'affiche" (fiche évènement, demande de Christophe 2026-08-06) : centré à l'écran plutôt
   qu'en feuille du bas, comme toute pop-up de l'app (voir #modal-qrcode ci-dessous) — l'image elle-
   même (#voir-affiche-image, voir HTML) est contrainte en max-width/max-height plutôt qu'en
   width:100% seul, pour rester entièrement visible (aucun recadrage) quel que soit son format.
   S'ouvre PAR-DESSUS #modal-event-detail sans le refermer : 10px de moins de large de chaque côté
   que ce parent (demande de Christophe, 2026-08-13, voir #modal-detail-palier plus haut pour le
   même principe). */
#modal-voir-affiche .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 60px);
  max-width: 460px;
  max-height: 85vh;
  max-height: 85dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}

/* "Je revendique cet établissement" (lien en bas de fiche, demande de Christophe, 2026-08-20) :
   s'ouvre PAR-DESSUS #modal-event-detail sans le refermer — même principe de sous-popup que
   #modal-voir-affiche ci-dessus, 10px de moins de large de chaque côté que ce parent (480px). */
#modal-revendiquer-etablissement .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* Réduit de 50px de chaque côté par rapport au 1er jet (460px) — demande de Christophe,
     2026-08-20. */
  width: calc(100% - 160px);
  max-width: 360px;
  max-height: 85vh;
  max-height: 85dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}

/* Boutons "Annuler" / "C'est mon établissement" : le passage en 2 lignes sur écran étroit est
   désormais géré par la règle générale .row:has(> button.btn) plus haut dans ce fichier (voir
   la base .row) — règle appliquée à toute rangée de boutons de popup, pas seulement celle-ci. */

.ligne-revendiquer-etablissement {
  text-align: center;
  margin-top: 20px;
}

.lien-revendiquer-etablissement {
  background: none;
  border: none;
  padding: 8px;
  font-size: 13px;
  color: var(--color-primary);
  text-decoration: underline;
  cursor: pointer;
}

/* Le QR code (ex: "Mes réservations" dans le compte visiteur, partage d'événement) s'affiche
   centré à l'écran plutôt qu'en feuille du bas — même technique de centrage indépendant du flex. */
#modal-qrcode .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 360px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}

/* Popup "PARTAGER ICILAGO" (#modal-partage-visiteur, 2026-09-04) : mêmes dimensions/centrage que
   #modal-partage-pro côté annonceurs.icilago.eu (copie demandée par Christophe). */
#modal-partage-visiteur .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 360px;
  max-height: 80vh;
  max-height: 80dvh;
  border-radius: 16px;
  margin: 0;
}

/* Popup "PARRAINAGE" (#modal-parrainage-scan, 2026-09-04) : même centrage que les autres popups. */
#modal-parrainage-scan .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 360px;
  max-height: 80vh;
  max-height: 80dvh;
  border-radius: 16px;
  margin: 0;
}

/* Popup caméra "Scanner un QR code" (#modal-scan-qr-parrain, inscription ET popup PARRAINAGE) :
   tombait par défaut en feuille du bas (pas de règle dédiée jusqu'ici) au lieu d'être centrée à
   l'écran comme les autres popups — signalé par Christophe, 2026-09-04. Même centrage, un peu plus
   large (420px) pour laisser de la place au cadrage du QR code dans le flux vidéo. */
#modal-scan-qr-parrain .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 80vh;
  max-height: 80dvh;
  border-radius: 16px;
  margin: 0;
}

/* Historique des modifications d'un événement, centré à l'écran, même technique. */
#modal-historique-modifs .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
.historique-modif-ligne {
  padding: 8px 0;
  border-bottom: 1px solid var(--color-border);
  font-size: 13px;
}
.historique-modif-ligne:last-child { border-bottom: none; }
.historique-modif-date {
  font-weight: 700;
  font-size: 12px;
  color: var(--color-text-muted);
  margin-bottom: 2px;
}
.historique-modif-champs { font-size: 13px; }
#modal-historique-modifs h2 { font-size: 15px; }
#modal-historique-modifs #historique-modifs-subtitle { font-size: 13px; font-weight: 600; margin-top: -8px; }

/* "Signalement" (icône Icone_0145.png, fiche évènement) : 2 popups centrées à l'écran, même
   technique que toutes les popups de l'app — voir #btn-icone-145-event (main.js). */
#modal-signalement-motif .modal-sheet,
#modal-signalement-confirmation .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
.signalement-motifs-liste {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 8px;
}
.signalement-motif-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  font-size: 14px;
  cursor: pointer;
}
.signalement-motif-item input[type="radio"] { flex-shrink: 0; width: 18px; height: 18px; }
/* Titre "ATTENTION" : même poids visuel que #modal-nouvelle-version, sans clignotement (non
   demandé pour ce popup, texte déjà assez alarmant). */
#modal-signalement-confirmation .titre-attention-signalement {
  font-weight: 700;
  font-size: 17px;
  letter-spacing: 1px;
  color: var(--color-danger);
}

/* Consentement de position (à l'entrée de l'accès visiteur) centré à l'écran, même technique. */
#modal-consentement-position .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 360px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
#modal-consentement-position h2 { letter-spacing: 1px; font-size: 17px; }

/* "Dommage" (refus de la position) centré à l'écran, même technique. */
#modal-regret-position .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 360px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
#modal-regret-position h2 { letter-spacing: 1px; font-size: 17px; }

/* Relance de géolocalisation (toutes les 30 min si la session reste ouverte) centrée à l'écran,
   même technique. */
#modal-relance-geoloc .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 360px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
#modal-relance-geoloc h2 { letter-spacing: 1px; font-size: 17px; }

/* "FLYER(S)" (flyers ajoutés/modifiés dans le rayon du compte) centré à l'écran, même technique —
   max-height + overflow-y auto car la liste des flyers concernés (bouton "Voir les flyers") peut
   être longue, contrairement aux popups de confirmation ci-dessus à hauteur fixe. */
#modal-flyers-nouveautes .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 85vh;
  max-height: 85dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  overflow-y: auto;
  border-radius: 16px;
  margin: 0;
}
#modal-flyers-nouveautes h2 { letter-spacing: 1px; font-size: 17px; }

/* "Modifications non enregistrées" (Mon compte, avant de quitter via le logo du menu latéral)
   centré à l'écran, même technique. */
#modal-confirmer-quitter-compte .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 360px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
#modal-confirmer-quitter-compte h2 { letter-spacing: 1px; font-size: 17px; }

/* "Déconnexion" centrée à l'écran, même technique. */
#modal-confirmer-deconnexion .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 360px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
#modal-confirmer-deconnexion h2 { letter-spacing: 1px; font-size: 17px; }

/* "Mot de passe oublié" centré à l'écran, même technique. */
#modal-mdp-oublie .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 400px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}

/* "Vérifiez votre e-mail" centrée à l'écran, même technique — largeur 90% de la fenêtre
   (demande explicite du 2026-07-28, au lieu du calc(100% - 40px) habituel). */
#modal-verification-email .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 90%;
  max-width: 400px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}

/* "Bienvenue" à la connexion visiteur, centrée, sans bouton (se referme seule après 2s). */
#modal-bienvenue-visiteur .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 60px);
  max-width: 320px;
  border-radius: 16px;
  margin: 0;
  padding: 22px 20px;
}

/* Statut de localisation GPS ("Localisation en cours…", "Position mise à jour."...), même
   principe que "Bienvenue" ci-dessus : centrée, sans bouton, se referme seule. */
#modal-statut-position .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 60px);
  max-width: 320px;
  border-radius: 16px;
  margin: 0;
  padding: 22px 20px;
}

/* Popups de suppression de compte (visiteur + pro) : centrés, fond rouge,
   texte blanc en gras — cohérent avec la gravité de l'action. */
#modal-suppr-etape1 .modal-sheet,
#modal-suppr-etape2 .modal-sheet,
#modal-suppr-annulee .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 380px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
.suppr-danger {
  background: #c0392b !important;
}
.suppr-danger .suppr-titre {
  color: #fff;
  font-weight: 700;
  font-size: 16px;
  margin: 0 0 16px;
  text-align: center;
}
.suppr-danger .row { gap: 10px; }
.suppr-btn-oui, .suppr-btn-non {
  flex: 1;
  font-weight: 700;
  border-radius: 10px;
  padding: 12px;
}
.suppr-btn-oui {
  background: #fff;
  color: #c0392b;
  border: none;
}
.suppr-btn-non {
  background: transparent;
  color: #fff;
  border: 2px solid #fff;
}
#suppr-etape2-password-field input {
  border-radius: 8px;
  border: none;
  padding: 10px 12px;
}

/* ---------- Map ---------- */
/* Leaflet fixe déjà position:relative sur .leaflet-container (dans leaflet.css) mais sans
   z-index — donc sans nouveau contexte d'empilement. Ses contrôles internes (zoom, etc., avec
   z-index jusqu'à 1000 dans les styles de Leaflet) se comparent alors directement aux autres
   éléments de la page dans le MÊME contexte d'empilement, y compris l'en-tête (z-index:20) :
   1000 > 20, donc la carte pouvait apparaître par-dessus l'en-tête en défilant (ex. "Ma
   position" dans Mon compte). En donnant un z-index explicite au conteneur, on crée un
   contexte d'empilement local qui confine tous ses contrôles internes à l'intérieur de sa
   propre boîte — la carte, dans son ensemble, ne compte plus que pour ce seul z-index (0) vis
   à vis du reste de la page, donc elle reste sous l'en-tête comme n'importe quel autre bloc. */
.leaflet-container { z-index: 0; }
/* position:relative + z-index:0 (2026-09-01) : sandbox les panes internes de Leaflet
   (overlayPane/popupPane/connecteurPopupPane, z-index jusqu'à ~700-1000, voir renderVisitorMap())
   dans leur PROPRE contexte d'empilement, pour qu'ils ne rivalisent plus avec le z-index global de
   la page (header:20, #modal-filtres-carte:18 depuis ce même jour). Sans ça, aucun z-index de
   #modal-filtres-carte inférieur à ~1000 ne peut suffire : les contrôles/repères de la carte
   passeraient par-dessus le panneau de filtres ouvert. #map-container lui-même (z-index:0, très
   bas) continue de se comparer normalement aux autres éléments de la page. */
#map-container { height: 65vh; height: 65dvh; border-radius: var(--radius); overflow: hidden; margin-bottom: 14px; box-shadow: var(--shadow); position: relative; z-index: 0; }
#detail-map { height: 200px; border-radius: var(--radius); overflow: hidden; margin: 14px 0; }
#stats-map { height: 240px; border-radius: var(--radius); overflow: hidden; }
#evt-position-map { height: 320px; border-radius: var(--radius); overflow: hidden; margin-top: 10px; box-shadow: var(--shadow); }
.map-legend-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 4px; vertical-align: middle; }

/* ---------- Filters bar ---------- */
.filter-bar { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 6px; margin-bottom: 8px; }
.filter-bar::-webkit-scrollbar { display: none; }
.filter-pill {
  flex-shrink: 0;
  padding: 7px 13px;
  border-radius: 20px;
  background: #fff;
  border: 1px solid var(--color-border);
  font-size: 12.5px;
  cursor: pointer;
  white-space: nowrap;
}
.filter-pill.active { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }

/* ---------- Loading ---------- */
.spinner {
  width: 22px; height: 22px;
  border: 3px solid var(--color-border);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  margin: 0 auto;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Panneau raccourcis "Centres d'intérêt" (clic sur le titre/sous-titre du header) — demande de
   Christophe, 2026-08-30, ALIGNÉ le 2026-08-31 sur les mêmes paramètres d'ouverture/fermeture que
   #modal-filtres (demande explicite : "La cadre blanc avoir les memes parametres d'ouverture et de
   fermeture du cadre blanc des 5 icones" — confirmé par Christophe = le panneau Filtres) :
   #panel-raccourcis-interets devient le voile de fond (fondu enchaîné transparent → rgba(0,0,0,0.45)
   sur `background`, classe .ouvert posée/retirée en JS avec double requestAnimationFrame à
   l'ouverture / délai avant .hidden à la fermeture — voir ouvrirPanelRaccourcisInterets()/
   fermerPanelRaccourcisInterets() dans main.js, copie exacte de ouvrirPanneauFiltres()/
   fermerPanneauFiltres()), .panel-raccourcis-interets-inner (le contenu) glisse en `transform:
   translateY(-100%) → translateY(0)` depuis sous le header, PAS `height` (ancien mécanisme,
   retiré) — même technique que .modal-sheet de #modal-filtres et que #pull-refresh-indicator.
   position:fixed sur .inner (pas flex du parent) : évite la contrainte max-width:480px de #app
   sur un position:fixed non confiné, même piège que documenté ci-dessous à l'origine. z-index
   3000 (comme .modal-overlay) : recouvre désormais aussi le header (assombri comme le reste),
   contrairement à l'ancien z-index:18 volontairement sous le header. */
#panel-raccourcis-interets {
  position: fixed;
  /* top: sous la barre bleue (PAS inset:0/top:0) — le voile de fond ne doit assombrir/recouvrir
     que ce qui est EN DESSOUS du header, pas le header lui-même (demande de Christophe,
     2026-08-31 : "le menu filtre doit se déployer depuis le dessous de la barre bleue", suite au
     passage à z-index:3000 qui recouvrait aussi le header). */
  top: calc(env(safe-area-inset-top, 0px) + var(--header-height));
  /* left:50%+transform+max-width (PAS left:0/right:0) : même correctif que .modal-overlay
     (2026-09-01, voir son commentaire plus bas) — le voile ne doit couvrir que la colonne de
     l'application (480px), pas toute la largeur du navigateur. */
  left: 50%;
  right: auto;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  bottom: 0;
  background: rgba(0, 0, 0, 0);
  transition: background 0.25s ease;
  /* backdrop-filter posé ici sans transition (comme #modal-filtres) : sans effet tant que
     #panel-raccourcis-interets.hidden (display:none) masque tout l'élément — seule la classe
     .ouvert (background) est réellement animée en fondu. Bug corrigé le 2026-09-01 : ce panneau
     n'avait NI flou NI valeur console (rgba(0,0,0,0.45) codé en dur), contrairement aux autres
     popups — Christophe : "j'ai 3 rendus différents au lieu d'un seul" sur les captures
     events/guide/annonceurs, corrigé une 1ère fois pour #modal-filtres puis repéré ici en 2e
     passe sur CE panneau, différent de #modal-filtres. Voir console > Réglages > Paramètres >
     "FLOUTAGE ET FOND PANNEAU" et [[icilago-console-floutage-fond-panneau-global]]. */
  backdrop-filter: blur(var(--modal-blur-px, 6px));
  -webkit-backdrop-filter: blur(var(--modal-blur-px, 6px));
  /* z-index:18 (PAS 3000/.modal-overlay) — SOUS le header (z-index:20, voir .app-header) :
     demande de Christophe, 2026-08-31, "le panneau arrive toujours depuis le haut et par devant,
     fait le passer par derriere le header". Avec z-index:3000, le panneau glissait EN AVANT du
     header pendant l'animation (visible par-dessus la bande bleue) au lieu de sembler émerger de
     DESSOUS elle. 18 reste largement au-dessus du contenu normal de la page (cartes, boutons —
     z-index:auto/bas), donc le voile assombrit toujours tout ce qui est en dessous du header. */
  z-index: 18;
  touch-action: none;
}
#panel-raccourcis-interets.hidden { display: none; }
#panel-raccourcis-interets.ouvert { background: rgba(0, 0, 0, var(--modal-overlay-opacite, 0.5)); }
.panel-raccourcis-interets-inner {
  position: fixed;
  /* top:0 (2026-09-02), PAS calc(safe-area+header-height) comme avant : depuis le correctif du
     2026-09-01 sur #panel-raccourcis-interets (transform:translateX(-50%) ajouté pour le centrer
     dans la colonne de l'app, voir plus haut), ce parent est devenu lui-même un bloc de
     confinement pour ses descendants position:fixed (tout transform non "none" en établit un).
     .inner (ce sélecteur), enfant direct de #panel-raccourcis-interets dans index.html, voit donc
     son "top" calculé RELATIVEMENT au parent — qui a LUI-MÊME déjà ce même décalage sur son
     propre "top". L'ancien calc() s'ajoutait donc EN PLUS de celui du parent (décalage double,
     ex. 70px+70px=140px), faisant apparaître le panneau bien plus bas que prévu, avec un bandeau
     gris flouté visible entre l'en-tête et le panneau — signalé par Christophe ("le bandeau est
     décalé"), capture desktop ET smartphone à l'appui (donc pas un bug de largeur d'écran). Voir
     aussi [[icilago-console-floutage-fond-panneau-global]] pour le correctif du 2026-09-01 qui a
     introduit ce transform, et [[icilago-annonceurs-2-menus-lateraux-simultanes]] pour le même
     type de piège (transform sur un ancêtre change la référence de tous ses descendants fixed). */
  top: 0;
  left: 0;
  right: 0;
  margin: 0 auto;
  width: 100%;
  max-width: 480px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 30px;
  background: var(--color-bg, #fff);
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.3);
  transform: translateY(-100%);
  transition: transform 0.25s ease;
  touch-action: auto;
}
#panel-raccourcis-interets.ouvert .panel-raccourcis-interets-inner { transform: translateY(0); }
.raccourci-interet {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100px;
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.raccourci-interet img { width: 40px; height: 40px; object-fit: contain; display: block; }
.raccourci-interet span { margin-top: 10px; font-size: 14px; color: var(--color-text); text-align: center; }

/* Indicateur "tirer pour actualiser" (pull-to-refresh) — "Près de vous" (view-visiteur-liste),
   guide.icilago.eu uniquement pour l'instant, 2026-08-29. Fixe sous le header (même ancrage que
   #modal-filtres/#modal-filtres-carte, voir plus haut dans ce fichier). --pull-progress (0 à
   60px) est posé en style inline par initPullToRefreshVisiteur() (main.js) pendant le geste tactile
   : translateY va de -100% (masqué, hors écran sous le header) à 0 (pleinement révélé) au fur et
   à mesure du tirage vers le bas. La classe .pull-refresh-transition (ajoutée seulement au
   relâchement du doigt, retirée juste après) évite tout ressort/rebond visible PENDANT le drag
   (qui doit suivre le doigt image par image, sans latence), tout en gardant une transition douce
   au retour/à l'ouverture complète une fois le doigt levé. */
#pull-refresh-indicator {
  position: fixed;
  /* RESTRUCTURATION, 2026-08-30 : demande de Christophe — le blanc du haut de l'indicateur doit
     COUVRIR tout le contenu de la vue entre le header et la roue (avant, cette zone laissait
     voir la carte "Position GPS"/le curseur de rayon en dessous, car seule une boîte de 90px
     ancrée en bas était révélée). "top" repasse juste sous le header (comme avant le
     correctif précédent) : la boîte fait de nouveau 240px (150 d'espace + ~90 de contenu),
     mais elle n'est plus révélée PROGRESSIVEMENT via translateY (ancré en bas, limité à
     TIRAGE_MAX=90px, donc incapable de révéler la totalité de 240px). Le masquage/affichage
     se fait maintenant intégralement via opacity + visibility (piloté en JS, voir
     appliquerTirage() dans main.js) : dès qu'un tirage commence, TOUTE la boîte de 240px
     apparaît d'un coup (fondu rapide), couvrant tout ce qu'il y a entre le header et la roue —
     plus de zone "trouée" qui laisse voir le contenu réel. */
  top: calc(env(safe-area-inset-top, 0px) + var(--header-height));
  /* left:0;right:0 (avant le 2026-09-08) couvrait TOUTE la largeur de la fenêtre — correct sur
     mobile/tablette (#app y occupe déjà 100% de la largeur), mais sur PC/desktop large, #app est
     centré à max-width:480px (voir plus haut) alors que ce fixed, non contraint, s'étalait sur
     toute la largeur du navigateur : la roue+texte apparaissaient centrés au milieu de l'ÉCRAN,
     visuellement déconnectés de la colonne (plus étroite) du header bleu. Repris via
     left:50%+transform:translateX(-50%)+max-width:480px, même formule que .menu-lateral-anchor/
     les autres éléments fixed déjà calés sur la colonne 480px — demande de Christophe, 2026-09-08 :
     "ajoute la roue et le texte en dessous de la barre bleue (identique à la version smartphone et
     tablette) mais pour version pc". */
  left: 50%;
  width: 100%;
  max-width: 480px;
  transform: translateX(-50%);
  /* 200px = 100 (espace sous le header, demande explicite de Christophe) + 44 (roue) + 16 (gap)
     + ~16 (texte 12px) + 24 (marge/padding-bottom). */
  height: 200px;
  padding: 100px 0 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 16px;
  background: var(--color-bg, #fff);
  z-index: 19;
  pointer-events: none;
  /* Masquage/affichage par opacity+visibility (plus de transform de position, voir note
     ci-dessus) — piloté en JS (appliquerTirage()). Pas de transition : apparition/disparition
     instantanée, demande explicite de Christophe (pas de fondu), 2026-08-30. */
  opacity: 0;
  visibility: hidden;
}
/* Texte SOUS la roue — demande de Christophe, 2026-08-30 (capture à l'appui), "taille de texte
   réduite" : 11px choisi (plus petit que .hint à 14px sur cette app, pour rester discret sous le
   header). */
.pull-refresh-texte { margin: 0; font-size: 12px; color: var(--color-primary); font-weight: 600; }
/* Roue à 8 lames en dégradé d'opacité (style natif iOS/Android "activity indicator"), à la place
   du rond à bordure .spinner (classe distincte, .spinner reste inchangée ailleurs dans l'app).
   Chaque <i> est une lame positionnée/orientée par nth-child ; tourner le conteneur entier
   (.pull-refresh-spinner) fait pivoter l'ensemble sans recalculer chaque lame. Pendant le tirage
   (avant relâchement), rotation manuelle pilotée par --pull-rotation (posée en JS) — donne
   l'impression que le geste "arme" le rafraîchissement. Au relâchement (.pull-refresh-loading),
   bascule sur un "tick" à 8 pas (steps(8), pas une rotation continue) pour rester fidèle au style
   natif reproduit ici, plutôt que réutiliser @keyframes spin (rotation continue, utilisée par
   .spinner ailleurs). */
.pull-refresh-spinner {
  position: relative;
  width: 44px;
  height: 44px;
  transform: rotate(var(--pull-rotation, 0deg));
}
.pull-refresh-spinner i {
  position: absolute;
  top: 2px;
  left: 19px;
  width: 6px;
  height: 16px;
  border-radius: 3px;
  background: var(--color-primary);
  transform-origin: 3px 20px;
}
.pull-refresh-spinner i:nth-child(1) { transform: rotate(0deg);   opacity: 1; }
.pull-refresh-spinner i:nth-child(2) { transform: rotate(45deg);  opacity: 0.87; }
.pull-refresh-spinner i:nth-child(3) { transform: rotate(90deg);  opacity: 0.74; }
.pull-refresh-spinner i:nth-child(4) { transform: rotate(135deg); opacity: 0.61; }
.pull-refresh-spinner i:nth-child(5) { transform: rotate(180deg); opacity: 0.48; }
.pull-refresh-spinner i:nth-child(6) { transform: rotate(225deg); opacity: 0.35; }
.pull-refresh-spinner i:nth-child(7) { transform: rotate(270deg); opacity: 0.22; }
.pull-refresh-spinner i:nth-child(8) { transform: rotate(315deg); opacity: 0.12; }
#pull-refresh-indicator.pull-refresh-loading .pull-refresh-spinner { animation: pull-refresh-tick 0.8s steps(8) infinite; }
@keyframes pull-refresh-tick { to { transform: rotate(360deg); } }

@media (min-width: 500px) {
  .bottom-nav { border-left: 1px solid var(--color-border); border-right: 1px solid var(--color-border); }
}

/* Texte sous l'image de la vignette Icônes (horaire/ville/distance), réduit sur smartphone —
   demande de Christophe, 2026-08-30. Seuil symétrique du @media (min-width:500px) ci-dessus. */
@media (max-width: 499px) {
  .vignette-evenement-visiteur-infos { font-size: 11px; }
}

/* ---------- Écran plein écran "Quitter l'application" (Accès libre) ----------
   Remplace la déconnexion classique pour un visiteur en Accès libre (demande explicite du
   2026-07-31, voir #ecran-quitter-application dans index.html). Page blanche plein écran,
   au-dessus de tout (y compris .modal-overlay à z-index:3000 et #splash-screen à 9999) : logo +
   message centrés horizontalement ET verticalement ensemble via .quitter-app-centre, GIF de
   démonstration ancré séparément en bas de la fenêtre (position:absolute dans ce conteneur
   position:fixed), au-dessus de la zone de sécurité iOS le cas échéant. */
#ecran-quitter-application {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  touch-action: none;
}
#ecran-quitter-application.hidden { display: none; }
.quitter-app-centre {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  padding: 0 30px;
  text-align: center;
}
.quitter-app-logo {
  width: 70%;
  max-width: 260px;
  height: auto;
}
.quitter-app-message {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--color-primary);
  line-height: 1.5;
  max-width: 300px;
}
.quitter-app-gif {
  position: absolute;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 24px);
  transform: translateX(-50%);
  width: 110px;
  height: auto;
}

/* ---------- Page "Messagerie" (menu bas visiteur) ---------- */
/* Mêmes conditions que la page Messagerie côté annonceurs.icilago.eu (demande de Christophe,
   2026-08-04) : rubriques accordéon (.details-rubrique-mesevt, même gabarit que "Mes évènements"
   côté annonceurs), cartes cliquables pour "Envoyés"/"Reçus" (.messagerie-visiteur-carte-fil) et
   "Brouillon" (.messagerie-visiteur-carte-brouillon). */
/* Centrage vertical des popups "Nouveau message" et "Conversation" (oubli au moment de la
   construction de la page — même technique que #modal-consentement-position plus haut, et mêmes
   valeurs que #modal-messagerie-pro / #modal-fil-messagerie-pro côté annonceurs.icilago.eu,
   demande de Christophe 2026-08-04). */
#modal-messagerie-visiteur .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 40px);
  max-width: 420px;
  max-height: 80vh;
  max-height: 80dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
/* S'ouvre PAR-DESSUS #modal-messagerie-visiteur sans le refermer : 10px de moins de large de
   chaque côté que ce parent (demande de Christophe, 2026-08-13). */
#modal-fil-messagerie-visiteur .modal-sheet {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% - 60px);
  max-width: 400px;
  max-height: 85vh;
  max-height: 85dvh; /* repli dvh (2026-09-02), voir #app en haut du fichier pour l'explication complete */
  border-radius: 16px;
  margin: 0;
}
.details-rubrique-mesevt { margin-top: 4px; }
.details-rubrique-mesevt summary {
  cursor: pointer; height: 15px; padding: 0 4px; overflow: hidden;
  color: var(--color-text-muted, #767b86);
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  line-height: 15px;
  display: flex; justify-content: space-between; align-items: center;
  list-style: none;
}
.details-rubrique-mesevt summary::-webkit-details-marker { display: none; }
.details-rubrique-mesevt summary::after {
  content: '▾'; margin-left: 8px; flex-shrink: 0;
  font-size: 14px; line-height: 15px; color: var(--color-text-muted, #767b86);
  transition: transform .15s ease;
}
.details-rubrique-mesevt[open] summary::after { transform: rotate(180deg); }
/* Le nombre et le libellé doivent former UN SEUL bloc de texte ancré à gauche — voir la même
   note côté annonceurs.icilago.eu (.mesevt-rubrique-titre). */
.mesevt-rubrique-titre { text-align: left; }
.carte-messagerie-visiteur {
  background: var(--color-bg, #f5f5f4);
  border-radius: 10px;
  padding: 10px 12px;
  margin: 8px 0;
}
.messagerie-visiteur-carte-fil, .messagerie-visiteur-carte-brouillon { cursor: pointer; }
.messagerie-visiteur-item-date { font-size: 11px; color: var(--color-text-muted); margin-bottom: 3px; }
.messagerie-visiteur-item-texte { font-size: 13.5px; white-space: pre-wrap; }
.messagerie-visiteur-item-meta { display: flex; align-items: center; gap: 8px; margin-top: 8px; font-size: 12px; color: var(--color-text-muted); }
.pastille-messagerie-visiteur-nombre {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--color-danger);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
}
/* Fil de discussion complet (#modal-fil-messagerie-visiteur) : bulles chronologiques —
   l'expéditeur (vous) à droite, ICILAGO à gauche. */
.liste-echanges-fil-visiteur { display: flex; flex-direction: column; gap: 10px; max-height: 45vh; max-height: 45dvh; overflow-y: auto; margin-bottom: 14px; padding: 4px 2px; }
.echange-fil-visiteur { max-width: 85%; padding: 8px 10px; border-radius: 10px; }
.echange-fil-visiteur-expediteur { align-self: flex-end; background: var(--color-primary); color: #fff; }
.echange-fil-visiteur-admin { align-self: flex-start; background: var(--color-bg, #f5f5f4); }
.echange-fil-visiteur-entete { font-size: 10.5px; font-weight: 600; opacity: .75; margin-bottom: 3px; }
.echange-fil-visiteur-texte { font-size: 13.5px; white-space: pre-wrap; }

/* Badge "ville" au-dessus du popup carte (vue "Carte", clic sur un repère) — demande de
   Christophe, 2026-08-31 : cadre bleu, texte blanc, angle haut-gauche du popup, juste au-dessus.
   Positionné en absolute par rapport à .leaflet-popup-content-wrapper (position:relative posée en
   JS, voir centrerPopupHorizontalementSurCarte dans main.js) — top négatif = déborde au-dessus du
   cadre, left:0 = aligné sur son bord gauche réel (le wrapper n'a pas de padding propre, seul son
   contenu en a, donc left:0 correspond bien au bord visuel du cadre). */
.popup-carte-badge-ville {
  position: absolute;
  top: -22px;
  left: 0;
  background: var(--color-primary);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 8px;
  white-space: nowrap;
}

/* Liste des lieux/évènements à l'intérieur du popup "Carte" (repère regroupant plusieurs lieux
   au même endroit) — demande de Christophe, 2026-09-02 : à partir de 4 lieux, le popup devenait
   plus haut que l'écran. Hauteur plafonnée à environ 3 lieux, défilement vertical au-delà plutôt
   que de laisser grandir le popup sans limite.
   Barre de défilement rendue TOUJOURS visible (demande de Christophe, 2026-09-11 : "la liste doit
   être totalement visible... faire apparaître le curseur de défilement") — sans ce complément, le
   défilement fonctionnait déjà mais la barre par défaut (fine/masquée sur mobile, notamment
   Safari iOS) ne laissait rien deviner : la liste semblait coupée/incomplète plutôt que
   scrollable. `scrollbar-width`/`scrollbar-color` pour Firefox, `::-webkit-scrollbar*` pour
   Chrome/Safari/webviews mobiles (les deux jeux de règles sont nécessaires, aucun des deux n'est
   reconnu par l'autre moteur). */
/* Pastille ronde 20px à gauche de chaque item de la liste ci-dessous (.popup-carte-liste-evenements),
   couleur du centre d'intérêt de l'évènement — demande de Christophe, 2026-09-11 : "Ajoute sur
   chaque liste dans les cadres blancs un rond à la couleur du centre d'intérêt, à gauche du
   'Sujet' [1ère ligne : raison sociale si présente, sinon le titre]". */
.popup-item-evenement { display: flex; align-items: flex-start; gap: 8px; }
.popup-item-puce-interet { width: 20px; height: 20px; border-radius: 50%; flex: 0 0 20px; margin-top: 2px; box-shadow: 0 1px 2px rgba(0,0,0,.25); }
.popup-item-texte { flex: 1; min-width: 0; }
.popup-carte-liste-evenements {
  max-height: 260px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--color-primary, #1a2b6d) #e4e7ee;
}
.popup-carte-liste-evenements::-webkit-scrollbar { width: 6px; }
.popup-carte-liste-evenements::-webkit-scrollbar-track { background: #e4e7ee; border-radius: 3px; }
.popup-carte-liste-evenements::-webkit-scrollbar-thumb { background: var(--color-primary, #1a2b6d); border-radius: 3px; }
