/* Portail Balcon Dépôt — feuille de style unique, sans outil de compilation.
   Le choix est délibéré : l'application précédente s'est retrouvée immobilisée
   parce que sa chaîne de compilation JavaScript ne s'installait plus. */

/* ⚠️ `hidden` NE CACHAIT PAS CE QUI PORTE UN `display` ÉCRIT ICI.

   La règle `[hidden] { display: none }` du navigateur est battue par
   n'importe quelle règle de cette feuille qui pose un `display` : une classe
   l'emporte sur un attribut. Mesuré dans Chromium, sur la vraie feuille :

     .bouton-icone    hidden → display: inline-flex   ⚠️ visible
     .bouton-ajouter  hidden → display: inline-flex   ⚠️ visible
     .carte-section   hidden → display: none          caché
     .trouvailles     hidden → display: none          caché

   Le premier est le BOUTON DE THÈME, dans l'en-tête de toutes les pages du
   portail : il est marqué `hidden` dans le gabarit et révélé par `theme.js`,
   précisément pour qu'il n'apparaisse pas là où le script ne tourne pas. Il
   apparaissait quand même, et ne faisait rien.

   `!important` est ici à sa place : `hidden` dit « cet élément n'est pas
   pertinent », et rien dans une feuille de style ne doit pouvoir contredire
   cela. C'est d'ailleurs ce que recommande la spécification HTML.
   `portail/tests/test_styles.py` tient la règle. */
[hidden] { display: none !important; }

:root {
  /* ⚠️ LA LIGNE QUI MANQUAIT.

     Sans `color-scheme`, le navigateur dessine ses contrôles NATIFS en clair
     quoi qu'on fasse : la liste déroulante qui s'ouvre par-dessus la page,
     les barres de défilement, le calendrier d'un champ date. En mode sombre,
     une liste déroulante s'ouvrait donc sur fond BLANC — avec la couleur de
     texte de la page sombre, c'est-à-dire du gris très pâle sur du blanc.
     Les états d'une soumission étaient illisibles, et aucune règle de
     couleur écrite ici n'y pouvait rien : ce fond n'est pas dessiné par la
     page. Une déclaration par thème, comme pour les variables. */
  color-scheme: light;

  --fond: #f6f8fa;
  --fond-carte: #ffffff;
  --texte: #1f2328;
  --texte-doux: #59636e;
  --bordure: #d1d9e0;
  /* Le rouge pur, #FF0000, partout — demandé explicitement par le client
     après lui avoir montré la mesure.

     Ce qu'elle dit, pour mémoire : le blanc sur ce rouge, et ce rouge sur du
     blanc, donnent tous deux 4,0:1. C'est au-dessus du seuil de 3:1 exigé
     pour du GROS texte — d'où les bandeaux portés à 19,2 px en gras — et
     sous celui de 4,5:1 exigé pour du texte courant. Les liens et les petits
     libellés rouges y sont donc en deçà de la norme.

     Le choix est assumé : c'est la couleur de la marque, et l'écart tient à
     un demi-point sur une échelle qui en compte vingt. Là où le texte est
     long ou petit, on continue d'écrire en --texte ; le rouge sert à
     désigner, pas à faire lire. */
  --accent: #ff0000;
  --accent-texte: #ffffff;
  /* LE NIVEAU EN DESSOUS DU ROUGE. Un aplat gris pour les sous-sections, le
     même dans les deux thèmes — comme le rouge. La règle que l'œil apprend
     une fois : bandeau rouge = section, bandeau gris = sous-section. */
  --bande-sous-section: #6e7781;
  --bande-sous-section-texte: #ffffff;
  /* ⚠️ #E60000 et non #FF0000, et c'est la MÊME décision qu'en mode sombre.

     Le rouge pur sur blanc donne 4,00:1 — sous le seuil de lisibilité du
     petit texte. Tant que ce rouge n'habillait que des liens de tableau, on
     pouvait fermer les yeux ; il habille maintenant un numéro de téléphone
     et une adresse de courriel qu'on lit caractère par caractère avant de
     composer. #E60000 donne 4,78:1 sur une carte blanche et 4,52:1 sur le
     fond de page, pour un rouge que l'œil ne distingue pas du pur.

     Les APLATS, eux, restent en #FF0000 : bandeaux, boutons, pastilles.
     « Le rouge pur partout » tient sur ce qui se voit ; un lien qu'on ne
     lit pas n'est pas rouge, il est absent. */
  --accent-lien: #e60000;
  --accent-sourd: #fdecec;
  --erreur-fond: #ffebe9;
  --erreur-bordure: #ff8182;
  --erreur-texte: #a40e26;
  --rayon: 10px;
  --ombre: 0 1px 3px rgba(31, 35, 40, .12);
  --ombre-fenetre: 0 12px 40px rgba(31, 35, 40, .28);
  --voile: rgba(31, 35, 40, .45);
  --succes: #1b7f37;
  --succes-sourd: rgba(27, 127, 55, .1);
  /* Les APLATS — ceux qui portent du texte blanc — ne changent pas d'un thème
     à l'autre. Voir la note du mode sombre.

     ⚠️ LE VERT EST CELUI DU CLIENT, ET IL EST PLUS CLAIR QUE LE SEUIL.

         « Peux-tu utiliser cette couleur verte partout dans le portail ? »
           — le client, devant un bouton « SE CONNECTER » en #4CAF50.

     Le blanc y tombe à 2,8:1, sous le 4,5:1 du texte courant ; le client
     l'a choisi en le sachant, contre #388E3C qui le tenait presque. Les
     boutons verts écrivent donc en gras. Le TEXTE vert (--succes) ne suit
     pas : écrit en #4CAF50 sur du blanc, il ne se lirait plus. */
  --succes-aplat: #4caf50;
  --vert: #4caf50;
  --vert-fonce: #43a047;
  --bleu: #1976d2;
  --bleu-fonce: #1565c0;
  --bleu-pale: #40c4ff;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Le mode sombre

   Trois états, et non deux : « comme le système », « clair », « sombre ».
   L'employé qui travaille sur un poste d'atelier en plein jour et rentre le
   soir ne veut pas le même écran ; celui qui a réglé son Windows une fois
   pour toutes ne veut pas le régler une seconde fois ici.

   Le jeu sombre est écrit DEUX FOIS, faute de mixin en CSS : une fois pour
   « le système est sombre et personne n'a forcé le clair », une fois pour
   « l'employé a choisi sombre ». Les deux doivent rester identiques — un test
   le vérifie, parce que deux copies finissent toujours par diverger.

   ⚠️ --accent-lien vaut #ff5c5c sur fond sombre et non #ff0000. Ce n'est pas
   une entorse au « rouge pur partout » : les APLATS restent en #FF0000, y
   compris les bandeaux. Seul le petit texte rouge sur fond sombre est
   éclairci, parce que #FF0000 y tombe à 4,33:1 sur une carte — sous le seuil
   — alors que #ff5c5c donne 6,1:1. Un lien qu'on ne lit pas n'est pas rouge,
   il est absent.

   La même règle vaut pour le vert et le bleu, et c'est l'inverse de ce qu'on
   écrit d'instinct : une couleur qui sert de FOND à du texte blanc ne doit
   PAS être éclaircie en mode sombre. Le réflexe « tout éclaircir » avait
   porté --vert à #2ea043 et --succes à #3fb950 ; le blanc y tombait
   respectivement à 3,37:1 et 2,54:1 — la pastille « ✓ » des paramètres
   devenait illisible. Les aplats (--vert, --vert-fonce, --bleu,
   --bleu-fonce, --succes-aplat) gardent donc leur valeur claire dans les
   deux thèmes ; seul --succes, qui n'écrit QUE du texte, s'éclaircit.
   Mesuré, pas supposé : bd-socle/portail/tests/test_theme.py.
   ═════════════════════════════════════════════════════════════════════════ */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="clair"]) {
    color-scheme: dark;
    --fond: #0d1117;
    --fond-carte: #151b23;
    --texte: #e6edf3;
    --texte-doux: #9198a1;
    --bordure: #3d444d;
    --accent: #ff0000;
    --accent-texte: #ffffff;
    --bande-sous-section: #6e7781;
    --bande-sous-section-texte: #ffffff;
    --accent-lien: #ff5c5c;
    --accent-sourd: #2a1113;
    --erreur-fond: #3c1618;
    --erreur-bordure: #b62324;
    --erreur-texte: #ff9a96;
    --ombre: 0 1px 3px rgba(0, 0, 0, .4);
    --ombre-fenetre: 0 12px 40px rgba(0, 0, 0, .6);
    --voile: rgba(0, 0, 0, .6);
    --succes: #3fb950;
    --succes-sourd: rgba(63, 185, 80, .15);
    --succes-aplat: #4caf50;
    --vert: #4caf50;
    --vert-fonce: #43a047;
    --bleu: #1976d2;
    --bleu-fonce: #1565c0;
  --bleu-pale: #40c4ff;
  }
}

:root[data-theme="sombre"] {
    color-scheme: dark;
    --fond: #0d1117;
    --fond-carte: #151b23;
    --texte: #e6edf3;
    --texte-doux: #9198a1;
    --bordure: #3d444d;
    --accent: #ff0000;
    --accent-texte: #ffffff;
    --bande-sous-section: #6e7781;
    --bande-sous-section-texte: #ffffff;
    --accent-lien: #ff5c5c;
    --accent-sourd: #2a1113;
    --erreur-fond: #3c1618;
    --erreur-bordure: #b62324;
    --erreur-texte: #ff9a96;
    --ombre: 0 1px 3px rgba(0, 0, 0, .4);
    --ombre-fenetre: 0 12px 40px rgba(0, 0, 0, .6);
    --voile: rgba(0, 0, 0, .6);
    --succes: #3fb950;
    --succes-sourd: rgba(63, 185, 80, .15);
    --succes-aplat: #4caf50;
    --vert: #4caf50;
    --vert-fonce: #43a047;
    --bleu: #1976d2;
    --bleu-fonce: #1565c0;
  --bleu-pale: #40c4ff;
}
* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--fond);
  color: var(--texte);
  font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}

/* --- Barre supérieure --------------------------------------------------- */

.barre {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: .75rem 1.5rem;
  background: var(--fond-carte);
  border-bottom: 1px solid var(--bordure);
  border-top: 3px solid var(--accent);
}

.marque {
  font-weight: 700;
  font-size: 1.1rem;
  color: var(--accent-lien);
  text-decoration: none;
  letter-spacing: -.01em;
}

.barre-actions {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  font-size: .9rem;
  /* `.barre` se replie déjà, mais pas son contenu : sur un téléphone, les six
     liens dépassaient de 40 px à droite et la page défilait à l'horizontale.
     Trouvé en mesurant `scrollWidth` à 420 px, pas à l'œil — un débordement
     de ce genre ne se voit qu'en glissant le doigt. */
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: .75rem 1.25rem;
}

.barre-actions form { margin: 0; }
.barre-actions a, .lien {
  color: var(--accent-lien);
  text-decoration: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.barre-actions a:hover, .lien:hover { text-decoration: underline; }
.employe { color: var(--texte-doux); }

/* --- Icônes et boutons d'icône -------------------------------------------- */

/* LE PROBLÈME QU'ON RÈGLE ICI, dit par le client : « l'effet année 90 :
   utilisation de texte au lieu de bouton ou icône cliquant ».

   Les gestes répétés d'une rangée — monter, descendre, renommer, dupliquer,
   retirer — étaient de petits mots soulignés. Trois défauts, et aucun n'est
   une question de goût :

     · on ne voit pas où l'on clique. Un mot souligné n'a pas de bord ; la
       cible est le mot lui-même, soit onze pixels de haut ;
     · cinq mots côte à côte se LISENT avant de se viser, et ils se relisent à
       chaque rangée d'un tableau qui en compte trente ;
     · « retirer » a la même apparence que « renommer », alors que l'un se
       rattrape et l'autre non.

   Un bouton d'icône répond aux trois : un bord qui dit où taper, un dessin
   qu'on reconnaît sans lire, et une couleur qui distingue le geste
   irréversible. Le nom du geste reste, porté par `aria-label` et `title` :
   il s'entend au lecteur d'écran et s'affiche au survol. */

.icone {
  /* `em` et non `rem` : l'icône suit la taille du texte du bouton qui la
     porte, donc un bouton agrandi au doigt agrandit son dessin avec lui. */
  width: 1.15em;
  height: 1.15em;
  /* Le trait déborderait la boîte sur les dessins qui touchent le bord du
     repère. */
  overflow: visible;
  /* Sans cela, le SVG s'assoit sur la ligne de base et décale le bouton de
     trois pixels vers le haut. */
  display: block;
  flex: none;
}

/* Le bloc de définitions : il ne se voit jamais. `hidden` suffirait, mais
   une feuille de style d'impression ou un `display` hérité peut le
   ressusciter au milieu de la page. */
.jeu-icones { display: none !important; }

.bouton-icone {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  /* Carré par défaut : c'est la forme d'un bouton qui ne porte qu'un dessin.
     `min-` et non une taille fixe — un bouton qui porte aussi un mot
     s'élargit tout seul. */
  min-width: 2.1rem;
  min-height: 2.1rem;
  padding: 0 .35rem;
  border: 1px solid var(--bordure);
  border-radius: 8px;
  background: var(--fond-carte);
  color: var(--texte-doux);
  font: inherit;
  font-size: .85rem;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}

.bouton-icone:hover {
  background: var(--fond);
  border-color: var(--texte-doux);
  color: var(--texte);
}

/* `:focus-visible` et non `:focus` : le cerne apparaît au clavier, et pas
   après un clic à la souris — où il resterait affiché sans rien signifier. */
.bouton-icone:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Désactivé — les extrémités d'une liste. On le montre plutôt que de le
   cacher : une rangée de commandes qui change de largeur d'une ligne à
   l'autre se vise mal. */
.bouton-icone[disabled] {
  opacity: .3;
  cursor: default;
}
.bouton-icone[disabled]:hover {
  background: var(--fond-carte);
  border-color: var(--bordure);
  color: var(--texte-doux);
}

/* LE GESTE QUI NE SE RATTRAPE PAS. Il reste neutre au repos — un tableau
   rouge à chaque rangée ne désignerait plus rien —, et se colore sous le
   doigt, à l'instant où l'on est sur le point de le faire. */
.bouton-icone-danger:hover {
  background: var(--erreur-fond);
  border-color: var(--erreur-bordure);
  color: var(--erreur-texte);
}

/* Un bouton qui porte son mot en plus du dessin : les gestes rares, où le
   dessin seul demanderait de deviner. */
.bouton-icone-texte { padding: 0 .7rem; }

/* Les boutons d'une rangée se serrent : ils forment un groupe, et l'espace
   entre deux groupes doit rester plus grand que l'espace à l'intérieur. */
.groupe-boutons {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
}
.groupe-boutons form { margin: 0; display: inline-flex; }

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

.contenu {
  max-width: 1100px;
  margin: 0 auto;
  padding: 2rem 1.5rem 4rem;
}

/* Les liens ORDINAIRES du contenu : un téléphone, un courriel, une adresse
   sur la carte. Aucune règle ne les couvrait, si bien qu'ils prenaient les
   couleurs par défaut du navigateur — dont le violet des liens VISITÉS, qui
   sur un fond sombre ne se lit plus du tout.

   `:not([class])` laisse de côté ce qui est déjà habillé : les boutons, les
   liens de tableau, ceux des listes de fichiers.

   Et la même couleur visité que non visité : ici, « déjà cliqué » ne veut
   rien dire — on rappelle un client qu'on a déjà appelé. */
.contenu a:not([class]),
.contenu a:not([class]):visited {
  color: var(--accent-lien);
  text-decoration: underline;
  text-underline-offset: .15em;
}

.contenu a:not([class]):hover { text-decoration-thickness: 2px; }

/* Les options d'une liste déroulante. `color-scheme` suffit sur les
   navigateurs à jour ; ces deux lignes couvrent ceux qui dessinent encore le
   menu natif avec les couleurs de la page. Elles ne coûtent rien et évitent
   de redécouvrir du gris pâle sur blanc sur un poste qu'on n'a pas sous la
   main. */
option {
  background-color: var(--fond-carte);
  color: var(--texte);
}

.contenu-centre {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  max-width: 460px;
}

h1 { font-size: 1.6rem; margin: 0 0 1.5rem; }

/* --- Tuiles ------------------------------------------------------------- */

/* `min(260px, 100%)` : le plancher descend à la place disponible quand elle
   est plus étroite. Mesuré, il ne déborde pas aujourd'hui — l'écran des
   tuiles laisse 272 px à 320 px de fenêtre, donc douze de marge — mais c'est
   la seule grille du portail dont le plancher dépassait les 238 px qu'une
   SECTION laisse à la même largeur. Douze pixels de marge qu'un padding
   ajouté un jour ferait disparaître sans bruit. Toutes les grilles suivent
   désormais la même règle, et `portail/tests/test_mobile.py` la tient. */
.tuiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
  gap: 1rem;
}

.tuile {
  display: flex;
  flex-direction: column;
  gap: .4rem;
  padding: 1.25rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-left: 4px solid var(--couleur-tuile, var(--accent));
  border-radius: var(--rayon);
  box-shadow: var(--ombre);
  color: inherit;
  text-decoration: none;
  transition: transform .12s ease, box-shadow .12s ease;
}

.tuile:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(31, 35, 40, .16);
}

.tuile-indisponible { opacity: .62; }
.tuile-icone { font-size: 1.8rem; line-height: 1; }
.non-classables { color: var(--rouge, #FF0000);
                  font-size: .76rem; font-weight: 600;
                  white-space: nowrap; }

.raison-du-prix { display: block; color: var(--texte-doux);
                  font-size: .76rem; font-weight: 400;
                  max-width: 32rem; }

.version { color: var(--texte-doux); font-size: .72rem;
           font-variant-numeric: tabular-nums;
           white-space: nowrap; }

.tuile-nom { font-weight: 600; font-size: 1.05rem; }
.tuile-pied { margin-top: .4rem; }

.etiquette {
  display: inline-block;
  padding: .15rem .5rem;
  border: 1px solid var(--bordure);
  border-radius: 999px;
  font-size: .75rem;
  color: var(--texte-doux);
}
.etiquette-attente { border-style: dashed; }

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

.carte-connexion, .carte-message {
  width: 100%;
  padding: 2rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  box-shadow: var(--ombre);
}

.carte-connexion { border-top: 4px solid var(--accent); }
.carte-connexion h1 {
  margin-bottom: .25rem;
  text-align: center;
  color: var(--accent-lien);
}
.sous-titre {
  margin: 0 0 1.75rem;
  text-align: center;
  color: var(--texte-doux);
  font-size: .95rem;
}

label {
  display: block;
  margin-bottom: .35rem;
  font-size: .88rem;
  font-weight: 600;
}

input[type="text"], input[type="password"], input[type="email"] {
  width: 100%;
  margin-bottom: 1.1rem;
  padding: .6rem .7rem;
  background: var(--fond);
  color: var(--texte);
  border: 1px solid var(--bordure);
  border-radius: 6px;
  font: inherit;
}

input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.bouton-principal {
  /* `inline-flex` et non `inline-block` depuis que le bouton porte une icône :
     en bloc, `text-align: center` centre le TEXTE et laisse le dessin collé au
     bord gauche d'une barre de mille pixels. Vu à l'écran, pas déduit. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  width: 100%;
  padding: .65rem 1rem;
  background: var(--accent);
  color: var(--accent-texte);
  border: 0;
  border-radius: 6px;
  font: inherit;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}

.bouton-principal:not([disabled]):hover { filter: brightness(1.08); }

/* --- Messages ----------------------------------------------------------- */

.erreur {
  margin-bottom: 1.25rem;
  padding: .7rem .9rem;
  background: var(--erreur-fond);
  border: 1px solid var(--erreur-bordure);
  border-radius: 6px;
  color: var(--erreur-texte);
  font-size: .9rem;
}
.erreur p { margin: .2rem 0; }

.messages { list-style: none; margin: 0 0 1.5rem; padding: 0; }
.message {
  padding: .7rem .9rem;
  margin-bottom: .5rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: 6px;
}

.vide {
  padding: 2rem;
  background: var(--fond-carte);
  border: 1px dashed var(--bordure);
  border-radius: var(--rayon);
  color: var(--texte-doux);
}
.vide p { margin: .4rem 0; }
.vide strong { color: var(--texte); }

.aide {
  margin: 1.25rem 0 0;
  color: var(--texte-doux);
  font-size: .82rem;
  text-align: center;
}

/* --- Cartes de tête d'un tableau de bord -------------------------------- */

.cartes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: .75rem;
  margin-bottom: 2rem;
}

/* « La rangée s'arrête ici. » Un élément qui couvre toutes les colonnes force
   les suivants à la ligne, sans quoi la grille continue de remplir. Hauteur
   nulle : il ne se voit pas, il ne fait que compter. */
.saut-de-rangee {
  grid-column: 1 / -1;
  height: 0;
}

.carte {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding: 1rem 1.1rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-top: 3px solid var(--accent);
  border-radius: var(--rayon);
  box-shadow: var(--ombre);
  color: inherit;
  text-decoration: none;
}

.carte:hover { box-shadow: 0 4px 12px rgba(31, 35, 40, .16); }
.carte-titre { font-size: .85rem; color: var(--texte-doux); }
.carte-nombre { font-size: 1.9rem; font-weight: 700; line-height: 1.1; }
.carte-urgente .carte-nombre { color: var(--accent-lien); }

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

.entete-liste {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.entete-liste h1, .entete-liste h2 { margin: 0; }

h2 { font-size: 1.15rem; margin: 2rem 0 .75rem; }

/* Un tableau à quatorze colonnes ne rentre pas sur un portable : il défile
   dans son cadre plutôt que de pousser la page entière de côté. */
/* UN TABLEAU LARGE PREND TOUTE LA CARTE, marges de la section comprises.
   `.corps-section` l'inset de 1,25 rem de chaque côté — vingt pixels perdus
   deux fois, sur le seul élément de la page qui en manque. Le bandeau rouge
   du titre court déjà d'un bord à l'autre ; le tableau s'aligne sur lui.

   Mesuré : le tableau des commandes fournisseur en demande 1 031 dans une
   boîte de 1 010 depuis que la colonne ID s'y est ajoutée. Les quarante
   pixels rendus ici le font tenir, sans retirer une colonne ni abréger un
   titre. */
.corps-section > .tableau-large {
  margin-left: -1.25rem;
  margin-right: -1.25rem;
}

.corps-section > .tableau-large > .tableau {
  border-left: 0;
  border-right: 0;
  border-radius: 0;
}

.tableau-large { overflow-x: auto; }

.tableau {
  width: 100%;
  border-collapse: collapse;
  font-size: .9rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
}

.tableau th, .tableau td {
  padding: .5rem .6rem;
  text-align: left;
  border-bottom: 1px solid var(--bordure);
  white-space: nowrap;
}
/* LES TITRES DE COLONNE SE REPLIENT, LES DONNÉES JAMAIS.
   Une règle, et l'ascenseur horizontal disparaît de la plupart des tableaux.

   Les colonnes étaient larges à cause de leurs TITRES, pas de leur contenu :
   « Type de livraison » réclamait 180 px pour afficher « BD LONG », et
   « Date (création) » 161 px pour une date de 90. Les dix titres des
   commandes fournisseur en demandaient 1 325 dans une boîte de 1 010, mesuré
   dans un navigateur — d'où la barre de défilement.

   Une donnée, elle, garde son `nowrap` : une date coupée en deux lignes
   (« 2026-09- » / « 12 ») ou un numéro brisé au milieu se relisent mal, et un
   tableau d'atelier se lit en diagonale. */
.tableau th {
  white-space: normal;
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .02em;
  color: var(--texte-doux);
  background: var(--fond);
}
.tableau tbody tr:last-child td { border-bottom: 0; }
.tableau tbody tr:hover { background: var(--accent-sourd); }
.tableau a { color: var(--accent-lien); font-weight: 600; text-decoration: none; }
.tableau a:hover { text-decoration: underline; }

/* LE QUADRILLÉ : des colonnes, et pas seulement des rangées.

   « Ce serait plus clair avec des lignes et des colonnes » — le client,
   devant les commandes fournisseur. Dix colonnes de numéros et de dates
   courtes, séparées par du blanc seul : l'œil glisse d'une colonne à
   l'autre. Un trait vertical entre chaque cellule, aucun au dernier bord
   — le cadre du tableau le tient déjà. */
.tableau-quadrille th,
.tableau-quadrille td { border-right: 1px solid var(--bordure); }
.tableau-quadrille th:last-child,
.tableau-quadrille td:last-child { border-right: 0; }
.tableau-quadrille thead th { border-bottom: 2px solid var(--bordure); }

/* La colonne ne porte plus qu'un « ! », le motif passant dans l'infobulle. */
.tableau td.action-requise { text-align: center; }
.discret { color: var(--texte-doux); }
.vide-ligne { color: var(--texte-doux); text-align: center; padding: 2rem; }

/* --- Pastilles d'indicateur ---------------------------------------------
   Surtout pas « .marque » : ce nom désigne déjà le lien « Balcon Dépôt » de
   l'en-tête. Le lui reprendre lui imposait une largeur de 20 px et tronquait
   le nom de l'entreprise sur toutes les pages portant un tableau — un défaut
   qu'aucun test ne pouvait voir, et que la page d'accueil ne montrait pas. */

.pastille {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 4px;
  font-size: .8rem;
  font-weight: 700;
  line-height: 1;
}
/* Le crochet est un DESSIN depuis qu'il vient du jeu : il lui faut sa taille,
   sinon il prend celle de la pastille et déborde du carré. */
.pastille .icone { width: .85rem; height: .85rem; }
.pastille-oui { background: var(--succes-aplat); color: #fff; }
.pastille-alerte { background: var(--accent); color: var(--accent-texte); }
/* « Mettre la couleur de la pastille "bleu pâle" » — le client, pour la
   colonne Installation du tableau de bord. Elle dit une NATURE du dossier,
   pas une étape franchie : le vert des étapes la faisait lire comme « fait ».
   Le crochet est foncé, le blanc ne se lirait pas sur ce bleu. */
.pastille-installation { background: var(--bleu-pale); color: #06344d; }
.pastille-non { border: 1px solid var(--bordure); }
/* Trois états, pas deux : « je ne sais pas » ne doit pas se lire « non ». */
.pastille-inconnue {
  border: 1px dashed var(--bordure);
  color: var(--texte-doux);
}

/* CE QUI NE SE VOIT PAS MAIS S'ENTEND. Un en-tête de colonne réduit à un
   dessin n'a plus de nom : le lecteur d'écran annonce la colonne par son
   en-tête, et un `<svg>` muet la rend anonyme dans un tableau de cinquante
   rangées. On garde donc le mot, hors de l'écran.

   `clip-path` plutôt que `display: none` ou `width: 0` : les deux retirent
   l'élément de ce qui est ANNONCÉ, ce qui est exactement le contraire du but. */
/* ⚠️ PAS DE `position: absolute` ICI, et c'est une leçon payée comptant.

   La recette courante de « caché à l'œil, lu à voix haute » emploie
   `position: absolute`. Sans ancêtre positionné, l'élément se place alors à
   sa position STATIQUE dans le bloc englobant initial — et cet en-tête de
   colonne vit dans un tableau qui défile horizontalement. Le libellé
   atterrissait donc 27 px hors de l'écran et poussait la PAGE entière : la
   liste des soumissions gagnait une barre de défilement horizontale sur un
   téléphone. Mesuré, pas supposé.

   Un bloc de 1 px qui reste dans le flux ne peut pas s'échapper. Il coûte un
   pixel de mise en page, ce qui est exactement le prix qu'on veut payer. */
.pour-lecteur {
  display: inline-block;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.visuel-seul { display: inline-flex; align-items: center; }


/* La case du consentement. Elle décide si les coordonnées et les PHOTOS d'une
   maison partent chez un tiers : elle mérite d'être vue, et d'être grande
   assez pour qu'on ne la coche pas de travers. */
.ligne-consentement { margin: 0; }

.case-consentement {
  display: flex;
  align-items: flex-start;
  gap: .55rem;
  cursor: pointer;
  font-weight: 600;
}
/* `input[type="checkbox"]` et non `input` : `.champ-case input` porte la même
   spécificité et vient PLUS BAS dans le fichier, donc il gagnait — la case
   sortait rouge alors que cette règle la voulait verte. Constaté à l'écran.

   Verte et non rouge : cochée, elle dit « accordé ». Le rouge de la maison
   sur une case à cocher se lit comme une alerte. */
.case-consentement input[type="checkbox"] {
  width: 1.15rem;
  height: 1.15rem;
  margin: .1rem 0 0;
  flex: none;
  accent-color: var(--succes-aplat);
  cursor: pointer;
}
.case-consentement input:disabled { cursor: default; }

.source-consentement { margin: .35rem 0 0; }
.texte-consentement { margin-top: .2rem; font-size: .85rem; }
.texte-consentement summary {
  color: var(--accent-lien);
  cursor: pointer;
  width: fit-content;
}
.texte-consentement p { margin: .3rem 0 0; }

@media (pointer: coarse) {
  .case-consentement input[type="checkbox"] { width: 1.5rem; height: 1.5rem; }
}


/* La porte d'un côté : son compte, sa largeur, sa place. Les trois sur une
   rangée de tableau, qui doit rester lisible sur un portable. */
.rangee-porte { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem; margin: 0; }
.reglages-porte { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .4rem; }
.champ-porte { display: flex; flex-direction: column; gap: .15rem; }
.champ-porte .etiquette-champ { font-size: .7rem; }
.champ-porte input { width: 5.5rem; margin: 0; }
.champ-porte select { margin: 0; }

/* LE BON DE COUPE. Le verre et la porte ne se lisent pas pareil : l'un se
   commande, l'autre s'installe. Le contraste fait la différence sans qu'on
   ait à écrire « verre » et « porte » quinze fois dans la colonne. */
.colonne-decoupe { white-space: nowrap; }
.morceau {
  display: inline-block;
  padding: .1rem .4rem;
  border-radius: 4px;
  font-variant-numeric: tabular-nums;
}
.morceau-verre { background: var(--fond); border: 1px solid var(--bordure); }
.morceau-porte {
  background: var(--accent-sourd);
  border: 1px solid var(--erreur-bordure);
  font-weight: 600;
}
.separateur-morceau { color: var(--texte-doux); padding: 0 .15rem; }


/* L'aide d'un champ, sous le champ. Petite et douce : elle explique, elle ne
   réclame pas — et elle ne doit pas se lire avant l'étiquette. */



/* Ce qui SERT vraiment, par opposition à ce qui est saisi. Un liseré et un
   fond doux : ce n'est ni une alerte ni un champ, c'est un constat. */
.expediteur-effectif {
  margin: .5rem 0 1rem;
  padding: .6rem .85rem;
  background: var(--fond);
  border-left: 3px solid var(--accent);
  border-radius: 0 6px 6px 0;
  font-size: .9rem;
}


/* --- Détail d'un dossier ------------------------------------------------ */

.indicateurs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: .5rem;
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
}
.indicateur { display: flex; align-items: center; gap: .5rem; }

.fichiers, .notes { list-style: none; margin: 0; padding: 0; }
.fichiers li, .notes li {
  padding: .55rem .8rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: 6px;
  margin-bottom: .4rem;
}
.notes li p { margin: .25rem 0 0; }

.formulaire-note { display: flex; gap: .5rem; margin-bottom: 1rem; }
.formulaire-note textarea { flex: 1; margin-bottom: 0; }

/* `wrap` : la barre porte un champ de recherche ET la rangée des dix états.
   En rangée unique sur un téléphone, les états étaient écrasés à 126 px pour
   un contenu qui en demande 300, et les pastilles sortaient de l'écran de
   96 px — mesuré à 390 px de large. Elles passent sous le champ. */
.recherche {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin: 1rem 0 1.5rem;
}
.recherche input { margin-bottom: 0; }

.carte-formulaire {
  max-width: 520px;
  padding: 1.5rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-top: 3px solid var(--accent);
  border-radius: var(--rayon);
  box-shadow: var(--ombre);
}

.bouton-principal.bouton-compact { width: auto; padding: .45rem 1rem; }

textarea {
  width: 100%;
  padding: .6rem .7rem;
  background: var(--fond);
  color: var(--texte);
  border: 1px solid var(--bordure);
  border-radius: 6px;
  font: inherit;
  margin-bottom: 1.1rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Les fenêtres d'édition

   L'allure reprend celle des écrans actuels — étiquette au-dessus, champ
   souligné, actions en bas à droite — parce que les employés la connaissent.
   Ce qui change est dessous : aucune chaîne de compilation, aucun cadriciel.
   ═════════════════════════════════════════════════════════════════════════ */

/* La LARGEUR est portée par le formulaire, pas par la fenêtre.

   Elle était sur la fenêtre, à min(1400px, 96vw) : toute fenêtre s'ouvrait
   donc à 1400 px, y compris « Ajouter une action » et ses deux champs. Une
   liste déroulante de 4 cm perdue au milieu d'un écran de 37 — l'œil doit
   traverser la fenêtre entière pour aller du champ au bouton.

   `fit-content` — et non `auto`, qui ÉCRASERAIT le `fit-content` que le
   navigateur donne d'office à un <dialog>, le rendant aussi large que la
   place disponible. Mesuré : avec `auto`, la fenêtre restait à 1728 px sur un
   écran de 1800. La fenêtre prend donc la taille de son contenu, et c'est le
   formulaire qui la fixe, selon ce qu'il contient. Ni :has() ni JavaScript :
   la règle vit là où l'information se trouve. */
.dialogue {
  width: fit-content;
  max-width: 96vw;
  max-height: 92vh;
  padding: 0;
  border: none;
  border-radius: var(--rayon);
  background: var(--fond-carte);
  color: var(--texte);
  box-shadow: var(--ombre-fenetre);
}

.dialogue::backdrop { background: var(--voile); }

/* LA FENÊTRE QUI AVERTIT, sans formulaire à remplir : « Le bon de commande
   a été modifié. » Son texte, puis ses deux gestes. */
.dialogue-avis {
  width: min(520px, 92vw);
  padding: 1.25rem 1.5rem;
}
.dialogue-avis h2 { margin: 0 0 .75rem; font-size: 1.2rem; }
.dialogue-avis .actions-dialogue { margin-top: 1.25rem; align-items: center; }
.dialogue-avis .actions-dialogue form { margin: 0; }
.bon-modifie { margin: 1rem 0 .5rem; }

.formulaire-dialogue {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  padding: 0 1.5rem 1.25rem;
  /* ⚠️ `max-width` ET NON `width`, ET C'EST LA CORRECTION.

     C'était `width: min(720px, 96vw)` — une largeur mesurée sur la FENÊTRE
     DU NAVIGATEUR. Dans un `<dialog>`, qui flotte au-dessus de la page,
     c'est juste. Mais ce même gabarit est rendu EN PLEINE PAGE quand le
     JavaScript n'est pas là, et là il vit dans `.contenu`, qui fait 1100 px
     moins son rembourrage. Le formulaire se taillait alors sur l'écran et
     débordait de son cadre.

     Mesuré : la fiche fournisseur, large de 1100, sortait de 25 px à
     1400 px de fenêtre ; à 1024, elle réclamait 1008 px dans un cadre de
     792 et faisait défiler la PAGE entière de travers ; à 390, elle
     dépassait encore de 34. Signalé par le client : « beaucoup d'erreurs de
     formatage de section et de pop-up ».

     `width: 100%` + `max-width` dit la seule chose qui vaille dans les deux
     situations : prends la place qu'on te donne, sans dépasser cette
     largeur-ci. */
  width: 100%;
  max-width: min(720px, 96vw);
}

/* Celles qui portent un TABLEAU. La fiche de commande fournisseur en a deux,
   plus une quinzaine de champs : l'y comprimer produirait une colonne par
   ligne. La largeur s'annonce dans le gabarit, à côté du contenu qui la
   justifie. */
/* 1100 px et non 1400 : c'est ce que réclame le tableau des envois, à six
   colonnes. Les 300 px de plus ne servaient qu'à étirer les champs — voir le
   plafond posé sur `.champ`. */
.formulaire-dialogue.dialogue-large { max-width: min(1100px, 96vw); }

/* DANS UNE FENÊTRE, le formulaire donne sa largeur au dialogue : il doit
   donc réclamer la sienne, et non « tout ce qu'on lui donne » — un
   `width: 100%` dans un `<dialog>` en `fit-content` se réduirait à la
   largeur du plus long mot. En pleine page, c'est l'inverse, et c'est la
   règle de base ci-dessus qui s'applique. */
.dialogue .formulaire-dialogue { width: min(720px, 96vw); }
.dialogue .formulaire-dialogue.dialogue-large { width: min(1100px, 96vw); }

/* Le formulaire défile, pas la fenêtre : le titre et les actions restent
   visibles sur un écran de portable, où la fiche dépasse la hauteur.
   Uniquement DANS la fenêtre — en pleine page, c'est la page qui défile, et
   imposer la contrainte ici donnerait un ascenseur dans un ascenseur. */
.dialogue .formulaire-dialogue {
  max-height: 92vh;
  overflow-y: auto;
}

/* ⚠️ ET RIEN NE SE COMPRIME DEDANS. Le formulaire est une colonne flexible :
   ses enfants ont donc `flex-shrink: 1` par défaut, et quand le `max-height`
   ci-dessus mord, ils se font ÉCRASER au lieu de laisser le formulaire
   défiler.

   Normalement la taille minimale automatique les en empêche — un bloc ne
   descend pas sous la hauteur de son contenu. Sauf qu'elle vaut ZÉRO dès que
   la boîte porte un `overflow` autre que `visible`, ce qui est le cas de
   `.tableau-large` (`overflow-x: auto`, pour les tableaux plus larges que la
   fenêtre).

   MESURÉ, fenêtre du fournisseur, historique vide (69 px de contenu) :

        hauteur de la fenêtre     hauteur du tableau
             900 px                    69 px      entier
             800 px                    69 px      entier
             700 px                    15 px      « Aucun envoi » coupé
             600 px                     0 px      plus rien

   Signalé par le client : « on voit l'historique, ce que je veux, mais le
   formatage cache le contenu presque au complet ». Sur un portable de
   1 366 × 768, la fenêtre du navigateur fait à peu près 700 px de haut.

   La règle porte sur TOUS les enfants et non sur le seul tableau : le
   prochain bloc à `overflow` s'écraserait de la même façon, et il serait
   trouvé de la même façon — par quelqu'un qui n'arrive pas à lire son
   écran. */
.formulaire-dialogue > * { flex-shrink: 0; }

.titre-dialogue {
  z-index: 1;
  margin: 0 -1.5rem 0;
  padding: 1rem 1.5rem;
  font-size: 1.15rem;
  font-weight: 600;
  background: var(--fond);
  border-bottom: 1px solid var(--bordure);
}

.sous-titre-dialogue {
  margin: 0;
  font-size: .85rem;
  font-weight: 500;
  color: var(--texte-doux);
}

.section-dialogue {
  margin: 0;
  padding: 1.25rem 0 0;
  border: none;
  border-top: 1px solid var(--bordure);
}

.legende-destination {
  padding: 0 .5rem 0 0;
  font-size: .75rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--texte-doux);
}

/* --- Les champs ---------------------------------------------------------- */

.rangee-champs {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem 2rem;
  align-items: flex-end;
}

.champ {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  /* Ni trop étroit pour lire une date, ni si large qu'un seul champ occupe la
     rangée. La base à 0 laisse les champs se partager l'espace restant. */
  flex: 1 1 12rem;
  min-width: 10rem;
  /* ⚠️ Et ils CESSENT de grandir passé cette largeur.

     Sans ce plafond, `flex-grow: 1` étire les champs jusqu'à remplir la
     rangée : dans la fiche de commande fournisseur, large de 1400 px, une
     liste déroulante affichant « — » occupait 620 px. Les rangées à deux
     champs donnaient 700 px chacune, et l'œil devait traverser la fenêtre
     entière pour aller de l'étiquette à sa valeur.

     22 rem tient la plus longue valeur qu'un de ces champs porte — une
     adresse de courriel de fournisseur — sans aller au-delà. */
  max-width: 22rem;
}

/* Une zone de texte a besoin de plus : c'est une phrase qu'on y écrit, pas
   une valeur. */
.champ:has(textarea) { max-width: 34rem; }

.etiquette-champ {
  font-size: .75rem;
  color: var(--texte-doux);
}

/* `--creux-champ` : la hauteur d'un champ, réglable par écran.

   ⚠️ DEUX RÈGLES DOIVENT S'ACCORDER SUR CETTE VALEUR — celle-ci et celle du
   focus juste dessous, qui retire un pixel pour compenser le trait qui
   s'épaissit. Écrites en littéral, elles ont coïncidé jusqu'au jour où
   l'écran du courriel a resserré ses lignes : le champ se serait alors
   déplacé d'un cheveu en recevant le curseur. Une variable, et il n'y a plus
   qu'un endroit à changer. */
.champ input[type="text"],
.champ input[type="date"],
.champ input[type="number"],
.champ input[type="email"],
/* ⚠️ `password` MANQUAIT À CETTE LISTE, et la liste avait l'air complète.

   Un champ de mot de passe retombait sur la règle générale du portail —
   cadre, coins arrondis, fond gris, et 1,1 rem de marge sous lui. Au milieu
   d'une colonne de soulignements, les quatre mots de passe de l'écran du
   courriel étaient les seuls encadrés, sans qu'aucune règle ne l'ait voulu.
   Ça ne s'est vu qu'en resserrant les lignes : les 17,6 px de marge qui les
   accompagnaient se perdaient dans le blanc de l'ancienne mise en page.

   Le champ de la page de connexion, lui, ne vit PAS dans un `.champ` : il
   garde son cadre, et c'est très bien — c'est un formulaire, pas une liste
   de réglages. */
.champ input[type="password"],
.champ select,
.champ textarea {
  width: 100%;
  padding: var(--creux-champ, .35rem) .1rem;
  font: inherit;
  color: inherit;
  background: transparent;
  border: none;
  /* Le soulignement seul, comme sur les écrans actuels. */
  border-bottom: 1px solid var(--bordure);
  border-radius: 0;
}

.champ textarea {
  padding: .5rem;
  border: 1px solid var(--bordure);
  border-radius: 4px;
  resize: vertical;
}

.champ input:focus,
.champ select:focus,
.champ textarea:focus {
  outline: none;
  border-bottom-color: var(--accent-lien);
  border-bottom-width: 2px;
  /* Le trait passe de 1 à 2 px : sans compensation, tout le champ remonte
     d'un pixel au focus et la rangée tressaute. */
  padding-bottom: calc(var(--creux-champ, .35rem) - 1px);
}

.champ textarea:focus { border-color: var(--accent-lien); padding-bottom: .5rem; }

/* ⚠️ UN CHAMP FIGÉ DOIT AVOIR L'AIR FIGÉ. La règle des champs remet la
   couleur et le fond d'un champ ouvert, et le gris du navigateur partait
   avec : le « Représentant (figé) » de BD-Prospect avait l'air modifiable et
   ne prenait aucune frappe. Placée APRÈS cette règle, pour l'emporter. */
.champ input:disabled,
.champ select:disabled,
.champ textarea:disabled {
  color: var(--texte-doux);
  border-bottom-style: dashed;
  cursor: not-allowed;
}

/* ──────────────────────────────────────────────────────────────────────────
   LA RECHERCHE D'ARTICLE — la liste que `verre/accessoire.js` installe.

   Le catalogue offert compte 204 articles. Le déroulant du navigateur, lui,
   ne filtre que sur un seul morceau de texte pris tel quel : « panneau 20 »
   n'y trouve rien, « 20 x 46 » non plus — le catalogue écrit « × ». D'où une
   liste à nous, qui cherche mot à mot et sans accents.

   AUCUNE LARGEUR FIXE ICI. `.champ` plafonne à 22 rem, et un nom de panneau
   avec son prix n'y tient pas : le panneau a donc le droit d'être plus large
   que son champ, sans jamais dépasser l'écran — voir
   `portail/tests/test_mobile.py`, qui refuse les largeurs en pixels. */
.recherche-article { position: relative; display: block; }

.trouvailles {
  position: absolute;
  z-index: 40;
  top: 100%;
  left: 0;
  margin: .15rem 0 0;
  padding: .2rem;
  list-style: none;
  min-width: 100%;
  width: max-content;
  max-width: min(30rem, 86vw);
  max-height: 50vh;
  overflow-y: auto;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: .4rem;
  box-shadow: 0 .4rem 1.2rem rgba(0, 0, 0, .18);
}

.trouvailles li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: .3rem .45rem;
  border-radius: .25rem;
  font-size: .85rem;
}

.trouvailles li[data-nom] { cursor: pointer; }
.trouvailles li[data-nom]:hover,
.trouvailles li[aria-selected="true"] {
  background: var(--accent);
  color: var(--accent-texte);
}

.trouvailles .prix-trouve { color: var(--texte-doux); white-space: nowrap; }
.trouvailles li[data-nom]:hover .prix-trouve,
.trouvailles li[aria-selected="true"] .prix-trouve {
  color: var(--accent-texte);
}

/* Ni l'une ni l'autre ne se choisit : elles DISENT quelque chose. Taire le
   nombre d'articles cachés ferait lire une liste coupée comme une liste
   complète — et conclure que l'article n'existe pas. */
.trouvailles .trouvaille-vide,
.trouvailles .trouvaille-reste {
  color: var(--texte-doux);
  font-size: .8rem;
  cursor: default;
}

.champ-case {
  flex-direction: row;
  /* `flex-start` ET NON `center`, avec la case décalée d'un cheveu.

     Mesuré dans la fenêtre fournisseur : les rangées SONT alignées par le
     bas, contrôle contre contrôle — sauf « Date confirmée avec fournisseur »,
     dont le libellé passe à deux lignes. `center` posait alors la case au
     MILIEU des deux, et elle décrochait de ses trois voisines qui n'en ont
     qu'une. Sur un libellé d'une ligne, les deux règles donnent le même
     résultat au pixel près ; sur deux, celle-ci garde la case sur la
     première. C'est aussi ce que fait déjà la case du consentement. */
  align-items: flex-start;
  gap: .5rem;
  /* Une case prend la place de son libellé, et rien de plus.

     Elle héritait du `min-width: 10rem` des autres champs : « Paiement Reçu »
     réservait donc une colonne entière de formulaire pour une case à cocher,
     et les rangées mêlant cases et champs se disloquaient. */
  flex: 0 0 auto;
  min-width: 0;
  max-width: none;
}

/* Le décalage qui centre la case sur la PREMIÈRE ligne du libellé : une case
   fait 13 px, une ligne de texte 21. */
.champ-case input[type="checkbox"] { margin-top: .2rem; }

.champ-case input { width: 1.1rem; height: 1.1rem; accent-color: var(--accent-lien); }
.etiquette-case { font-size: .9rem; }

.champ-en-erreur input,
.champ-en-erreur select { border-bottom-color: var(--erreur-bordure); }

.erreur-champ {
  font-size: .75rem;
  color: var(--erreur-texte);
}

/* --- L'envoi du croquis --------------------------------------------------- */

.rangee-envoi { align-items: flex-end; }
.rangee-envoi .champ:first-child { flex: 2 1 20rem; }

.bouton-envoi {
  flex: 0 0 auto;
  padding: .5rem 1.5rem;
  font-size: 1rem;
  color: #fff;
  background: var(--bleu);     /* le bleu d'action de l'écran actuel */
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.bouton-envoi:not([disabled]):hover { background: var(--bleu-fonce); }

/* ═══ UN BOUTON DÉSACTIVÉ SE LIT DÉSACTIVÉ ═════════════════════════════════

   La règle existait, écrite deux fois et POSÉE NULLE PART — une fois sur
   `.bouton-icone`, une fois sur les boutons de l'écran des demandes. Partout
   ailleurs, un bouton désactivé gardait son aplat de couleur pleine.

   ⚠️ CE QUE ÇA DONNAIT, ET C'EST LE CLIENT QUI L'A VU. Dans la fenêtre du
   fournisseur, le portail écrit « Ce dossier n'est PAS approuvé en interne :
   un croquis ne part pas chez un fournisseur avant de l'être » — et juste à
   côté, le bouton d'envoi s'affichait en bleu plein, exactement comme quand
   l'envoi est permis. L'écran disait donc deux choses contraires en même
   temps. Le bouton portait bien `disabled`, le clic ne faisait rien, la vue
   aurait refusé de toute façon : le DROIT était déjà retiré. Ce qui manquait,
   c'est que ça se VOIE.

   C'est le même défaut que partout ailleurs dans ce portail — une règle qui
   agit sans se montrer —, pris par l'autre bout : un écran qui offre ce que
   la règle interdit.

   `pointer-events: none` aurait suffi à tuer le survol, et aurait emporté
   l'infobulle avec — or c'est elle qui dit POURQUOI (« Approbation interne
   requise »). Le survol est donc neutralisé à la main, `:not([disabled])` sur
   chaque règle de survol d'un bouton qui peut être désactivé. */
button[disabled],
input[type="submit"][disabled] {
  opacity: .45;
  /* La couleur EN PLUS de l'opacité : sur un fond clair, un aplat translucide
     reste coloré, et un bleu pâle se lit encore comme un bouton d'action. */
  filter: grayscale(.75);
  cursor: not-allowed;
}


/* --- Les actions ---------------------------------------------------------- */

.actions-dialogue {
  display: flex;
  justify-content: flex-end;
  gap: 1.5rem;
  margin: 0 -1.5rem -1.25rem;
  padding: 1rem 1.5rem;
  background: var(--fond-carte);
  border-top: 1px solid var(--bordure);
}

/* ENREGISTRER ET FERMER — la même remarque du client, appliquée au geste le
   plus fréquent de la journée.

   Les deux étaient des MOTS colorés, sans bord et sans fond : « ENREGISTRER »
   en vert au bas de chaque emplacement, « TOUT EFFACER » au bout de la barre
   du tracé. Sur l'écran de soumission, l'action la plus importante de la page
   était ainsi la moins visible des trois — les deux boutons secondaires, eux,
   étaient pleins.

   ENREGISTRER devient donc un aplat : c'est le geste qui confirme, il se voit
   de loin et se vise sans chercher. FERMER garde un simple contour — annuler
   ne doit pas attirer la main. */
.bouton-fermer,
.bouton-enregistrer {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  min-height: 2.25rem;
  padding: .45rem 1rem;
  font: inherit;
  font-weight: 600;
  letter-spacing: .03em;
  text-decoration: none;
  border-radius: 8px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease, filter .12s ease;
}

/* `--succes-aplat` et non `--succes` : c'est la variable des fonds qui
   portent du texte blanc, et elle ne change pas d'un thème à l'autre —
   le vert clair du mode sombre donnerait du blanc sur vert pâle. */
.bouton-enregistrer {
  background: var(--succes-aplat);
  border-color: var(--succes-aplat);
  color: #fff;
}
.bouton-enregistrer:not([disabled]):hover { filter: brightness(1.12); }

.bouton-fermer {
  background: var(--fond-carte);
  border-color: var(--bordure);
  color: var(--texte);
}
.bouton-fermer:not([disabled]):hover { background: var(--fond); border-color: var(--texte-doux); }

.bouton-fermer:focus-visible,
.bouton-enregistrer:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .bouton-fermer, .bouton-enregistrer { transition: none; }
}

/* ⚠️ LA MARGE HORIZONTALE COMPTE DIX FOIS. Elle valait .75rem : douze
   pixels de chaque côté, sur dix colonnes, soit 240 px de la largeur du
   tableau des commandes fournisseur — plus que ce qui le sortait de sa
   boîte. Un tableau compact serré à .4rem reste aéré : ses colonnes ne sont
   séparées par aucun filet, et treize pixels entre deux valeurs suffisent à
   les distinguer. */
.tableau-compact th,
.tableau-compact td { padding: .4rem .4rem; font-size: .85rem; }

/* Sur un téléphone, une rangée de cinq champs devient illisible : ils
   s'empilent plutôt que de se serrer à trois caractères de large. */
@media (max-width: 700px) {
  .champ { flex: 1 1 100%; }
  .formulaire-dialogue { padding: 0 1rem 1rem; }
  .titre-dialogue { margin: 0 -1rem; padding: .85rem 1rem; }
  .actions-dialogue { margin: 0 -1rem -1rem; padding: .85rem 1rem; }
}

.dialogue .titre-dialogue { position: sticky; top: 0; }
/* ⚠️ ET EN PLEINE PAGE AUSSI. Une fenêtre rouverte par son adresse — un
   rechargement, un Ctrl+clic — perd la fenêtre et garde le formulaire :
   « Fiche du client » faisait 851 px, et ENREGISTRER tombait sous le bord
   de l'écran, sans rien en haut. Collée au bas, la barre suit, comme dans
   la fenêtre. Mesuré à la revue des boutons ; voir
   `portail.tests.test_boutons_en_haut`. Les barres posées EN HAUT d'un
   formulaire (`actions-en-ligne`) ne collent pas : elles sont déjà en
   haut. */
.dialogue .actions-dialogue,
.dialogue-en-page .actions-dialogue:not(.actions-en-ligne) {
  position: sticky;
  bottom: 0;
}

/* --- Les rangées : une grille, et non une boîte flexible ------------------
   En flex, une rangée de cinq champs qui déborde renvoie le cinquième à la
   ligne — où il s'étire sur toute la largeur, parce que rien ne le retient.
   « Livraison » occupait ainsi une rangée entière à lui seul. Une grille
   range les champs en colonnes égales et garde cette largeur quel que soit
   leur nombre. */
.rangee-champs {
  display: grid;
  /* 10,5 rem et un écart de 1,5 rem : cinq colonnes tiennent dans la fenêtre,
     ce qui met la rangée d'en-tête sur une seule ligne comme sur l'écran
     actuel. À 11 rem et 2 rem d'écart il manquait quatre pixels, et
     « Livraison » partait seul à la ligne suivante. */
  /* ⚠️ `min(10.5rem, 100%)` ET NON `10.5rem` TOUT COURT.
     Une colonne de 10,5 rem ne peut pas rétrécir sous 168 px, même quand la
     fenêtre en fait moins. Mesuré à 320 px — un iPhone SE : la rangée
     poussait la page de trois pixels, et un bouton sortait de l'écran. Le
     `min` laisse la colonne descendre à la largeur disponible quand celle-ci
     est plus étroite, et ne change rien au-dessus. */
  grid-template-columns: repeat(auto-fit, minmax(min(10.5rem, 100%), 1fr));
  gap: 1.25rem 1.5rem;
  align-items: end;
}

/* Un bouton ne sort pas de sa colonne. Son libellé est écrit en toutes
   lettres — « MODIFIER LA FICHE » fait 178 px —, et sur un écran de 320 px
   c'est plus large que la place qu'il a. Il passe à la ligne plutôt que de
   pousser la page. */
.bouton-ajouter, .bouton-enregistrer { max-width: 100%; }

/* Les champs s'alignent par le bas — leurs soulignements forment une ligne.
   Sauf quand la rangée porte une zone de texte : haute de plusieurs lignes,
   elle enverrait les autres champs tout en bas d'une rangée presque vide. */
.rangee-champs:has(textarea) { align-items: start; }

/* ═══ L'ÉCRAN DU COURRIEL : UN RÉGLAGE PAR LIGNE ═══════════════════════════

   « 1 email par ligne. Rendu ecran Super compacte » — le client, mot pour
   mot. Les deux ensemble, et c'est ce qui rend la chose intéressante : une
   adresse par ligne a l'air du contraire de compact.

   Elle ne l'est pas, parce que L'ÉTIQUETTE PASSE À GAUCHE. Trois colonnes
   d'adresses coûtaient 78 px pour trois champs — 26 px chacun — mais chaque
   champ y payait DEUX lignes : son étiquette au-dessus, sa case en dessous.
   À gauche, l'étiquette ne coûte plus rien : elle partage la ligne de sa
   case.

   MESURÉ, la même page dans le même navigateur, à 1 100 px :

                                      lignes   hauteur   adresses coupées
     trois colonnes, étiquette dessus     21    3 317 px      1
     une ligne, étiquette à gauche        37    2 961 px      0

   (37 réglages dans les deux cas ; hauteurs relevées la clé des secrets
   posée, donc sans le bandeau d'avertissement qui vaut 249 px.)

   Onze pour cent de page en moins EN METTANT UNE ADRESSE PAR LIGNE. Le
   compte tient parce que la ligne d'un réglage est passée de 41,6 px —
   36 de case, 5,6 de la marge que `label` pose sur tout le portail — à 28.

   ⚠️ ET LES SOULIGNEMENTS FORMENT UNE COLONNE, ce qu'une grille de colonnes
   égales ne donnait justement pas : les étiquettes étaient toutes de
   longueurs différentes, donc les traits partaient de trois hauteurs et de
   trois largeurs. Ici toutes les cases commencent au même pixel, et l'œil
   descend la liste sans chercher.

   CE QUE ÇA RÈGLE AUSSI, ET QUI ÉTAIT LE PLUS COÛTEUX : plus rien n'est
   coupé. Les colonnes de 12,5 rem donnaient 235 px par champ, où
   « installation@balcondepot.ca » se lisait « installation@balcondep ». Un
   réglage qu'on ne peut pas RELIRE est un réglage qu'on ressaisit à
   l'aveugle — et celui-ci décide de l'adresse d'où part le courrier de
   l'entreprise. Une ligne entière lui laisse 21 rem, largement de quoi.
   ═══════════════════════════════════════════════════════════════════════════ */

.rangee-reglages {
  /* La largeur de la colonne des étiquettes.

     ⚠️ MESURÉE DANS LE NAVIGATEUR, ET LE CALCUL DE TÊTE ÉTAIT FAUX. Compté
     à la main — 21 caractères, six pixels et demi chacun — elle donnait
     138 px, et 10,5 rem paraissaient généreux. Rendue, la plus longue
     étiquette de l'écran, « Réponse qui vaut accord : », fait 188 px : les
     étiquettes sont en demi-gras, et le compte par caractère ne vaut rien
     sur une police à chasse variable. QUATRE étiquettes passaient à deux
     lignes, ce qui décale leur case — et elles seules — hors de la colonne
     de soulignements qu'on est justement venu chercher.

     12,5 rem font 200 px : les 188 mesurés, et de quoi tenir une étiquette
     de 24 caractères, le plafond que garde `test_les_etiquettes_sont_COURTES`. */
  --colonne-reglage: 12.5rem;
  /* Des lignes RESSERRÉES. C'est ici que se paie « super compacte ».

     MESURÉ : une case fait 36 px de haut par la règle générale, plus 5,6 px
     de marge héritée de `label` — 41,6 px par réglage. À 28 px et sans marge,
     trente-sept réglages descendent de 470 px.

     28 et non moins : une ligne de texte en fait 21, et 1,75 rem laisse
     3,2 px de creux de chaque côté. En dessous, le soulignement touche la
     lettre et la case cesse d'avoir l'air d'une case. C'est aussi ce que
     mesure l'administration de Django sur ses propres formulaires. */
  --hauteur-champ: 1.75rem;
  --creux-champ: .2rem;
  display: grid;
  grid-template-columns: 1fr;
  /* Aucun écart : la ligne EST la hauteur du champ, et c'est tout le gain.
     Le creux du champ sépare déjà deux soulignements de 6 px. */
  gap: 0;
  align-items: stretch;
}

/* Un réglage : son nom à gauche, sa case à droite, sur UNE ligne. */
.rangee-reglages > .champ {
  display: grid;
  grid-template-columns: var(--colonne-reglage) minmax(0, 21rem);
  align-items: center;
  gap: 0 .75rem;
  /* Les plafonds de la règle générale ne s'appliquent plus : c'est la grille
     de la ligne qui décide des deux largeurs, pas le champ. */
  max-width: none;
  min-width: 0;
  /* `label { margin-bottom: .35rem }` vaut pour tout le portail, et un champ
     EST une étiquette. Cinq virgules six pixels sous chacun des trente-sept
     réglages font 207 px de page, invisibles un par un. Ici la ligne suivante
     est le séparateur : le soulignement de l'une touche presque le nom de
     l'autre, et c'est ce qu'on veut d'une liste de réglages. */
  margin: 0;
  padding-block: 1px;
}

/* L'étiquette est CADRÉE À DROITE, contre sa case.

   Cadrée à gauche, elle laisse un vide variable entre le nom et la case —
   large sous « Port », nul sous « Déplacer les lus vers » —, et l'œil doit
   traverser ce vide pour relier les deux. À droite, la distance est la même
   pour tous : celle de l'écart. */
.rangee-reglages > .champ > .etiquette-champ {
  text-align: right;
  /* .8 plutôt que .75 : l'étiquette n'est plus une légende au-dessus d'un
     champ, c'est la moitié gauche de la ligne. On la lit autant que la
     valeur. */
  font-size: .8rem;
  line-height: 1.2;
}

/* Le refus se dit sous la case, pas sous l'étiquette. */
.rangee-reglages > .champ > .erreur-champ { grid-column: 2; }

/* Le port rend la largeur qu'il ne sert pas.

   Le soulignement d'un champ ANNONCE ce qu'on attend dedans. Long comme une
   adresse de courriel sous « Port », il annonce faux : l'œil cherche ce qui
   manque après « 587 ». Cinq chiffres et demi, c'est tout ce qu'un port peut
   valoir — 65535. Ici, contrairement à la grille de colonnes égales, le
   raccourcir ne désaligne rien : chaque ligne porte sa propre case. */
.rangee-reglages .champ input[type="number"] { max-width: 5.5rem; }

/* Une case à cocher se range dans la COLONNE DES CASES, et non dans celle
   des étiquettes : son libellé est à sa droite, donc elle n'a rien à mettre
   à gauche. Posée en colonne 1, elle flotterait seule au bout d'une ligne
   vide, à l'écart des soulignements qu'elle accompagne. */
.rangee-reglages > .champ-case {
  display: flex;
  padding-left: calc(var(--colonne-reglage) + .75rem);
  /* Une case fait 1,1 rem, une ligne de réglage 28 px : le peu d'air qui
     manque la met à la même hauteur que ses voisines, sans la marge de
     `label` que les autres lignes perdent aussi. */
  margin: 0;
  padding-block: .2rem;
}

/* LA PHRASE DU CONSENTEMENT GARDE SON ÉTIQUETTE AU-DESSUS, et toute la
   largeur sous elle.

   C'est le texte que l'entreprise archive comme preuve de ce à quoi un client
   a consenti, et son étiquette porte la règle — « à recopier mot pour mot »,
   48 caractères. Dans la colonne de 10,5 rem elle passerait à trois lignes,
   en face d'une zone de texte de quatre : on ne vérifie pas mot pour mot un
   texte dont on lit l'étiquette de travers.

   Et le creux resserré ne la concerne pas : une zone de texte se lit, elle ne
   s'aligne pas. */
.rangee-reglages > .champ:has(textarea) {
  display: flex;
  flex-direction: column;
  /* ⚠️ `align-items` VIENT DE LA RÈGLE DU DESSUS, ET IL Y CENTRE VERTICALEMENT.
     Passer en colonne lui fait changer d'axe sans changer de valeur : le même
     `center` qui alignait une étiquette sur sa case la centrait ici AU-DESSUS
     de la zone de texte, à cheval entre le bord gauche et le milieu. Ça s'est
     vu sur une capture, pas dans le code : l'étiquette a l'air placée, elle
     n'est simplement placée par rien. */
  align-items: stretch;
  gap: .25rem;
  grid-column: 1 / -1;
  max-width: none;
  --creux-champ: .5rem;
  margin-block: .4rem;
}
.rangee-reglages > .champ:has(textarea) > .etiquette-champ { text-align: left; }

/* UNE ADRESSE WEB N'EST PAS UNE ADRESSE DE COURRIEL : elle porte un chemin,
   deux paramètres et leurs accolades. 21 rem en montrent le tiers, et une
   accolade oubliée se relit d'autant plus mal. Cette rangée-là prend ce qui
   reste de la carte. */
.rangee-large > .champ {
  grid-template-columns: var(--colonne-reglage) minmax(0, 1fr);
}

/* Le rythme des bandeaux, resserré POUR CET ÉCRAN.

   Cinq bandes grises — deux sens dans deux cartes, plus l'essai d'envoi —
   coûtaient 354 px à elles seules, sur un écran qu'on nous demande de
   compacter. Elles gardent leur aplat et leur place dans la hiérarchie
   (bandeau rouge = section, gris = sous-section) : c'est leur ESPACEMENT qui
   cède, pas leur forme. */
.reglages-courriel .corps-section h3 { margin: 1rem 0 .5rem; }
.reglages-courriel .corps-section > :first-child { margin-top: 0; }

/* LES CONSTATS, RESSERRÉS MAIS PAS RETIRÉS.

   « Les soumissions partent actuellement de… », « une réponse du client
   arrive à… » : ce sont des faits calculés sur les vraies valeurs, pas des
   descriptions — une description peut cesser d'être vraie sans que rien ne
   bouge, un constat non. Ils restent donc tous, et c'est leur ENCADREMENT
   qui cède : MESURÉ, chacun coûtait 63 px pour une ligne de texte, dont 35
   de marges et de creux. La carte de BD-SOUM en porte deux à la suite. */
.reglages-courriel .expediteur-effectif {
  margin: .3rem 0 .6rem;
  padding: .35rem .7rem;
}

/* Le bouton d'enregistrement : cinq sur l'écran, un par bloc. Il garde sa
   taille de doigt — c'est le geste de la page — mais pas l'air qu'on met
   autour d'un bouton qu'on ne voit qu'une fois. */
.reglages-courriel .actions-dialogue { margin-top: .6rem; margin-bottom: 0; }
.reglages-courriel .bouton-enregistrer { padding: .45rem 1.1rem; }

/* La prose qui reste : une ligne, et l'air d'une ligne. */
.reglages-courriel .corps-section > .discret,
.reglages-courriel .corps-section .discret { margin: .35rem 0 .6rem; }

.champ { flex: initial; min-width: 0; }

/* PRISES DE CONTACT (BD-Prospect) : « le bouton en bas à droite de la boîte
   et la boîte plus large vers la droite » — le client. Le moyen garde sa
   colonne ; « Ce qui s'est dit » prend le reste, sans le plafond des
   zones de texte ; INSCRIRE passe dessous, à droite. */
.prise-de-contact .rangee-champs {
  grid-template-columns: minmax(min(12rem, 100%), 16rem) 1fr;
  align-items: start;
}
.prise-de-contact .champ:has(textarea) { max-width: none; }
.actions-prise-de-contact {
  display: flex;
  justify-content: flex-end;
  margin-top: .75rem;
}
@media (max-width: 700px) {
  .prise-de-contact .rangee-champs { grid-template-columns: 1fr; }
}

/* L'envoi du croquis a ses propres proportions : le nom de fichier est long,
   le bouton n'a besoin que de sa largeur. */
.rangee-envoi {
  grid-template-columns: minmax(0, 2fr) minmax(0, 2fr) auto;
}

.dialogue-en-page {
  max-width: 1400px;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  box-shadow: var(--ombre);
}

@media (max-width: 700px) {
  .rangee-champs,
  .rangee-envoi,
  .rangee-import,
  .rangee-calcul,
  .rangee-accessoire,
  .rangee-statut { grid-template-columns: 1fr; }

  /* L'étiquette REPASSE AU-DESSUS de sa case.

     10,5 rem d'étiquette sur un téléphone de 360 px, c'est 168 px pris sur
     192 : l'adresse se saisirait dans ce qui reste. La colonne cède donc, et
     avec elle le cadrage à droite — une étiquette cadrée à droite au-dessus
     d'une case pleine largeur n'a plus rien contre quoi s'appuyer. */
  .rangee-reglages > .champ,
  .rangee-large > .champ {
    display: flex;
    flex-direction: column;
    gap: .15rem;
  }
  .rangee-reglages > .champ > .etiquette-champ { text-align: left; }
  .rangee-reglages > .champ-case { padding-left: 0; }
}

/* --- La section Installation --------------------------------------------- */

.bouton-ajout {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  margin-left: .5rem;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--accent-texte);
  text-decoration: none;
  background: var(--accent);
  border-radius: 50%;
}

.actions-en-ligne {
  justify-content: flex-start;
  margin: .75rem 0 0;
  padding: 0;
  border: none;
  background: none;
}

.total-libelle { text-align: right; font-weight: 600; }
.total-valeur { font-weight: 600; }

.bilan-installation { margin: .75rem 0 1.5rem; }
.depassement { color: var(--erreur-texte); font-weight: 600; }

/* --- Les fichiers d'une section ------------------------------------------- */

.fichiers { margin: .5rem 0; padding: 0; list-style: none; }

.fichier {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .75rem;
  padding: .35rem 0;
  border-bottom: 1px solid var(--bordure);
}

.fichier a { color: var(--accent-lien); }
.en-ligne { display: inline; }

/* `.lien-discret` — le petit mot souligné qui servait d'action — a disparu
   avec la conversion en boutons d'icône. Voir `.bouton-icone`. */

.depot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .75rem;
  margin: .75rem 0 1.5rem;
}

.depot input[type="file"] { font: inherit; font-size: .85rem; max-width: 100%; }

/* ═══════════════════════════════════════════════════════════════════════════
   La fiche de commande : une carte par section

   Des titres nus sur fond plat donnaient une page qui se lit comme un
   document, alors qu'elle se parcourt comme un tableau de bord : rien ne
   disait où une section finissait et où la suivante commençait. L'écran
   actuel pose chaque section dans un panneau, et il a raison.
   ═════════════════════════════════════════════════════════════════════════ */

.entete-dossier {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .25rem 1rem;
  align-items: center;
  margin-bottom: 1.25rem;
  padding: 1rem 1.25rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-left: 4px solid var(--accent);
  border-radius: var(--rayon);
  box-shadow: var(--ombre);
}

.entete-identite {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .75rem;
}

.entete-identite h1 { margin: 0; font-size: 1.6rem; }
.entete-details { grid-column: 1; margin: 0; }
.entete-action { grid-column: 2; grid-row: 1 / span 2; }

/* LES BOUTONS DU PROJET DE BD-VERRE, EN LIGNE. « Peux-tu mettre les boutons
   en ligne » — le client : « Nouveau projet » est un formulaire, un bloc qui
   cassait la rangée en trois lignes. Ils passent à la ligne seulement
   quand l'écran manque de place. */
.actions-projet {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: .5rem;
}
.actions-projet form { margin: 0; display: inline-flex; }

/* « Pour ENREGISTRER utilise qu'une icône de disquette sans texte. » — le
   client. Carrée, comme un bouton qui ne porte qu'un dessin. */
.bouton-disquette { padding-inline: .6rem; gap: 0; }
.bouton-disquette .icone { width: 1.15rem; height: 1.15rem; }

/* Les liens qui portent une icône à côté de leur mot : « ← Toutes les
   soumissions », « + AJOUTER ». Sans cela le dessin s'assied sur la ligne de
   base et se colle au texte. */
.entete-action a,
.titre-section a:not(.bouton-icone),
.barre-actions a {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
}

.etiquette-installation {
  color: var(--accent-texte);
  background: var(--accent);
  border-color: var(--accent);
}

.etiquette-close { opacity: .65; }

.carte-section {
  margin-bottom: 1.25rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  box-shadow: var(--ombre);
  /* Le bandeau de titre épouse les coins arrondis de la carte. */
  overflow: hidden;
}

.titre-section {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 0;
  padding: .8rem 1.25rem;
  font-size: 1.05rem;
  font-weight: 600;
  background: var(--fond);
  border-bottom: 1px solid var(--bordure);
}

.corps-section { padding: 1.25rem; }
.corps-section > :first-child { margin-top: 0; }
.corps-section > :last-child { margin-bottom: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   UNE RÈGLE QUI SE RÉPÈTE, ET C'EST TOUT LE PROPOS.

   « Il faut des règles répétitives : section (rouge, blanc), sous-section
   (gris, blanc)… » — le client, après avoir vu la première version.

   Elle en portait QUATRE teintes — ardoise, ambre, sauge, indigo — une par
   sous-section. L'intention était de les différencier ; le résultat était
   quatre aplats pleine largeur dans une seule carte, à côté du rouge de la
   section : « le layout est vraiment difficile à suivre pour les yeux ». Il
   avait raison, et je m'en étais inquiété à voix haute sans en tirer la
   conséquence.

   Ce qu'une couleur par sous-section demande à l'œil, c'est de RETENIR une
   correspondance — ambre = croquis, sauge = envoi — qui ne sert à rien,
   puisque le titre le dit déjà en toutes lettres. Ce qu'une règle qui se
   répète demande, c'est de l'apprendre UNE fois : bandeau rouge, c'est une
   section ; bandeau gris, c'est une sous-section. Après quoi on ne lit plus
   la couleur, on lit le niveau.

   Le gris : #6e7781, blanc dessus à 4,55:1 — juste au-dessus du seuil de
   4,5:1 du texte courant, et plus lisible que le blanc sur le rouge de la
   marque (4,00:1), qui n'y échappe qu'en étant du GROS texte. Il se
   détache des deux fonds de carte : 4,55:1 sur le blanc, 3,8:1 sur le
   sombre.

   ⚠️ C'EST LE GRIS LE PLUS PÂLE QUI GARDE LE BLANC LISIBLE.

       « Utilise un gris plus pâle et pleine largeur. » — le client, devant
         #5b6470.

   Plus pâle encore, le blanc passe sous 4,5:1 : il faudrait écrire en
   foncé, et la règle « sous-section (gris, blanc) » ne serait plus la
   sienne. portail/tests/test_theme.py tient le seuil.

   Le MÊME gris dans les deux thèmes, comme le rouge : un aplat est un aplat.
   C'est la règle déjà posée plus haut pour la couleur de marque.
   ═══════════════════════════════════════════════════════════════════════════ */

.corps-section h3 {
  display: flex;
  align-items: center;
  gap: .5rem;
  /* Le même rythme partout : de l'air au-dessus du bandeau, moins en dessous,
     pour que le titre appartienne visiblement à ce qui le suit. */
  margin: 1.6rem 0 .75rem;
  padding: .45rem .9rem;
  border-radius: var(--rayon);
  background: var(--bande-sous-section);
  color: var(--bande-sous-section-texte);
  /* 1 rem contre 1,2 rem pour la bande de section : c'est la taille, et non
     la couleur, qui dit lequel des deux niveaux on regarde. */
  font-size: 1rem;
  font-weight: 700;
}

/* ═══ LE TROISIÈME NIVEAU : UN SOUS-BLOC DANS UNE SOUS-SECTION ════════════

   Depuis que l'écran du courriel est rangé par application, il y a trois
   profondeurs : la carte (BD-COMMANDE), le sens (Courriel SORTANT), et
   dedans un bloc à part — « Approbation du client », qui n'est ni une
   adresse d'envoi ni un compte, mais le gabarit du lien envoyé au client.

   ⚠️ NI BANDE NI COULEUR, ET C'EST LA RÈGLE QUI CONTINUE. Bandeau rouge,
   c'est une section ; bandeau gris, c'est une sous-section ; après quoi on
   ne lit plus la couleur, on lit le niveau. Une TROISIÈME bande grise dans
   une bande grise ne se distinguerait que par son indentation, et il n'y en
   a pas. Ce niveau se reconnaît donc à ce qu'il N'A PAS : pas d'aplat, plus
   petit, en capitales — la forme d'une étiquette, pas d'un titre. */
.corps-section .sous-titre-bloc {
  margin: 1.5rem 0 .6rem;
  font-size: .85rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--texte-doux);
}

/* Dans un en-tête de tableau, le bandeau prend la largeur qui reste et les
   boutons la suivent : sans cela il se réduirait à une pastille collée à
   gauche, et deux sous-sections voisines n'auraient pas la même allure selon
   qu'elles portent un bouton ou non. */
.entete-tableau h3 { margin: 1.6rem 0 .75rem; flex: 1 1 auto; }

/* Le compte et les mentions discrètes, sur l'aplat gris : un jeton clair,
   comme sur le bandeau rouge. Ils héritaient d'un gris posé pour un fond de
   carte, et devenaient illisibles. */
.corps-section h3 .compte {
  color: var(--bande-sous-section);
  background: var(--bande-sous-section-texte);
  border-color: transparent;
}
.corps-section h3 .discret { color: inherit; opacity: .85; }

/* Le compte de fichiers : une pastille, pas une parenthèse perdue. */
.compte {
  padding: .1rem .5rem;
  font-size: .75rem;
  font-weight: 600;
  color: var(--texte-doux);
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: 1rem;
}

/* --- Bloc à dessin ------------------------------------------------------
   Le canevas garde ses proportions et ne dépasse jamais la largeur de la
   carte : sur un portable, un canevas de 900 px de large poussait la page
   entière à défiler latéralement. */

.bloc-dessin {
  display: block;
  width: 100%;
  max-width: 900px;
  aspect-ratio: 3 / 2;
  background: #fff;
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  /* Sans cela, un glissement du doigt fait défiler la page au lieu de
     dessiner — le bloc est inutilisable sur une tablette de chantier. */
  touch-action: none;
  cursor: crosshair;
}

.rangee-bloc { align-items: end; margin-top: .75rem; }

.champ-couleur input[type="color"] {
  width: 3rem;
  height: 2.2rem;
  padding: 2px;
}

/* --- Photos et documents d'une soumission -------------------------------- */

.rangee-depot { align-items: end; margin-bottom: .75rem; }

/* --- La zone de glisser-déposer -------------------------------------------
   Demandée par le client : l'ancien BD-SOUM acceptait qu'on lâche ses photos
   sur la page. La classe est posée PAR LE SCRIPT — sans lui, le formulaire
   reste une rangée de champs ordinaire, parce qu'un cadre qui annonce
   « glissez vos fichiers ici » et ne reçoit rien est pire qu'un cadre absent.

   Le trait est en pointillé : c'est ce qui dit « on peut lâcher ici » sans
   une phrase de plus. */

.zone-depot {
  padding: 1rem;
  border: 2px dashed var(--bordure);
  border-radius: var(--rayon);
  transition: border-color .12s, background-color .12s;
}

/* Pendant le survol d'un fichier, la zone prend l'accent du portail : le
   rouge dit où le fichier va atterrir, et il est visible du coin de l'œil
   alors qu'on regarde le fichier qu'on traîne. */
.zone-depot.survol {
  border-color: var(--accent);
  border-style: solid;
  background: var(--accent-sourd);
}

/* ⚠️ `pointer-events` ET NON `disabled` : un champ désactivé n'est pas
   envoyé, et le geste qui annonce le dépôt le viderait. */
.zone-depot.en-cours {
  pointer-events: none;
  opacity: .6;
}

.invite-depot {
  grid-column: 1 / -1;
  margin: 0 0 .5rem;
  font-weight: 600;
}

.liste-fichiers {
  margin: 0 0 1.25rem;
  padding: 0;
  list-style: none;
}

.liste-fichiers li {
  display: flex;
  /* ⚠️ `center` ET NON `baseline` : une vignette n'a pas de ligne de base, et
     une ligne alignée sur elle posait le nom et les deux boutons tout en bas
     du cadre de l'image. */
  align-items: center;
  gap: .6rem;
  padding: .25rem 0;
  border-bottom: 1px solid var(--bordure);
}

/* Le nom prend la place qui reste : les fichiers horodatés se ressemblent
   par la fin, et c'est le début qui les distingue.

   ⚠️ SUR UNE LIGNE, COUPÉ PAR TROIS POINTS — ET NON `word-break: break-all`.

   Mesuré dans Chromium à 390 px : la vignette, la taille et deux boutons
   laissaient une centaine de pixels au nom, qui descendait alors en colonne
   de TROIS caractères — « bal / con / -av / ant / _20 … » — et donnait des
   lignes de 273 px de haut, 681 px à 320 px de large. Le nom entier reste
   dans l'infobulle du lien, et c'est la vignette qui dit maintenant quelle
   photo c'est. */
.liste-fichiers li > a { flex: 1; min-width: 0; }

.liste-fichiers li > a > span {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- La grille des photos -------------------------------------------------
   « Voici l'ancien bloc, j'aimerais répliquer ça. » — de grandes vignettes
   côte à côte, et sous chacune le bouton qui tourne et celui qui retire.

   `auto-fill` avec un minimum : une colonne sur un téléphone, deux sur une
   tablette, quatre sur l'écran de l'atelier, sans une seule requête média.
   Le minimum est en `rem` et vaut moins qu'un iPhone SE moins ses marges —
   une case plus large que le téléphone sortirait de l'écran. */

.lien-fichier {
  display: flex;
  align-items: center;
  gap: .6rem;
  min-width: 0;
}

.grille-photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(14rem, 100%), 1fr));
  gap: 1rem;
  margin: 0 0 1.25rem;
  padding: 0;
  list-style: none;
}

.carte-photo {
  display: flex;
  flex-direction: column;
  gap: .5rem;
}

/* ⚠️ TOUTES LES CASES À LA MÊME HAUTEUR, et c'est en cela que ceci s'écarte
   de l'ancien bloc. Là-bas, chaque photo gardait sa hauteur naturelle : sur
   une rangée de trois, les boutons se retrouvaient à trois hauteurs
   différentes, et on vise la corbeille de la photo d'à côté.

   `contain` et non `cover` : on cherche à reconnaître un balcon, et un carré
   recadré dans une photo de garde-corps peut ne montrer qu'un mur. */
.carte-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: contain;
  background: var(--fond);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
}

.actions-photo {
  display: flex;
  gap: .5rem;
}

/* Les deux boutons se partagent la largeur de la case : une cible de la
   taille du doigt, sur un téléphone tenu d'une main au milieu d'un chantier.
   Ils gardent leurs couleurs — le rouge du retrait reste distinct. */
.actions-photo form { flex: 1; }
.actions-photo .bouton-icone { width: 100%; }

/* --- La barre d'actions d'une soumission ---------------------------------
   Au BAS de la page : on remplit une soumission de haut en bas, et c'est
   arrivé en bas qu'on décide de l'envoyer. Remonter chercher le bouton, sur
   une soumission de trois emplacements, veut dire remonter à chaque fois.

   Deux rangées, et la séparation n'est pas décorative : en haut ce qui SERT
   le dossier, en bas ce qui le QUITTE. Mêlées, on clique sur « refuser » en
   visant « envoyer ».

   ⚠️ SURTOUT PAS « .barre-actions » : ce nom désigne déjà la navigation de
   l'en-tête — thème, mot de passe, déconnexion — sur TOUTES les pages du
   portail. Le lui reprendre lui imposait un trait de séparation et deux
   centimètres de marge en haut de chaque écran. Constaté en pilotant un
   navigateur, pas en relisant le code : les tests ne regardent pas le
   bandeau, et l'écran des soumissions avait l'air juste. C'est la deuxième
   fois qu'un nom trop général fait ça ici — voir « .marque ». */

.actions-soumission {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 2px solid var(--bordure);
}

.rangee-actions {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  align-items: center;
}

.rangee-actions-secondaire {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--bordure);
}

/* Refuser n'est pas une action rouge parmi d'autres : le rouge est la
   couleur de la maison, il habille déjà « envoyer ». Celle-ci se distingue
   par son contour, pas par un aplat — un bouton plein de plus dans la
   rangée se cliquerait par habitude. */
.bouton-refus {
  border-color: var(--erreur-bordure);
  color: var(--erreur-texte);
}

.bouton-refus:hover { background: var(--erreur-fond); }

@media (max-width: 640px) {
  /* Au doigt, sur une tablette de chantier : les boutons prennent toute la
     largeur plutôt que de se serrer à trois par ligne. */
  .rangee-actions > * { flex: 1 1 100%; }
  .rangee-actions .bouton-ajouter,
  .rangee-actions .bouton-principal,
  .rangee-actions button { width: 100%; text-align: center; }
}

/* Le courriel d'origine d'une demande, replié par défaut. */
.ligne-repliable {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  margin: 0;
}

.source-courriel {
  margin: .5rem 0 0;
  padding: .75rem;
  max-height: 22rem;
  overflow: auto;
  font-size: .8rem;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--texte-doux);
  background: var(--fond);
  border: 1px solid var(--bordure);
  border-radius: 6px;
}

/* --- Conditions de vente -------------------------------------------------
   Treize lignes à cocher, lues avant d'envoyer une soumission. Une liste
   serrée se relit d'un coup d'œil ; espacée, elle oblige à faire défiler et
   on coche sans lire. */

.conditions {
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}

.condition {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  padding: .2rem 0;
  cursor: pointer;
}

.condition input { margin-top: .2rem; }

/* --- Le bloc-notes de l'installateur --------------------------------------
   « PAS AU CLIENT », a écrit le client en majuscules. Les notes qui
   l'entourent dans cette section, elles, s'impriment sur sa soumission.

   La seule chose qui distingue ce champ de ses voisins est son étiquette :
   elle est donc marquée — un filet rouge à gauche et le titre en gras — pour
   que personne n'écrive « prix d'ami convenu avec Raphaël » dans le mauvais
   bloc. Le rouge est celui du portail, pas une alerte : rien ne va mal, il
   s'agit de reconnaître le bloc du premier coup d'œil. */

.bloc-installateur {
  margin: 1.5rem 0 .5rem;
  padding-left: .75rem;
  border-left: 3px solid var(--accent);
}

.bloc-installateur textarea { width: 100%; }

.etiquette-installateur { font-weight: 600; }

.bloc-installateur textarea { resize: vertical; }

/* La zone cliquable couvre le texte entier, pas seulement la case : viser un
   carré de 13 px au doigt, sur un chantier, n'est pas raisonnable. */
.condition span { flex: 1; }

.condition input:disabled { cursor: default; }
.condition input:disabled + span { color: var(--texte-doux); }

/* --- La boîte des demandes ----------------------------------------------
   Le premier geste de la journée : relever ce que le formulaire du site a
   déposé. Le bandeau se tient en haut de la liste, et il change de couleur
   SEULEMENT quand il y a du travail — un bandeau rouge en permanence ne se
   voit plus au bout de trois jours. */

.demandes {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: .75rem;
  margin-bottom: 1rem;
  padding: .7rem 1rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
}

.demandes-en-retard {
  background: var(--accent-sourd);
  border-color: var(--accent);
}

.demandes-etat {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-weight: 600;
  letter-spacing: .01em;
}


.action-requise-fiche {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 1rem 0 0;
  padding: .6rem .85rem;
  background: var(--erreur-fond);
  border: 1px solid var(--erreur-bordure);
  border-radius: 6px;
  color: var(--erreur-texte);
}

.indicateurs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: .6rem 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.bouton-danger {
  padding: .5rem 1rem;
  font: inherit;
  font-weight: 600;
  letter-spacing: .03em;
  color: var(--accent-texte);
  background: var(--accent);
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

.bouton-secondaire {
  padding: .5rem 1rem;
  font: inherit;
  color: var(--texte);
  background: var(--fond);
  border: 1px solid var(--bordure);
  border-radius: 6px;
  cursor: pointer;
}

.bouton-danger:not([disabled]):hover { filter: brightness(.92); }
.bouton-secondaire:not([disabled]):hover { background: var(--bordure); }

@media (max-width: 700px) {
  .entete-dossier { grid-template-columns: 1fr; }
  .entete-action { grid-column: 1; grid-row: auto; margin-top: .5rem; }
  .corps-section { padding: 1rem; }
  .titre-section { padding: .7rem 1rem; }
}

/* --- Corrections de rendu sur la fiche ------------------------------------ */

/* Un état vide n'est pas une donnée : encadré comme une ligne, « Aucune
   note. » se lit comme un champ de saisie vide qu'on pourrait remplir. */
.notes li.discret,
.fichiers li.discret {
  padding: .35rem 0;
  background: none;
  border: none;
}

/* Six boutons rouges sur une même page se disputent l'attention et aucun ne
   ressort. Le rouge de la marque reste aux actions qui engagent — ajouter une
   note, terminer un dossier ; déposer un fichier devient une action ordinaire. */
.depot .bouton-principal {
  color: var(--accent-lien);
  background: none;
  border: 1px solid currentColor;
}

.depot .bouton-principal:not([disabled]):hover {
  color: var(--accent-texte);
  background: var(--accent);
  border-color: var(--accent);
}

/* La liste des fichiers d'une section : des lignes, pas des cartes empilées. */
.fichiers li.fichier {
  background: none;
  border: none;
  border-bottom: 1px solid var(--bordure);
  border-radius: 0;
  margin-bottom: 0;
  padding: .5rem .2rem;
}

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

/* --- L'entête d'un tableau, avec son bouton d'ajout ----------------------- */

.entete-tableau {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin: 1.5rem 0 .5rem;
}

.entete-tableau h3 { margin: 0; }

/* ═══ LA SOUS-SECTION COURT D'UN BORD À L'AUTRE ═══════════════════════════

       « Utilise un gris plus pâle et pleine largeur. » — le client.

   Le bandeau rouge du titre va déjà d'un bord de la carte à l'autre ; le
   gris s'arrêtait à l'inset de `.corps-section`, et se lisait comme une
   pastille posée DANS la section plutôt que comme le niveau sous elle. Même
   geste que `.tableau-large` : rendre l'inset, et des coins droits — un
   bandeau qui touche les bords n'a pas de coins à arrondir.

   ⚠️ L'EN-TÊTE D'UN TABLEAU DEVIENT LE BANDEAU, BOUTON COMPRIS. Sans quoi
   « Mesures » restait un aplat raccourci, son bouton « Ajouter » à côté,
   et deux sous-sections voisines n'avaient pas la même allure selon
   qu'elles portent un bouton ou non.

   ⚠️ SEULEMENT LES ENFANTS DIRECTS DE LA SECTION. Un titre rangé plus
   profond — dans un formulaire, une colonne — n'a pas l'inset à rendre :
   sa marge négative le ferait déborder de son propre cadre. */
.corps-section > h3,
.corps-section > .entete-tableau {
  margin-left: -1.25rem;
  margin-right: -1.25rem;
  border-radius: 0;
}

.corps-section > h3 { padding-left: 1.25rem; padding-right: 1.25rem; }

.corps-section > .entete-tableau {
  padding: .45rem 1.25rem;
  background: var(--bande-sous-section);
  color: var(--bande-sous-section-texte);
}

.corps-section > .entete-tableau > h3 {
  padding: 0;
  background: none;
}

@media (max-width: 700px) {
  /* L'inset de la section y est d'1 rem, pas d'1,25. */
  .corps-section > h3,
  .corps-section > .entete-tableau {
    margin-left: -1rem;
    margin-right: -1rem;
  }
  .corps-section > h3 { padding-left: 1rem; padding-right: 1rem; }
  .corps-section > .entete-tableau { padding-left: 1rem; padding-right: 1rem; }
}

/* Vert, comme sur l'écran actuel. Ajouter n'est ni destructeur ni la marque :
   le rouge de Balcon Dépôt reste aux actions qui engagent le dossier. */
.bouton-ajouter {
  /* `inline-flex` depuis que le « + » est un DESSIN et non un caractère : il
     faut centrer l'icône sur la ligne du texte, et lui laisser son écart.
     Un « + » écrit prenait la graisse de la police et changeait de forme
     d'un système à l'autre. */
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .4rem .9rem;
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .04em;
  color: #fff;
  text-decoration: none;
  background: var(--vert);
  border-radius: 6px;
  white-space: nowrap;
}

.bouton-ajouter:not([disabled]):hover { background: var(--vert-fonce); }

/* La note d'une étape est souvent un courriel entier. Sans cette règle, la
   cellule impose sa largeur au tableau et pousse les autres colonnes dehors. */
/* ⚠️ ELLE PERDAIT CONTRE `.tableau td`, ET ELLE NE LE DISAIT PAS.

   `.tableau th, .tableau td` pose `white-space: nowrap` — c'est ce qui
   évite l'ascenseur horizontal partout ailleurs — et sa spécificité (une
   classe + un élément) bat celle de `.colonne-note` (une classe). Le
   `max-width` s'appliquait, lui : la cellule était donc bornée à 40rem
   avec un texte qui continuait dedans, coupé net au milieu d'un mot.

   MESURÉ dans un navigateur : `whiteSpace: "nowrap"`, `maxWidth: "640px"`,
   largeur de cellule 640 px. La règle faisait la moitié de ce qu'elle
   annonçait, dans le dossier de commande depuis le début.

   L'élément est donc nommé, pour qu'elle gagne. */
.tableau td.colonne-note,
.tableau th.colonne-note,
.colonne-note {
  max-width: 40rem;
  white-space: normal;
}

/* --- Approbation interne et envoi au client ------------------------------- */

.bloc-approbation {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .75rem;
  margin: 1.25rem 0;
  padding: .75rem 1rem;
  background: var(--fond);
  border: 1px solid var(--bordure);
  border-radius: 6px;
}

.bloc-approbation form { display: flex; align-items: center; gap: .75rem; }

.envoi-client .rangee-champs { align-items: end; }
.bouton-envoyer { border: none; cursor: pointer; font: inherit; font-weight: 600; }
.envoi-client p { margin: .75rem 0 0; }

/* --- Revenir à la section où l'on travaillait ----------------------------- */

/* Sans cette marge, la section visée se colle au bord haut de la fenêtre et
   son bandeau de titre touche la barre du portail : on ne voit plus où l'on
   est. Une carte de hauteur, et la section se lit comme si on y était arrivé
   en défilant. */
.carte-section { scroll-margin-top: 1.25rem; }

/* Les messages restent visibles même après un saut vers une section : sans
   cela, la confirmation « 3 fichiers déposés » — ou pire, le motif d'un refus
   — défile hors de l'écran au moment même où on en a besoin. */
.messages {
  position: sticky;
  top: 0;
  z-index: 5;
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}

.messages .message {
  padding: .6rem .9rem;
  border: 1px solid var(--bordure);
  border-radius: 6px;
  margin-bottom: .4rem;
  background: var(--fond-carte);
  box-shadow: var(--ombre);
}

.messages .message.error {
  color: var(--erreur-texte);
  background: var(--erreur-fond);
  border-color: var(--erreur-bordure);
}

.messages .message.success { border-left: 4px solid var(--vert); }

/* ⚠️ « EN JAUNE » ET « EN BLEU » N'EXISTAIENT QU'EN COMMENTAIRE. Sans ces
   deux règles, `messages.warning` et `messages.info` sortaient en gris
   neutre — MOINS visibles qu'un succès vert. Une bordure, comme le succès :
   elle se voit dans les deux thèmes sans toucher au texte. (BD-FAB, BD-COMMANDE) */
.messages .message.warning { border-left: 4px solid #f9a825; }
.messages .message.info { border-left: 4px solid var(--bleu); }

/* L'heure ne doit pas se couper en deux lignes sous la date. */
.colonne-date { white-space: nowrap; }


/* --- Un titre de section qui porte une action ----------------------------- */

.titre-section {
  justify-content: space-between;
}

.titre-section h2 {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
}

/* --- La ligne de détails d'une commande fournisseur ----------------------- */

/* ⚠️ C'EST ELLE QUI TENAIT LE TABLEAU LARGE, pas les colonnes.

   La rangée de détails court sur toute la largeur — un `colspan` de dix — et
   héritait du `nowrap` des cellules ordinaires. « ↳ Détails : Réception
   Entrepôt : … Date de réception : … Note : … » exigeait donc 967 px sur UNE
   ligne, et le tableau entier s'alignait sur elle : les dix colonnes avaient
   beau se serrer, l'ascenseur horizontal restait. Une note de réception un
   peu bavarde l'aurait rallongé encore.

   Trouvé en SONDANT chaque rangée dans un navigateur. À ne regarder que les
   en-têtes — ce que le tableau semble dire de lui-même — on ne voit rien.

   Ce n'est pas un alignement de colonnes : c'est une suite d'étiquettes et de
   valeurs, qui se replient comme une phrase. */
.ligne-detail td {
  background: var(--accent-sourd);
  font-size: .85rem;
  white-space: normal;
}

.detail-champ {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin-right: 1.5rem;
  white-space: nowrap;
}

/* UNE COLONNE DE NOMS SE REPLIE ; UNE COLONNE DE NOMBRES, JAMAIS.
   Le complément de la règle posée sur les titres — et il a fallu de VRAIES
   données pour le voir. Avec une ligne d'essai minimale — matériau « RAMPE »,
   fournisseur « BD » —, le tableau des commandes fournisseur tenait tout
   juste dans sa boîte. Avec ce que l'atelier saisit vraiment — « RAMPE
   ALUMINIUM », « BALCON DEPOT », « F-2026-004512 » —, il en sortait de 43 px
   même sur un grand écran : ce n'étaient plus les titres qui tenaient la
   largeur, c'étaient les mots.

   Un nom de matériau coupé en deux lignes reste un nom. Une date, un numéro
   de commande ou un montant coupés en deux ne se relisent pas — et c'est
   justement dans ces colonnes-là qu'on vérifie un chiffre. */
/* ⚠️ `.tableau td` ET NON `.colonne-texte` SEUL. La règle qui pose le
   `nowrap` vise « .tableau th, .tableau td » : deux classes de spécificité.
   Une classe isolée ne la bat pas, et ma première version n'a rien changé du
   tout — le tableau mesurait au pixel près la même largeur qu'avant. Vérifié
   en relevant `white-space` sur la cellule elle-même, pas en relisant la
   feuille. */
.tableau td.colonne-texte { white-space: normal; }

/* L'ID d'une commande fournisseur : un ou deux chiffres, qu'on lit en
   descendant la colonne. Alignés à droite et en chiffres de même chasse,
   ils forment une colonne droite ; à gauche, « 9 » et « 10 » ne se
   comparent plus d'un coup d'œil. */
.colonne-id {
  width: 1%;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--texte-doux);
}

.colonne-actions { white-space: nowrap; }
.colonne-actions a:not(.bouton-icone) { text-decoration: none; font-size: 1rem; }

[data-deplier] { font-size: 1rem; padding: 0 .35rem; }

.manque { color: var(--erreur-texte); font-size: .85rem; }


/* ═══════════════════════════════════════════════════════════════════════════
   Les bandeaux de section, en rouge

   Demandé : « chaque titre de section doit être un bandeau rouge et écriture
   en blanc ». L'écran actuel les porte en noir ; le rouge est celui de la
   marque.

   Le rouge est #FF0000, demandé explicitement. Le blanc dessus donne 4,0:1,
   au-dessus du seuil de 3:1 exigé pour du GROS texte — d'où la taille du
   bandeau, fixée plus bas et vérifiée par un test.
   ═════════════════════════════════════════════════════════════════════════ */

.titre-section {
  color: var(--accent-texte);
  background: var(--accent);
  border-bottom: none;
  /* 1,2 rem en gras, soit 19,2 px : au-dessus du seuil de 18,66 px à partir
     duquel le blanc sur #FF0000 (4,0:1) satisfait le contraste exigé. En
     dessous, le même texte serait non conforme — et pénible à lire sur un
     écran d'atelier en plein jour. */
  font-size: 1.2rem;
  font-weight: 700;
}

.titre-section h2 { color: inherit; font-size: inherit; font-weight: inherit; }

/* LA MENTION QUI ACCOMPAGNE UN TITRE DE SECTION — « tracé fermé · 6 coin(s) »,
   à droite du bandeau.

   `.discret` vaut `--texte-doux`, un gris calculé pour un fond blanc. Posé
   sur le rouge de la bande il tombe à 1,53:1 — mesuré, pas estimé : le texte
   est bien là, il ne se lit pas. Le blanc ne suffirait pas non plus, 4,00:1
   étant le plafond de ce rouge et le seuil du petit texte étant 4,5:1.

   D'où le jeton, même famille que `.compte` juste dessous : la mention
   quitte le rouge et se pose sur un aplat blanc, où le gris des
   sous-sections donne 6,00:1. Les deux jetons sont invariables d'un thème à
   l'autre, comme les aplats qui les portent. */
.titre-section .discret {
  color: var(--bande-sous-section);
  background: var(--accent-texte);
  padding: .1rem .55rem;
  border-radius: 999px;
  font-size: .78rem;
  font-weight: 600;
}

/* Le compte de fichiers, sur fond rouge : un jeton clair plutôt qu'un texte
   gris devenu illisible. */
.titre-section .compte {
  color: var(--accent);
  font-size: .7rem;
  background: var(--accent-texte);
  border-color: transparent;
}

/* Le bouton vert garde sa bordure blanche : vert sur rouge se distingue mal
   pour qui confond ces deux couleurs — un homme sur douze. */
.titre-section .bouton-ajouter {
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .65);
}


/* --- L'alignement des champs d'un dialogue -------------------------------- */

/* La règle globale « input[type=text] » pose une marge basse de 1,1 rem pour
   les formulaires en colonne — connexion, recherche. Dans une rangée de
   dialogue, elle ne s'appliquait qu'aux champs texte : les listes et les
   dates n'en avaient pas. Résultat, « # de commande » flottait dix-huit
   pixels au-dessus de ses voisins, et toute la rangée paraissait de travers.
   C'est ce que voulait dire « certains champs sont mal formatés ».

   On remet tous les contrôles d'un dialogue sur le même pied, littéralement. */
.champ input,
.champ select,
.champ textarea {
  margin: 0;
}

/* Et une hauteur commune : une liste déroulante fait 30 px, un champ date 35
   à cause de son icône de calendrier, un champ texte 33. Alignés par le bas,
   leurs soulignements tombaient bien mais leurs textes non.

   ⚠️ C'EST UNE HAUTEUR FIXE, ET ELLE GAGNE CONTRE LE CREUX. Resserrer
   `--creux-champ` sans toucher à celle-ci ne rétrécit rien du tout : le texte
   remonte dans une case qui garde ses 36 px, et le soulignement s'éloigne de
   la lettre. Mesuré en croyant avoir gagné 250 px — la page n'avait pas bougé
   d'un pixel. Les deux se règlent donc ensemble, par deux variables lues au
   même endroit. */
.champ input:not([type="checkbox"]),
.champ select {
  height: var(--hauteur-champ, 2.25rem);
  line-height: 1.2;
}

/* Le nom d'un croquis fait soixante caractères : sans retour à la ligne, le
   tableau des envois pousse la colonne « Courriel » hors de l'écran. */
.tableau-compact td { word-break: break-word; }

/* L'avertissement d'une fenêtre de confirmation : ce qui va se passer en plus
   de ce qu'on demande. Les jetons d'erreur, parce que c'est le même registre
   — « attention » — et qu'ils sont déjà définis dans les deux thèmes. */
.avertissement-archivage {
  margin: .9rem 0 0;
  padding: .7rem .9rem;
  border-radius: var(--rayon);
  border: 1px solid var(--erreur-bordure);
  background: var(--erreur-fond);
  color: var(--erreur-texte);
  font-size: .92rem;
  line-height: 1.45;
}

/* ═══════════════════════════════════════════════════════════════════════════
   La colonne de gauche

   Demandée par le client : un bouton « Tableau de bord » toujours visible.
   Une fiche de commande fait plusieurs écrans de haut, et revenir au tableau
   de bord obligeait à remonter en tête de page puis à passer par la tuile.

   `position: fixed` et non `sticky` : elle ne bouge pas du tout, même sur une
   fiche plus courte que l'écran. Le contenu ne lui laisse pas de place dans
   le flux — il reste centré, et la colonne vit dans la marge. Sur un écran
   trop étroit pour avoir une marge, elle disparaît plutôt que de mordre sur
   le texte : les mêmes liens restent dans la barre du haut.
   ═════════════════════════════════════════════════════════════════════════ */

.colonne-gauche {
  position: fixed;
  top: 5.5rem;
  left: .75rem;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: .25rem;
  /* 12,5 rem : la largeur à laquelle « Tableau de bord » tient sur une ligne,
     mesurée plutôt que devinée. À 11 rem il se coupait en deux, et un
     raccourci sur deux lignes se lit moins vite qu'il ne se clique. */
  width: 12.5rem;
  padding: .5rem;
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  background: var(--fond-carte);
  box-shadow: var(--ombre);
}

.raccourci {
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .5rem .6rem;
  border-radius: calc(var(--rayon) - 4px);
  color: var(--texte);
  font-size: .9rem;
  font-weight: 600;
  text-decoration: none;
}

.raccourci:hover { background: var(--accent-sourd); }

/* L'écran où l'on se trouve : aplat rouge, écriture blanche — comme les
   bandeaux de section. 19,2 px en gras y seraient trop gros pour une colonne
   de 11 rem ; on garde donc le contraste du gros texte en épaississant. */
.raccourci-actif,
.raccourci-actif:hover {
  background: var(--accent);
  color: var(--accent-texte);
  font-weight: 700;
}

.raccourci-icone {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.1rem;
  font-size: 1rem;
}

/* La colonne RÉSERVE sa place au lieu de parier sur la marge.

   Premier essai : la laisser flotter dans la marge du contenu centré, et la
   cacher sous une certaine largeur. Mesuré dans un navigateur : à 1500 px il
   restait 12 px de jeu, mais à 1280 px — un portable ordinaire — la colonne
   recouvrait la fiche de 98 px, et de 148 px à 1200. Le seuil aurait dû
   monter à ~1480 px, c'est-à-dire cacher la colonne sur la plupart des
   écrans : l'inverse de « toujours visible ».

   Le décalage est porté par le CORPS de la page et non par `.contenu` : celui-ci
   est en `max-width` + `margin: 0 auto`, si bien qu'un `padding-left` y serait
   avalé par sa propre largeur au lieu de le pousser à droite — mesuré, il
   recouvrait encore de 98 px à 1280. Décaler le corps déplace la zone dans
   laquelle `.contenu` se centre, ce qui est bien l'effet voulu. */
@media (min-width: 900px) {
  body { padding-left: 14.5rem; }
  /* La barre du haut traverse toute la page : on lui rend sa largeur. */
  .barre { margin-left: -14.5rem; padding-left: calc(14.5rem + 1.5rem); }
}

/* Sous 900 px il n'y a plus de marge du tout : la colonne s'efface, et ses
   liens restent atteignables par la barre du haut. */
@media (max-width: 900px) {
  .colonne-gauche { display: none; }
}

/* Deux sections courtes côte à côte. La grille et non flex : les deux colonnes
   restent de largeur égale même quand l'une porte un fichier et l'autre pas,
   sans quoi elles se redimensionneraient au gré du contenu et l'œil ne
   retrouverait plus la même section au même endroit d'un dossier à l'autre.

   `align-items: start` : une carte qui a moins de contenu garde sa hauteur
   propre au lieu de s'étirer pour rejoindre sa voisine, ce qui laisserait un
   grand vide blanc sous son champ de dépôt. */
.deux-colonnes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem;
  align-items: start;
}

/* Sous 900 px, à deux, le champ de dépôt d'un navigateur — « Choisir un
   fichier » suivi du nom — ne tient plus sur une ligne. */
@media (max-width: 900px) {
  .deux-colonnes { grid-template-columns: 1fr; }
}

/* Des champs empilés plutôt qu'alignés.

   Pour les formulaires dont les champs n'ont rien à faire côte à côte : une
   liste déroulante courte et une note de plusieurs lignes se lisent mieux
   l'une sous l'autre. Chaque champ prend alors toute la largeur utile — le
   plafond de 22 rem vaut pour une rangée partagée, pas pour une colonne.

   ⚠️ UNE COLONNE DE GRILLE, pas `flex-direction: column`.

   `.rangee-champs` a l'air d'une boîte flexible — elle l'était — mais une
   règle plus bas la passe en GRILLE, pour que cinq champs gardent des
   colonnes égales. Un `flex-direction` posé ici n'a donc aucun effet : le
   navigateur le calcule bien, et la grille l'ignore. Les deux champs
   restaient côte à côte alors que `getComputedStyle` rendait
   « column » — c'est en remontant la chaîne des ancêtres que le `display:
   grid` est apparu. */
.rangee-champs.en-colonne {
  grid-template-columns: 1fr;
  align-items: start;
  gap: 1.1rem;
}

.rangee-champs.en-colonne .champ,
.rangee-champs.en-colonne .champ:has(textarea) {
  max-width: none;
}

/* --- L'élévation du calculateur de rampe en verre ------------------------- */

/* Le SVG hérite la couleur du texte : ses traits sont dessinés en
   « currentColor », donc le croquis reste lisible dans les deux thèmes sans
   qu'une seule couleur soit écrite dans le dessin. */
.elevation-apercu {
  margin: 1rem 0 1.5rem;
  color: var(--texte);
}

.elevation-apercu svg {
  display: block;
  width: 100%;
  max-width: 60rem;
  height: auto;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: 6px;
}

.elevation-apercu figcaption { margin-top: .4rem; }

/* La case « Remplacer » du versement, dans la colonne d'un tableau : serrée,
   sur sa propre ligne au-dessus du bouton, et jamais plus large que la
   colonne — une étiquette qui pousse le tableau décale toutes les autres. */
.case-verser {
  display: block;
  margin-bottom: .4rem;
  max-width: 22rem;
  font-weight: normal;
}

/* --- RANGER LES VERRES D'UN CÔTÉ ------------------------------------------

   La rangée se lit dans le MÊME SENS que l'élévation au-dessus d'elle : de
   gauche à droite, le premier bloc ici est le premier verre là. C'est tout
   ce qui rend les flèches compréhensibles sans légende.

   Elle passe à la ligne quand le côté a trop de verres pour la largeur de
   l'écran — `wrap` plutôt qu'un défilement : un bloc sorti du cadre par la
   droite est un bloc qu'on ne sait pas qu'on a. */
.rangee-verres {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 0 0 1.5rem;
}

.verre-range {
  display: flex;
  align-items: center;
  gap: .1rem;
  padding: .15rem .3rem;
  border: 1px solid var(--bordure);
  border-radius: 6px;
  background: var(--fond-carte);
}

.verre-range-libelle {
  padding: 0 .35rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* L'ENSEMBLE DE PORTE SE DISTINGUE SANS QU'ON LE NOMME, par le même trait
   double que l'élévation lui donne. Un bloc qui se déplace d'un seul tenant
   doit se voir comme un seul tenant. */
.verre-range-porte {
  border-width: 3px;
  border-style: double;
  border-color: var(--texte);
}

/* Une flèche qui ne peut rien faire reste à sa place — la rangée garde sa
   largeur — mais elle cesse de se proposer. */
.verre-range .bouton-icone[disabled] {
  opacity: .25;
  cursor: default;
}

/* Le champ du numéro n'a pas besoin de la moitié de l'écran, et le bouton
   n'a besoin que de sa propre largeur. */
.rangee-import {
  grid-template-columns: minmax(0, 18rem) auto;
  justify-content: start;
  margin-top: 1rem;
}

/* Le formulaire du calculateur porte une quinzaine de champs courts. Étirés
   sur toute la largeur — ce que fait la grille par défaut — deux champs se
   retrouvaient aux deux bouts de l'écran, séparés par un vide. Ici les
   colonnes gardent leur largeur et se rangent à gauche. */
.rangee-calcul {
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 11rem));
  justify-content: start;
  gap: .9rem 1.4rem;
}

/* La colonne d'action d'un tableau : elle ne porte qu'un bouton-lien, et le
   formulaire qui l'entoure ne doit pas l'écarter de sa ligne. */
.cellule-action { width: 1%; white-space: nowrap; }
.cellule-action form { display: inline; }

/* Les boutons d'une cellule de commandes : ils portent maintenant un bord,
   et deux bords qui se touchent se lisent comme un seul bouton large. */
.cellule-action .bouton-icone + .bouton-icone,
.cellule-action .groupe-boutons { margin-left: .3rem; }
.colonne-actions { padding-top: .3rem; padding-bottom: .3rem; }

/* Une ligne retirée de la soumission reste VISIBLE : la faire disparaître
   empêcherait de la remettre, et laisserait croire qu'elle n'a jamais été
   proposée. */
.ligne-retiree { opacity: .55; }
.ligne-retiree td:first-child { text-decoration: line-through; }

/* LE NUMÉRO DU PROJET, À CÔTÉ DES BOUTONS QUI AGISSENT DESSUS.

   Il porte l'accent parce qu'il répond à une question qu'on se pose avant
   de cliquer : « Enregistrer » va-t-il mettre à jour ce dossier-ci, ou en
   créer un autre ? Un numéro discret laisserait la question ouverte. */
.etiquette-projet {
  display: inline-flex; align-items: center;
  padding: .3rem .6rem; border-radius: var(--rayon);
  background: var(--accent); color: var(--accent-texte);
  font-weight: 700; letter-spacing: .02em; white-space: nowrap;
}

/* --- LE BANDEAU DU PROJET RV ---------------------------------------------

   « On devrait avoir un bandeau avec le numéro du projet RV écrit en gros. »

   Pleine largeur, en tête de l'écran : c'est la première chose qu'on lit en
   revenant sur le calculateur, et la question qu'on se pose alors est « quel
   projet ai-je sous les yeux ? ».

   Le numéro est en `clamp` : il reste gros sur un portable sans déborder
   d'un téléphone. Un numéro coupé serait pire que pas de bandeau. */
.bandeau-projet {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem 1rem;
  margin: 0 0 1rem;
  padding: .7rem 1rem;
  border-radius: var(--rayon);
  background: var(--accent);
  color: var(--accent-texte);
}

.bandeau-projet-numero {
  font-size: clamp(1.5rem, 4vw, 2.3rem);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: .01em;
  /* UN NUMÉRO NE SE COUPE PAS. « RV-0001 » cassé en « RV- » et « 0001 » se
     lit comme deux choses, et c'est le genre de numéro qu'on recopie. */
  white-space: nowrap;
}

/* ⚠️ MAIS « PROJET NON ENREGISTRÉ » EST UNE PHRASE, ET ELLE SE COUPE.
   Mesuré à 320 px — un iPhone SE : le `nowrap` la poussait 16 pixels hors de
   l'écran, et la page entière se mettait à défiler. Le `nowrap` protège un
   numéro ; appliqué à une phrase, il déborde. */
.bandeau-projet-vide .bandeau-projet-numero { white-space: normal; }

.bandeau-projet-client,
.bandeau-projet-commande {
  font-size: 1rem;
  /* Pas `var(--discret)` : sur le rouge, un gris pâli devient illisible.
     L'opacité garde le contraste du texte d'accent en l'atténuant. */
  opacity: .85;
}

.bandeau-projet-commande { font-weight: 700; }

/* ⚠️ SANS PROJET, LE BANDEAU CHANGE DE TON ET NON DE PLACE. En rouge plein,
   « Projet non enregistré » se lirait comme une alerte ; il n'y a rien
   d'anormal à n'avoir pas encore enregistré. */
.bandeau-projet-vide {
  background: var(--fond-carte);
  color: var(--texte);
  border: 1px dashed var(--bordure);
}

/* LES ONGLETS D'ÉTAT, au-dessus de la liste des comptes. Des liens, et non
   une liste déroulante : le nombre à côté de chaque mot est la moitié de ce
   qu'on vient y chercher, et une liste déroulante le cache jusqu'au clic. */
.filtres-etat {
  display: flex; flex-wrap: wrap; gap: .4rem;
  margin: 0 0 1rem;
}
.filtre-etat {
  padding: .3rem .7rem; border-radius: var(--rayon);
  border: 1px solid var(--bordure); background: var(--fond-carte);
  color: var(--texte); text-decoration: none; font-size: .85rem;
}
.filtre-etat:hover { border-color: var(--accent); }
.filtre-etat-actif {
  background: var(--accent); color: var(--accent-texte);
  border-color: transparent;
}
.filtre-etat-actif .discret { color: var(--accent-texte); opacity: .8; }
/* Il ne répond pas à la même question que les états : un espace le dit. */
.filtre-relance { margin-left: .6rem; }

/* UNE RELANCE ÉCHUE. C'est le seul moment où la date compte : le reste du
   temps elle n'est qu'un repère, et la chercher à l'œil dans une colonne de
   dates rendrait la colonne inutile.

   Pas d'aplat : la rangée en porte déjà un pour l'état, et deux blocs rouges
   côte à côte ne se hiérarchisent plus. */
.relance-due { color: var(--accent-lien); font-weight: 700; }

/* L'ÉTAT D'UN COMPTE, ET POURQUOI IL A SES PROPRES COULEURS.

   Quatre états, dont deux disent « attention » : « Retard de paiement » et
   « Fermé ». Les rendre tous du même rouge que le numéro de projet
   reviendrait à ne rien dire — une étiquette qui crie sur les quatre cas ne
   distingue plus celui qui compte.

   Le rouge de la marque est donc RÉSERVÉ au retard, seul état qui doit
   arrêter la main avant de vendre. L'actif est vert, le prospect neutre, le
   fermé effacé. */
.etiquette-etat {
  display: inline-flex; align-items: center;
  padding: .15rem .5rem; border-radius: var(--rayon);
  font-size: .78rem; font-weight: 700; white-space: nowrap;
  border: 1px solid var(--bordure);
  color: var(--texte-doux);
}
.etiquette-etat-prospect { background: var(--fond); }
.etiquette-etat-actif {
  background: var(--succes-aplat); color: #fff; border-color: transparent;
}
.etiquette-etat-retard {
  background: var(--accent); color: var(--accent-texte);
  border-color: transparent;
}
/* Fermé : présent, mais qui ne réclame rien. Le rayer le rendrait illisible
   sur une rangée déjà grise. */
.etiquette-etat-ferme { opacity: .65; }

/* LA RANGÉE QU'ON REGARDE DÉJÀ. Dans la liste des projets de rampe en
   verre, celle du projet ouvert à l'écran : sans marque, on rouvre celui
   qu'on a déjà sous les yeux et on croit que rien ne s'est passé.

   `--accent-sourd` et non l'accent plein : la rangée se distingue sans
   crier, et la variable suit le thème sombre d'elle-même. */
.rangee-active > td { background: var(--accent-sourd); }
.rangee-active > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

/* Le nom d'un accessoire est long — « Pince large 90 (Noir) » —, la quantité
   tient en deux chiffres. Des colonnes de même largeur donneraient un champ
   de nom trop court et un champ de quantité vide aux trois quarts. */
.rangee-accessoire {
  grid-template-columns: minmax(0, 22rem) 5rem 9rem auto;
  justify-content: start;
  gap: .9rem 1.4rem;
  margin-top: 1rem;
}

/* --- Le tracé du balcon --------------------------------------------------- */

.planche-trace {
  display: block;
  width: 100%;
  /* ═══ LA PLANCHE GARDE LES PROPORTIONS DE SON `viewBox`, TOUJOURS ══════

     C'était `max-height: 62vh` sur une boîte large de 100 % — et c'est ce
     qui faisait que LE POINT N'APPARAISSAIT PAS OÙ L'ON CLIQUAIT.

     Un SVG à `viewBox` centre son dessin quand la boîte n'a pas les mêmes
     proportions (`preserveAspectRatio` vaut `xMidYMid meet` par défaut) :
     le tracé de 1000 × 680 se rangeait au milieu, avec du blanc à gauche et
     à droite. Le clic, lui, était rapporté à la LARGEUR DE LA BOÎTE — donc
     décalé de toute la marge, et à la mauvaise échelle.

     Le plafond de hauteur porte maintenant sur la LARGEUR : 62vh de haut
     pour 1000 × 680, c'est 91,2vh de large. La boîte ne peut plus avoir
     d'autres proportions que le dessin, il n'y a plus de marge, et rien ne
     dépasse le bas de la fenêtre — ce que le plafond cherchait à obtenir.

     ⚠️ LA CORRECTION DU CLIC NE TIENT PAS ICI. Le script lit désormais la
     matrice de l'élément (`getScreenCTM`), qui donne la bonne réponse même
     sous une mise en page fautive. Cette règle-ci ne fait que rendre la
     planche entière utilisable ; c'est le script qui garantit le point. */
  max-width: min(60rem, 91.2vh);
  aspect-ratio: 1000 / 680;
  height: auto;
  margin-top: 1rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: 6px;
  color: var(--texte);
  cursor: crosshair;
}

/* ═══ LE PLAN MONTRE, LA LISTE CORRIGE ═════════════════════════════════════

   « Inscrire les longueurs sur les côtés facilement. Localiser la ou les
   portes. » — le client.

   Chaque côté a d'abord porté une CARTOUCHE posée sur le dessin, en
   position absolue, aux pourcentages que le serveur calculait. C'est ce
   que le client a trouvé « difficile d'utilisation », et la mesure lui
   donne raison : sur son tracé en Z, à 1 451 px de fenêtre, deux
   cartouches se recouvraient et une troisième sortait du cadre.

   Une cartouche fait 280 px. Six d'entre elles ne se placent pas sur un
   plan de 800 px — j'ai essayé un décalage en quinconce, qui réglait le
   rectangle et cassait le L, puis un seuil au nombre de côtés, que ce
   tracé-là a franchi en restant illisible. On ne cherche plus : le plan
   porte ses étiquettes, la saisie se fait en liste dessous, et plus rien
   ne peut se chevaucher parce que plus rien n'est posé sur le dessin. */

.planche-lecture {
  display: block;
  width: 100%;
  max-width: 62rem;
  margin: 1rem auto 0;
}

.planche-lecture > svg {
  display: block;
  width: 100%;
  height: auto;
  color: var(--texte);
}

.saisie-cotes {
  width: 100%;
  max-width: 62rem;
  margin: 1.25rem auto 0;
}

.liste-cotes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .4rem;
}

/* Une ligne par côté. Elle passe À LA LIGNE plutôt que de déborder : sur
   une fenêtre étroite, la porte descend sous la longueur et tout reste
   lisible — ce qu'un débordement horizontal n'aurait pas donné. */
.ligne-cote {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem .75rem;
  padding: .45rem .7rem;
  border: 1px solid var(--bordure);
  border-radius: 6px;
  background: var(--fond-carte);
}

/* La lettre est la CLÉ : c'est elle qu'on lit sur le plan. Même largeur
   partout pour que les champs s'alignent en colonne. */
.cote-lettre {
  min-width: 1.6rem;
  font-weight: 700;
  color: var(--accent);
}

.cote-longueur,
.cote-largeur,
.cote-double {
  display: flex;
  align-items: center;
  gap: .3rem;
  white-space: nowrap;
}

.cote-intitule {
  color: var(--texte-doux);
  font-size: .85rem;
}

/* Quatre chiffres et trois décimales : au-delà, le champ s'étire et
   pousse la porte à la ligne suivante pour rien. */
.ligne-cote input[type="number"] {
  width: 5.5rem;
  padding: .2rem .35rem;
  border: 1px solid transparent;
  border-bottom-color: var(--bordure);
  border-radius: 3px;
  background: transparent;
  color: var(--texte);
  font: inherit;
  font-weight: 600;
  text-align: right;
}

.ligne-cote input[type="number"]:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  background: var(--fond);
}

/* L'endroit de la porte : quatre choix dont UN est l'état courant. Le
   bouton radio lui-même est masqué ; c'est son étiquette qui s'allume. */
.cote-porte {
  display: flex;
  align-items: center;
  gap: .25rem;
  flex-wrap: wrap;
}

.cote-porte input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.cote-porte label {
  padding: .15rem .5rem;
  border: 1px solid var(--bordure);
  border-radius: 999px;
  font-size: .85rem;
  line-height: 1.4;
  cursor: pointer;
  user-select: none;
}

.cote-porte input[type="radio"]:checked + label {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
}

/* ⚠️ VISIBLE AU CLAVIER. Le bouton radio étant masqué, sans cette règle
   la tabulation traverse quatre choix sans qu'on voie où l'on est. */
.cote-porte input[type="radio"]:focus-visible + label {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* La largeur et l'ouverture double n'ont de sens qu'une fois la porte
   posée. Sans JavaScript : `:has()` lit l'état des boutons ci-dessus. Des
   champs vides sur chaque ligne cacheraient celui qui compte. */
.ligne-cote:has(.cote-porte input[value=""]:checked) .cote-largeur,
.ligne-cote:has(.cote-porte input[value=""]:checked) .cote-double,
.ligne-cote:has(.cote-porte input[value=""]:checked) .cote-avis {
  display: none;
}

/* Une largeur de porte gardée d'avant, que le catalogue n'offre pas : la
   ligne qui dit pourquoi aucune pastille n'est allumée. SOUS le côté, sur
   toute la largeur — glissée entre deux pastilles, elle pousserait le
   « double » à la ligne et se lirait comme une troisième option. Elle se
   cache avec la largeur quand on retire la porte : le problème part avec
   elle. */
.cote-avis {
  flex-basis: 100%;
  margin: 0;
}

/* ⚠️ ET ELLE SE CACHE DÈS QU'ON CHOISIT UNE LARGEUR OFFERTE. Restée là,
   elle disait encore « choisissez » sous la pastille qu'on venait
   d'allumer, et l'on croyait que le choix n'avait pas pris. Le serveur ne
   la rend que quand AUCUNE pastille n'est cochée : elle reste donc visible
   tant qu'on n'a rien choisi. */
.ligne-cote:has(.cote-largeur input:checked) .cote-avis {
  display: none;
}

.cote-double input { width: .9rem; height: .9rem; margin: 0; }

.rangee-longueur {
  display: flex;
  align-items: center;
  gap: .75rem;
}

.rangee-longueur input { width: 7rem; }

/* Le choix du nombre de portes : trois boutons côte à côte dont UN est
   l'état courant. Le marquer avec le bouton vert des actions ferait lire
   « aucune » comme une action à faire sur chaque ligne sans porte. */
.choix-porte {
  background: none;
  border: 1px solid transparent;
  border-radius: 4px;
  padding: .15rem .5rem;
  font: inherit;
  color: var(--texte);
  opacity: .55;
  cursor: pointer;
}

.choix-porte:hover { opacity: 1; border-color: var(--bordure); }

.choix-actif {
  opacity: 1;
  font-weight: 600;
  border-color: var(--accent);
  background: var(--fond-carte);
}

/* --- Les états d'une soumission ------------------------------------------- */

/* LES APLATS SONT CEUX DE L'APPLICATION DÉPLOYÉE, AU CHIFFRE PRÈS. Le client
   reconnaît ses soumissions à leur couleur, et les « améliorer » lui ferait
   relire une liste qu'il lisait d'un coup d'œil.

   SEULE LA COULEUR DU TEXTE CHANGE, et elle a été MESURÉE. L'original écrivait
   en blanc sur six des dix aplats ; sur l'orange « attente », cela donnait
   2,15:1 — illisible, et très loin du seuil de 4,5:1. Le noir y donne 10,63:1,
   sur le même orange. Chaque état porte donc maintenant la couleur de texte
   qui mesure le mieux sur SON aplat, et un test le vérifie à chaque exécution
   plutôt que de le croire.

   L'aplat porte sa propre couleur de fond : la pastille reste donc lisible
   dans le thème sombre comme dans le clair, sans deuxième jeu de valeurs. */

.statut-a_appeler { --statut-fond: #808080; --statut-texte: #000000; }
.statut-msg_voc { --statut-fond: #f20061; --statut-texte: #000000; }
.statut-cedule { --statut-fond: #800080; --statut-texte: #ffffff; }
.statut-preparation { --statut-fond: #808080; --statut-texte: #000000; }
.statut-attente { --statut-fond: #ffa500; --statut-texte: #000000; }
.statut-suivi { --statut-fond: #ffff00; --statut-texte: #000000; }
.statut-envoye { --statut-fond: #4682b4; --statut-texte: #000000; }
.statut-envoi_signature { --statut-fond: #9370db; --statut-texte: #000000; }
.statut-signe { --statut-fond: #008000; --statut-texte: #ffffff; }
.statut-ferme { --statut-fond: #ff0000; --statut-texte: #000000; }

.etiquette-statut {
  display: inline-block;
  padding: .1rem .55rem;
  border-radius: 999px;
  font-size: .82rem;
  font-weight: 600;
  white-space: nowrap;
  background: var(--statut-fond, var(--bordure));
  color: var(--statut-texte, var(--texte));
}

.filtres-statut {
  display: flex;
  flex-wrap: wrap;
  gap: .3rem .5rem;
  align-items: center;
}

.filtre-statut {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .1rem .5rem;
  border-radius: 999px;
  font-size: .8rem;
  background: var(--statut-fond, var(--bordure));
  color: var(--statut-texte, var(--texte));
  cursor: pointer;
}

.rangee-statut { grid-template-columns: minmax(0, 16rem) auto;
                 justify-content: start; margin-bottom: 1rem; }

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

.tableau-total { max-width: 28rem; }

.details-dossier {
  display: grid;
  grid-template-columns: minmax(0, 9rem) minmax(0, 1fr);
  gap: .35rem 1rem;
  margin: 0;
}

.details-dossier dt { color: var(--texte-doux); }
.details-dossier dd { margin: 0; }

@media (max-width: 700px) {
  .details-dossier { grid-template-columns: 1fr; }
  .details-dossier dd { margin-bottom: .5rem; }
}

/* L'adresse des travaux est longue et le TOTAL est la colonne qu'on vient
   lire. Laissée libre, l'adresse poussait le total hors de l'écran : on la
   coupe, et l'infobulle rend la version entière. */
.colonne-adresse {
  max-width: 20rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Les actions d'une partie — monter, descendre, renommer, dupliquer,
   retirer — tiennent sur une ligne. Sur deux, elles se lisaient comme deux
   groupes distincts.

   L'écart est passé de .9rem à .35rem le jour où ce sont devenus des boutons
   bordés : cinq mots séparés ont besoin d'air pour ne pas se confondre,
   cinq boutons ont besoin de se serrer pour former un groupe. */
.entete-tableau .actions-en-ligne {
  display: flex;
  align-items: center;
  gap: .35rem;
  white-space: nowrap;
}

/* Les trois états de la permission, sur la fiche du client. Ils portent des
   MOTS — « demandée », « accordée », « refusée » ne se dessinent pas — donc
   des boutons larges, et à trois côtés à côté ils dépassaient de treize
   pixels sur un téléphone : mesuré, la liste débordait sans pouvoir défiler.
   Ils passent à la ligne plutôt que de sortir de l'écran. */
.actions-permission {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem;
}

/* --- L'adresse du formulaire d'approbation -------------------------------- */

/* Une adresse avec son chemin, ses deux paramètres et leurs accolades fait
   facilement quatre-vingts caractères ; il lui faut donc plus que la colonne
   d'une adresse de courriel. C'est `.rangee-large`, posée avec le reste de
   l'écran du courriel — voir « UN RÉGLAGE PAR LIGNE » plus haut. La règle qui
   vivait ici étalait la rangée en UNE colonne pleine largeur, ce qui n'a plus
   de sens : chaque réglage tient déjà sa propre ligne. */


/* ═════════════════════════════════════════════════════════════════════════
   LA SAISIE DES LIGNES À MÊME LE TABLEAU

   Le tableau d'une partie n'est plus une liste qu'on regarde : c'est le
   formulaire qu'on remplit. Les champs doivent donc se fondre dans la
   rangée — un tableau où chaque cellule porte une boîte grise encadrée se
   lit comme un questionnaire, pas comme une soumission.
   ═════════════════════════════════════════════════════════════════════════ */

/* ⚠️ La règle globale « input[type=text] » pose 1,1 rem de marge basse, pour
   les formulaires EN COLONNE. Dans une rangée de tableau, elle ajoutait
   dix-huit pixels sous chaque champ : les rangées doublaient de hauteur et
   le tableau ne tenait plus dans l'écran. */
.tableau-saisie input[type="text"] {
  width: 100%;
  margin-bottom: 0;
  padding: .25rem .4rem;
  font-size: .85rem;
  background: transparent;
  /* Une bordure seulement EN DESSOUS : elle dit « on écrit ici » sans
     encadrer cinquante cellules. Au survol et au foyer, le champ prend son
     relief — c'est le moment où l'on en a besoin. */
  border: 1px solid transparent;
  border-bottom-color: var(--bordure);
  border-radius: 3px;
}

.tableau-saisie input[type="text"]:hover {
  background: var(--fond);
  border-color: var(--bordure);
}

.tableau-saisie input[type="text"]:focus {
  background: var(--fond);
}

/* LA LARGEUR DES COLONNES.
   La désignation est la colonne qu'on LIT : « Service de grutier (plan de
   signalisation et gestion de la circulation non incluse) » fait soixante-dix
   caractères, et les colonnes réparties à parts égales la coupaient à
   « Service de grutier (plan d… » — pendant que « Précision », vide neuf fois
   sur dix, occupait la même place. */
/* Les colonnes étroites sont fixées en PIXELS, pas en pourcentage : leur
   contenu — « 24 », « 48,55 », les quatre commandes — ne rétrécit pas avec
   l'écran. À
   1024 px, huit pour cent ne logeaient plus les quatre commandes, la cellule
   ne coupant pas ses mots poussait le tableau au large, et le cadre répondait
   par une barre de défilement. L'article prend ce qui reste. */
.tableau-saisie { table-layout: fixed; width: 100%; }
.tableau-saisie th:nth-child(1) { width: auto; }
.tableau-saisie th:nth-child(2) { width: 16%; }
.tableau-saisie th:nth-child(3) { width: 4.5rem; }
.tableau-saisie th:nth-child(4) { width: 6.5rem; }
.tableau-saisie th:nth-child(5) { width: 7rem; }
.tableau-saisie th:nth-child(6) { width: 9rem; }

/* LES COMMANDES D'UNE RANGÉE sont plus serrées que celles d'un emplacement, et
   c'est une contrainte de place, pas un choix d'esthétique : elles se répètent
   à chaque ligne d'un tableau qui en porte trente, dans une colonne qui doit
   laisser la sienne à la désignation — la seule qu'on LIT.

   Mesuré avant de fixer le chiffre : à la taille ordinaire, la corbeille
   débordait de 57 px et le cadre répondait par une barre de défilement
   horizontale. Celle-là même que le client avait demandé de faire
   disparaître ; on ne la ramène pas par la porte de derrière. */
.tableau-saisie td.cellule-action {
  /* La colonne est comptée au pixel (voir ci-dessus) : on lui retire le
     rembourrage qu'elle n'utilise pas. À gauche, la poignée fait sa propre
     marge ; à droite, le bord du bouton tient lieu de repère. */
  padding-left: .25rem;
  padding-right: .5rem;
}
.cellule-action .bouton-icone {
  min-width: 1.85rem;
  min-height: 1.85rem;
  padding: 0;
}
.cellule-action .bouton-icone .icone { width: 1em; height: 1em; }

/* Quantité et prix : deux ou trois chiffres, pas une colonne de dix
   centimètres. Le prix s'aligne à droite, comme le montant sous lequel il
   se lit. */
.tableau-saisie .champ-court { text-align: right; }
.tableau-saisie td.colonne-montant .champ-court { margin-left: auto; }

/* Les rangées vierges du bas. Elles sont toujours là — c'est ce qui permet
   d'ajouter un article sans ouvrir quoi que ce soit — donc elles doivent se
   distinguer de ce qui est déjà enregistré sans pour autant crier. */
.tableau-saisie .rangee-vierge input[type="text"] {
  border-bottom-style: dashed;
  color: var(--texte);
}

.tableau-saisie .rangee-vierge td { opacity: .85; }
.tableau-saisie .rangee-vierge:focus-within td { opacity: 1; }

/* Le retrait est le seul geste irréversible de la rangée. Il ne se colore
   plus en permanence — trente rangées de corbeilles rouges ne désignent
   rien — mais sous le doigt : voir `.bouton-icone-danger`. Les états
   désactivé et survolé viennent de la même classe. */


/* --- Une carte qu'on replie ---------------------------------------------- */

/* `<details>` fait tout le travail : l'état est tenu par le navigateur, et
   la zone se déplie chez qui n'exécute aucun script. Reste à faire du
   « summary » un titre de section qui ne trahit pas son rôle de bouton. */
.carte-repliable details > summary.titre-section {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  cursor: pointer;
  /* Le triangle par défaut se place AVANT le contenu et casse
     l'alignement du titre avec les autres sections. On le retire et on le
     remplace par le mot « déplier », qui dit la même chose en clair. */
  list-style: none;
}

.carte-repliable details > summary.titre-section::-webkit-details-marker {
  display: none;
}

.carte-repliable .indice-deplier {
  color: var(--accent-texte);
  font-size: .75rem;
  font-weight: 400;
  /* Le libellé change avec l'état, sans une ligne de script. */
  opacity: .85;
}

/* Le mot ET le chevron viennent d'ici : un libellé écrit dans le gabarit
   mentirait dans l'un des deux états. Le « summary » porte déjà l'état
   déplié/replié pour les lecteurs d'écran, d'où l'aria-hidden. */
.carte-repliable details[open] .indice-deplier::after { content: "replier ▴"; }
.carte-repliable details:not([open]) .indice-deplier::after { content: "déplier ▾"; }

.carte-repliable details > summary.titre-section:focus-visible {
  outline: 2px solid var(--accent-texte);
  outline-offset: -3px;
}


/* Le chevron d'un repli dit son état en pivotant. La transition est la même
   que celle des couleurs du bouton : le mouvement se voit, sans durer. */
.bouton-icone .icone { transition: transform .15s ease; }
.bouton-icone[aria-expanded="true"] .icone { transform: rotate(180deg); }

/* Qui a demandé moins d'animation n'en aura aucune. */
@media (prefers-reduced-motion: reduce) {
  .bouton-icone, .bouton-icone .icone { transition: none; }
}


/* --- La poignée du glisser-déposer --------------------------------------- */

/* Elle est révélée par `lignes.js` : sans script elle ne ferait rien, et
   sur une tablette le glisser-déposer HTML n'existe pas. Monter et descendre
   restent le chemin principal. */
.tableau-saisie .poignee {
  /* `inline-flex` et non `inline-block` : le SVG doit se centrer sur la
     hauteur du bouton voisin, sinon la poignée pend sous la rangée. */
  display: inline-flex;
  align-items: center;
  padding: 0 .2rem;
  font-size: 1.05rem;
  color: var(--texte-doux);
  cursor: grab;
  /* Sans cela, amorcer un glisser sélectionne le caractère au passage et
     laisse une traînée bleue sur la rangée. */
  user-select: none;
  -webkit-user-select: none;
}

.tableau-saisie .poignee:active { cursor: grabbing; }

/* SOUS 1100 px, LA POIGNÉE S'EFFACE. Entre la largeur où la carte se replie
   (900 px) et celle où la page cesse de rétrécir (1100 px), le tableau se
   serre et c'est la DÉSIGNATION qui paie — mesuré : 157 px à 1024, 95 px à
   950, pour la seule colonne qu'on lit. La poignée en reprend vingt-six, et
   elle est le seul élément de la rangée dont on puisse se passer : monter et
   descendre font le même travail, au clavier comme à la souris. */
@media (max-width: 1100px) {
  .tableau-saisie .poignee { display: none; }
  /* Et la colonne rétrécit d'autant : cacher la poignée sans reprendre sa
     place ne ferait que déplacer le vide. */
  .tableau-saisie th:nth-child(6) { width: 7rem; }
}
.tableau-saisie tr:hover .poignee { color: var(--texte); }

/* La rangée qu'on tient : allégée, pour qu'on voie où on la pose. */
.tableau-saisie tr.ligne-en-deplacement { opacity: .45; }

/* Celle qu'on survole : un trait de la couleur de la maison, qui dit
   « c'est ici que ça tombe ». */
.tableau-saisie tr.ligne-survolee td {
  box-shadow: inset 0 -2px 0 var(--accent);
}


/* --- Le pied d'une partie ------------------------------------------------ */

/* Le bouton qui enregistre la partie, SOUS le tableau et non dans une de ses
   cellules — c'est lui qui forçait le tableau plus large que son cadre, et
   le cadre répondait par une barre de défilement horizontale. */
.pied-partie {
  display: flex;
  justify-content: flex-end;
  padding: .6rem 0 1.2rem;
}

/* Une cible tactile ne se vise pas à deux pixels près : 44 px est le minimum
   retenu partout, et l'atelier travaille avec des gants. */
.pied-partie .bouton-enregistrer { min-height: 44px; padding: .55rem 1.4rem; }

/* Le même bouton en TÊTE de partie — « en haut et en bas de page », le
   client. Il garde le dessin du pied ; seul l'air change de côté : rien
   au-dessus, sous le titre, et un peu sous lui, avant le tableau. */
.tete-partie { padding: 0 0 .6rem; }


/* ═════════════════════════════════════════════════════════════════════════
   L'ÉCRAN ÉTROIT, ET LE DOIGT

   Un tableau de six colonnes avec des champs de saisie ne tient pas dans
   390 px, et aucun réglage de largeur n'y changera rien : il n'y a pas la
   place. Le réduire jusqu'à ce qu'il entre donnerait des colonnes de deux
   caractères.

   Sous 900 px, chaque rangée devient donc une FICHE empilée. C'est le même
   HTML — pas une seconde page à tenir à jour —, et l'intitulé de chaque
   champ vient de l'attribut `data-colonne` que le gabarit pose déjà.
   ═════════════════════════════════════════════════════════════════════════ */

@media (max-width: 900px) {
  /* L'EN-TÊTE D'UNE PARTIE. « Description des travaux » et son bouton se
     chevauchaient ; le nom de l'emplacement se cassait en trois lignes à
     côté de ses commandes. Chacun sa ligne, et le bouton en pleine
     largeur — il est le geste principal de la section. */
  .titre-section {
    flex-wrap: wrap;
    gap: .5rem;
    padding: .7rem .9rem;
  }
  .titre-section .bouton-ajouter { width: 100%; text-align: center; }
  /* ⚠️ ET UN GROUPE DE BOUTONS SE REPLIE, UN BOUTON PAR LIGNE. Le groupe
     restait sur une seule rangée, plus large que la bande, et la carte
     coupait ce qui dépassait. Mesuré à 390 px sur la fiche d'une commande,
     quand le bouton « DESSINER DANS BD-CROQUIS » est venu s'ajouter à la
     bande « Mesures » : AJOUTER commençait à 523 px, la bande finissait à
     365 — invisible, donc introuvable. APPROBATION DU CLIENT et AJOUTER
     côte à côte débordaient déjà de quelques pixels. */
  .titre-section .groupe-boutons {
    flex-wrap: wrap;
    width: 100%;
  }

  #sections .entete-tableau {
    display: block;
    padding-bottom: .35rem;
  }
  #sections .entete-tableau h3 { margin-bottom: .4rem; }
  #sections .entete-tableau .actions-en-ligne {
    display: flex;
    flex-wrap: wrap;
    gap: .25rem;
  }

  .tableau-saisie,
  .tableau-saisie tbody,
  .tableau-saisie tfoot,
  .tableau-saisie tr,
  .tableau-saisie td { display: block; width: auto; }

  /* L'en-tête ne sert plus à rien : chaque champ porte son intitulé. */
  .tableau-saisie thead { display: none; }

  .tableau-saisie tbody tr {
    margin-bottom: .9rem;
    padding: .6rem .75rem;
    border: 1px solid var(--bordure);
    border-radius: 8px;
    background: var(--fond-carte, transparent);
  }

  .tableau-saisie tbody td {
    display: grid;
    grid-template-columns: 6.5rem 1fr;
    align-items: center;
    gap: .6rem;
    padding: .2rem 0;
    text-align: left;
  }

  .tableau-saisie tbody td[data-colonne]::before {
    content: attr(data-colonne);
    color: var(--texte-doux);
    font-size: .78rem;
    font-weight: 600;
  }

  /* La cellule des commandes n'a pas d'intitulé : les boutons s'alignent
     à droite, assez grands pour le doigt. */
  .tableau-saisie tbody td.cellule-action {
    display: flex;
    justify-content: flex-end;
    gap: .35rem;
    margin-top: .35rem;
    padding-top: .5rem;
    border-top: 1px dashed var(--bordure);
  }

  .tableau-saisie .champ-court { max-width: none; text-align: left; }
  .tableau-saisie td.colonne-montant { text-align: left; }

  /* Le pied : le sous-total et son montant. `wrap` parce que « Sous-total
     (arrondi à 5 $) » et « 1 680,00 $ » font ensemble 344 px là où il y en a
     306 — mesuré, pas supposé. Sans lui, le cadre répondait par une barre de
     défilement de cinquante pixels. */
  .tableau-saisie tfoot tr {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: .25rem .75rem;
    padding: .6rem .75rem 0;
  }
  .tableau-saisie tfoot td { min-width: 0; }
  .tableau-saisie tfoot td:empty { display: none; }

  /* La poignée ne sert à rien ici : le glisser-déposer HTML n'existe pas au
     doigt. Monter et descendre restent, et c'est pour cela qu'on les a
     gardés. */
  .tableau-saisie .poignee { display: none; }
}


/* --- Le doigt plutôt que la souris --------------------------------------- */

/* `pointer: coarse` vise l'APPAREIL, pas la largeur : une tablette de 1024 px
   se touche autant qu'un téléphone, et un portable étroit se clique encore à
   la souris. On grossit donc les cibles là où l'on tape, et nulle part
   ailleurs — l'écran de bureau reste compact.

   44 px : le minimum retenu par Apple comme par Google, et l'atelier
   travaille parfois avec des gants. */
@media (pointer: coarse) {
  .tableau-saisie input[type="text"] {
    min-height: 44px;
    padding: .5rem .6rem;
    font-size: 1rem;          /* 16 px : en dessous, iOS zoome à la saisie */
  }

  /* Les boutons d'icône, partout. Ils sont déjà bordés et centrés ; il ne
     reste qu'à les porter à la taille du doigt.

     44 px de LARGE autant que de haut : une flèche fait onze pixels de
     large, et la hauteur seule ferait viser un trait. */
  .bouton-icone {
    min-width: 44px;
    min-height: 44px;
    font-size: .95rem;
  }
  .bouton-icone-texte { padding: 0 .9rem; }

  .titre-section .bouton-ajouter {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}


/* Le suivi interne d'un dossier : lu souvent, jamais envoyé. Le liseré le
   distingue de ce que le client a écrit juste au-dessus — les deux vivaient
   dans le même champ, et c'est ce qui les faisait partir ensemble. */
.suivi-interne {
  padding-left: .7rem;
  border-left: 3px solid var(--bordure);
  color: var(--texte-doux);
  font-size: .9rem;
}


/* --- « Déjà venu » : le client a d'autres soumissions -------------------- */

.colonne-deja { width: 1%; text-align: center; white-space: nowrap; }

/* `<details>` : la liste s'ouvre sur place, sans quitter le tableau. C'est
   du HTML — l'état est tenu par le navigateur, et ça marche sans script. */
.deja-venu { display: inline-block; position: relative; }

.deja-venu > summary {
  cursor: pointer;
  list-style: none;
  padding: .15rem .35rem;
  color: var(--accent);
  font-weight: 700;
  border-radius: 4px;
}

.deja-venu > summary::-webkit-details-marker { display: none; }
.deja-venu > summary:hover { background: var(--fond); }

.compte-deja {
  margin-left: .15rem;
  color: var(--texte-doux);
  font-size: .75rem;
  font-weight: 400;
}

/* La liste flotte au-dessus du tableau : l'insérer dans le flux décalerait
   toutes les rangées du dessous à chaque ouverture. */
.liste-apparentees {
  position: absolute;
  z-index: 30;
  top: 100%;
  left: 0;
  min-width: 17rem;
  max-width: 24rem;
  margin: .25rem 0 0;
  padding: .5rem .7rem;
  list-style: none;
  text-align: left;
  white-space: normal;
  background: var(--fond-carte, var(--fond));
  border: 1px solid var(--bordure);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
}

.liste-apparentees li + li {
  margin-top: .45rem;
  padding-top: .45rem;
  border-top: 1px solid var(--bordure);
}

/* Près du bord droit, la liste sortirait de l'écran : les dernières colonnes
   l'ouvrent vers la gauche. */
.tableau td:nth-last-child(-n+3) .liste-apparentees { left: auto; right: 0; }

@media (pointer: coarse) {
  .deja-venu > summary { min-width: 44px; min-height: 44px;
                         display: inline-flex; align-items: center;
                         justify-content: center; }
}


/* Un dossier fermé : ni une alerte ni une erreur, un CONSTAT. Le liseré
   reprend celui de « ce qui sert vraiment » plus haut, en gris plutôt qu'en
   rouge — rien n'est cassé, le dossier est simplement fini. */
.constat-verrou {
  margin: 0 0 1rem;
  padding: .6rem .85rem;
  background: var(--fond);
  border-left: 3px solid var(--bordure-forte, var(--bordure));
  border-radius: var(--rayon);
  color: var(--texte-doux);
  font-size: .9rem;
}


/* LES DEUX FAÇONS D'AJOUTER UN CROQUIS, À DROITE, L'UNE SOUS L'AUTRE.

     « J'aimerais que "Depuis un fichier" soit placé à droite sous
       "Dessiné dans BD-CROQUIS" et que "Dessiné dans BD-CROQUIS" devienne
       "Depuis BD-CROQUIS". »                                — le client.

   Elles étaient côte à côte, le fichier à gauche ; elles sont maintenant
   en colonne, calées à droite : BD-CROQUIS en haut, le fichier dessous.
   Une colonne plutôt qu'une rangée qui passe à la ligne : la rangée
   décidait SEULE, selon la largeur de l'écran, si les deux tenaient côte à
   côte — et le client a demandé une place, pas une largeur.

   ⚠️ L'ORDRE DE LA COLONNE EST CELUI DU GABARIT (`dossier.html` met le
   formulaire BD-CROQUIS avant `_fichiers.html`), sauf la liste, que `order`
   envoie en bas. */
.croquis-ajout {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

/* CE QUI N'EST PAS UNE OPTION PREND TOUTE LA LARGEUR : la liste des croquis
   déjà joints et le message « aucun croquis déposé » disent l'ÉTAT de la
   section, pas une troisième façon d'ajouter. Calés à droite avec les deux
   options, ils se seraient lus comme une suite du formulaire.

   ⚠️ ET `order` LES ENVOIE SOUS LES DEUX OPTIONS, quelle que soit leur place
   dans le gabarit. `_fichiers.html` sert huit sections et y sort la liste
   APRÈS son dépôt (« mettre la ligne Sélect. fichiers au-dessus », la règle
   vaut partout) : sans `order`, l'état se glisserait entre les deux façons
   d'ajouter un croquis le jour où l'une changerait de place. */
/* ⚠️ `.depot-plein` A ÉTÉ RETIRÉE DE CETTE LISTE AVEC LA PHRASE QU'ELLE
   habillait — « cette section n'accepte qu'un seul fichier… », partie à la
   demande du client. Un sélecteur qui ne désigne plus rien a l'air vivant :
   on le recopie, on le déplace, et personne ne voit qu'il ne sert pas. */
.croquis-ajout > .fichiers,
.croquis-ajout > .aucun-fichier {
  order: 1;
  align-self: stretch;
  margin-bottom: 0;
}

/* LES DEUX OPTIONS ONT LA MÊME LARGEUR, et c'est ce qui les aligne : leurs
   bords droits se touchent au bord de la carte, leurs titres se lisent l'un
   sous l'autre. À leur largeur naturelle, le menu de BD-CROQUIS et le champ
   de fichier n'ont pas la même, et deux blocs de largeurs différentes calés
   à droite se lisent comme un escalier. `min` les laisse rétrécir avec la
   carte. */
.croquis-ajout > .depot,
.croquis-ajout > .sans-croquis-bd {
  width: min(100%, 30rem);
  margin: .6rem 0 .2rem;
}
.croquis-ajout > .depot { justify-content: flex-end; }

/* LE TITRE DE CHAQUE OPTION, SUR SA PROPRE LIGNE, À DROITE COMME ELLE.

   Mis en travers de la rangée de contrôles — « Croquis dessiné dans
   BD-CROQUIS : » faisait 230 px à lui seul —, il poussait le bouton hors de
   la largeur et le faisait décrocher à la ligne suivante, loin de son menu.
   Au-dessus, il laisse les contrôles au large et dit ce qu'ils font. */
.titre-depot {
  flex: 1 0 100%;
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--texte-discret, #6b7280);
  margin-bottom: -.25rem;
}
.croquis-ajout .titre-depot { text-align: right; }

/* Les contrôles restent groupés : le menu et son bouton ne se séparent plus.
   `select` à 9 rem plutôt que 12 — un numéro de commande tient largement. */
.croquis-ajout .champ-en-ligne select,
.croquis-ajout select { min-width: 9rem; }

/* LE CHAMP DE FICHIER CÈDE LA PLACE À SON BOUTON.

   Mesuré : à sa taille naturelle il fait 295 px, et 295 + 12 + 109 dépasse la
   colonne de 373 — le bouton « Déposer » décrochait donc à la ligne suivante,
   détaché de son champ. Il se rétrécit maintenant pour tenir à côté ; c'est le
   texte « Aucun fichier choisi » du navigateur qui se raccourcit, et ce texte
   ne dit rien qu'on ait besoin de lire en entier. */
.croquis-ajout .depot input[type="file"] {
  flex: 1 1 8rem;
  min-width: 0;
}

/* LE MENU DE BD-CROQUIS S'ÉTIRE COMME LE CHAMP DE FICHIER, juste dessous :
   les deux rangées de contrôles occupent alors la même largeur, et leurs
   boutons tombent l'un sous l'autre au bord droit. */
.croquis-ajout > .depot-croquis select {
  flex: 1 1 9rem;
}

/* Quand il n'y a rien à joindre depuis BD-CROQUIS, la phrase occupe la place
   du menu : elle explique ce qui manque, à l'endroit où on l'aurait choisi —
   en haut, à droite, au-dessus du dépôt de fichier. */
.croquis-ajout > .sans-croquis-bd { text-align: right; }

/* Sur un téléphone, TOUT revient à gauche et prend la largeur. Un bloc calé
   à droite d'un écran étroit laisse un vide à gauche qui se lit comme une
   erreur de mise en page. */
@media (max-width: 700px) {
  .croquis-ajout { align-items: stretch; }
  .croquis-ajout > .depot,
  .croquis-ajout > .sans-croquis-bd { width: auto; }
  .croquis-ajout > .depot { justify-content: flex-start; }
  .croquis-ajout > .sans-croquis-bd,
  .croquis-ajout .titre-depot { text-align: left; }
}
.champ-en-ligne {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  flex-wrap: wrap;
}
.champ-en-ligne select {
  min-width: 12rem;
  max-width: 100%;
}


/* LES EN-TÊTES DU TABLEAU DE BORD.

   Onze colonnes d'indicateurs, une icône chacune. Un peu plus grandes que les
   icônes de bouton : elles nomment une colonne entière, pas un geste, et une
   colonne mal identifiée fait mal lire tout ce qu'elle contient.

   `vertical-align: middle` avec la cellule centrée : sans lui, l'icône
   s'assoit sur la ligne de base du texte, et la rangée d'en-têtes ondule
   parce que « Commande » y reste en lettres. */
.icone-entete {
  width: 1.25rem;
  height: 1.25rem;
  vertical-align: middle;
}
.tableau thead th {
  text-align: center;
  vertical-align: middle;
}
.tableau thead th:first-child {
  text-align: left;
}


/* LE CONSTAT D'APPROBATION INTERNE, pour qui ne peut pas la donner.

   Ni une alerte ni un champ : un fait. Le vert dit « c'est fait », le gris
   « pas encore » — et les deux se disent, sans quoi le silence serait
   ambigu. Pas de rouge : ne pas être encore approuvé n'est pas une faute,
   c'est une étape. */
.constat-approbation {
  display: flex;
  align-items: center;
  gap: .45rem;
  margin: 0 0 1rem;
  padding: .55rem .8rem;
  background: var(--fond);
  border-left: 3px solid var(--succes);
  border-radius: var(--rayon);
  font-size: .9rem;
}
.constat-approbation .icone {
  flex: none;
  color: var(--succes);
}
.constat-en-attente {
  border-left-color: var(--bordure);
  color: var(--texte-doux);
}
.constat-en-attente .icone { color: var(--texte-doux); }



/* --- BD-RAPPORT ---------------------------------------------------------- */

/* LES CINQ CHIFFRES, EN CARTES PLUTÔT QU'EN TABLEAU.
   Ce sont cinq réponses indépendantes, pas cinq lignes d'une même liste :
   un tableau les ferait lire de haut en bas alors qu'on vient en chercher
   une seule. `auto-fit` les range sur une ligne quand l'écran le permet et
   les empile sinon, sans point de rupture à maintenir. */
.cartes-chiffres {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1rem;
  margin: 1.25rem 0;
}

.carte-chiffre {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding: 1rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  /* La bande rouge des cartes du tableau de bord : c'est la même famille
     d'objet, et deux traitements pour un même rôle se lisent comme deux
     rôles. */
  border-top: 3px solid var(--rouge);
}

.libelle-chiffre {
  font-size: .82rem;
  color: var(--texte-doux);
}

/* Les chiffres d'argent s'alignent à l'œil : même chasse pour tous les
   caractères, sinon « 1 » et « 4 » ne tombent pas l'un sous l'autre d'une
   carte à l'autre. */
.valeur-chiffre {
  font-size: 1.45rem;
  font-variant-numeric: tabular-nums;
}

.recherche-periode .rangee-champs { align-items: flex-end; }

.raccourcis-periode {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin: .5rem 0 1.25rem;
}

/* Le raccourci qu'on vient de cliquer. Il se DISTINGUE sans changer de
   taille : un bouton qui grossit en devenant actif déplace ses voisins, et
   la rangée bouge sous le doigt. */
.bouton-actif {
  background: var(--rouge);
  border-color: var(--rouge);
  color: #fff;
}

/* --- DOUBLE AUTHENTIFICATION --------------------------------------------- */

/* Le champ du code. Chasse fixe et lettres espacées : six chiffres se
   relisent alors caractère par caractère, ce qui est exactement ce qu'on
   fait en les recopiant d'un téléphone. */
.champ-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.4rem;
  letter-spacing: .3em;
  text-align: center;
}

/* La clé à recopier à la main. Elle DOIT pouvoir se replier : trente-deux
   caractères ne tiennent pas sur un téléphone, et une clé coupée hors de
   l'écran ne se recopie pas. */
.cle-totp {
  display: inline-block;
  font-size: 1.05rem;
  letter-spacing: .12em;
  word-break: break-all;
  padding: .35rem .5rem;
  background: var(--fond-page);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
}

/* Le carré à scanner. `max-width` en pourcentage plutôt qu'une taille fixe :
   le SVG porte ses propres millimètres, et un carré plus large que l'écran
   d'un téléphone ne se scanne pas — il se scrolle. */
.carre-qr {
  margin: 1rem 0;
  max-width: 260px;
}
.carre-qr svg { width: 100%; height: auto; display: block; }

/* Les codes de secours. En colonnes, en chasse fixe, assez gros pour se
   recopier sur papier — c'est leur seul usage. */
.liste-codes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: .4rem 1rem;
  list-style: none;
  padding: 0;
  margin: 1rem 0;
}
.liste-codes code {
  font-size: 1.1rem;
  letter-spacing: .1em;
}

.etapes-inscription { margin: 0 0 1rem; padding-left: 1.2rem; }
.etapes-inscription li { margin-bottom: .35rem; }

/* ═══ BD-FAB : LE TABLEAU DE L'ATELIER ═════════════════════════════════════

   Un écran qu'on regarde en passant, les mains prises, souvent debout. Les
   cibles y sont plus grandes qu'ailleurs et les états se lisent à la couleur
   avant de se lire au texte.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Les quatre états, en onglets. Chacun porte son compte : un onglet qui ne
   dit pas combien il contient oblige à l'ouvrir pour savoir s'il y a
   quelque chose dedans. */
.onglets-etat {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 0 0 1rem;
}

.onglet {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .45rem .9rem;
  border-radius: var(--rayon);
  border: 1px solid var(--bordure);
  background: var(--fond-carte);
  color: var(--texte);
  text-decoration: none;
  font-size: .9rem;
  font-weight: 600;
}

.onglet:hover { border-color: var(--accent); }

/* L'onglet courant prend l'aplat rouge de la marque : c'est le même signal
   que la bande d'une carte, et on ne lit pas deux grammaires sur un écran. */
.onglet-actif {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-texte);
}
.onglet-actif .compte {
  background: var(--accent-texte);
  color: var(--accent);
  border-color: transparent;
}

/* Une ligne préparée s'efface sans disparaître.

   ⚠️ ELLE RESTE LISIBLE, et ce n'est pas un détail de goût : l'atelier
   revient dessus pour vérifier ce qui a été coché, et un gris trop pâle
   oblige à décocher pour relire. Le texte garde son contraste ; c'est le
   FOND qui recule. */
.ligne-prete { background: var(--fond); }
.ligne-prete td:nth-child(3) { text-decoration: line-through; opacity: .75; }

/* La case à cocher est un BOUTON, parce que cocher enregistre.

   Une vraie case laisserait croire qu'il reste à valider quelque part, et
   l'atelier repartirait sans avoir enregistré. Elle est large : on y clique
   avec un doigt, parfois ganté. */
.bouton-case {
  min-width: 2.4rem;
  min-height: 2.4rem;
  padding: 0;
  font-size: 1.4rem;
  line-height: 1;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--rayon);
  color: var(--texte);
  cursor: pointer;
}
.bouton-case:hover { border-color: var(--bordure); }
.bouton-case:focus-visible { outline: 2px solid var(--accent-lien); }

/* Une note d'atelier : la date en petit, le texte en grand, un filet à
   gauche comme les constats du reste du portail. */
.note-atelier {
  margin: .6rem 0;
  padding: .45rem .8rem;
  border-left: 3px solid var(--bordure);
  font-size: .92rem;
}

/* La bande des trois gestes de la fiche d'atelier.

   Un filet la détache de la saisie qui la précède : sans lui, AJOUTER et
   PRÊT se touchent, et ce sont deux gestes qui n'ont rien à voir. */
.actions-atelier {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .75rem;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--bordure);
}

/* PRÊT est le geste de la page, et il se voit.

   Plus grand que tout ce qui l'entoure — c'est la taille, pas la couleur,
   qui fait la différence : la couleur est déjà prise par la marque, et
   l'atelier travaille parfois avec des gants et une lumière rasante. */
.bouton-pret {
  padding: .7rem 2.2rem;
  font: inherit;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: .06em;
  color: #fff;
  background: var(--succes, #1f7a34);
  border: none;
  border-radius: 6px;
  cursor: pointer;
}
.bouton-pret:not([disabled]):hover { filter: brightness(.93); }
.bouton-pret:focus-visible { outline: 3px solid var(--accent-lien); }

/* La carte de revue d'une conversion : elle demande quelque chose, et elle
   le demande AVANT la liste. Le filet rouge est celui des refus — c'est le
   même registre : on ne passe pas outre. */
.carte-revue { border: 2px solid var(--accent); }

/* Une ligne dont le croquis ne donnait pas la quantité. Le fond signale
   qu'il manque quelque chose sans crier : le relecteur les prend une par
   une, il n'a pas besoin d'être alarmé six fois. */
.ligne-incomplete { background: var(--erreur-fond); }

/* --- La fabrication bloquée par une mesure -------------------------------
   « La commande doit être bloquée dans BD-FAB jusqu'au changement. »

   Le cadre d'erreur et non l'accent de la revue : « à relire » appelle
   quelqu'un d'ici, « bloquée » attend un croquis corrigé qui vient
   d'ailleurs. Deux attentes différentes doivent se distinguer de loin, sinon
   la fabrication bloquée se range dans la pile de celles qu'on peut
   relâcher. */

.carte-bloquee {
  border: 2px solid var(--erreur-bordure);
  background: var(--erreur-fond);
}

.mesures-a-corriger {
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}

.mesures-a-corriger li {
  padding: .5rem 0;
  border-bottom: 1px solid var(--erreur-bordure);
}

/* La ligne du croquis, telle que le dessinateur l'a écrite : c'est elle
   qu'on cherche des yeux sur le dessin. */
.ligne-du-croquis {
  margin: 0 0 .25rem;
  font-weight: 600;
}

.alerte-sourde { color: var(--erreur-texte); font-weight: 600; }

/* La ligne en cours de revue : quantité, unité, désignation, deux boutons,
   sur une seule ligne. On corrige vite ou on ne corrige pas. */
.rangee-revue {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
}
.rangee-revue input[type="number"] { width: 5rem; }
.rangee-revue input[type="text"] { flex: 1 1 8rem; min-width: 6rem; }
.rangee-revue input {
  padding: .3rem .4rem;
  font: inherit;
  color: inherit;
  background: var(--fond);
  border: 1px solid var(--bordure);
  border-radius: 4px;
}

/* Ce que le dessinateur a écrit à côté de la liste : les renvois, les
   consignes. Court, et jamais jeté — « VOIR SUITE P.2 » est la différence
   entre une rampe complète et une demi-rampe. */
.notes-croquis {
  margin: .4rem 0 .8rem;
  padding-left: 1.2rem;
  font-size: .9rem;
  color: var(--texte-doux);
}

/* LE COUPLAGE ENTRE LA BOÎTE DE L'ATELIER ET LA FICHE DU FOURNISSEUR.
   Vert quand il tient, rouge quand il est rompu : c'est un état, pas un
   conseil, et il se lit d'un coup d'œil sans lire la phrase. */
.couplage-atelier {
  border-left: 4px solid;
  padding: .45rem .7rem;
  margin: .5rem 0 .9rem;
  border-radius: 0 3px 3px 0;
  font-size: .9rem;
}
.couplage-tenu {
  border-left-color: #4caf50;
  background: color-mix(in srgb, #4caf50 8%, transparent);
}
.couplage-rompu {
  border-left-color: var(--rouge, #FF0000);
  background: color-mix(in srgb, #FF0000 8%, transparent);
}

/* D'OÙ SORT LA QUANTITÉ D'UNE LIGNE DÉDUITE.
   Discrète, sur la même ligne que la désignation : elle se lit quand on la
   cherche, sans encombrer une liste qu'on parcourt en marchant. */
.raison-ligne {
  display: block;
  font-size: .78rem;
  opacity: .62;
  margin: .1rem 0 0;
}

/* --- La saisie d'une section de rampe, sur la fiche de fabrication ------- */

/* ⚠️ UN PLIAGE PLUTÔT QU'UNE PAGE DE PLUS. « Gardons ça simple pour le moment
   et pas de gestion avec une page de plus » — le client. Quatorze champs par
   section, dépliés d'office sur une rampe de six côtés, feraient une fiche
   qu'on ne peut plus lire ; `<details>` les range sans rien déplacer. */
.pliage-section {
  margin: .75rem 0 0;
}

.pliage-section > summary {
  cursor: pointer;
  font-size: .85rem;
  font-weight: 500;
  color: var(--accent-lien);
  padding: .35rem 0;
}

.saisie-section {
  display: flex;
  flex-direction: column;
  gap: .9rem;
  padding: .9rem 0 .25rem;
}

/* ⚠️ LES BOUTONS SE SÉPARENT, ET POUR LA RAISON DÉJÀ MESURÉE SUR LA BARRE
   D'ACTIONS. « ENREGISTRER » et « RETIRER » côte à côte et de même taille,
   dans une main gantée, c'est un clic de travers qui attend son tour : le
   second recule en bordure. */
.actions-saisie {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem 1.5rem;
  align-items: center;
}

/* --- La liste de matériel en revue : une colonne par champ -------------- */

/* ⚠️ LES CHAMPS REMPLISSENT LEUR CELLULE, ET LA LARGEUR SE DÉCIDE SUR LA
   COLONNE. Avant, les trois champs vivaient dans une case unique, rangés par
   un flex : aucun titre de colonne ne pouvait s'aligner dessus. « Il faudrait
   des titres de colonne pour savoir c'est quoi ? » — le client. */
.tableau-materiel input {
  width: 100%;
  box-sizing: border-box;
  padding: .3rem .4rem;
  font: inherit;
  color: inherit;
  background: var(--fond);
  border: 1px solid var(--bordure);
  border-radius: 4px;
}

.tableau-materiel input[type="number"] { min-width: 4rem; }

/* Chaque titre au bord GAUCHE de son champ : centré, il flotte au-dessus
   d'une colonne large et ne désigne plus rien de précis. */
.tableau-materiel thead th { text-align: left; }

/* La quantité et l'unité sont courtes ; la désignation prend ce qui reste. */
.tableau-materiel th:nth-child(2),
.tableau-materiel td:nth-child(2) { width: 6rem; }
.tableau-materiel th:nth-child(3),
.tableau-materiel td:nth-child(3) { width: 7rem; }

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

.cellule-actions .bouton-secondaire + .bouton-secondaire { margin-left: .4rem; }

/* ⚠️ AU TÉLÉPHONE, LA LIGNE SE PLIE ET CHAQUE CASE PORTE SON TITRE.
   Cinq colonnes dont trois champs de saisie ne tiennent pas dans 390 px : le
   tableau déborderait, ou les champs tomberaient à deux caractères de large.
   Mesuré, pas supposé. L'en-tête disparaît alors, et `data-colonne` le
   remplace case par case — sans quoi on retrouverait exactement le défaut
   qu'on corrige, trois boîtes nues. */
@media (max-width: 640px) {
  .tableau-materiel thead { display: none; }
  /* ⚠️ ET UN TRAIT ENTRE LES RANGÉES, parce que les cases ont perdu le leur.
     Mesuré à l'écran : cinq fiches empilées sans séparation, et on ne voit
     plus où finit une ligne de matériel et où commence la suivante. */
  .tableau-materiel tbody tr {
    display: block;
    padding: .6rem 0;
    border-bottom: 1px solid var(--bordure);
  }
  .tableau-materiel tbody tr:last-child { border-bottom: none; }
  .tableau-materiel tbody td { display: block; width: auto; border: none; }
  .tableau-materiel th:nth-child(2),
  .tableau-materiel td:nth-child(2),
  .tableau-materiel th:nth-child(3),
  .tableau-materiel td:nth-child(3) { width: auto; }
  .tableau-materiel tbody td[data-colonne]::before {
    content: attr(data-colonne);
    display: block;
    font-size: .75rem;
    color: var(--texte-doux);
    margin-bottom: .15rem;
  }
  .cellule-actions { text-align: left; padding-top: .5rem; }
}


/* ══ LA LISTE DE PRIX D'UN PROSPECT ════════════════════════════════════
   Voir `prospect/prix.py`. Le tableau du bas est CE QUE LE CLIENT LIRA :
   il sert à l'écran et dans le courriel, donc il ne s'appuie sur rien
   qu'une boîte de réception ne saurait rendre — pas de grille, pas de
   variable. Les règles ci-dessous n'habillent que l'écran. */
.liste-au-client {
  width: 100%;
  border-collapse: collapse;
}
.liste-au-client th,
.liste-au-client td {
  border-bottom: 1px solid var(--bordure);
  padding: .4rem .6rem;
}
.liste-au-client td:nth-child(n+4) { font-variant-numeric: tabular-nums; }

/* Les conditions de vente, sous le tableau de la liste. */
.notes-au-client {
  margin: .75rem 0;
  padding-left: 1.2rem;
}

/* Une note à cocher sur la fiche : la case et son texte sur une ligne,
   le texte replié proprement sous lui-même quand il est long. */
.note-a-cocher {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  margin: .35rem 0;
}

/* Le filtre du choix des produits : en ligne, au-dessus du tableau. */
.filtre-produits {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  align-items: flex-end;
  margin: .75rem 0;
}

/* Une barre de boutons sous un formulaire. */
.barre-boutons {
  display: flex;
  /* ⚠️ À LA LIGNE QUAND ÇA NE TIENT PAS. À 390 px, les trois boutons de
     « Corriger ce rapprochement » s'écrasaient en colonnes de 120 px de
     haut, au libellé coupé. */
  flex-wrap: wrap;
  gap: .75rem;
  margin: 1rem 0;
}

/* Ce qu'il faut savoir sans que rien ne soit cassé — un produit retenu
   qui n'a pas de prix, une fiche sans adresse courriel. */
.avertissement {
  border-left: 3px solid var(--accent);
  padding: .5rem .75rem;
  margin: .75rem 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Des lignes à trancher, des replis, un tableau qui compare

   Des réglages qu'on ajoute, qu'on corrige ou qu'on retire ligne par ligne
   (le courriel, les paramètres de BD-SoumInstall), et le tableau qui met
   Acomba à côté du dossier dans BD-Commande.
   ═══════════════════════════════════════════════════════════════════════ */

/* Un geste qui se déclenche à la main : un bouton, une ligne. */
.ligne-geste { margin: 0 0 .5rem; }

/* Une ligne qu'on tranche — un formulaire en cadre, ses champs côte à
   côte. */
.ligne-rapprochement {
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  padding: .75rem;
  margin: 0 0 .75rem;
  display: flex;
  flex-wrap: wrap;
  gap: .5rem .75rem;
  align-items: flex-end;
}
.ligne-rapprochement > p { flex: 1 1 100%; margin: 0; }

/* Deux colonnes de valeurs, ligne par ligne, sous une tête qui reste en
   haut quand on descend. */
.tableau-comparaison {
  border-collapse: collapse;
  width: 100%;
  font-size: .9rem;
}
.tableau-comparaison th,
.tableau-comparaison td {
  padding: .4rem .6rem;
  border-bottom: 1px solid var(--bordure);
  text-align: left;
  vertical-align: top;
}
.tableau-comparaison thead th {
  position: sticky;
  top: 0;
  background: var(--bande-sous-section);
  color: var(--bande-sous-section-texte);
  white-space: nowrap;
}
.tableau-comparaison tbody th { font-weight: 400; }
.tableau-comparaison .nombre {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ⚠️ APRÈS `.choix-actif` PLUS HAUT, ET IL L'ÉPAISSIT : c'est ce gras-ci
   que porte l'option choisie de la recherche du catalogue de BD-SoumInstall
   (`soumissions/catalogue.js`). Le retirer changerait cet écran-là. */
.choix-actif { font-weight: 700; text-decoration: none; }

/* Corriger ou retirer un réglage : replié par défaut. Des formulaires
   dépliés sur toute une liste feraient un écran qu'on ne lit plus. */
.correction { margin: .4rem 0; }
.correction > summary { cursor: pointer; color: var(--accent-lien); }

/* Ce que Raf ajoute de sa main, et qui devient une règle.

   ⚠️ LE TEXTE AUSSI PREND TOUTE LA LARGEUR, ET IL NE LA PRENAIT PAS :

       « Élargir les champs par défaut, car c'est illisible. » — le
         client, devant des règles apprises dans une boîte de huit
         centimètres.

   La règle ne visait que `input`. Un `textarea` posé dans une ligne en
   flex retombe sur sa largeur par défaut — une vingtaine de
   caractères —, et une règle de trois lignes s'y lit par le trou d'une
   serrure. */
.mot-de-raf { display: block; margin: .4rem 0; flex: 1 1 100%; }
.mot-de-raf input,
.mot-de-raf textarea { width: 100%; }

/* ⚠️ APRÈS `.aide` PLUS HAUT, ET C'EST CETTE MARGE-CI QUE PORTE LA PHRASE
   SOUS LA CONNEXION (« Mot de passe oublié ? ») et sous le refus hors du
   réseau. La retirer déplacerait ces deux écrans. */
.aide { margin: 0 0 .6rem; }

/* Une ligne qu'on ajoute, en retrait sous celles qui existent déjà. */
.ligne-variante {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem .75rem;
  align-items: flex-end;
  padding: .5rem 0 .5rem .75rem;
  border-left: 2px solid var(--bordure);
  margin: .35rem 0;
}
.ligne-variante > p { flex: 1 1 100%; margin: 0; }

/* ═══ UNE PASTILLE QUI SE COCHE ════════════════════════════════════════════

       « Est-ce possible de rendre cette colonne activable dans le tableau
         de bord ? » — le client.

   Un bouton qui garde l'allure de la pastille : la colonne se lit comme
   avant, et le survol dit qu'elle se coche. Un formulaire par cellule, sans
   JavaScript — un clic, un envoi, et la page revient. */
.bascule { display: inline; margin: 0; }

.bouton-pastille {
  padding: 0;
  border: 0;
  background: none;
  line-height: 0;
  cursor: pointer;
}

.bouton-pastille:hover .pastille {
  box-shadow: 0 0 0 2px var(--bordure);
}

.bouton-pastille:focus-visible {
  outline: 2px solid var(--accent-lien);
  outline-offset: 2px;
  border-radius: 50%;
}

/* LA BARRE DE FILTRES ET LE BOUTON « FERMER » DU TABLEAU DE BORD.

       « Il faut une barre de filtrage et de recherche. » — le client.

   Chaque étiquette tient SON champ sur la même ligne : la règle globale de
   `label` (un bloc, en gras) empilait « Installation » au-dessus de sa
   liste, et trois choix prenaient six lignes. `.recherche` porte déjà le
   `wrap` qui les fait passer l'un sous l'autre sur un téléphone. */
.filtres-tableau { align-items: center; margin: .75rem 0 1rem; }
.filtres-tableau label {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  margin: 0;
  font-weight: normal;
}
.filtres-tableau input[type="search"] { width: 9rem; }

/* Au bout de chaque rangée : la hauteur d'une pastille, pas d'un
   formulaire — sinon la colonne du bouton épaissit tout le tableau. */
.bouton-rangee { padding: .15rem .6rem; font-size: .8rem; }
/* « Fermer » est un lien qui demande d'abord : il garde l'allure du bouton.
   ⚠️ `.tableau a` colore, épaissit et souligne les liens du tableau : il
   faut le nommer pour l'emporter sur lui. */
.tableau a.bouton-rangee { display: inline-block; color: var(--texte);
                           font-weight: inherit; text-decoration: none; }
.tableau a.bouton-rangee:hover { text-decoration: none; }

/* ═══ LES NOTES AU SURVOL DE L'ŒIL ═══════════════════════════════════════

       « Est-ce possible d'avoir une fenêtre activable par "hover" qui
         montre les notes au dossier ? » — le client.

   ⚠️ `position: fixed` : hors du cadre qui fait défiler le tableau — en
   `absolute`, elle y serait coupée au bord. Ses coordonnées viennent de
   `portail/notes_survol.js` à l'ouverture (sous l'œil, ou au-dessus près
   du bas de l'écran) ; sans coordonnées, le navigateur la posait sans
   compter le défilement de la page, et elle sortait de l'écran. Voir
   `commandes/_notes_survol.html`. */
.survol-notes {
  display: inline-block;
  margin-left: .35rem;
  vertical-align: middle;
}

.declencheur-notes {
  display: inline-flex;
  color: var(--texte-doux);
  cursor: help;
  border-radius: 4px;
}

.declencheur-notes:hover { color: var(--texte); }

.declencheur-notes:focus-visible {
  outline: 2px solid var(--accent-lien);
  outline-offset: 2px;
}

.fenetre-notes {
  display: none;
  position: fixed;
  z-index: 50;
  width: 30rem;
  max-width: calc(100vw - 2rem);
  max-height: 60vh;
  overflow: auto;
  background: var(--fond-carte);
  color: var(--texte);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  box-shadow: var(--ombre-fenetre);
  text-align: left;
  white-space: normal;
  font-weight: normal;
}

.survol-notes:hover .fenetre-notes,
.survol-notes:focus-within .fenetre-notes { display: block; }

.titre-fenetre-notes {
  display: block;
  padding: .6rem 1rem;
  background: var(--fond);
  border-bottom: 1px solid var(--bordure);
  font-weight: 600;
}

.note-survol {
  display: block;
  padding: .6rem 1rem;
  border-bottom: 1px solid var(--bordure);
}

.note-survol:last-child { border-bottom: 0; }

.texte-note { display: block; margin-top: .2rem; }

/* « ENVOYER CE COURRIEL AU CLIENT ? » — livraison gérée, commande prête.
   L'aperçu EST le courriel : il garde ses retours de ligne (`pre-wrap`), mais
   pas la police à chasse fixe d'un <pre> — c'est un courriel qu'on relit,
   pas du code. Il défile dans son cadre plutôt que de pousser les boutons
   hors de la fenêtre. */
.formulaire-avis-client { max-width: 640px; }
.apercu-courriel {
  padding: .75rem 1rem;
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  background: var(--fond);
}
.apercu-courriel p { margin: 0 0 .5rem; }
.apercu-courriel pre {
  margin: 0;
  max-height: 40vh;
  overflow: auto;
  font-family: inherit;
  font-size: .9rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.avis-en-attente { margin: .75rem 0 0; }

/* Un bouton qui paraît sans pouvoir servir — et qui dit pourquoi dans son
   `title` et la phrase qui le suit. « Un écran qui dit POURQUOI vaut mieux
   qu'un écran qui dit NON. » (BD-Verre, « Exporter dans Acomba » sans
   projet enregistré.) */
.bouton-inactif { opacity: .55; cursor: not-allowed; }

/* ═══════════════════════════════════════════════════════════════════════════
   Administration › Intelligence artificielle

   « Il faudrait migrer tous les paramètres d'API d'IA dans
     Administration. » — le client.

   ⚠️ LE RÉSULTAT D'UN ESSAI SE PORTE SUR `data-bon`, PAS SUR UNE CLASSE
   CALCULÉE : `portail.tests.test_styles` ne devine pas une classe écrite
   avec `{% if %}`. */
.reglages-ia .ia-etat {
  border-left: 3px solid var(--succes);
  padding: .5rem .75rem;
  margin: 0 0 .75rem;
}
.reglages-ia .ia-gestes {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  margin: .9rem 0 .4rem;
}
.reglages-ia .ia-essais {
  list-style: none;
  margin: .75rem 0 0;
  padding: 0;
}
.reglages-ia .ia-essais li {
  border-left: 3px solid var(--succes);
  padding: .35rem .75rem;
  margin: .35rem 0;
  overflow-wrap: anywhere;
}
.reglages-ia .ia-essais li[data-bon="non"] {
  border-left-color: var(--erreur-bordure);
  background: var(--erreur-fond);
  color: var(--erreur-texte);
}
.reglages-ia .ia-usages {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 26rem), 1fr));
  gap: 1rem;
  margin-bottom: 1rem;
}
.reglages-ia .ia-usage {
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  padding: .75rem 1rem 1rem;
  margin: 0;
  min-width: 0;
}
.reglages-ia .ia-usage legend {
  font-weight: 600;
  padding: 0 .35rem;
}
.reglages-ia .ia-usage .discret { margin: 0 0 .75rem; }
/* ⚠️ RETIRER EST UN GESTE EN DEUX TEMPS : un repli fermé, puis une case
   obligatoire. Loin des autres boutons, jamais un faux clic à côté
   d'« Essayer ». */
.reglages-ia .ia-retirer {
  margin: 1rem 0 0;
  border-top: 1px solid var(--bordure);
  padding-top: .75rem;
}
.reglages-ia .ia-retirer > summary {
  cursor: pointer;
  color: var(--erreur-texte);
  font-weight: 600;
}
.reglages-ia .ia-retirer form {
  border-left: 3px solid var(--erreur-bordure);
  background: var(--erreur-fond);
  padding: .5rem .75rem .75rem;
  margin: .6rem 0 0;
}
.reglages-ia .ia-retirer p { margin: 0 0 .6rem; }
.reglages-ia .ia-confirmer {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  margin: 0 0 .75rem;
}
.reglages-ia .ia-confirmer input { margin-top: .2rem; flex: none; }

/* La même ligne dans chaque application : « Clé et modèles d'IA : réglés
   dans Administration › Intelligence artificielle ». */
.ou-se-regle-l-ia { overflow-wrap: anywhere; }

/* --- Courriels envoyés (Administration) ---------------------------------
   La copie TXT de chaque envoi. Le texte garde ses lignes telles qu'écrites,
   mais une adresse ou un lien de 200 caractères se replie plutôt que de
   pousser la page de côté sur un téléphone. */
.recherche-courriels { align-items: center; }
.recherche-courriels label { display: inline-flex; align-items: center; gap: .35rem; margin: 0; }
.recherche-courriels input[type="search"],
.recherche-courriels input[type="date"] {
  padding: .45rem .6rem;
  background: var(--fond);
  color: var(--texte);
  border: 1px solid var(--bordure);
  border-radius: 6px;
  font: inherit;
}
.recherche-courriels input[type="search"] { flex: 1 1 14rem; min-width: 0; }
/* La durée de conservation : un nombre de jours, pas une phrase. */
.conservation-courriels { align-items: center; margin-top: 0; }
.conservation-courriels label { display: inline-flex; align-items: center; gap: .35rem; margin: 0; font-weight: normal; }
.conservation-courriels input[type="number"] {
  width: 6rem;
  padding: .35rem .5rem;
  background: var(--fond);
  color: var(--texte);
  border: 1px solid var(--bordure);
  border-radius: 6px;
  font: inherit;
}
.recherche-courriels .case-echecs input { width: auto; margin: 0; }
.courriels-envoyes .courriel-sujet,
.courriels-envoyes .courriel-destinataire { white-space: normal; overflow-wrap: anywhere; min-width: 10rem; }
.courriel-envoye .entete-liste h1 { overflow-wrap: anywhere; }
/* Le nom du fichier ne se coupe qu'aux tirets : « 13h38m44s_jean.tremblay@
   videotron.ca_Liste- » poussait la page de côté à 390 px. */
.courriel-envoye p, .courriel-chemin { overflow-wrap: anywhere; }
.courriel-actions { display: inline-flex; gap: .5rem; flex-wrap: wrap; }
.courriel-txt {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: .85rem;
  line-height: 1.45;
  padding: 1rem;
  background: var(--fond-carte);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  max-width: 100%;
}
