/* ============ Ami-Chemin — design system ============
   Fond gris-vert #EEF1EC · encre #152420 · duo corail #FF5A4E → bleu #2E6BFF (dégradés)
   Or #F2A81D · titres Bricolage Grotesque · texte Instrument Sans
   Boutons >= 48px · dark mode via [data-theme=dark]                     */

/* ---- Contrastes : mesures, pas estimes ----
   Ces trois couleurs passaient le seuil AA (4,5:1) sur le fond NU de la page,
   et c'est ainsi qu'elles avaient ete verifiees en juillet. Mais elles servent
   surtout de texte sur des fonds TEINTES — un message d'erreur, un message
   d'information, une pastille — ou le fond est la meme couleur a 10 ou 12 %
   d'opacite. Compose sur la page, ce fond remonte vers le texte et fait chuter
   le rapport. Mesure dans le navigateur, sur une page rendue :
     .erreur  3,94   .info  4,03   .chip  3,11   texte d'accent  3,53
   Les valeurs ci-dessous ramenent les quatre au-dessus de 4,5. --accent-fonce
   ne sert QUE de couleur de texte : la couleur de marque qui porte le degrade
   et le logo est --accent, elle n'est pas touchee. */
:root {
  --bg: #EEF1EC;
  --carte: #ffffff;
  --carte-2: #F4F6F1;
  --encre: #152420;
  --sourdine: #4A5A54;
  --trait: rgba(21, 36, 32, .1);
  --accent: #FF5A4E;
  --accent-fonce: #B33B32;   /* 3,11 -> 4,52 sur le fond teinte d'une pastille */
  --accent-doux: rgba(255, 90, 78, .12);
  /* Assombri de 8 % le 31/07/2026 : #2E6BFF donnait 3,95:1 sur --bg (#EEF1EC),
     sous le seuil WCAG AA de 4,5:1 pour du texte normal — et --lien EST une
     couleur de texte (les liens). Mesuré, pas estimé. #2A62EB donne 4,56:1.
     Le thème sombre passait déjà (6,34:1) et n'est pas touché. */
  --lien: #275BDB;           /* 4,03 -> 4,52 sur le fond teinte d'un message */
  --lien-doux: rgba(46, 107, 255, .1);
  --ambre: #F2A81D;
  --ambre-doux: rgba(242, 168, 29, .14);
  /* 4,00:1 sur --bg, sous le seuil. Assombri de 7 % -> 4,51:1. */
  --danger: #B83224;         /* 3,94 -> 4,58 sur le fond teinte d'une erreur */
  /* 3,04:1 sur --bg : le plus loin du compte des trois. Assombri de 21 %
     -> 4,57:1. C'est la retouche la plus visible, et la seule qui suffise :
     un vert clair sur fond clair ne peut pas atteindre 4,5:1 autrement. */
  --ok: #187C4D;
  --bleu: #2E6BFF;
  --rouge: #FF5A4E;
  /* Sens unique dans toute l'app : bleu à gauche, rouge à droite */
  --degrade: linear-gradient(90deg, var(--bleu), var(--rouge));
  --degrade-diag: linear-gradient(135deg, var(--bleu), var(--rouge));
  --r: 18px;
  --ombre: 0 10px 30px rgba(21, 36, 32, .14);
  /* Deux hauteurs de contrôle dans toute l'app, et rien d'autre :
     --h  = action pleine largeur (boutons + champs de formulaire)
     --hc = action en ligne (boutons ronds, boutons de liste, barres au-dessus de la carte) */
  --h: 50px;
  --hc: 44px;
  /* Halos du fond vivant. Ces opacités ne sont pas décoratives : elles sont la
     limite au-delà de laquelle le texte sourdine posé dessus (titres de section,
     étiquettes) descend sous le contraste 4,5:1 exigé par le WCAG AA. Mesuré sur
     le seul recouvrement géométriquement possible, bleu + violet : 4,60:1 ici.
     Ne pas monter ces valeurs sans refaire le calcul. */
  --halo-bleu: rgba(46, 107, 255, .155);
  --halo-corail: rgba(255, 90, 78, .135);
  --halo-violet: rgba(110, 63, 168, .104);
}
[data-theme=dark] {
  --bg: #131A17;
  --carte: #1C2520;
  --carte-2: #232D27;
  --encre: #ECF2EE;
  --sourdine: #9FAFA7;
  --trait: rgba(236, 242, 238, .12);
  --accent: #FF6A5F;
  --accent-fonce: #FF5A4E;
  --accent-doux: rgba(255, 90, 78, .18);
  --lien: #6E97FF;
  --lien-doux: rgba(110, 151, 255, .16);
  --ambre-doux: rgba(242, 168, 29, .16);
  --danger: #E45F52;         /* 4,11 -> 4,54 sur une carte sombre, 4,75 sur un message */
  --ok: #35B57A;
  --ombre: 0 10px 30px rgba(0, 0, 0, .4);
  /* Sur fond sombre, la même mesure laisse 4,92:1 à pleine intensité : les halos
     peuvent y être deux fois plus présents sans rien coûter à la lecture. */
  --halo-bleu: rgba(46, 107, 255, .30);
  --halo-corail: rgba(255, 90, 78, .26);
  --halo-violet: rgba(110, 63, 168, .20);
}

* { box-sizing: border-box; }

/* L'attribut hidden vaut « display:none » dans la feuille du navigateur, donc n'importe
   quelle classe qui pose un display (.btn est en flex) le rend inopérant : l'élément
   reste visible alors que le code le croit caché. On tranche une fois pour toutes. */
[hidden] { display: none !important; }

/* ============ Fond vivant ============
   Deux couches à deux vitesses, pour que l'écran ne soit jamais tout à fait
   immobile : trois halos qui dérivent lentement, et quelques lucioles qui les
   traversent. Tout est animé en transform et opacity uniquement — les deux
   seules propriétés que le compositeur traite sans redessiner la page. Aucune
   animation de scale ni de filter : cela forcerait à re-flouter à chaque image.

   La couche est posée hors de .app, donc la navigation PJAX ne la recrée pas :
   les halos poursuivent leur course en changeant de page. */
.fond-vivant {
  position: fixed; inset: 0; z-index: 0;
  overflow: hidden; pointer-events: none;
  /* contenu isolé : les halos ne peuvent pas allonger la page ni déclencher
     un recalcul de mise en page du contenu qui passe par-dessus */
  contain: strict;
}
.fond-vivant span {
  position: absolute; display: block; border-radius: 50%;
  filter: blur(60px);
  will-change: transform, opacity;
}
.fond-vivant .halo-a {
  width: 78vw; height: 78vw; left: -26vw; top: -16vh;
  background: var(--halo-bleu);
  animation: halo-a 34s ease-in-out infinite alternate;
}
.fond-vivant .halo-b {
  width: 86vw; height: 86vw; right: -32vw; bottom: -12vh;
  background: var(--halo-corail);
  animation: halo-b 42s ease-in-out infinite alternate;
}
.fond-vivant .halo-c {
  width: 62vw; height: 62vw; left: 20vw; top: 42vh;
  background: var(--halo-violet);
  animation: halo-c 52s ease-in-out infinite alternate;
}
/* Trois durées premières entre elles : le motif ne se répète jamais à l'identique. */
@keyframes halo-a { from { transform: translate3d(0,0,0);          opacity: .85; }
                    to   { transform: translate3d(20vw, 13vh, 0);  opacity: 1;   } }
@keyframes halo-b { from { transform: translate3d(0,0,0);          opacity: 1;   }
                    to   { transform: translate3d(-17vw, -11vh, 0);opacity: .8;  } }
@keyframes halo-c { from { transform: translate3d(0,0,0);          opacity: .55; }
                    to   { transform: translate3d(-13vw, -19vh, 0);opacity: .95; } }

/* Les lucioles : montent d'un bord à l'autre de l'écran, chacune à son rythme. */
.fond-vivant i {
  position: absolute; display: block; border-radius: 50%;
  opacity: 0;
  animation: luciole linear infinite;
  will-change: transform, opacity;
}
@keyframes luciole {
  0%   { transform: translate3d(0, 0, 0);          opacity: 0; }
  12%  { opacity: .55; }
  80%  { opacity: .45; }
  100% { transform: translate3d(6vw, -108vh, 0);   opacity: 0; }
}

/* Un fond décoratif ne vaut pas qu'on impose du mouvement à qui n'en veut pas. */
@media (prefers-reduced-motion: reduce) {
  .fond-vivant span, .fond-vivant i { animation: none; }
  .fond-vivant i { opacity: .3; }
}

html, body { margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }
a, button, .ligne, .puce-coche { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
button, .btn, .puce-coche, .onglets button { user-select: none; -webkit-user-select: none; }
body {
  overscroll-behavior-y: contain;
  /* Les halos ne sont plus peints ici mais dans .fond-vivant, où ils peuvent bouger. */
  background: var(--bg);
  color: var(--encre);
  font-family: 'Instrument Sans', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 16px; line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}
.app { position: relative; z-index: 1; max-width: 480px; margin: 0 auto; min-height: 100dvh; display: flex; flex-direction: column; }
.content { flex: 1; padding: 0 16px calc(110px + env(safe-area-inset-bottom)); }
a { color: var(--lien); text-decoration: none; }
h1, h2, h3 { margin: 0; font-family: 'Bricolage Grotesque', 'Avenir Next', 'Segoe UI', system-ui, sans-serif; letter-spacing: -.02em; }
/* Titres de section façon app native : petites capitales espacées */
h2 {
  font-family: inherit; font-size: .72rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .12em; color: var(--sourdine);
  margin: 24px 4px 10px;
}
/* Sauf les titres d'affichage : nom du profil, hero, titre de feuille */
.hero h2, .centre h2, .sheet h2:first-child {
  font-family: 'Bricolage Grotesque', 'Avenir Next', system-ui, sans-serif;
  font-size: 1.25rem; font-weight: 800; text-transform: none;
  letter-spacing: -.02em; color: var(--encre); margin: 12px 0 6px;
}
.muted { color: var(--sourdine); }
.small { font-size: .85rem; }
.centre { text-align: center; }

/* ---- Barre haute ---- */
.topbar { display: flex; align-items: center; gap: 12px; padding: 14px 16px 10px; }
/* Le titre s'efface en défilant vers le bas et revient dès qu'on remonte. La flèche
   de retour, elle, ne bouge jamais : c'est la seule sortie de la page. */
.topbar-title { flex: 1; min-width: 0; transition: opacity .25s ease, transform .25s cubic-bezier(.2, .8, .3, 1); }
.topbar-title.cachee { opacity: 0; transform: translateY(-10px); pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .topbar-title { transition: none; } }
.topbar-title h1 { font-size: 1.25rem; font-weight: 800; }
/* Champ de saisie occupant la ligne du titre : même hauteur que la flèche de retour,
   pas de marge, il doit se fondre dans la barre plutôt que flotter dessous. */
/* Ligne « personne + actions » : le nom occupe la place restante et se coupe par des
   points de suspension. Sans min-width:0, un flex refuse de rétrécir sous la taille de
   son texte et pousse les boutons hors de la ligne. */
.contact-nom { flex: 1; min-width: 0; display: flex; align-items: center; }
.contact-nom span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.contact-actions { display: flex; gap: 6px; flex: none; }
.contact-actions .btn, .btn.btn-compact { width: auto; min-height: var(--hc); padding: 0 14px; }

/* Corbeille en deux temps : l'icône seule, puis le mot « Supprimer » qui se déplie
   à la première pression. Supprimer un contact sur un écran tactile est trop facile
   à déclencher par erreur pour n'être qu'un seul geste. */
.btn-corbeille { width: var(--hc); padding: 0; gap: 0; }
.btn-corbeille svg { width: 18px; height: 18px; flex: none; }
.btn-corbeille span {
  max-width: 0; overflow: hidden; white-space: nowrap; opacity: 0;
  transition: max-width .28s cubic-bezier(.2, .8, .3, 1), opacity .2s, margin .28s;
}
.btn-corbeille.confirme { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-corbeille.confirme span { max-width: 120px; opacity: 1; margin-left: 7px; }
@media (prefers-reduced-motion: reduce) { .btn-corbeille span { transition: none; } }

/* Pendant la confirmation, « Retrouver » se replie : il laisse la place au mot
   « Supprimer » et évite qu'on vise l'un en croyant appuyer sur l'autre.
   Règle isolée : un :has() non reconnu par un vieux navigateur invaliderait toute
   la liste de sélecteurs s'il était groupé avec d'autres. */
.contact-actions .btn-primaire {
  max-width: 200px; min-width: 0; overflow: hidden; white-space: nowrap;
  transition: max-width .28s cubic-bezier(.2, .8, .3, 1), opacity .2s, padding .28s, border-width .28s;
}
.contact-actions:has(.btn-corbeille.confirme) .btn-primaire {
  max-width: 0; opacity: 0; padding: 0; border-width: 0;
}

.champ.champ-barre {
  flex: 1; min-width: 0; margin: 0; min-height: var(--hc); padding: 0 14px;
  font-family: 'Bricolage Grotesque', 'Avenir Next', system-ui, sans-serif;
  font-size: 1.05rem; font-weight: 700; letter-spacing: -.01em;
}
.topbar-title p { margin: 0; font-size: .82rem; }
.iconbtn {
  width: var(--hc); height: var(--hc); flex: none; border-radius: 50%;
  background: var(--carte); border: 1px solid var(--trait); box-shadow: 0 4px 12px rgba(21, 36, 32, .12);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1.2rem; color: var(--encre); cursor: pointer; text-decoration: none;
}
.iconbtn:active { transform: scale(.9); transition: transform .15s; }
/* l'ombre claire était codée en dur : elle disparaissait sur fond sombre */
[data-theme=dark] .iconbtn { box-shadow: 0 4px 12px rgba(0, 0, 0, .45); }

/* ---- Cartes & listes ---- */
.carte {
  background: var(--carte); border: 1px solid var(--trait); border-radius: var(--r);
  box-shadow: var(--ombre); padding: 16px; margin-bottom: 12px;
}
.ligne {
  position: relative;
  display: flex; align-items: center; gap: 12px;
  background: var(--carte); border: 1px solid var(--trait); border-radius: var(--r);
  box-shadow: var(--ombre); padding: 14px; margin-bottom: 10px;
  color: inherit; min-height: 48px;
}
a.ligne:active, .vote-ligne:active { transform: scale(.98); transition: transform .15s; }
/* Lien de recouvrement : un element interactif dans un lien est invalide, donc
   les cartes qui portent un bouton (suppression) sont des <div> couverts par ce
   <a> vide. Deux etages seulement : le lien couvre tout (1), les vrais boutons
   passent devant (2). Le contour de focus du lien dessine le tour de la carte,
   ce qui est exactement ce qu'on veut : c'est la carte qu'on va ouvrir. */
.lien-couvre { position: absolute; inset: 0; z-index: 1; border-radius: inherit; }
.ligne:has(> .lien-couvre:active) { transform: scale(.98); transition: transform .15s; }
.vedette .v-suppr { position: relative; z-index: 2; }
.ligne .fleche { margin-left: auto; color: var(--sourdine); transition: opacity .2s, transform .25s; }

/* ---- Appui long : corbeille de suppression (glisse depuis la droite) ---- */
.ligne[data-suppr] { -webkit-touch-callout: none; user-select: none; -webkit-user-select: none; overflow: hidden; }
.ligne-suppr {
  position: absolute; top: 50%; right: 10px; z-index: 2; /* devant .lien-couvre */
  transform: translateY(-50%) translateX(calc(100% + 26px));
  opacity: 0; pointer-events: none;
  height: var(--hc); min-width: var(--hc); padding: 0 12px;
  border: 0; border-radius: 99px;
  background: var(--danger); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-family: inherit; font-weight: 800; font-size: .82rem;
  box-shadow: 0 4px 14px rgba(217, 59, 43, .4);
  cursor: pointer;
  transition: transform .32s cubic-bezier(.2, .8, .3, 1), opacity .22s ease;
}
.ligne-suppr svg { width: 18px; height: 18px; flex: none; }
.ligne-suppr span {
  max-width: 0; overflow: hidden; white-space: nowrap; opacity: 0;
  transition: max-width .28s cubic-bezier(.2, .8, .3, 1), opacity .2s, margin .28s;
}
.ligne-suppr.confirme span { max-width: 110px; opacity: 1; margin-left: 7px; }
.ligne.suppr-visible { border-color: rgba(217, 59, 43, .35); }
.ligne.suppr-visible .ligne-suppr { transform: translateY(-50%) translateX(0); opacity: 1; pointer-events: auto; }
.ligne.suppr-visible .fleche { opacity: 0; transform: translateX(10px); }
.ligne-suppr:active { transform: translateY(-50%) scale(.92); }

/* Entrée en douceur des cartes et lignes, léger décalage en cascade */
.carte, .ligne, .vote-ligne { animation: surgir .32s cubic-bezier(.2, .8, .3, 1) backwards; }
.content > .carte:nth-child(2), .content > .ligne:nth-child(2), .vote-ligne:nth-child(2) { animation-delay: .04s; }
.content > .carte:nth-child(3), .content > .ligne:nth-child(3), .vote-ligne:nth-child(3) { animation-delay: .08s; }
.content > .carte:nth-child(4), .content > .ligne:nth-child(4), .vote-ligne:nth-child(4) { animation-delay: .12s; }
.content > .carte:nth-child(5), .content > .ligne:nth-child(5), .vote-ligne:nth-child(5) { animation-delay: .16s; }
@keyframes surgir { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ---- Pastille date ---- */
.pdate {
  flex: none; width: 54px; border-radius: 12px; background: var(--ambre-doux);
  color: var(--encre); text-align: center; padding: 6px 0; line-height: 1.15;
}
.pdate .j { font-size: 1.25rem; font-weight: 800; display: block; font-family: 'Bricolage Grotesque', sans-serif; letter-spacing: -.02em; }
.pdate .m, .pdate .s { font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--sourdine); }

/* ---- Avatars / puces membres ---- */
.avatar {
  width: 40px; height: 40px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 800; font-size: .95rem;
  font-family: 'Bricolage Grotesque', sans-serif;
}
.avatar.grand { width: 72px; height: 72px; font-size: 1.6rem; border: 3px solid var(--carte); box-shadow: 0 4px 12px rgba(21, 36, 32, .2); background-clip: padding-box; }
/* Avec une photo, l'initiale s'efface : c'est le repli, pas un décor par-dessus. */
.avatar.photo { background-size: cover; background-position: center; color: transparent; }

/* Avatar modifiable : au survol et au toucher, un voile annonce l'action. */
.avatar-modif { position: relative; display: inline-block; cursor: pointer; border: 0; padding: 0; background: none; }
.avatar-modif .crayon {
  position: absolute; right: 0; bottom: 0; width: 26px; height: 26px; border-radius: 50%;
  background-color: #2E6BFF; background-image: var(--degrade-diag);
  background-repeat: no-repeat; background-size: calc(100% + 2px) calc(100% + 2px); background-position: -1px -1px;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--carte); box-shadow: 0 2px 8px rgba(21, 36, 32, .3);
}
.avatar-modif .crayon svg { width: 13px; height: 13px; color: #fff; }
.avatar-modif:active { transform: scale(.96); transition: transform .15s; }
.puces { display: flex; flex-wrap: wrap; gap: 8px; }
.puce {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--carte); border: 1px solid var(--trait); border-radius: 999px;
  padding: 4px 12px 4px 4px; font-weight: 600; font-size: .9rem;
}
.puce .avatar { width: 28px; height: 28px; font-size: .75rem; }

.puce-coche { cursor: pointer; user-select: none; }
.puce-coche input { position: absolute; opacity: 0; pointer-events: none; }
.puce-coche .coche { display: none; font-style: normal; color: var(--accent-fonce); font-weight: 700; }
.puce-coche:has(input:checked) { border-color: transparent; background: linear-gradient(90deg, rgba(46, 107, 255, .16), rgba(255, 90, 78, .16)); outline: 1px solid var(--encre); }
.puce-coche:has(input:checked) .coche { display: inline; }
.puce-dedans { opacity: .75; }

/* ---- Boutons ---- */
.btn {
  display: flex; align-items: center; justify-content: center; width: 100%;
  min-height: var(--h); padding: 0 18px; border-radius: 14px; border: 1px solid transparent;
  background-clip: padding-box; /* le fond ne passe pas sous la bordure : évite les liserés qui débordent aux zooms fractionnaires */
  font-family: inherit; font-size: .95rem; font-weight: 700; cursor: pointer; text-decoration: none;
  transition: filter .15s, background-color .15s, color .15s, border-color .15s;
}
/* La transition transform n'existe qu'à l'appui : au repos, Chrome ne met pas le bouton
   sur un calque GPU isolé (source de liserés colorés aux bords en zoom fractionnaire) */
.btn:active { transform: scale(.97); transition: transform .15s; }
.btn + .btn { margin-top: 10px; }
/* ...mais uniquement quand ils sont empilés. Sur une rangée horizontale, cette marge
   décalait le second bouton vers le bas et, la rangée étant en flex, étirait le premier
   à 54px : deux boutons voisins de tailles différentes. L'écart y vient du gap. */
:is(.contact-actions, .barre-carte, .rangee-action, .v-actions, .copie-bloc, .puces, .onglets) .btn + .btn { margin-top: 0; }
/* align-items: center plutôt que l'étirement par défaut : un enfant plus haut ne peut
   plus imposer sa taille aux autres boutons de la rangée. */
.contact-actions, .barre-carte, .rangee-action, .vedette .v-actions, .copie-bloc { align-items: center; }
.btn-primaire { background-color: #2E6BFF; background-image: var(--degrade); color: #fff; }
.btn-primaire:active { filter: brightness(.95); }
.btn-blanc { background: var(--carte); color: var(--encre); border-color: var(--trait); box-shadow: var(--ombre); }
.btn-lien { background: none; border: 0; color: var(--lien); font-weight: 700; cursor: pointer; font-size: .95rem; }
.btn-danger { background: var(--carte); color: var(--danger); border-color: rgba(217, 59, 43, .3); }
.btn-pointille { background: transparent; border: 2px dashed rgba(21, 36, 32, .18); color: var(--lien); box-shadow: none; }
[data-theme=dark] .btn-pointille { border-color: var(--trait); }
.btn[disabled] { opacity: .55; cursor: default; }

/* ---- Formulaires ---- */
.champ {
  width: 100%; min-height: var(--h); padding: 12px 14px; margin-bottom: 10px;
  border: 1px solid var(--trait); border-radius: 13px;
  background: var(--carte); color: var(--encre); font-family: inherit; font-size: 1rem; outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.champ::placeholder { color: var(--sourdine); opacity: .8; }
.champ:focus { border-color: var(--lien); box-shadow: 0 0 0 3px var(--lien-doux); }
/* Chrome peint les champs auto-remplis en clair : on les ramène aux couleurs du thème */
.champ:-webkit-autofill, .champ:-webkit-autofill:hover, .champ:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--carte) inset;
  -webkit-text-fill-color: var(--encre);
  caret-color: var(--encre);
  transition: background-color 9999999s ease-out;
}
.rangee { display: flex; gap: 10px; }
.rangee > * { flex: 1; }
.erreur { background: rgba(217, 59, 43, .1); color: var(--danger); border-radius: 13px; padding: 10px 14px; margin-bottom: 12px; font-size: .92rem; font-weight: 600; }
.info { background: var(--lien-doux); color: var(--lien); border-radius: 13px; padding: 10px 14px; margin-bottom: 12px; font-size: .92rem; font-weight: 600; }

/* ---- Onglets (connexion / inscription) ---- */
.onglets { display: flex; background: var(--bg); border: 1px solid var(--trait); border-radius: 13px; padding: 4px; margin-bottom: 14px; gap: 4px; }
.onglets button {
  flex: 1; min-height: var(--hc); border: 0; border-radius: 10px; background: transparent;
  color: var(--sourdine); font-family: inherit; font-weight: 700; font-size: .95rem; cursor: pointer; transition: .2s;
}
.onglets button.actif { background: var(--encre); color: var(--carte); box-shadow: none; }
[data-theme=dark] .onglets button.actif { background: var(--encre); color: var(--bg); }

/* ---- Vote ---- */
.vote-ligne {
  display: flex; align-items: center; gap: 12px; padding: 13px 14px;
  background: var(--carte); border: 1px solid var(--trait); border-radius: var(--r);
  margin-bottom: 8px; cursor: pointer; box-shadow: var(--ombre);
}
.vote-ligne.choisi { border-color: var(--accent); outline: 1px solid var(--accent); }
.vote-rang {
  width: 30px; height: 30px; flex: none; border-radius: 50%;
  background: var(--bg); color: var(--sourdine); border: 1px solid var(--trait);
  display: inline-flex; align-items: center; justify-content: center; font-weight: 800; font-size: .85rem;
  font-family: 'Bricolage Grotesque', sans-serif;
}
.vote-ligne.premier .vote-rang { background-color: #2E6BFF; background-image: var(--degrade-diag); color: #fff; border-color: transparent; }
.vote-corps { flex: 1; min-width: 0; }
.vote-corps .nom { font-weight: 700; }
.jauge { height: 6px; border-radius: 99px; background: var(--bg); margin-top: 7px; overflow: hidden; }
.jauge > i { display: block; height: 100%; background-color: #2E6BFF; background-image: var(--degrade); border-radius: 99px; }
.vote-nb { text-align: right; flex: none; }
.vote-nb b { font-size: 1.05rem; color: var(--encre); display: block; line-height: 1.1; font-family: 'Bricolage Grotesque', sans-serif; font-weight: 800; }
.vote-nb span { font-size: .68rem; font-weight: 700; color: var(--sourdine); text-transform: uppercase; letter-spacing: .08em; }

/* ---- Présence (RSVP) ---- */

/* ---- Carte Leaflet ---- */
/* position:relative est vital : c'est lui qui fait du cadre le bloc conteneur des
   repères. Sans lui, un marqueur absolu se cale sur la page entière — il file en
   bas de l'écran et overflow:hidden ne le rattrape plus. MapLibre le pose déjà via
   .maplibregl-map, mais on ne dépend pas d'une feuille externe pour ça. */
.map { position: relative; width: 100%; height: 260px; border-radius: var(--r); border: 1px solid var(--trait); box-shadow: var(--ombre); z-index: 0; background: var(--bg); overflow: hidden; }
.map-grande { height: 62vh; min-height: 460px; border-radius: 20px; }

/* ---- Carte date/heure de la sortie ---- */
.carte-sortie { padding: 4px 16px; }
.rangee-dt { display: flex; align-items: stretch; }
.champ-dt { flex: 1; display: flex; align-items: center; gap: 9px; padding: 13px 0; cursor: pointer; }
.champ-dt svg { width: 18px; height: 18px; color: var(--sourdine); flex: none; }
.champ-dt input {
  border: 0; background: none; outline: none; width: 100%; padding: 0;
  color: var(--encre); font-family: inherit; font-size: 1rem; font-weight: 600;
}
.champ-dt input::-webkit-calendar-picker-indicator { opacity: 0; position: absolute; inset: 0; width: 100%; height: 100%; cursor: pointer; }
.champ-dt { position: relative; }
.sep-v { width: 1px; margin: 10px 14px; background: var(--trait); }
[data-theme=dark] :is(input[type=date], input[type=time]) { color-scheme: dark; }

/* ---- Carte 3D (MapLibre) ---- */
/* Recherche d'adresse posée SUR la carte : elle flotte au-dessus du fond au lieu
   de manger de la hauteur au-dessus, et le translucide laisse deviner la carte. */
.map-cadre { position: relative; }
.barre-carte {
  position: absolute; top: 10px; left: 10px; right: 10px; z-index: 5;
  display: flex; gap: 8px;
}
.barre-carte .champ {
  flex: 1; margin: 0; min-height: var(--hc); padding: 0 14px;
  background: rgba(255, 255, 255, .94); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border-color: transparent; box-shadow: 0 6px 18px rgba(21, 36, 32, .18);
}
[data-theme=dark] .barre-carte .champ { background: rgba(28, 37, 32, .94); }
.barre-carte .btn { width: auto; margin: 0; min-height: var(--hc); white-space: nowrap; box-shadow: 0 6px 18px rgba(21, 36, 32, .22); }
/* Les commandes de zoom descendent sous la barre, sinon elles passent dessous */
.map-cadre .maplibregl-ctrl-top-right { top: 56px; }
.pin3d { position: relative; width: 36px; height: 42px; cursor: grab; }
.pin3d:active { cursor: grabbing; }
.pin3d .goutte {
  position: absolute; left: 50%; top: 4px; width: 30px; height: 30px; margin-left: -15px;
  background-color: #2E6BFF; background-image: var(--degrade-diag);
  border: 2.5px solid #fff; border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  box-shadow: 0 8px 18px rgba(21, 36, 32, .4);
}
.pin3d .goutte::after { content: ""; position: absolute; inset: 9px; background: #fff; border-radius: 50%; }
[data-theme=dark] .pin3d .goutte { border-color: #1C2520; }
[data-theme=dark] .pin3d .goutte::after { background: #ECF2EE; }
.maplibregl-ctrl-group { border-radius: 12px !important; box-shadow: 0 4px 14px rgba(21, 36, 32, .18) !important; }
/* ---- Colonne d'outils du coin bas-droit ----
   MapLibre dessine ses boutons en 29 px ; à côté d'une pastille de 44 px la
   colonne partait en escalier. On les met tous au même gabarit, et le groupe
   prend la forme d'une gélule pour répondre au rond du bouton de vue. */
.maplibregl-ctrl-bottom-right .maplibregl-ctrl-group { margin: 0 12px 12px 0; border-radius: calc(var(--hc) / 2) !important; }
.maplibregl-ctrl-bottom-right .maplibregl-ctrl-group button { width: var(--hc); height: var(--hc); }
.maplibregl-ctrl-bottom-right .maplibregl-ctrl-group .maplibregl-ctrl-icon { background-size: 22px 22px; }
.maplibregl-ctrl button.maplibregl-ctrl-geolocate { width: var(--hc); height: var(--hc); border-radius: 50%; }
.maplibregl-user-location-dot, .maplibregl-user-location-dot::before { background-color: #2E6BFF; }
[data-theme=dark] .maplibregl-ctrl-group { background: #232D27; }
[data-theme=dark] .maplibregl-ctrl-group button + button { border-top: 1px solid rgba(236, 242, 238, .12); }
[data-theme=dark] .maplibregl-ctrl button .maplibregl-ctrl-icon { filter: invert(1) hue-rotate(180deg); }
[data-theme=dark] .maplibregl-ctrl.maplibregl-ctrl-attrib { background-color: rgba(19, 26, 23, .6); }
[data-theme=dark] .maplibregl-ctrl-attrib, [data-theme=dark] .maplibregl-ctrl-attrib a { color: #9FAFA7; }
[data-theme=dark] .maplibregl-ctrl-attrib-button { filter: invert(1); }
/* ---- Attribution ----
   En bas à DROITE, dans le coin, sous la colonne de commandes (voir carte-3d.js :
   l'ordre d'ajout des contrôles est ce qui la place là).

   En navigation, la feuille d'itinéraire recouvre le bas de la carte : le coin
   bas-droit passerait dessous et l'attribution deviendrait invisible — or une
   attribution masquée ne vaut pas mieux qu'une attribution absente, et la licence
   ODbL d'OpenStreetMap l'exige.

   Une première version la remontait en haut à droite, sous le bandeau de manœuvre.
   Elle restait visible, mais elle changeait de coin en cours de route, ce qui n'a
   pas de sens pour un repère fixe. Elle reste donc en bas à droite, simplement
   posée JUSTE AU-DESSUS de la feuille.

   `--feuille-haut` est mesurée en continu par sortie-live.js et portée par
   .map-conteneur : coder cette hauteur en dur mettrait l'attribution sous la
   feuille dès qu'une deuxième carte d'itinéraire s'y ajoute. C'est la même
   mécanique que .vitesse, qui occupe le coin opposé.

   On déplace le conteneur entier sans risque : en navigation, les commandes de
   zoom y sont masquées (règle plus bas) et le bouton de vue a rejoint le résumé
   d'itinéraire — il ne reste que l'attribution dedans. */
.maplibregl-ctrl-bottom-right { z-index: 4; }
.app.navigation .maplibregl-ctrl-bottom-right {
  top: auto; right: 0; z-index: 56;
  bottom: calc(var(--feuille-haut, 96px) + 12px);
}
/* Un peu d'air dans le coin : sans marge, l'attribution colle au bord arrondi de
   la carte. On garde le même retrait que les commandes juste au-dessus. */
.maplibregl-ctrl-bottom-right .maplibregl-ctrl-attrib { margin: 0 12px 10px 0; border-radius: 10px; }
.map-haute { height: 56vh; min-height: 340px; }
.map-conteneur { position: relative; overflow: hidden; border-radius: var(--r); z-index: 0; }
/* ---- Bouton de vue : quatrième pastille de la colonne d'outils ----
   Position de repli, le temps que sortie-live.js le glisse dans la pile MapLibre
   (voir la règle suivante). Sans script, il reste utilisable en bas à droite. */
.map-bouton {
  position: absolute; right: 12px; bottom: 12px; z-index: 800;
  width: var(--hc); height: var(--hc); padding: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--carte); color: var(--encre); border: 1px solid var(--trait);
  box-shadow: 0 4px 12px rgba(21, 36, 32, .12); cursor: pointer;
}
/* Une fois dans la pile, il se comporte comme les autres contrôles : bloc aligné
   à droite, même gouttière de 12 px, empilé au-dessus du zoom.
   pointer-events est indispensable : MapLibre neutralise les clics sur le coin
   entier et ne les rend qu'à ses propres contrôles (.maplibregl-ctrl). Sans cette
   ligne le bouton s'affiche mais ne répond pas — le clic traverse vers la carte. */
.maplibregl-ctrl-bottom-right .map-bouton { position: static; display: flex; margin: 0 12px 10px auto; pointer-events: auto; }
.map-bouton svg { width: 22px; height: 22px; display: block; }
/* Quelle icône est visible : c'est une classe sur le bouton, pas l'attribut hidden
   sur les <svg>. « hidden » n'existe pas sur SVGElement — écrire svg.hidden = true
   en JavaScript ne fait rien du tout, l'icône ne changeait jamais. */
.map-bouton .ic-moi { display: none; }
.map-bouton.vise-moi .ic-tous { display: none; }
.map-bouton.vise-moi .ic-moi { display: block; }
[data-theme=dark] .map-bouton { box-shadow: 0 4px 12px rgba(0, 0, 0, .45); }

/* ---- En navigation : le bouton de vue rejoint le haut, en pastille ronde ----
   Positionné dans le cadre de la carte (et non en fixed) pour rester aligné sur la
   colonne de l'app quel que soit l'écran, comme la flèche de retour en face.
   Le bandeau d'itinéraire réserve déjà 66 px de ce côté. */
/* En route, on ne règle pas le zoom à la main : la carte suit. Seul le bouton de
   vue reste — il garde exactement la place qu'il avait à l'arrêt, ce qui vaut
   mieux que de le faire changer de coin sous le pouce d'une personne qui roule. */
.app.navigation .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group { display: none; }
/* La vitesse passe donc à gauche, où plus rien ne la gêne. */
/* En navigation, la vitesse prend la place qu'occupait le bouton de vue : en bas à
   gauche, juste au-dessus de la feuille, qui a cédé sa gauche au bouton. La hauteur
   de la feuille est mesurée par sortie-live.js — la coder en dur la mettrait dessous
   dès qu'une carte d'itinéraire s'ajoute. Aucune vitesse LIMITE n'est affichée : le
   serveur de routage public ne la fournit pas, et l'inventer au volant serait
   dangereux. */
.app.navigation .vitesse {
  right: auto; left: 12px; top: auto; z-index: 61;
  bottom: calc(var(--feuille-haut, 96px) + 12px);
  min-width: 62px; border-radius: 18px; padding: 8px 0;
}
.app.navigation .vitesse b { font-size: 1.45rem; }

/* ---- Choix de l'itinéraire, pendant la navigation ----
   OSRM propose parfois deux ou trois chemins. Le bandeau reste en bas de la carte,
   au-dessus de la vitesse, et ne s'affiche que s'il y a vraiment un choix à faire :
   un seul chemin ne se choisit pas. */
.routes-choix { display: flex; gap: 10px; margin: 0 0 4px; padding: 2px 0 6px; }
.routes-choix::-webkit-scrollbar { display: none; }
/* Les deux chemins possibles sont la vraie décision de cet écran : ils occupent
   chacun la moitié de la largeur, à parts égales, et portent leur couleur. */
.routes-choix .route-carte { flex: 1 1 0; min-width: 0; }
.route-carte {
  text-align: left; cursor: pointer; font-family: inherit;
  border-radius: 18px; padding: 12px 14px; color: var(--encre);
  background: var(--carte-2); border: 2px solid var(--trait);
  box-shadow: 0 6px 18px rgba(21, 36, 32, .22);
}
[data-theme=dark] .route-carte { box-shadow: 0 6px 18px rgba(0, 0, 0, .45); }
.route-carte .via { display: flex; align-items: center; gap: 6px; margin-bottom: 5px; }
.route-carte .via i { width: 9px; height: 9px; border-radius: 3px; flex: none; background: currentColor; }
.route-carte .via em {
  font-style: normal; font-size: .6rem; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--sourdine);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.route-carte b { display: block; font-family: 'Bricolage Grotesque', sans-serif; font-size: 1.35rem; font-weight: 800; line-height: 1; }
.route-carte span { display: block; font-size: .72rem; color: var(--sourdine); margin-top: 3px; }
.route-carte.choisie { border-color: currentColor; background: var(--carte); }

/* ---- Heures d'arrivée du groupe ----
   Ce qu'aucune application de navigation ne montre : non pas quand J'arrive, mais
   si on arrive ENSEMBLE. C'est la raison d'être de l'application. */
.arrivees { margin: 18px 0; }
.arr-tete { display: flex; align-items: baseline; gap: 8px; margin: 0 2px 8px; }
.arr-tete b { font-size: .82rem; font-weight: 800; }
.arr-tete span { font-size: .76rem; color: var(--sourdine); margin-left: auto; }
/* La ligne est un <button> depuis qu'elle remplace la pastille du membre : sans
   ce reset elle hériterait du fond, de la bordure et de la police des boutons du
   navigateur. width:100% et text-align:left pour qu'elle se comporte en ligne de
   liste et non en bouton centré. */
.arr-ligne {
  display: flex; align-items: center; gap: 10px; padding: 9px 14px;
  width: 100%; text-align: left; background: none; border: 0; color: inherit;
  font: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
/* Sa propre ligne n'ouvre aucune fiche : le curseur ne doit pas la promettre. */
.arr-ligne[data-moi] { cursor: default; }
.arr-ligne:focus-visible { outline: 3px solid var(--lien); outline-offset: -3px; border-radius: 10px; }
.arr-ligne + .arr-ligne { border-top: 1px solid var(--trait); }
.arr-ligne .avatar { width: 28px; height: 28px; font-size: .72rem; }
/* Le prénom ne se tronque jamais : c'est lui qui identifie la ligne. C'est le motif
   (« si elle part maintenant ») qui cède la place, puisqu'il ne fait que nuancer. */
.arr-ligne .nom { flex: none; font-size: .9rem; font-weight: 700; }
.arr-ligne .motif { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--sourdine); }
.arr-ligne .heure {
  margin-left: auto; flex: none; font-family: 'Bricolage Grotesque', sans-serif;
  font-size: .98rem; font-weight: 800; font-variant-numeric: tabular-nums;
}
.arr-ligne .ecart { flex: none; font-size: .7rem; font-weight: 800; padding: 2px 8px; border-radius: 999px; }
.ecart.pile { background: var(--lien-doux); color: var(--lien); }
.ecart.avance { background: rgba(31, 157, 98, .16); color: var(--ok); }
.ecart.retard { background: var(--accent-doux); color: var(--accent-fonce); }
/* Une estimation faite depuis une position périmée, ou pour quelqu'un qui n'est pas
   encore parti, ne vaut pas une heure d'arrivée réelle : elle le dit. */
.arr-ligne.suppose { opacity: .72; }
.arr-bilan { margin: 10px 2px 0; font-size: .78rem; color: var(--sourdine); line-height: 1.45; }
.arr-bilan b { color: var(--encre); }

/* ---- Vitesse : en bas de carte, côté opposé aux commandes ---- */
.vitesse {
  position: absolute; bottom: 12px; right: 12px; z-index: 800;
  min-width: 64px; padding: 6px 10px; border-radius: 16px; text-align: center;
  background: var(--carte); color: var(--encre); border: 1px solid var(--trait);
  box-shadow: 0 4px 12px rgba(21, 36, 32, .12);
  font-variant-numeric: tabular-nums;
}
[data-theme=dark] .vitesse { box-shadow: 0 4px 12px rgba(0, 0, 0, .45); }
.vitesse b { display: block; font-size: 1.3rem; font-weight: 800; line-height: 1.05; }
.vitesse span { display: block; font-size: .6rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--sourdine); }
.app:not(.navigation) .vitesse { display: none; } /* la vitesse ne sert qu'en route */
.point-moi {
  display: block; width: 18px; height: 18px; border-radius: 50%;
  background: var(--lien); border: 3px solid #fff;
  animation: pulser 2s infinite;
}
/* ---- Bandeau de manœuvre ----
   Il se lit en conduisant : la distance domine, la voie la suit, et la manœuvre
   d'après tient la droite pour qu'on anticipe un enchaînement. Le dégradé de
   l'application remplace l'ancienne pastille sombre — c'est notre couleur, et elle
   se détache mieux d'une carte de nuit. */
.nav-bandeau {
  position: absolute; top: 12px; left: 12px; right: 12px; z-index: 55;
  display: flex; align-items: center; gap: 13px;
  background: var(--degrade-diag); color: #fff;
  border-radius: 22px; padding: 13px 15px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, .42);
}
.topbar .iconbtn { position: relative; z-index: 56; }
.nav-fleche {
  width: 42px; height: 42px; flex: none; display: grid; place-items: center;
  background: rgba(255, 255, 255, .2); border-radius: 14px;
  font-size: 1.55rem; line-height: 1;
}
.nav-texte { flex: 1; min-width: 0; }
.nav-texte b {
  display: block; font-family: 'Bricolage Grotesque', sans-serif;
  font-size: 1.5rem; font-weight: 800; line-height: 1; letter-spacing: -.02em;
}
.nav-texte span {
  display: block; margin-top: 3px; font-size: .88rem; font-weight: 600;
  color: rgba(255, 255, 255, .92); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nav-apres { flex: none; max-width: 96px; text-align: right; }
.nav-apres small { display: block; font-size: .68rem; opacity: .8; }
.nav-apres b {
  display: block; font-size: .9rem; font-weight: 800; line-height: 1.15;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---- Feuille de navigation ----
   Posée sur la carte, elle rassemble ce qui reste à faire : la distance restante,
   les chemins possibles, et l'heure d'arrivée de chacun. Un appui sur la poignée
   la replie pour rendre la carte entière. */
.nav-feuille {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 60;
  background: var(--carte); border-top: 1px solid var(--trait);
  border-radius: 24px 24px 0 0; padding: 0 14px 16px;
  box-shadow: 0 -16px 40px rgba(0, 0, 0, .45);
  max-height: 68%; overflow-y: auto;
  transition: transform .34s cubic-bezier(.2, .8, .3, 1);
}
/* ---- Feuille repliée ----
   ⚠ PLUS DÉCLENCHÉE PAR L'INTERFACE depuis que la poignée sert à dévoiler ce qui
   suit la carte (voir devoiler() dans sortie-live.js). La feuille reste dépliée en
   permanence : c'est la position de base. Ces règles sont conservées parce qu'elles
   sont justes et que le repli peut resservir — mais aucun appui ne pose la classe
   aujourd'hui. Ne pas les lire comme un comportement en vigueur.
   Ce qui suit décrit donc l'état replié TEL QU'IL SERAIT.

   Elle glissait vers le bas en laissant dépasser une bande de 74 px :
   `transform: translateY(calc(100% - 74px))`. Ces 74 px avaient été mesurés à la
   main sur l'ancienne feuille, qui n'avait qu'UNE ligne de chiffres. Depuis
   qu'elle en a quatre (destination, jauge, chiffres, commandes), la bande tombe au
   milieu de la ligne des nombres : on voyait « RESTE  DISTANCE  ARRIVÉE » sans
   aucune valeur en dessous. Constaté en ligne.

   Deuxième défaut, invisible celui-là : `translateY` ne change pas la HAUTEUR de
   l'élément. `getBoundingClientRect().height` renvoyait donc la hauteur dépliée
   même une fois repliée — et --feuille-haut avec elle. L'attribution de la carte
   et l'indicateur de vitesse, qui se calent dessus, flottaient loin au-dessus de
   la feuille repliée.

   On replie donc pour de vrai : les lignes accessoires sont retirées, la feuille
   rétrécit d'elle-même. Plus de nombre magique à re-mesurer à chaque changement de
   contenu, et le ResizeObserver de sortie-live.js voit enfin le repli — ce que son
   propre commentaire déplorait.

   Ce qu'on garde replié : tout ce qui SE LIT — la destination, la progression et
   les trois nombres. Ce qui se replie : ce sur quoi on APPUIE, c'est-à-dire la
   ligne des commandes, et le choix d'itinéraire.

   La séparation est donc « lire » / « agir », et non « important » /
   « accessoire ». Replié, on consulte d'un coup d'œil sans quitter la route ;
   déplié, on agit.

   Conséquence à connaître : « Arrêter la navigation » vit dans la ligne des
   commandes, il faut donc déplier pour arrêter. La poignée est juste au-dessus,
   et arrêter n'est pas un geste qu'on fait au jugé — mais c'est un appui de plus,
   et il vaut mieux l'avoir écrit ici que de le redécouvrir. */
.nav-feuille.repliee { overflow: hidden; }
.nav-feuille.repliee .nav-actions,
.nav-feuille.repliee .routes-choix { display: none; }
/* Sans les commandes, la marge basse des nombres n'a plus rien à espacer : elle
   laisserait un vide sous la dernière ligne. */
.nav-feuille.repliee .nav-chiffres { margin-bottom: 0; }
.nav-feuille.repliee .nav-resume { margin-bottom: 2px; }
.nav-poignee {
  display: block; width: 100%; border: 0; background: none; cursor: pointer;
  padding: 9px 0 10px; -webkit-tap-highlight-color: transparent;
}
.nav-poignee span { display: block; width: 44px; height: 5px; margin: 0 auto; border-radius: 99px; background: var(--trait); }
[data-theme=dark] .nav-poignee span { background: rgba(236, 242, 238, .3); }
/* ---- Résumé de route ----
   Trois étages, du contexte vers l'action : où l'on va et où l'on en est, puis les
   deux nombres que l'on cherche au volant, puis les commandes.

   L'agencement précédent mettait l'heure d'arrivée en gros au centre, et reléguait
   le TEMPS RESTANT — ce qu'on regarde vraiment en roulant — dans la plus petite
   ligne de l'écran, en capitales espacées. La destination n'apparaissait nulle
   part, et « Arrêter » était un lien rouge collé à l'information, à hauteur de
   pouce. Les trois sont corrigés ici. */
/* Retrait latéral : 32 px au total (14 de padding sur .nav-feuille + 18 ici),
   contre 16 à l'origine. La jauge et la ligne de destination touchaient presque
   les bords arrondis de la feuille : elles paraissaient déborder plutôt que
   posées dedans.
   Le retrait s'applique au résumé ENTIER — destination, jauge, nombres et
   commandes restent alignés sur la même colonne, ce qui n'aurait pas été le cas
   en ne resserrant que l'en-tête.
   C'est la seule valeur à bouger pour resserrer ou élargir l'ensemble. */
.nav-resume { margin: 0 18px 10px; }

/* 1 · Où l'on va, et où l'on en est — au CENTRE de la rangée des commandes.
   Cet ensemble occupait une ligne à lui seul, en haut du résumé, tandis que la
   rangée des boutons laissait un grand vide entre ses deux extrémités. Le placer
   là gagne une ligne entière de hauteur — donc autant de carte — sans rien
   retirer de ce qui est affiché.
   `min-width: 0` sur toute la chaîne : un élément flex refuse par défaut de
   rétrécir sous la largeur de son contenu, et sans cette ligne un nom de rue long
   pousserait les deux boutons hors de l'écran au lieu de se couper. */
.nav-ou {
  display: flex; flex-direction: column; justify-content: center; gap: 5px;
  flex: 1 1 auto; min-width: 0; padding: 0 12px;
}
.nav-dest { display: flex; align-items: center; gap: 6px; min-width: 0; color: var(--sourdine); }
.nav-dest svg { width: 14px; height: 14px; flex: none; }
/* Un nom de rue peut être long : il se coupe plutôt que de pousser le pourcentage
   hors de l'écran. min-width:0 sur toute la chaîne, sinon un flex refuse de
   rétrécir sous la largeur de son contenu et l'ellipse ne se déclenche jamais. */
.nav-dest b {
  font: 700 .84rem/1.3 system-ui, sans-serif; color: var(--encre);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
/* Le pourcentage suit le nom au lieu d'être poussé à l'autre bout : dans une
   colonne étroite, l'écarter le collerait au bouton d'arrêt. `margin-left: auto`
   le maintient à droite tant qu'il reste de la place. */
.nav-pct { flex: none; margin-left: auto; padding-left: 8px; font: 700 .72rem system-ui; color: var(--sourdine); font-variant-numeric: tabular-nums; }

.nav-jauge { display: block; height: 4px; border-radius: 99px; background: var(--trait); overflow: hidden; }
.nav-jauge i { display: block; height: 100%; width: 0; background: var(--degrade); transition: width .6s ease; }

/* 2 · Les trois nombres du trajet, alignés et étiquetés.
   Trois colonnes ÉGALES, et non un flex qui répartit selon le contenu : c'est
   l'égalité qui donne trois positions fixes à l'écran. Le regard revient toujours
   au même endroit, quelle que soit la longueur des valeurs — « 5 min » et
   « 128 min » n'ont pas la même largeur, et un flex ferait glisser les deux autres
   colonnes chaque fois que l'une change. */
.nav-chiffres {
  display: grid; grid-template-columns: repeat(3, 1fr);
  text-align: center; align-items: end; margin-bottom: 11px;
}
.nav-bloc { min-width: 0; padding: 0 2px; }
/* Séparateurs portés par la colonne du milieu : deux traits pour trois colonnes,
   sans élément vide ni pseudo-élément à positionner. */
.nav-bloc-milieu { border-left: 1px solid var(--trait); border-right: 1px solid var(--trait); }
.nav-resume .nav-bloc small {
  display: block; font-size: .62rem; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--sourdine);
}
.nav-resume .nav-bloc b {
  display: block; margin-top: 3px; font-family: 'Bricolage Grotesque', sans-serif;
  font-size: 1.4rem; font-weight: 800; line-height: 1; letter-spacing: -.02em;
  white-space: nowrap;
  /* Chiffres à chasse fixe : sans cela « 34 min » puis « 9 min » font sauter tout
     le bloc à chaque minute écoulée, dans le coin de l'œil de quelqu'un qui roule. */
  font-variant-numeric: tabular-nums;
}
/* L'unité suit le nombre sans le concurrencer. */
.nav-resume .nav-bloc b i { font-style: normal; font-size: .78rem; font-weight: 700; letter-spacing: 0; }
/* À l'arrivée, distance et heure n'ont plus d'objet : les deux colonnes s'effacent
   et « Arrivée ! » prend toute la largeur plutôt que de rester coincé dans un
   tiers d'écran. */
.nav-chiffres.arrivee { grid-template-columns: 1fr; }
.nav-chiffres.arrivee .nav-bloc b { font-size: 1.2rem; }

/* 3 · Les commandes, sur leur propre ligne. */
/* Les boutons ne bougent pas de leurs extrémités : `flex: none` les protège du
   rétrécissement quand le nom de destination est long. C'est le nom qui cède, pas
   les commandes. */
.nav-actions { display: flex; align-items: center; gap: 0; }
.nav-actions > .map-bouton, .nav-actions > .nav-arreter { flex: none; }
/* Le bouton de vue générale est glissé ici par sortie-live.js. Il vivait posé sur
   la carte ; dans la feuille, il ne recouvre plus rien. */
.app.en-route .nav-actions .map-bouton { position: static; margin: 0; }
/* « Arrêter » : même gabarit que le bouton de vue, à l'autre bout, et sans texte.
   Un lien rouge au milieu de l'information s'appuie par erreur ; une pastille
   isolée à l'opposé demande une intention. Le libellé vit dans aria-label et
   title — l'action reste annoncée aux lecteurs d'écran et au survol. */
.nav-arreter {
  width: var(--hc); height: var(--hc); flex: none; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; cursor: pointer;
  background: none; color: var(--danger);
  border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
}
.nav-arreter svg { width: 20px; height: 20px; display: block; }
/* Repli si color-mix n'est pas comprise : une bordure unie plutôt qu'aucune. */
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  .nav-arreter { border-color: var(--danger); }
}
/* En route, la barre d'action disparaît : l'arrêt est passé dans la feuille, en
   lien discret. Une action rare ne mérite pas une barre pleine largeur, encore
   moins posée sur la carte. */
.app.en-route .rangee-action { display: none; }

/* Puck GPS : flèche dégradée, halo qui pulse.
   La flèche était posée sur une pastille opaque — blanche en thème clair, presque
   noire en thème sombre. C'est ce disque sombre qu'on voyait autour de la flèche.
   Il est retiré : seule la flèche reste.
   La pastille servait à détacher la flèche d'un fond de carte quelconque. Ce rôle
   est repris par un `drop-shadow`, qui épouse la forme de la flèche au lieu de
   dessiner un disque — la lisibilité est conservée sans le cercle.
   `filter` et non `box-shadow` : box-shadow suit la BOÎTE de l'élément, il aurait
   redessiné exactement le cercle qu'on vient d'enlever. */
.fleche-moi {
  display: flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; border-radius: 50%;
  background: none;
  animation: pulser 2s infinite;
}
/* Agrandie de 20×23 à 27×31 px. Sans la pastille, la flèche paraissait petite :
   c'est le disque qui donnait sa présence au repère, pas la flèche elle-même.
   L'ombre est épaissie en proportion, sinon elle disparaît sous une forme plus
   grande. La boîte reste à 42 px — c'est elle qui porte le halo, et la flèche y
   tient largement. */
.fleche-moi::after {
  content: ""; width: 27px; height: 31px;
  background: linear-gradient(180deg, #2E6BFF, #FF5A4E);
  clip-path: polygon(50% 0, 100% 100%, 50% 76%, 0 100%);
  transform: translateY(-1px);
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .6));
}
@keyframes pulser {
  0% { box-shadow: 0 0 0 0 rgba(46, 107, 255, .45); }
  70% { box-shadow: 0 0 0 16px rgba(46, 107, 255, 0); }
  100% { box-shadow: 0 0 0 0 rgba(46, 107, 255, 0); }
}
.pin-num {
  background: var(--carte); border: 1px solid var(--trait); border-radius: 999px;
  padding: 4px 10px 4px 4px; display: inline-flex; align-items: center; gap: 6px;
  font-weight: 700; font-size: .82rem; color: var(--encre); box-shadow: 0 4px 12px rgba(21, 36, 32, .12); white-space: nowrap;
}
.pin-num i { width: 22px; height: 22px; border-radius: 50%; background-color: #2E6BFF; background-image: var(--degrade-diag); color: #fff; font-style: normal; display: inline-flex; align-items: center; justify-content: center; font-size: .75rem; font-weight: 800; }

/* ---- Carte immersive (page sortie) : la carte passe sous les blocs du haut ---- */
.app.immersive .topbar {
  position: fixed; top: 0; left: 50%; transform: translateX(-50%);
  width: 100%; max-width: 480px; z-index: 54;
  background: transparent; pointer-events: none;
}
.app.immersive .topbar .iconbtn, .app.immersive .topbar button { pointer-events: auto; }
.app.immersive .topbar-title h1, .app.immersive .topbar-title p { text-shadow: 0 1px 6px rgba(238, 241, 236, .9); }
[data-theme=dark] .app.immersive .topbar-title h1, [data-theme=dark] .app.immersive .topbar-title p { text-shadow: 0 1px 6px rgba(19, 26, 23, .9); }
/* En navigation, la barre du haut n'a plus d'usage : son titre était déjà masqué,
   et sa flèche de retour venait se poser SUR le bandeau de manœuvre. On la retire
   entièrement — la sortie du mode se fait par « arrêter », juste sous la carte. */
.app.navigation .topbar { display: none; }
.app.immersive .content { padding-top: 0; }
.app.immersive .map-conteneur { margin: 0 -16px; border-radius: 0 0 var(--r) var(--r); }
.app.immersive .map-conteneur #map { border-radius: 0 0 var(--r) var(--r); border: 0; }
.app.immersive .map-conteneur { height: 62vh; min-height: 380px; }
.app.immersive .map-conteneur .map-haute { height: 100%; }

/* Action principale et réglages sur une seule ligne, le second calé à droite */
.rangee-action { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
.rangee-action .btn { flex: 1; margin: 0; }
.rangee-action .iconbtn { width: var(--h); height: var(--h); margin-left: auto; font-size: 1.35rem; }

/* ---- « J'y vais ! » : la carte prend tout l'écran sauf une bande en bas ----
   Cette bande garde le bouton d'arrêt sous le pouce ; sans elle, il faudrait
   défiler en conduisant pour se remettre en pause.
   !important est nécessaire : la poignée de redimensionnement pose la hauteur en
   style en ligne, qui l'emporterait sinon sur cette règle. */
.map-conteneur { transition: height .3s cubic-bezier(.2, .8, .3, 1); }
/* ---- En route : la carte prend tout l'écran ----
   Elle ne s'arrête plus au-dessus du bouton, elle passe DESSOUS : le bouton flotte
   par-dessus, comme les autres commandes. La barre du haut étant retirée en
   navigation, la carte part vraiment du bord supérieur. */
.app.en-route .map-conteneur {
  height: 100vh !important; min-height: 0 !important;
  border-radius: 0 !important; margin: 0 -16px;
}
.app.en-route .map-conteneur #map { border-radius: 0 !important; height: 100%; }
.app.en-route .poignee { display: none; } /* on ne redimensionne pas en roulant */
/* En route, l'écran ne garde que la carte et le bouton d'arrêt : ni redimensionnement,
   ni réglages. Ce que contient l'engrenage — voter pour un lieu, inviter, annuler la
   sortie — ne se fait pas au volant, et le bouton d'arrêt gagne toute la largeur. */
.app.en-route .rangee-action .iconbtn { display: none; }
/* Le bouton d'arrêt flotte au-dessus de la carte, au ras du bas. */
.app.en-route .rangee-action {
  position: fixed; left: 16px; right: 16px; z-index: 70;
  bottom: calc(14px + env(safe-area-inset-bottom)); margin: 0;
}
.app.en-route .rangee-action .btn { box-shadow: 0 10px 28px rgba(0, 0, 0, .45); }
/* La feuille descend au ras du bas : le bouton d'arrêt vit maintenant dedans, et
   plus rien ne l'oblige à flotter. Autant de carte rendue. */
.app.en-route .nav-feuille {
  bottom: 0; left: 0; right: 0;
  border-radius: 24px 24px 0 0;
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
}
/* En route, le bouton de vue quitte la pile de MapLibre pour la barre de résumé
   elle-même (voir .nav-resume plus haut). Il n'a donc plus de position flottante :
   c'est la vitesse qui occupe désormais le bas gauche. */
@supports (height: 100dvh) { /* sur mobile, dvh suit la barre d'adresse qui se rétracte */
  .app.en-route .map-conteneur { height: 100dvh !important; }
}
@media (prefers-reduced-motion: reduce) { .map-conteneur { transition: none; } }

/* ---- Profil : groupes de réglages ---- */
.etiquette { font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .12em; color: var(--sourdine); margin: 20px 4px 8px; }
.groupe-reglages { background: var(--carte); border: 1px solid var(--trait); border-radius: var(--r); box-shadow: var(--ombre); overflow: hidden; }
.reglage-ligne, .reglage summary {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  min-height: 52px; padding: 6px 16px; cursor: pointer; color: inherit;
}
.reglage summary { list-style: none; }
.reglage summary::-webkit-details-marker { display: none; }
.reglage + .reglage, .reglage-ligne + .reglage-ligne, .reglage + .reglage-ligne, .reglage-ligne + .reglage { border-top: 1px solid var(--trait); }
.reglage .valeur { color: var(--sourdine); font-size: .9rem; max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.reglage .valeur i { font-style: normal; margin-left: 4px; }
.reglage[open] summary { border-bottom: 1px solid var(--trait); }
.reglage[open] .valeur i { display: inline-block; transform: rotate(90deg); }
.reglage-form { padding: 14px 16px 16px; background: var(--carte-2); }
.reglage-form .btn { min-height: var(--hc); }

/* ---- Familles de réglages repliables ----
   Le profil déroulait tout d'un seul tenant : cinq champs de compte, six bascules
   de notification, et le reste de la page était loin sous le pouce.
   La section est une carte, comme tout le reste de la page ; son titre en est la
   première ligne. Replié, il ressemble donc exactement aux lignes qu'il contient
   (« Pseudo », « Thème »…) : on comprend qu'il s'ouvre sans avoir à l'essayer.
   Une étiquette grise flottante, elle, ne disait rien de tout cela. */
.section-reglages {
  background: var(--carte); border: 1px solid var(--trait);
  border-radius: var(--r); box-shadow: var(--ombre); overflow: hidden;
  margin: 0 0 12px;
}
.section-reglages > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 10px;
  min-height: 52px; padding: 6px 16px; color: inherit; font-weight: 700;
}
.section-reglages > summary::-webkit-details-marker { display: none; }
.section-reglages[open] > summary { border-bottom: 1px solid var(--trait); }
.section-reglages > summary .chevron {
  margin-left: auto; width: 7px; height: 7px; flex: none;
  border-right: 2px solid var(--sourdine); border-bottom: 2px solid var(--sourdine);
  transform: translateY(-2px) rotate(45deg); transition: transform .22s;
}
.section-reglages[open] > summary .chevron { transform: translateY(1px) rotate(-135deg); }
@media (prefers-reduced-motion: reduce) { .section-reglages > summary .chevron { transition: none; } }
/* Point coloré : il reste un champ à renseigner sous le repli. */
.section-reglages > summary .a-faire { width: 7px; height: 7px; border-radius: 50%; background: var(--accent-fonce); flex: none; }

/* Les groupes imbriqués perdent leur propre habillage : c'est la section qui porte
   désormais le fond, le cadre et l'ombre. Sans cela on empilait une carte dans une
   carte, avec double bordure et double arrondi. */
.section-reglages .groupe-reglages { background: none; border: 0; border-radius: 0; box-shadow: none; }
/* Les sous-titres internes suivent le retrait des lignes plutôt que celui de la page. */
.section-reglages .etiquette { margin: 14px 16px 6px; }
.section-reglages > .etiquette:first-of-type { margin-top: 12px; }
/* Deux groupes qui se suivent : une ligne les sépare, comme entre deux réglages. */
.section-reglages .groupe-reglages + .etiquette { border-top: 1px solid var(--trait); margin: 0; padding: 14px 16px 6px; }


/* ---- Poignée de redimensionnement de la carte ---- */
/* La poignée est un <button> depuis qu'un appui dessus dévoile la suite de la
   page : sans ce reset elle hériterait du fond, de la bordure et de la police
   des boutons du navigateur. width:100% pour que la zone d'appui couvre toute
   la largeur — une cible de 40 px se rate au pouce.
   cursor: row-resize reste : le glissement pour redimensionner n'a pas changé. */
.poignee {
  display: flex; justify-content: center; width: 100%; padding: 10px 0 6px;
  background: none; border: 0; font: inherit; color: inherit;
  cursor: row-resize; touch-action: none; -webkit-tap-highlight-color: transparent;
}
/* Le contour de focus se pose sur la barre elle-même : sur le bouton, large et
   sans fond, il dessinerait un grand rectangle vide en travers de la page. */
.poignee:focus-visible { outline: none; }
.poignee:focus-visible span { outline: 3px solid var(--lien); outline-offset: 4px; }
.poignee span { width: 40px; height: 5px; border-radius: 99px; background: rgba(21, 36, 32, .22); transition: background .2s; }
[data-theme=dark] .poignee span { background: rgba(236, 242, 238, .25); }
.poignee:active span { background: rgba(21, 36, 32, .38); }
[data-theme=dark] .poignee:active span { background: rgba(236, 242, 238, .45); }

/* ---- Chat ----
   Le bloc reprend la couleur de la zone qui le précède, mais FIXE : les halos qui
   respirent derrière le reste du site ne le traversent pas. Les bulles blanches se
   détachent alors franchement, au lieu des deux gris presque identiques d'avant. */
.chat {
  max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; gap: 8px;
  padding: 12px; background: var(--bg);
}
/* L'encre des bulles : le vert-noir du fond sombre. Sur blanc il donne 17:1, et il
   reste le même dans les deux thèmes — une bulle blanche appelle une encre noire,
   quel que soit le reste de la page. */
.bulle {
  background: #fff; color: #131A17;
  border: 1px solid var(--trait); border-radius: 14px 14px 14px 4px;
  padding: 8px 12px; max-width: 85%; align-self: flex-start; font-size: .92rem;
  background-clip: padding-box; position: relative; }
/* Mes messages : même fond que ceux des autres. Ce sont l'alignement à droite et le
   coin inversé qui les distinguent, pas la couleur. */
.bulle.moi { border-radius: 14px 14px 4px 14px; align-self: flex-end; }
/* Le nom garde la couleur du pseudo — c'est elle qui relie le message à l'avatar —
   mais assombrie : la palette est réglée pour un fond sombre, et telle quelle sur
   blanc l'ambre tombait à 2,0:1, illisible. Le facteur 0,6 remonte la plus faible à
   5,2:1. Un filtre plutôt qu'une seconde palette : la couleur est posée en style
   direct par le script, rien en CSS ne pourrait la remplacer sans l'écraser toutes. */
.bulle .qui { display: block; font-size: .72rem; font-weight: 800; margin-bottom: 2px; filter: brightness(.6); }
/* Le champ de saisie suit les bulles : c'est le message qu'on est en train d'écrire. */
#champMsg { background: #fff; color: #131A17; }
#champMsg::placeholder { color: rgba(19, 26, 23, .55); }

/* ---- Nav basse + FAB ---- */
.bottomnav {
  position: fixed; bottom: calc(12px + env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%);
  width: calc(100% - 28px); max-width: 452px; display: flex; align-items: center; justify-content: space-around;
  background:
    radial-gradient(80px 70px at calc(var(--lueur-x) - 16px) 108%, rgba(46, 107, 255, .26), rgba(46, 107, 255, 0) 70%),
    radial-gradient(80px 70px at calc(var(--lueur-x) + 16px) 108%, rgba(255, 90, 78, .26), rgba(255, 90, 78, 0) 70%),
    rgba(255, 255, 255, .94);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  transition: --lueur-x .55s cubic-bezier(.3, .75, .2, 1), transform .32s cubic-bezier(.2, .8, .3, 1);
  border: 1px solid rgba(21, 36, 32, .09);
  border-radius: 26px;
  box-shadow: 0 12px 32px rgba(21, 36, 32, .16);
  padding: 8px 6px; z-index: 50;
}
/* Défilement vers le bas : la nav s'efface sous l'écran (le FAB part avec elle) */
.bottomnav.cachee { transform: translateX(-50%) translateY(calc(100% + 46px + env(safe-area-inset-bottom))); }
[data-theme=dark] .bottomnav {
  background:
    radial-gradient(80px 70px at calc(var(--lueur-x) - 16px) 108%, rgba(46, 107, 255, .3), rgba(46, 107, 255, 0) 70%),
    radial-gradient(80px 70px at calc(var(--lueur-x) + 16px) 108%, rgba(255, 90, 78, .3), rgba(255, 90, 78, 0) 70%),
    rgba(28, 37, 32, .94);
  border-color: var(--trait);
  box-shadow: 0 12px 32px rgba(0, 0, 0, .5);
}
.bottomnav a:not(.fab) {
  flex: 1; text-align: center; font-size: .72rem; font-weight: 700; color: var(--sourdine);
  display: flex; flex-direction: column; align-items: center; gap: 4px; min-height: var(--hc); justify-content: center;
  letter-spacing: -.01em; border-radius: 14px; margin: 0 4px;
  transition: color .18s, background .18s;
}
.bottomnav a:not(.fab):active { transform: scale(.94); transition: transform .18s; }
.bottomnav a .icn { width: 20px; height: 20px; display: block; transition: transform .18s; }
.bottomnav a.actif { color: var(--encre); }
.bottomnav a.actif .icn { transform: scale(1.08); }
.bottomnav a.fab, .fab {
  color: #fff; font-size: 1.8rem; flex: none; text-decoration: none;
}
.fab {
  display: inline-flex; align-items: center; justify-content: center; line-height: 1;
  width: calc(var(--h) + 8px); height: calc(var(--h) + 8px); flex: none; border-radius: 50%; border: 4px solid var(--bg);
  background-color: #2E6BFF; background-image: var(--degrade-diag); color: #fff; font-size: 1.8rem; line-height: 1;
  background-clip: padding-box;
  box-shadow: 0 6px 18px rgba(255, 90, 78, .35); cursor: pointer; margin-top: -26px;
  transition: box-shadow .18s;
}
.fab:active { transform: scale(.92); box-shadow: 0 8px 22px rgba(46, 107, 255, .45); transition: transform .18s, box-shadow .18s; }
.sheet-backdrop { position: fixed; inset: 0; background: rgba(21, 36, 32, .45); z-index: 60; display: flex; align-items: flex-end; justify-content: center; animation: voile .25s ease; }
.sheet-backdrop[hidden] { display: none; }
.sheet { background: var(--bg); border-radius: 22px 22px 0 0; box-shadow: 0 -8px 30px rgba(21, 36, 32, .14); padding: 20px 16px calc(20px + env(safe-area-inset-bottom)); width: 100%; max-width: 480px; animation: feuille .32s cubic-bezier(.2, .8, .3, 1); }
@keyframes voile { from { opacity: 0; } }
@keyframes feuille { from { transform: translateY(60%); opacity: .4; } }

/* ---- Tableau de bord ---- */
.accueil-tete { display: flex; align-items: center; gap: 12px; padding: 18px 0 4px; }
.accueil-tete .avatar { width: 46px; height: 46px; border-radius: 15px; font-size: 1.05rem; }
.accueil-tete h1 { font-family: 'Bricolage Grotesque', sans-serif; font-weight: 800; font-size: 1.35rem; margin: 0; letter-spacing: -.02em; }
.section-titre {
  display: flex; align-items: center; gap: 10px;
  font-size: .72rem; font-weight: 800; letter-spacing: .09em; text-transform: uppercase;
  color: var(--sourdine); margin: 20px 2px 8px;
}
.section-titre .btn-lien { margin-left: auto; padding: 0; font-size: .72rem; letter-spacing: .06em; text-transform: none; }

/* ---- Choix du lieu : à mi-chemin, ou une adresse ----
   Deux blocs de même poids : ni l'un ni l'autre n'est un réglage secondaire, ce sont
   deux façons de répondre à la même question. */
.choix-lieu { display: flex; flex-direction: column; gap: 10px; }
.choix-mode {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 16px; border-radius: var(--r); cursor: pointer;
  background: var(--carte); border: 1px solid var(--trait); box-shadow: var(--ombre);
  color: inherit; font-family: inherit;
}
.choix-mode:active { transform: scale(.985); transition: transform .15s; }

/* ---- Déploiement ----
   Le choix disparaissait d'un coup et la carte apparaissait à sa place : rien ne
   reliait le geste au résultat. Le bloc touché se soulève, l'ensemble se referme
   vers le haut, et le contenu sort par en dessous — on voit le bloc s'ouvrir.
   max-height plutôt que height : la hauteur réelle n'est pas connue à l'avance. */
.choix-lieu { overflow: hidden; max-height: 440px; }
.choix-lieu.replie { max-height: 0; opacity: 0; }
.choix-lieu, .choix-lieu.replie {
  transition: max-height .36s cubic-bezier(.2, .8, .3, 1), opacity .26s ease-out;
}
/* Le bloc que l'on vient de toucher part légèrement en avant : c'est lui qui ouvre. */
.choix-lieu.replie .choix-mode.choisi { transform: scale(1.015); transition: transform .3s ease-out; }
.choix-lieu.replie .choix-mode:not(.choisi) { opacity: 0; transition: opacity .18s; }

/* Ce qui sort du bloc. Deux variantes : la carte ne prend que l'opacité — un
   transform sur un ancêtre fausse les calculs de coordonnées de MapLibre le temps
   de l'animation, et déformerait sa toile. */
@keyframes deploiement { from { opacity: 0; transform: translateY(-14px); } to { opacity: 1; transform: none; } }
@keyframes deploiement-doux { from { opacity: 0; } to { opacity: 1; } }
.se-deploie { animation: deploiement .4s cubic-bezier(.2, .8, .3, 1) both; }
.se-deploie-doux { animation: deploiement-doux .44s ease-out both; }
@media (prefers-reduced-motion: reduce) {
  .choix-lieu, .choix-lieu.replie, .choix-lieu.replie .choix-mode.choisi { transition: none; }
  .se-deploie, .se-deploie-doux { animation: none; }
}
.choix-ico {
  width: var(--hc); height: var(--hc); flex: none; border-radius: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--lien-doux); color: var(--lien);
}
.choix-mode:first-child .choix-ico { background-image: var(--degrade-diag); color: #fff; }
.choix-ico svg { width: 22px; height: 22px; }
.choix-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.choix-txt b { font-size: 1rem; font-weight: 800; letter-spacing: -.01em; }
.choix-txt small { color: var(--sourdine); font-size: .82rem; line-height: 1.3; }

/* ---- Points de départ pris en compte par le mi-chemin ---- */
.departs { margin-bottom: 10px; }
.depart-ligne {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 14px; margin-bottom: 6px;
  background: var(--carte-2); border: 1px solid var(--trait); border-radius: 13px;
}
.depart-txt { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.depart-txt b { font-size: .92rem; flex: none; }
/* Le libellé peut être une adresse complète : il se coupe plutôt que de pousser le nom */
.depart-txt small { color: var(--sourdine); font-size: .8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.depart-ligne.ok .depart-txt small { color: var(--ok); }
/* Conteneur du champ + ses suggestions : c'est lui qui sert de repère à la liste,
   sinon elle se placerait sous le formulaire entier, donc sous le bouton. */
.depart-saisie { position: relative; margin-bottom: 10px; }
.depart-saisie > .champ { margin-bottom: 0; }
.depart-ligne .depart-saisie .champ { margin: 0; min-height: var(--hc); padding: 0 14px; font-size: .9rem; }
.depart-note { margin: 6px 2px 0; }
/* « Revenir au point calculé » vit à l'intérieur de cette note : sans cela il
   arriverait à sa taille de bouton et écraserait la phrase qui le porte. */
.depart-note .btn-lien { font-size: inherit; vertical-align: baseline; }

/* Liste de suggestions : la même dans le profil et dans une ligne de départ. */
.depart-resultats {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 30;
  max-height: 220px; overflow-y: auto;
  background: var(--carte); border: 1px solid var(--trait); border-radius: 13px; box-shadow: var(--ombre);
}
.depart-resultat {
  display: block; width: 100%; text-align: left;
  padding: 10px 12px; border: 0; border-bottom: 1px solid var(--trait);
  background: none; color: inherit; font-family: inherit; font-size: .85rem; line-height: 1.3; cursor: pointer;
}
.depart-resultat:last-child { border-bottom: 0; }
.depart-resultat:active { background: var(--carte-2); }

/* Point de départ posé sur la carte : discret, le repère du rendez-vous doit rester
   le seul élément qui attire l'oeil. */
.pastille-depart {
  display: block; width: 14px; height: 14px; border-radius: 50%;
  background: var(--lien); border: 2px solid #fff; box-shadow: 0 2px 6px rgba(0, 0, 0, .35);
}

.vedette {
  display: block; padding: 16px; border: 1.5px solid transparent; border-radius: 20px;
  background: linear-gradient(var(--carte), var(--carte)) padding-box, var(--degrade) border-box;
}
.chip { display: inline-flex; align-items: center; gap: 5px; font-size: .78rem; font-weight: 700; padding: 3px 10px; border-radius: 999px; background: var(--accent-doux); color: var(--accent-fonce); }
.vedette .v-haut { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.vedette h3 { font-family: 'Bricolage Grotesque', sans-serif; font-weight: 800; font-size: 1.3rem; margin: 8px 0; letter-spacing: -.01em; }
.vedette .v-ligne { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; min-height: 26px; flex-wrap: wrap; }
.vedette .v-ligne .avatar { width: 26px; height: 26px; font-size: .68rem; border: 2px solid var(--carte); }
.vedette .v-ligne .avatar + .avatar { margin-left: -12px; }
.vedette .v-actions { display: flex; gap: 8px; }
.vedette .v-actions .btn { margin: 0; min-height: var(--hc); }
.vedette .v-actions .v-plus { flex: none; width: var(--hc); padding: 0; font-size: 1.3rem; }
.v-suppr { color: var(--danger); }
.v-suppr svg { width: 20px; height: 20px; }
.v-suppr.confirme { background: var(--danger); border-color: var(--danger); color: #fff; }

/* ---- Divers ---- */
.hero { text-align: center; padding: 48px 8px 24px; }
/* Le verrou : les trois signes en couleur. Deux fichiers, un par theme, parce
   qu'un SVG pose en <img> ne peut pas heriter des couleurs de la page. Le nom de
   l'application est porte par le titre juste en dessous, donc les images sont
   decoratives et masquees aux lecteurs d'ecran. */
/* Le code d'invitation : espace entre les lettres et chiffres non ambigus, pour
   qu'il se lise et se recopie sans erreur. Le O et le 0 ne peuvent pas se
   confondre puisque code_groupe() n'emploie ni o, ni i, ni l, ni 0, ni 1. */
.code-recu { margin: 10px 0 0; font-size: .9rem; color: var(--sourdine); }
.code-recu b { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 1.05rem; letter-spacing: .16em; color: var(--encre); }
.champ.champ-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.25rem; font-weight: 700; letter-spacing: .18em; text-align: center;
  text-transform: uppercase;
}

/* Le bouton « quitter » passe au rouge une fois armé, comme la corbeille des
   contacts : la couleur dit que le second appui, lui, agira. */
.btn-quitter { color: var(--danger); border-color: var(--trait); }
.btn-quitter.confirme { background: var(--danger); border-color: var(--danger); color: #fff; }

/* La marque est carrée depuis la refonte du logo. Sa taille était fixée ici à
   min(230px, 62vw) — hérité du logo horizontal, qui alignait trois pictogrammes.
   Ce CSS écrasait silencieusement les attributs width/height du <img> : la
   marque s'affichait à 230 px de côté alors que le balisage annonçait 84.
   MESURÉ dans le navigateur avant correction : 230 × 230.
   Les deux disent désormais la même chose. Les attributs restent — ils
   réservent la place avant que l'image n'arrive, et évitent le sursaut. */
.verrou { display: inline-block; line-height: 0; }
.verrou img { display: block; width: 96px; height: 96px; }
.verrou .v-sombre { display: none; }
[data-theme=dark] .verrou .v-clair { display: none; }
[data-theme=dark] .verrou .v-sombre { display: block; }
.hero h1 { font-size: 2.1rem; margin: 14px 0 4px; font-weight: 800; }
.copie-bloc { display: flex; gap: 8px; }
.copie-bloc input { flex: 1; }
.sep { display: flex; align-items: center; gap: 12px; color: var(--sourdine); font-size: .78rem; font-weight: 600; margin: 14px 0; }
.sep::before, .sep::after { content: ""; flex: 1; height: 1px; background: var(--trait); }
.switch { position: relative; width: 50px; height: 30px; flex: none; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch i { position: absolute; inset: 0; border-radius: 999px; background: rgba(21, 36, 32, .16); transition: .2s; }
[data-theme=dark] .switch i { background: rgba(236, 242, 238, .2); }
.switch i::after { content: ""; position: absolute; top: 3px; left: 3px; width: 24px; height: 24px; border-radius: 50%; background: #fff; box-shadow: 0 2px 5px rgba(21, 36, 32, .3); transition: .2s; }
.switch input:checked + i { background-color: #2E6BFF; background-image: var(--degrade); }
.switch input:checked + i::after { left: 23px; }
.switch input:focus-visible + i { outline: 2px solid var(--encre); outline-offset: 2px; }

/* ---- Sélecteur segmenté : plusieurs choix exclusifs sur une seule ligne ----
   Bâti sur de vrais boutons radio (masqués) : le clavier, le lecteur d'écran et
   la navigation par flèches fonctionnent sans une ligne de JavaScript. */
.segmente { display: flex; flex: 1 1 auto; max-width: 268px; gap: 2px; padding: 3px; border-radius: 999px; background: rgba(21, 36, 32, .08); }
[data-theme=dark] .segmente { background: rgba(236, 242, 238, .12); }
.segmente label { flex: 1; display: block; position: relative; }
.segmente input { position: absolute; opacity: 0; width: 0; height: 0; }
.segmente span {
  display: block; padding: 8px 6px; border-radius: 999px; text-align: center;
  font-size: .85rem; font-weight: 600; color: var(--sourdine); white-space: nowrap;
  transition: color .18s, background-color .18s;
}
.segmente input:checked + span {
  background-color: #2E6BFF; background-image: var(--degrade); color: #fff;
  box-shadow: 0 2px 8px rgba(46, 107, 255, .32);
}
.segmente input:focus-visible + span { outline: 2px solid var(--encre); outline-offset: 2px; }

/* ---- Transition néon entre les onglets ---- */
@property --lueur-x {
  syntax: "<length>";
  inherits: false;
  initial-value: -300px;
}
.bottomnav { overflow: visible; }
.bottomnav .fab { position: relative; z-index: 5; } /* la balle passe derrière le + */
main.content.page-sort { animation: pageSort .2s ease forwards; }
main.content.page-entre { animation: pageEntre .32s cubic-bezier(.2, .8, .3, 1); }
@keyframes pageSort { to { opacity: 0; transform: translateY(12px) scale(.995); } }
@keyframes pageEntre { from { opacity: 0; transform: translateY(16px) scale(.995); } }
.bottomnav a.actif .dot {
  box-shadow: 0 0 7px 2px rgba(46, 107, 255, .85), 0 0 16px 5px rgba(255, 90, 78, .4);
  animation: neonDot .4s ease;
}
@keyframes neonDot { from { transform: scale(.4); } 60% { transform: scale(1.35); } to { transform: scale(1); } }

/* ---- Correctif « liseré de dégradé » -------------------------------------
   Un dégradé CSS mesure par défaut exactement la boîte de l'élément ET se répète.
   Dès que la largeur tombe sur un nombre fractionnaire (zoom navigateur, écrans
   à densité non entière, transform: scale), la dernière colonne de pixels
   déborde d'un poil sur la répétition suivante et réaffiche la couleur de DÉPART
   du dégradé : d'où le trait rouge collé au bord droit des boutons bleus.
   On étire donc le dégradé d'1 px de chaque côté et on interdit la répétition. */
.btn-primaire, .fab, .vedette, .bottomnav,
.vote-ligne.premier .vote-rang, .pin-num i, .pin3d .goutte, .jauge > i,
.switch input:checked + i, .segmente input:checked + span, .fleche-moi::after {
  background-repeat: no-repeat;
  background-size: calc(100% + 2px) calc(100% + 2px);
  background-position: -1px -1px;
}
/* règle séparée : un `:has()` non reconnu invaliderait toute la liste ci-dessus */
.puce-coche:has(input:checked) {
  background-repeat: no-repeat;
  background-size: calc(100% + 2px) calc(100% + 2px);
  background-position: -1px -1px;
}

@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }

/* ---- Pages légales ----
   Un tableau de traitements se lit mal en colonnes serrées sur un téléphone :
   on le laisse défiler horizontalement plutôt que d'écraser les mots. */
.tab-legal { width: 100%; border-collapse: collapse; margin: 10px 0 4px; font-size: .8rem; }
.tab-legal th, .tab-legal td { text-align: left; vertical-align: top; padding: 7px 8px; border-bottom: 1px solid var(--trait); }
.tab-legal th { color: var(--sourdine); font-weight: 700; font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; }
.tab-legal td { color: var(--sourdine); }
.carte h2 { margin: 20px 0 6px; }
.carte ul.muted { padding-left: 20px; }
.carte ul.muted li { margin-bottom: 6px; }

/* La puce d'un participant est un bouton : on hérite du style de .puce mais il
   faut annuler ce que le navigateur impose aux boutons. */
/* color: inherit est indispensable. Un <button> ne reçoit pas la couleur du texte
   de la page : le navigateur lui impose la sienne, presque noire. Sur le thème
   sombre, le nom du participant se retrouvait écrit en noir sur noir. */
.puce-membre { font-family: inherit; font-size: inherit; color: inherit; cursor: pointer; text-align: left; }
/* Sa propre pastille : elle sert encore à déplier la pile, mais n'ouvre pas de
   fiche. Marquée par data-moi et non par disabled, un bouton désactivé n'émettant
   aucun clic — le premier appui n'aurait alors rien déplié. */
.puce-membre[data-moi] { cursor: default; }
.puce-membre:not([data-moi]):active { transform: scale(.97); transition: transform .12s; }

/* ---- Participants : pile d'avatars qui se déplie ----
   Repliée, la liste n'est qu'une rangée d'avatars, chacun masquant la moitié du
   précédent. Un appui déplie les pastilles et fait apparaître les noms.
   La marge négative est portée par TOUTES les pastilles, première comprise, et
   compensée par le retrait du conteneur. Sans cela, après un retour à la ligne, la
   première pastille de chaque nouvelle ligne partirait trop à gauche. */
.puces-pile { column-gap: 0; padding-left: var(--emboite); transition: padding-left .32s cubic-bezier(.2, .8, .3, 1); }
.puces-pile .puce {
  margin-left: calc(var(--emboite) * -1);
  transition: margin-left .32s cubic-bezier(.2, .8, .3, 1), padding-right .32s cubic-bezier(.2, .8, .3, 1);
}
/* Le nom et le statut vivent dans ce bloc : c'est lui seul que l'animation ouvre
   et referme. On anime max-width et non width, la largeur réelle n'étant pas
   connue à l'avance — même procédé que le bouton de suppression des contacts. */
.puce-texte {
  display: inline-flex; align-items: center; gap: 8px;
  white-space: nowrap; overflow: hidden;
  max-width: 340px; opacity: 1;
  transition: max-width .32s cubic-bezier(.2, .8, .3, 1), opacity .22s .06s, margin-left .32s cubic-bezier(.2, .8, .3, 1);
}

/* Dépliée : la pastille mord 14 px sur sa voisine — soit le remplissage vide de
   son bord arrondi, jamais le texte. D'où les 18 px de remplissage à droite, qui
   laissent respirer la fin du statut. */
.puces-pile.deployee { --emboite: 14px; }
.puces-pile.deployee .puce { padding-right: 18px; }

/* Repliée : la pastille se referme sur son avatar. La marge négative est calculée
   pour que chaque avatar cache exactement la moitié du précédent ; la marge
   négative du texte annule la gouttière de 8 px devenue inutile. */
.puces-pile:not(.deployee) { --emboite: 24px; }
.puces-pile:not(.deployee) .puce { padding-right: 4px; }
.puces-pile:not(.deployee) .puce-texte { max-width: 0; opacity: 0; margin-left: -8px; transition-delay: 0s; }

@media (prefers-reduced-motion: reduce) {
  .puces-pile, .puces-pile .puce, .puce-texte { transition: none; }
}

/* Champ de recherche des amis : compact, il précède immédiatement les puces. */
.chercher-amis { margin-bottom: 10px; }
.chercher-amis .champ { margin: 0; min-height: var(--hc); padding: 0 14px; font-size: .92rem; }

/* ---------- Lien d'évitement (WCAG 2.4.1, niveau A) ----------
   Ajouté le 31/07/2026. Hors de l'écran tant qu'il n'a pas le focus, visible dès
   qu'on l'atteint au clavier. `clip` plutôt que `display:none` ou
   `visibility:hidden` : ces deux-là retireraient l'élément de l'ordre de
   tabulation, et le lien ne serait donc jamais atteignable — c'est l'erreur
   classique sur ce motif. */
.lien-evitement {
  position: absolute; left: -9999px; top: 0; z-index: 1000;
  background: var(--encre); color: var(--bg);
  padding: 12px 18px; border-radius: 0 0 var(--r) 0;
  font-weight: 700; text-decoration: none;
}
.lien-evitement:focus {
  left: 0;
  outline: 3px solid var(--lien); outline-offset: 2px;
}
/* `main` reçoit tabindex="-1" pour pouvoir recevoir le focus au saut : sans lui,
   plusieurs navigateurs déplacent la vue sans déplacer le focus, et la
   navigation au clavier repart du haut de la page. Le contour n'apporte rien
   dans ce cas — on le retire, mais pour ce cas SEULEMENT. */
main.content:focus { outline: none; }

/* ---------- Contour de focus visible (WCAG 2.4.7, niveau AA) ----------
   Ajouté le 31/07/2026. `.champ` avait bien un halo au focus, et les
   interrupteurs un contour, mais les boutons, les liens de réglage et les lignes
   de vote n'avaient rien : au clavier, on ne savait pas où l'on se trouvait.
   `:focus-visible` et non `:focus` : le contour n'apparaît donc pas au clic
   souris, seulement à la navigation clavier. */
/* ---- Liens legaux du pied de page : cibles tactiles ----
   Mesures a 375 px : ces liens faisaient 20 px de haut. Le critere 2.5.8 de
   WCAG 2.2 (« Taille de cible (minimum) », niveau AA — celui que vise la
   directive europeenne sur l'accessibilite) demande 24 px, et son exception
   pour les liens « dans une phrase » ne joue pas ici : ce sont des liens isoles,
   separes par des points mediaux, pas du texte courant.
   inline-block, sans quoi le remplissage vertical d'un element en ligne
   n'agrandit pas la zone cliquable. La hauteur passe a 32 px sans deplacer quoi
   que ce soit, le remplissage debordant sur l'interligne existant. */
.liens-legaux a { display: inline-block; padding: 6px 4px; }

a:focus-visible,
button:focus-visible,
summary:focus-visible,
[role="button"]:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 3px solid var(--lien);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ---- Le bloc sous la carte (page d'une sortie) ----
   Participants, discussion et champ de message flottaient directement sur le fond
   animé de l'application — les halos colorés de .fond-vivant. Le dégradé passait
   donc derrière le texte, et la page semblait s'arrêter au bas de la carte pour
   laisser place à un vide violet. Ils tiennent désormais sur un fond plein, de la
   couleur des cartes, comme le bloc de la carte au-dessus.

   Marges latérales négatives : elles annulent le retrait de 16 px de .content, si
   bien que le fond va d'un bord à l'autre de l'écran. Le padding les rend
   aussitôt, pour que le contenu, lui, garde son retrait.

   flex: 1 et la marge basse négative : sans eux, le fond s'arrête à la dernière
   ligne de contenu et le vide coloré revient en dessous dès que la discussion est
   courte. La marge annule le padding bas de .content — celui qui réserve la place
   de la barre de navigation — pour que le fond descende jusqu'en bas de l'écran.

   .app.immersive : posé par $carteImmersive, que seule sortie.php définit. Le
   passage de .content en colonne flexible ne touche donc aucune autre page. */
.app.immersive .content { display: flex; flex-direction: column; }
.sous-carte {
  /* `flex: 1 0 auto` et NON `flex: 1`.
     `flex: 1` vaut `1 1 0%` : la base à zéro fait que la hauteur réelle du contenu
     n'entre pas dans le calcul, et le bloc est comprimé dans la place qui reste
     sous la carte. Mesuré : la page ne défilait plus DU TOUT — hauteur du
     document exactement égale à celle de la fenêtre — donc participants et
     discussion étaient écrasés au lieu de continuer plus bas.
     Avec la base sur le contenu (`auto`) et l'interdiction de rétrécir (`0`), le
     bloc fait au moins sa hauteur naturelle, dépasse quand il le faut — la page
     défile de nouveau — et s'étire quand il reste de la place, ce qui est le but
     du fond plein. */
  flex: 1 0 auto;
  margin: 0 -16px calc(-110px - env(safe-area-inset-bottom));
  padding: 4px 16px calc(110px + env(safe-area-inset-bottom));
  background: var(--carte);
}
/* La discussion garde son propre fond, plus sourd : sur .sous-carte elle aurait
   exactement la même couleur et la zone de messages disparaîtrait dans la page.
   .chat utilise déjà var(--bg) — on n'y touche pas, on le note pour la prochaine
   fois où l'on voudra changer l'un des deux. */


/* ---- Signalement d'un message ----
   Google Play refuse une application qui diffuse des contenus écrits par ses
   utilisateurs sans moyen de les signaler DEPUIS l'application. Le bouton doit
   donc être visible — un signalement caché derrière un appui long ne compte pas,
   et surtout ne se trouve pas au moment où l'on en a besoin.
   Il reste discret : opacité faible au repos, pleine au survol et au focus. Une
   cible de 30 px, au-dessus du minimum tactile confortable pour une action
   aussi rare. */
.signaler {
  position: absolute; top: 2px; right: 4px;
  width: 30px; height: 30px; padding: 0; border: 0; background: none;
  display: grid; place-items: center; cursor: pointer;
  color: var(--sourdine); opacity: .38; transition: opacity .15s, color .15s;
  -webkit-tap-highlight-color: transparent;
}
.signaler svg { width: 15px; height: 15px; display: block; }
.signaler:hover, .signaler:focus-visible { opacity: 1; color: var(--danger); }
.signaler:focus-visible { outline: 2px solid var(--lien); outline-offset: -2px; border-radius: 8px; }
.signaler[disabled] { opacity: .2; cursor: default; }
/* La bulle réserve la place du bouton, sinon un message court passe dessous. */
.bulle:not(.moi) { padding-right: 34px; }
