@charset "UTF-8";
/* =============================================================================
   SOURCÈNE — le système visuel.
   Établi le 30/08/2026. Aucune dépendance, aucune requête sortante.

   CE QUE CE FICHIER PROMET
   ------------------------
   Le site est un BULLETIN D'ANALYSE, pas un tableau de bord. Un tableau de bord
   promet un jugement synthétique ; un bulletin promet des mesures, des dates,
   des seuils et un laboratoire. C'est tout ce que le service a le droit de dire
   (CONTRAT §0 : aucun verdict, aucun classement, aucune alerte).

   Conséquences visuelles, toutes volontaires :
     - pas d'ombre portée, pas de dégradé, pas de carte flottante ;
     - des filets, comme sur un imprimé de laboratoire ;
     - pas de feu tricolore : le vert n'est pas « potable » et le rose n'est pas
       « danger ». Le rose dit UNE chose et une seule (voir PASTILLES) ;
     - aucune animation qui accompagnerait un chiffre : un résultat d'analyse ne
       se met pas en scène.

   LES TROIS RÔLES TYPOGRAPHIQUES — la règle la plus forte du site
   ---------------------------------------------------------------
     titres         Spectral
     texte          Atkinson Hyperlegible (dessinée pour la basse vision : un
                    service de santé publique se lit aussi mal voyant)
     données brutes IBM Plex Mono

   ⚠ RÈGLE FORTE : la CHASSE FIXE signale la chaîne du LABORATOIRE, mot pour mot.
   Dès qu'un chiffre est retraité par nous — une médiane, un compte, un âge — il
   CHANGE DE FONTE. Le lecteur voit d'un coup d'œil ce qui vient de la source et
   ce qui vient de nous. Si les deux finissent dans la même fonte, le site ment
   sur l'origine de ses chiffres.
     `.mesure`, `.sentinelle`, `.borne`  -> Plex Mono (la source)
     tout le reste                       -> Atkinson  (nous)

   INDEX DES CLASSES RÉUTILISABLES (contrat avec les pages)
   --------------------------------------------------------
     .fiche                 le bloc-bulletin ; accepte une colonne .rail
     .rail                  colonne marginale : références, dates, laboratoire
     .mesure                une valeur quantifiée, telle que le laboratoire l'a
                            écrite  (+ .forte pour la valeur de tête d'une fiche)
     .non-detecte           « non détecté (< 0,020 µg/L) »  — JAMAIS « 0 »   (I2)
     .sentinelle            TRACES, N.D., ILLISIBL, N.M., INCOMPT.           (I1)
     .borne                 le nombre du « < » à l'intérieur de .non-detecte (I2)
     .pastille-limite       dépassement d'une LIMITE de qualité — seul usage du rose
     .pastille-reference    dépassement d'une RÉFÉRENCE de qualité (≠ limite)
     .pastille-aucun-seuil  paramètre sans seuil réglementaire (24,3 % des analyses)
     .fraicheur             l'âge des données, donnée de premier plan         (I4)
     .tableau-large         conteneur qui défile seul ; le corps de page jamais
     .recherche/.suggestions  le champ commune et sa liste (voir app.js)

   CONTRASTES — MESURÉS, pas estimés (WCAG 2.1, sur la palette imposée)
   --------------------------------------------------------------------
     --ink       sur --paper   17,59:1 (clair)   14,29:1 (sombre)
     --ink-soft  sur --paper    7,61:1            7,99:1
     --accent    sur --paper    6,84:1            7,08:1
     --reagent   sur --paper    5,87:1            6,61:1
     --ink-faint sur --paper    3,30:1            4,35:1   ⚠ PAS pour du texte
     --censored  sur --paper    3,03:1            4,06:1   ⚠ voir ci-dessous
     --censored  sur --paper-2  2,83:1            3,82:1   ⚠ d'où : pas de zébrures

   ⚠⚠ --censored ne tient PAS le 4,5:1 exigé pour du petit texte (3,03:1 en
   clair). La direction artistique impose pourtant ce gris pour toute donnée
   censurée, et elle prime. Trois parades, toutes appliquées plus bas :
     1. la couleur n'est JAMAIS le seul canal — « non détecté » est écrit en
        toutes lettres, et la sentinelle porte un souligné pointillé ;
     2. --censored ne descend jamais sous 1rem, et ne se pose jamais sur
        --ground ni sur --paper-2 (d'où l'absence de zébrures dans les tableaux,
        qui feraient tomber le rapport à 2,83:1) ;
     3. `prefers-contrast: more` le remplace par --ink-soft (7,61:1).
   👉 Reste un arbitrage pour l'auteur : éclaircir --censored en clair, ou
   assumer. Ce n'est pas au CSS de trancher en silence.
   ============================================================================= */

/* -----------------------------------------------------------------------------
   1. POLICES — auto-hébergées, six fichiers.

   ⚠ Aucune requête vers fonts.googleapis.com. Appeler Google Fonts transmettrait
   l'adresse IP du visiteur à un tiers, sur un site où l'on vient chercher l'eau
   de SA commune : le trajet dit où l'on habite. C'est exactement la donnée qu'un
   service de santé publique n'exporte pas.

   ⚠ Les six .woff2 ne sont PAS dans le dépôt. Voir web/polices/A-RECUPERER.md.
   Tant qu'ils manquent, les piles de repli ci-dessous tiennent le site debout —
   il est moins juste, il n'est pas cassé.

   Pas d'`unicode-range` : cette déclaration PROMET qu'un fichier couvre une
   plage, et une promesse fausse ne produit aucune erreur, juste des glyphes pris
   ailleurs. Sans elle, le repli se fait glyphe par glyphe. Plus honnête.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Spectral";
  src: url("/polices/spectral-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Spectral";
  src: url("/polices/spectral-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  src: url("/polices/atkinson-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  src: url("/polices/atkinson-700.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("/polices/plex-mono-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("/polices/plex-mono-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}

/* -----------------------------------------------------------------------------
   2. JETONS.

   ⚠ TOUTE valeur de couleur du site est écrite ICI, sur `:root` nu, et nulle
   part ailleurs. Les blocs de thème plus bas ne font que RE-CÂBLER des rôles sur
   ces valeurs : c'est ce qui rend impossible qu'une palette dérive entre le
   `@media` et l'attribut `[data-theme]`, le jour où quelqu'un retouche un seul
   des deux. Aucun jeton n'existe uniquement dans un `@media`.
   -------------------------------------------------------------------------- */
:root {
  /* --- palette CLAIRE (valeurs sources) --- */
  --clair-ground:    #E9EDE9;
  --clair-paper:     #FBFCFA;
  --clair-paper-2:   #F2F5F1;
  --clair-ink:       #10171B;
  --clair-ink-soft:  #47554F;
  --clair-ink-faint: #7E8F89;
  --clair-rule:      #CFD8D2;
  --clair-rule-firm: #A9B6AF;
  --clair-accent:    #0C6459;
  --clair-censored:  #84968F;
  --clair-reagent:   #B32E60;

  /* --- palette SOMBRE (valeurs sources) --- */
  --sombre-ground:    #0A0F11;
  --sombre-paper:     #121A1D;
  --sombre-paper-2:   #172023;
  --sombre-ink:       #E2E9E5;
  --sombre-ink-soft:  #A3B2AC;
  --sombre-ink-faint: #71827C;
  --sombre-rule:      #263134;
  --sombre-rule-firm: #3C4A4C;
  --sombre-accent:    #4CB5A2;
  --sombre-censored:  #6C7D77;
  --sombre-reagent:   #EB7BA0;

  /* --- câblage par défaut : CLAIR ---
     Le vert-de-gris est celui du verre de laboratoire vu par la tranche. Ce
     n'est PAS un « bleu = eau » : le service ne décore pas un élément, il
     reproduit un imprimé d'analyse. */
  --ground:    var(--clair-ground);
  --paper:     var(--clair-paper);
  --paper-2:   var(--clair-paper-2);
  --ink:       var(--clair-ink);
  --ink-soft:  var(--clair-ink-soft);
  --ink-faint: var(--clair-ink-faint);
  --rule:      var(--clair-rule);
  --rule-firm: var(--clair-rule-firm);
  --accent:    var(--clair-accent);
  --censored:  var(--clair-censored);
  --reagent:   var(--clair-reagent);
  /* encre posée SUR un aplat d'accent / de réactif (mesuré : 6,84:1 et 5,87:1) */
  --sur-accent:  var(--clair-paper);
  --sur-reagent: var(--clair-paper);
  /* la sélection reprend le filet : aucune couleur hors palette n'est inventée */
  --selection-fond: var(--clair-rule);

  color-scheme: light dark;

  /* --- fontes : trois rôles, trois piles de repli complètes --- */
  --fonte-titre: "Spectral", "Iowan Old Style", "Palatino Linotype", Palatino,
                 Georgia, "Times New Roman", serif;
  /* Verdana avant system-ui, et c'est délibéré : à défaut d'Atkinson, on veut la
     police installée la plus lisible en basse vision (grande hauteur d'x, chasse
     large), pas la plus élégante. */
  --fonte-texte: "Atkinson Hyperlegible", Verdana, "DejaVu Sans", "Segoe UI",
                 system-ui, -apple-system, sans-serif;
  /* ⚠ `monospace` est répété : quand une pile se termine par ce seul mot-clé,
     les navigateurs appliquent leur « taille de police fixe » (13 px) et le
     tableau se désaligne. Le doublon désamorce cette heuristique. */
  --fonte-donnees: "IBM Plex Mono", "SFMono-Regular", Consolas, "DejaVu Sans Mono",
                   "Liberation Mono", monospace, monospace;

  /* --- mesures --- */
  --largeur-page: 62rem;   /* le site n'est pas une application : on garde l'œil */
  --largeur-texte: 68ch;   /* longueur de ligne confortable en lecture suivie */
  --filet: 1px;
  --rayon: 2px;            /* presque nul : un imprimé n'a pas de coins ronds */
}

/* Thème SOMBRE, deux entrées.
   `:not([data-theme="light"])` : un visiteur qui a demandé le clair garde le
   clair même si son système est sombre. Les deux sélecteurs pèsent (0,1,1),
   donc c'est l'ORDRE qui tranche, et l'attribut est écrit en dernier : un choix
   explicite gagne toujours sur la préférence système. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground:    var(--sombre-ground);
    --paper:     var(--sombre-paper);
    --paper-2:   var(--sombre-paper-2);
    --ink:       var(--sombre-ink);
    --ink-soft:  var(--sombre-ink-soft);
    --ink-faint: var(--sombre-ink-faint);
    --rule:      var(--sombre-rule);
    --rule-firm: var(--sombre-rule-firm);
    --accent:    var(--sombre-accent);
    --censored:  var(--sombre-censored);
    --reagent:   var(--sombre-reagent);
    --sur-accent:  var(--sombre-ground);
    --sur-reagent: var(--sombre-ground);
    --selection-fond: var(--sombre-rule);
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --ground:    var(--sombre-ground);
  --paper:     var(--sombre-paper);
  --paper-2:   var(--sombre-paper-2);
  --ink:       var(--sombre-ink);
  --ink-soft:  var(--sombre-ink-soft);
  --ink-faint: var(--sombre-ink-faint);
  --rule:      var(--sombre-rule);
  --rule-firm: var(--sombre-rule-firm);
  --accent:    var(--sombre-accent);
  --censored:  var(--sombre-censored);
  --reagent:   var(--sombre-reagent);
  --sur-accent:  var(--sombre-ground);
  --sur-reagent: var(--sombre-ground);
  --selection-fond: var(--sombre-rule);
  color-scheme: dark;
}
/* `[data-theme="light"]` n'a rien à redéclarer : `:root` nu EST déjà le clair,
   et le bloc `@media` ci-dessus s'exclut lui-même. Une redéclaration ici serait
   une troisième copie à maintenir — donc une dérive de plus. */

/* Contraste renforcé : on remonte les deux jetons faibles sur --ink-soft
   (7,61:1 en clair, 7,99:1 en sombre). `var(--ink-soft)` est déjà câblé au bon
   thème, donc UN seul bloc suffit pour les deux.
   `:root, :root[data-theme]` : même poids (0,1,1) que les blocs de thème, et
   placé après — sinon un thème explicite écraserait cette demande du visiteur. */
@media (prefers-contrast: more) {
  :root,
  :root[data-theme] {
    --censored:  var(--ink-soft);
    --ink-faint: var(--ink-soft);
    --rule:      var(--rule-firm);
  }
}

/* -----------------------------------------------------------------------------
   3. SOCLE
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  /* ⚠ `clip` et non `hidden` : `hidden` fabrique un conteneur de défilement qui
     casserait toute position:sticky ultérieure. On coupe le débord horizontal
     sans rien rendre défilable — le corps de page ne défile JAMAIS en largeur,
     seuls les .tableau-large le font. */
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--fonte-texte);
  font-size: 1.0625rem;   /* 17 px : Atkinson a une grande hauteur d'x, on l'exploite */
  line-height: 1.6;
  /* Un nom de commune ou un code réseau ne doit jamais élargir la page. */
  overflow-wrap: break-word;
}

::selection { background: var(--selection-fond); color: var(--ink); }

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

/* Focus TOUJOURS visible. Une liste explicite (et non `:where()`) pour que la
   règle ne se fasse pas écraser par le premier sélecteur venu. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--rayon);
}

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
a:hover { text-decoration-thickness: 2px; }
/* Pas de couleur « visitée » distincte : la palette n'en prévoit pas, et en
   inventer une reviendrait à écrire une douzième valeur hors direction. */

/* -----------------------------------------------------------------------------
   4. TYPOGRAPHIE
   -------------------------------------------------------------------------- */
h1, h2, h3, h4 {
  font-family: var(--fonte-titre);
  font-weight: 600;
  line-height: 1.2;
  margin: 0 0 0.5em;
  text-wrap: balance;   /* ignoré par les vieux moteurs, sans conséquence */
}
h1 { font-size: clamp(1.6rem, 1.15rem + 1.7vw, 2.2rem); letter-spacing: -0.01em; }
h2 { font-size: clamp(1.25rem, 1.05rem + 0.8vw, 1.5rem); }
h3 { font-size: 1.125rem; }
h4 { font-size: 1rem; }

p, ul, ol, dl, table { margin: 0 0 1rem; }
p, li { max-width: var(--largeur-texte); }

/* Le chapô : Spectral en romain, c'est NOTRE voix, pas une donnée. */
.chapo {
  font-family: var(--fonte-titre);
  font-size: 1.15rem;
  color: var(--ink-soft);
  max-width: var(--largeur-texte);
}

small, .petit { font-size: 0.875rem; }

/* Une note de bas de fiche : la limite de ce qu'on affirme. */
.note {
  font-size: 0.875rem;
  color: var(--ink-soft);
  max-width: var(--largeur-texte);
}

abbr[title] { text-decoration: underline dotted; text-underline-offset: 0.2em; cursor: help; }

/* -----------------------------------------------------------------------------
   5. SQUELETTE
   -------------------------------------------------------------------------- */
.saut-contenu {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--paper);
  color: var(--ink);
  border: var(--filet) solid var(--rule-firm);
  padding: 0.6rem 1rem;
  z-index: 10;
}
.saut-contenu:focus { left: 0.5rem; top: 0.5rem; }

.entete {
  background: var(--paper);
  border-bottom: var(--filet) solid var(--rule-firm);
}
.entete-interieur {
  max-width: var(--largeur-page);
  margin: 0 auto;
  padding: 0.9rem 1.25rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
}

.marque {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  color: inherit;
}
.marque img { width: 34px; height: 34px; flex: none; }
.marque-nom {
  font-family: var(--fonte-titre);
  font-weight: 600;
  font-size: 1.35rem;
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.marque-quoi {
  display: block;
  font-family: var(--fonte-texte);
  font-weight: 400;
  font-size: 0.78rem;
  color: var(--ink-soft);
  letter-spacing: 0.01em;
}

.nav {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.1rem;
  font-size: 0.95rem;
}
.nav a { color: var(--ink-soft); text-decoration: none; }
.nav a:hover { color: var(--ink); text-decoration: underline; }
/* Page courante : UN seul mécanisme, la classe posée sur <body> par le gabarit.
   Volontairement pas de `aria-current` en plus — deux mécanismes pour le même
   geste finissent par se contredire, et c'est celui qu'on oublie qui ment. */
body.p-methode  .nav-methode,
body.p-conseils .nav-conseils { color: var(--ink); font-weight: 700; }

.contenu {
  max-width: var(--largeur-page);
  margin: 0 auto;
  padding: 1.75rem 1.25rem 3rem;
  /* ⚠ min-width:0 : sans lui, un enfant en grille/flex refuse de rétrécir
     (min-width:auto) et c'est LE défaut qui fait défiler la page entière en
     largeur, alors que le tableau seul devait défiler. */
  min-width: 0;
}

.pied {
  border-top: var(--filet) solid var(--rule-firm);
  background: var(--paper);
  color: var(--ink-soft);
  font-size: 0.9rem;
}
.pied-interieur {
  max-width: var(--largeur-page);
  margin: 0 auto;
  padding: 1.5rem 1.25rem 2.5rem;
  display: grid;
  gap: 0.75rem 2.5rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.pied a { color: var(--accent); }
.pied p { max-width: 46ch; margin: 0 0 0.4rem; }

/* -----------------------------------------------------------------------------
   6. FICHE ET RAIL — le bloc-bulletin

   `.fiche:has(> .rail)` ouvre la colonne marginale. Un moteur sans `:has()`
   ignore la règle et tout s'empile : le repli est la mise en page mobile, donc
   lisible. Aucune fonctionnalité ne dépend de la colonne.
   -------------------------------------------------------------------------- */
.fiche {
  background: var(--paper);
  border: var(--filet) solid var(--rule);
  border-radius: var(--rayon);
  padding: 1.25rem 1.4rem;
  margin: 0 0 1.25rem;
  display: grid;
  gap: 0.75rem;
  min-width: 0;
}
.fiche > * { min-width: 0; }
.fiche > :last-child { margin-bottom: 0; }

/* Le titre de fiche se pose sur un filet, comme l'en-tête d'un imprimé. */
.fiche > h2, .fiche > h3 {
  margin: 0;
  padding-bottom: 0.5rem;
  border-bottom: var(--filet) solid var(--rule);
}

.fiche:has(> .rail) {
  grid-template-columns: minmax(0, 1fr) minmax(0, 15rem);
  gap: 1rem 2rem;
  align-items: start;
}
.fiche:has(> .rail) > h2,
.fiche:has(> .rail) > h3 { grid-column: 1 / -1; }

/* La colonne marginale : références, dates, laboratoire, méthode.
   Tout ce qui répond à « d'où sort ce chiffre ? ». */
.rail {
  font-size: 0.9rem;
  color: var(--ink-soft);
  border-left: var(--filet) solid var(--rule);
  padding-left: 1rem;
  min-width: 0;
}
.rail dl { display: grid; grid-template-columns: 1fr; gap: 0.1rem; margin: 0; }
.rail dt {
  /* --ink-faint mesure 3,30:1 : il ne porte JAMAIS de mot. L'étiquette reste
     donc en --ink-soft (7,61:1), et c'est la casse qui la distingue. */
  color: var(--ink-soft);
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: 0.6rem;
}
.rail dd { margin: 0; color: var(--ink); }
.rail dd + dt { border-top: var(--filet) solid var(--rule); padding-top: 0.6rem; }

@media (max-width: 46rem) {
  .fiche:has(> .rail) { grid-template-columns: minmax(0, 1fr); }
  .rail {
    border-left: 0;
    border-top: var(--filet) solid var(--rule);
    padding-left: 0;
    padding-top: 0.75rem;
  }
}

/* -----------------------------------------------------------------------------
   7. LES DONNÉES — chasse fixe = chaîne du laboratoire, mot pour mot

   ⚠ I1 : `valtraduite` mélange des mesures et des sentinelles ; 86 457 364
   analyses sur 131 211 329 (65,9 %) sont des « inférieur à » traduites en zéro
   dans 100,0 % des cas. Le site n'affiche donc jamais ce zéro : il affiche ce
   que le laboratoire a écrit, dans la fonte qui le dit.
   -------------------------------------------------------------------------- */
.mesure, .sentinelle, .borne, .brut, .code {
  font-family: var(--fonte-donnees);
  /* 0,95em : Plex Mono porte plus large qu'Atkinson à taille égale ; on ramène
     la hauteur d'x au même niveau pour que la ligne ne « saute » pas. */
  font-size: 0.95em;
  font-variant-numeric: tabular-nums;  /* les colonnes de chiffres s'alignent */
}

.mesure { color: var(--ink); white-space: nowrap; }
/* La valeur de tête d'une fiche. Pas d'aplat, pas de cadre : c'est la taille
   qui hiérarchise, comme sur un bulletin. */
.mesure.forte { font-weight: 600; font-size: 1.35rem; letter-spacing: -0.01em; }
.mesure .unite { color: var(--ink-soft); font-weight: 400; }

/* ⚠ I2 — une non-détection n'est PAS un zéro, c'est une BORNE.
   `<0,0005` et `<1,0` ne disent pas la même chose : sur un seul prélèvement
   réel, l'écart des seuils de quantification va de 1 à 2 000. On écrit donc
   toujours « non détecté (< 0,020 µg/L) », jamais « 0 », jamais « — ».

   Balisage attendu :
     <span class="non-detecte">non détecté
       <span class="borne">(&lt; 0,020 µg/L)</span></span>

   Les mots sont en Atkinson (c'est NOTRE formulation) ; la borne passe en Plex
   Mono parce que ce nombre-là est celui du laboratoire, non recalculé. */
.non-detecte {
  color: var(--censored);
  font-family: var(--fonte-texte);
  font-size: 1rem;   /* plancher : --censored ne descend pas plus bas (3,03:1) */
}
.non-detecte .borne { color: var(--censored); white-space: nowrap; }

/* Sentinelles : TRACES, N.D., ILLISIBL, N.M., INCOMPT.
   Ce sont des chaînes du laboratoire — donc chasse fixe, mot pour mot — et
   elles ne valent AUCUN nombre : `INCOMPT.` vaut 1,11 et `PRESENCE` vaut 1 dans
   le fichier officiel, ce qui n'a aucun sens à agréger (I1).
   Le pointillé est un second canal : la couleur seule ne porte jamais le sens. */
.sentinelle {
  color: var(--censored);
  font-size: 1rem;
  text-decoration: underline dotted;
  text-underline-offset: 0.22em;
  white-space: nowrap;
}
.sentinelle[title] { cursor: help; }

/* La chaîne brute, quand on la montre telle quelle (page méthode, API). */
.brut, .code { color: var(--ink-soft); }

/* -----------------------------------------------------------------------------
   8. PASTILLES

   ⚠⚠ LE ROSE --reagent NE SERT QU'À UNE CHOSE SUR TOUT LE SITE : un dépassement
   de LIMITE de qualité. S'il apparaît ailleurs — un bouton, un lien, une erreur
   de formulaire, une base périmée — il ne veut plus rien dire, et le jour où une
   limite est vraiment dépassée, plus personne ne le voit.

   Le texte de la pastille porte le sens ; la couleur ne fait que le répéter.
   Une pastille vide serait un défaut d'accessibilité, pas un raccourci.
   -------------------------------------------------------------------------- */
.pastille-limite,
.pastille-reference,
.pastille-aucun-seuil {
  display: inline-block;
  font-size: 0.8rem;
  line-height: 1.35;
  padding: 0.1rem 0.5rem;
  border-radius: var(--rayon);
  border: var(--filet) solid transparent;
  white-space: nowrap;
  vertical-align: baseline;
}

/* Dépassement d'une LIMITE de qualité. Le seul rose du site. */
.pastille-limite {
  background: var(--reagent);
  color: var(--sur-reagent);
  font-weight: 700;
}

/* Dépassement d'une RÉFÉRENCE de qualité. Ce n'est PAS une limite sanitaire :
   une référence est un repère de bon fonctionnement de l'installation. Lui
   donner le rose reviendrait à annoncer un dépassement de limite — donc à faire
   exactement le faux diagnostic que le service s'interdit (CONTRAT §0).
   Bordure en --ink-faint : 3,30:1, le minimum pour un contour, et ce jeton n'a
   ici aucun mot à porter. */
.pastille-reference {
  background: var(--paper-2);
  color: var(--ink-soft);
  border-color: var(--ink-faint);
}

/* Aucun seuil réglementaire : 31 855 033 analyses sur 131 211 329 (24,3 %).
   Ce n'est ni bon ni mauvais, c'est une ABSENCE — d'où le pointillé, qui se lit
   comme un cadre inachevé. */
.pastille-aucun-seuil {
  background: transparent;
  color: var(--ink-soft);
  border-style: dashed;
  border-color: var(--ink-faint);
}

/* En couleurs forcées (contraste système Windows), les aplats disparaissent :
   on garantit qu'il reste au moins un contour. */
@media (forced-colors: active) {
  .pastille-limite,
  .pastille-reference,
  .pastille-aucun-seuil { border: 1px solid; }
}

/* -----------------------------------------------------------------------------
   9. FRAÎCHEUR — I4

   Nimbus a servi un bulletin de 13 heures d'âge pendant 5 h 30 sans que rien ne
   le signale. Ici l'âge est une donnée de PREMIER PLAN : il est sur la page, pas
   seulement dans un journal.

   ⚠ Une base périmée n'est PAS colorée en --reagent. Le rose dit « limite de
   qualité dépassée » ; une base périmée est une panne de NOTRE côté, pas un
   événement sanitaire. Les confondre ferait croire à un problème d'eau là où il
   n'y a qu'un ingesteur en retard. On alerte donc par le poids et le filet.
   -------------------------------------------------------------------------- */
.fraicheur {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.5rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
}
/* La DATE vient du fichier source : chasse fixe. L'ÂGE (« il y a 12 jours ») est
   calculé par nous : il reste en Atkinson. C'est la règle forte, appliquée. */
.fraicheur .date {
  font-family: var(--fonte-donnees);
  font-size: 0.95em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.fraicheur[data-etat="perimee"] {
  background: var(--paper-2);
  color: var(--ink);
  font-weight: 700;
  border-left: 3px solid var(--ink);
  padding: 0.4rem 0.7rem;
  border-radius: 0 var(--rayon) var(--rayon) 0;
}
.fraicheur[data-etat="perimee"] .date { font-weight: 700; }

/* -----------------------------------------------------------------------------
   10. TABLEAUX

   ⚠ Pas de zébrures. Ce n'est pas un choix d'humeur : --censored tient 3,03:1
   sur --paper mais tombe à 2,83:1 sur --paper-2, et toute non-détection posée
   sur une ligne grise deviendrait illisible. Les lignes se séparent par un
   filet, comme sur un imprimé de laboratoire.
   -------------------------------------------------------------------------- */
.tableau-large {
  overflow-x: auto;
  /* ⚠ le corps de page ne défile jamais en largeur : c'est CE conteneur qui
     défile. Toute nouvelle grille large doit passer par là.
     👉 la page doit poser `tabindex="0"` et un `aria-label` sur ce conteneur :
     une zone défilante non focalisable est inatteignable au clavier. */
  max-width: 100%;
  border: var(--filet) solid var(--rule);
  border-radius: var(--rayon);
  background: var(--paper);
}
.tableau-large > table { margin: 0; border: 0; }

table { border-collapse: collapse; width: 100%; background: var(--paper); }
caption {
  text-align: left;
  color: var(--ink-soft);
  font-size: 0.9rem;
  padding: 0.6rem 0.75rem;
}
th, td {
  text-align: left;
  padding: 0.5rem 0.75rem;
  border-bottom: var(--filet) solid var(--rule);
  vertical-align: baseline;
}
thead th {
  font-size: 0.78rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
  border-bottom: var(--filet) solid var(--rule-firm);
  white-space: nowrap;
}
tbody tr:last-child td { border-bottom: 0; }
/* Colonne de valeurs : alignée à droite et en chiffres tabulaires. */
td.valeur, th.valeur { text-align: right; font-variant-numeric: tabular-nums; }

/* -----------------------------------------------------------------------------
   11. RECHERCHE DE COMMUNE — le duo avec app.js

   Le balisage vit dans gabarit.html ; app.js n'ajoute que les suggestions.
   Sans JavaScript, c'est un formulaire GET ordinaire qui part vers `/`.
   -------------------------------------------------------------------------- */
/* ⚠ `min-width: 0` N'EST PAS DÉCORATIF. Un élément de flex vaut `min-width:auto`
   par défaut : il REFUSE de rétrécir sous la largeur de son contenu. Sans cette
   ligne, à 320 px le formulaire mesurait 341 px et le document 362 px — mesuré.
   Et comme `html` porte `overflow-x: clip`, le débord ne se voyait pas : il
   était COUPÉ. Un bouton « Chercher » à moitié hors cadre, sans barre de
   défilement pour aller le chercher.
   👉 C'est le même défaut que sur `.contenu`, et c'est le premier à soupçonner
   dès qu'une page déborde en largeur. Mesuré après correction : document 320 px,
   bouton entièrement visible. */
.recherche { position: relative; flex: 1 1 18rem; min-width: 0; max-width: 26rem; }
.recherche-ligne { display: flex; gap: 0.4rem; }

.recherche input[type="search"],
.recherche input[type="text"] {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--fonte-texte);
  font-size: 1rem;
  color: var(--ink);
  background: var(--paper);
  border: var(--filet) solid var(--rule-firm);
  border-radius: var(--rayon);
  padding: 0.45rem 0.6rem;
}
.recherche input::placeholder { color: var(--ink-soft); opacity: 1; }

.recherche button {
  font-family: var(--fonte-texte);
  font-size: 0.95rem;
  background: var(--accent);
  color: var(--sur-accent);
  border: var(--filet) solid var(--accent);
  border-radius: var(--rayon);
  padding: 0.45rem 0.85rem;
  cursor: pointer;
}

/* La liste de suggestions. Bordure franche plutôt qu'ombre portée : le site n'a
   pas de profondeur, et une ombre demanderait une couleur hors palette. */
.suggestions {
  position: absolute;
  z-index: 20;
  left: 0;
  right: 0;
  top: calc(100% + 0.25rem);
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--paper);
  border: var(--filet) solid var(--rule-firm);
  border-radius: var(--rayon);
  max-height: 22rem;
  overflow-y: auto;
}
.suggestions[hidden] { display: none; }

.suggestion {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.5rem;
  padding: 0.45rem 0.65rem;
  border-bottom: var(--filet) solid var(--rule);
  cursor: pointer;
}
.suggestion:last-child { border-bottom: 0; }
/* Survol ET sélection clavier passent par le MÊME rendu : deux mécanismes pour
   un même geste finiraient par diverger. */
.suggestion:hover,
.suggestion[aria-selected="true"] { background: var(--paper-2); }
.suggestion[aria-selected="true"] { box-shadow: inset 3px 0 0 0 var(--accent); }

.suggestion .nom { color: var(--ink); }
.suggestion .nom mark {
  background: transparent;      /* pas de surligneur : on épaissit, c'est tout */
  color: inherit;
  font-weight: 700;
}
/* Le code INSEE vient du fichier : chasse fixe. */
.suggestion .insee {
  font-family: var(--fonte-donnees);
  font-size: 0.82rem;
  color: var(--ink-soft);
}
/* I3 — 8 175 communes sur 34 944 (23,4 %) sont servies par PLUSIEURS réseaux.
   Le dire dès la suggestion évite de laisser croire qu'une commune = une eau. */
.suggestion .reseaux { font-size: 0.82rem; color: var(--ink-soft); margin-left: auto; }

.recherche-message {
  padding: 0.6rem 0.65rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

/* Annonce vocale du nombre de résultats : présente pour les lecteurs d'écran,
   invisible à l'œil. Surtout pas `display:none`, qui la rendrait muette. */
.hors-ecran {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* -----------------------------------------------------------------------------
   12. MOUVEMENT

   Le site n'anime rien de significatif. Ce bloc existe pour que toute
   transition ajoutée plus tard soit couverte d'avance.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* -----------------------------------------------------------------------------
   13. IMPRESSION

   Un bulletin d'analyse se sort sur papier — pour un dossier, un syndic, un
   médecin. Deux raisons de forcer la palette claire ici : imprimer un fond
   sombre vide une cartouche, et la plupart des moteurs n'appliquent de toute
   façon aucun aplat de fond. Le bloc est écrit EN DERNIER pour l'emporter sur
   les deux câblages de thème, à poids égal (0,1,1).
   -------------------------------------------------------------------------- */
@media print {
  :root,
  :root[data-theme] {
    --ground:    var(--clair-ground);
    --paper:     var(--clair-paper);
    --paper-2:   var(--clair-paper-2);
    --ink:       var(--clair-ink);
    --ink-soft:  var(--clair-ink-soft);
    --ink-faint: var(--clair-ink-faint);
    --rule:      var(--clair-rule);
    --rule-firm: var(--clair-rule-firm);
    --accent:    var(--clair-accent);
    --censored:  var(--clair-censored);
    --reagent:   var(--clair-reagent);
    --sur-accent:  var(--clair-paper);
    --sur-reagent: var(--clair-paper);
    color-scheme: light;
  }
  body { background: var(--paper); font-size: 11pt; }
  .saut-contenu, .nav, .recherche, .suggestions { display: none !important; }
  .entete, .pied { background: transparent; }
  /* Une fiche coupée en deux pages perd son en-tête, donc sa provenance. */
  .fiche { break-inside: avoid; border-color: var(--rule-firm); }
  .tableau-large { overflow: visible; }
  /* Sur papier, un lien sans son adresse n'est plus vérifiable — et tout ce
     service repose sur le fait qu'on puisse remonter à la source. */
  .contenu a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; }
  /* Le bloc d'origine tient d'un seul tenant : coupé, il perdrait son avertissement
     de retard, et le lecteur croirait la donnée actuelle. */
  .origine { break-inside: avoid; }
}

/* =========================================================================
   .origine — « D'où vient cette eau » (couche BNPE, CONTRAT §5 ter)
   =========================================================================
   ⚠ Ces règles ont été ajoutées le 31/08/2026, APRÈS coup : le bloc a d'abord été
     livré sans aucun style, parce que j'avais écrit son HTML dans app/rendu.py sans
     vérifier que ses classes existaient ici. Une classe inventée dans un gabarit ne
     casse rien et ne se voit pas — la page s'affiche, simplement nue. */
.origine {
  margin-top: 1.5rem;
  padding: 0.9rem 1.1rem;
  background: var(--paper-2);
  border-left: 3px solid var(--accent);
  border-radius: 2px;
}
.origine h2 {
  margin: 0 0 0.5rem;
  font-size: 1.05rem;
}
.origine p {
  margin: 0 0 0.5rem;
  font-size: 0.95rem;
}
.origine p:last-child { margin-bottom: 0; }
/* ⚠ PAS DE `font-family: var(--fonte-donnees)` ICI, ET C'EST UNE CORRECTION.
   Je l'y avais mis pour aligner les chiffres — mais dans ce bloc, `<strong>` entoure des
   PHRASES ENTIÈRES (« est desservie par plusieurs réseaux », « 3 ans de retard »), qui
   sortaient donc en chasse fixe. Vu à l'écran, pas à la lecture du code.
   La règle du projet est plus fine : la chasse fixe est pour ce qui VIENT DU FICHIER
   SOURCE (une date, une valeur mesurée) ; ce que NOUS calculons reste en Atkinson. Les
   nombres d'ici — nombre de captages, années de retard — sont des calculs à nous.
   `tabular-nums` seul reste utile : il ne change pas la police, il aligne les chiffres. */
.origine strong {
  font-variant-numeric: tabular-nums;
}
/* ⚠ I4 — LE RETARD DE LA COUCHE SE VOIT, il ne se lit pas en petit. La BNPE s'arrête
   à 2023 quand les analyses sont de cette année : juxtaposer sans le dire ferait lire
   un état actuel là où il y a trois ans d'écart. */
.origine .retard {
  color: var(--ink);
  font-weight: 600;
}
/* La source s'efface sans disparaître : c'est une réserve, pas le propos.
   ⚠ Jamais --ink-faint ici : contraste mesuré 3,30:1, interdit pour du texte. */
.origine .source {
  font-size: 0.85rem;
  color: var(--ink-soft);
  border-top: 1px solid var(--rule);
  padding-top: 0.5rem;
  margin-top: 0.7rem;
}
