/* ============================================================================
   Entreprise Atlas — COUCHE D'ADAPTATION (mobile ⇄ bureau)
   Réf. : docs/specs/spec-design.md §10
   Paliers : tailwind/viewport.json (source unique) — voir aussi graph/core/viewport.py

   ── Pourquoi ce fichier existe ──────────────────────────────────────────────
   Avant lui, l'adaptation mobile était éparpillée : douze largeurs de bascule
   différentes (480, 620, 640, 720, 760, 768, 900, 1024…) réparties dans
   tokens.css, site.css, entreprise.css et des <style> en ligne dans les gabarits.
   Personne ne pouvait dire, en lisant un gabarit, comment il se comportait sous
   768 px — donc personne ne le vérifiait, donc ça cassait.

   Ici : UNE échelle, et des primitives de mise en page qui sont correctes en
   mobile PAR CONSTRUCTION. On ne « rend pas une page responsive » après coup ;
   on la compose de briques qui n'ont jamais eu de version cassée.

   ── L'échelle ───────────────────────────────────────────────────────────────
     compact   < 768px    téléphone
     medium    768-1023   tablette, petit portable
     large     >= 1024    bureau

   Ces deux valeurs sont EXACTEMENT `md` et `lg` de Tailwind. Ce n'est pas une
   coïncidence : 984 classes `lg:`/`sm:` en dépendent déjà. Ajouter un palier
   maison (720px…) recréerait la deuxième échelle qu'on vient de supprimer.
   `web/checks/check_viewport_coherence.py` refuse toute autre largeur.

   ── Le principe ─────────────────────────────────────────────────────────────
   MOBILE D'ABORD, ET SANS EXCEPTION. Chaque primitive est écrite dans son état
   téléphone (empilée, pleine largeur, cible 44 px). Le bureau est ce qu'on
   AJOUTE dans un `@media (min-width: 1024px)`.

   L'inverse — écrire le bureau puis rattraper le mobile — est très exactement
   ce qui a produit le bug le plus grave de l'audit du 2026-08-21 : un panneau
   de filtres en `w-64 shrink-0` (256 px incompressibles) dans une rangée flex,
   sans repli. Sur un écran de 390 px, le graphe qu'il accompagnait recevait les
   89 px restants. Une largeur fixe posée par défaut est une largeur fixe posée
   sur téléphone.

   ── État d'emploi (2026-08-21) ──────────────────────────────────────────────
   Employées : §1 DUO (graphe de liens), §2 RAIL (sous-navigations), §3
   TABLE-LARGE (9 tableaux), §8 REPLI (menu du header, facettes, arbre secteur).

   Documentées, pas encore employées : §4 TABLE-CARTES, §5 PAIRE, §6 CIBLE (la
   règle des 44 px est aujourd'hui appliquée directement aux composants
   concernés dans tokens.css et facets.css), §7 CONTENEUR (le
   partiel des ratios pose `container-type` lui-même). Elles sont conservées
   parce que la spec §10.3 les désigne comme LA réponse à des problèmes qui se
   reposeront à chaque conversion de page — pas par anticipation gratuite.
   Si l'une d'elles est encore inemployée à la fin de l'étape 3, la supprimer.
   ========================================================================== */


/* ============================================================================
   1. DUO — contenu principal + panneau latéral
   ----------------------------------------------------------------------------
   LA primitive à connaître. Remplace tout `flex` + `w-64 shrink-0` écrit à la
   main. Empilé sous `large`, côte à côte au-delà.

     <div class="duo" style="--duo-panneau: 18rem">
       <div class="duo-principal">…le graphe, la carte, le tableau…</div>
       <aside class="duo-panneau">…filtres, légende…</aside>
     </div>

   Deux détails qui ne sont pas décoratifs :
     · `min-width: 0` sur le principal — sans lui, `min-width: auto` (défaut
       flex) empêche l'élément de rétrécir sous la largeur de son contenu, et
       un canvas ou un tableau large repousse le panneau hors de l'écran ;
     · la largeur du panneau n'est POSÉE qu'à partir de `large`. Sous ce seuil
       il est en flux normal, pleine largeur. C'est ce qui rend le bug des 89 px
       structurellement impossible.

   `--duo-panneau` (défaut 16rem) se règle sur l'élément, pas dans ce fichier.
   ========================================================================== */

.duo {
  display: flex;
  flex-direction: column;
  /* `--duo-gap: 0` pour deux surfaces jointives séparées par un simple filet
     (le graphe et son panneau de filtres, par exemple). */
  gap: var(--duo-gap, var(--sp-4));
  min-width: 0;
}

.duo-principal,
.duo-panneau {
  min-width: 0;
}

/* Sur téléphone le panneau passe SOUS le contenu, quel que soit l'ordre du DOM.
   L'ordre du DOM garde le panneau en second pour le clavier et le lecteur
   d'écran ; `order` ne sert qu'aux cas où le gabarit doit l'écrire en premier. */
.duo-panneau {
  order: 2;
}

.duo-principal {
  order: 1;
}

@media (min-width: 1024px) {
  .duo {
    flex-direction: row;
    align-items: stretch;
  }

  .duo-principal {
    flex: 1 1 auto;
  }

  .duo-panneau {
    flex: 0 0 var(--duo-panneau, 16rem);
    width: var(--duo-panneau, 16rem);
  }

  /* Panneau à gauche : `.duo--panneau-avant` sur le conteneur. */
  .duo--panneau-avant .duo-panneau { order: 0; }
}


/* ============================================================================
   2. RAIL — bande défilante horizontalement, AVEC son affordance
   ----------------------------------------------------------------------------
   Tout défilement horizontal doit se voir. Une barre d'onglets qui cache 1 026
   px de contenu sans le moindre signal (cas relevé sur /secteur/ le 2026-08-21,
   où `scrollbar-width: none` masquait 73 % des ancres) n'est pas « compacte » :
   elle est amputée du point de vue de l'utilisateur.

   `viewport.js` pose `data-debord="gauche|droite|deux"` selon la position de
   défilement ; le dégradé de bord suit. Sans JS, le rail reste défilable et
   l'ombre statique de `--rail-ombre` prend le relais.
   ========================================================================== */

.rail {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  /* Le pouce doit pouvoir lancer le rail sans accrocher le défilement vertical. */
  touch-action: pan-x pan-y;
}

.rail > * {
  flex: 0 0 auto;
  scroll-snap-align: start;
}

/* Le masque est l'affordance : le contenu s'efface vers le bord où il continue. */
.rail[data-debord="droite"] {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent 100%);
}

.rail[data-debord="gauche"] {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 2.5rem), transparent 100%);
  mask-image: linear-gradient(to left, #000 calc(100% - 2.5rem), transparent 100%);
}

.rail[data-debord="deux"] {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 2.5rem, #000 calc(100% - 2.5rem), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 2.5rem, #000 calc(100% - 2.5rem), transparent 100%);
}

/* The same cue on a wide table (B17 Q45): the comptes annuels tables (ten years of columns in a
   305 px card) scrolled sideways with nothing saying so but a column cut in half. Only the
   RIGHT edge fades: the left edge is the frozen key column, and a fade over it would wash out
   the very labels the freeze keeps in view. `viewport.js` sets the attribute on every
   `.table-large`; the rule is scoped to a plate's section. */
.planche-section .table-large[data-debord="droite"],
.planche-section .table-large[data-debord="deux"] {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent 100%);
}

/* Le rail garde son ascenseur sur pointeur fin (souris) : c'est là qu'il sert.
   Sur pointeur grossier le geste suffit et l'ascenseur vole 8 px de hauteur. */
@media (pointer: coarse) {
  .rail {
    scrollbar-width: none;
  }

  .rail::-webkit-scrollbar {
    display: none;
  }
}


/* ============================================================================
   3. TABLE-LARGE — tableau plus large que l'écran, colonne de clé figée
   ----------------------------------------------------------------------------
   Un tableau de données ne se « rend pas responsive » en rétrécissant sa police.
   Il défile — mais en gardant sous les yeux la colonne qui dit DE QUOI on parle.
   Sans elle (constat du 2026-08-21 : 877 px masqués sur les comptes annuels),
   on lit un montant sans savoir à quel poste il se rapporte.

     <div class="table-large table-large--cle">
       <table>
         <thead><tr><th class="col-cle">Poste</th><th>2024</th>…
         <tbody><tr><td class="col-cle">Chiffre d'affaires</td><td>…

   La colonne figée a besoin d'un FOND OPAQUE, sinon les cellules défilent
   visiblement dessous. On le prend sur le token de surface, pas sur `white` :
   les tableaux vivent aussi sur `--ca-surface-2`.
   ========================================================================== */

.table-large {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  max-width: 100%;
}

.table-large > table {
  /* `separate` est obligatoire : avec `collapse`, les bordures des cellules
     figées se peignent sous les cellules défilantes sur WebKit. */
  border-collapse: separate;
  border-spacing: 0;
}

/* Deux façons de désigner la colonne de clé :
     `.table-large--cle`  + `class="col-cle"` sur les cellules — explicite, à
                            préférer quand on écrit un tableau neuf ;
     `.table-large--cle1`   la PREMIÈRE colonne, sans toucher aux cellules —
                            pour équiper un tableau existant sans réécrire ses
                            boucles de gabarit.
   Les deux produisent exactement le même rendu. */
.table-large--cle .col-cle,
.table-large--cle1 > table > thead > tr > :first-child,
.table-large--cle1 > table > tbody > tr > :first-child {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--table-cle-fond, var(--ca-surface));
}

/* Le filet de séparation est en ::after et non en `border-right` : une bordure
   sur une cellule sticky se dédouble au défilement sur Chrome. */
.table-large--cle .col-cle::after,
.table-large--cle1 > table > thead > tr > :first-child::after,
.table-large--cle1 > table > tbody > tr > :first-child::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 1px;
  background: var(--ca-filet);
}

/* L'en-tête reste au-dessus de la colonne de clé à leur intersection. */
.table-large--cle thead .col-cle,
.table-large--cle1 > table > thead > tr > :first-child {
  z-index: 3;
}

/* Au-delà de `large` la plupart des tableaux tiennent : on relâche le sticky,
   qui coûte une couche de composition à chaque défilement. */
@media (min-width: 1024px) {
  .table-large--cle:not(.table-large--cle-toujours) .col-cle,
  .table-large--cle1:not(.table-large--cle-toujours) > table > thead > tr > :first-child,
  .table-large--cle1:not(.table-large--cle-toujours) > table > tbody > tr > :first-child {
    position: static;
  }

  .table-large--cle:not(.table-large--cle-toujours) .col-cle::after,
  .table-large--cle1:not(.table-large--cle-toujours) > table > thead > tr > :first-child::after,
  .table-large--cle1:not(.table-large--cle-toujours) > table > tbody > tr > :first-child::after {
    display: none;
  }
}


/* ============================================================================
   4. TABLE-CARTES — le tableau qui devient une liste de fiches
   ----------------------------------------------------------------------------
   C'est ici que la structure de la page diffère VRAIMENT entre mobile et
   bureau, sans dupliquer le gabarit ni renifler l'agent utilisateur : un seul
   DOM, deux mises en forme.

   À réserver aux tableaux de 3-6 colonnes dont chaque ligne est une entité
   (un établissement, une opération). Un tableau de 12 exercices comptables
   n'est PAS ce cas : il veut `.table-large`, parce que la comparaison
   colonne à colonne est justement ce qu'on vient y chercher.

     <table class="table-cartes">
       <thead><tr><th>SIRET</th><th>Adresse</th>…
       <tbody><tr>
         <td data-libelle="SIRET">…</td>
         <td data-libelle="Adresse">…</td>

   `data-libelle` est obligatoire sur chaque <td> : c'est lui qui devient
   l'étiquette de la fiche. `check_design.py --structure` le vérifie.
   ========================================================================== */

@media (max-width: 767.98px) {
  .table-cartes,
  .table-cartes tbody,
  .table-cartes tr,
  .table-cartes td {
    display: block;
    width: auto;
  }

  /* L'en-tête n'est pas `display:none` : il reste lu par les lecteurs d'écran
     comme en-tête de tableau. On le sort du flux visuel seulement. */
  .table-cartes thead {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .table-cartes tr {
    /* The card's own padding is a line's (tokens.css « Densité »). */
    padding: var(--densite-ligne) 0;
    border-bottom: 1px solid var(--ca-filet);
  }

  .table-cartes td {
    display: grid;
    grid-template-columns: minmax(6.5rem, 40%) 1fr;
    gap: var(--sp-2);
    align-items: baseline;
    padding: var(--sp-1) 0;
    border: 0;
    text-align: left;
  }

  .table-cartes td::before {
    content: attr(data-libelle);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--ca-gris-fort);
    text-transform: uppercase;
    letter-spacing: 0.08em;
  }

  /* B17 Q46: a table whose row header is a <th scope="row"> (the privacy endpaper's data table)
     leads its card like a `.cellule-titre`: full width, no label, and no longer sticky (the
     token table sets its heads sticky at the top of their scroll box). */
  .planche-section .table-cartes tbody th {
    display: block;
    position: static;
    padding: 0 0 var(--sp-2);
    border: 0;
    font-family: inherit;
    font-size: inherit;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
    color: inherit;
    white-space: normal;
    background: none;
  }

  /* The value of a cell is ONE grid item: a cell that holds text, a <br> and a sub-line (an
     établissement's locality and its département) would otherwise deal them into three grid
     cells and drop the sub-line under the label column. The template wraps a card cell's content
     in `.cellule-valeur`. */
  .table-cartes .cellule-valeur { min-width: 0; }

  /* A number is a value in a card, read left to right after its label. */
  .planche-section .table-cartes td.num { text-align: left; }

  /* Une cellule vide n'occupe pas une ligne de fiche pour rien. */
  .table-cartes td:empty {
    display: none;
  }

  /* La cellule marquée `.cellule-titre` devient l'en-tête de la fiche :
     pleine largeur, sans étiquette. */
  .table-cartes td.cellule-titre {
    display: block;
    font-weight: 600;
    padding-bottom: var(--sp-2);
  }

  .table-cartes td.cellule-titre::before {
    content: none;
  }

  /* ── The card in reading order, no labels (decision 11 of 2026-09-29, B9 Q6) ────────
     What a row of an index becomes on a phone. The labelled card above measured 277–323 px
     per row on the four indexes (a star alone on a 44 px row, six label/value lines, « CA (k€) »
     right-aligned while the other labels sat left), 25 rows ≈ 7 000–8 000 px, 2.5 companies a
     screen. The row is now one flex-wrapped card of about 105 px: the star beside the name
     (the cell `.cellule-titre`, which leads whatever its column order), the sub-line under it,
     the other cells flowing on as words. Every value keeps what says what it is: a number
     keeps its column's label as a prefix (a bare « 1 234 » is not « CA (k€) »), the others are
     words that carry their own noun (« Unités non employeuses », « liquidation »).
     `--etoile`: the first column is the star, which sits in a 44 px gutter so the name and the
     cells under it share one left edge. */
  .table-cartes--fiche tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--sp-2);
    position: relative;
    padding: var(--sp-2) 0;
  }

  .table-cartes--fiche.table-cartes--etoile tr { padding-left: 2.75rem; }

  .table-cartes--fiche td {
    display: block;
    padding: 0;
    font-size: var(--fs-s);
    line-height: 1.4;
  }

  .table-cartes--fiche td::before { content: none; }

  /* The label a number keeps: mono, small, grey, before its value. A text or date cell whose
     value does not say what it is (`etiquette=True` on its Colonne, B17) keeps it too. */
  .table-cartes--fiche td.num { text-align: left; }
  .table-cartes--fiche td.num::before,
  .table-cartes--fiche td.cellule-etiquetee::before {
    content: attr(data-libelle) " ";
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--ca-gris-fort);
    text-transform: none;
    letter-spacing: 0;
  }

  /* The year under a number joins it on the line (« CA (k€) 176 2023 »): the break between
     the two is for the table, where the cell is a column. */
  .table-cartes--fiche td.num br { display: none; }
  .table-cartes--fiche td.num .discret { margin-left: var(--sp-1); }
  /* E0 #14 review: joined by a space alone, a second line that is a number read as the first
     number's tail: Secteurs' « Ventes de fonds 9 124 100 k€ » (9 124 ventes, médiane 100 k€) and
     « CA (k€) 176 2023 » above. A middle dot, the site's separator in a line (spec-experience
     §7.9), keeps them two values. */
  .table-cartes--fiche td.num .discret::before { content: "· "; }
  /* E0 #14: a card cell may wrap. `.num` is `nowrap` for the table's columns, and « Entreprises
     actives 3 716 495 +3,5 % sur un an » (E0 #15 put the croissance under the value) then ran to
     336 px in a 305 px card, its end cut off (measured at 390 px on the Secteurs rows). The count
     itself stays whole (its spaces are narrow no-break). */
  .table-cartes--fiche td.num { white-space: normal; }

  /* A cell that holds two lines in the table (an établissement's locality over its
     département) is words of the card: the break goes, the second part follows the first. A
     cell marked absent (« — ») is not printed: in a card a missing fact is a missing word, and
     « — » alone, without its column's head, says nothing. `cellule-fin`: the key of the row
     (a SIRET), last. An établissement was a labelled card of nine lines, 351 px; in reading
     order it is about 150 (spec-design §8.1 decision 18, which revises B17 Q46). */
  .table-cartes--fiche td:not(.cellule-titre):not(.num) br { display: none; }
  .table-cartes--fiche td:not(.cellule-titre):not(.num) .discret { margin-left: var(--sp-1); }
  .table-cartes--fiche td.cellule-absente { display: none; }
  .table-cartes--fiche td.cellule-fin { order: 3; }

  /* The name leads: first in the flow and on its own full line, whatever its column. */
  .table-cartes--fiche td { order: 2; }
  .table-cartes--fiche td.cellule-titre {
    order: 1;
    flex: 1 1 100%;
    font-weight: 500;
    padding-bottom: 0;
  }

  /* A card has no columns to freeze: the frozen first cell of a `.table-large--cle1` table
     (Secteurs, the staff tables) is an ordinary cell again, without its right rule. */
  .table-large--cle1 > table.table-cartes--fiche > tbody > tr > :first-child {
    position: static;
    background: none;
  }
  .table-large--cle1 > table.table-cartes--fiche > tbody > tr > :first-child::after {
    display: none;
  }

  /* The star: in the gutter, level with the name (its 44 px target is index.css's). */
  .table-cartes--etoile td:first-child {
    position: absolute;
    left: 0;
    top: var(--sp-2);
    order: 0;
  }
}


/* B17 Q41 / Q49 — the link of a row on the touch tier. On a card (compact) the cell keeps its
   grid and the link takes the 44 px floor: 17 px measured on the cells of a plate (5 rows on
   each secteur cell and on /liquidations/<dep>/, where index.css is not loaded).
   On a table (a tablet with a finger) the link keeps the line, and takes the floor from
   padding matched by a negative margin: the hit area grows, the row does not. Sentence links
   are not concerned. Scoped to a plate's section. */
@media (max-width: 767.98px) {
  .planche-section .table-cartes td a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

@media (min-width: 768px) and (max-width: 1023.98px) and (pointer: coarse) {
  .planche-section .table-donnees td > a,
  .planche-section .table-donnees td > .cellule-valeur > a,
  /* A link inside a cell's line (an établissement's « Succession », one <p> per link): the two
     selectors above stopped at the cell's children and left it 17 px, 34 links on the 77
     établissements of 301984563 (check_design, 2026-10-02). */
  .planche-section .table-donnees td > .cellule-valeur > p > a {
    display: inline-block;
    padding: 13px 0;
    margin: -13px 0;
  }
}


/* ============================================================================
   5. PAIRE — libellé / valeur / visualisation
   ----------------------------------------------------------------------------
   La ligne « Marge d'EBE · 4,2 % · [histogramme] » des ratios sectoriels. En
   grille fixe héritée du bureau, l'histogramme et ses bornes P25/P75 sortaient
   de la carte et se faisaient rogner (constat du 2026-08-21 : « P25–P75 53 j →
   96 », borne haute amputée, et deux étiquettes imprimées l'une sur l'autre).

   Empilée sous `medium` : libellé + valeur sur une ligne, visualisation en
   dessous, pleine largeur.
   ========================================================================== */

.paire {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--sp-1) var(--sp-3);
  align-items: baseline;
  min-width: 0;
}

.paire-libelle {
  grid-column: 1;
  min-width: 0;
}

.paire-valeur {
  grid-column: 2;
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* La visualisation prend toute la largeur, sous les deux premiers. */
.paire-viz {
  grid-column: 1 / -1;
  min-width: 0;
  overflow: hidden;
}

/* Les bornes d'une échelle : jamais en position absolue — elles se recouvrent
   dès que l'axe rétrécit. `space-between` ne peut pas se superposer. */
.paire-bornes {
  grid-column: 1 / -1;
  display: flex;
  justify-content: space-between;
  gap: var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ca-gris-fort);
}

.paire-bornes > * {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (min-width: 768px) {
  .paire {
    grid-template-columns: var(--paire-colonnes, 1fr auto minmax(0, var(--paire-viz-l, 14rem)));
  }

  .paire-viz {
    grid-column: 3;
    grid-row: 1;
  }

  .paire-bornes {
    grid-column: 3;
    grid-row: 2;
  }
}


/* ============================================================================
   6. CIBLE — surface tactile de 44 px
   ----------------------------------------------------------------------------
   La règle existait depuis toujours (spec §2.4, « cible tactile : 44 px
   minimum sur mobile ») ; rien ne l'appliquait. L'audit du 2026-08-21 a relevé
   20 px sur les liens de nav, 27 px sur le CTA, 20-26 px sur les lignes de
   facette — c'est-à-dire partout.

   On l'applique par `min-height` plutôt qu'en gonflant la police : le texte
   est déjà au bon cran typographique, c'est la SURFACE qui manque.

   `(pointer: coarse)` autant que la largeur : une tablette de 900 px se pilote
   au doigt tout autant qu'un téléphone.
   ========================================================================== */

@media (max-width: 767.98px), (pointer: coarse) {
  .cible {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}


/* ============================================================================
   7. CONTENEUR — adaptation au CONTENANT, pas à l'écran
   ----------------------------------------------------------------------------
   Un partiel comme `_ratios_sectoriels` est rendu tantôt dans la colonne
   principale (1 100 px sur bureau), tantôt dans un panneau de 320 px. La
   largeur de l'ÉCRAN ne lui apprend rien d'utile ; celle de sa boîte, si.

   Le gabarit appelant déclare le contexte :
       <section class="conteneur">{% include "…/_ratios_sectoriels.html" %}</section>

   et le partiel s'adapte avec `@container`. C'est ce qui permet à un composant
   d'être réellement réutilisable : il n'a plus à savoir où on l'a posé.
   ========================================================================== */

.conteneur {
  container-type: inline-size;
}



/* ============================================================================
   8. REPLI — un bloc rangé derrière un bouton, sous un palier donné
   ----------------------------------------------------------------------------
   Le mécanisme est piloté par `data-repli` (viewport.js) : le bouton nomme la
   cible, la cible porte `est-replie` tant qu'elle est fermée.

     <button data-repli="ca-nav" aria-expanded="false">☰</button>
     <nav id="ca-nav" class="repli-large est-replie">…</nav>

   POURQUOI PAS LE `hidden` DE TAILWIND — cinq gabarits (accueil/page,
   secteurs/page, anomalies/comptes, anomalies/comptes_secteur, analysis_progress/page) ne chargent PAS
   tailwind.css. La classe `hidden` y est purement inerte : le menu du header,
   qui vit dans base.html donc sur toutes les pages, y restait grand ouvert
   alors qu'il se repliait correctement ailleurs. viewport.css, lui, est chargé
   partout — le mécanisme de base doit vivre ici, pas dans une couche optionnelle.

   La classe dit à QUEL PALIER le repli s'applique ; au-delà, aucune règle ne
   s'applique et l'élément retrouve son `display` normal (d'où deux `@media`
   `max-width` plutôt qu'un `display: revert`, qui reviendrait au défaut UA et
   casserait un `display: flex` d'auteur).
   ========================================================================== */

@media (max-width: 767.98px) {
  .repli-compact.est-replie {
    display: none;
  }
}

@media (max-width: 1023.98px) {
  .repli-large.est-replie {
    display: none;
  }
}

/* The atlas header's own fold, one seuil above `large` (spec-design §8.1 decision 10, Q21: at
   1024 px the search field was 34 px wide beside a nav of six entries). tailwind/viewport.json
   `seuils_atlas` holds the 1280. */
@media (max-width: 1279.98px) {
  .repli-atlas.est-replie {
    display: none;
  }
}


/* ============================================================================
   9. GARDE-FOUS GLOBAUX
   ----------------------------------------------------------------------------
   Filets de sécurité, pas des primitives. Ils rattrapent ce qui a échappé aux
   règles ci-dessus au lieu de laisser la page déborder.
   ========================================================================== */

/* Aucun média ne dépasse sa boîte. Vaut aussi pour les <canvas> de vis-network,
   qui se dimensionnent en JS et ignorent la mise en page. */
img,
svg,
video,
canvas,
iframe {
  max-width: 100%;
}

/* Les longues chaînes sans espace (SIRET, URL, identifiants d'acte) sont la
   première cause de débordement horizontal sur téléphone. */
@media (max-width: 767.98px) {
  .mono,
  .num,
  code {
    overflow-wrap: anywhere;
  }
}

/* iOS zoome la page dès qu'un champ passe sous 16 px au focus. Le plancher
   vaut pour TOUT champ, y compris ceux ajoutés après coup — c'est pour ça que
   la règle est ici et non sur une classe. */
@media (max-width: 767.98px) {
  input[type="text"],
  input[type="search"],
  input[type="number"],
  input[type="email"],
  input[type="tel"],
  select,
  textarea {
    font-size: max(16px, 1em);
  }
}
