/* ==========================================================================
   LogiScan — feuille de style unique du site public.

   Elle applique la charte de marque (CLAUDE.md, section « Marque LogiScan ») et
   n'introduit aucune couleur en dehors des sept teintes admises : les cinq
   jetons de marque, plus les deux couleurs d'état. Toute autre nuance est une
   variation d'opacité de l'une d'elles, écrite en rgba().

   Aucune ressource distante : la typographie est servie par LogiScan
   (/fonts/…), jamais par Google Fonts. C'est un choix de confidentialité —
   charger une police chez un tiers ferait connaître à ce tiers l'adresse IP de
   chaque visiteur, pour une police que l'on peut tout aussi bien héberger.

   Organisation : jetons, socle, mise en page, en-tête et pied, puis les
   composants dans l'ordre où le visiteur les rencontre.
   ========================================================================== */

/* --- Typographie de marque, servie localement ---------------------------- */

/* Un seul fichier par sous-ensemble : Plus Jakarta Sans est une police
   variable, dont l'axe de graisse couvre à lui seul les trois graisses de la
   charte (400, 600, 800). */

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("/fonts/plus-jakarta-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("/fonts/plus-jakarta-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- Jetons -------------------------------------------------------------- */

:root {
  --nuit: #0F172A;
  --vert: #22C55E;
  --cyan: #06B6D4;
  --clair: #F1F5F9;
  --ardoise: #64748B;
  --ambre: #F59E0B;
  --rouge: #EF4444;

  /* Variations d'opacité, seules libertés admises par la charte. */
  --trait-clair: rgba(100, 116, 139, 0.22);
  --trait-sombre: rgba(241, 245, 249, 0.14);
  --voile-sombre: rgba(241, 245, 249, 0.05);
  --texte-sombre-attenue: rgba(241, 245, 249, 0.72);
  --ombre-douce: 0 1px 2px rgba(15, 23, 42, 0.06), 0 10px 30px rgba(15, 23, 42, 0.05);
  --ombre-portee: 0 2px 6px rgba(15, 23, 42, 0.08), 0 18px 48px rgba(15, 23, 42, 0.10);

  --police: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --rayon: 14px;
  --rayon-petit: 9px;
  --largeur: 1140px;
  --gouttiere: 1.25rem;
}

/* --- Socle --------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--police);
  font-weight: 400;
  font-size: 1rem;
  line-height: 1.65;
  color: var(--nuit);
  background: var(--clair);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 {
  margin: 0 0 0.5em;
  line-height: 1.2;
  font-weight: 800;
  letter-spacing: -0.02em;
}

h1 { font-size: clamp(2rem, 5.2vw, 3.25rem); }
h2 { font-size: clamp(1.5rem, 3.4vw, 2.125rem); }
h3 { font-size: 1.1875rem; font-weight: 700; letter-spacing: -0.01em; }
h4 { font-size: 1rem; font-weight: 600; letter-spacing: 0; }

p { margin: 0 0 1rem; }
p:last-child { margin-bottom: 0; }

a {
  color: var(--nuit);
  text-decoration-color: rgba(15, 23, 42, 0.3);
  text-underline-offset: 3px;
}

a:hover { text-decoration-color: var(--vert); }

img, svg { max-width: 100%; }

ul, ol { margin: 0 0 1rem; padding-left: 1.25rem; }
li { margin-bottom: 0.35rem; }

:focus-visible {
  outline: 3px solid var(--cyan);
  outline-offset: 2px;
  border-radius: 4px;
}

.invisible {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Mise en page -------------------------------------------------------- */

.conteneur {
  width: 100%;
  max-width: var(--largeur);
  margin-inline: auto;
  padding-inline: var(--gouttiere);
}

.conteneur--etroit { max-width: 780px; }

/* Le tableau de bord, et lui seul, prend la largeur de l'écran.

   1140 px conviennent à un texte suivi — au-delà, une ligne devient pénible à
   lire. Un tableau de bord ne se lit pas ainsi : ce sont des tuiles côte à côte,
   et les brider à 1140 px sur un écran de 1920 revenait à en écraser quatre dans
   mille pixels. Les intitulés se coupaient alors en trois lignes, et le Résumé
   débordait de l'écran qu'il devait précisément tenir. */
.conteneur--large { max-width: 1600px; }

.section { padding-block: clamp(2.5rem, 6vw, 4.5rem); }
.section--serree { padding-block: clamp(1.75rem, 4vw, 2.75rem); }
/* Le tableau de bord commence haut : son bandeau porte déjà l'adresse en gros. */
.conteneur--large.section--serree { padding-block: 1.25rem 1.75rem; }

.section--sombre {
  background: var(--nuit);
  color: var(--clair);
}

.section--sombre a { color: var(--clair); }

.grille {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: 1fr;
}

@media (min-width: 720px) {
  .grille--deux { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grille--trois { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 980px) {
  .grille--quatre { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.surtitre {
  display: block;
  margin-bottom: 0.75rem;
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cyan);
}

.chapeau {
  font-size: clamp(1.0625rem, 2.2vw, 1.1875rem);
  color: var(--ardoise);
  max-width: 62ch;
}

.section--sombre .chapeau { color: var(--texte-sombre-attenue); }

.note {
  font-size: 0.875rem;
  color: var(--ardoise);
}

/* Sur une surface sombre, le gris ardoise d'une note tombe à un contraste
   illisible : c'est le même traitement que « .chapeau » reçoit deux lignes plus
   haut, et pour la même raison. Sans cette règle, une note posée dans un bandeau
   de titre s'affiche parfaitement — en gris foncé sur bleu nuit. */
.section--sombre .note { color: var(--texte-sombre-attenue); }

/* --- En-tête ------------------------------------------------------------- */

.entete {
  position: sticky;
  top: 0;
  z-index: 400; /* au-dessus des cartes Leaflet, qui montent à 400 */
  background: var(--nuit);
  border-bottom: 1px solid var(--trait-sombre);
}

/* **Sur un téléphone, l'en-tête se laisse dépasser.** Signalé en service : il restait
   ancré en haut et ne s'effaçait jamais, quoi qu'on fasse défiler.

   Ce n'est pas la même barre selon la largeur. Sur un écran large, c'est **une ligne**
   de 68 pixels : la garder sous la main ne coûte presque rien et met « Nouvelle
   analyse » à portée. Sur un téléphone, les six liens passent à la ligne et la barre
   fait trois rangées — près de trois cents pixels, soit un bon quart de l'écran,
   retranchés en permanence à ce qu'on est venu lire. Une analyse se lit en faisant
   défiler ; lui voler un quart de la fenêtre à chaque écran est cher payé pour un
   bouton qu'on atteint en remontant, geste que personne n'a besoin d'apprendre.

   C'est le raisonnement du bandeau d'installation : ce qui flotte doit gagner sa
   place, et se juge à ce qu'il masque. */
@media (max-width: 720px) {
  .entete { position: static; }
}

.entete__barre {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 68px;
  padding-block: 0.75rem;
}

.entete__logo { display: inline-flex; align-items: center; }
.entete__logo img { height: 40px; width: auto; display: block; }

/* La barre de navigation se lit en deux temps : d'abord les pages qui expliquent
   le produit, puis l'espace personnel. Un écart uniforme entre les six liens les
   donnait à lire comme une liste plate, où plus rien ne se distinguait.

   L'écart est donc resserré **à l'intérieur** d'un groupe et élargi **entre** les
   groupes : c'est la loi de proximité, et elle fait à elle seule l'essentiel du
   travail. Le filet vertical ne fait que confirmer ce que l'espacement dit déjà. */
.entete__navigation {
  display: flex;
  align-items: center;
  gap: 0.35rem 1.5rem;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.entete__groupe {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
}

/* Le filet n'apparaît que là où la barre tient sur une seule ligne. En dessous,
   les groupes passent à la ligne : un trait vertical se retrouverait en tête de
   rangée, à séparer un lien de rien du tout. L'écart, lui, continue de grouper —
   et le repère de page courante, qui est l'essentiel, ne dépend pas de la
   largeur. */
@media (min-width: 1040px) {
  .entete__groupe--compte {
    padding-left: 1.5rem;
    border-left: 1px solid var(--trait-sombre);
  }
}

.entete__lien {
  font-size: 0.9375rem;
  /* 500 et non 600 : quand tout est en demi-gras, il ne reste aucune graisse
     disponible pour distinguer la page courante. */
  font-weight: 500;
  color: var(--texte-sombre-attenue);
  text-decoration: none;
  padding-block: 0.25rem;
  /* Le trait est réservé dès le départ, transparent : sans lui, la page courante
     serait deux pixels plus haute que ses voisines et la barre sauterait à chaque
     changement de page. Même montage que les onglets du tableau de bord. */
  border-bottom: 2px solid transparent;
}

/* Le survol s'éclaircit sans jamais atteindre le blanc plein.

   C'est le défaut que ce lot corrige : survol et page courante partageaient le
   même traitement, si bien que passer la souris sur un lien faisait paraître
   **deux** éléments actifs — et qu'on ne savait plus lequel était la page qu'on
   lisait. */
.entete__lien:hover { color: rgba(241, 245, 249, 0.92); }

.entete__lien[aria-current="page"] {
  color: var(--clair);
  font-weight: 700;
  border-bottom-color: var(--vert);
}

.entete__logo img { height: 40px; }

/* --- Pied de page -------------------------------------------------------- */

.pied {
  background: var(--nuit);
  color: var(--texte-sombre-attenue);
  padding-block: clamp(2.5rem, 5vw, 3.5rem);
  font-size: 0.9375rem;
}

.pied__haut {
  display: grid;
  gap: 2rem;
  grid-template-columns: 1fr;
  padding-bottom: 2rem;
  border-bottom: 1px solid var(--trait-sombre);
}

@media (min-width: 760px) {
  .pied__haut { grid-template-columns: 1.4fr 1fr 1fr; }
}

.pied__logo img { height: 34px; width: auto; display: block; }

.pied__baseline {
  margin-top: 1rem;
  color: var(--cyan);
  font-weight: 600;
}

.pied h4 {
  color: var(--clair);
  margin-bottom: 0.75rem;
}

.pied ul { list-style: none; margin: 0; padding: 0; }
.pied li { margin-bottom: 0.5rem; }

.pied a {
  color: var(--texte-sombre-attenue);
  text-decoration: none;
}

.pied a:hover { color: var(--vert); }

/* Le lien de soutien. Même traitement visuel que les autres liens du pied de
   page — même couleur, même absence de soulignement, même vert au survol : ce
   n'est pas un bouton, et il ne doit pas en avoir l'air. Rien ne clignote, rien
   ne s'anime, rien ne change de couleur pour attirer l'œil.

   La phrase et le lien sont dans un même paragraphe, et le retour à la ligne se
   fait naturellement sur un écran étroit : c'est du texte, il se comporte comme
   du texte. */
.pied__soutien {
  padding-top: 1.5rem;
  font-size: 0.875rem;
  color: rgba(241, 245, 249, 0.55);
}

.pied__soutien-lien {
  /* « inline-flex » pour que la tasse et la flèche restent solidaires du texte
     du lien, sans jamais l'empêcher de passer à la ligne suivante. */
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  white-space: nowrap;
}

.pied__soutien-tasse {
  /* La tasse prend la couleur du texte (« currentColor »), donc le vert du
     survol avec lui : une icône qui garderait sa propre teinte serait un accent
     de plus, et la charte n'en admet pas. */
  flex: none;
}

.pied__bas {
  padding-top: 1.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  justify-content: space-between;
  font-size: 0.875rem;
  color: rgba(241, 245, 249, 0.55);
}

/* --- Héro ---------------------------------------------------------------- */

.hero {
  position: relative;
  background: var(--nuit);
  color: var(--clair);
  padding-block: clamp(3rem, 8vw, 5.5rem);
  overflow: hidden;
}

/* Halo de marque : deux teintes de la charte, en très faible opacité. */
.hero::before {
  content: "";
  position: absolute;
  inset: -40% 30% auto -20%;
  height: 130%;
  background:
    radial-gradient(closest-side, rgba(34, 197, 94, 0.16), rgba(34, 197, 94, 0)),
    radial-gradient(closest-side, rgba(6, 182, 212, 0.12), rgba(6, 182, 212, 0));
  pointer-events: none;
}

.hero > * { position: relative; }

.hero__titre { max-width: 20ch; }

.hero__accent { color: var(--vert); }

.hero__chapeau {
  margin-top: 1.25rem;
  font-size: clamp(1.0625rem, 2.4vw, 1.25rem);
  color: var(--texte-sombre-attenue);
  max-width: 58ch;
}

.hero__disposition {
  display: grid;
  gap: clamp(2rem, 5vw, 3rem);
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 940px) {
  .hero__disposition { grid-template-columns: 1fr 1fr; }
}

/* --- Bandeau de réassurance --------------------------------------------- */

.reassurance {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.75rem;
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--trait-sombre);
  list-style: none;
  padding-left: 0;
  font-size: 0.9375rem;
  color: var(--texte-sombre-attenue);
}

.reassurance li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
}

.reassurance li::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--vert);
  flex: none;
}

/* --- Cartes -------------------------------------------------------------- */

.carte {
  background: var(--clair);
  border: 1px solid var(--trait-clair);
  border-radius: var(--rayon);
  padding: clamp(1.15rem, 2.5vw, 1.75rem);
  box-shadow: var(--ombre-douce);
}

.carte--posee { box-shadow: var(--ombre-portee); }

.carte--sombre {
  background: var(--voile-sombre);
  border-color: var(--trait-sombre);
  color: var(--clair);
  box-shadow: none;
}

.carte__titre {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.carte__titre h3 { margin: 0; }

.carte__numero {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  margin-bottom: 0.75rem;
  border-radius: 50%;
  background: rgba(34, 197, 94, 0.16);
  color: var(--nuit);
  font-weight: 800;
  font-size: 0.9375rem;
}

.carte--sombre .carte__numero { color: var(--vert); }

/* --- Boutons ------------------------------------------------------------- */

.bouton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.8rem 1.4rem;
  border: 1px solid transparent;
  border-radius: var(--rayon-petit);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.12s ease, background-color 0.12s ease, border-color 0.12s ease;
}

.bouton:hover { transform: translateY(-1px); }
.bouton:active { transform: translateY(0); }

.bouton--principal {
  background: var(--vert);
  color: var(--nuit);
}

.bouton--principal:hover { background: rgba(34, 197, 94, 0.88); }

.bouton--secondaire {
  background: transparent;
  border-color: var(--trait-clair);
  color: var(--nuit);
}

.bouton--secondaire:hover { border-color: var(--ardoise); }

.section--sombre .bouton--secondaire,
.hero .bouton--secondaire,
.carte--sombre .bouton--secondaire {
  border-color: var(--trait-sombre);
  color: var(--clair);
}

.bouton--discret {
  background: transparent;
  border: none;
  padding-inline: 0.25rem;
  color: var(--ardoise);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.bouton--discret:hover { color: var(--nuit); transform: none; }

.section--sombre .bouton--discret,
.hero .bouton--discret,
.carte--sombre .bouton--discret { color: var(--texte-sombre-attenue); }

.section--sombre .bouton--discret:hover,
.hero .bouton--discret:hover,
.carte--sombre .bouton--discret:hover { color: var(--clair); }

.bouton--large { width: 100%; }

.bouton--danger {
  background: transparent;
  border-color: rgba(239, 68, 68, 0.5);
  color: var(--rouge);
}

.bouton[disabled],
.bouton[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1rem;
  margin-top: 1.5rem;
}

/* Deux actions dans une cellule de tableau : ni marge haute, ni passage à la
   ligne. La colonne « Ouvrir | Supprimer » de l'historique tient sur un rang, et
   c'est le tableau qui défile latéralement, pas le bouton qui se replie. */
.actions--serrees {
  margin-top: 0;
  gap: 0.25rem 0.5rem;
  flex-wrap: nowrap;
}

/* --- Formulaires --------------------------------------------------------- */

.champ { margin-bottom: 1.1rem; }

.champ > label,
.champ__intitule {
  display: block;
  margin-bottom: 0.35rem;
  font-size: 0.9375rem;
  font-weight: 600;
}

.champ__aide {
  margin: 0.35rem 0 0;
  font-size: 0.875rem;
  color: var(--ardoise);
}

/* Le même paragraphe, mais en TÊTE de la fenêtre de mise en relation, où il
   n'explique plus un champ : il introduit le formulaire entier. L'absence de marge
   basse ci-dessus est volontaire — sous un champ, l'aide doit rester collée au champ
   qu'elle commente — et elle devient un défaut ici : « Civilité » venait se coller à
   la phrase, si bien que la première question paraissait en faire partie. */
.mer-modale__corps > .champ__aide:first-child { margin: 0 0 1.35rem; }

/* Un chapeau de page dans un conteneur large : le bloc s'élargit, le texte non.
   Une ligne de 1140 px se lit mal — l'œil perd le début de la suivante. Seuls
   les tableaux, les grilles et les images profitent de la largeur. */
.entete-page { max-width: 780px; }

/* --- Captures de l'extension --------------------------------------------

   Ces images sont les visuels **de boutique** : des diapositives de 1280 × 800
   dessinées pour une fiche Chrome ou Firefox, avec de larges marges vides
   au-dessus et au-dessous du contenu. Mesuré : sur 800 pixels de haut, le
   contenu n'en occupe que 444.

   D'où le recadrage à l'affichage plutôt qu'un second jeu d'images : un
   « aspect-ratio » plus large que l'original, le tout en débordement masqué,
   escamote le vide sans toucher au fichier. Recouper les PNG dans
   « static/visuels/ » aurait été plus simple à lire, mais ces fichiers doivent
   rester **octet pour octet** ceux qui sont déposés sur les boutiques — un test
   le vérifie, et deux jeux d'images divergeraient au premier mot changé.

   Le rapport retenu (1280/500) laisse passer le contenu de la capture montrée,
   qui va de 204 à 648 pixels sur 800 — soit 444 de haut. Le resserrer couperait
   une ligne de texte ; l'élargir ferait revenir le vide que ce cadre masque.
   Dans les deux cas, en silence : la page resterait correcte. */

.capture {
  margin: 0;
}

.capture__cadre {
  display: block;
  position: relative;
  aspect-ratio: 1280 / 500;
  overflow: hidden;
  border-radius: 12px;
  border: 1px solid var(--trait-clair);
}

/* **L'image est hors flux, et c'est indispensable.** Laissée dans le flux, elle
   impose sa hauteur naturelle au cadre : « aspect-ratio » n'est qu'un rapport
   souhaité, et le contenu le repousse. Le cadre reprenait alors les 800 pixels
   d'origine, vide compris, et le recadrage n'avait tout simplement pas lieu —
   sans rien signaler, puisque la page restait correcte. */
.capture__cadre img {
  display: block;
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: auto;
  transform: translateY(-50%);
}

.capture__legende {
  margin-top: 0.5rem;
  font-size: 0.85rem;
  color: var(--ardoise);
}

/* Le texte d'un côté, la capture de l'autre.

   **Ne pas rebaptiser ce bloc « duo ».** Ce nom est déjà pris, plus bas, par les
   deux prix côte à côte du tableau de bord — un « display: flex » qui écraserait
   celui-ci en silence puisqu'il vient après. Le défaut ne se voit pas dans la
   feuille de style : il se voit sur la page, où l'illustration repasse sous le
   texte sans que rien n'ait l'air cassé. */
.presentation {
  display: grid;
  gap: 2rem;
  align-items: center;
}

@media (min-width: 900px) {
  .presentation { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); }
}

/* --- Consentements facultatifs -------------------------------------------

   Une seule règle gouverne ce bloc : les deux cases se ressemblent trait pour
   trait. Pas de sélecteur par type de consentement, pas de variante « discrète »,
   pas d'ordre d'affichage qui mettrait la plus intrusive sous le pouce. Rendre
   l'option protectrice moins visible que l'option intrusive serait le dark
   pattern que le projet s'interdit (CLAUDE.md, § Collecte de données
   utilisateur) — et cela se ferait ici, en CSS, bien plus discrètement que dans
   le gabarit.

   Le texte reste en taille et en couleur de texte courant : un libellé de
   consentement écrit en gris clair et en 12 pixels n'est pas lu, et un
   consentement qu'on n'a pas lu n'en est pas un. */

.consentements {
  margin: 0 0 1.1rem;
  padding: 1rem;
  border: 1px solid var(--trait-clair);
  border-radius: 0.6rem;
}

.consentements > legend {
  padding: 0 0.4rem;
  color: var(--ardoise);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.75rem;
}

.consentement {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  margin-top: 0.9rem;
  cursor: pointer;
}

/* La case ne se redessine pas : la case native est déjà accessible au clavier,
   annoncée par les lecteurs d'écran et cochable. Elle est seulement agrandie,
   parce que la cible par défaut est petite au doigt — et une cible trop petite
   fait cocher par accident, ce qui est un consentement qu'on n'a pas voulu. */
.consentement__case {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.15rem;
  accent-color: var(--vert);
}

.consentement__texte {
  font-size: 0.9375rem;
  line-height: 1.5;
}

/* L'historique est une seconde lecture, pas la suite du formulaire : il lui faut
   l'air qui dit « ceci est autre chose ». Sans cette respiration, son titre venait
   se coller sous le bouton d'enregistrement, et l'on ne savait plus si le bouton
   validait le formulaire au-dessus ou la liste au-dessous. */
.consentements__titre {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--trait-clair);
}

.consentements__historique {
  margin-top: 0.75rem;
  font-size: 0.9375rem;
}

.consentements__historique > li {
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.5rem 0;
  border-top: 1px solid var(--trait-clair);
}

.consentement__etat {
  flex: none;
  min-width: 5.5rem;
  font-weight: 600;
  color: var(--ardoise);
}

.consentement__etat--oui { color: var(--vert); }

input[type="text"],
input[type="url"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="tel"],
select,
textarea {
  width: 100%;
  padding: 0.7rem 0.85rem;
  font-family: inherit;
  font-size: 1rem;
  color: var(--nuit);
  background: var(--clair);
  border: 1px solid var(--trait-clair);
  border-radius: var(--rayon-petit);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

input::placeholder,
textarea::placeholder { color: rgba(100, 116, 139, 0.75); }

input:focus,
select:focus,
textarea:focus {
  border-color: var(--cyan);
  box-shadow: 0 0 0 3px rgba(6, 182, 212, 0.18);
  outline: none;
}

textarea { resize: vertical; min-height: 8rem; line-height: 1.55; }

.champs {
  display: grid;
  gap: 0 1rem;
  grid-template-columns: 1fr;
}

@media (min-width: 620px) {
  .champs--deux { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .champs--trois { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* --- Zone de collage, chemin de premier rang ---------------------------- */

.collage {
  background: var(--voile-sombre);
  border: 1px solid var(--trait-sombre);
  border-radius: var(--rayon);
  padding: clamp(1.15rem, 3vw, 1.6rem);
}

.collage__intitule {
  display: block;
  margin-bottom: 0.6rem;
  font-weight: 700;
  color: var(--clair);
}

.collage textarea {
  min-height: 11rem;
  color: var(--clair);
  background: rgba(15, 23, 42, 0.55);
  border-color: var(--trait-sombre);
}

.collage textarea::placeholder { color: rgba(241, 245, 249, 0.45); }

.collage__pied {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.9rem;
}

.collage__note {
  font-size: 0.875rem;
  color: rgba(241, 245, 249, 0.6);
}

.collage__separateur {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin: 1.5rem 0 1rem;
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(241, 245, 249, 0.45);
}

.collage__separateur::before,
.collage__separateur::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--trait-sombre);
}

/* La marge haute sépare le champ d'adresse du « ou » qui le précède. Sans elle, le
   séparateur touchait presque le champ, et les deux se lisaient comme un seul bloc —
   alors que ce sont deux façons distinctes d'entrer une annonce, dont l'une est
   secondaire et le dit. */
.collage--secondaire { padding-top: 0; margin-top: 1.35rem; border: none; background: none; }

.collage--secondaire input {
  color: var(--clair);
  background: rgba(15, 23, 42, 0.55);
  border-color: var(--trait-sombre);
}

.collage--secondaire input::placeholder { color: rgba(241, 245, 249, 0.45); }

.ligne-champ {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.ligne-champ > *:first-child { flex: 1 1 16rem; }

/* --- Citation d'annonce (l'argument produit) ---------------------------- */


/* --- Mode d'emploi en étapes -------------------------------------------- */

.etapes {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: etape;
}

.etapes > li {
  position: relative;
  margin-bottom: 1.25rem;
  padding-left: 3rem;
  counter-increment: etape;
}

.etapes > li::before {
  content: counter(etape);
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: var(--nuit);
  color: var(--clair);
  font-weight: 800;
  font-size: 0.9375rem;
}

.etapes h3 { margin-bottom: 0.25rem; }

/* --- Encadrés d'avertissement et d'information -------------------------- */

.encadre {
  display: flex;
  gap: 0.85rem;
  padding: 1rem 1.15rem;
  border-radius: var(--rayon-petit);
  border: 1px solid var(--trait-clair);
  background: rgba(100, 116, 139, 0.07);
  font-size: 0.9375rem;
}

.encadre__marque {
  flex: none;
  font-weight: 800;
  font-size: 1.1rem;
  line-height: 1.4;
}

.encadre--attention {
  border-color: rgba(245, 158, 11, 0.45);
  background: rgba(245, 158, 11, 0.1);
}

.encadre--attention .encadre__marque { color: var(--ambre); }

.encadre--refus {
  border-color: rgba(239, 68, 68, 0.45);
  background: rgba(239, 68, 68, 0.09);
}

.encadre--refus .encadre__marque { color: var(--rouge); }

.encadre--succes {
  border-color: rgba(34, 197, 94, 0.45);
  background: rgba(34, 197, 94, 0.1);
}

.encadre--succes .encadre__marque { color: var(--vert); }

.encadre p { margin-bottom: 0.35rem; }

/* --- Étiquettes et pastilles -------------------------------------------- */

.etiquette {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0.65rem;
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  border: 1px solid transparent;
}

.etiquette--eleve {
  background: rgba(34, 197, 94, 0.15);
  border-color: rgba(34, 197, 94, 0.4);
  color: var(--nuit);
}

.etiquette--moyen {
  background: rgba(245, 158, 11, 0.15);
  border-color: rgba(245, 158, 11, 0.45);
  color: var(--nuit);
}

.etiquette--faible {
  background: rgba(239, 68, 68, 0.13);
  border-color: rgba(239, 68, 68, 0.45);
  color: var(--nuit);
}

.etiquette--neutre {
  background: rgba(100, 116, 139, 0.14);
  border-color: var(--trait-clair);
  color: var(--ardoise);
}

.etiquette--bientot {
  background: rgba(6, 182, 212, 0.14);
  border-color: rgba(6, 182, 212, 0.4);
  color: var(--nuit);
}

/* Sur fond bleu nuit, le texte de marque de ces étiquettes deviendrait illisible :
   il est écrit en bleu nuit, précisément pour trancher sur les fonds clairs. La
   couleur d'état reste portée par le fond et la bordure — le niveau se lit donc
   toujours —, et le mot, lui, passe au gris clair. Un code couleur ne doit jamais
   se payer d'une perte de lisibilité. */
.section--sombre .etiquette,
.hero .etiquette,
.carte--sombre .etiquette,
.collage .etiquette {
  color: var(--clair);
}

/* Même raison pour les mentions secondaires : le gris ardoise est prévu pour du
   texte sur fond clair, et tombe sous le seuil de contraste sur le bleu nuit. */
.section--sombre .note,
.hero .note,
.carte--sombre .note,
.section--sombre .repartition,
.hero .repartition,
.section--sombre .chiffre__intitule,
.section--sombre .chiffre__detail,
.section--sombre .jauge__sur,
.section--sombre .jauge__reperes,
.section--sombre .faits dt {
  color: var(--texte-sombre-attenue);
}

.section--sombre .indisponible,
.carte--sombre .indisponible {
  color: var(--texte-sombre-attenue);
  border-color: var(--trait-sombre);
}

.pastille {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
  background: var(--ardoise);
}

.pastille--ok { background: var(--vert); }
.pastille--degraded { background: var(--ambre); }
.pastille--down { background: var(--rouge); }
.pastille--not_used { background: rgba(100, 116, 139, 0.45); }

/* --- Onglets du tableau de bord ----------------------------------------- */

.onglets {
  display: flex;
  gap: 0.25rem;
  overflow-x: auto;
  min-width: 0;
  scrollbar-width: thin;
  border-bottom: 1px solid var(--trait-clair);
  margin-bottom: 1.75rem;
  -webkit-overflow-scrolling: touch;
}

.onglets__lien {
  flex: none;
  padding: 0.75rem 1rem;
  border-bottom: 2px solid transparent;
  color: var(--ardoise);
  font-weight: 600;
  font-size: 0.9375rem;
  text-decoration: none;
  white-space: nowrap;
}

.onglets__lien:hover { color: var(--nuit); }

.onglets__lien[aria-current="page"] {
  color: var(--nuit);
  border-bottom-color: var(--vert);
}

/* --- Volets, à l'intérieur d'un onglet -----------------------------------

   Ils ne reprennent **pas** le trait souligné de la barre du dessus : deux
   rangées d'onglets identiques, l'une sous l'autre, ne diraient pas laquelle
   commande l'autre. La pastille est celle des filtres de quartier — même
   vocabulaire visuel, pour une même nature de geste : restreindre ce qu'on
   regarde sans quitter la page. */
.sous-onglets {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 0 0 1.5rem;
}

.sous-onglets__lien {
  padding: .35rem .9rem;
  border: 1px solid var(--trait-clair);
  border-radius: 999px;
  color: var(--nuit);
  font-size: .875rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.sous-onglets__lien:hover { border-color: var(--vert); }

.sous-onglets__lien[aria-current="page"] {
  background: var(--vert);
  border-color: var(--vert);
  color: #FFFFFF;
}

/* --- Jauge du Deal Score ------------------------------------------------- */

.jauge__note {
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
  font-size: clamp(2.75rem, 8vw, 3.75rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
}

.jauge__sur { font-size: 1.25rem; font-weight: 600; color: var(--ardoise); }

.jauge__piste {
  position: relative;
  height: 10px;
  margin: 1rem 0 0.6rem;
  border-radius: 999px;
  background: rgba(100, 116, 139, 0.18);
  overflow: hidden;
}

.jauge__remplissage {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 999px;
  background: var(--ardoise);
}

.jauge--bonne .jauge__remplissage { background: var(--vert); }
.jauge--moyenne .jauge__remplissage { background: var(--ambre); }
.jauge--mauvaise .jauge__remplissage { background: var(--rouge); }

.jauge--bonne .jauge__note { color: var(--vert); }
.jauge--moyenne .jauge__note { color: var(--ambre); }
.jauge--mauvaise .jauge__note { color: var(--rouge); }

.jauge__reperes {
  display: flex;
  justify-content: space-between;
  font-size: 0.75rem;
  color: var(--ardoise);
}

/* --- Chiffres clés ------------------------------------------------------- */

.chiffres {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}

.chiffre {
  padding: 0.9rem 1rem;
  border: 1px solid var(--trait-clair);
  border-radius: var(--rayon-petit);
  margin: 0;
}

.chiffre__intitule {
  display: block;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ardoise);
  margin-bottom: 0.2rem;
}

.chiffre__valeur {
  display: block;
  font-size: 1.375rem;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.chiffre__detail { font-size: 0.8125rem; color: var(--ardoise); }

/* --- Listes de faits ----------------------------------------------------- */

.faits {
  margin: 0;
  display: grid;
  gap: 0.1rem 1.5rem;
  grid-template-columns: 1fr;
}

@media (min-width: 620px) {
  .faits { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.faits > div {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--trait-clair);
}

.faits dt { color: var(--ardoise); font-size: 0.9375rem; }
.faits dd { margin: 0; font-weight: 600; text-align: right; }

/* --- Tableaux ------------------------------------------------------------ */

/* **Un conteneur à défilement doit aussi accepter de rétrécir.** « overflow-x: auto »
   lui donne le droit de faire défiler son contenu ; il ne lui donne pas celui d'être
   plus étroit que ce contenu. Posé dans une grille ou dans un flex, il réclame donc la
   largeur de son tableau et emporte la mise en page avec lui — le tableau défile
   parfaitement, à l'intérieur d'un cadre qui, lui, est sorti de l'écran.

   « min-width: 0 » lève cette réclamation. Les deux lignes vont ensemble : l'une sans
   l'autre ne fait rien. */
.tableau-defilant {
  overflow-x: auto;
  min-width: 0;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

th, td {
  padding: 0.6rem 0.75rem;
  text-align: left;
  border-bottom: 1px solid var(--trait-clair);
  white-space: nowrap;
}

th {
  font-size: 0.8125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ardoise);
}

td.nombre, th.nombre { text-align: right; }

/* --- Donnée indisponible ------------------------------------------------- */

.indisponible {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.85rem 1rem;
  border: 1px dashed var(--trait-clair);
  border-radius: var(--rayon-petit);
  color: var(--ardoise);
  font-size: 0.9375rem;
}

/* --- Classes d'énergie A à G -------------------------------------------- */

.classes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  border: none;
  margin: 0;
  padding: 0;
}

.classes legend {
  padding: 0;
  margin-bottom: 0.5rem;
  font-size: 0.9375rem;
  font-weight: 600;
}

.classe input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.classe span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--trait-clair);
  border-radius: var(--rayon-petit);
  font-weight: 800;
  font-size: 1.0625rem;
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

.classe span:hover { border-color: var(--ardoise); }

.classe input:checked + span {
  background: var(--vert);
  border-color: var(--vert);
  color: var(--nuit);
}

.classe input:focus-visible + span {
  outline: 3px solid var(--cyan);
  outline-offset: 2px;
}

/* --- Progression de l'analyse ------------------------------------------- */

/* La barre d'avancement, et pourquoi elle bouge toute seule.

   Trois pastilles qui s'allument disent l'avancement, mais rien n'y remue entre
   deux rafraîchissements : pendant les secondes où le moteur travaille, la page
   paraît figée — et une page figée se lit comme une page en panne. La largeur
   dit la proportion franchie ; les rayures glissent en continu pour dire que le
   travail se poursuit, y compris quand aucune étape n'a changé d'état. */
.progression__barre {
  height: 8px;
  margin-top: 1.25rem;
  border-radius: 999px;
  background: rgba(100, 116, 139, 0.15);
  overflow: hidden;
}

.progression__jauge {
  display: block;
  height: 100%;
  border-radius: inherit;
  background-color: var(--vert);
  background-image: linear-gradient(
    115deg,
    rgba(255, 255, 255, 0.35) 25%,
    transparent 25%,
    transparent 50%,
    rgba(255, 255, 255, 0.35) 50%,
    rgba(255, 255, 255, 0.35) 75%,
    transparent 75%
  );
  background-size: 1.5rem 1.5rem;
  animation: progression-rayures 1s linear infinite;
  transition: width .4s ease-out;
}

@keyframes progression-rayures {
  from { background-position: 0 0; }
  to { background-position: 1.5rem 0; }
}

/* L'étape en cours bat doucement : c'est là que le travail se fait. */
.progression li[data-etat="en_cours"] .progression__marque {
  border-color: var(--vert);
  animation: progression-battement 1.4s ease-in-out infinite;
}

@keyframes progression-battement {
  0%, 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.45); }
  50% { box-shadow: 0 0 0 6px rgba(34, 197, 94, 0); }
}

/* Qui a demandé moins d'animations n'en reçoit aucune : la barre garde sa
   largeur, qui porte déjà toute l'information. */
@media (prefers-reduced-motion: reduce) {
  .progression__jauge { animation: none; }
  .progression li[data-etat="en_cours"] .progression__marque { animation: none; }
}

.progression { list-style: none; margin: 1.5rem 0 0; padding: 0; }

.progression li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0;
  color: var(--ardoise);
  border-bottom: 1px solid var(--trait-clair);
}

.progression li:last-child { border-bottom: none; }

.progression__marque {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  flex: none;
  border-radius: 50%;
  border: 2px solid var(--trait-clair);
  font-size: 0.8125rem;
  font-weight: 800;
}

.progression li[data-etat="faite"] { color: var(--nuit); }

.progression li[data-etat="faite"] .progression__marque {
  background: var(--vert);
  border-color: var(--vert);
  color: var(--nuit);
}

.progression li[data-etat="en-cours"] { color: var(--nuit); font-weight: 600; }

.progression li[data-etat="en-cours"] .progression__marque {
  border-color: var(--cyan);
  border-top-color: transparent;
  animation: rotation 0.9s linear infinite;
}

@keyframes rotation { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .progression li[data-etat="en-cours"] .progression__marque { animation: none; }
  .bouton:hover { transform: none; }
}

/* --- Emplacement de carte (branché en session 16) ----------------------- */

.emplacement-carte {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 240px;
  padding: 1.5rem;
  text-align: center;
  border: 1px dashed var(--trait-clair);
  border-radius: var(--rayon-petit);
  background:
    repeating-linear-gradient(
      45deg,
      rgba(100, 116, 139, 0.05),
      rgba(100, 116, 139, 0.05) 12px,
      rgba(100, 116, 139, 0.02) 12px,
      rgba(100, 116, 139, 0.02) 24px
    );
  color: var(--ardoise);
  font-size: 0.9375rem;
}

/* --- Carte ---------------------------------------------------------------- */

.carte-plan {
  width: 100%;
  border-radius: var(--rayon-petit);
  border: 1px solid var(--trait-clair);
  background: rgba(100, 116, 139, 0.08);
  overflow: hidden;
  z-index: 0;
}

/* La courbe de l'historique et son infobulle.

   Le SVG arrive dessiné du serveur ; ces règles n'habillent que ce que le survol
   ajoute par-dessus. Sans JavaScript, rien de tout cela n'apparaît et la courbe
   garde ses infobulles natives. */
.graphique__hote { position: relative; }

.graphique__point { transition: r .1s ease-out; cursor: pointer; }

.graphique__bulle {
  position: absolute;
  transform: translate(-50%, calc(-100% - 14px));
  padding: .45rem .7rem;
  border-radius: var(--rayon-petit);
  background: var(--nuit);
  color: var(--clair);
  font-size: .85rem;
  line-height: 1.35;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: var(--ombre-douce);
  z-index: 2;
}

.graphique__bulle-note { color: var(--texte-sombre-attenue); font-size: .8rem; }

/* Les repères porteurs d'un pictogramme.

   Le signe est un caractère, pas une image : rien à télécharger, rien à héberger.
   La pastille blanche le détache du fond — un emoji posé nu sur une photo
   aérienne se perd. */
.repere-carte__hote { background: none; border: 0; }

/* L'épingle : une goutte pointée vers le bas, dont la pointe touche le lieu.
   Le signe, lui, reste droit — une boulangerie penchée à 45° ne se lit plus. */
.repere-carte__epingle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  background: var(--vert);
  border: 2px solid #FFFFFF;
  box-shadow: 0 2px 6px rgba(15, 23, 42, 0.45);
}

.repere-carte__signe {
  transform: rotate(45deg);
  font-size: 17px;
  line-height: 1;
}

/* Le même signe, dans les listes, pour qu'un repère de la carte se retrouve
   d'un coup d'œil dans le texte. */
.repere-signe {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  background: var(--voile-clair, rgba(100, 116, 139, 0.1));
  font-size: 1rem;
}

/* Les boutons de filtre par famille. */
.filtres {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 0 0 1rem;
}

.filtre {
  padding: .3rem .8rem;
  border: 1px solid var(--trait-clair);
  border-radius: 999px;
  background: transparent;
  color: var(--nuit);
  font: inherit;
  font-size: .875rem;
  cursor: pointer;
}

.filtre:hover { border-color: var(--vert); }

.filtre--actif {
  background: var(--vert);
  border-color: var(--vert);
  color: #FFFFFF;
  font-weight: 600;
}

/* La liste à gauche, la carte à droite — et l'une au-dessus de l'autre dès que
   l'écran ne les tient plus côte à côte. */
/* Cette carte-ci occupe toute la largeur de la grille, et il le faut : elle
   contient elle-même deux colonnes — la liste et le plan. Logée dans une demi-
   largeur, chacune tombait sous les 200 px, les noms d'équipements se coupaient
   en trois lignes et le plan devenait un timbre-poste. La classe était posée
   dans le gabarit depuis l'origine ; elle n'avait jamais eu de règle. */
/* Une cellule qui prend toute la largeur de la grille — et qui doit pouvoir
   **rétrécir** sous la largeur de son contenu.

   « min-width » vaut « auto » par défaut dans une grille : la piste s'élargit alors
   au plus large de ses enfants, et un tableau de cinq colonnes pousse toute la carte
   hors de l'écran. Le conteneur à défilement qui l'entoure a beau savoir défiler, il
   ne peut le faire que si on lui laisse la place de se contracter — c'est la leçon du
   § 36, où « overflow-x: auto » sans « min-width: 0 » faisait défiler un tableau
   **à l'intérieur d'un cadre sorti de l'écran**. Mesuré ici sur le volet Délinquance :
   586 px de large dans une fenêtre de 390. */
.carte--large {
  grid-column: 1 / -1;
  min-width: 0;
}

/* La tuile du quartier est elle-même une grille : le texte à gauche, la carte à
   droite **et à partir du haut**, titre compris. Elle commençait sous la barre de
   filtres, ce qui laissait un vide en haut à droite et poussait son bord bas hors
   de l'écran. */
.quartier {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: 1.25rem;
  align-items: start;
}

.quartier__texte { min-width: 0; }

.quartier__corps {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: 1.25rem;
  align-items: start;
}

/* La colonne du plan. Un enfant de grille refuse par défaut de descendre sous la
   largeur de son contenu : sans cette ligne, la carte déborde sa colonne au lieu
   de s'y ajuster. Classe posée dans le gabarit depuis l'origine, elle non plus
   n'avait jamais eu de règle. */
.quartier__carte { min-width: 0; }

.quartier__liste { margin: 0; max-height: 420px; overflow-y: auto; }

.quartier__liste li { display: flex; gap: .6rem; align-items: flex-start; }

@media (max-width: 860px) {
  .quartier,
  .quartier__corps { grid-template-columns: 1fr; }
  .quartier__liste { max-height: none; }
}

/* Les ventes du tableau, teintées selon leur écart à la médiane communale.

   Deux teintes de **signal**, jamais d'accent : elles portent un niveau — cette
   vente s'est faite au-dessus ou au-dessous du marché — et rien d'autre. D'où un
   fond très pâle et un filet à gauche plutôt qu'une ligne peinte : le tableau doit
   rester lisible, et le chiffre reste ce qu'on lit.

   Le signe « ▼ » / « ▲ » double la couleur, pour que l'information survive à une
   impression en noir et blanc comme à un daltonisme. */
.vente--basse { background: rgba(34, 197, 94, 0.07); border-left-color: var(--vert); }
.vente--haute { background: rgba(245, 158, 11, 0.09); border-left-color: var(--ambre); }

.vente__signe { margin-left: .3rem; font-size: .8em; }

/* La référence cadastrale sous l'adresse : une ligne, jamais deux.

   Écrite « Parcelle 59090000BK0424 » et laissée libre de se replier, elle
   occupait deux lignes à elle seule — quatre au total avec une adresse qui se
   coupait —, et un tableau de vingt-cinq ventes devenait interminable. Le mot
   « Parcelle » passe en infobulle, le code reste d'un bloc. */
.vente__parcelle {
  display: block;
  color: var(--ardoise);
  font-size: .75rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: help;
}

/* Des cellules moins hautes : le tableau se parcourt, il ne se lit pas mot à mot. */
.tableau-defilant td { padding-block: .4rem; }

/* L'adresse tient sur **une** ligne, et le tableau défile latéralement s'il le
   faut — c'est ce que sa boîte sait déjà faire. Laissée libre de se replier dans
   une colonne étroite, « 31 ALL SAINT HUBERT » occupait trois lignes, la
   référence cadastrale une quatrième, et vingt-cinq ventes devenaient
   interminables. */
.vente__adresse { white-space: nowrap; }
.vente--basse .vente__signe { color: var(--vert); }
.vente--haute .vente__signe { color: #F59E0B; }

/* --- Les ventes de la commune : cinq colonnes, le reste au dépli -----------

   Le tableau portait neuf colonnes dont on n'en lit que trois ou quatre en
   parcourant. Ce qui reste visible sert à **balayer** ; le reste attend qu'on le
   demande. Une liste de « details » plutôt qu'un tableau, parce qu'une ligne de
   tableau ne se déplie pas sans JavaScript. */
.ventes { display: grid; gap: .15rem; }

.ventes__entete,
.vente__resume {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr) 5rem 7rem 7.5rem;
  align-items: center;
  gap: .75rem;
}

.ventes__entete {
  margin: 0 0 .25rem;
  padding: 0 .6rem;
  color: var(--ardoise);
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.vente {
  border-left: 3px solid var(--trait-clair);
  border-radius: 0 var(--rayon-petit) var(--rayon-petit) 0;
}

.vente__resume {
  position: relative;
  padding: .45rem .6rem;
  cursor: pointer;
  list-style: none;
  font-size: .9375rem;
}

/* Le triangle natif du navigateur occuperait une colonne sans rien apprendre :
   toute la ligne est cliquable, et son fond change au survol pour le dire. */
.vente__resume::-webkit-details-marker { display: none; }
.vente__resume:hover { background: rgba(100, 116, 139, 0.08); }

.vente__date { color: var(--ardoise); font-variant-numeric: tabular-nums; }

/* La pastille des ventes qui ont servi au Deal Score.

   **Un point, et pas une couleur de ligne.** Les lignes portent déjà un fond et un
   liseré qui disent tout autre chose — le prix au m² est-il sous ou sur la médiane du
   secteur. Repeindre celles qui ont compté aurait écrasé cette information sous une
   seconde, et le lecteur n'aurait plus su laquelle des deux il regardait. Le point
   s'ajoute au lieu de remplacer.

   Il porte un « title » pour la souris et un texte invisible pour les lecteurs
   d'écran : une pastille seule ne se lit pas à voix haute, et la couleur ne dit rien à
   qui ne la distingue pas. */
.vente__marque {
  display: inline-block;
  width: .5rem;
  height: .5rem;
  margin-right: .35rem;
  border-radius: 50%;
  background: var(--vert);
  vertical-align: middle;
  flex: none;
}

/* **Dans la marge, pas dans la ligne.** Posée à l'intérieur, la pastille prenait le
   début de la colonne des dates : les lignes retenues voyaient leur date décalée par
   rapport aux autres, et la colonne perdait l'alignement qui permet de la balayer.
   Sortie du flux et poussée dans le blanc de la tuile, elle signale la ligne sans rien
   y déplacer — la date reste à la même abscisse pour les vingt-cinq ventes.

   Le décalage la place dans le rembourrage de la tuile, qui vaut au minimum 1,15 rem :
   elle a la place de s'y poser sans jamais être rognée, y compris sur un écran étroit.

   La pastille de la phrase d'explication, elle, reste dans le texte : c'est le même
   signe, mais il s'y lit au fil des mots. D'où une règle qui ne vise que les lignes —
   le « position: relative » qui l'ancre est posé plus haut, sur « .vente__resume ». */
.vente__resume .vente__marque {
  position: absolute;
  left: -1rem;
  top: 50%;
  transform: translateY(-50%);
  margin: 0;
}

/* Un liseré un peu plus marqué, pour que la ligne se repère en balayant sans que la
   pastille ait à grossir. */
.vente--comparable { border-left-width: 4px; }
.vente__adresse { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vente__distance,
.vente__m2 { font-variant-numeric: tabular-nums; }

/* Dépliée, la vente se détache du reste de la liste.

   Sans contour, le détail flottait entre deux lignes voisines et l'on ne voyait
   plus où finissait la vente qu'on venait d'ouvrir — d'autant que les teintes de
   deux ventes qui se suivent peuvent être les mêmes. Le cadre reprend la couleur
   du filet gauche : c'est la même vente, simplement ouverte. */
.vente[open] {
  border: 1px solid var(--trait-clair);
  border-left-width: 3px;
  border-radius: var(--rayon-petit);
  box-shadow: 0 2px 10px rgba(15, 23, 42, 0.07);
}

.vente--basse[open] { border-color: var(--vert); }
.vente--haute[open] { border-color: var(--ambre); }

.vente__detail {
  padding: .35rem .85rem .85rem;
  border-top: 1px solid var(--trait-clair);
}

.faits--serres { gap: .15rem 1.5rem; font-size: .875rem; }

/* La confrontation : ce que l'annonce disait, ce que la source déclare.

   Deux colonnes plutôt qu'une phrase, parce que le lecteur compare des valeurs et
   non des règles. Elles passent l'une sous l'autre dès que la carte se resserre —
   une valeur coupée en deux lignes ne se compare plus. */
/* Sa propre remise à zéro, plutôt que « liste-nue ». Cette dernière pose
   « .liste-nue > li { display: flex } », de spécificité supérieure à celle de
   « .confrontation__ligne » : la grille des lignes n'était jamais appliquée, et
   trois corrections des largeurs de colonnes ont porté sur des règles inertes. */
.confrontation { list-style: none; margin: .75rem 0 0; padding: 0; }

/* Une grille plutôt qu'une rangée souple : les deux colonnes de valeurs
   s'alignent d'une ligne à l'autre, et l'œil descend une colonne au lieu de
   chercher où commence chaque nombre. */
/* **Les deux colonnes de valeurs ont une largeur fixe, et la colonne qui les
   porte aussi.** C'est la seconde qui manquait : en « auto », elle se
   dimensionnait sur le contenu de chaque ligne, si bien que « maison » et
   « 144,6 kWh/m²/an » ne commençaient pas au même endroit — le bloc entier
   glissait d'une ligne à l'autre, et « ANNONCE » avec lui. Fixer les tracks
   intérieures ne suffisait pas tant que leur conteneur bougeait. */
.confrontation__ligne {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 20rem;
  align-items: center;
  gap: .35rem 1rem;
  padding: .55rem 0;
  border-bottom: 1px solid var(--trait-clair);
}

.confrontation__ligne:last-child { border-bottom: none; }

.confrontation__critere { font-weight: 600; }

/* La précision — « écart admis », « au jour près » — se pose sous l'intitulé :
   elle qualifie la comparaison, elle n'en fait pas partie. */
.confrontation__critere .note { display: block; font-weight: 400; }

/* **Largeurs fixes, et c'est tout l'intérêt.** Avec des colonnes en « auto »,
   chaque ligne se dimensionnait sur son propre contenu : « maison ↔ maison » se
   serrait au milieu quand « 08/03/2026 ↔ 08/03/2026 » s'étalait, et les deux
   flèches ne tombaient pas au même endroit. L'œil ne pouvait plus descendre une
   colonne. Une largeur commune les remet en face les unes des autres. */
.confrontation__valeurs {
  display: grid;
  grid-template-columns: 9rem 1.5rem 9rem;
  align-items: center;
  gap: .1rem .25rem;
  /* Centré dans sa colonne plutôt qu'aligné à droite : l'en-tête « ANNONCE » et
     le nombre qu'il coiffe se répondent, et les deux valeurs s'encadrent
     symétriquement autour de la flèche. */
  text-align: center;
}

@media (max-width: 720px) {
  /* Sur un téléphone, les valeurs passent sous l'intitulé plutôt que de le
     comprimer : vingt rem ne tiennent pas à côté d'un libellé. */
  .confrontation__ligne { grid-template-columns: minmax(0, 1fr); }
  .confrontation__valeurs { justify-content: start; }
}

/* **Trois enfants, trois colonnes, et rien à deviner.**

   La version précédente mettait les deux blocs de valeurs en « display: contents »
   et comptait sur « grid-row » pour empiler l'en-tête et le nombre. La grille
   recevait alors **cinq** éléments pour trois colonnes, et les plaçait au fil de
   l'auto-placement : les largeurs fixes n'y pouvaient rien, chaque ligne se
   composait différemment de la précédente. C'était la vraie cause du désalignement
   que deux corrections n'avaient pas résolu.

   Chaque bloc de valeurs est désormais une colonne à part entière, qui empile son
   en-tête et son nombre pour son propre compte. */
.confrontation__valeur {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .1rem;
  min-width: 0;
}

.confrontation__source {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ardoise);
}

/* Une taille qui laisse « 144,6 kWh/m²/an » tenir sur une ligne dans ses neuf
   rem : une valeur coupée en deux ne se compare plus à celle d'en face. */
.confrontation__valeurs strong { font-size: 0.9375rem; }

/* **« Trouvé » est vert, toujours.** C'est ce que LogiScan a rapporté des
   sources publiques, par opposition à ce que l'annonce affirmait : la couleur
   dit d'où vient le chiffre, et non s'il est bon. */
.confrontation__valeur--trouve .confrontation__source,
.confrontation__valeur--trouve strong { color: var(--vert); }

.confrontation__fleche { color: var(--ardoise); align-self: center; }

/* Un écart admis se signale sans crier : l'ambre porte un niveau, jamais un
   refus — la fiche a bien été retenue, elle l'a été de justesse. */
.confrontation__ligne--ecart .confrontation__valeur--trouve strong { color: var(--ambre); }

.repere-carte__note { margin-top: .15rem; color: var(--ardoise); font-size: .85em; }

/* Le lien de l'infobulle. Il ouvre un service tiers : il se distingue donc du
   texte de la carte au lieu de s'y fondre, pour qu'un clic ne surprenne pas. */
.repere-carte__lien {
  display: inline-block;
  margin-top: .4rem;
  color: var(--vert);
  font-size: .85em;
  font-weight: 600;
}

/* La légende des pictogrammes, sous une carte de quartier. */
.legende-carte {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem .9rem;
  margin: .75rem 0 0;
  padding: 0;
  list-style: none;
}

.legende-carte li { display: flex; align-items: center; gap: .35rem; font-size: .9rem; }

/* La vue aérienne assemblée par le serveur : une image, pas une carte. Elle
   s'affiche avant que la bibliothèque de cartographie soit chargée, et reste
   seule visible pour un navigateur sans script. */
.vue-aerienne { margin: 0 0 1rem; }

.vue-aerienne img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--rayon-petit);
  border: 1px solid var(--trait-clair);
}

.vue-aerienne figcaption { margin-top: .4rem; }

/* Leaflet pose ses propres surfaces : on les ramène dans la charte plutôt que
   de laisser son gris et sa police par défaut trancher avec le reste. */
.leaflet-container { font-family: var(--police); background: rgba(100, 116, 139, 0.1); }

.leaflet-control-layers,
.leaflet-bar a,
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
  background: var(--clair);
  color: var(--nuit);
  border-color: var(--trait-clair);
}

.leaflet-bar a:hover { background: rgba(100, 116, 139, 0.12); }

.leaflet-control-attribution {
  background: rgba(241, 245, 249, 0.85) !important;
  color: var(--ardoise);
  font-size: 0.6875rem;
}

.leaflet-control-attribution a { color: var(--ardoise); }

/* --- Tableau de bord : la disposition en colonnes ------------------------- */

.bord {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 1000px) {
  /* La carte tient la moitié gauche, les deux colonnes de fiches la droite :
     c'est la disposition des maquettes, et elle a une raison — on regarde
     d'abord où est le bien, puis ce qu'on en dit. */
  .bord--trois { grid-template-columns: 1.35fr 1fr 1fr; }
  .bord--deux { grid-template-columns: 1.35fr 1fr; }
}

.bord__carte { min-width: 0; }
.bord__carte .carte-plan { height: 420px; }

/* --- Sélecteur d'adresses -------------------------------------------------- */

.adresses { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: .5rem; }

.adresse {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin: 0;
  border: 1px solid var(--trait-clair);
  border-radius: var(--rayon-petit);
  background: var(--clair);
  transition: border-color .12s ease, box-shadow .12s ease;
}

.adresse:hover { border-color: var(--ardoise); }

.adresse--active {
  border-color: var(--vert);
  box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.18);
}

/* Quand une adresse se détache vraiment, elle se voit — un cran, pas deux :
   elle reste une proposition parmi d'autres, et les autres restent lisibles. */
.adresse--dominante .adresse__libelle { font-size: 1.0625rem; font-weight: 800; }

.adresse__choix {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .5rem 1rem;
  padding: .7rem .9rem;
  text-decoration: none;
  color: inherit;
  min-width: 0;
}

.adresse__libelle { font-weight: 600; }

.adresse__mesures { display: flex; align-items: center; gap: .75rem; flex: none; }

.adresse__externe {
  flex: none;
  padding: .35rem .7rem;
  margin-right: .6rem;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--ardoise);
  border: 1px solid var(--trait-clair);
  border-radius: 999px;
  text-decoration: none;
  white-space: nowrap;
}

.adresse__externe:hover { color: var(--nuit); border-color: var(--ardoise); }

/* --- Cadran du Deal Score ------------------------------------------------- */

.cadran { width: 100%; max-width: 190px; height: auto; display: block; margin-inline: auto; }

.cadran__libelle {
  text-align: center;
  font-weight: 700;
  font-size: 0.9375rem;
  margin-top: 0.25rem;
}

/* --- Graphique SVG rendu côté serveur ----------------------------------- */

.graphique { width: 100%; height: auto; display: block; }
.graphique__legende { font-size: 0.875rem; color: var(--ardoise); margin-top: 0.5rem; }

/* --- Divers -------------------------------------------------------------- */

.liste-nue { list-style: none; margin: 0; padding: 0; }
.liste-nue > li { display: flex; align-items: center; gap: 0.5rem; }

/* « hidden » doit l'emporter, et c'est tout sauf théorique : les filtres du
   quartier posent cet attribut sur des <li> que la règle ci-dessus met en
   « display: flex ». Une déclaration de feuille de style bat celle du
   navigateur, si bien que les boutons « Alimentation », « Santé »… changeaient
   d'état sans que la liste bouge d'une ligne. */
[hidden] { display: none !important; }

.repartition {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.25rem;
  font-size: 0.875rem;
  color: var(--ardoise);
}

.pile > * + * { margin-top: 1.25rem; }
.pile--serree > * + * { margin-top: 0.6rem; }

.indicateur { display: none; align-items: center; gap: 0.5rem; color: var(--ardoise); }
.htmx-request .indicateur { display: inline-flex; }


/* ==========================================================================
   Tableau de bord d'une analyse
   ========================================================================== */

.retour {
  display: inline-block;
  margin-bottom: 1rem;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ardoise);
  text-decoration: none;
}

.retour:hover { color: var(--nuit); }

/* --- Le bandeau de l'analyse : adresse, statut, actions ------------------- */

.bandeau-analyse {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}

.bandeau-analyse__titre {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem .9rem;
  min-width: 0;
  /* **Rétractable, pour que les actions remontent à sa droite.** Sans base de
     rétraction, ce bloc réclamait la largeur de son contenu — un titre d'adresse
     complète, plus deux étiquettes — et poussait les boutons à la ligne suivante,
     où ils se rangeaient à gauche sous le titre. L'espace libre à droite du titre
     restait vide, et deux boutons d'action occupaient une ligne pour eux seuls.
     Vingt rem suffisent à afficher une adresse ordinaire ; en deçà, tout revient à
     la ligne, ce qui est le comportement voulu sur un téléphone. */
  /* **« min(20rem, 100%) », et jamais « 20rem » seul.** Une base de vingt rem sur un
     écran de 320 px réclame plus que la largeur disponible : le bloc refuse alors de
     se contracter et **pousse la page**, qui déborde vers la droite. C'est la leçon de
     « minmax(min(N, 100%), 1fr) » du § r1, transposée du grid au flex — et je l'ai
     reperdue en remontant les boutons à droite. */
  flex: 1 1 min(20rem, 100%);
  /* Le repère du panneau de choix d'adresse, qui s'ancre ici et non sur son bouton :
     voir « .choix-adresse ». Sans cette ligne, le panneau repart du bouton et ressort
     de l'écran. */
  position: relative;
}

.bandeau-analyse__titre h1 {
  margin: 0;
  font-size: clamp(1.375rem, 3.2vw, 1.875rem);
}

/* Collées à droite quand il y a la place, et **contractables** quand il n'y en a pas.
   « flex: 0 0 auto » les figeait à la largeur de leur contenu : deux boutons dont les
   libellés font ensemble plus de six cents pixels débordaient d'un écran de téléphone,
   sans qu'aucune ligne ne puisse les rattraper. Le contenu, lui, ne se coupe pas — les
   boutons passent à la ligne entre eux. */
.bandeau-analyse__actions {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex: 0 1 auto;
  min-width: 0;
  margin-left: auto;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* Sur un écran étroit, la rangée s'aligne à gauche comme le titre : un bloc d'actions
   poussé à droite sous un titre à gauche donne une page qui bascule d'un bord à
   l'autre. */
@media (max-width: 40rem) {
  .bandeau-analyse__actions { margin-left: 0; justify-content: flex-start; }
}

.bandeau-analyse__meta {
  margin: .5rem 0 1.25rem;
  font-size: 0.9375rem;
  color: var(--ardoise);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
}

/* --- Le choix d'adresse : replié tant qu'on ne le demande pas ------------- */

/* **Le panneau se cale sur la largeur du bandeau, jamais sur la position du bouton.**
   Signalé en service : « des menus qui débordent carrément de la page du navigateur sur
   la droite ». Mesuré : 532 px de bord droit dans une fenêtre de 390 — cent quarante-deux
   pixels de panneau hors de l'écran, sur les dix pages du tableau de bord. Les adresses
   proposées y étaient coupées, donc illisibles et impossibles à choisir.

   La cause n'était pas la largeur du panneau, qui était déjà plafonnée à la fenêtre,
   mais son **point d'ancrage** : « position: relative » sur le bouton faisait partir le
   panneau du décalage de celui-ci, et il débordait d'autant. Le bouton se trouve après
   le titre et une étiquette, donc au milieu de la ligne sur un téléphone.

   **Un point de rupture n'y aurait pas suffi, et c'est ce qui décide de la forme.** Le
   débordement vaut ce que vaut le décalage du bouton : sur un grand écran, une adresse
   longue le repousse tout aussi bien hors du cadre. Le panneau s'ancre donc au bandeau
   — qui, lui, fait la largeur de la page — et se cale entre ses deux bords. Il ne peut
   alors plus sortir, à aucune largeur, quelle que soit la longueur du titre. */
.choix-adresse { position: static; }

.choix-adresse__bouton {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .3rem .7rem;
  border: 1px solid var(--trait-clair);
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ardoise);
  cursor: pointer;
  list-style: none;
  white-space: nowrap;
}

.choix-adresse__bouton::-webkit-details-marker { display: none; }
.choix-adresse__bouton::after { content: "▾"; font-size: .75rem; }
.choix-adresse[open] .choix-adresse__bouton { color: var(--nuit); border-color: var(--ardoise); }
.choix-adresse[open] .choix-adresse__bouton::after { content: "▴"; }

.choix-adresse__panneau {
  position: absolute;
  z-index: 30;
  top: calc(100% + .5rem);
  /* Les deux bords, et une largeur « auto » : le panneau occupe le bandeau, puis
     s'arrête à 34 rem s'il en a la place. Poser « width » à la place de « right »
     ferait repartir le débordement dès que le bandeau serait plus étroit. */
  left: 0;
  right: 0;
  width: auto;
  max-width: 34rem;
  padding: 1rem;
  background: var(--clair);
  border: 1px solid var(--trait-clair);
  border-radius: var(--rayon);
  box-shadow: var(--ombre-portee);
}

/* --- La page de collage : la marche à suivre et la zone de dépôt ----------

   On arrive ici avec un texte dans le presse-papiers : la zone où le déposer doit
   être visible sans rien faire. Empilées, la marche à suivre poussait le bouton
   « Analyser » sous la ligne de flottaison ; côte à côte, on lit l'une pendant
   qu'on remplit l'autre — ce qui est précisément l'usage. */
/* **Un nom qui lui est propre.** « collage » désignait déjà, depuis l'origine, le
   cadre de saisie de la page d'accueil : lui donner ici une grille à deux
   colonnes a disloqué le bandeau d'accueil, où le même mot ne voulait pas dire la
   même chose. Un nom de classe est un identifiant global — celui-ci dit
   maintenant de quelle page il parle. */
.page-collage {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: 1fr;
  /* Pas de « align-items: start » : les deux colonnes doivent faire la MÊME
     hauteur, c'est ce qui permet à la carte du bas de gauche de se caler sur le
     pied de la carte de collage. Avec « start », chaque colonne s'arrête à son
     contenu et les deux pieds tombent où ils veulent. */
}

/* La colonne de gauche est une pile qui occupe toute la hauteur : le titre en
   haut, et la carte « Vous n'avez pas d'annonce ? » repoussée tout en bas par sa
   marge automatique. Les deux bords s'alignent alors sur ceux de la carte de
   droite — sans qu'aucune hauteur ne soit écrite nulle part, ce qui serait faux
   dès qu'un texte changerait de longueur. */
.page-collage__aide {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

.page-collage__aide > .carte:last-child { margin-top: auto; }

/* La colonne de droite occupe toute la hauteur de la rangée, et c'est la ZONE DE
   TEXTE qui absorbe ce qui reste.

   Sans cela, la carte s'arrête à son contenu : la rangée prend la hauteur de la
   colonne la plus haute, et les deux pieds ne se rejoignent jamais — c'est ce que
   la capture a montré, la carte de gauche dépassant de soixante-dix pixels. Faire
   grandir le champ plutôt que d'ajouter du vide sous le bouton rend d'ailleurs
   service : c'est la seule chose que cette page demande de remplir.

   « min-height: 0 » n'est pas décoratif : sans lui, un enfant de conteneur
   flexible refuse de descendre sous la hauteur de son contenu, et la répartition
   ne se produit tout simplement pas. C'est le piège relevé en session L2 sur la
   fenêtre de mise en relation. */
.page-collage__saisie { display: flex; flex-direction: column; }

.page-collage__saisie > .carte {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.page-collage__saisie > .carte > .champ {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.page-collage__saisie > .carte > .champ textarea {
  flex: 1;
  min-height: 12rem;
}

@media (min-width: 900px) {
  /* La zone de collage est la plus large : c'est elle qu'on remplit. */
  .page-collage { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); }
}
/* La règle « nth-child » qui alignait la première rangée a disparu avec elle : le
   titre est sorti de la grille, et l'avertissement a rejoint la carte de collage.
   Compter des rangs, c'est dépendre d'une liste d'enfants qu'un ajout futur
   décalerait sans rien casser de visible — la leçon de la session r1. */

/* Les titres de la colonne d'aide ne doivent pas peser autant que celui de la page.
   Ils le faisaient : « Comment faire » avait presque la taille de « Analyser une
   annonce », si bien que l'œil hésitait sur ce que la page demandait. Cela ne se
   voyait pas dans la feuille de style — les deux règles vivent à cent lignes l'une
   de l'autre — mais sur une capture, immédiatement. */
.page-collage__aide h2 { font-size: 1.25rem; }

/* Un encadré au-dessus d'un champ, et non au milieu d'une page : il doit tenir en
   deux lignes, faute de quoi il repousse hors de vue ce qu'il commente. */
.encadre--compact { padding: 0.65rem 0.85rem; font-size: 0.875rem; }

.encadre--compact p { margin: 0; }

/* L'issue de secours se cale à droite de la rangée d'actions : elle reste
   accessible sans se présenter comme le geste attendu. */
.page-collage__ailleurs { margin-left: auto; }

/* La liste des adresses proposées par le référentiel national.

   Elle se pose sous le champ sans pousser le reste : une liste qui décale le
   bouton à chaque frappe est une liste qu'on rate au clic. */
.propositions {
  list-style: none;
  margin: -.5rem 0 .5rem;
  padding: 0;
  border: 1px solid var(--trait-clair);
  border-radius: var(--rayon-petit);
  background: #FFFFFF;
  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.10);
  overflow: hidden;
}

.propositions__choix {
  display: block;
  width: 100%;
  padding: .55rem .75rem;
  border: 0;
  background: transparent;
  color: var(--nuit);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.propositions__choix:hover,
.propositions__choix:focus-visible { background: rgba(6, 182, 212, 0.12); }

/* Les trois gestes, resserrés : ils accompagnent, ils n'occupent pas l'écran. */
.etapes--serrees > li { margin-bottom: .75rem; }
.etapes--serrees h3 { margin-bottom: .15rem; }
.etapes--serrees p { margin: 0; font-size: 0.9375rem; }

/* --- Deux colonnes : le tableau à gauche, ce qui l'illustre à droite -------

   L'onglet Historique et l'onglet Projets empilaient un tableau puis une image.
   Sur un écran large, cela laissait la moitié droite vide et obligeait à faire
   défiler pour rapprocher les deux — alors que ce sont précisément les deux vues
   d'une même chose : les ventes et leur courbe, les chantiers et leur carte. */
.cote-a-cote {
  display: grid;
  gap: 1rem;
  /* **« minmax(0, 1fr) », et jamais « 1fr » seul.** « 1fr » vaut « minmax(auto, 1fr) »,
     dont le minimum est le **contenu minimal** de la piste : un tableau de chantiers,
     avec sa colonne de description, y réclamait 701 px. La piste refusait de descendre
     en dessous, la grille poussait la carte à 740 px dans un écran de 390, et toute la
     moitié droite de l'onglet Projets se retrouvait hors de l'écran — rognée, donc
     invisible et inatteignable. Mesuré à 320 px comme à 390.

     C'est la faute de « minmax(340px, 1fr) » du § r1 sous son autre visage : là-bas
     c'était une largeur écrite en dur, ici c'est le contenu qui impose la sienne. */
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

@media (min-width: 1080px) {
  .cote-a-cote { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); }
  /* L'illustration suit le regard quand on parcourt un long tableau. */
  .cote-a-cote__illustration { position: sticky; top: 1rem; }
}

/* La courbe est un SVG à « viewBox » : elle occupe toute la largeur qu'on lui
   laisse et grandit **en hauteur** d'autant. Dans un conteneur de 1600 px, elle
   atteignait près de six cents pixels de haut pour dix points. Le plafond la
   ramène à une proportion lisible. */
.graphique { display: block; width: 100%; max-width: 760px; margin-inline: auto; }

/* --- Tuiles du tableau de bord ------------------------------------------- */

.tuiles {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;
  margin-bottom: 1rem;
}

@media (min-width: 700px) {
  .tuiles--maitresses { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tuiles--chiffres { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tuiles--deux { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1080px) {
  /* La carte du Deal Score est plus large que les trois autres : c'est la
     réponse que le visiteur est venu chercher, et elle porte le plus de texte. */
  .tuiles--maitresses { grid-template-columns: 1.5fr 1fr 1fr 1fr; }
  .tuiles--chiffres { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* --- Le Résumé : deux rangées, et pas une de plus -------------------------

   L'engagement est simple à énoncer et exigeant à tenir : sur un écran de bureau,
   le Résumé se lit **sans faire défiler**. Il répond à quatre questions — cela
   vaut-il son prix, qu'est-ce que c'est, où est-ce, qu'y a-t-il autour — et
   chaque ligne ajoutée en repousse une hors de vue.

   D'où une grille explicite plutôt qu'un empilement de rangées : les huit tuiles
   d'origine sont devenues sept, puis six, en regroupant ce qui se lit ensemble.
   La localisation occupe deux colonnes à droite, parce qu'une vue aérienne écrasée
   dans un quart de largeur n'apprend rien. */
.resume {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;
  margin-bottom: 1rem;
}

@media (min-width: 700px) {
  .resume { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1180px) {
  .resume { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* Le Deal Score porte le plus de texte, la localisation la plus grande image. */
  .resume__localisation { grid-column: span 2; }
}

/* Sur un écran haut, les tuiles d'une rangée s'alignent sur la plus grande : les
   colonnes de gauche s'étirent plutôt que de laisser des marches inégales.

   **Et leur contenu se centre dans la hauteur ainsi obtenue — le titre excepté.**
   L'étirement est une décision de mise en page, pas une intention de lecture : une
   tuile de trois lignes étirée à la hauteur d'une tuile de huit posait son texte tout
   en haut et laissait un grand vide dessous, si bien que la rangée se lisait comme une
   suite de blocs décrochés.

   Le titre, lui, **reste en haut**. Centré avec le reste, il flottait au milieu de la
   tuile : or c'est lui qui dit ce qu'on regarde, et une rangée de titres alignés se
   parcourt d'un seul coup d'œil là où des titres à des hauteurs différentes obligent à
   chercher. C'est le rôle d'un intitulé de colonne, et il se tient en haut de sa
   colonne.

   Le centrage se fait donc **sous** lui, par deux marges automatiques : la première
   posée sur ce qui suit le titre, la seconde sur le dernier élément. En disposition
   flexible, deux « auto » qui se font face se partagent l'espace libre à parts égales —
   le bloc se retrouve centré dans ce qui reste, sans qu'aucun gabarit ait eu besoin
   d'un conteneur de plus. */
.resume > .tuile {
  align-self: stretch;
  display: flex;
  flex-direction: column;
}

.resume > .tuile > .tuile__titre + * { margin-top: auto; }
.resume > .tuile > :last-child { margin-bottom: auto; }

.tuile { padding: 1.1rem 1.15rem; }

/* Deux chiffres côte à côte dans une tuile : les deux prix se comparent, la
   surface et le prix au mètre carré se déduisent l'un de l'autre. Les séparer en
   deux tuiles obligeait l'œil à faire le trajet. */
.duo { display: flex; flex-wrap: wrap; gap: .35rem 1.25rem; margin-bottom: .35rem; }
.duo__part { display: flex; flex-direction: column; min-width: 0; }
.duo__part .tuile__valeur { margin: 0; font-size: 1.5rem; }
.duo__part .tuile__valeur--vide { color: var(--ardoise); }

/* --- Les récapitulatifs du Résumé ----------------------------------------

   Une ligne par famille, un filet coloré à gauche, et le chiffre à droite. La
   couleur range, l'intitulé informe : qui ne distingue pas les teintes lit
   « aucun relevé à moins de 800 m » et sait exactement la même chose. */
/* Sa propre remise à zéro, pour la même raison que « .confrontation » : mêler
   « liste-nue » à une classe de mise en page expose au piège de spécificité qui a
   rendu inertes trois corrections de la confrontation. */
.recap { list-style: none; padding: 0; display: grid; gap: .1rem; margin: 0 0 .75rem; }

/* Une ligne, trois éléments alignés, et rien qui aille à la ligne.

   La première version répétait « à moins de 800 m » sur chacune des quatre
   lignes : quatre fois la même information, qui se coupait en trois lignes dans
   une tuile étroite et faisait tripler la hauteur du bloc. Le rayon se dit
   **une fois**, sous la liste. */
.recap__ligne {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .35rem .55rem;
  border-left: 3px solid var(--trait-clair);
  border-radius: 0 var(--rayon-petit) var(--rayon-petit) 0;
}

.recap__signe { flex: none; font-size: 1rem; }
.recap__intitule { flex: 1 1 auto; min-width: 0; font-weight: 600; font-size: 0.9375rem; }
.recap__nombre { flex: none; font-weight: 800; font-size: 1.0625rem; }
/* Le détail d'un risque — « Exposition moyenne » — reste sur la ligne, à droite
   de son intitulé, et se réduit s'il le faut plutôt que de pousser la tuile. */
.recap__detail { flex: 0 1 auto; min-width: 0; text-align: right; }

.recap__ligne--bon { border-left-color: var(--vert); background: rgba(34, 197, 94, 0.07); }
.recap__ligne--moyen { border-left-color: var(--cyan); background: rgba(6, 182, 212, 0.06); }
.recap__ligne--faible { border-left-color: var(--ambre); background: rgba(245, 158, 11, 0.08); }
.recap__ligne--fort { border-left-color: var(--rouge); background: rgba(239, 68, 68, 0.08); }
.recap__ligne--inconnu { border-left-color: var(--ardoise); background: rgba(100, 116, 139, 0.06); }

.recap__ligne--bon .recap__nombre { color: var(--vert); }
.recap__ligne--faible .recap__nombre { color: var(--ambre); }
.recap__ligne--inconnu .recap__nombre { color: var(--ardoise); }

/* L'intitulé d'une tuile du Résumé.

   **Il se détache du reste, sans lui disputer la vedette.** En gris ardoise et à la
   taille du texte courant, il se confondait avec les mentions secondaires qui
   l'entourent — « Annoncé », « Médiane du secteur » — alors qu'il ne dit pas la même
   chose : il nomme la tuile. Il passe donc au bleu nuit, la couleur du texte principal,
   et gagne une graisse. Ce qui reste inchangé est plus important encore : sa **taille**.
   Un titre qui grossirait entrerait en concurrence avec le chiffre qu'il annonce, et
   c'est le chiffre qu'on vient lire. */
.tuile__titre {
  margin: 0 0 .75rem;
  font-size: 0.9375rem;
  font-weight: 800;
  letter-spacing: 0;
  color: var(--nuit);
}

/* La grande valeur d'une tuile — un prix, une surface. C'est le seul endroit du
   site qui mette une chasse resserrée sous un nombre à quatre chiffres et plus,
   et c'est ce qui a rendu « 1 150 000 € » illisible en service : le séparateur
   de milliers y perdait 0,02 em, sur les 0,17 em qu'il mesure. Le mot-à-mot le
   lui rend, et un peu au-delà — un prix doit se lire en groupes de trois du
   premier coup d'œil, pas se déchiffrer. La cause première du défaut est
   ailleurs, dans le caractère employé : voir « web/nombres.py ». */
.tuile__valeur {
  margin: 0 0 .35rem;
  font-size: 1.75rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  word-spacing: 0.08em;
  line-height: 1.1;
}

.tuile__ecart { margin: 0 0 .35rem; font-weight: 700; font-size: 0.9375rem; }

.tuile__details { margin: .6rem 0 .35rem; display: grid; gap: .2rem; }
.tuile__details > div { display: flex; justify-content: space-between; gap: 1rem; }
.tuile__details dt { color: var(--ardoise); font-size: 0.875rem; }
.tuile__details dd { margin: 0; font-weight: 600; font-size: 0.875rem; }

.tuile__anneau { display: flex; align-items: center; gap: .9rem; }
.tuile__anneau > div { min-width: 0; }

.tuile__verdict { margin: 0 0 .25rem; font-weight: 800; font-size: 1.0625rem; }

.tuile--petite { display: flex; flex-direction: column; gap: .15rem; }

.tuile__intitule {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ardoise);
}

.tuile__chiffre { font-size: 1.375rem; font-weight: 800; letter-spacing: -0.02em; }

.tuile__ligne {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .6rem;
  margin-bottom: .75rem;
}

/* --- Faire tenir le Résumé : où les pixels ont été repris ------------------

   L'engagement — se lire sans faire défiler — se gagne en hauteur, et la hauteur
   se prend là où elle ne coûte rien : des marges de tuile un peu plus serrées,
   une vue aérienne qui s'ajuste à la place restante plutôt que d'imposer la
   sienne, et un bandeau de titre qui ne pousse pas le contenu sous la ligne de
   flottaison. Aucun chiffre n'y est réduit, aucune phrase n'y est coupée. */
.resume > .tuile { padding: .9rem 1rem; }

/* Le contenu des tuiles chiffrées est **centré**.

   Trois nombres alignés à gauche dans une tuile large laissent un vide à droite
   qui déséquilibre la rangée. Centrer les met en regard les uns des autres d'une
   tuile à l'autre, et l'œil parcourt la rangée au lieu de sauter d'un bord à
   l'autre. Deux exceptions, et elles se justifient : la localisation porte une
   adresse — un texte, qui se lit toujours depuis la gauche — et les
   récapitulatifs sont des listes, dont l'alignement fait tout l'intérêt. */
.resume__score,
.resume__prix,
.resume__bien,
.resume__dpe { text-align: center; }

.resume__score .tuile__anneau,
.resume__prix .tuile__details > div { justify-content: center; }

.resume__score .tuile__anneau { text-align: left; }

.resume__prix .tuile__details > div { gap: .5rem; }

.resume__bien .duo,
.resume__dpe .duo { justify-content: center; }

.resume__score .actions,
.resume__score > .bouton,
.resume__quartier > .bouton,
.resume__risques > .bouton { margin-inline: auto; }
.resume .tuile__titre { margin-bottom: .5rem; }
.resume .note { margin-block: .25rem; }
.resume .actions { margin-top: .6rem; }

.resume__localisation .vue-aerienne { margin: .5rem 0; }
.resume__localisation .vue-aerienne img {
  max-height: 210px;
  object-fit: cover;
  border-radius: var(--rayon-petit);
}

/* Le bandeau de l'analyse : la même information, moins d'air. Sur un écran de
   1080 px, chaque ligne gagnée en tête est une ligne rendue au Résumé. */
.bandeau-analyse__meta { margin-block: .35rem .5rem; }
.bandeau-analyse__compte { margin-block: 0 .75rem; }
.conteneur--large .onglets { margin-bottom: 1rem; }
.conteneur--large .retour { margin-bottom: .5rem; }

/* --- Anneau du Deal Score ------------------------------------------------- */

.anneau { width: 88px; height: 88px; flex: none; display: block; }

/* Le verdict reprend la couleur du niveau : le mot et l'anneau disent la même
   chose, et le mot reste lisible pour qui ne distingue pas les teintes. */
.jauge--bonne { color: var(--vert); }
.jauge--moyenne { color: var(--ambre); }
.jauge--mauvaise { color: var(--rouge); }

/* Le bouton qui ouvre une carte chez un tiers.

   Il se distingue des autres actions secondaires — c'est le seul lien du tableau
   de bord qui fasse quitter LogiScan — sans prendre le vert plein de l'action
   principale, qui appartient au geste que le produit demande. Contour et texte
   verts, fond clair : visible, et à sa place dans la hiérarchie. */
.bouton--carte {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  background: rgba(6, 182, 212, 0.12);
  border: 1.5px solid var(--cyan);
  color: var(--nuit);
  font-weight: 700;
}

.bouton--carte:hover { background: rgba(6, 182, 212, 0.22); color: var(--nuit); }

/* Le repère prend le cyan ; le libellé garde le bleu nuit. Le cyan de la charte
   est un accent, pas une couleur de texte : sur fond clair, il n'atteint pas le
   contraste qu'un mot doit avoir pour se lire. C'est donc le pictogramme et le
   contour qui le portent, et la lisibilité n'y perd rien. */
.bouton--carte svg { flex: none; color: var(--cyan); }

/* Un signe cliquable qui ouvre la source d'un chiffre affiché.

   Discret par construction : vérifier n'est pas l'action que la tuile propose,
   c'est une porte qu'on laisse ouverte à qui veut la pousser. Le cyan le
   distingue du texte sans en faire un bouton. */
.lien-source {
  display: inline-flex;
  vertical-align: middle;
  margin-left: .3rem;
  color: var(--cyan);
  opacity: .75;
}

.lien-source:hover { opacity: 1; }

/* --- Bouton compact ------------------------------------------------------- */

.bouton--compact { padding: .5rem .95rem; font-size: 0.875rem; }

/* --- Les boutons sur le fond sombre de l'exploitation ---------------------- */
/* Signalé en service : « Voir le PDF » et « Envoyer une demande d'essai » s'écrivaient
   en bleu nuit sur le bleu nuit de /admin — invisibles, alors que le bouton était bien
   là, cliquable, et parfaitement fonctionnel.

   Deux causes, et aucune ne produit d'erreur :

   - « .bouton--secondaire » pose « color: var(--nuit) », prévu pour le site clair. Une
     compensation existait déjà pour les fonds sombres, mais elle ne visait que
     « .section--sombre », « .hero » et « .carte--sombre » — et /admin n'emploie aucune
     des trois. Une liste de contextes tenue à la main finit toujours par en oublier un.
   - « .bouton » sans variante ne déclare **ni fond ni couleur** : il hérite. Sur une
     page claire, l'héritage tombe juste par accident ; sur une page sombre, non.

   C'est la famille de défauts du § 1 quinquies (« admin__tableau » écrivant en blanc
   sur blanc) : rien n'échoue, aucun test ne rougit, et la page ne dit simplement pas ce
   qu'elle devait dire. On vise donc la **racine** de l'exploitation plutôt que
   d'allonger la liste des contextes, pour qu'une page d'admin future soit couverte
   d'office. */
.admin .bouton {
  /* **Le fond est posé explicitement, et c'est le cœur du correctif.** « .bouton » ne
     déclare aucun « background » : sur une balise « a » il reste transparent, mais un
     élément « button » reçoit le fond clair que le navigateur donne d'office à ses
     boutons. Poser un texte clair sans poser de fond a donc produit du **blanc sur
     blanc** sur tous les boutons de formulaire de /admin — « Afficher », « Filtrer »,
     « Enregistrer », « Marquer transmis », « Envoyer un SMS d'essai ».

     C'est la même faute que celle qu'on corrigeait : une couleur posée sans son fond
     dépend alors de ce que le contexte fournit, et un contexte finit toujours par
     changer. Les deux se déclarent désormais ensemble. */
  background: rgba(241, 245, 249, 0.06);
  color: var(--clair);
  border-color: var(--trait-sombre);
}
.admin .bouton--principal {
  background: var(--vert);
  color: var(--nuit);
}
.admin .bouton--secondaire {
  background: transparent;
  border-color: var(--trait-sombre);
  color: var(--clair);
}
.admin .bouton:hover { background: rgba(241, 245, 249, 0.12); border-color: var(--ardoise); }
.admin .bouton--principal:hover { background: rgba(34, 197, 94, 0.88); }

/* --- Bandeau d'installation ---------------------------------------------- */

/* Proposé une seule fois, sur téléphone, et refusable pour de bon. Il est posé
   en bas de l'écran plutôt qu'en haut : en haut, il recouvrirait le titre de la
   page qu'on vient d'ouvrir ; en bas, il est à portée du pouce et ne masque que
   du blanc. Il est **caché par défaut** — l'attribut « hidden » du gabarit — et
   ce n'est le script d'installation qui le montre que là où il a du sens.

   Fond bleu nuit, comme l'en-tête : c'est une surface de marque, et le vert de
   l'action y ressort sans qu'on ait à introduire quoi que ce soit. */
.bandeau-installation {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: .85rem;
  padding: .85rem 1rem calc(.85rem + env(safe-area-inset-bottom, 0px));
  background: var(--nuit);
  color: var(--clair);
  border-top: 1px solid var(--trait-sombre);
  box-shadow: 0 -8px 30px rgba(15, 23, 42, 0.25);
}

/* Un élément caché reste caché : sans cette règle, la déclaration « display:
   flex » ci-dessus l'emporterait sur le « display: none » du navigateur, et le
   bandeau s'afficherait à tout le monde. Le défaut a déjà été payé une fois sur
   les filtres de l'onglet Environnement. */
.bandeau-installation[hidden] { display: none; }

.bandeau-installation__icone {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 11px;
}

.bandeau-installation__texte { flex: 1 1 auto; min-width: 0; }

.bandeau-installation__titre {
  margin: 0;
  font-weight: 700;
  font-size: .9375rem;
  line-height: 1.3;
}

.bandeau-installation__detail {
  margin: .2rem 0 0;
  font-size: .8125rem;
  line-height: 1.4;
  color: var(--texte-sombre-attenue);
}

.bandeau-installation__detail[hidden] { display: none; }

.bandeau-installation__actions {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: .35rem;
}

.bandeau-installation__actions .bouton[hidden] { display: none; }

/* Le refus n'est pas un bouton : c'est une sortie. Lui donner la même forme
   qu'« Installer » ferait hésiter, alors qu'il n'y a rien à peser. */
.bandeau-installation__fermer {
  border: none;
  background: none;
  padding: .2rem;
  font: inherit;
  font-size: .8125rem;
  color: var(--texte-sombre-attenue);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.bandeau-installation__fermer:hover { color: var(--clair); }

@media (max-width: 480px) {
  .bandeau-installation { gap: .6rem; padding-inline: .75rem; }
  .bandeau-installation__icone { width: 36px; height: 36px; }
}

/* --- Poste de pilotage (session M2) --------------------------------------

   Un outil interne, ouvert tous les jours, lu vite. Trois partis pris :

   - **fond sombre**, comme les surfaces de marque du produit : c'est un poste
     d'exploitation, pas une page de vente, et la distinction doit se voir au
     premier coup d'œil pour qu'on ne confonde jamais les deux ;
   - **plus large que le site** (1440 px) : un tableau de résolution par domaine
     a six colonnes, et les serrer dans la largeur d'un texte les rend illisibles ;
   - **aucune couleur hors charte.** L'ambre et le rouge n'y portent que des états
     — une source en panne, une sauvegarde en retard —, jamais un accent.
   ------------------------------------------------------------------------- */

.admin {
  background: var(--nuit);
  color: var(--clair);
  min-height: 100vh;
}

.admin__entete {
  background: rgba(15, 23, 42, 0.96);
  border-bottom: 1px solid var(--trait-sombre);
  position: sticky;
  top: 0;
  z-index: 20;
}

/* L'en-tête d'exploitation suit la même règle que celui du site, et pour la même
   raison : sa barre passe à la ligne sur un téléphone, et les tableaux qu'on y consulte
   se lisent en défilant.

   **Après la règle qu'elle annule, et non avant.** À spécificité égale, c'est la
   dernière écrite qui gagne : posée au-dessus, elle n'aurait rien fait — et rien ne
   l'aurait signalé, la page s'affichant parfaitement avec l'en-tête toujours collé.
   C'est l'erreur que j'ai commise en écrivant ce correctif, et le test la refuse. */
@media (max-width: 720px) {
  .admin__entete { position: static; }
}

/* **Elle passe à la ligne**, et c'était tout le défaut. Cinq liens dans une rangée
   sans retour poussaient la page à 542 px de large sur un téléphone de 390 : le
   visiteur pouvait dézoomer, tout le contenu s'en trouvait rétréci, et rien ne le
   signalait — une capture d'écran est tronquée à la largeur de la fenêtre, si bien
   que la photo d'une page qui déborde ressemble trait pour trait à celle d'une page
   qui tient. Constaté avec un détecteur écrit pour l'occasion, pas à l'œil. */
.admin__barre {
  max-width: 1440px;
  margin: 0 auto;
  padding: .75rem 1.25rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .6rem 1rem;
}

.admin__logo { display: inline-flex; }
.admin__logo img { height: auto; }

/* L'étiquette dit ce qu'on regarde. Sans elle, une capture d'écran de cette page
   ne se distingue pas d'une capture du produit — et elle n'a pas du tout la même
   sensibilité. */
.admin__etiquette {
  font-size: .7rem;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--nuit);
  background: var(--cyan);
  border-radius: 999px;
  padding: .15rem .55rem;
}

/* « margin-left: auto » la colle à droite tant qu'il reste de la place, et
   « flex-wrap » la laisse se replier plutôt que pousser la page. Les deux vont
   ensemble : sans le retour à la ligne, la marge automatique ne fait que déplacer un
   débordement. */
.admin__navigation {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.25rem;
  align-items: center;
}
.admin__lien {
  color: var(--texte-sombre-attenue);
  text-decoration: none;
  font-size: .9rem;
  font-weight: 600;
}
.admin__lien:hover { color: var(--clair); }
.admin__lien[aria-current="page"] { color: var(--vert); }

.admin__contenu { padding-bottom: 4rem; }
.admin__page { max-width: 1440px; margin: 0 auto; padding: 1.5rem 1.25rem; }

.admin__tete {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 1.25rem;
}
.admin__tete h1 { font-size: 1.6rem; margin: 0 0 .3rem; color: var(--clair); }
.admin__periode { margin: 0; color: var(--texte-sombre-attenue); font-size: .9rem; }
.admin__reference { color: var(--ardoise); }

.admin__selecteur { display: flex; flex-direction: column; gap: .6rem; align-items: flex-end; }
.admin__onglets { display: flex; gap: .35rem; }
.admin__onglet {
  padding: .35rem .8rem;
  border-radius: var(--rayon-petit);
  border: 1px solid var(--trait-sombre);
  color: var(--texte-sombre-attenue);
  text-decoration: none;
  font-size: .85rem;
  font-weight: 600;
}
.admin__onglet:hover { border-color: var(--vert); color: var(--clair); }
.admin__onglet--actif { background: var(--vert); border-color: var(--vert); color: var(--nuit); }

.admin__bornes { display: flex; gap: .4rem; align-items: center; font-size: .82rem; color: var(--ardoise); }
.admin__bornes input[type="date"] {
  background: var(--voile-sombre);
  border: 1px solid var(--trait-sombre);
  border-radius: var(--rayon-petit);
  color: var(--clair);
  padding: .3rem .5rem;
  font-family: var(--police);
  font-size: .82rem;
}

.admin__rafraichissement {
  font-size: .82rem;
  color: var(--ardoise);
  margin: 0 0 1.25rem;
  padding-left: .75rem;
  border-left: 2px solid var(--cyan);
}

.admin__section {
  background: var(--voile-sombre);
  border: 1px solid var(--trait-sombre);
  border-radius: var(--rayon);
  padding: 1.25rem;
  margin-bottom: 1.25rem;
}
.admin__titre { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.admin__titre h2 { font-size: 1.15rem; margin: 0; color: var(--clair); }
.admin__section h3 { font-size: .95rem; margin: 1.1rem 0 .5rem; color: var(--clair); }
.admin__section h3:first-child { margin-top: 0; }
.admin__chapeau { color: var(--texte-sombre-attenue); font-size: .88rem; margin: .35rem 0 1rem; }
.admin__note, .admin__rien { color: var(--ardoise); font-size: .8rem; margin: .35rem 0 .75rem; }

/* Titre de section qui vit hors d'un bloc « admin__section » : il sépare deux
   journaux de nature différente sur la page des erreurs. */
.admin__section-titre {
  font-size: 1.15rem;
  margin: 2rem 0 .75rem;
  color: var(--clair);
}

/* Un incident, en un seul bloc sélectionnable d'un geste : cette page existe pour
   qu'on le copie-colle tel quel dans une demande d'aide. D'où le défilement
   horizontal plutôt qu'un retour à la ligne, qui casserait une ligne de pile en
   deux au milieu d'un nom de fichier. */
.admin__incident {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .78rem;
  line-height: 1.5;
  color: var(--clair);
  background: rgba(15, 23, 42, 0.55);
  border: 1px solid rgba(241, 245, 249, 0.12);
  border-radius: var(--rayon-petit);
  padding: .7rem .85rem;
  margin: 0;
  overflow-x: auto;
  min-width: 0;
  white-space: pre;
}

/* Un incident se replie, comme une demande et pour la même raison : le résumé porte
   de quoi décider — le type, le chemin, l'heure —, et l'on ouvre celui sur lequel on
   veut agir. Tous sont repliés au départ ; les ouvrir d'office rendrait la page
   illisible le jour où il y en a trente, c'est-à-dire le jour où l'on en a besoin. */
.incident {
  border: 1px solid var(--trait-sombre);
  border-radius: 10px;
  background: var(--voile-sombre);
  overflow: hidden;
  margin-bottom: .6rem;
}

.incident__entete {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  padding: .55rem .9rem;
  cursor: pointer;
}

.incident__type { font-weight: 700; color: var(--ambre); font-size: .9rem; }
.incident__chemin { color: var(--clair); font-size: .85rem; }
.incident__date { color: var(--ardoise); font-size: .78rem; white-space: nowrap; }

.incident__numero {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .75rem;
  color: var(--ardoise);
}

.incident__corps { padding: 0 .9rem .8rem; }

.admin__rappel {
  color: var(--texte-sombre-attenue);
  font-size: .82rem;
  background: rgba(6, 182, 212, 0.08);
  border-left: 2px solid var(--cyan);
  padding: .6rem .85rem;
  border-radius: 0 var(--rayon-petit) var(--rayon-petit) 0;
  margin: 0 0 1rem;
}

.admin__constat {
  color: var(--clair);
  font-size: .95rem;
  background: rgba(245, 158, 11, 0.10);
  border-left: 3px solid var(--ambre);
  padding: .7rem .9rem;
  border-radius: 0 var(--rayon-petit) var(--rayon-petit) 0;
  margin: 0 0 1.1rem;
}

/* Le bouton d'export : discret à dessein. Il sert une fois par trimestre, pas une
   fois par jour, et un bouton voyant volerait l'attention aux chiffres. */
.admin__export {
  font-size: .78rem;
  color: var(--ardoise);
  text-decoration: none;
  border: 1px solid var(--trait-sombre);
  border-radius: var(--rayon-petit);
  padding: .25rem .6rem;
  white-space: nowrap;
}
.admin__export:hover { color: var(--clair); border-color: var(--vert); }

/* --- Les six chiffres de tête ------------------------------------------- */

.admin__chiffres {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: .85rem;
}
.admin__chiffre {
  background: rgba(241, 245, 249, 0.04);
  border: 1px solid var(--trait-sombre);
  border-radius: var(--rayon-petit);
  padding: .85rem;
}
.admin__chiffre-libelle {
  font-size: .78rem;
  color: var(--ardoise);
  margin: 0 0 .3rem;
  display: flex;
  align-items: center;
  gap: .35rem;
}
.admin__chiffre-valeur {
  font-size: 1.75rem;
  font-weight: 800;
  margin: 0;
  color: var(--clair);
  /* Pas de chasse resserrée ici : c'est elle qui, sur la tuile « Prix » du site,
     faisait disparaître le séparateur de milliers (session 29). */
  letter-spacing: 0;
}
.admin__chiffre-valeur--texte { font-size: .95rem; font-weight: 600; }
.admin__chiffre-ecart { margin: .35rem 0 0; font-size: .78rem; display: flex; gap: .4rem; align-items: baseline; }

/* Ce que le chiffre recouvre, écrit à côté de lui. Cela remplace un bouton « ? » dont
   l'explication ne vivait que dans un attribut « title » : une infobulle au survol
   n'existe pas sur un écran tactile, et « visiteurs uniques » est justement le chiffre
   qu'on lit de travers quand rien ne le corrige. */
.admin__chiffre-precision {
  margin: .4rem 0 0;
  font-size: .72rem;
  line-height: 1.35;
  color: var(--ardoise);
}

.ecart { font-weight: 700; white-space: nowrap; }
.ecart--hausse { color: var(--vert); }
.ecart--baisse { color: var(--ambre); }
.ecart--neutre { color: var(--ardoise); }

.admin__vide { color: var(--ardoise); }

/* --- L'entonnoir --------------------------------------------------------- */

.admin__marches { display: flex; flex-direction: column; gap: .55rem; margin-bottom: 1.25rem; }
.marche__entete { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: .25rem; }
.marche__nom { font-weight: 600; font-size: .9rem; color: var(--clair); }
.marche__valeur { font-weight: 800; font-size: 1rem; color: var(--clair); }
.marche__barre {
  height: 10px;
  background: rgba(241, 245, 249, 0.06);
  border-radius: 999px;
  overflow: hidden;
}
.marche__remplissage {
  height: 100%;
  background: linear-gradient(90deg, var(--vert), var(--cyan));
  border-radius: 999px;
}
.marche__pied {
  margin: .25rem 0 0;
  font-size: .76rem;
  color: var(--ardoise);
  display: flex;
  gap: .75rem;
  flex-wrap: wrap;
}
.marche__passage { color: var(--texte-sombre-attenue); font-weight: 600; }

/* --- Blocs, tableaux, graphiques ---------------------------------------- */

/* **« min(340px, 100%) » et non « 340px ».** Une piste de grille dont le minimum est
   plus large que l'écran ne se contracte pas : elle impose sa largeur, et la page
   déborde. C'est ce qui débordait de 61 px sur un téléphone de 320. La forme
   « min(…, 100%) » dit « 340 px quand il y a la place, la largeur disponible sinon »,
   et c'est la seule qui tienne les deux bouts. */
.admin__duo {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  gap: 1rem;
}
.admin__bloc {
  background: rgba(241, 245, 249, 0.03);
  border: 1px solid var(--trait-sombre);
  border-radius: var(--rayon-petit);
  padding: .9rem;
  min-width: 0;
}
.admin__graphique { position: relative; height: 240px; margin-bottom: .75rem; }

/* Un tableau large défile dans son propre cadre : la page, elle, ne défile jamais
   horizontalement. */
.admin__defilant { overflow-x: auto; min-width: 0; }
/* **Un tableau prend la largeur de son contenu, jamais celle de son cadre.**

   « width: 100 % » paraissait sans conséquence : c'est pourtant l'origine de tout ce
   qui a été signalé. Un tableau de deux colonnes étiré sur 1 400 pixels pose son
   libellé contre le bord gauche et sa valeur contre le bord droit — « Comptes créés »
   ici, « 0 » là-bas, un mètre de vide entre les deux —, et l'œil ne relie plus la
   valeur à sa ligne. Ce n'était pas un défaut d'alignement mais un défaut de largeur.

   « auto » redonne à chaque colonne la place qu'elle demande et laisse le reste vide à
   droite. Le plancher évite qu'un tableau de deux mots paraisse tassé dans un coin ;
   le plafond garantit qu'un tableau large reste dans son cadre, où « admin__defilant »
   le fera défiler seul. */
.admin__tableau {
  width: auto;
  max-width: 100%;
  border-collapse: collapse;
  font-size: .82rem;
}
.admin__tableau th {
  text-align: left;
  font-weight: 600;
  color: var(--ardoise);
  border-bottom: 1px solid var(--trait-sombre);
  padding: .4rem .5rem;
  white-space: nowrap;
}
.admin__tableau td {
  padding: .4rem .5rem;
  border-bottom: 1px solid rgba(241, 245, 249, 0.06);
  color: var(--texte-sombre-attenue);
}
.admin__tableau tr:last-child td { border-bottom: none; }
.admin__nombre { text-align: right; font-variant-numeric: tabular-nums; color: var(--clair); }

/* **Aucune largeur en pourcentage sur une cellule, et c'est le cœur du sujet.**

   La recette répandue pour serrer une colonne de chiffres est « width: 1% » : le
   navigateur la réduit alors à son contenu et donne le reste aux autres. Elle a un
   effet de bord que personne n'annonce — **une largeur exprimée en pourcentage force
   le tableau lui-même à prendre toute la largeur disponible**, puisqu'il lui faut une
   référence à laquelle rapporter ce pourcentage. Le remède fabriquait donc exactement
   le défaut qu'on cherchait à corriger. Vérifié à l'écran, côte à côte.

   Il ne reste qu'un espacement — les colonnes se touchaient — et l'interdiction de
   couper un nombre en deux lignes. */
.admin__tableau th.admin__nombre,
.admin__tableau td.admin__nombre {
  white-space: nowrap;
  padding-left: 1.75rem;
}

/* Les chiffres se lisent en colonne : une graisse un peu plus marquée les détache du
   libellé sans les peindre. */
.admin__tableau td.admin__nombre { font-weight: 600; }

/* Une ligne sur deux à peine teintée. Sur un tableau large, c'est ce qui empêche l'œil
   de sauter d'une ligne à l'autre entre la première colonne et la dernière. */
.admin__tableau tbody tr:nth-child(even) { background: var(--voile-sombre); }

/* Un formulaire réduit à son bouton, posé dans une rangée d'actions. Sans cela, le
   bloc de formulaire passe à la ligne et le bouton « Renvoyer » quitte la rangée. */
.en-ligne { display: inline; margin: 0; }

/* Le seul formulaire de tout /admin : l'envoi d'un courriel d'essai. Il n'emprunte
   pas « .champ » du site public, dont les couleurs sont faites pour un fond clair. */
.admin__envoi { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .6rem; }
.admin__envoi label {
  flex: 1 1 100%;
  color: var(--ardoise);
  font-size: .82rem;
  font-weight: 600;
}
.admin__envoi input {
  flex: 1 1 18rem;
  min-width: 0;
  padding: .55rem .7rem;
  font: inherit;
  font-size: .9rem;
  color: var(--clair);
  background: rgba(241, 245, 249, 0.05);
  border: 1px solid var(--trait-sombre);
  border-radius: var(--rayon-petit);
}
.admin__envoi input::placeholder { color: var(--ardoise); }
.admin__envoi input:focus-visible { outline: 2px solid var(--vert); outline-offset: 1px; }

/* **Même défaut que les tableaux, même remède.** « space-between » dans un bloc large
   pose l'intitulé contre un bord et la valeur contre l'autre : on lit « Comptes créés »
   d'un côté, « 0 » à trente centimètres de là, et rien ne dit que les deux vont
   ensemble. Le plafond ramène la valeur à portée de l'œil, et l'aligne d'une ligne à
   l'autre — ce qui en fait une colonne pour de bon. */
.admin__liste { margin: 0; max-width: 34rem; }
.admin__liste > div {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: .4rem 0;
  border-bottom: 1px solid rgba(241, 245, 249, 0.06);
}
.admin__liste > div:last-child { border-bottom: none; }
.admin__liste dt { color: var(--ardoise); font-size: .84rem; margin: 0; }
.admin__liste dd {
  margin: 0;
  font-weight: 700;
  color: var(--clair);
  font-size: .9rem;
  /* Des chiffres de même chasse : sans cela, deux nombres de même longueur ne
     finissent pas au même endroit, et la colonne tremble. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- Santé --------------------------------------------------------------- */

.admin__constats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: .75rem;
  margin-bottom: 1.25rem;
}
.constat {
  display: flex;
  gap: .6rem;
  align-items: flex-start;
  background: rgba(241, 245, 249, 0.04);
  border: 1px solid var(--trait-sombre);
  border-radius: var(--rayon-petit);
  padding: .75rem;
}
.constat--alerte { border-color: rgba(239, 68, 68, 0.45); }
.constat__libelle { margin: 0; font-size: .78rem; color: var(--ardoise); }
.constat__valeur { margin: .1rem 0 0; font-weight: 700; color: var(--clair); font-size: .92rem; }
.constat__detail { margin: .35rem 0 0; font-size: .76rem; color: var(--texte-sombre-attenue); }

.admin__pastille { width: 9px; height: 9px; border-radius: 999px; flex: 0 0 auto; margin-top: .3rem; }
.admin__pastille--ok { background: var(--vert); }
.admin__pastille--alerte { background: var(--rouge); }
/* Gris, et non vert : ne pas savoir n'est pas aller bien. C'est la règle
   « pas de réponse n'est pas pas de risque », appliquée à une pastille. */
.admin__pastille--inconnu { background: var(--ardoise); }

.admin__lien-erreurs { margin: 1rem 0 0; font-size: .88rem; }
.admin__lien-erreurs a { color: var(--vert); text-decoration: none; font-weight: 600; }
.admin__lien-erreurs a:hover { text-decoration: underline; }

.admin__pied {
  color: var(--ardoise);
  font-size: .78rem;
  border-top: 1px solid var(--trait-sombre);
  padding-top: 1rem;
  margin-top: 1.5rem;
}
.admin__pied p { margin: 0 0 .4rem; }

@media (max-width: 720px) {
  .admin__tete { flex-direction: column; }
  .admin__selecteur { align-items: flex-start; width: 100%; }
  .admin__onglets { flex-wrap: wrap; }
  .admin__bornes { flex-wrap: wrap; }
  /* Sous cette largeur, la navigation prend sa propre rangée et se lit de gauche à
     droite comme le reste : collée à droite sous le logo, elle se lisait à l'envers. */
  .admin__navigation { margin-left: 0; width: 100%; justify-content: flex-start; }
  .admin__page { padding-inline: .9rem; }
  .admin__barre { padding-inline: .9rem; }
}

/* Un graphique dessiné par Chart.js prend la largeur qu'on lui laisse ; encore
   faut-il la lui borner. Sans « min-width: 0 », un canevas dans une piste de grille
   refuse de descendre sous sa taille intrinsèque. */
.admin__graphique { min-width: 0; max-width: 100%; }
.admin__graphique canvas { max-width: 100% !important; }

/* Un titre de bloc ne se coupe nulle part : « Retours par analyse sauvegardée » sur
   un écran de 320 px pousse son cadre s'il n'a pas le droit de couper un mot. */
.admin__section h3,
.admin__bloc h3 { overflow-wrap: anywhere; }


/* ==========================================================================
   Questions fréquentes
   ==========================================================================

   Mobile d'abord : une colonne, des accordéons pleine largeur, des cibles de
   clic généreuses. Aucune couleur hors des cinq jetons de la charte — un
   accordéon n'a rien à signaler, il ouvre et il ferme.
   ========================================================================== */

.faq__recherche { margin-top: 1.5rem; }

.faq__recherche > label {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--texte-sombre-attenue);
}

.faq__recherche input {
  width: 100%;
  padding: 0.75rem 0.9rem;
  font: inherit;
  font-size: 1rem;
  color: var(--clair);
  background: var(--voile-sombre);
  border: 1px solid var(--trait-sombre);
  border-radius: 10px;
}

.faq__recherche input::placeholder { color: rgba(241, 245, 249, 0.45); }

.faq__recherche input:focus-visible {
  outline: 2px solid var(--vert);
  outline-offset: 2px;
  border-color: transparent;
}

.faq__recherche .note { margin: 0.5rem 0 0; min-height: 1.25rem; }
.section--sombre .faq__recherche .note { color: var(--texte-sombre-attenue); }

/* Le sommaire : des pastilles, le vocabulaire visuel des filtres de quartier et
   des volets d'environnement — un même geste, restreindre ce qu'on regarde. */
.faq__sommaire { margin-bottom: 0.5rem; }

.faq__sommaire-titre {
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ardoise);
  margin-bottom: 0.75rem;
}

.faq__sommaire ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.faq__sommaire a {
  display: inline-block;
  padding: 0.4rem 0.85rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--nuit);
  text-decoration: none;
  background: rgba(100, 116, 139, 0.08);
  border: 1px solid var(--trait-clair);
  border-radius: 999px;
}

.faq__sommaire a:hover,
.faq__sommaire a:focus-visible {
  border-color: var(--vert);
  color: var(--nuit);
}

.faq__theme + .faq__theme { margin-top: 2.5rem; }

.faq__theme > h2 {
  font-size: 1.375rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--trait-clair);
  margin-bottom: 0.25rem;
  /* Une ancre visée depuis le sommaire ne doit pas se coller au bord haut de
     l'écran, ni passer sous le bandeau. */
  scroll-margin-top: 5rem;
}

.faq__question {
  border-bottom: 1px solid var(--trait-clair);
  scroll-margin-top: 5rem;
}

.faq__question > summary {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 1rem 0;
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--nuit);
  cursor: pointer;
  list-style: none;
}

/* Le chevron est dessiné à la place du marqueur natif, que les navigateurs
   rendent de façon trop différente pour qu'on puisse l'aligner. */
.faq__question > summary::-webkit-details-marker { display: none; }

.faq__question > summary::before {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: 0.35rem;
  border-right: 2px solid var(--vert);
  border-bottom: 2px solid var(--vert);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}

.faq__question[open] > summary::before { transform: rotate(45deg); }

.faq__question > summary:focus-visible {
  outline: 2px solid var(--vert);
  outline-offset: 2px;
  border-radius: 6px;
}

.faq__intitule { flex: 1; }

/* Le lien d'ancre ne se montre qu'au survol ou au clavier : présent en
   permanence, il ferait du bruit à droite de chaque question. Il reste
   atteignable à la tabulation, où le focus le fait apparaître. */
.faq__ancre {
  flex: none;
  color: var(--ardoise);
  text-decoration: none;
  font-weight: 700;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.faq__question > summary:hover .faq__ancre,
.faq__ancre:focus-visible { opacity: 1; }
.faq__ancre:hover { color: var(--vert); }

.faq__reponse { padding: 0 0 1.25rem; max-width: 68ch; }
.faq__reponse p { margin-bottom: 0.75rem; }
.faq__reponse p:last-child { margin-bottom: 0; }

.faq__vide {
  padding: 1.25rem;
  background: rgba(100, 116, 139, 0.06);
  border: 1px solid var(--trait-clair);
  border-radius: 12px;
}

/* Le lien discret qui mène du produit vers une question : du texte, jamais un
   bouton. Il propose une explication, il ne réclame pas un geste. */
.lien-faq {
  display: inline-block;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ardoise);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.lien-faq:hover { color: var(--vert); }
.section--sombre .lien-faq,
.carte--sombre .lien-faq { color: var(--texte-sombre-attenue); }

@media (min-width: 720px) {
  .faq__question > summary { font-size: 1.125rem; }
}
/* --- Mise en relation ----------------------------------------------------- */

/* La proposition tient en trois lignes : un filet, une action, une note.

   La première version était un pavé vert de sept lignes, et elle a été refaite
   après un retour d'usage : elle **répétait ce que la tuile disait déjà** trois
   centimètres plus haut, et pesait plus lourd que la réponse que le lecteur était
   venu chercher. Le filet horizontal remplace le bloc teinté — il sépare sans
   peindre, ce qui est exactement ce qu'on demande à une proposition qui ne doit
   jamais passer devant le contenu. */
.mer {
  margin-top: 0.9rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--trait-clair);
}

/* Un lien d'action, pas un second bouton : la tuile en porte déjà un (« Voir le
   détail »), et deux boutons de même poids feraient hésiter sur celui qui répond à
   la question. La flèche dit qu'il se passera quelque chose. */
.mer__action {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--nuit);
  text-decoration: none;
  cursor: pointer;
}

.mer__action:hover,
.mer__action:focus-visible { color: var(--vert); }

.mer__fleche {
  flex: none;
  color: var(--vert);
  transition: transform 0.15s ease;
}

.mer__action:hover .mer__fleche { transform: translateX(2px); }

/* La seule chose que la proposition ajoute vraiment à l'écran : le fait que la
   tuile ne dit pas. En gris ardoise, une ligne, sous l'action. */
.mer__note {
  margin: 0.35rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--ardoise);
}

/* --- La fenêtre du formulaire --------------------------------------------- */

/* Elle n'existe pas tant que personne n'a cliqué : le conteneur est vide, et un
   conteneur vide ne doit rien peindre ni rien intercepter. D'où « :empty », qui
   évite un voile invisible posé par-dessus toute la page. */
.mer-modale:empty { display: none; }

.mer-modale {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
  background: rgba(15, 23, 42, 0.6);
}

/* **Trois zones, et c'est la correction d'un vrai défaut.** La première version
   posait « max-height » sur la carte entière avec un défilement interne : le bouton
   d'envoi se retrouvait sous le pli du cadre, à moitié coupé, et l'on ne savait plus
   si le formulaire était terminé. Ici, seul le **corps** défile ; l'en-tête et le
   pied restent en place, si bien que le bouton qui termine la démarche est toujours
   visible, quelle que soit la hauteur de l'écran. */
.mer-modale__cadre {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 30rem;
  max-height: min(88vh, 46rem);
  padding: 0;
  overflow: hidden;
}

.mer-modale__entete {
  flex: none;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.25rem 1.5rem 0.9rem;
  border-bottom: 1px solid var(--trait-clair);
}

.mer-modale__titre {
  margin: 0;
  font-size: 1.0625rem;
  line-height: 1.3;
}

/* Le rappel du motif, sous le titre : « Ce bien est proposé 9 % au-dessus des prix
   constatés. » Il n'a sa place qu'ici — dans la tuile, il répétait le chiffre affiché
   juste au-dessus ; une fois la fenêtre ouverte, la tuile n'est plus visible. */
.mer-modale__motif {
  margin: 0.3rem 0 0;
  font-size: 0.875rem;
  line-height: 1.45;
  color: var(--ardoise);
  font-weight: 400;
}

.mer-modale__fermer {
  flex: none;
  border: 0;
  background: none;
  font-size: 1.6rem;
  line-height: 1;
  padding: 0 0.15rem;
  margin: -0.25rem -0.25rem 0 0;
  cursor: pointer;
  color: var(--ardoise);
}

.mer-modale__fermer:hover { color: var(--nuit); }

/* **Le formulaire doit relayer la colonne flexible**, et l'oublier a un effet
   très précis : il est l'enfant direct de la carte, et c'est lui qui contient le
   corps et le pied. Sans ces trois lignes, le corps ne se contracte pas, le pied
   est poussé sous le pli, et le bouton d'envoi disparaît de l'écran — exactement
   le défaut qu'on voulait corriger. Constaté en regardant une capture, pas en
   relisant la feuille de style. */
.mer-modale__cadre > form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* « min-height: 0 » n'est pas décoratif : sans lui, un enfant de conteneur flexible
   refuse de descendre sous la hauteur de son contenu, et le défilement interne ne
   se déclenche jamais. */
.mer-modale__corps {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 1.15rem 1.5rem 1.25rem;
}

.mer-modale__corps > .champ:first-child { margin-top: 0; }

.mer-modale__pied {
  flex: none;
  padding: 0.9rem 1.5rem 1.15rem;
  border-top: 1px solid var(--trait-clair);
  background: #fff;
}

.mer-modale__pied .actions { margin: 0; }

.mer-modale__succes {
  margin: 0 0 0.75rem;
  font-weight: 600;
  color: var(--vert);
}

/* Le renvoi est un lien d'apparence, pas un second bouton : deux boutons de même
   poids feraient hésiter sur celui qui termine la démarche. */
.mer-formulaire__renvoi { margin-top: 0.9rem; }

.lien-bouton {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 0.875rem;
  color: var(--ardoise);
  text-decoration: underline;
  cursor: pointer;
}

.lien-bouton:hover { color: var(--nuit); }

/* Le champ de code se lit chiffre par chiffre : grand, espacé, centré. Sur un
   téléphone, c'est la seule saisie du parcours qu'on fait en regardant ailleurs — la
   notification du SMS — et il faut pouvoir vérifier d'un coup d'œil qu'on n'a pas
   sauté un chiffre. */
.champ__code {
  font-size: 1.5rem;
  letter-spacing: 0.45em;
  text-align: center;
  font-weight: 700;
  padding-left: 0.45em;
}

/* Sur un téléphone, la fenêtre devient une feuille posée en bas de l'écran : c'est
   la forme qu'on y attend, et surtout celle dont le bas — donc le bouton d'envoi —
   tombe sous le pouce plutôt qu'au milieu de l'écran. */
@media (max-width: 640px) {
  .mer-modale {
    padding: 0;
    align-items: flex-end;
  }

  .mer-modale__cadre {
    max-width: none;
    max-height: 92vh;
    border-radius: 16px 16px 0 0;
  }

  .mer-modale__entete { padding: 1.1rem 1.15rem 0.8rem; }
  .mer-modale__corps { padding: 1rem 1.15rem 1.15rem; }
  .mer-modale__pied { padding: 0.85rem 1.15rem 1.15rem; }
  .mer-modale__pied .bouton { width: 100%; justify-content: center; }
}

/* La même fenêtre, servie en pleine page à qui n'exécute pas de JavaScript. Le
   voile et le positionnement fixe n'ont plus de sens : il n'y a rien derrière à
   assombrir, et la carte doit se poser dans le flux comme n'importe quel
   formulaire du site. */
.mer-modale--page {
  position: static;
  padding: 0;
  background: none;
  display: block;
}

.mer-modale--page .mer-modale__cadre {
  max-width: none;
  max-height: none;
  overflow: visible;
}

.mer-modale--page .mer-modale__corps { overflow: visible; }

/* La croix de fermeture ne mène nulle part sans JavaScript : le lien « Retour à
   l'analyse » posé au-dessus de la carte est là pour ça. */
.mer-modale--page .mer-modale__fermer { display: none; }

/* Un message d'erreur de formulaire.

   **La classe n'existait pas**, et le message s'affichait donc en texte ordinaire, au
   milieu d'un formulaire, sans rien pour le distinguer d'une consigne. Signalé en
   service : « Ce code ne correspond pas » passait inaperçu, et l'on ressaisissait le
   même code sans comprendre.

   Le rouge est l'une des deux couleurs de **signal** de la charte (§ 7) : il ne sert
   qu'à porter un niveau, jamais de fond ni de texte courant. Le fond très pâle et le
   filet à gauche suffisent à accrocher l'œil sans peindre le formulaire. */
.erreur {
  margin: 0.75rem 0;
  padding: 0.6rem 0.8rem;
  border-left: 3px solid var(--rouge);
  border-radius: 0 6px 6px 0;
  background: rgba(239, 68, 68, 0.08);
  color: var(--nuit);
  font-size: 0.9375rem;
  line-height: 1.45;
  font-weight: 600;
}

/* Sous un champ, l'erreur se fait plus discrète : elle commente une saisie
   précise, elle ne résume pas l'état du formulaire. */
.champ .erreur {
  margin: 0.35rem 0 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--rouge);
  font-size: 0.875rem;
  font-weight: 600;
}

/* --- Les demandes, en fiches ---------------------------------------------- */

/* **Une fiche par demande, et non une ligne de tableau.** Le tableau était la
   mauvaise forme parce que ce n'est pas le bon geste : on ne parcourt pas ces
   lignes du regard, on en traite une. Douze colonnes dont deux portaient un
   formulaire débordaient forcément — défilement horizontal, zone de notes à cheval
   sur la colonne voisine, adresse e-mail coupée avant d'être lisible.

   La lecture d'ensemble, elle, est déjà portée par les compteurs et l'entonnoir plus
   haut ; et l'export CSV rend un vrai tableau, à ouvrir dans un tableur. */
.demandes {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  margin-top: 1rem;
}

.demande {
  border: 1px solid var(--trait-sombre);
  border-radius: 10px;
  background: var(--voile-sombre);
  overflow: hidden;
}

.demande__entete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--trait-sombre);
}

.demande__identite { display: flex; align-items: center; gap: 0.6rem; }

.demande__type { font-weight: 700; color: var(--clair); font-size: 0.95rem; }

.demande__statut {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  color: var(--nuit);
  background: var(--ardoise);
}

/* Le statut porte une couleur, et une seule à la fois. « nouveau » est ambre — il
   appelle un geste ; « transmis » est vert — quelque chose est sorti et c'est
   consigné. Les autres restent gris : ils n'appellent rien. */
.demande__statut--nouveau { background: var(--ambre); }
.demande__statut--transmis { background: var(--vert); }
.demande__statut--retire { background: var(--rouge); }

.demande__date { color: var(--ardoise); font-size: 0.8rem; white-space: nowrap; }

/* Deux colonnes sur large écran — qui rappeler, à quel sujet — et une seule dès
   qu'on manque de place. Rien n'est jamais coupé. */
.demande__corps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
  gap: 1rem 1.75rem;
  padding: 0.9rem 1rem;
}

.demande__titre {
  margin: 0 0 0.45rem;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ardoise);
}

.demande__prenom {
  margin: 0 0 0.2rem;
  font-size: 1rem;
  font-weight: 700;
  color: var(--clair);
}

/* Le téléphone et l'adresse sont des **liens** : appeler ou écrire est le geste qui
   suit, et les recopier à la main était le vrai coût de l'ancien tableau. */
.demande__contact {
  margin: 0 0 0.15rem;
  font-size: 0.9rem;
  /* Une adresse e-mail longue n'offre aucune césure : sans permission de couper,
     elle pousse sa colonne, donc sa grille, donc la page entière. */
  overflow-wrap: anywhere;
}
.demande__contact a { color: var(--cyan); text-decoration: none; }
.demande__contact a:hover { text-decoration: underline; }

.demande__adresse { margin: 0 0 0.2rem; color: var(--clair); font-size: 0.92rem; }

.demande__contexte { margin: 0; color: var(--ardoise); font-size: 0.85rem; }

.demande__etiquette {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  border-radius: 4px;
  background: var(--trait-sombre);
  color: var(--clair);
  font-size: 0.78rem;
  font-weight: 600;
}

/* Ce que la personne a écrit, présenté comme une citation : c'est la seule chose de
   cette fiche qui vienne d'elle, et non de nos sources. */
.demande__message {
  margin: 0.6rem 0 0;
  padding: 0.5rem 0.75rem;
  border-left: 3px solid var(--cyan);
  background: rgba(6, 182, 212, 0.07);
  border-radius: 0 6px 6px 0;
  color: var(--texte-sombre-attenue);
  font-size: 0.88rem;
  font-style: italic;
}

.demande__retire {
  margin: 0;
  color: var(--ardoise);
  font-size: 0.88rem;
  line-height: 1.5;
}

.demande__actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
  gap: 0.75rem 1.75rem;
  padding: 0.85rem 1rem;
  border-top: 1px solid var(--trait-sombre);
}

.demande__formulaire { display: flex; flex-direction: column; gap: 0.5rem; margin: 0; }

.demande__formulaire input[type="text"],
.demande__formulaire textarea {
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--trait-sombre);
  border-radius: 6px;
  background: rgba(15, 23, 42, 0.5);
  color: var(--clair);
  font: inherit;
  font-size: 0.88rem;
}

.demande__formulaire textarea { resize: vertical; min-height: 2.6rem; }

.demande__formulaire input::placeholder,
.demande__formulaire textarea::placeholder { color: var(--ardoise); }

.demande__boutons {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.demande__traitee {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--ardoise);
  font-size: 0.82rem;
}

.demande__transmis {
  margin: 0;
  color: var(--texte-sombre-attenue);
  font-size: 0.88rem;
  line-height: 1.5;
}

.demande__transmis strong { color: var(--vert); }

/* La pastille était dessinée pour un conteneur flexible (le tableau de santé) : posée
   dans une ligne de texte, il lui faut son propre affichage et son propre alignement,
   faute de quoi elle se réduit à rien. */
.demande__contact .admin__pastille {
  display: inline-block;
  vertical-align: baseline;
  margin: 0 0 0 .25rem;
}

/* Un intitulé de champ ne se coupe pas en deux : « Votre / numéro » se lisait comme
   deux lignes de tableau, et l'œil cherchait la seconde valeur. */
.admin__bornes label { white-space: nowrap; }

/* Une empreinte tronquée se lit de gauche à droite comme un mot, jamais comme un
   montant : l'aligner à droite la rangeait dans la colonne des chiffres. La chasse
   fixe, elle, sert à comparer deux empreintes d'un coup d'œil — c'est le seul usage
   qu'on en fait (§ e3). */
.admin__tableau td.admin__empreinte {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .78rem;
  color: var(--clair);
  white-space: nowrap;
}

/* --- Une fiche se replie -------------------------------------------------- */

/* Le triangle par défaut du navigateur est retiré et remplacé par un chevron dessiné
   en CSS : celui de Chrome, de Firefox et de Safari n'ont ni la même forme, ni la même
   place, ni la même couleur, et l'un des trois se serait retrouvé collé au titre. */
.demande > summary { list-style: none; cursor: pointer; }
.demande > summary::-webkit-details-marker { display: none; }

/* Le repère d'ouverture est **à gauche du titre**, là où l'œil descend la colonne des
   fiches. À droite, il aurait fallu le chercher au bout d'une ligne large. */
.demande > summary::before {
  content: "";
  flex: 0 0 auto;
  width: .42rem;
  height: .42rem;
  margin-right: .1rem;
  border-right: 2px solid var(--ardoise);
  border-bottom: 2px solid var(--ardoise);
  transform: rotate(-45deg);
  transition: transform .15s ease;
}
.demande[open] > summary::before { transform: rotate(45deg); }

/* Replié, le résumé est toute la fiche : il ne porte donc pas de filet en bas, qui
   soulignerait un vide. Ouvert, il redevient un en-tête et le filet revient. */
.demande > summary { border-bottom: none; }
.demande[open] > summary { border-bottom: 1px solid var(--trait-sombre); }

.demande > summary:hover { background: rgba(241, 245, 249, 0.04); }

/* Le clavier doit voir où il est : « summary » est focalisable, et sans marque
   visible on plie des fiches sans savoir laquelle. */
.demande > summary:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: -2px;
}

/* Ce qui distingue une demande d'une autre quand elles sont toutes repliées. */
.demande__apercu {
  color: var(--texte-sombre-attenue);
  font-size: .88rem;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* L'identité doit pouvoir se contracter, sinon un prénom long pousse la date hors du
   cadre au lieu d'être coupé. */
.demande__identite { min-width: 0; flex: 1 1 auto; }

/* --- La barre du site sur un téléphone ------------------------------------ */

/* Elle ne débordait pas — mesuré à 320 comme à 390 px — mais elle se lisait mal :
   « space-between » gardait le logo centré verticalement pendant que les six liens
   se répartissaient sur trois rangées **à sa droite**, si bien que la marque se
   retrouvait au milieu de son propre menu.

   Sous cette largeur, la barre prend deux temps : le logo seul, puis la navigation
   sur sa rangée, alignée à gauche comme tout le reste de la page. C'est le même
   raisonnement que pour la barre d'exploitation. */
@media (max-width: 760px) {
  .entete__barre {
    flex-wrap: wrap;
    row-gap: 0.5rem;
    min-height: 0;
  }
  .entete__navigation {
    flex: 1 1 100%;
    justify-content: flex-start;
    gap: 0.35rem 1.1rem;
  }
}

/* --- Les ventes voisines sur un téléphone --------------------------------- */

/* Cinq colonnes dont quatre à largeur fixe : 25 rem au total, soit 400 px, dans un
   écran qui en compte 320. Le débordement ne s'est pas vu dans l'audit — la commune
   du bien d'essai n'ayant pas pu être déterminée, aucune vente n'était affichée —
   mais il est certain sur une analyse aboutie. C'est exactement le genre de défaut
   qu'on ne trouve qu'une fois en service, et sur la page la plus consultée.

   Deux pistes souples au lieu de cinq fixes : les cinq valeurs s'y rangent d'elles-
   mêmes, dans l'ordre du document, et l'en-tête emploie la même grille — donc les
   colonnes s'alignent d'une ligne à l'autre. **Rien n'est retiré** : on ne cache pas
   une donnée parce que l'écran est petit, on la range autrement.

   Aucun « nth-child » ici, et c'est délibéré : le résumé d'une vente porte deux
   enfants de plus — la pastille et le libellé pour lecteurs d'écran —, tous deux hors
   du flux. Compter sur des rangs, c'est dépendre d'une liste d'enfants qu'un ajout
   futur décalerait sans rien casser de visible. */
@media (max-width: 700px) {
  .ventes__entete,
  .vente__resume {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 0.1rem 0.75rem;
  }
  /* Alignées à gauche : sur deux colonnes, un alignement à droite éloignerait le
     chiffre de son intitulé au lieu de l'en rapprocher (leçon des tableaux, § L5). */
  .ventes__entete .nombre,
  .vente__resume .nombre { text-align: left; }
}

/* --- Documents juridiques (session 17) -----------------------------------

   Trois pages — mentions légales, politique de confidentialité, conditions
   d'utilisation — qui ne se lisent pas comme le reste du site : on ne les
   parcourt pas, on y **cherche un paragraphe**. D'où une colonne étroite, des
   titres qui se repèrent de loin, et rien qui décore.

   Deux composants seulement s'y ajoutent, et tous deux servent à dire un état
   d'inachèvement. C'est le point de fond : une page de conformité incomplète
   qui *paraît* complète ne se corrige jamais, puisque rien ne la signale. Ces
   deux marques existent pour qu'on ne puisse pas ne pas voir ce qui manque. */

/* Le bandeau « document provisoire ». Ambre — c'est un avertissement, pas une
   erreur : la page est utilisable, elle n'est simplement pas validée. Il se
   retire par un seul réglage (LEGAL_PROVISOIRE), jamais en modifiant un
   gabarit : un bandeau qu'il faut aller supprimer à la main dans trois fichiers
   survit toujours quelque part. */
.bandeau-juridique {
  margin: 0 0 2rem;
  padding: 0.9rem 1.1rem;
  border-left: 4px solid var(--ambre);
  border-radius: 0 8px 8px 0;
  background: rgba(245, 158, 11, 0.1);
  color: var(--nuit);
  font-size: 0.9375rem;
  line-height: 1.5;
}

.bandeau-juridique strong { font-weight: 800; }

/* La marque d'un renseignement légal qui n'existe pas encore.

   Avant l'ouverture publique, cette marque était volontairement laide — un
   cadre rouge en tirets. Le raisonnement tenait : une valeur absente rendue
   discrètement se lit comme une valeur, et la mention légale reste fausse
   pendant des mois sans que personne ne s'en aperçoive.

   Il ne tient plus une fois la page lue par des visiteurs. Un cadre rouge sur
   des mentions légales ne se lit pas « il manque un numéro », il se lit « ce
   site est cassé » — et le doute déborde sur tout le reste de la page. La
   phrase, elle, dit désormais le fait en toutes lettres : c'est le texte qui
   porte l'information, et le style n'a plus qu'à empêcher qu'on la confonde
   avec une valeur définitive. */
.mention-en-attente {
  font-style: italic;
  color: var(--ardoise);
  border-bottom: 1px dotted var(--ardoise);
}

/* Le corps d'un document juridique : de la prose, des listes, des titres. Rien
   d'autre — pas de carte, pas de tuile, pas de colonne. */
.document-legal { max-width: 46rem; }

.document-legal > * + * { margin-top: 1.15rem; }

.document-legal h2 {
  margin-top: 2.75rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(100, 116, 139, 0.25);
  font-size: 1.375rem;
}

.document-legal h3 {
  margin-top: 1.75rem;
  font-size: 1.0625rem;
}

.document-legal ul,
.document-legal ol {
  padding-left: 1.25rem;
}

.document-legal li + li { margin-top: 0.45rem; }

/* La date de dernière mise à jour, et le rappel de ce qu'on lit. */
.document-legal__date {
  color: var(--ardoise);
  font-size: 0.875rem;
}

/* Un tableau dans un document juridique — « quelle donnée, pourquoi, combien de
   temps » se lit mal en prose. Il défile seul plutôt que de pousser la page :
   c'est la règle de la session r1, une largeur fixe dans un téléphone déborde. */
.document-legal__tableau {
  overflow-x: auto;
  min-width: 0;
  margin-top: 1.15rem;
}

.document-legal__tableau table {
  width: auto;
  max-width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

.document-legal__tableau th,
.document-legal__tableau td {
  padding: 0.5rem 0.9rem 0.5rem 0;
  border-bottom: 1px solid rgba(100, 116, 139, 0.2);
  text-align: left;
  vertical-align: top;
}

.document-legal__tableau th { font-weight: 700; }

/* --- Le rapport payant --------------------------------------------------- */

/* La liste des dix sections, sur la page de vente. Une ligne par section : ce qu'on
   vend doit se lire d'un coup d'œil, sans dépliage ni survol. */

.rapport-sections {
  list-style: none;
  counter-reset: section;
  margin: 1rem 0 0;
  padding: 0;
}

.rapport-sections__item {
  counter-increment: section;
  display: grid;
  grid-template-columns: 1.9rem 1fr;
  gap: 0.15rem 0.6rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid rgba(100, 116, 139, 0.16);
}

.rapport-sections__item::before {
  content: counter(section) ".";
  grid-row: span 2;
  color: var(--vert);
  font-weight: 700;
}

.rapport-sections__titre { font-weight: 600; }

.rapport-sections__detail {
  grid-column: 2;
  color: var(--ardoise);
  font-size: 0.9375rem;
}

/* Le prix. Grand, mais sans bouton coloré ni animation : c'est une information, pas
   une injonction — la règle du § 1 quater appliquée à une vraie vente. */

.rapport-prix {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.75rem;
  margin: 0 0 1rem;
}

.rapport-prix__montant {
  font-size: 1.9rem;
  font-weight: 800;
  letter-spacing: -0.01em;
}

.rapport-prix__mention { color: var(--ardoise); font-size: 0.9375rem; }

/* Les deux cases. Visuellement distinctes l'une de l'autre par leur cadre, et
   **jamais** différenciées en taille ni en gris : le § 1 ter interdit qu'une option
   protectrice soit moins visible que l'autre, et ces deux-là engagent autant. */

.rapport-consentements {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin: 1rem 0;
}

.rapport-consentement {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.7rem;
  align-items: start;
  padding: 0.85rem 1rem;
  border: 1px solid rgba(100, 116, 139, 0.3);
  border-radius: 0.5rem;
  font-size: 0.9375rem;
  line-height: 1.5;
  cursor: pointer;
}

.rapport-consentement input {
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.15rem;
  accent-color: var(--vert);
}

.rapport-consentement:focus-within { border-color: var(--vert); }

/* Les trois étapes de l'attente. Aucune ne progresse toute seule : chacune reflète le
   statut réel en base (§ r1), et une barre qui avancerait sans rien derrière serait un
   mensonge facturé. */

.rapport-etapes {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.rapport-etape {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--ardoise);
  font-size: 0.9375rem;
}

.rapport-etape::before {
  content: "";
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  border: 2px solid rgba(100, 116, 139, 0.4);
  flex-shrink: 0;
}

.rapport-etape--faite { color: var(--nuit); }
.rapport-etape--faite::before { background: var(--vert); border-color: var(--vert); }

.rapport-etape--en_cours { color: var(--nuit); font-weight: 600; }
.rapport-etape--en_cours::before { border-color: var(--vert); }

/* Une rangée d'actions où le texte introduit le bouton plutôt que de le suivre en
   petits caractères. Elle sert là où une action secondaire — créer un compte quand on
   arrive sur la page de connexion — doit se voir autant que l'action principale : une
   création de compte reléguée en note de bas de page se cherche, et quelqu'un qu'un
   bouton vient d'envoyer là ne devrait pas avoir à lire pour la trouver. */
.actions--separees {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid rgba(100, 116, 139, 0.2);
}

/* --- Le justificatif d'achat --------------------------------------------- */

/* Un tableau **du site**, sur fond clair. Il a existé un temps sous la classe du poste
   de pilotage, dont le texte de cellule est prévu pour un fond sombre : la ligne était
   bien là, écrite en blanc sur blanc, et le justificatif paraissait n'avoir aucune
   prestation. Rien n'échouait — c'est le genre de défaut qui ne se voit qu'à l'œil. */
.justificatif__tableau {
  width: 100%;
  border-collapse: collapse;
  margin: 1rem 0;
  font-size: 0.9375rem;
}

.justificatif__tableau th {
  text-align: left;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ardoise);
  border-bottom: 1px solid rgba(100, 116, 139, 0.3);
  padding: 0.5rem 0.75rem 0.5rem 0;
}

.justificatif__tableau td {
  color: var(--nuit);
  border-bottom: 1px solid rgba(100, 116, 139, 0.15);
  padding: 0.75rem 0.75rem 0.75rem 0;
  vertical-align: top;
}

.justificatif__num { text-align: right; padding-right: 0 !important; }

/* Un rapport payé, pas encore livré. Ambre : ce n'est ni un succès (rien n'est encore
   téléchargeable) ni une panne (tout se passe bien, il faut une minute). L'étiquette
   remplace le bouton d'achat — ce n'est pas un ornement mais un garde-fou : sans elle,
   la page reproposait l'achat et un second paiement aboutissait. */
.etiquette--attente {
  background: rgba(245, 158, 11, 0.15);
  border-color: rgba(245, 158, 11, 0.45);
  color: var(--nuit);
}

/* --- Les ventes de rapports, dans le poste de pilotage -------------------- */

/* **Tout ce bloc est cantonné à « .admin », et il faut dire pourquoi.** Le mot
   « vente » désigne deux choses dans ce produit : une **mutation foncière** affichée
   dans l'onglet Historique du site public, et la **vente d'un rapport** listée ici. Les
   deux portaient la classe « .vente », et celle-ci, définie plus bas dans la feuille,
   gagnait la cascade : les ventes DVF du site clair recevaient le fond bleu nuit prévu
   pour l'exploitation, et s'affichaient en barres grises illisibles.

   Rien n'échouait — c'est la marque de cette famille de défauts. C'est exactement le
   piège de « .duo » (§ L1) : deux définitions de la même classe se laissent écraser en
   silence et divergent à la première correction. Le cantonnement rend la collision
   inoffensive ; le test « tests/unit/test_classes_en_collision.py » la rend visible. */

/* Ces fiches ont d'abord emprunté « admin__incident », qui est un bloc de journal en
   chasse fixe fait pour des piles d'appel : les ventes sortaient en petit, en
   monospace, et leurs libellés partaient à un bout de la page pendant que les valeurs
   partaient à l'autre. Une vente n'est pas une trace technique — c'est une ligne sur
   laquelle on agit, une par une. */
.admin .vente {
  border: 1px solid rgba(241, 245, 249, 0.12);
  border-radius: var(--rayon-petit);
  background: rgba(15, 23, 42, 0.45);
  margin-bottom: 0.6rem;
  overflow: hidden;
}

.admin .vente[open] { border-color: rgba(241, 245, 249, 0.22); }

/* Le résumé porte de quoi **décider** : statut, commune, client, date, montant. Ni le
   modèle, ni la durée, ni le coût — ce sont des raisons d'ouvrir une fiche, pas de la
   choisir. */
.admin .vente__tete {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.9rem;
  padding: 0.7rem 0.9rem;
  cursor: pointer;
  color: var(--clair);
  font-size: 0.875rem;
  list-style: none;
}

.admin .vente__tete::-webkit-details-marker { display: none; }
.admin .vente__tete::before {
  content: "▸";
  color: var(--ardoise);
  font-size: 0.8em;
}
.admin .vente[open] .vente__tete::before { content: "▾"; }

.admin .vente__commune { font-weight: 700; }
.admin .vente__email { color: var(--ardoise); word-break: break-all; }
.admin .vente__date { color: var(--ardoise); font-variant-numeric: tabular-nums; }

/* Le montant pousse à droite quand la place le permet, et revient dans le flux quand
   elle manque : sur un téléphone, une valeur collée au bord droit se lit mal. */
.admin .vente__montant {
  margin-left: auto;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* La couleur ne peint que ce qui appelle un geste — c'est la règle du § L5. « Prêt »
   est vert parce que quelque chose a été livré ; « payé » et « en génération » sont
   ambre parce que quelqu'un attend ; un échec est rouge. Un remboursement et une
   commande jamais payée restent gris : il n'y a rien à faire. */
.admin .vente__pastille {
  display: inline-flex;
  align-items: center;
  padding: 0.1rem 0.55rem;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

.admin .vente__pastille--ok {
  background: rgba(34, 197, 94, 0.18);
  border-color: rgba(34, 197, 94, 0.45);
  color: #22C55E;
}

.admin .vente__pastille--attente {
  background: rgba(245, 158, 11, 0.18);
  border-color: rgba(245, 158, 11, 0.45);
  color: #F59E0B;
}

.admin .vente__pastille--alerte {
  background: rgba(239, 68, 68, 0.16);
  border-color: rgba(239, 68, 68, 0.45);
  color: #EF4444;
}

.admin .vente__pastille--neutre {
  background: rgba(100, 116, 139, 0.18);
  border-color: rgba(100, 116, 139, 0.4);
  color: var(--ardoise);
}

.admin .vente__corps {
  padding: 0 0.9rem 0.9rem;
  border-top: 1px solid rgba(241, 245, 249, 0.08);
  color: var(--clair);
  font-size: 0.875rem;
}

/* Une grille, et non une liste en « space-between ». Sur un conteneur large, cette
   dernière pose le libellé contre un bord et la valeur contre l'autre, et l'œil ne
   relie plus les deux — c'est le défaut corrigé au § L5, qui s'était reproduit ici. */
.admin .vente__champs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  gap: 0.75rem 1.5rem;
  margin: 0.9rem 0 0;
}

.admin .vente__champs > div { min-width: 0; }

.admin .vente__champs dt {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ardoise);
}

.admin .vente__champs dd {
  margin: 0.15rem 0 0;
  color: var(--clair);
  overflow-wrap: anywhere;
}

.admin .vente__titre {
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ardoise);
  margin: 1.2rem 0 0.4rem;
}

/* Le libellé consenti se lit comme une citation : ce sont les mots exacts que
   l'acheteur a lus, et c'est ce qu'on produit en cas de contestation. */
.admin .vente__consentement {
  margin: 0 0 0.5rem;
  padding: 0.6rem 0.85rem;
  border-left: 2px solid rgba(6, 182, 212, 0.5);
  background: rgba(15, 23, 42, 0.5);
  border-radius: 0 var(--rayon-petit) var(--rayon-petit) 0;
  font-size: 0.82rem;
}

.admin .vente__consentement p { margin: 0 0 0.25rem; }
.admin .vente__consentement p:last-child { margin-bottom: 0; }

.admin .vente__erreur {
  margin: 0.9rem 0 0;
  padding: 0.55rem 0.85rem;
  border-radius: var(--rayon-petit);
  border: 1px solid rgba(239, 68, 68, 0.4);
  background: rgba(239, 68, 68, 0.1);
  overflow-wrap: anywhere;
}

.admin .vente__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  margin-top: 1.2rem;
  padding-top: 0.9rem;
  border-top: 1px solid rgba(241, 245, 249, 0.08);
}

.admin .vente__remboursement {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.5rem;
  margin-left: auto;
}

.admin .vente__remboursement label {
  display: block;
  width: 100%;
  font-size: 0.72rem;
  color: var(--ardoise);
}

.admin .vente__remboursement input {
  min-width: min(18rem, 100%);
  padding: 0.4rem 0.6rem;
  border-radius: var(--rayon-petit);
  border: 1px solid rgba(241, 245, 249, 0.18);
  background: rgba(15, 23, 42, 0.7);
  color: var(--clair);
  font: inherit;
}

/* --- La page de vente du rapport ----------------------------------------- */

/* Deux colonnes qui se replient d'elles-mêmes. La forme « minmax(min(N, 100%), 1fr) »
   et non « minmax(N, 1fr) » : sous N, la seconde ne se contracte pas, elle **pousse la
   page** — c'est le piège des grilles souples relevé au § r1, et il ne se voit sur
   aucune capture d'écran, celle-ci étant tronquée à la largeur de la fenêtre. */
.vente-grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
  gap: 1.25rem;
  align-items: start;
}

/* La rangée de décision est délibérément **déséquilibrée** : ce qu'on vient faire est
   d'acheter, et deux colonnes égales feraient de la liste des limites une alternative
   au paiement plutôt que son avertissement. Les limites restent pleinement lisibles —
   c'est le point —, simplement plus étroites. */
@media (min-width: 62rem) {
  .vente-grille--decision { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
}

/* --- Le héros de la page de vente ---------------------------------------- */

/* La page s'ouvrait sur un titre de rubrique et quatre cartes de même poids : rien n'y
   disait à quoi sert le document, et le prix n'apparaissait qu'après deux écrans. Le
   héros reprend les blocs de l'accueil — même fond, même halo, même bandeau de
   réassurance — plutôt que d'inventer une mise en page pour cette page-là. */

/* **Le lien de retour, sur fond sombre.** Il héritait de la couleur de lien du site
   clair : gris ardoise sur bleu nuit, c'est-à-dire lisible de justesse et seulement sur
   un bon écran. On ne pose pas la règle sur « .hero a », qui l'emporterait sur la
   couleur des boutons du héros — c'est le piège du § 8septies, où une règle de contraste
   trop large a repeint des boutons en blanc sur blanc. */
.vente-retour { margin: 0 0 1.25rem; }
.vente-retour a { color: var(--texte-sombre-attenue); }
.vente-retour a:hover { color: var(--clair); }

.vente-hero { align-items: center; }

.vente-hero__achat {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
  margin-top: 2rem;
}

/* Le prix vit ici sur fond sombre : sa mention reprend le gris atténué du héros, sans
   quoi elle s'écrirait en ardoise sur bleu nuit — lisible de justesse, et seulement sur
   un bon écran. C'est la leçon du § 8septies : une règle qui fixe l'une des deux
   couleurs d'un contraste et laisse l'autre à l'héritage n'a rien fixé du tout. */
.vente-hero__achat .rapport-prix { margin: 0; }
.vente-hero__achat .rapport-prix__mention { color: var(--texte-sombre-attenue); }

/* La couverture, en grand. Une page A4 tient dans sa colonne sans jamais la pousser :
   « width: 100 % » sur l'image, et rien d'écrit en dur. */
.vente-hero__apercu {
  margin: 0;
  max-width: 26rem;
  justify-self: center;
}

/* Sur le bleu nuit, le filet gris des vignettes disparaît et la page paraît flotter
   sans bord. Un filet clair et une ombre plus franche la posent. */
.vente-hero__apercu .rapport-apercu__page {
  border-color: rgba(241, 245, 249, 0.22);
  box-shadow: 0 18px 45px rgba(15, 23, 42, 0.55);
}

.vente-hero__apercu .rapport-apercu__legende { color: var(--texte-sombre-attenue); }

/* Les dix sections sur deux colonnes quand il y a la place. En une seule, la liste se
   parcourt du pouce au lieu de se lire — et elle repousse d'autant le bloc d'achat. */
@media (min-width: 48rem) {
  .rapport-sections--deux {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 2rem;
  }
  .rapport-sections--deux .rapport-sections__item:nth-child(-n + 2) { border-top: 0; }
}

/* Ce qui se passe après le paiement. Trois étapes, numérotées, en petit : un achat dont
   on ne sait pas ce qu'il déclenche se remet au lendemain. */
.vente-suite {
  margin: 0;
  padding-left: 1.1rem;
  color: var(--ardoise);
  font-size: 0.9375rem;
}

.vente-suite li + li { margin-top: 0.25rem; }

/* Les pages du rapport, à une taille où l'on distingue ce qu'elles portent. Elles
   étaient à « 9rem » de piste minimale, ce qui donnait des vignettes de cent pixels
   dans une carte étroite : les graphiques — ce qui distingue précisément ce rapport
   d'une mise en page — y redevenaient les rectangles gris que le flou d'origine
   produisait déjà. La forme « minmax(min(N, 100 %), 1fr) » et non « minmax(N, 1fr) » :
   sous N, la piste ne se contracte pas, elle **pousse la page** (§ r1, § 36). */
.rapport-apercu {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  gap: 1.1rem;
  margin: 1rem 0 0;
}

/* **Deux colonnes, et non quatre, dès qu'il y a la place.** Quatre pages de front
   tiennent, mais à cent quatre-vingts pixels de large : on y voit qu'il y a des
   graphiques, pas ce qu'ils disent. Or c'est exactement ce qu'on vend. À deux colonnes,
   une page fait cinq cents pixels et se lit. */
@media (min-width: 62rem) {
  .rapport-apercu { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem; }
}

.rapport-apercu__figure { margin: 0; min-width: 0; }
.rapport-apercu__lien { display: block; }

/* --- L'aperçu agrandi, sur la page ---------------------------------------- */
/* Un « dialog » natif : le navigateur apporte seul la fermeture par Échap, le piégeage
   du clavier et le rôle d'accessibilité. Il n'est peint que lorsqu'il est ouvert —
   « dialog » est masqué d'office tant qu'il ne l'est pas. */
.apercu-agrandi {
  border: 0;
  padding: 0;
  background: transparent;
  max-width: min(92vw, 780px);
  max-height: 92vh;
  overflow: visible;
}
.apercu-agrandi::backdrop { background: rgba(15, 23, 42, 0.78); }
.apercu-agrandi__image {
  display: block;
  width: 100%;
  height: auto;
  max-height: 84vh;
  object-fit: contain;
  border-radius: var(--rayon-petit);
  background: #fff;
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.45);
}
.apercu-agrandi__legende {
  margin: 0.6rem 0 0;
  text-align: center;
  color: var(--clair);
  font-size: 0.9rem;
}
/* Le bouton de fermeture est **dans** la fenêtre et non sur l'image : posé dessus, il
   masquerait un coin de la page qu'on vient justement d'agrandir pour la lire. */
.apercu-agrandi__fermer {
  position: absolute;
  top: -2.2rem;
  right: 0;
  background: none;
  border: 0;
  color: var(--clair);
  font-size: 1.8rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.3rem;
  font-family: var(--police);
}

.rapport-apercu__page {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--rayon-petit);
  border: 1px solid rgba(100, 116, 139, 0.28);
  /* Les pages sont **nettes** : l'ombre porte, la vignette ne tremble pas. */
  box-shadow: 0 2px 10px rgba(15, 23, 42, 0.1);
  cursor: zoom-in;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.rapport-apercu__page:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.18);
}

@media (prefers-reduced-motion: reduce) {
  .rapport-apercu__page { transition: none; }
  .rapport-apercu__page:hover { transform: none; }
}

.rapport-apercu__legende {
  margin-top: 0.4rem;
  font-size: 0.8125rem;
  color: var(--ardoise);
  text-align: center;
}

/* --- « Mes achats » ------------------------------------------------------- */

/* Une carte par achat imposait de faire défiler pour comparer deux achats du même
   jour — précisément le cas où l'heure devient la seule chose qui les distingue. */
.achat__jour { display: block; }
.achat__heure { display: block; font-size: 0.8125rem; color: var(--ardoise); }
.achat__commune { display: block; font-weight: 600; }
.achat__adresse { display: block; font-size: 0.8125rem; color: var(--ardoise); }

.achat__montant { text-align: right; white-space: nowrap; }

.achat__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
}

/* --- Vue de rue : Panoramax d'abord, Google au clic ---------------------- */

/* La vignette de rue posée sur chaque adresse candidate. C'est le moyen le plus
   rapide de reconnaître qu'une adresse n'est pas la bonne — d'où sa place, à
   gauche, avant même le libellé. Elle disparaît quand la route rend 404 : le
   script la retire, et la rangée se referme d'elle-même. */
.adresse__vue {
  flex: none;
  width: 120px;
  height: 80px;
  object-fit: cover;
  border-radius: var(--rayon-petit) 0 0 var(--rayon-petit);
  display: block;
}

.vue-de-rue { margin: .75rem 0 0; }

/* Le cadre garde sa place que l'image soit là ou non : sans hauteur imposée,
   l'arrivée de l'iframe au clic ferait sauter toute la colonne sous le pointeur.
   Le rapport 3/2 est celui du recadrage fait côté serveur. */
.vue-de-rue__cadre {
  position: relative;
  aspect-ratio: 3 / 2;
  border-radius: var(--rayon-petit);
  overflow: hidden;
  background: var(--clair);
}

.vue-de-rue__cadre:empty { display: none; }

.vue-de-rue__photo,
.vue-de-rue__embed {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
}

.vue-de-rue__bouton { margin-top: .5rem; }

/* La réserve est écrite **avant** le clic, jamais après : c'est au moment de
   décider qu'elle sert. Le script la retire une fois la vue chargée, le geste
   ayant eu lieu. */
.vue-de-rue__reserve { margin-top: .35rem; }

.vue-de-rue--chargee .vue-de-rue__cadre { aspect-ratio: 4 / 3; }

@media (max-width: 640px) {
  .adresse__vue { width: 88px; height: 66px; }
}
