/* =============================================================================
   Bozkurt Construction — 03-app.css
   -----------------------------------------------------------------------------
   TOUS les composants de l'application. Ne consomme QUE les tokens de
   01-tokens.css : aucune couleur en dur, aucune ombre en dur, aucun rayon en dur.
   Le contrôle 12 de `outils/verifier.mjs` échoue sur tout #hex trouvé ici.

   ⚠️ LES DEUX PARTICULARITÉS DE « PAPIER SOUS NÉON », qui expliquent la moitié
   des choix de ce fichier :

   1. L'ACCENT EST L'ENCRE. --couleur-accent porte le même indigo que le texte.
      Un bouton primaire n'est donc pas « une couleur », c'est un BLOC PLEIN, et
      un onglet actif est une PASTILLE D'ENCRE : dans une palette sans couleur
      vive, c'est la MASSE qui signale l'état, jamais la teinte. Ce qui distingue
      un lien d'un texte, c'est --couleur-accent-texte, jamais --couleur-accent.

   2. LA CARTE NE TIENT QUE PAR SON OMBRE. Blanc sur le fond gris ne fait que
      1,14 de contraste : une carte sans ombre DISPARAÎT dans la page. Les
      --ombre-* portent ici de l'information, pas de la décoration.
      NE PAS LES ALLÉGER.

   ⚠️ AUCUNE COULEUR N'EST SEULE PORTEUSE DE SENS. Chaque badge, chaque bandeau,
   chaque montant en alerte ouvre par un glyphe (✓ ⚠ ● ○) : l'information reste
   lisible en noir et blanc et pour un daltonien.

   MOBILE-FIRST. Alain lit ça sur un chantier, au soleil, avec des gants. Toutes
   les règles de base visent le téléphone ; les media queries n'ÉLARGISSENT que.

   -----------------------------------------------------------------------------
   PLAN DU FICHIER
     1. Structure de page      — .barre-haute .entete .onglets .pied
     2. Cartes et tuiles       — .carte .tuiles .tuile
     3. Connexion              — .corps--connexion .carte--connexion
     4. Barre d'outils         — .barre-outils .segments
     5. TABLEAU DU CHANTIER    — .tableau-cadre .tableau .num  (pièce maîtresse)
     6. Listes en cartes       — .cartes-lignes .ligne  (repli du tableau au doigt)
     7. Formulaires            — .formulaire .champ-groupe .champ
     8. Boutons                — .bouton .lien-bouton
     9. Badges d'état          — .badge
    10. Messages               — .message-erreur .message-succes .bandeau .toast
    11. Chargement et vide     — .squelette .etat-vide
    12. Modale
    13. Téléphone et cibles tactiles
   ========================================================================== */


/* =============================================================================
   1 — STRUCTURE DE PAGE
   ---------------------------------------------------------------------------
   L'en-tête et les onglets voyagent ensemble : sur un tableau qui fait trois
   écrans de haut, perdre l'onglet courant en défilant coûte un aller-retour.
   Le MARKUP RECOMMANDÉ enveloppe les deux dans .barre-haute ; une .entete seule
   reste collante d'elle-même si la page n'a pas d'onglets.
============================================================================= */
.barre-haute {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--couleur-surface);
  border-bottom: var(--trait-fin) solid var(--couleur-bordure);
  box-shadow: var(--ombre-xs);
}

/* Dans la barre haute, l'en-tête ne colle plus lui-même et rend son ombre :
   deux ombres superposées feraient une bavure grise sous la barre. */
.barre-haute .entete {
  position: static;
  border-bottom: none;
  box-shadow: none;
}

.entete {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-4);
  padding: var(--espace-4) var(--espace-5);
  background: var(--couleur-surface);
  border-bottom: var(--trait-fin) solid var(--couleur-bordure);
  box-shadow: var(--ombre-xs);
}

.entete__identite {
  display: flex;
  flex-direction: column;
  gap: var(--espace-1);
  min-width: 0; /* sans quoi un long nom de projet fait déborder le flex */
}

.entete__titre {
  margin: 0;
  font-family: var(--police-titre);
  font-size: var(--taille-lg);
  font-weight: var(--graisse-semi);
  line-height: var(--interligne-titre);
  letter-spacing: var(--lettrage-titre);
  color: var(--couleur-texte);
}

.entete__sous-titre {
  margin: 0;
  font-size: var(--taille-sm);
  line-height: var(--interligne-titre);
  color: var(--couleur-texte-secondaire);
}

/* Code de projet / n° de lot : un jeton lisible d'un coup d'œil dans l'en-tête. */
.entete__code {
  display: inline-block;
  padding: var(--espace-1) var(--espace-2);
  border-radius: var(--rayon-sm);
  background: var(--couleur-surface-basse);
  font-size: var(--taille-xs);
  font-weight: var(--graisse-semi);
  letter-spacing: var(--lettrage-majuscule);
  text-transform: uppercase;
  color: var(--couleur-texte-secondaire);
}

.entete__outils {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-2);
}

/* --- Onglets -----------------------------------------------------------------
   L'onglet actif est une PASTILLE D'ENCRE PLEINE. Un simple soulignement se
   perdrait : le gris du fond et le blanc de la carte ne sont qu'à 1,14 l'un de
   l'autre, un trait de 2 px n'y suffit pas. La masse, si.
   La rangée défile horizontalement — jamais la page. */
.onglets {
  display: flex;
  gap: var(--espace-2);
  padding: var(--espace-3) var(--espace-5) var(--espace-3);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
}

.onglet {
  flex: 0 0 auto;
  padding: var(--espace-3) var(--espace-5);
  border: var(--trait-fin) solid transparent;
  border-radius: var(--rayon-plein);
  background: transparent;
  font-family: var(--police-corps);
  font-size: var(--taille-base);
  font-weight: var(--graisse-moyenne);
  line-height: 1.2;
  white-space: nowrap;
  color: var(--couleur-texte-secondaire);
  cursor: pointer;
  transition: background var(--transition-douce), color var(--transition-douce);
}

.onglet:hover {
  background: var(--couleur-surface-basse);
  color: var(--couleur-texte);
}

.onglet--actif,
.onglet--actif:hover {
  background: var(--couleur-encre);
  color: var(--couleur-surface);
  font-weight: var(--graisse-semi);
}

/* Compteur accolé au libellé (« Lots 14 »). Sur l'onglet actif il s'inverse. */
.onglet__compte {
  margin-left: var(--espace-2);
  font-size: var(--taille-xs);
  font-variant-numeric: tabular-nums;
  color: var(--couleur-texte-secondaire);
}
.onglet--actif .onglet__compte {
  color: color-mix(in srgb, var(--couleur-surface) 78%, transparent);
}

/* --- Zone principale et pied ---------------------------------------------- */
.zone-principale { padding-bottom: var(--espace-7); }

#panneau { display: block; padding-top: var(--espace-5); }
#panneau:focus { outline: none; }

.pied {
  padding: var(--espace-6) var(--espace-5);
  border-top: var(--trait-fin) solid var(--couleur-bordure);
  font-size: var(--taille-sm);
  color: var(--couleur-texte-secondaire);
  text-align: center;
}
.pied p { margin: 0 0 var(--espace-2); }
.pied__mention { font-size: var(--taille-xs); }


/* =============================================================================
   2 — CARTES ET TUILES
============================================================================= */
.carte {
  margin-bottom: var(--espace-5);
  padding: var(--espace-5);
  border-radius: var(--rayon-lg);
  background: var(--couleur-surface);
  box-shadow: var(--ombre-sm); /* ⚠️ porteuse d'information — ne pas alléger */
}

/* Carte posée sur le sable : sert aux récapitulatifs et aux encarts de rappel,
   là où la carte blanche ferait un deuxième niveau de surface pour rien. */
.carte--sable {
  background: var(--couleur-surface-basse);
  box-shadow: var(--ombre-xs);
}
.carte--sable .texte-secondaire {
  color: var(--couleur-texte-secondaire-sur-sable);
}

/* Une carte qui n'est qu'un cadre de tableau ne met pas de rembourrage sur les
   côtés : le tableau doit pouvoir défiler bord à bord. */
.carte--tableau { padding: var(--espace-5) 0; }
.carte--tableau > .carte__entete,
.carte--tableau > .carte__titre,
.carte--tableau > .carte__note { padding-inline: var(--espace-5); }

.carte__entete {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--espace-3);
  margin-bottom: var(--espace-3);
}
.carte__entete .carte__titre { margin: 0; }

.carte__titre {
  margin: 0 0 var(--espace-3);
  font-family: var(--police-titre);
  font-size: var(--taille-lg);
  font-weight: var(--graisse-semi);
  color: var(--couleur-texte);
}

.carte__note {
  margin: var(--espace-3) 0 0;
  font-size: var(--taille-xs);
  color: var(--couleur-texte-secondaire);
}

/* --- Tuiles de chiffres ------------------------------------------------------
   ⚠️ Une tuile AFFICHE un agrégat venu d'une VUE. Rien n'est additionné ici ni
   dans le navigateur : le CSS met en forme un nombre déjà calculé en base. */
.tuiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--espace-4);
  margin-bottom: var(--espace-5);
}

.tuile {
  padding: var(--espace-5);
  border-radius: var(--rayon-lg);
  background: var(--couleur-surface);
  box-shadow: var(--ombre-sm);
}

/* La tuile maîtresse (coût complet, coût projeté) est un bloc d'encre : c'est
   encore la masse qui hiérarchise, faute de couleur vive dans la palette. */
.tuile--principale { background: var(--couleur-encre); }
.tuile--principale .tuile__libelle,
.tuile--principale .tuile__detail,
.tuile--principale .tuile__ecart {
  color: color-mix(in srgb, var(--couleur-surface) 82%, transparent);
}
.tuile--principale .tuile__valeur { color: var(--couleur-surface); }
.tuile--principale .tuile__valeur--negative { color: var(--couleur-accent-voile); }

.tuile__libelle {
  margin: 0;
  font-size: var(--taille-xs);
  font-weight: var(--graisse-semi);
  text-transform: uppercase;
  letter-spacing: var(--lettrage-majuscule);
  color: var(--couleur-texte-secondaire);
}

.tuile__valeur {
  margin: var(--espace-2) 0 0;
  font-family: var(--police-titre);
  font-size: var(--taille-xl);
  font-weight: var(--graisse-semi);
  line-height: var(--interligne-serre);
  font-variant-numeric: tabular-nums;
  color: var(--couleur-texte);
}
.tuile__valeur--negative { color: var(--couleur-erreur); }

.tuile__detail {
  margin: var(--espace-2) 0 0;
  font-size: var(--taille-sm);
  color: var(--couleur-texte-secondaire);
}

/* L'écart ouvre par une flèche : le signe et le glyphe suffisent, la couleur
   n'est qu'un renfort. */
.tuile__ecart { margin: var(--espace-2) 0 0; font-size: var(--taille-sm); }
.tuile__ecart--hausse::before { content: "▲ "; }
.tuile__ecart--baisse::before { content: "▼ "; }
.tuile__ecart--defavorable { color: var(--couleur-erreur); font-weight: var(--graisse-semi); }
.tuile__ecart--favorable { color: var(--couleur-succes); font-weight: var(--graisse-semi); }

/* --- Jauge d'avancement (budget consommé, avancement d'un lot) ---------------
   Le remplissage est de l'encre ; l'alerte seule change de teinte, et un badge
   voisin porte alors le mot. */
.jauge {
  height: var(--espace-2);
  margin-top: var(--espace-3);
  border-radius: var(--rayon-plein);
  background: var(--couleur-surface-basse);
  overflow: hidden;
}
.jauge__remplissage {
  height: 100%;
  border-radius: var(--rayon-plein);
  background: var(--couleur-accent);
  transition: width var(--duree-lente) var(--courbe);
}
.jauge__remplissage--attention { background: var(--couleur-texte-secondaire); }
.jauge__remplissage--alerte { background: var(--couleur-erreur); }


/* =============================================================================
   3 — CARTE DE CONNEXION
   ---------------------------------------------------------------------------
   Pas de page « créer un compte » : cet écran n'a qu'un formulaire et rien
   d'autre. Il est centré verticalement, en colonne étroite.
============================================================================= */
.corps--connexion {
  display: flex;
  align-items: center;
  min-height: 100vh;
  min-height: 100svh; /* barre d'URL mobile : svh évite le saut à l'ouverture */
}

.carte--connexion {
  margin-top: var(--espace-7);
  padding: var(--espace-6) var(--espace-5);
  box-shadow: var(--ombre-md); /* seule carte de la page : elle porte plus fort */
}

.connexion__marque {
  display: inline-block;
  margin-bottom: var(--espace-3);
  padding: var(--espace-1) var(--espace-3);
  border-radius: var(--rayon-plein);
  background: var(--couleur-encre);
  color: var(--couleur-surface);
  font-size: var(--taille-xs);
  font-weight: var(--graisse-semi);
  letter-spacing: var(--lettrage-majuscule);
  text-transform: uppercase;
}

.connexion__titre {
  margin: 0 0 var(--espace-2);
  font-family: var(--police-titre);
  font-size: var(--taille-xl);
  font-weight: var(--graisse-semi);
}

.connexion__mention {
  margin-top: var(--espace-5);
  padding-top: var(--espace-4);
  border-top: var(--trait-fin) solid var(--couleur-bordure);
  font-size: var(--taille-xs);
  color: var(--couleur-texte-secondaire);
}


/* =============================================================================
   4 — BARRE D'OUTILS ET SEGMENTS
============================================================================= */
.barre-outils {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-3);
  margin-bottom: var(--espace-4);
}
.barre-outils__recherche { display: flex; flex: 1 1 18rem; gap: var(--espace-2); }
.barre-outils__actions { display: flex; flex-wrap: wrap; gap: var(--espace-2); }

.champ--filtre { flex: 0 0 auto; max-width: 16rem; }
.champ--court { width: auto; min-width: 7rem; }

/* Segments : plusieurs boutons collés qui se lisent comme un seul contrôle
   (« Tous / En cours / Terminés »). L'actif est, là encore, un bloc d'encre. */
.segments {
  display: inline-flex;
  padding: var(--espace-1);
  border: var(--trait-fin) solid var(--couleur-bordure);
  border-radius: var(--rayon-plein);
  background: var(--couleur-surface);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.segments::-webkit-scrollbar { display: none; }

.segment {
  flex: 0 0 auto;
  padding: var(--espace-2) var(--espace-4);
  border: none;
  border-radius: var(--rayon-plein);
  background: transparent;
  font-family: var(--police-corps);
  font-size: var(--taille-sm);
  font-weight: var(--graisse-moyenne);
  white-space: nowrap;
  color: var(--couleur-texte-secondaire);
  cursor: pointer;
  transition: background var(--transition-douce), color var(--transition-douce);
}
.segment:hover { color: var(--couleur-texte); }
.segment--actif,
.segment--actif:hover {
  background: var(--couleur-encre);
  color: var(--couleur-surface);
  font-weight: var(--graisse-semi);
}


/* =============================================================================
   5 — LE TABLEAU DU CHANTIER  ★ PIÈCE MAÎTRESSE
   ---------------------------------------------------------------------------
   Une ligne par lot, une quinzaine de colonnes chiffrées (budget, engagé,
   facturé, payé, retenue, reste, écart). Quatre exigences, dans cet ordre :

   a. LE DÉFILEMENT HORIZONTAL EST ENFERMÉ DANS .tableau-cadre. Jamais la page.
      Un tableau qui pousse le <body> de côté rend l'en-tête collant inutilisable
      et fait « sauter » la page au moindre glissement de pouce.
   b. LES MONTANTS SONT À DROITE, EN CHIFFRES TABULAIRES (.num). Des colonnes de
      dollars qui ne s'alignent pas ne se comparent pas d'un coup d'œil.
   c. LA COLONNE DU LOT RESTE VISIBLE pendant le défilement (th[scope="row"]
      collé à gauche). Sans elle, à la douzième colonne on ne sait plus quel lot
      on lit. ⚠️ Le markup DOIT donc utiliser <th scope="row"> pour le libellé
      du lot — c'est aussi ce qu'attend un lecteur d'écran.
   d. RIEN N'EST ADDITIONNÉ ICI. Le <tfoot> affiche les totaux de la VUE.

   ⚠️ border-collapse: separate (et non collapse, posé par 02-base.css) : avec
   `collapse`, les bordures des cellules COLLÉES ne se déplacent pas avec elles
   et laissent une trace fantôme au défilement. C'est un choix technique, pas
   esthétique — ne pas le « corriger ».
============================================================================= */
.tableau-cadre {
  overflow-x: auto;
  overscroll-behavior-x: contain; /* le glissement ne remonte pas à la page */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}

/* Variante figée : en-tête de colonnes collé en haut D'UN CADRE QUI DÉFILE.
   ⚠️ À n'utiliser que via ce modificateur. Un <thead> collant sans hauteur
   maximale se colle au haut de la FENÊTRE et passe sous la .barre-haute. */
.tableau-cadre--fige { max-height: min(70vh, 40rem); overflow-y: auto; }
.tableau-cadre--fige .tableau thead th {
  position: sticky;
  top: 0;
  z-index: 2;
}
.tableau-cadre--fige .tableau thead th[scope="col"]:first-child,
.tableau-cadre--fige .tableau--chantier thead th:first-child { z-index: 3; }

.tableau {
  width: 100%;
  border-collapse: separate; /* voir l'avertissement ci-dessus */
  border-spacing: 0;
}

.tableau th,
.tableau td {
  padding: var(--espace-3);
  border-bottom: var(--trait-fin) solid var(--couleur-bordure);
  text-align: left;
  font-size: var(--taille-sm);
  line-height: var(--interligne-titre);
  vertical-align: top;
}

.tableau thead th {
  background: var(--couleur-surface);
  font-family: var(--police-corps);
  font-size: var(--taille-xs);
  font-weight: var(--graisse-semi);
  text-transform: uppercase;
  letter-spacing: var(--lettrage-majuscule);
  color: var(--couleur-texte-secondaire);
  white-space: nowrap;
  vertical-align: bottom;
}

/* Rangée de surtitres regroupant les colonnes (BUDGET | ENGAGÉ | FACTURÉ…).
   Le trait vertical sépare les familles mieux qu'un espace. */
.tableau__groupes th {
  border-bottom: var(--trait-fin) solid var(--couleur-bordure);
  text-align: center;
  color: var(--couleur-texte);
}
.tableau__groupes th + th,
.tableau__separation { border-left: var(--trait-fin) solid var(--couleur-bordure); }

.tableau tfoot th,
.tableau tfoot td {
  border-bottom: none;
  border-top: var(--trait-moyen) solid var(--couleur-texte);
  background: var(--couleur-surface);
  font-weight: var(--graisse-semi);
  color: var(--couleur-texte);
}

.tableau caption {
  padding: var(--espace-2) var(--espace-5) var(--espace-3);
  caption-side: bottom;
  text-align: left;
  font-size: var(--taille-xs);
  color: var(--couleur-texte-secondaire);
}

/* --- Cellules de montants ----------------------------------------------------
   .num est la classe qui compte : à droite, tabulaire, insécable. */
.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.tableau thead .num,
.tableau tfoot .num { text-align: right; }

.num--fort { font-weight: var(--graisse-semi); }
.num--sourdine { color: var(--couleur-texte-secondaire); }
.num--favorable { color: var(--couleur-succes); }
.num--defavorable { color: var(--couleur-erreur); }

/* Dépassement / retard : la couleur ne suffit jamais. Le glyphe se pose À
   GAUCHE du nombre, dans une cellule alignée à droite — les chiffres restent
   donc parfaitement alignés entre eux. */
.num--alerte {
  color: var(--couleur-erreur);
  font-weight: var(--graisse-semi);
}
.num--alerte::before { content: "⚠ "; }

/* Une valeur absente n'est pas un zéro. On l'écrit, on ne la calcule pas. */
.num--vide { color: var(--couleur-neutre); }

/* --- Le tableau du chantier proprement dit -------------------------------- */
.tableau--chantier {
  min-width: 60rem; /* en dessous, les colonnes se compriment illisiblement */
}

/* Colonne des lots, collée à gauche. `background: inherit` reprend la couleur
   de SA rangée : sans quoi la cellule collée serait transparente et les
   chiffres défileraient visiblement dessous. */
.tableau--chantier th[scope="row"] {
  position: sticky;
  left: 0;
  z-index: 1;
  min-width: 12rem;
  max-width: 18rem;
  background: inherit;
  box-shadow: var(--trait-fin) 0 0 var(--couleur-bordure);
  font-weight: var(--graisse-semi);
  color: var(--couleur-texte);
  white-space: normal;
}

.tableau--chantier thead th:first-child,
.tableau--chantier tfoot th:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--couleur-surface);
  box-shadow: var(--trait-fin) 0 0 var(--couleur-bordure);
}

/* Alternance très douce : sur un tableau de quinze colonnes, l'œil perd la
   ligne. Le sable est le seul gris de la palette assez proche du blanc pour
   ne pas trouer la carte. */
.tableau--chantier tbody tr { background: var(--couleur-surface); }
.tableau--chantier tbody tr:nth-child(even) { background: var(--couleur-surface-basse); }
.tableau--chantier tbody tr:hover { background: var(--couleur-accent-voile); }

/* Code de lot en petit, au-dessus du libellé. */
.tableau__code {
  display: block;
  font-size: var(--taille-xs);
  font-weight: var(--graisse-normale);
  letter-spacing: var(--lettrage-majuscule);
  text-transform: uppercase;
  color: var(--couleur-texte-secondaire);
}
.tableau__zone {
  display: block;
  font-size: var(--taille-xs);
  font-weight: var(--graisse-normale);
  color: var(--couleur-texte-secondaire);
}

/* Rangée de sous-total intermédiaire (par phase). */
.tableau__sous-total,
.tableau--chantier tbody tr.tableau__sous-total {
  background: var(--couleur-surface-basse);
  font-weight: var(--graisse-semi);
}
.tableau__sous-total td,
.tableau__sous-total th { border-top: var(--trait-fin) solid var(--couleur-texte-secondaire); }

/* Rangée cliquable (ouvre le détail d'un lot). */
.tableau__ligne--cliquable { cursor: pointer; }
.tableau__ligne--cliquable:focus-visible {
  outline: var(--focus-epaisseur) solid var(--focus-anneau);
  outline-offset: calc(-1 * var(--focus-decalage));
}

/* Variante dense, pour un écran large. */
.tableau--compact th,
.tableau--compact td { padding: var(--espace-2); }

/* --- Repli : un long tableau secondaire vit dans un <details> -------------- */
.repli { margin-top: var(--espace-4); }
.repli > summary {
  cursor: pointer;
  font-size: var(--taille-sm);
  font-weight: var(--graisse-moyenne);
  color: var(--couleur-accent-texte);
}
.repli[open] > summary { margin-bottom: var(--espace-3); }
.repli > .tableau-cadre { margin-top: var(--espace-3); }


/* =============================================================================
   6 — LISTES EN CARTES
   ---------------------------------------------------------------------------
   Le même contenu qu'une rangée de tableau, mais empilé : c'est ce qu'on sert
   au doigt quand le tableau serait à faire défiler quinze fois. Contrats,
   factures, paiements.
============================================================================= */
.cartes-lignes { display: flex; flex-direction: column; gap: var(--espace-3); }

.ligne {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "principal montant"
    "actions   actions";
  gap: var(--espace-2) var(--espace-4);
  padding: var(--espace-4);
  border-radius: var(--rayon-md);
  background: var(--couleur-surface);
  box-shadow: var(--ombre-xs);
}

.ligne__principal { grid-area: principal; min-width: 0; }
.ligne__montant {
  grid-area: montant;
  text-align: right;
  font-size: var(--taille-md);
  font-weight: var(--graisse-semi);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ligne__actions {
  grid-area: actions;
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-2);
  justify-content: flex-end;
}

.ligne__titre { margin: 0; font-size: var(--taille-base); font-weight: var(--graisse-semi); color: var(--couleur-texte); }
.ligne__meta { margin: var(--espace-1) 0 0; font-size: var(--taille-sm); color: var(--couleur-texte-secondaire); }
.ligne__note { margin: var(--espace-1) 0 0; font-size: var(--taille-sm); font-style: italic; color: var(--couleur-texte-secondaire); }
.ligne__montant-note { display: block; font-size: var(--taille-xs); font-weight: var(--graisse-normale); color: var(--couleur-texte-secondaire); }

.ligne--archivee { opacity: 0.7; }
.ligne--brouillon { border: var(--trait-fin) dashed var(--couleur-bordure); box-shadow: none; }


/* =============================================================================
   7 — FORMULAIRES
============================================================================= */
.formulaire {
  display: flex;
  flex-direction: column;
  gap: var(--espace-4);
  margin-top: var(--espace-4);
}

.champ-groupe { display: flex; flex-direction: column; gap: var(--espace-1); }

/* Deux champs côte à côte dès qu'il y a la place (dates, taxes). */
.champ-rangee {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--espace-4);
}

.champ-label {
  font-size: var(--taille-sm);
  font-weight: var(--graisse-semi);
  color: var(--couleur-texte);
}
.champ-label__facultatif {
  margin-left: var(--espace-2);
  font-weight: var(--graisse-normale);
  color: var(--couleur-texte-secondaire);
}

.champ {
  width: 100%;
  padding: var(--espace-3);
  border: var(--trait-fin) solid var(--couleur-bordure);
  border-radius: var(--rayon-md);
  background: var(--couleur-surface);
  font-family: var(--police-corps);
  font-size: var(--taille-base); /* 16 px : en dessous, iOS zoome à la saisie */
  color: var(--couleur-texte);
  transition: border-color var(--transition-douce);
}

.champ::placeholder { color: var(--couleur-neutre); }

.champ:focus-visible {
  outline: var(--focus-epaisseur) solid var(--focus-anneau);
  outline-offset: var(--focus-decalage);
  border-color: var(--couleur-accent);
}

.champ:disabled,
.champ[readonly] {
  background: var(--couleur-surface-basse);
  color: var(--couleur-texte-secondaire-sur-sable);
}

.champ--zone { resize: vertical; min-height: 4rem; line-height: var(--interligne-corps); }

/* Saisie d'un montant : mêmes chiffres tabulaires que le tableau, alignés à
   droite — on tape un montant comme on le lit. */
.champ--montant {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Champ en erreur : la bordure rouge est DOUBLÉE par le .message-erreur en
   dessous, jamais seule. */
.champ--erreur,
.champ[aria-invalid="true"] {
  border-color: var(--couleur-erreur);
  border-width: var(--trait-moyen);
}

/* Champ calculé côté BASE et affiché en lecture seule (retenue, taxes, totaux).
   ⚠️ Aucun total ne se calcule dans le navigateur : ce champ RECOPIE une vue. */
.champ--calcule {
  background: var(--couleur-surface-basse);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--couleur-texte);
}

/* --- Menu déroulant ----------------------------------------------------------
   ⚠️ La flèche est dessinée par DEUX DÉGRADÉS, pas par une data-URI. Une
   data-URI ne peut pas lire une variable CSS : sur Nataly Fit, la flèche est
   restée grise après un re-thémage, et le contrôle 12 la signale à chaque
   passage. Ici la teinte vient d'un token et suivra la palette toute seule. */
select.champ {
  --fleche-cote: 0.34rem; /* géométrie locale, pas une couleur */
  appearance: none;
  padding-right: var(--espace-6);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--couleur-texte-secondaire) 50%),
    linear-gradient(135deg, var(--couleur-texte-secondaire) 50%, transparent 50%);
  background-position:
    calc(100% - var(--espace-4) - var(--fleche-cote)) center,
    calc(100% - var(--espace-4)) center;
  background-size: var(--fleche-cote) var(--fleche-cote);
  background-repeat: no-repeat;
}

/* --- Cases et interrupteurs ----------------------------------------------- */
.champ-case {
  display: flex;
  align-items: flex-start;
  gap: var(--espace-3);
  font-size: var(--taille-sm);
  line-height: var(--interligne-corps);
}
.champ-case input[type="checkbox"],
.champ-case input[type="radio"] {
  flex: 0 0 auto;
  width: var(--espace-5);
  height: var(--espace-5);
  margin-top: var(--espace-1);
  accent-color: var(--couleur-accent);
}

/* --- Aides et mentions ---------------------------------------------------- */
.champ-aide { margin: 0; font-size: var(--taille-xs); color: var(--couleur-texte-secondaire); }

.champ-aide--encadre {
  margin-top: var(--espace-1);
  padding: var(--espace-2) var(--espace-3);
  border-radius: var(--rayon-sm);
  background: var(--couleur-accent-voile);
  color: var(--couleur-texte);
}

/* Valeur réglementaire pas encore confirmée par le comptable ou le notaire.
   Décision du 2026-09-02 : tout écran qui affiche une valeur non confirmée
   DOIT le dire. Le style existe pour qu'on n'ait aucune excuse de l'omettre. */
.mention-provisoire {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-1);
  font-size: var(--taille-xs);
  font-weight: var(--graisse-semi);
  color: var(--couleur-texte-secondaire);
}
.mention-provisoire::before { content: "◇"; }

.formulaire__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-2);
  justify-content: flex-end;
  margin-top: var(--espace-2);
}


/* =============================================================================
   8 — BOUTONS
   ---------------------------------------------------------------------------
   L'accent étant l'encre, un bouton plein est un BLOC INDIGO, pas une couleur.
   Le bouton fantôme se distingue par sa BORDURE — jamais par une teinte plus
   pâle du même indigo, qui ne se verrait pas sur le gris du fond.
============================================================================= */
.bouton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--espace-2);
  padding: var(--espace-3) var(--espace-4);
  border: var(--trait-fin) solid transparent;
  border-radius: var(--rayon-md);
  font-family: var(--police-corps);
  font-size: var(--taille-sm);
  font-weight: var(--graisse-semi);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--transition-douce),
              border-color var(--transition-douce),
              color var(--transition-douce);
}

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

.bouton--pleine-largeur { width: 100%; }
.bouton--petit { padding: var(--espace-2) var(--espace-3); font-size: var(--taille-xs); }

.bouton--plein {
  background: var(--couleur-accent);
  color: var(--couleur-accent-contraste);
  box-shadow: var(--ombre-accent);
}
.bouton--plein:hover:not(:disabled) { background: var(--couleur-accent-fort); }

.bouton--fantome {
  background: var(--couleur-surface);
  border-color: var(--couleur-bordure);
  color: var(--couleur-texte);
}
.bouton--fantome:hover:not(:disabled) { background: var(--couleur-surface-basse); }

.bouton--discret {
  padding: var(--espace-2) var(--espace-3);
  background: transparent;
  color: var(--couleur-accent-texte);
}
.bouton--discret:hover:not(:disabled) { background: var(--couleur-surface-basse); }

.bouton--danger {
  background: var(--couleur-erreur);
  color: var(--couleur-surface);
}
.bouton--danger:hover:not(:disabled) { filter: brightness(0.9); }
.bouton--danger-texte { color: var(--couleur-erreur); }

/* Bouton en attente : le libellé reste lisible, le pointeur dit « ça travaille ». */
.bouton--occupe { cursor: progress; opacity: 0.7; }

.lien-bouton {
  display: inline-block;
  margin-top: var(--espace-2);
  padding: 0;
  border: none;
  background: none;
  font-family: var(--police-corps);
  font-size: var(--taille-sm);
  color: var(--couleur-accent-texte);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  cursor: pointer;
}
.lien-bouton:hover { color: var(--couleur-texte); }


/* =============================================================================
   9 — BADGES D'ÉTAT
   ---------------------------------------------------------------------------
   Statut d'un lot, d'un contrat, d'une facture. Les VALEURS admises viennent
   des contraintes `check` de la base — ce fichier ne nomme donc que des RÔLES
   visuels (neutre, encours, attente, ok, alerte), jamais une valeur métier.
   Chaque badge ouvre par un glyphe : la couleur n'est jamais seule à parler.
============================================================================= */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-1);
  padding: var(--espace-1) var(--espace-3);
  border: var(--trait-fin) solid transparent;
  border-radius: var(--rayon-plein);
  background: var(--couleur-surface-basse);
  font-size: var(--taille-xs);
  font-weight: var(--graisse-semi);
  line-height: 1.4;
  white-space: nowrap;
  color: var(--couleur-texte);
}

/* Brouillon, archivé, sans suite — l'état « rien à faire ». */
.badge--neutre { background: var(--couleur-surface-basse); color: var(--couleur-texte-secondaire-sur-sable); }
.badge--neutre::before { content: "○"; }

/* En cours : bloc d'encre. C'est l'état qui doit sauter aux yeux dans une liste. */
.badge--encours { background: var(--couleur-encre); color: var(--couleur-surface); }
.badge--encours::before { content: "●"; }

/* En attente d'une action d'Alain (à approuver, à signer). Voile Icy Blue avec
   du Space Indigo par-dessus : 9,16 de contraste, la seule façon dont ce bleu
   très clair peut porter du texte. */
.badge--attente { background: var(--couleur-accent-voile); color: var(--couleur-texte); }
.badge--attente::before { content: "◔"; }

/* Approuvé, payé, terminé. */
.badge--ok { background: var(--couleur-succes-fond); color: var(--couleur-succes); }
.badge--ok::before { content: "✓"; }

/* En retard, dépassé, refusé. */
.badge--alerte { background: var(--couleur-erreur-fond); color: var(--couleur-erreur); }
.badge--alerte::before { content: "⚠"; }

/* Contour seul, pour un badge qui accompagne un titre sans le concurrencer. */
.badge--contour {
  background: transparent;
  border-color: var(--couleur-bordure);
  color: var(--couleur-texte-secondaire);
}

/* Suite de badges (statut + genre + zone) sous un titre de ligne. */
.badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-2);
  margin-top: var(--espace-2);
}


/* =============================================================================
   10 — MESSAGES D'ERREUR, DE SUCCÈS ET BANDEAUX
   ---------------------------------------------------------------------------
   Règle 2 du poste : devant un incident, on montre la cause, on n'accuse pas
   l'utilisateur. Ces blocs sont faits pour porter une phrase technique, pas un
   « oups, réessayez ». D'où la place laissée à deux ou trois lignes de texte.
============================================================================= */
.message-erreur,
.message-succes {
  margin: 0;
  padding: var(--espace-3) var(--espace-4);
  border-left: var(--espace-1) solid var(--couleur-erreur);
  border-radius: var(--rayon-sm);
  background: var(--couleur-erreur-fond);
  font-size: var(--taille-sm);
  line-height: var(--interligne-corps);
  color: var(--couleur-texte);
}
.message-erreur::before { content: "⚠ "; font-weight: var(--graisse-forte); }

.message-succes {
  border-left-color: var(--couleur-succes);
  background: var(--couleur-succes-fond);
}
.message-succes::before { content: "✓ "; font-weight: var(--graisse-forte); }

/* Détail technique replié sous le message (code PostgREST, contrainte violée).
   Il ne remplace pas la phrase en clair, il la complète. */
.message-erreur__detail {
  display: block;
  margin-top: var(--espace-2);
  font-family: ui-monospace, "SFMono-Regular", "Consolas", monospace;
  font-size: var(--taille-xs);
  word-break: break-word;
  color: var(--couleur-texte-secondaire);
}

/* --- Bandeaux d'information persistante ----------------------------------- */
.bandeau {
  margin-bottom: var(--espace-4);
  padding: var(--espace-3) var(--espace-4);
  border-left: var(--espace-1) solid var(--couleur-neutre);
  border-radius: var(--rayon-sm);
  background: var(--couleur-surface-basse);
  font-size: var(--taille-sm);
  line-height: var(--interligne-corps);
  color: var(--couleur-texte);
}
.bandeau--fin { padding: var(--espace-2) var(--espace-3); }

.bandeau--info { border-left-color: var(--couleur-accent); background: var(--couleur-accent-voile); }
.bandeau--info::before { content: "ℹ "; font-weight: var(--graisse-forte); }

.bandeau--attention { border-left-color: var(--couleur-accent); }
.bandeau--attention::before { content: "⚠ "; font-weight: var(--graisse-forte); }

.bandeau--alerte { border-left-color: var(--couleur-erreur); background: var(--couleur-erreur-fond); }
.bandeau--alerte::before { content: "⚠ "; font-weight: var(--graisse-forte); }

.bandeau__titre { display: block; font-weight: var(--graisse-semi); margin-bottom: var(--espace-1); }

/* --- Messages éphémères --------------------------------------------------- */
.toasts {
  position: fixed;
  right: var(--espace-4);
  bottom: var(--espace-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--espace-2);
  max-width: min(24rem, calc(100vw - var(--espace-6)));
}

.toast {
  padding: var(--espace-3) var(--espace-4);
  border-radius: var(--rayon-md);
  background: var(--couleur-encre);
  color: var(--couleur-surface);
  font-size: var(--taille-sm);
  box-shadow: var(--ombre-md);
  animation: toast-entree var(--duree-moyenne) var(--courbe);
}
.toast--succes { background: var(--couleur-succes); }
.toast--erreur { background: var(--couleur-erreur); }
.toast--sortant { opacity: 0; transition: opacity var(--duree-moyenne) var(--courbe); }

@keyframes toast-entree {
  from { opacity: 0; transform: translateY(var(--espace-2)); }
  to   { opacity: 1; transform: none; }
}


/* =============================================================================
   11 — CHARGEMENT ET ÉTAT VIDE
============================================================================= */
.squelette {
  /* Onde plus lente que --duree-lente, qui est faite pour une micro-interaction.
     Déclarée ici, sur le composant, plutôt qu'en dur dans l'animation. */
  --duree-onde: 1.4s;
  display: flex;
  flex-direction: column;
  gap: var(--espace-3);
}

.squelette__ligne {
  height: var(--espace-7);
  border-radius: var(--rayon-md);
  background: linear-gradient(
    90deg,
    var(--couleur-surface-basse) 25%,
    var(--couleur-surface) 50%,
    var(--couleur-surface-basse) 75%
  );
  background-size: 200% 100%;
  animation: squelette-onde var(--duree-onde) linear infinite;
}

.squelette__ligne--titre { height: var(--espace-5); width: 40%; }
.squelette__ligne--courte { width: 60%; }

/* Squelette calé sur le tableau : mêmes hauteurs de rangée, pour que la page
   ne saute pas quand les vraies lignes arrivent. */
.squelette--tableau .squelette__ligne { height: var(--espace-6); }

@keyframes squelette-onde {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

.etat-vide {
  padding: var(--espace-7) var(--espace-5);
  border: var(--trait-fin) dashed var(--couleur-bordure);
  border-radius: var(--rayon-lg);
  text-align: center;
}
.etat-vide__titre {
  margin: 0 0 var(--espace-2);
  font-family: var(--police-titre);
  font-size: var(--taille-md);
  font-weight: var(--graisse-semi);
}
.etat-vide__texte {
  margin: 0 0 var(--espace-4);
  font-size: var(--taille-sm);
  color: var(--couleur-texte-secondaire);
}

/* ⚠️ Un état vide ne dit JAMAIS « vous n'avez pas accès ». S'il n'y a rien,
   c'est qu'il n'y a rien à voir OU que la RLS filtre — et dans ce second cas on
   veut que ça se VOIE, pas qu'un texte rassurant le masque. */
.etat-vide--rls { border-color: var(--couleur-erreur); }


/* =============================================================================
   12 — MODALE
============================================================================= */
.corps--modale-ouverte { overflow: hidden; }

.modale {
  position: fixed;
  inset: 0;
  z-index: var(--z-modale);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: var(--espace-4);
  overflow-y: auto;
}
.modale[hidden] { display: none; }

.modale__voile {
  position: fixed;
  inset: 0;
  z-index: var(--z-voile);
  background: var(--couleur-voile);
}

.modale__boite {
  position: relative;
  z-index: var(--z-modale);
  width: 100%;
  max-width: var(--largeur-texte);
  margin: var(--espace-5) auto;
  padding: var(--espace-5);
  border-radius: var(--rayon-lg);
  background: var(--couleur-surface);
  box-shadow: var(--ombre-lg);
}
.modale__boite--etroite { max-width: var(--largeur-etroite); }
.modale__boite--large { max-width: var(--largeur-contenu); }

.modale__titre {
  margin: 0 0 var(--espace-2);
  font-family: var(--police-titre);
  font-size: var(--taille-lg);
}

.modale__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--espace-2);
  margin-top: var(--espace-5);
}
.modale__actions--empilees { flex-direction: column-reverse; }
.modale__actions--empilees .bouton { width: 100%; }


/* =============================================================================
   13 — ÉLARGISSEMENTS ET CIBLES TACTILES
   ---------------------------------------------------------------------------
   Tout ce qui précède est écrit pour un téléphone. Ci-dessous, on n'ajoute que
   ce qu'un écran plus large permet.
============================================================================= */
@media (min-width: 48rem) {
  .champ-rangee { grid-template-columns: repeat(2, 1fr); }
  .champ-rangee--trois { grid-template-columns: repeat(3, 1fr); }

  .ligne {
    grid-template-columns: 1fr auto auto;
    grid-template-areas: "principal montant actions";
    align-items: center;
  }

  .carte--tableau > .carte__entete,
  .carte--tableau > .carte__titre,
  .carte--tableau > .carte__note { padding-inline: var(--espace-6); }

  .tableau caption { padding-inline: var(--espace-6); }
}

/* --- Téléphone étroit ------------------------------------------------------ */
@media (max-width: 40rem) {
  .entete { padding: var(--espace-3) var(--espace-4); gap: var(--espace-3); }
  .entete__outils { width: 100%; }
  .entete__outils .bouton { flex: 1 1 auto; }

  .onglets { padding: var(--espace-2) var(--espace-4); gap: var(--espace-1); }
  .onglet { padding: var(--espace-3) var(--espace-4); font-size: var(--taille-sm); }

  .carte { padding: var(--espace-4); border-radius: var(--rayon-md); }
  .carte--tableau { padding-inline: 0; }
  .carte--tableau > .carte__entete,
  .carte--tableau > .carte__titre,
  .carte--tableau > .carte__note { padding-inline: var(--espace-4); }

  .tuile { padding: var(--espace-4); }
  .tuile__valeur { font-size: var(--taille-lg); }

  /* Le champ de recherche tombait à côté du menu de filtres et se retrouvait
     large de quelques dizaines de pixels : on les empile. */
  .barre-outils__recherche { flex: 1 1 100%; flex-direction: column; align-items: stretch; }
  .champ--filtre { max-width: none; }
  .barre-outils__actions { width: 100%; }
  .barre-outils__actions .bouton { flex: 1 1 10rem; }

  .segments { display: flex; width: 100%; }

  /* Un tableau de quinze colonnes ne se lit pas à 360 px : on resserre le
     rembourrage plutôt que la taille du texte, qui doit rester lisible dehors. */
  .tableau th, .tableau td { padding: var(--espace-2); }
  .tableau--chantier th[scope="row"] { min-width: 9rem; max-width: 11rem; }

  .modale { padding: var(--espace-2); }
  .modale__boite { margin: var(--espace-3) auto; padding: var(--espace-4); }

  .formulaire__actions { flex-direction: column-reverse; }
  .formulaire__actions .bouton { width: 100%; }

  .toasts { right: var(--espace-3); left: var(--espace-3); bottom: var(--espace-3); max-width: none; }
}

/* --- Cibles tactiles ---------------------------------------------------------
   44 px est la recommandation d'usage. On vise le POINTEUR autant que la
   largeur : une tablette large se manipule aussi au doigt, avec des gants. */
@media (pointer: coarse), (max-width: 40rem) {
  .bouton { min-height: 2.75rem; }
  .onglet { min-height: 2.75rem; }
  .segment { min-height: 2.5rem; }
  .champ { min-height: 2.75rem; }
  .lien-bouton { display: inline-block; padding: var(--espace-2) 0; }
  .repli > summary { padding: var(--espace-2) 0; }
  .tableau__ligne--cliquable td,
  .tableau__ligne--cliquable th { padding-block: var(--espace-3); }
}

/* --- Mouvement réduit ------------------------------------------------------
   02-base.css neutralise déjà les durées globalement ; on coupe ici ce qui
   tournerait en boucle et resterait perceptible. */
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  .squelette__ligne { animation: none; background: var(--couleur-surface-basse); }
  .jauge__remplissage,
  .bouton,
  .onglet,
  .segment,
  .champ { transition: none; }
}

/* --- Impression --------------------------------------------------------------
   Un tableau de chantier finit sur une imprimante, dans une chemise, sur un
   chantier sans réseau. On déplie le défilement et on retire les ombres. */
@media print {
  .barre-haute, .entete, .onglets, .toasts, .bouton, .barre-outils { display: none !important; }
  .carte, .tuile { box-shadow: none; border: var(--trait-fin) solid var(--couleur-bordure); }
  .tableau-cadre, .tableau-cadre--fige { overflow: visible; max-height: none; }
  .tableau--chantier { min-width: 0; font-size: var(--taille-xs); }
  .tableau--chantier th[scope="row"] { position: static; box-shadow: none; }
  .tableau--chantier tbody tr,
  .tableau--chantier tbody tr:nth-child(even) { background: transparent; }
}
/* =============================================================================
   14 — COMPLÉMENT DES ÉCRANS  (ajouté le 2026-09-02)
   -----------------------------------------------------------------------------
   POURQUOI CE BLOC EXISTE. Le front du lot 1 a été écrit par quatre agents en
   parallèle. Chacun avait raison chez lui, et pourtant 42 classes employées par
   `ecran-chantier.js`, `ecran-projet.js`, `ecran-argent.js`, `ui.js` et
   `app.html` n'avaient AUCUNE règle : l'agent des styles et les agents des
   écrans ont employé deux vocabulaires. Le défaut ne casse rien — la page
   s'affiche, elle est seulement illisible — c'est pour ça qu'il passe les
   relectures et qu'il faut le contrôle 14 de `outils/verifier.mjs` pour le voir.

   CE BLOC NE REDÉFINIT RIEN DES SECTIONS 1 À 13. Il n'ajoute que des sélecteurs
   nouveaux. Là où un nom d'écran désigne un composant DÉJÀ stylé sous un autre
   nom, les déclarations sont RECOPIÉES telles quelles plutôt que fusionnées
   dans la règle d'origine, qui n'est pas touchée :

     .tableau-defilant  ≡ .tableau-cadre   (§5)
     .entete__marque    ≡ .entete__identite (§1)
     .champ-paire       ≡ .champ-rangee    (§7)
     .case-label / .case ≡ .champ-case     (§7)

   Mêmes contraintes que le reste du fichier : aucune couleur, aucun rayon,
   aucune ombre, aucune taille en dur (contrôle 12) ; mobile-first, 390 px
   d'abord, on n'élargit qu'à partir de 48rem ; le défilement horizontal reste
   enfermé dans son cadre ; aucune couleur n'est seule porteuse de sens.

   PLAN
     14.1  Avant la session          — .pre-auth
     14.2  En-tête                   — .entete__marque .champ--projet
     14.3  Identité du projet        — .carte--entete .projet-entete
     14.4  Listes étiquette/valeur   — .grille-infos .info
     14.5  Formulaires               — .champ-paire .champ-fige .case .bloc-lots
     14.6  Étiquettes en ligne       — .etiquette
     14.7  Cadre à défilement        — .tableau-defilant
     14.8  Tableau des lots          — .tableau--lots .lot .tableau__note
     14.9  L'ÉCART  ★                — .ecart et ses états
     14.10 Messages                  — .message-erreur__titre …
     14.11 Élargissements et doigts
     14.12 Impression
============================================================================= */


/* =============================================================================
   14.1 — AVANT LA SESSION
   ---------------------------------------------------------------------------
   `app.html` porte <html class="pre-auth"> et `exigerSession()` (auth.js) retire
   la classe une fois la session confirmée. Tant qu'elle est là, la page est
   invisible.

   ⚠️ CETTE RÈGLE DOIT AGIR AVANT LE PREMIER SCRIPT. C'est le cas : une feuille
   de style liée dans le <head> bloque le rendu, donc rien n'est peint avant
   qu'elle soit lue. Sans elle, l'en-tête et les onglets clignotent une fraction
   de seconde avant la redirection — et laissent croire une seconde que
   l'application s'est ouverte à quelqu'un qui n'y a pas droit.

   `visibility` et non `display: none` : la mise en page est calculée pendant ce
   temps, donc rien ne saute au moment où la classe tombe.

   La même règle est doublée dans un <style> en ligne de `app.html`. Ce n'est pas
   un oubli : si cette feuille n'arrive pas, la page ne doit pas s'ouvrir quand
   même. `index.html` (la connexion) ne porte PAS la classe, volontairement —
   un module ES injoignable y laisserait sinon une page blanche définitive.
============================================================================= */
html.pre-auth body {
  visibility: hidden;
}


/* =============================================================================
   14.2 — EN-TÊTE DE L'APPLICATION
============================================================================= */

/* Le bloc nom + projet courant de `app.html`. Mêmes déclarations que
   .entete__identite (§1) : `min-width: 0` est la seule qui compte vraiment,
   sans elle un long nom de projet fait déborder le flex de l'en-tête. */
.entete__marque {
  display: flex;
  flex-direction: column;
  gap: var(--espace-1);
  min-width: 0;
}

/* Le sélecteur de projet vit dans .entete__outils, à côté de « Déconnexion ».
   `.champ` le pose à 100 % de large : ici on veut qu'il partage la rangée.
   Il garde --taille-base (16 px) : en dessous, iOS zoome à l'ouverture du menu
   et ne redézoome pas. */
.champ--projet {
  flex: 1 1 12rem;
  width: auto;
  min-width: 0;
  max-width: 100%;
}


/* =============================================================================
   14.3 — CARTE D'IDENTITÉ DU PROJET (écran Projet)
============================================================================= */

/* Carte de tête de l'écran : elle porte le sélecteur et l'identité du projet
   ouvert. Une ombre d'un cran au-dessus des cartes de contenu — dans cette
   palette la hiérarchie passe par l'ombre, pas par la couleur (particularité 2).
   Ne pas l'alléger. */
.carte--entete {
  box-shadow: var(--ombre-md);
}

/* La barre d'outils est suivie de .projet-entete, qui pose déjà son propre
   filet et son espace : sans ça, on cumule deux respirations. */
.carte--entete .barre-outils {
  margin-bottom: 0;
}

.projet-entete {
  margin-top: var(--espace-4);
  padding-top: var(--espace-4);
  border-top: var(--trait-fin) solid var(--couleur-bordure);
}

/* Le code de projet est un jeton, pas une phrase : même recette que
   .entete__code (§1), pour que le même objet se lise pareil aux deux endroits. */
.projet-entete__code {
  display: inline-block;
  margin: 0 0 var(--espace-2);
  padding: var(--espace-1) var(--espace-2);
  border-radius: var(--rayon-sm);
  background: var(--couleur-surface-basse);
  font-size: var(--taille-xs);
  font-weight: var(--graisse-semi);
  letter-spacing: var(--lettrage-majuscule);
  text-transform: uppercase;
  color: var(--couleur-texte-secondaire-sur-sable);
}

/* C'est un <h2> : sans cette règle il hérite du clamp de 02-base.css et arrive
   à 36 px dans une carte, plus gros que le titre de la page. */
.projet-entete__nom {
  margin: 0 0 var(--espace-2);
  font-size: var(--taille-lg);
}

.projet-entete .ligne__actions {
  margin-top: var(--espace-3);
}


/* =============================================================================
   14.4 — LISTES « ÉTIQUETTE : VALEUR »
   ---------------------------------------------------------------------------
   Les cartes Propriété et Acquisition alignent une quinzaine de faits courts.
   Un filet au-dessus de CHAQUE ligne (et non entre elles) : en deux colonnes,
   les deux cellules d'une même rangée partagent alors la même hauteur de trait,
   là où un `+ .info` laisserait un trait orphelin en tête de seconde colonne.
============================================================================= */
.grille-infos {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.info {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--espace-1) var(--espace-4);
  padding-block: var(--espace-2);
  border-top: var(--trait-fin) solid var(--couleur-bordure);
}

/* ⚠️ AUCUNE propriété de disposition ici : `.info__etiquette` sert AUSSI seule,
   dans une cellule du tableau des factures (ecran-argent.js), pour le motif
   d'une facture hors engagement. Elle ne doit donc rien supposer de son parent. */
.info__etiquette {
  font-size: var(--taille-sm);
  color: var(--couleur-texte-secondaire);
}

/* Beaucoup de ces valeurs sont des montants : tabulaires et à droite, elles se
   comparent d'un coup d'œil d'une ligne à l'autre. */
.info__valeur {
  margin-left: auto;
  text-align: right;
  font-weight: var(--graisse-semi);
  font-variant-numeric: tabular-nums;
  overflow-wrap: break-word;
  color: var(--couleur-texte);
}


/* =============================================================================
   14.5 — FORMULAIRES
============================================================================= */

/* Deux champs qui ne se lisent bien qu'ensemble (dates, taxes, taux).
   Recopie de .champ-rangee (§7) sous le nom qu'emploient les écrans. */
.champ-paire {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--espace-4);
}

/* Valeur immuable affichée à la place d'un champ (la propriété d'un projet déjà
   créé). Elle a EXACTEMENT l'aspect d'un `.champ:disabled` — c'est honnête :
   ça ressemble à un champ qu'on ne peut pas modifier parce que c'en est un. */
.champ-fige {
  margin: 0;
  padding: var(--espace-3);
  border: var(--trait-fin) solid var(--couleur-bordure);
  border-radius: var(--rayon-md);
  background: var(--couleur-surface-basse);
  font-size: var(--taille-base);
  line-height: var(--interligne-titre);
  color: var(--couleur-texte-secondaire-sur-sable);
}

/* --- Cases à cocher --------------------------------------------------------
   .case-label est la cible tactile : c'est le <label> entier qu'on vise avec un
   gant, pas le carré de 24 px. */
.champ-groupe--case {
  gap: var(--espace-2);
}

/* L'aide se cale sous le TEXTE du libellé, pas sous la case : la colonne de
   gauche vaut la case (--espace-5) plus l'écart du label (--espace-3). */
.champ-groupe--case > .champ-aide {
  padding-left: calc(var(--espace-5) + var(--espace-3));
}

.case-label {
  display: flex;
  align-items: flex-start;
  gap: var(--espace-3);
  font-size: var(--taille-sm);
  line-height: var(--interligne-corps);
  cursor: pointer;
}

/* `align-items: flex-start` + ce décalage : sur un libellé de deux lignes, la
   case reste alignée sur la PREMIÈRE, pas centrée sur le bloc. */
.case {
  flex: 0 0 auto;
  width: var(--espace-5);
  height: var(--espace-5);
  margin-top: var(--espace-1);
  accent-color: var(--couleur-accent);
  cursor: pointer;
}

/* --- Choix des lots au catalogue (modale « Ajouter des lots ») ------------- */

/* `min-inline-size: 0` : un <fieldset> vaut `min-content` par défaut, et le plus
   long nom de lot élargirait la modale au-delà de l'écran. */
.bloc-lots {
  min-inline-size: 0;
  margin: 0;
  padding: var(--espace-2) var(--espace-3) var(--espace-3);
  border: var(--trait-fin) solid var(--couleur-bordure);
  border-radius: var(--rayon-md);
}

.bloc-lots__titre {
  padding-inline: var(--espace-2);
  font-size: var(--taille-xs);
  font-weight: var(--graisse-semi);
  letter-spacing: var(--lettrage-majuscule);
  text-transform: uppercase;
  color: var(--couleur-texte-secondaire);
}

.choix-lot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-2) var(--espace-3);
  min-height: 2.75rem;
  padding: var(--espace-2);
  border-radius: var(--rayon-sm);
  font-size: var(--taille-sm);
  cursor: pointer;
}

.choix-lot:hover {
  background: var(--couleur-surface-basse);
}

/* La case cochée porte déjà l'état (accent-color) : ce voile n'est qu'un
   renfort pour retrouver ses choix dans une liste de soixante lots. Si
   `:has()` manque au navigateur, on perd le voile, jamais l'information. */
.choix-lot:has(.case:checked) {
  background: var(--couleur-accent-voile);
}

/* Dans cette rangée en flex, l'écart est déjà tenu par `gap`. */
.choix-lot .case {
  margin-top: 0;
}

.choix-lot .etiquette {
  margin-left: 0;
}

/* Largeur plancher : les codes du catalogue font quatre lettres (ARCH, DEMO,
   PERM…), les noms s'alignent donc tous sur la même verticale. */
.choix-lot__code {
  flex: 0 0 auto;
  min-width: 4rem;
  font-weight: var(--graisse-semi);
  letter-spacing: var(--lettrage-majuscule);
  color: var(--couleur-texte);
}

.choix-lot__nom {
  flex: 1 1 10rem;
  min-width: 0;
  color: var(--couleur-texte);
}

/* Lot déjà monté sur ce projet dans cette zone : la case est `disabled` côté JS.
   ⚠️ Le gris seul ne dit rien à qui distingue mal les contrastes — d'où le mot,
   ajouté ici pour que l'état se LISE, comme partout ailleurs dans ce fichier. */
.choix-lot--deja,
.choix-lot--deja:hover {
  background: transparent;
  cursor: not-allowed;
}

.choix-lot--deja .choix-lot__code,
.choix-lot--deja .choix-lot__nom {
  color: var(--couleur-texte-secondaire);
}

.choix-lot--deja .choix-lot__nom::after {
  content: " · déjà monté";
  font-size: var(--taille-xs);
}


/* =============================================================================
   14.6 — ÉTIQUETTES EN LIGNE
   ---------------------------------------------------------------------------
   Plus légère qu'un `.badge` (§9), qu'elle ne remplace pas : le badge porte un
   STATUT, l'étiquette une précision accolée à un libellé déjà présent
   (« retenue 10 % », « permis », « hors engagement »).
============================================================================= */
.etiquette {
  display: inline-block;
  margin-left: var(--espace-2);
  padding: 0 var(--espace-2);
  border-radius: var(--rayon-sm);
  background: var(--couleur-surface-basse);
  font-size: var(--taille-xs);
  font-weight: var(--graisse-semi);
  line-height: var(--interligne-corps);
  white-space: nowrap;
  color: var(--couleur-texte-secondaire-sur-sable);
}

/* « quittance exigée », « hors engagement » : ça bloque un paiement. Le glyphe
   ouvre, comme pour .badge--alerte — la couleur ne parle jamais seule. */
.etiquette--alerte {
  background: var(--couleur-erreur-fond);
  color: var(--couleur-erreur);
}
.etiquette--alerte::before {
  content: "⚠ ";
}


/* =============================================================================
   14.7 — CADRE À DÉFILEMENT HORIZONTAL
   ---------------------------------------------------------------------------
   C'est le nom que les quatre écrans donnent au cadre que §5 appelle
   `.tableau-cadre`. Déclarations RECOPIÉES à l'identique, §5 non touchée.

   `overscroll-behavior-x: contain` est la règle qui compte : sans elle, un
   glissement du pouce arrivé en bout de tableau remonte à la page, qui part de
   côté sous l'en-tête collant.
============================================================================= */
.tableau-defilant {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}

/* Variante figée — en-tête de colonnes collé en haut D'UN CADRE QUI DÉFILE.
   ⚠️ Uniquement via ce modificateur : un <thead> collant sans hauteur maximale
   se colle au haut de la FENÊTRE et passe sous la .barre-haute. */
.tableau-defilant--fige {
  max-height: min(70vh, 40rem);
  overflow-y: auto;
}

.tableau-defilant--fige .tableau thead th {
  position: sticky;
  top: 0;
  z-index: 2;
}

/* Le coin haut-gauche est collé dans les DEUX sens : il passe au-dessus. */
.tableau-defilant--fige .tableau thead th[scope="col"]:first-child,
.tableau-defilant--fige .tableau--lots thead th:first-child {
  z-index: 3;
}


/* =============================================================================
   14.8 — LE TABLEAU DES LOTS (écran Chantier)
   ---------------------------------------------------------------------------
   Huit colonnes, une ligne par lot. Mêmes exigences que §5, à une différence
   près qui change tous les sélecteurs : ici la colonne d'identité est un
   <td class="lot"> et NON un th[scope="row"]. Les règles de .tableau--chantier
   ne l'attrapent pas — elles sont donc reprises pour ce markup-ci.
============================================================================= */

/* Plancher de lisibilité : 12rem pour le lot + sept colonnes chiffrées. En
   dessous, les montants se coupent au milieu. Le cadre défile, pas la page. */
.tableau--lots {
  min-width: 48rem;
}

/* ⚠️ `.num` (§5) vaut (0,1,0) et perd contre `.tableau td` (0,1,1) : dans un
   <tbody>, les montants retombent à gauche — l'exigence (b) de §5 n'est donc
   pas tenue par la seule classe `.num`. On la rétablit ici pour ce tableau,
   sans toucher à §5. Des colonnes de dollars qui ne s'alignent pas ne se
   comparent pas. */
.tableau--lots tbody td.num,
.tableau--lots tbody th.num {
  text-align: right;
}

/* Alternance très douce : sur huit colonnes l'œil perd la ligne. Le sable est le
   seul gris assez proche du blanc pour ne pas trouer la carte. Ces fonds sont
   AUSSI ce que reprend `background: inherit` sur la cellule collée ci-dessous. */
.tableau--lots tbody tr { background: var(--couleur-surface); }
.tableau--lots tbody tr:nth-child(even) { background: var(--couleur-surface-basse); }
.tableau--lots tbody tr:hover { background: var(--couleur-accent-voile); }

/* Colonne d'identité collée à gauche : à la huitième colonne, sans elle, on ne
   sait plus quel lot on lit. `background: inherit` reprend la couleur de SA
   rangée — sans quoi la cellule serait transparente et les chiffres
   défileraient visiblement dessous. */
.tableau--lots td.lot {
  position: sticky;
  left: 0;
  z-index: 1;
  min-width: 12rem;
  max-width: 18rem;
  background: inherit;
  box-shadow: var(--trait-fin) 0 0 var(--couleur-bordure);
  white-space: normal;
}

.tableau--lots thead th:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--couleur-surface);
  box-shadow: var(--trait-fin) 0 0 var(--couleur-bordure);
}

/* --- L'identité d'un lot ---------------------------------------------------
   Les trois fragments servent DEUX markups. Dans la cellule `.lot` du tableau
   du chantier ils sont EMPILÉS (code / libellé / statut · zone). Dans le
   th[scope="row"] du tableau « Budget par lot » (écran Projet) ils sont écrits
   en ligne, séparés par des espaces déjà présents dans le texte : ils y restent
   donc en ligne. D'où l'empilement porté par `.lot`, et non par les fragments. */
.lot .lot__code,
.lot .lot__libelle,
.lot .lot__meta {
  display: block;
}

.lot__code {
  font-size: var(--taille-xs);
  font-weight: var(--graisse-semi);
  letter-spacing: var(--lettrage-majuscule);
  text-transform: uppercase;
  color: var(--couleur-texte-secondaire);
}

.lot__libelle {
  font-weight: var(--graisse-semi);
  color: var(--couleur-texte);
}

.lot__meta,
.lot__zone {
  font-size: var(--taille-xs);
  font-weight: var(--graisse-normale);
  color: var(--couleur-texte-secondaire);
}

/* Unité d'une colonne chiffrée (« hors taxes », « versé »), sous son intitulé.
   Ni majuscules ni interlettrage : c'est une note, pas un second titre — et
   c'est ce qui la distingue de l'intitulé, à taille égale. */
.tableau__unite {
  display: block;
  font-weight: var(--graisse-normale);
  letter-spacing: var(--lettrage-normal);
  text-transform: none;
  color: var(--couleur-texte-secondaire);
}

/* Le pied de ce tableau ne porte AUCUN nombre : il explique pourquoi il n'y a
   pas de ligne de total. Le filet épais des <tfoot> de §5 annonce une somme —
   le laisser ici ferait lire ce paragraphe comme un total. On le ramène donc au
   filet ordinaire, et le texte au corps secondaire.
   (Sélecteur à (0,2,2) : il doit passer devant `.tableau tfoot td` à (0,1,2).) */
.tableau tfoot td.tableau__note {
  border-top: var(--trait-fin) solid var(--couleur-bordure);
  font-size: var(--taille-xs);
  font-weight: var(--graisse-normale);
  line-height: var(--interligne-corps);
  text-align: left;
  white-space: normal;
  color: var(--couleur-texte-secondaire);
}

/* « 14 lots · les lots annulés ne sont pas listés » — le décompte au-dessus du
   tableau. Tabulaire parce qu'il porte un nombre. */
.total-liste {
  margin: 0 0 var(--espace-3);
  font-size: var(--taille-sm);
  font-variant-numeric: tabular-nums;
  color: var(--couleur-texte-secondaire);
}

/* Les deux paragraphes qui disent contre QUOI l'écart se mesure. Ils se lisent
   une fois, pas à chaque coup d'œil : petits, en colonne étroite, sous un
   filet qui les détache du tableau. */
.legende-tableau {
  max-width: var(--largeur-texte);
  margin-top: var(--espace-4);
  padding-top: var(--espace-3);
  border-top: var(--trait-fin) solid var(--couleur-bordure);
  font-size: var(--taille-xs);
  line-height: var(--interligne-corps);
}

.legende-tableau p + p {
  margin-top: var(--espace-3);
}

/* --- Tiroir de détail (paiements d'une facture, écran Argent) ---------------
   ⚠️ `.ligne--detail` n'est PAS un modificateur de `.ligne` (§6) : c'est un
   <tr> qui suit la ligne de facture et en développe les paiements. Le filet
   d'accent à gauche dit qu'il appartient à la rangée du dessus. */
.tableau .ligne--detail {
  background: var(--couleur-surface-basse);
}

.tableau .ligne--detail > td {
  padding: var(--espace-3) var(--espace-4);
  border-left: var(--espace-1) solid var(--couleur-accent);
  border-bottom: var(--trait-moyen) solid var(--couleur-bordure);
  font-size: var(--taille-sm);
  white-space: normal;
  color: var(--couleur-texte);
}


/* =============================================================================
   14.9 — L'ÉCART  ★ LA CLASSE QUI RÉPOND À LA QUESTION DE L'ÉCRAN
   ---------------------------------------------------------------------------
   « Est-ce que ce lot dépasse ? » Tout l'écran est là.

   QUATRE PORTEURS REDONDANTS, JAMAIS UN SEUL :
     1. un GLYPHE   ▲ ▼ =            2. un SIGNE  + / −
     3. un MOT      .ecart__mot      4. la couleur, en dernier
   Sur une impression en noir et blanc, pour qui distingue mal le rouge du vert,
   ou sur un écran en plein soleil au bout d'un chantier, les trois premiers
   suffisent. C'est la règle, pas une précaution.

   DEUX MARKUPS À SERVIR :
     • écran Chantier — <td class="num ecart ecart--…"> avec .ecart__valeur et
       .ecart__mot ; le JS y met déjà le glyphe et le mot.
     • écran Projet, « Budget par lot » — <td class="ecart ecart--…"> avec un
       SEUL texte, sans glyphe, sans mot et SANS `.num`. D'où, ci-dessous,
       l'alignement porté par `.ecart` elle-même, et le glyphe rendu en CSS pour
       ce cas-là seulement.
============================================================================= */

/* (0,2,0) : passe devant `.tableau td` à (0,1,1), qui aligne à gauche. */
.tableau .ecart {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ecart__valeur {
  display: block;
  font-weight: var(--graisse-semi);
  white-space: nowrap;
}

/* Le mot, lui, a le droit de passer à la ligne : « dépassement · 12,3 % » tenu
   d'un bloc élargirait la colonne d'un tiers. */
.ecart__mot {
  display: block;
  margin-top: var(--espace-1);
  font-size: var(--taille-xs);
  font-weight: var(--graisse-normale);
  line-height: var(--interligne-titre);
  white-space: normal;
}

/* DÉPASSEMENT — le fond teinté est le quatrième porteur, celui qui rend la
   colonne repérable en balayant le tableau. Paire de contrastes déjà validée
   par les tokens (celle de .badge--alerte). */
.ecart--depassement {
  background: var(--couleur-erreur-fond);
  color: var(--couleur-erreur);
}

/* ÉCONOMIE — même construction, paire de .badge--ok. */
.ecart--economie {
  background: var(--couleur-succes-fond);
  color: var(--couleur-succes);
}

/* PILE DANS LE BUDGET — rien à signaler : ni fond, ni couleur d'état. */
.ecart--neutre {
  color: var(--couleur-texte);
}
.ecart--neutre .ecart__mot {
  color: var(--couleur-texte-secondaire);
}

/* PAS DE CIBLE — `budget_initial` n'a jamais été figé : il n'y a rien à
   comparer. ⚠️ Cet état doit se lire « rien à dire », et SURTOUT PAS comme une
   économie : aucun vert, aucun fond. Le tiret prend le gris des valeurs
   absentes (.num--vide), mais le MOT reste en texte secondaire — « pas de
   cible » est une phrase à lire, elle doit tenir le contraste AA. */
.ecart--sans-cible {
  background: transparent;
}
.ecart--sans-cible .ecart__valeur {
  font-weight: var(--graisse-normale);
  color: var(--couleur-neutre);
}
.ecart--sans-cible .ecart__mot {
  color: var(--couleur-texte-secondaire);
}

/* ⚠️ LE GLYPHE DE SECOURS. Dans « Budget par lot », la cellule ne contient
   qu'un montant : ni glyphe, ni mot. La couleur y serait SEULE à dire le
   dépassement — exactement ce que ce fichier s'interdit. Le CSS rétablit donc
   le glyphe, et seulement là : `:not(:has(.ecart__valeur))` écarte la cellule
   détaillée du chantier, où le JS l'a déjà posé. Comme dans .num--alerte, le
   glyphe se pose à GAUCHE du nombre : les chiffres restent alignés entre eux. */
.ecart--depassement:not(:has(.ecart__valeur))::before { content: "▲ "; }
.ecart--economie:not(:has(.ecart__valeur))::before { content: "▼ "; }


/* =============================================================================
   14.10 — TITRE ET TEXTE DES MESSAGES
   ---------------------------------------------------------------------------
   ⚠️ CE QUI DICTE CES RÈGLES : `.message-erreur::before` (§10) pose « ⚠ » comme
   contenu EN LIGNE du bloc. Si le titre ou le texte passaient en `display:
   block`, le glyphe resterait SEUL sur sa première ligne à chaque message sans
   titre — et il n'y a pas de titre la plupart du temps. Tout reste donc en
   ligne, le titre séparé par un tiret : « ⚠ Titre — la phrase ».
============================================================================= */
.message-erreur__titre,
.message-succes__titre {
  font-weight: var(--graisse-forte);
}

.message-erreur__titre::after,
.message-succes__titre::after {
  content: " — ";
  font-weight: var(--graisse-normale);
}

/* Une phrase de `messageErreur()` peut porter un nom de contrainte Postgres
   d'un seul tenant : sans coupure, il déborde de la carte. */
.message-erreur__texte {
  display: inline;
  overflow-wrap: break-word;
}

/* Le bouton « Réessayer » d'`ui.js` suit le texte. `flex` (et non le
   `inline-flex` de §8) le fait passer à la ligne suivante ; `fit-content` lui
   évite de s'étirer sur toute la largeur du message. */
.message-erreur > .bouton {
  display: flex;
  width: fit-content;
  margin-top: var(--espace-3);
}


/* =============================================================================
   14.11 — ÉLARGISSEMENTS ET CIBLES TACTILES
   ---------------------------------------------------------------------------
   Tout ce qui précède est écrit pour 390 px. On n'ajoute ici que ce qu'un écran
   plus large permet.
============================================================================= */
@media (min-width: 48rem) {
  .champ-paire { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Deux colonnes de faits : quinze lignes tiennent alors dans un écran. */
  .grille-infos {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--espace-6);
  }

  /* Le sélecteur cesse de s'étirer : un nom de projet long ne doit pas pousser
     « Déconnexion » hors de l'en-tête. */
  .champ--projet { flex: 0 1 auto; max-width: 18rem; }
}

/* --- Téléphone étroit ------------------------------------------------------ */
@media (max-width: 40rem) {
  /* Même resserrement que .tableau--chantier (§13) : on rogne la colonne du
     lot, jamais la taille du texte, qui doit rester lisible dehors. */
  .tableau--lots td.lot { min-width: 9rem; max-width: 11rem; }

  .legende-tableau { max-width: none; }
}

/* --- Cibles tactiles : 44 px, avec des gants ------------------------------- */
@media (pointer: coarse), (max-width: 40rem) {
  .case-label {
    min-height: 2.75rem;
    padding-block: var(--espace-1);
  }
}


/* =============================================================================
   14.12 — IMPRESSION
   ---------------------------------------------------------------------------
   Le tableau des lots finit sur une imprimante, dans une chemise, sur un
   chantier sans réseau. On déplie le défilement et on retire ce qui n'a de sens
   qu'à l'écran. L'écart, lui, garde son glyphe, son signe et son mot : c'est
   précisément le cas où la couleur ne suffit pas.
============================================================================= */
@media print {
  .tableau-defilant,
  .tableau-defilant--fige { overflow: visible; max-height: none; }

  .tableau--lots { min-width: 0; font-size: var(--taille-xs); }

  .tableau--lots td.lot,
  .tableau--lots thead th:first-child { position: static; box-shadow: none; }

  .tableau--lots tbody tr,
  .tableau--lots tbody tr:nth-child(even),
  .tableau--lots tbody tr:hover { background: transparent; }

  /* Fonds teintés retirés : une imprimante ne les rend pas fidèlement, et ils
     ne portaient qu'un quatrième rappel. Le glyphe, le signe et le mot restent. */
  .ecart--depassement,
  .ecart--economie { background: transparent; }

  .carte--entete { box-shadow: none; }
}


/* =============================================================================
   15 — CORRECTIONS DU CONTRÔLE  (2026-09-02)
   -----------------------------------------------------------------------------
   Quatre corrections issues de la relecture du complément ci-dessus. Elles sont
   ICI, en fin de fichier, et non fondues dans les règles d'origine : à
   spécificité égale c'est l'ORDRE qui tranche, donc ces règles gagnent, et on
   garde sous les yeux ce qui a été corrigé et pourquoi.
============================================================================= */

/* 15.1 — CONTRASTE : le tiret d'un lot sans budget était SOUS AA.
   --couleur-neutre (#858585) mesure 3,69 sur --couleur-surface (rangées
   impaires), 3,07 sur --couleur-surface-basse (rangées paires) et 3,26 au
   survol. Le seuil est 4,5. Ce tiret n'est pas un ornement : c'est LA valeur
   que lit Alain pour un lot sans budget, et 3,07 sur un téléphone en plein
   soleil ne se lit pas. --couleur-texte-secondaire tient les trois fonds
   (6,39 · 5,31 · 5,65) tout en restant en retrait des valeurs chiffrées. */
.ecart--sans-cible .ecart__valeur {
  font-weight: var(--graisse-normale);
  color: var(--couleur-texte-secondaire);
}

/* 15.2 — LE MOT, sur l'écran Projet aussi.
   « Budget par lot » (ecran-projet.js) rend une cellule d'écart qui ne contient
   qu'un texte : §14.9 y ajoute déjà le GLYPHE, mais ni signe ni mot. Le glyphe
   suffit à ne pas laisser la couleur seule ; le mot lève la dernière ambiguïté,
   et il est gratuit. `:not(:has(.ecart__mot))` évite de le doubler sur l'écran
   Chantier, dont le JS pose déjà le mot dans le texte. */
.tableau .ecart--depassement:not(:has(.ecart__mot))::after {
  content: " · dépassement";
  font-weight: var(--graisse-normale);
  font-size: var(--taille-xs);
}
.tableau .ecart--economie:not(:has(.ecart__mot))::after {
  content: " · économie";
  font-weight: var(--graisse-normale);
  font-size: var(--taille-xs);
}

/* 15.3 — LA NOTE DE PIED redevient lisible au téléphone.
   Elle est dans un <td colspan="8"> d'un tableau à `min-width: 48rem`, donc
   composée sur 768 px. À 390 px, Alain n'en voyait que 45 % et devait défiler
   latéralement LIGNE PAR LIGNE pour lire la phrase qui explique pourquoi il n'y
   a pas de total. Sortie de la grille du tableau et collée à gauche du cadre,
   elle se lit sans défiler. */
.tableau--lots tfoot,
.tableau--lots tfoot tr { display: block; }

.tableau tfoot td.tableau__note {
  display: block;
  position: sticky;
  left: 0;
  width: min(100vw - var(--espace-8), var(--largeur-texte));
  white-space: normal;
  text-align: left;
}

/* 15.4 — LA VALEUR NE DÉBORDE PLUS de sa carte.
   `.info` est un conteneur flex : `overflow-wrap: break-word` seul n'entre pas
   dans le calcul du min-content, donc `min-width: auto` fige l'item à la
   largeur du plus long mot — une adresse de rue ou un courriel débordait la
   carte à 390 px. `min-width: 0` lève ce plancher, `anywhere` fait compter la
   coupure. */
.info__valeur,
.info__etiquette { min-width: 0; }
.info__valeur { overflow-wrap: anywhere; }


/* =============================================================================
   16 — CARTE REPLIABLE  (ajouté le 2026-09-02)
   -----------------------------------------------------------------------------
   Demandé par Alain : « tout replié, c'est moi qui dois cliquer pour ouvrir ».

   Bâtie sur <details>/<summary>. Le marqueur natif est retiré des DEUX façons
   qu'il faut — `list-style: none` et le pseudo-élément WebKit — parce que
   Safari ignore la première et affiche sinon son triangle en plus du nôtre.
============================================================================= */

.carte--repliable {
  padding: 0;                       /* le rembourrage passe au résumé et au corps */
  overflow: hidden;                 /* le résumé ne déborde pas des coins arrondis */
}

.carte__resume {
  display: flex;
  align-items: flex-start;
  gap: var(--espace-3);
  padding: var(--espace-4) var(--espace-5);
  cursor: pointer;
  list-style: none;                 /* Firefox, Chrome */
  transition: background var(--transition-douce);
}
.carte__resume::-webkit-details-marker { display: none; }   /* Safari */

.carte__resume:hover { background: var(--couleur-surface-basse); }

/* Le focus doit se voir : la carte se plie au clavier, pas seulement à la
   souris, et sans anneau on ne sait plus où on est dans la page. */
.carte__resume:focus-visible {
  outline: var(--trait-moyen, 2px) solid var(--couleur-accent-texte);
  outline-offset: calc(var(--espace-1) * -1);
}

/* Le chevron tourne à l'ouverture. Dessiné en bordures plutôt qu'en caractère :
   un glyphe dépend de la police installée et saute d'un poste à l'autre. */
.carte__chevron {
  flex: 0 0 auto;
  width: var(--espace-3);
  height: var(--espace-3);
  margin-top: var(--espace-2);
  border-right: var(--trait-moyen, 2px) solid var(--couleur-texte-secondaire);
  border-bottom: var(--trait-moyen, 2px) solid var(--couleur-texte-secondaire);
  transform: rotate(-45deg);
  transition: transform var(--transition-douce);
}
.carte--repliable[open] .carte__chevron { transform: rotate(45deg); }

.carte__resume-texte {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--espace-1) var(--espace-3);
  min-width: 0;                     /* sinon un long indice pousse le chevron dehors */
}

.carte__resume .carte__titre { margin: 0; }

/* L'INDICE — ce qui rend le repli supportable. Il dit ce que contient la carte
   sans l'ouvrir ; sans lui, on ouvre tout pour vérifier et le repli n'a servi à
   rien. En retrait du titre, mais au-dessus du seuil AA. */
.carte__indice {
  font-size: var(--taille-sm);
  font-weight: var(--graisse-normale);
  color: var(--couleur-texte-secondaire);
  overflow-wrap: anywhere;
}

.carte__corps {
  padding: 0 var(--espace-5) var(--espace-5);
  border-top: var(--trait-fin) solid var(--couleur-bordure);
  padding-top: var(--espace-4);
}

/* Les actions sont en tête du corps et JAMAIS dans le résumé : un bouton
   imbriqué dans un <summary> plie la carte en même temps qu'il agit, et
   l'action paraît n'avoir rien fait. */
.carte__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-2);
  margin-bottom: var(--espace-4);
}

/* À l'impression, on veut tout lire : rien n'est replié sur papier. */
@media print {
  .carte--repliable .carte__corps { display: block !important; }
  .carte__chevron { display: none; }
  .carte__resume { cursor: auto; }
}


/* =============================================================================
   17 — ZONE DE DÉPÔT DE PIÈCES  (ajouté le 2026-09-02)
   -----------------------------------------------------------------------------
   Le composant de `documents.js`. Il vit ici, dans la feuille des composants
   GÉNÉRIQUES, et non dans 04-acquisition.css : il servira aussi aux soumissions
   du lot 4, aux quittances du lot 3 et aux photos de chantier du lot 2. Une
   règle rangée dans la feuille d'un écran devient invisible pour les suivants.
============================================================================= */

.zone-depot {
  display: flex;
  flex-direction: column;
  gap: var(--espace-3);
}

/* La liste des pièces déjà déposées. Pas de puces : chaque ligne porte déjà son
   nom de fichier, son poids et sa date — un point devant n'ajoute rien. */
.zone-depot__liste {
  display: flex;
  flex-direction: column;
  gap: var(--espace-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Le message de résultat. `role="status"` est posé côté JS : la mise en forme
   ne doit donc rien porter d'autre que du contraste, et surtout pas dépendre de
   la couleur seule — succès et échec se distinguent par leur texte. */
.zone-depot__message {
  margin: 0;
  font-size: var(--taille-sm);
  color: var(--couleur-texte-secondaire);
  overflow-wrap: anywhere;
}

/* Le bouton de dépôt et ce qui l'accompagne. Sur un téléphone tenu d'une main,
   les actions s'empilent plutôt que de se serrer : une cible tactile réduite
   pour tenir sur une rangée est une cible qu'on manque. */
.zone-depot__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-2);
}
