/* ============================================================
   Guiter Ops · components.css
   Kit de composants du design system (Lot A).
   Ne consomme QUE les variables de tokens.css.
   Chargé par les gabarits de base : ne jamais le recharger.
   ============================================================ */

/* ==================== BOUTONS ==================== */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--esp-2);
  min-height: var(--cible-min);
  padding: 0 var(--esp-5);
  border: 1px solid transparent;
  border-radius: var(--rayon-bouton);
  font-family: var(--police);
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  color: var(--g-encre);
  background: var(--g-blanc);
  cursor: pointer;
  text-decoration: none;
  overflow: hidden;
  transition: transform var(--t-rapide), box-shadow var(--t-rapide),
    background-color var(--t-rapide), border-color var(--t-rapide),
    color var(--t-rapide), opacity var(--t-rapide);
  -webkit-tap-highlight-color: transparent;
}

.btn:focus-visible {
  outline: none;
  box-shadow: var(--anneau-focus);
}

.btn:active {
  transform: translateY(1px) scale(.99);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: .55;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
  pointer-events: none;
}

/* Boutons pleins : aplats stricts, le survol fonce simplement la teinte. */
.btn-primaire {
  background: var(--g-vert-600);
  color: var(--g-blanc);
}

.btn-primaire:hover {
  background: var(--g-vert-800);
}

.btn-orange {
  background: var(--g-orange-600);
  color: var(--g-blanc);
}

.btn-orange:hover {
  background: var(--g-orange-500);
}

.btn-fantome {
  background: transparent;
  border-color: var(--g-filet);
  color: var(--g-encre-2);
}

.btn-fantome:hover {
  border-color: var(--g-vert-600);
  color: var(--g-vert-600);
  background: var(--g-vert-100);
}

/* État chargement : spinner intégré, libellé conservé, double clic impossible */
.btn.chargement {
  pointer-events: none;
  opacity: .85;
}

.btn.chargement::before {
  content: "";
  width: 16px;
  height: 16px;
  flex: none;
  border: 2px solid rgba(255, 255, 255, .35);
  border-top-color: var(--g-blanc);
  border-radius: 50%;
  animation: btn-rotation 700ms linear infinite;
}

.btn-fantome.chargement::before {
  border-color: rgba(27, 122, 61, .25);
  border-top-color: var(--g-vert-600);
}

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

/* ==================== CHAMPS (étiquette flottante) ==================== */

.champ {
  position: relative;
  margin-bottom: var(--esp-2);
}

.champ-saisie {
  width: 100%;
  min-height: 56px;
  padding: 22px var(--esp-4) 6px;
  font-family: var(--police);
  font-size: 15px;
  color: var(--g-encre);
  background: var(--g-blanc);
  border: 1.5px solid var(--g-filet);
  border-radius: var(--rayon-champ);
  transition: border-color var(--t-rapide), box-shadow var(--t-rapide);
}

.champ-saisie::placeholder {
  color: transparent; /* le placeholder ne sert qu'à piloter l'étiquette */
}

.champ-etiquette {
  position: absolute;
  top: 17px;
  left: var(--esp-4);
  font-size: 15px;
  color: var(--g-muet);
  pointer-events: none;
  transform-origin: left top;
  transition: transform var(--t-rapide), color var(--t-rapide);
}

/* L'étiquette se hausse au focus ou quand le champ est rempli */
.champ-saisie:focus + .champ-etiquette,
.champ-saisie:not(:placeholder-shown) + .champ-etiquette {
  transform: translateY(-11px) scale(.78);
}

.champ-saisie:focus + .champ-etiquette {
  color: var(--g-vert-600);
}

/* Focus : bordure verte nette, anneau doux. Aucun dégradé. */
.champ-saisie:focus {
  outline: none;
  border-color: var(--g-vert-600);
  box-shadow: 0 0 0 3px rgba(22, 130, 18, .13);
}

/* Erreur : rouge lisible, fondu, SANS déplacement de mise en page
   (l'espace du message est réservé en permanence). */
.champ-erreur {
  display: block;
  min-height: 20px;
  margin: var(--esp-1) var(--esp-1) 0;
  font-size: 13px;
  color: var(--g-rouge-erreur);
  opacity: 0;
  transition: opacity var(--t-moyen);
}

.champ.est-invalide .champ-erreur,
.champ-erreur.est-visible {
  opacity: 1;
}

.champ.est-invalide .champ-saisie {
  border-color: var(--g-rouge-erreur);
}

.champ.est-invalide .champ-etiquette {
  color: var(--g-rouge-erreur);
}

/* ==================== CARTES ==================== */

.carte {
  background: var(--g-blanc);
  border: 1px solid var(--g-filet);
  border-radius: var(--rayon-carte);
  box-shadow: var(--ombre-douce);
  padding: var(--esp-5);
}

/* Verre dépoli · écrans publics (base_public) */
.carte-verre {
  background: rgba(255, 255, 255, .10);
  -webkit-backdrop-filter: blur(18px) saturate(130%);
  backdrop-filter: blur(18px) saturate(130%);
  border: 1px solid rgba(255, 255, 255, .22);
  border-top-color: rgba(255, 255, 255, .38); /* liseré lumineux fin */
  border-radius: var(--rayon-carte);
  box-shadow: var(--ombre-verre);
  padding: var(--esp-6);
  color: var(--g-blanc);
}

.carte-verre .champ-saisie {
  background: rgba(255, 255, 255, .92);
}

/* ==================== BADGES DE STATUT ==================== */
/* Pastille en couleur, texte TOUJOURS en encre · jamais la couleur seule. */

.badge-statut {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-2);
  padding: 4px 12px;
  border: 1px solid var(--g-filet);
  border-radius: 999px;
  background: var(--g-blanc);
  font-size: 13px;
  font-weight: 600;
  color: var(--g-encre);
  white-space: nowrap;
}

.badge-statut::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
  background: var(--statut-gris);
}

.statut-gris::before { background: var(--statut-gris); }
.statut-bleu::before { background: var(--statut-bleu); }
.statut-jaune::before { background: var(--statut-jaune); }
.statut-orange::before { background: var(--statut-orange); }
.statut-vert::before { background: var(--statut-vert); }
.statut-rouge::before { background: var(--statut-rouge); }

/* ==================== JETON DE FILIALE ==================== */
/* Logo s'il existe, pastille de couleur sinon, NOM toujours.
   Même doctrine que les badges de statut : la couleur ACCOMPAGNE le
   libellé, elle ne le remplace jamais · une page imprimée en noir et
   blanc perd la couleur, jamais le texte. Le référentiel le dit déjà de
   la couleur d'accent (« elle accompagne toujours le nom de la filiale,
   jamais seule »).
   Il vit ICI et non dans le partiel d'équipe : depuis le multi-filiales
   (03/08/2026) la filiale s'affiche aussi sur la fiche RH, et un style
   recopié dans deux gabarits diverge au premier ajustement.
   Gabarit : ui/partials/filiale_jeton.html */

.filiale-jeton {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px 2px 6px;
  border: 1px solid var(--g-filet);
  border-radius: 999px;
  background: var(--g-blanc);
  font-size: 12px;
  font-weight: 700;
  color: var(--g-encre);
  white-space: nowrap;
}

.filiale-pastille {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
}

/* `contain` interdit tout recadrage : un logo tronqué n'est plus le logo. */
.filiale-logo {
  height: 14px;
  width: auto;
  max-width: 34px;
  object-fit: contain;
  flex: none;
}

/* ==================== RATTACHEMENT D'UNE PERSONNE ==================== */
/* Filiales servies + transverse · gabarit ui/partials/champ_rattachement.html.
   Il occupe TOUTE la largeur de la grille : une rangée d'options ne tient
   pas dans une demi-colonne, et un rattachement coupé en deux se lit mal
   alors que c'est lui qui s'imprime sur la carte.

   CHAQUE OPTION EST UNE CIBLE ENTIÈRE, pas une case suivie d'un texte :
   on clique la pastille comme le nom. Et surtout, l'état coché SE VOIT ·
   la case seule est un point de 13 px qu'on cherche des yeux sur quatre
   filiales alignées, alors qu'un rattachement faux part s'imprimer.
   L'état passe par `input:checked + .filiale-jeton`, un sélecteur de
   frère qui marche partout, plutôt que `:has()` que les WebView de
   terrain ne connaissent pas toutes. */

.form-groupe-large { grid-column: 1 / -1; }

.rattachement-cases {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 6px;
}

.rattachement-cases label {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
  padding: 7px 14px 7px 11px;
  border: 1px solid var(--g-filet);
  border-radius: 999px;
  background: var(--g-blanc);
  transition: border-color .15s ease, background .15s ease;
}

.rattachement-cases label:hover { border-color: #C9C8C2; }

/* La case garde sa taille de case · voir la note de theme.css sur
   `.form-groupe input`, qui l'étirait à toute la ligne. */
.rattachement-cases input[type="checkbox"],
.rattachement-transverse input[type="checkbox"] {
  width: 17px;
  height: 17px;
  flex: none;
  margin: 0;
  accent-color: var(--g-vert-600);
}

/* Le jeton perd sa bordure ici : la pilule de l'option la porte déjà, et
   deux cadres emboîtés font un bouton dans un bouton. */
.rattachement-cases .filiale-jeton {
  border: 0;
  padding: 0;
  background: none;
  font-size: 13.5px;
}

/* COCHÉ · l'état se voit à DEUX niveaux, et c'est délibéré.
   `accent-color` colore la case elle-même et le libellé passe en gras :
   ça marche partout, y compris sur les WebView de terrain les plus
   anciennes. `:has()` ajoute par-dessus la pilule verte, là où il est
   connu. Le second est un confort ; le premier est la garantie. */
.rattachement-cases input[type="checkbox"]:checked + .filiale-jeton {
  color: var(--g-vert-900);
  font-weight: 800;
}

.rattachement-cases label:has(input:checked) {
  border-color: var(--g-vert-600);
  background: var(--g-vert-100);
}

.rattachement-cases label:focus-within,
.rattachement-transverse:focus-within {
  outline: 2px solid var(--g-vert-600);
  outline-offset: 2px;
}

/* Le TRANSVERSE est une SORTIE de la règle, pas une filiale de plus. Il
   est donc détaché, précédé d'un « ou » : le mêler aux autres laisserait
   croire qu'on le coche EN PLUS d'une filiale, alors qu'il dit justement
   qu'aucune ne s'applique. */
.rattachement-ou {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 16px 0 12px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--g-muet);
}

.rattachement-ou::before,
.rattachement-ou::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--g-filet);
}

.rattachement-transverse {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 12px 14px;
  border: 1px solid var(--g-filet);
  border-radius: 12px;
  background: var(--g-blanc);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}

.rattachement-transverse:hover { border-color: #C9C8C2; }
.rattachement-transverse input[type="checkbox"] { margin-top: 2px; }

.rattachement-transverse:has(input:checked) {
  border-color: var(--g-vert-600);
  background: var(--g-vert-100);
}

.rattachement-transverse input:checked ~ .corps .titre {
  color: var(--g-vert-900);
}

.rattachement-transverse .titre {
  display: block;
  font-size: 14px;
  font-weight: 700;
  color: var(--g-encre);
}

.rattachement-transverse .detail {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--g-muet);
}

/* Régime consultation « Lecture » */
.badge-lecture {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px dashed var(--g-muet);
  background: var(--g-creme);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--g-encre-2);
}

/* Pictogramme « oeil » tracé en SVG (masque teinté par currentColor) :
   cohérent avec le jeu ui_icones, jamais d'émoji dans l'interface. */
.badge-lecture::before {
  content: "";
  width: 13px;
  height: 13px;
  flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z'/><circle cx='12' cy='12' r='3'/></svg>") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z'/><circle cx='12' cy='12' r='3'/></svg>") center / contain no-repeat;
}

/* ==================== RÉGIME DE LECTURE ==================== */
/*
   « est-lecture-seule » est posé sur <body> par ui/base.html quand le
   régime de la zone servie est CONSULTATION (RG-2.08). Une seule classe,
   127 écrans.

   CE QUE CETTE FEUILLE FAIT · elle RETIRE le geste, elle ne le protège
   pas. Le refus est serveur (apps/core/access/middleware.py, seconde
   porte) ; ici on évite seulement de montrer un bouton qui rendrait un
   403. Un masquage pris pour une serrure est la faute classique · elle
   n'est pas commise ici, les deux existent.

   POURQUOI CES SÉLECTEURS-LÀ, mesurés sur le dépôt le 25/08/2026 :
   · 375 formulaires portent method="post" et pas un de moins · un
     formulaire qui poste EST une écriture, sans exception dans ce dépôt ;
     les 57 formulaires de filtre postent en GET et restent donc entiers ;
   · .btn-primaire (354 emplois) et .btn-orange sont les boutons d'ACTION
     du système : 215 sont des <button> de soumission, 139 des liens vers
     un écran de création ou d'ouverture (…_creer, …_ouvrir, …_attribuer).
     .btn et .btn-fantome, eux, portent la navigation et restent visibles.

   `data-action` COMPLÈTE LE JEU · 155 liens du dépôt mènent à un écran de
   création ou d'ouverture tout en portant le style discret `.btn-fantome`
   (« + Nouveau site », « Dossier d'assurance », « Saisir la récolte »). Les
   reclasser en `.btn-primaire` aurait changé leur apparence POUR TOUT LE
   MONDE, alors que le sujet ne concerne qu'un régime : ils portent donc un
   attribut, que ce sélecteur lit et que rien d'autre ne voit.

   L'ÉCHAPPATOIRE · `data-lecture-ok` sur ce qui doit rester visible malgré
   le régime : le retrait de portée du fil d'Ariane, le parcours du
   DEMANDEUR (déposer une demande de pièce, exprimer un besoin · ouvert par
   la case `peut_demander`, pas par le régime) et le bouton « Relancer » de
   l'assistanat, seul geste d'écriture que la lecture seule conserve.
   Elle est explicite et se relit : une exception qui se déclare vaut
   mieux qu'un sélecteur qui devine.

   Tout cela ne vaut QUE dans .zone-contenu · la topbar garde son
   sélecteur de portée, sa recherche et sa cloche, qui ne sont pas des
   écritures de la zone. */

body.est-lecture-seule .zone-contenu form[method="post" i]:not([data-lecture-ok]),
body.est-lecture-seule .zone-contenu .btn-primaire:not([data-lecture-ok]),
body.est-lecture-seule .zone-contenu .btn-orange:not([data-lecture-ok]) {
  display: none !important;
}

/* `data-action` VAUT PARTOUT, et pas seulement dans le contenu · les
   raccourcis de la barre latérale (« Nouveau dossier d'achat », « Nouveau
   fournisseur ») mènent au même refus, et ils sont les PREMIERS que l'œil
   trouve. Mesuré à l'écran : la zone des Achats en consultation rendait un
   contenu propre au-dessus d'une barre qui offrait deux créations.

   L'attribut se pose à la main, jamais par une classe de style : il ne
   peut donc pas s'étendre par accident à la topbar, qui garde son
   sélecteur de portée, sa recherche et sa cloche. */
body.est-lecture-seule [data-action]:not([data-lecture-ok]) {
  display: none !important;
}

/* PAS DE BADGE DE RÉGIME dans la topbar · retiré le 25/08/2026 sur demande
   de l'utilisateur (« sans mentionner lecture seule »). `.badge-lecture`
   reste défini plus haut : le sélecteur d'espaces l'emploie sur la CARTE
   d'une vue détenue en consultation, ce qui est un renseignement pour
   l'intéressé au moment de choisir, et non une étiquette portée toute la
   journée en haut de chaque écran. */

/* ==================== CHIPS ==================== */

.barre-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-2);
  padding: var(--esp-2);
  background: var(--g-creme);
  border: 1px solid var(--g-filet);
  border-radius: var(--rayon-champ);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 36px;
  padding: 0 var(--esp-4);
  border: 1px solid var(--g-filet);
  border-radius: 999px;
  background: var(--g-blanc);
  font-size: 13px;
  font-weight: 600;
  color: var(--g-encre-2);
  cursor: pointer;
  transition: background-color var(--t-rapide), color var(--t-rapide),
    border-color var(--t-rapide);
}

.chip:hover {
  border-color: var(--g-vert-600);
  color: var(--g-vert-600);
}

.chip.est-active {
  background: var(--g-vert-950);
  border-color: var(--g-vert-950);
  color: var(--g-blanc);
}

.chip:focus-visible {
  outline: none;
  box-shadow: var(--anneau-focus);
}

/* ==================== TUILE KPI ==================== */

.tuile-kpi {
  background: var(--g-blanc);
  border: 1px solid var(--g-filet);
  border-radius: var(--rayon-carte);
  box-shadow: var(--ombre-douce);
  padding: var(--esp-4) var(--esp-5);
  display: flex;
  flex-direction: column;
  gap: var(--esp-1);
}

.tuile-kpi-libelle {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--g-muet);
}

.tuile-kpi-valeur {
  font-size: 34px;
  font-weight: 800;
  line-height: 1.1;
  color: var(--g-encre);
  font-variant-numeric: tabular-nums;
}

.tuile-kpi-delta {
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.tuile-kpi-delta.est-hausse { color: var(--statut-vert); }
.tuile-kpi-delta.est-baisse { color: var(--statut-rouge); }

.tuile-kpi-delta.est-hausse::before { content: "↑ "; }
.tuile-kpi-delta.est-baisse::before { content: "↓ "; }

/* ==================== TABLE ==================== */

.table-guiter {
  width: 100%;
  border-collapse: collapse;
  background: var(--g-blanc);
  font-size: 14px;
}

.table-guiter th {
  padding: var(--esp-3) var(--esp-4);
  text-align: left;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--g-muet);
  border-bottom: 1px solid var(--g-filet);
  white-space: nowrap;
}

.table-guiter td {
  padding: var(--esp-4);
  border-bottom: 1px solid var(--g-filet);
  color: var(--g-encre);
}

.table-guiter tbody tr {
  transition: background-color var(--t-rapide);
}

.table-guiter tbody tr:hover {
  background: var(--g-vert-100);
}

.table-guiter .col-chiffres {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ==================== COQUILLE APP : SIDEBAR + TOPBAR ==================== */

.app-coquille {
  display: grid;
  grid-template-columns: 264px minmax(0, 1fr);
  min-height: 100vh;
  background: var(--g-creme);
}

.sidebar {
  display: flex;
  flex-direction: column;
  background: var(--g-vert-950);
  color: var(--g-vert-100);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  scrollbar-width: thin;
}

.sidebar-logo {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  padding: var(--esp-5) var(--esp-4);
}

.sidebar-logo img {
  height: 34px;
  width: auto;
}

/* Filiale choisie · bandeau discret SOUS la marque du Groupe, jamais à sa
   place. Le liseré supérieur le détache de l'en-tête sans ajouter de bloc
   lourd dans une barre déjà dense. */
.sidebar-filiale {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  margin: 0 var(--esp-4) var(--esp-2);
  padding: var(--esp-2) 0 var(--esp-3);
  border-top: 1px solid rgba(255, 255, 255, .14);
  font-size: 12.5px;
  font-weight: 700;
  color: rgba(255, 255, 255, .82);
  min-width: 0;
}

/* Le logo d'une filiale est de forme imprévisible (carré, bandeau, rond) :
   on borne sa hauteur ET sa largeur, et `contain` interdit tout recadrage ·
   un logo tronqué ne serait plus le logo. */
.sidebar-filiale img {
  height: 22px;
  width: auto;
  max-width: 84px;
  object-fit: contain;
  flex: none;
}

/* Sans logo déposé, la couleur d'accent tient le rôle de repère · mais
   jamais seule : le nom est toujours écrit à côté. */
.sidebar-filiale-pastille {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .18);
}

.sidebar-filiale .sidebar-libelle {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-section {
  margin: var(--esp-4) 0 var(--esp-1);
  padding: 0 var(--esp-4);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(230, 243, 234, .45);
}

.sidebar-item {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  min-height: 44px;
  margin: 2px var(--esp-2);
  padding: 0 var(--esp-3);
  border-left: 3px solid transparent;
  border-radius: 10px;
  color: var(--g-vert-100);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: background-color var(--t-rapide), border-color var(--t-rapide);
}

.sidebar-item:hover {
  background: var(--g-vert-900);
}

.sidebar-item:focus-visible {
  outline: none;
  box-shadow: var(--anneau-focus);
}

.sidebar-item.est-actif {
  background: var(--g-vert-800);
  border-left-color: var(--g-orange-500);
  font-weight: 700;
}

.sidebar-picto {
  flex: none;
  width: 22px;
  text-align: center;
  font-size: 16px;
  line-height: 1;
}

.sidebar-pied {
  margin-top: auto;
  padding: var(--esp-4);
  font-size: 11px;
  color: rgba(230, 243, 234, .4);
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--esp-4);
  min-height: 64px;
  padding: 0 var(--esp-5);
  background: rgba(250, 247, 240, .92);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--g-filet);
}

/* LA MARGE AUTOMATIQUE EST ICI, et pas sur la rangée d'outils · c'est
   elle qui pousse à droite TOUT ce qui suit le titre, le sélecteur de
   langue comme les outils. La porter sur `.topbar-outils` marchait tant
   que la rangée était la seule chose à droite ; dès qu'un second bloc
   s'intercale, deux marges automatiques se partagent l'espace et le
   premier se retrouve au milieu de la barre. */
.topbar-titre {
  margin: 0 auto 0 0;
  font-size: 19px;
  font-weight: 800;
  color: var(--g-encre);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topbar-outils {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
}

.topbar-recherche {
  min-height: 40px;
  width: clamp(140px, 22vw, 300px);
  padding: 0 var(--esp-4);
  border: 1px solid var(--g-filet);
  border-radius: 999px;
  background: var(--g-blanc);
  font-family: var(--police);
  font-size: 14px;
  color: var(--g-encre);
  transition: border-color var(--t-rapide), box-shadow var(--t-rapide);
}

.topbar-recherche:focus {
  outline: none;
  border-color: var(--g-vert-600);
  box-shadow: 0 0 0 3px rgba(27, 122, 61, .12);
}

.topbar-select {
  min-height: 40px;
  padding: 0 var(--esp-3);
  border: 1px solid var(--g-filet);
  border-radius: 10px;
  background: var(--g-blanc);
  font-family: var(--police);
  font-size: 14px;
  font-weight: 600;
  color: var(--g-encre);
  cursor: pointer;
}

.topbar-select:focus-visible {
  outline: none;
  box-shadow: var(--anneau-focus);
}

/* ==================== SÉLECTEUR DE LANGUE ====================

   Un bouton de la largeur d'un jeton (globe + deux lettres) et un
   panneau de huit lignes. Il vit dans la topbar sur les écrans métier,
   et dans l'en-tête des deux pages à aplat vert (connexion, choix d'un
   espace) · d'où la variante « langue--sombre », qui ne change QUE le
   bouton. Le panneau reste blanc dans les deux cas : huit langues, dont
   trois en écritures non latines, se lisent sur du blanc et nulle part
   ailleurs.

   Seule la LISTE est marquée `notranslate` dans le gabarit, avec le
   jeton du bouton. Sans cela « English » deviendrait « Anglais » sous les
   yeux de qui cherche l'anglais. Le reste de la prose du panneau se
   traduit comme le reste de l'écran. */

.langue {
  position: relative;
  flex: none;
}

.langue-bouton {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: 0 8px 0 10px;
  border: 1px solid var(--g-filet);
  border-radius: 10px;
  background: var(--g-blanc);
  font-family: var(--police);
  font-size: 13px;
  font-weight: 700;
  color: var(--g-encre);
  cursor: pointer;
  transition: border-color var(--t-rapide), background-color var(--t-rapide);
}

.langue-bouton:hover {
  border-color: var(--g-vert-600);
  background: var(--g-vert-100);
}

.langue-jeton {
  letter-spacing: .04em;
}

.langue-chevron {
  display: inline-flex;
  color: var(--g-muet);
  transition: transform var(--t-rapide);
}

.langue.est-ouvert .langue-chevron {
  transform: rotate(180deg);
}

.langue-panneau {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 60;
  width: 268px;
  max-width: calc(100vw - 28px);
  padding: 8px;
  border: 1px solid var(--g-filet);
  border-radius: 14px;
  background: var(--g-blanc);
  box-shadow: var(--ombre-elevee);
  /* Les huit langues tiennent sans défilement (environ 430 px) : la
     borne ne sert que sur un écran couché, où le panneau serait sinon
     coupé par le bas sans qu'on puisse l'atteindre. */
  max-height: min(78vh, 460px);
  overflow-y: auto;
}

.langue-titre {
  margin: 4px 8px 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--g-muet);
}

.langue-liste {
  margin: 0;
  padding: 0;
  list-style: none;
}

.langue-choix {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 5px 8px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  font-family: var(--police);
  text-align: left;
  color: var(--g-encre);
  cursor: pointer;
  transition: background-color var(--t-rapide);
}

.langue-choix:hover {
  background: var(--g-vert-100);
}

.langue-choix.est-active {
  background: var(--g-vert-100);
}

/* La pastille porte le code de la langue · deux lettres de largeur fixe,
   pour que les huit lignes s'alignent quelle que soit l'écriture du nom
   qui suit. */
.langue-pastille {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 24px;
  border-radius: 7px;
  background: var(--g-vert-900);
  color: var(--g-blanc);
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .04em;
}

.langue-choix.est-active .langue-pastille {
  background: var(--g-vert-600);
}

.langue-noms {
  flex: 1 1 auto;
  min-width: 0;
  line-height: 1.25;
}

/* Le nom NATIF en premier · c'est celui que cherche des yeux quelqu'un
   qui ne lit pas le français, et c'est tout l'objet de ce panneau. Le
   nom français vient dessous, pour qui règle l'appareil d'un collègue. */
.langue-noms b {
  display: block;
  overflow: hidden;
  font-size: 14px;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.langue-noms small {
  display: block;
  font-size: 11.5px;
  color: var(--g-muet);
}

.langue-coche {
  flex: none;
  display: inline-flex;
  color: var(--g-vert-600);
  opacity: 0;
}

.langue-choix.est-active .langue-coche {
  opacity: 1;
}

.langue-mention {
  margin: 8px 8px 2px;
  font-size: 11px;
  color: var(--g-muet);
}

/* Fond vert : le bouton se pose en verre, le panneau reste blanc. */
.langue--sombre .langue-bouton {
  border-color: rgba(255, 255, 255, .22);
  background: rgba(255, 255, 255, .1);
  color: var(--g-blanc);
}

.langue--sombre .langue-bouton:hover {
  border-color: rgba(255, 255, 255, .4);
  background: rgba(255, 255, 255, .18);
}

.langue--sombre .langue-chevron {
  color: rgba(255, 255, 255, .65);
}

/* Écran de connexion · le sélecteur se pose dans le coin haut droit de
   la scène, au-dessus du voile. C'est le premier écran de la
   plateforme : quelqu'un qui ne lit pas le français doit pouvoir choisir
   sa langue AVANT de chercher où taper son mot de passe. */
.public-langue {
  position: absolute;
  top: 18px;
  right: 18px;
  z-index: 3;
}

/* --- Ce que le moteur de traduction pose de lui-même, et qu'on retire.

   Il ajoute un bandeau en haut de page, DÉCALE le corps de 40 px pour
   lui faire place, et surligne chaque segment traduit. Les trois sont
   incompatibles avec cette coquille : la topbar est `sticky` et passait
   sous le bandeau, et le surlignage rendait les tableaux illisibles.
   `!important` est nécessaire · ces styles sont posés en ligne par un
   script qu'on ne modifie pas. */
.goog-te-banner-frame,
.goog-te-balloon-frame,
iframe.skiptranslate {
  display: none !important;
}

body {
  top: 0 !important;
}

.goog-text-highlight {
  background: none !important;
  box-shadow: none !important;
}

#goog-gt-tt {
  display: none !important;
}

/* SON ROND DE CHARGEMENT AUSSI · un disque bleu de cent pixels qui se
   pose en haut à gauche de l'écran pendant la réécriture, là où rien ne
   vient d'être cliqué et où aucun de nos composants ne vit. L'attente
   est réelle et se dit · mais sur le bouton qu'on vient de toucher
   (`.langue.est-en-cours`, plus bas), pas dans un coin.

   Les noms de classe du moteur sont engendrés et peuvent changer : la
   règle vise le PRÉFIXE et se borne aux enfants directs du corps de
   page, ce qui ne peut atteindre ni notre berceau ni la liste qu'il
   contient. Si le préfixe change un jour, le rond réapparaît · c'est un
   défaut d'apparence, jamais une panne. */
body > div[class*="VIpgJd"],
body > .goog-te-spinner-pos {
  display: none !important;
}

/* Le bouton en veille pendant que la page se réécrit · une seconde ou
   deux. Sobre : la teinte baisse, le curseur dit l'attente. Rien ne
   bouge, rien ne clignote. */
.langue.est-en-cours .langue-bouton {
  opacity: .55;
  cursor: progress;
}

/* Le berceau du composant · CLIPPÉ, jamais « display: none ». Le
   composant y construit la liste déroulante que langues.js pilote, et
   un parent retiré du rendu l'empêcherait de fonctionner. */
.traduction-berceau {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

/* --- ARABE · le texte se lit de droite à gauche, la CHARPENTE ne bouge
   pas. Retourner la coquille entière demanderait de réécrire quatre
   mille lignes posées en gauche/droite PHYSIQUES : on obtiendrait un
   écran de travers, pas un écran arabe. On retourne donc ce qui se LIT
   (titres, paragraphes, listes, libellés, cellules de texte), et rien
   d'autre. Les colonnes de chiffres gardent leur sens : un montant ne
   change pas d'ordre avec la langue de la page. */
body.langue-rtl .topbar-titre,
body.langue-rtl .zone-contenu h1,
body.langue-rtl .zone-contenu h2,
body.langue-rtl .zone-contenu h3,
body.langue-rtl .zone-contenu h4,
body.langue-rtl .zone-contenu p,
body.langue-rtl .zone-contenu li,
body.langue-rtl .zone-contenu label,
body.langue-rtl .zone-contenu th,
body.langue-rtl .zone-contenu td {
  direction: rtl;
  text-align: right;
}

body.langue-rtl .sidebar-libelle {
  direction: rtl;
}

body.langue-rtl .zone-contenu .col-chiffres {
  direction: ltr;
  text-align: right;
}

/* La LISTE n'est jamais retournée : elle nomme des langues, elle n'est
   écrite dans aucune d'elles · et chaque nom natif porte déjà son propre
   `dir` dans le gabarit. Le titre et la mention du panneau, eux, sont de
   la prose et suivent la page. */
body.langue-rtl .langue-liste,
body.langue-rtl .langue-choix {
  direction: ltr;
  text-align: left;
}

.bouton-cloche {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--g-filet);
  border-radius: 50%;
  background: var(--g-blanc);
  font-size: 18px;
  cursor: pointer;
  transition: border-color var(--t-rapide), background-color var(--t-rapide);
}

.bouton-cloche:hover {
  border-color: var(--g-vert-600);
  background: var(--g-vert-100);
}

.bouton-cloche:focus-visible {
  outline: none;
  box-shadow: var(--anneau-focus);
}

.cloche-compteur {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--statut-rouge);
  color: var(--g-blanc);
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.panneau-cloche {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: min(360px, 90vw);
  max-height: 60vh;
  overflow-y: auto;
  background: var(--g-blanc);
  border: 1px solid var(--g-filet);
  border-radius: var(--rayon-carte);
  box-shadow: var(--ombre-elevee);
  padding: var(--esp-4);
  display: none;
  z-index: 60;
}

.panneau-cloche.est-ouvert {
  display: block;
  animation: panneau-fondu var(--t-rapide);
}

@keyframes panneau-fondu {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: translateY(0); }
}

.zone-contenu {
  padding: var(--esp-5);
  min-width: 0;
}

/* LA BANDE, ET NON « TOUT CE QUI EST SOUS 1024 » · corrigé le 23/08/2026.

   Ce bloc décrit LE RAIL D'ICÔNES, qui n'existe qu'entre 901 et 1023 px.
   Sous 901 px la barre devient un TIROIR (theme.css) et redevient large :
   elle n'a plus rien à replier.

   Il était écrit « max-width: 1023px », donc il s'appliquait aussi au
   tiroir, et theme.css devait le défaire règle par règle
   (`.sidebar-libelle { display: inline }`, `.sidebar-section { display:
   block }`, `.sidebar-pied { display: block }`…). Ce montage tient tant
   qu'on n'ajoute rien : la première ligne de masquage ajoutée ici sans son
   antidote là-bas disparaît du tiroir. C'est arrivé pendant cette même
   correction · la marque et le nom de zone se sont évanouis du menu
   mobile, mesuré à 820 px.

   Borner la bande supprime la classe entière de défaut : ce qui replie le
   rail ne peut plus atteindre le tiroir, et personne n'a plus à écrire
   d'antidote. Les annulations de theme.css deviennent inertes · elles
   réaffirment les valeurs par défaut, elles ne gênent pas.

   Sidebar repliée en icônes · bande 901 à 1023 px. */
@media (min-width: 901px) and (max-width: 1023px) {
  .app-coquille {
    grid-template-columns: 72px minmax(0, 1fr);
  }

  /* CE QUI DISPARAÎT DU RAIL · et il a fallu mesurer pour s'apercevoir
     que deux des trois lignes ne servaient à rien.

     `.sidebar-logo-texte` NE VISAIT RIEN. Cette classe ne vit que dans
     `ui/partials/sidebar.html`, un partiel que `base.html` n'inclut plus :
     la vraie marque porte `.marque-texte`. La règle était donc morte, et
     « GuiterOps » restait écrit en 19 px dans un rail de 72 · mesuré le
     23/08/2026, son bord droit tombait à 120 px, soit 48 px hors barre.

     Le NOM DE LA ZONE dans le commutateur débordait de la même façon, plus
     loin encore (bord droit à 150 px). Il n'était couvert par aucune règle.
     Ensemble, ces deux textes portaient la barre à 165 px de contenu pour
     72 px de cadre.

     Le nom de la zone ne se perd pas pour autant : le commutateur s'ouvre
     d'un clic et les liste toutes, et le titre de la page le redit en
     haut de l'écran. */
  .sidebar-libelle,
  .sidebar-section,
  .sidebar-logo .marque-texte,
  .sidebar-logo .sidebar-logo-texte,
  .zone-switch summary > span {
    display: none;
  }

  /* Le commutateur garde une cible tactile franche, réduite à son
     chevron · sans quoi il tombe à la largeur du seul pictogramme et
     devient difficile à viser au doigt. */
  .zone-switch {
    margin: var(--esp-3) var(--esp-2) var(--esp-1);
  }

  .zone-switch summary {
    justify-content: center;
    padding: 11px 0;
  }

  /* LE PIED N'EST PLUS CACHÉ, ET C'EST UNE CORRECTION · mesuré le
     23/08/2026 à 980 px de fenêtre : `.sidebar-pied { display: none }`
     emportait « Ma carte », « Mon activité », « Télécharger l'app »,
     « Gestes refusés » ET « Se déconnecter ». Entre 901 et 1023 px · une
     fenêtre de portable non maximisée, un écran partagé en deux · IL
     N'Y AVAIT AUCUN MOYEN DE SE DÉCONNECTER. Sur une tablette de
     guichet partagée, c'est la porte de sortie qui manquait.

     Il n'y a rien à cacher : ces liens portent déjà un pictogramme et un
     `.sidebar-libelle`, exactement comme les entrées du menu. Le libellé
     tombe avec les autres, le pictogramme reste, et la barre garde sa
     sortie. On les centre comme `.sidebar-item`, sinon ils restent collés
     à gauche d'un rail de 72 px. */
  .sidebar-pied {
    padding: var(--esp-3) var(--esp-2);
  }

  .sidebar-pied > a {
    justify-content: center;
    min-height: 40px;
    margin-bottom: 2px !important;
  }

  .sidebar-logo {
    justify-content: center;
    padding: var(--esp-4) var(--esp-2);
  }

  .sidebar-logo img {
    height: 28px;
  }

  .sidebar-item {
    justify-content: center;
    padding: 0;
  }

  /* CEINTURE · un rail de 72 px n'a rien à faire défiler latéralement.
     La cause est corrigée juste en dessous (les titres transverses), mais
     un menu qui déborde de côté est le genre de défaut qu'un ajout futur
     ramène sans prévenir, et il ne se voit pas sur un grand écran. */
  .sidebar {
    overflow-x: hidden;
  }
}

@media (max-width: 767px) {
  .topbar-recherche {
    display: none;
  }

  .zone-contenu {
    padding: var(--esp-4);
  }
}

/* ==================== CASES OTP ==================== */

.otp-cases {
  display: flex;
  gap: var(--esp-3);
  justify-content: center;
}

.otp-case {
  width: 56px;
  height: 56px;
  padding: 0;
  text-align: center;
  font-family: var(--police);
  font-size: 24px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--g-encre);
  background: var(--g-blanc);
  border: 1.5px solid var(--g-filet);
  border-radius: var(--rayon-champ);
  caret-color: var(--g-vert-600);
  transition: border-color var(--t-rapide), box-shadow var(--t-rapide),
    transform var(--t-rapide);
}

.otp-case:focus {
  outline: none;
  border-color: var(--g-orange-500);
  box-shadow: 0 0 0 4px rgba(240, 160, 40, .18);
  transform: translateY(-2px);
}

.otp-cases.valide .otp-case {
  border-color: var(--statut-vert);
  box-shadow: 0 0 0 4px rgba(12, 163, 12, .15);
}

.otp-cases.secousse {
  animation: otp-secousse 300ms ease-in-out;
}

.otp-cases.secousse .otp-case {
  border-color: var(--g-rouge-erreur);
}

@keyframes otp-secousse {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-8px); }
  40% { transform: translateX(8px); }
  60% { transform: translateX(-5px); }
  80% { transform: translateX(5px); }
}

@media (max-width: 420px) {
  .otp-cases { gap: var(--esp-2); }
  .otp-case { width: 44px; height: 52px; }
}

/* ==================== COMPTE À REBOURS ==================== */

.compte-a-rebours {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.compte-a-rebours.est-expire {
  color: var(--g-rouge-erreur);
}

/* ==================== VERDICT PLEIN ÉCRAN (kit terrain non-lecteurs) ====================
   Un écran = un geste. Vert grande coche / rouge grande croix, photo du
   porteur en grand, chiffres géants. Son + vibration pilotés par
   data-son / data-vibration (voir app.js). */

.verdict-plein-ecran {
  position: fixed;
  /* Décalages en toutes lettres : le raccourci `inset` demande Chrome 87,
     les WebView de terrain sont plus anciennes. Sans les quatre ancrages,
     le verdict se réduisait à une vignette en haut à gauche au lieu de
     noyer l'écran de vert ou de rouge. Même règle partout ci-dessous. */
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--esp-5);
  padding: var(--esp-5);
  text-align: center;
  color: var(--g-blanc);
  animation: verdict-apparition 260ms ease-out;
}

@keyframes verdict-apparition {
  from { opacity: 0; transform: scale(1.04); }
  to { opacity: 1; transform: scale(1); }
}

.verdict-vert { background: var(--statut-vert); }
.verdict-rouge { background: var(--statut-rouge); }

.verdict-icone {
  width: clamp(120px, 24vmin, 200px);
  height: clamp(120px, 24vmin, 200px);
}

.verdict-icone svg {
  width: 100%;
  height: 100%;
  display: block;
}

.verdict-photo {
  width: clamp(140px, 30vmin, 240px);
  height: clamp(140px, 30vmin, 240px);
  border-radius: 50%;
  overflow: hidden;
  border: 6px solid rgba(255, 255, 255, .85);
  box-shadow: 0 10px 40px rgba(0, 0, 0, .3);
  background: rgba(255, 255, 255, .2);
}

.verdict-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.verdict-chiffre {
  font-size: 96px;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  text-shadow: 0 2px 12px rgba(0, 0, 0, .18);
}

.verdict-libelle {
  font-size: clamp(20px, 4vmin, 32px);
  font-weight: 700;
  max-width: 18em;
}

@media (max-width: 480px) {
  .verdict-chiffre { font-size: 72px; }
}

/* ==================== ÉCRANS PUBLICS (base_public) ==================== */

.public-scene {
  position: relative;
  min-height: 100vh;
  display: flex;
  align-items: center;
  background: var(--g-vert-950);
  overflow: hidden;
}

.diaporama {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.diaporama .diapo {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.5s ease;
}

.diaporama .diapo.est-active {
  opacity: 1;
}

/* Voile UNIFORME aux couleurs de la charte : aplat vert profond,
   garant du contraste AA de tout texte posé sur photographie. */
.voile-degrade {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: rgba(8, 43, 8, .78);
}

.public-contenu {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--esp-6);
  width: 100%;
  max-width: 1440px;
  margin: 0 auto;
  padding: var(--esp-6);
}

.public-accroche {
  margin-right: auto;
  max-width: 520px;
  color: var(--g-blanc);
}

.public-accroche h1 {
  font-size: clamp(32px, 3.4vw, 48px);
  font-weight: 800;
  line-height: 1.15;
  color: var(--g-blanc);
  margin-bottom: var(--esp-4);
}

.public-accroche p {
  font-size: 17px;
  color: rgba(255, 255, 255, .85);
}

.public-carte {
  width: min(420px, 100%);
  flex: none;
}

.public-logo {
  display: block;
  height: 44px;
  width: auto;
  margin: 0 auto var(--esp-4);
}

.public-plateforme {
  text-align: center;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .8);
  margin-bottom: var(--esp-5);
}

/* Signature discrète du concepteur (jamais sur les écrans métier) */
.signature-concepteur {
  position: absolute;
  bottom: var(--esp-3);
  left: 0;
  right: 0;
  z-index: 3;
  text-align: center;
  font-size: 11px;
  color: var(--g-blanc);
  opacity: .35;
  transition: opacity 200ms ease-out;
}

.signature-concepteur:hover {
  opacity: 1;
}

/* Tablette / terminal durci : carte centrée, même voile uniforme */
@media (max-width: 1279px) {
  .public-accroche {
    display: none;
  }

  .public-contenu {
    justify-content: center;
  }
}

/* Mobile : photo en bandeau supérieur, formulaire pleine largeur */
@media (max-width: 767px) {
  .public-scene {
    display: block;
    background: var(--g-vert-950);
  }

  .diaporama {
    position: relative;
    height: 180px;
  }

  .voile-degrade {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background: rgba(8, 43, 8, .78);
  }

  .diaporama,
  .voile-degrade {
    height: 180px;
  }

  .public-contenu {
    padding: var(--esp-4) var(--esp-4) var(--esp-6);
  }

  .public-carte {
    width: 100%;
  }

  .carte-verre {
    padding: var(--esp-5);
  }

  .signature-concepteur {
    position: static;
    padding: var(--esp-4) 0;
  }
}

/* ==================== ACCESSIBILITÉ ====================

   `:focus-visible` demande Chrome 86 : sur les WebView de terrain plus
   anciennes, TOUTE règle qui l'emploie est jetée · y compris la trentaine
   de règles fines des composants. Sans repli, plus un seul anneau de
   focus sur ces appareils : on tabule à l'aveugle dans les formulaires.

   Le repli tient en trois règles, dans cet ordre :
   1. `:focus` pose le halo · comprise partout, c'est elle qui sauve les
      vieux moteurs ;
   2. `:focus:not(:focus-visible)` l'efface dès que le moteur SAIT
      distinguer le clavier du clic · elle-même jetée par les vieux
      moteurs, qui gardent donc le halo ;
   3. `:focus-visible` reste la règle de référence des moteurs récents.
   Sur un navigateur à jour, le rendu est inchangé. */

:focus {
  outline: none;
  box-shadow: var(--anneau-focus);
}

:focus:not(:focus-visible) {
  box-shadow: none;
}

:focus-visible {
  outline: none;
  box-shadow: var(--anneau-focus);
}

/* Porte réservée aux grands écrans · le Mur Direction est fait pour un
   téléviseur mural (il se masque lui-même sous 900px). Proposer son lien
   sur un téléphone, c'est promettre une porte qui ne mène nulle part :
   `!important` parce que la règle est catégorique et doit gagner sur le
   `display` propre à chaque emplacement (élément de sidebar, bouton…). */
@media (max-width: 900px) {
  .sans-telephone { display: none !important; }
}

.sr-uniquement {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }

  /* Le diaporama reste figé sur la première photo (voir app.js). */
  .diaporama .diapo {
    transition: none;
  }
}

/* ==================== MESSAGES GLOBAUX ====================
   Bandeau de retour visuel sous la topbar (django.contrib.messages) :
   aplat vert (succès), rouge (erreur), ambre (avertissement) ·
   fermeture automatique après 6 s (JS léger dans ui/base.html). */
.messages-globaux {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0 0 16px;
}
.messages-globaux .bandeau {
  display: flex;
  align-items: center;
  gap: 10px;
  border-radius: 12px;
  padding: 13px 16px;
  margin: 0;
  font-weight: 700;
  font-size: 14px;
  line-height: 1.4;
  background: #168212;
  color: #FFFFFF;
}
.messages-globaux .bandeau.error { background: #B02A2A; }
.messages-globaux .bandeau.warning { background: #B36A00; }
.messages-globaux .bandeau .icone { flex: none; }

/* ==================== CHOISIR UNE PIÈCE EN IMAGES ====================
   Grille de vignettes à cocher, en remplacement des listes déroulantes de
   choix d'article. Une liste déroulante demande de LIRE trois cents
   désignations pour en trouver une : sur un chantier, ce n'est pas la
   bonne question à poser. La carte montre la pièce (photo réelle ou repli
   pictogramme), son stock en très gros et son rangement ; on la coche du
   doigt, et sa quantité apparaît dessous.

   Deux usages, même grammaire : cases à cocher (demander plusieurs pièces
   d'un geste) et boutons radio (choisir LE produit d'un colis reçu). Le
   composant ne présume rien du type d'entrée · il stylise l'état coché,
   porté par la classe .est-cochee posée en JS. */
.grille-pieces {
  display: grid;
  gap: 12px;
  margin-top: 14px;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
}
.carte-piece {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  text-align: center;
  cursor: pointer;
  background: #FFFFFF;
  border: 2px solid var(--g-filet);
  border-radius: 14px;
  padding: 14px 10px 12px;
  min-width: 0;
  transition: border-color 120ms ease, background 120ms ease;
}
.carte-piece:hover { border-color: var(--g-vert-600); }
/* La commande reste focalisable au clavier bien qu'invisible : le contour
   de focus est porté par la carte (:focus-within). */
.carte-piece input[type="checkbox"],
.carte-piece input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}
.carte-piece:focus-within { outline: none; box-shadow: var(--anneau-focus); }
.carte-piece.est-cochee { border-color: var(--g-vert-600); background: #F3FAF2; }
.carte-piece .designation {
  font-weight: 700;
  font-size: 14px;
  line-height: 1.25;
  max-width: 100%;
}
.carte-piece .reference {
  font-size: 11.5px;
  color: var(--g-muet);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.carte-piece .stock-piece {
  font-size: 30px;
  font-weight: 900;
  line-height: 1;
  color: var(--g-vert-600);
  display: flex;
  align-items: baseline;
  gap: 5px;
}
.carte-piece .stock-piece small {
  font-size: 11px;
  font-weight: 600;
  color: var(--g-muet);
}
.carte-piece.est-vide .stock-piece { color: var(--statut-rouge); }
.carte-piece .coche {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--g-vert-600);
  color: #FFFFFF;
  opacity: 0;
  transform: scale(0.6);
  transition: opacity 120ms ease, transform 120ms ease;
}
.carte-piece.est-cochee .coche { opacity: 1; transform: scale(1); }
.carte-piece .quantite-piece {
  display: none;
  align-items: center;
  gap: 6px;
  margin-top: 2px;
  font-size: 12px;
  color: var(--g-muet);
}
.carte-piece.est-cochee .quantite-piece { display: flex; }
.carte-piece .quantite-piece input {
  width: 64px;
  text-align: center;
  font-weight: 700;
}
.pieces-outils {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 4px;
}
.pieces-outils input[type="search"] { flex: 1 1 220px; max-width: 340px; }
.pieces-compte { font-size: 13px; color: var(--g-muet); }
.pieces-compte b { color: var(--g-vert-600); }
.pieces-vide { grid-column: 1 / -1; color: var(--g-muet); font-size: 13.5px; }
@media (max-width: 480px) {
  .grille-pieces { grid-template-columns: repeat(auto-fill, minmax(138px, 1fr)); }
  .carte-piece .stock-piece { font-size: 26px; }
}
@media (prefers-reduced-motion: reduce) {
  .carte-piece,
  .carte-piece .coche { transition: none; }
}

/* ==================== BLOC TRANSVERSE DE LA BARRE LATÉRALE ====================
   « Demandes » ne fait partie d'AUCUNE zone : le besoin naît au magasin
   comme à l'atelier, et le suivi appartient à la personne, pas au
   département où elle se trouve à cet instant.

   Sans séparation, ces trois liens se lisent comme la suite du menu de la
   zone · on croit alors que le Magasin, l'Atelier et la RH offrent chacun
   leur propre « Exprimer un besoin ». Le filet et la note d'en-tête
   disent ce que la simple position ne dit pas.

   (Le bloc était rendu, dans huit gabarits sur douze, à l'intérieur du
   <summary> du commutateur de zones : collé au titre, avec le chevron du
   commutateur au milieu. Il est désormais rendu une seule fois par
   ui/base.html, après le menu.) */
.sidebar-transverse {
  margin-top: var(--esp-4);
  padding-top: var(--esp-2);
  border-top: 1px solid rgba(230, 243, 234, .14);
}
.sidebar-transverse .sidebar-section {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 0;
}
.sidebar-section-note {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: none;
  color: rgba(230, 243, 234, .32);
}
/* Barre repliée (≤1023px) : les libellés disparaissent, le filet suffit à
   séparer · sans lui, les pictogrammes transverses se confondraient avec
   ceux de la zone. */
@media (min-width: 901px) and (max-width: 1023px) {
  .sidebar-transverse {
    margin-top: var(--esp-2);
    padding-top: var(--esp-2);
  }

  /* LES TITRES TRANSVERSES DISPARAISSENT AUSSI · et il faut l'écrire ICI,
     pas plus haut. Le rail cache `.sidebar-section` (0,1,0) ; mais
     `.sidebar-transverse .sidebar-section` vaut (0,2,0) et lui rendait
     `display: flex` quelle que soit la largeur. Une media query n'ajoute
     aucune spécificité : la règle la plus précise gagne, point.

     Ce que cela donnait à 980 px, mesuré le 23/08/2026 : « DEMANDES » et
     « TÂCHES » écrits en toutes lettres dans un rail de 72 px, avec leurs
     notes (« suit la personne », « consignes reçues ») · 165 px de contenu
     pour 72 px de cadre, soit 93 px qui sortaient de la barre. C'est
     exactement ce que le dépôt appelle « caché veut dire caché » : le HTML
     disait une chose, l'écran en montrait une autre.

     La règle est posée à la FIN du bloc transverse, à spécificité égale
     mais plus loin dans la feuille · c'est ce qui la fait gagner, et elle
     reste à côté des règles qu'elle corrige. */
  .sidebar-transverse .sidebar-section {
    display: none;
  }
}

/* ============================================================
   PIED DE LISTE PAGINÉE · « 1 à 50 sur 118 » et les deux portes.

   Le COMPTE est à gauche et se lit toujours, même quand il n'y a
   qu'une page : une liste tronquée qui ne dit pas qu'elle l'est
   se lit comme un effectif plus petit qu'il n'est.

   Les deux portes n'apparaissent qu'à partir de deux pages ·
   une commande inerte ne se montre pas.
   ============================================================ */
.pagination-pied {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--esp-2);
  padding: var(--esp-2) var(--esp-3);
  border-top: 1px solid var(--g-filet, #E1E0D9);
  font-size: 13px;
  color: var(--g-muet, #6C7570);
}

.pagination-compte b { color: var(--g-encre, #1A1A18); }

.pagination-portes {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
}

/* La position dans la liste ne bouge pas de place quand le nombre de
   pages passe de 9 à 10 · sinon les deux boutons sautent sous les
   doigts au moment précis où l'on vise le suivant. */
.pagination-rang {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Téléphone · le compte passe au-dessus, les portes gardent leur
   hauteur de cible tactile. */
@media (max-width: 560px) {
  .pagination-pied { flex-direction: column; align-items: stretch; }
  .pagination-portes { justify-content: space-between; }
}

/* ===========================================================================
   LE MONTANT COUVERT · demande de l'utilisateur du 29/08/2026
   ---------------------------------------------------------------------------
   « Ça doit masquer obligatoirement, et on clique sur l'icône pour
   afficher · sinon par défaut on masque ça. »

   ÉTAT PAR DÉFAUT DE CETTE FEUILLE : DÉCOUVERT. C'est délibéré, et c'est
   l'inverse de ce qu'on lit à l'écran. Le script pose `.est-couvert` sur
   chaque montant au chargement (voir ui/base.html) ; sans JavaScript, le
   nombre reste donc LISIBLE plutôt que remplacé par une ligne de points
   qu'aucun clic n'ouvrirait. Un écran qui ne s'ouvre pas est pire qu'un
   écran qui montre.

   Le montant garde sa CHASSE FIXE et ses points occupent la place du
   nombre : découvrir un salaire ne doit pas faire sauter la colonne
   d'à côté ni décaler la ligne suivante.
   =========================================================================== */
.montant-couvert {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.montant-couvert-points,
.montant-couvert-valeur {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
}

/* Découvert · les points s'effacent, la valeur s'écrit. */
.montant-couvert .montant-couvert-points,
.montant-couvert .montant-oeil-ouvrir {
  display: none;
}

/* Couvert · l'inverse, et l'œil propose d'ouvrir. */
.montant-couvert.est-couvert .montant-couvert-points,
.montant-couvert.est-couvert .montant-oeil-ouvrir {
  display: inline;
}
.montant-couvert.est-couvert .montant-couvert-valeur,
.montant-couvert.est-couvert .montant-oeil-fermer {
  display: none;
}

.montant-couvert-oeil {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 2px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--g-texte-doux, #6B6B63);
  cursor: pointer;
  /* La cible tactile fait 32 px alors que l'icône en fait 16 · sur une
     tablette de bureau RH, un œil de 16 px se rate une fois sur trois. */
  min-width: 32px;
  min-height: 32px;
  margin: -8px 0;
}
.montant-couvert-oeil:hover { color: var(--g-vert, #0E3A25); }
.montant-couvert-oeil:focus-visible {
  outline: 2px solid var(--g-vert, #0E3A25);
  outline-offset: 1px;
}

/* La bascule de page · même jeu d'icônes que l'œil de ligne, donc la même
   règle d'affichage. Elle naît en état COUVERT (rien n'est découvert), et
   propose donc l'œil ouvert. */
.montant-bascule .montant-oeil-fermer { display: none; }
.montant-bascule.est-decouvert .montant-oeil-ouvrir { display: none; }
.montant-bascule.est-decouvert .montant-oeil-fermer { display: inline-flex; }

/* ===========================================================================
   LE CADRE PHOTO DU POINTAGE · refait le 29/08/2026
   ---------------------------------------------------------------------------
   L'écran du guichet portait ses styles EN LIGNE, dans l'attribut `style`
   de chaque balise : un aperçu de 180 × 135 px posé à côté d'un bouton,
   sans état visible, sans place pour reprendre une prise ratée. Sur le
   téléphone d'un chef d'équipe, le tout se repliait en une colonne où la
   vidéo occupait un timbre-poste.

   TROIS CHOSES QUE CE STYLE DOIT TENIR :

   1. ON VOIT CE QUI PART. L'aperçu est grand, et la prise figée s'affiche
      À LA MÊME TAILLE, au même endroit · comparer un visage flou à un
      visage net dans deux cadres différents ne veut rien dire.
   2. LE DÉCLENCHEUR EST GROS, ET « SANS PHOTO » AUSSI. À six heures du
      matin, devant vingt personnes, on ne vise pas un bouton de 16 px.
      Les deux issues sont à la même hauteur : aucune n'est un piège.
   3. LA COLONNE NE SAUTE PAS entre le direct et la prise figée · un cadre
      qui change de taille au déclenchement décale tout ce qui suit et
      fait rater le bouton qu'on visait.
   =========================================================================== */
.cadre-photo {
  display: flex;
  gap: 16px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 14px;
  padding: 14px;
  border-radius: 14px;
  border: 1px solid var(--g-bordure, #E3E3E3);
  background: var(--g-fond-doux, #FAFAF7);
}

/* Le cadre de l'image · il garde SA taille, quoi qu'on y mette. */
.cadre-photo-vue {
  position: relative;
  width: 240px;
  aspect-ratio: 4 / 3;
  border-radius: 12px;
  overflow: hidden;
  background: #11150F;
  flex: none;
}
.cadre-photo-vue > video,
.cadre-photo-vue > img {
  position: absolute;
  /* PAS DE RACCOURCI « inset » · il demande Chrome 87, et les WebView des
     tablettes de terrain sont plus anciennes. Sans les quatre côtés
     écrits en toutes lettres, la prise figée se poserait n'importe où
     dans le cadre au lieu de recouvrir le direct. Voir
     apps.core.accounts.tests_mobile.RaccourciInsetInterditTests. */
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* CACHÉ VEUT DIRE CACHÉ · `position: absolute` l'emporterait sur le
   `display: none` que le navigateur attache à [hidden], et la prise figée
   resterait posée sur le direct. La règle qui referme cela vit dans
   tokens.css, AVEC SON `!important`, et elle y vit SEULE · la recopier
   ici ferait deux versions d'un même correctif, qui finissent toujours
   par ne plus dire la même chose (CacheVeutDireCacheTests). */

.cadre-photo-cote {
  flex: 1 1 220px;
  min-width: 0;
}
.cadre-photo-cote .sous-titre { margin: 0 0 10px; }

.cadre-photo-gestes {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}

/* LE CADRE DE PRISE · il s'ouvre APRÈS le geste, sous l'aperçu. */
.cadre-photo-prise {
  margin-top: 12px;
  padding: 14px;
  border-radius: 14px;
  border: 2px solid var(--g-vert, #0E3A25);
  background: var(--g-blanc, #FFF);
}
.cadre-photo-qui {
  margin: 0 0 10px;
  font-weight: 700;
  font-size: 15px;
}
/* Les gestes du cadre sont des cibles TACTILES · 44 px, la hauteur qu'un
   pouce atteint sans regarder. */
.cadre-photo-prise .btn {
  min-height: 44px;
  padding-inline: 18px;
}

@media (max-width: 560px) {
  /* Sur téléphone, l'aperçu prend la largeur · c'est le visage qu'on
     regarde, pas les boutons. */
  .cadre-photo-vue { width: 100%; }
  .cadre-photo-prise .btn { flex: 1 1 100%; justify-content: center; }
}
