/* ===========================================================================
   EXPLORiT Manager 3 — feuille de style
   ===========================================================================

   La charte est celle de la version 2.1, reprise à l'identique : mêmes
   couleurs, mêmes classes, mêmes proportions. L'utilisateur doit retrouver
   son logiciel, pas en découvrir un autre.

   DEUX VOCABULAIRES, UNE SEULE CHARTE
   -----------------------------------
   Les classes historiques de la 2.1 (.card, .btn, .field, .tablewrap…) sont
   conservées telles quelles : c'est ce que l'œil connaît, et ce que reprennent
   les blocs écrits à la main dans app.html.

   assets/app.js, lui, construit ses éléments avec des noms français
   (.carte, .bouton, .champ, .saisie, .tableau, .dialogue, .notification…).
   Plutôt que de dupliquer la feuille, chaque règle porte les deux noms :
   .btn et .bouton reçoivent exactement la même déclaration. Une couleur ne
   se corrige donc qu'à un seul endroit, et les deux vocabulaires ne peuvent
   pas diverger — c'est la même erreur qu'en 2.1, où trois listes de statuts
   de commande vivaient chacune de leur côté.

   CE QUI CHANGE PAR RAPPORT A LA 2.1
   ----------------------------------
   - Tout le style est ici. La Content-Security-Policy refuse le style en
     ligne (style-src 'self' sans 'unsafe-inline') : plus un seul attribut
     style= dans le HTML, plus un seul element.style.display dans le
     JavaScript. Montrer ou cacher se fait avec l'attribut hidden, d'où la
     règle [hidden] plus bas.
   - Ajouts : écrans de connexion et de mot de passe, fil de suivi d'un
     ticket, pastilles de statut, pagination, erreurs de champ, bandeaux de
     notification, état de chargement, états vides.
   - :focus-visible partout. La 2.1 n'avait aucun état de focus : au clavier,
     on ne savait jamais où l'on se trouvait.

   Sommaire
     1.  Variables et base
     2.  Accessibilité (focus, libellés masqués)
     3.  Mise en page générale
     4.  Panneaux de rubrique
     5.  Blocs de contenu (cartes, grilles, barres d'outils)
     6.  Boutons
     7.  Champs et formulaires
     8.  Tableaux
     9.  Badges, criticités et pastilles de statut
     10. Arborescence
     11. Écrans de connexion et de mot de passe
     12. Fenêtres modales et boîtes de confirmation
     13. Fil de suivi d'un ticket
     14. Pagination
     15. Notifications, chargement, états vides
     16. Tableau de bord
     17. Rapports et statistiques
     18. Écrans étroits
     19. Impression
   =========================================================================== */


/* ===========================================================================
   1. Variables et base — reprises telles quelles de la 2.1
   =========================================================================== */

:root{
  --bg:#07111f;
  --panel:#0d1b2d;
  --panel2:#132b47;
  --line:#29425f;
  --text:#edf5ff;
  --muted:#9cb0c7;
  --accent:#21a5ff;
  --accent2:#16d1e8;
  --danger:#ff5a68;
  --warn:#ffb84d;
  --ok:#39d98a;
  color-scheme:dark;
}

*{box-sizing:border-box}

body{
  margin:0;
  font-family:Segoe UI,Arial,sans-serif;
  background:var(--bg);
  color:var(--text);
}

/* Règle centrale de la version 3 : c'est l'attribut hidden qui masque, jamais
   un style en ligne. Le !important est nécessaire parce que les éléments
   masqués portent par ailleurs display:grid, flex ou block. */
[hidden]{display:none!important}

h1,h2,h3{line-height:1.25}
h2{font-size:19px;margin:0 0 10px}
h3{font-size:16px}
a{color:var(--accent)}


/* ===========================================================================
   2. Accessibilité
   =========================================================================== */

/* Libellé lu par les lecteurs d'écran mais invisible à l'œil : permet de
   donner un <label for> à chaque champ sans alourdir la barre d'outils.
   Aucun champ de la 2.1 n'avait de libellé associé. */
.sr-only{
  position:absolute;
  width:1px;height:1px;
  padding:0;margin:-1px;
  overflow:hidden;
  clip:rect(0 0 0 0);
  clip-path:inset(50%);
  white-space:nowrap;border:0;
}

/* État de focus visible partout : c'est le seul repère de qui navigue au
   clavier ou au lecteur d'écran. */
:focus-visible{
  outline:2px solid var(--accent2);
  outline-offset:2px;
  border-radius:6px;
}
.nav button:focus-visible{outline-offset:-2px}

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


/* ===========================================================================
   3. Mise en page générale
   =========================================================================== */

.app{display:grid;grid-template-columns:260px 1fr;min-height:100vh}

.sidebar{
  background:#091624;
  border-right:1px solid var(--line);
  padding:22px;
  position:sticky;top:0;height:100vh;overflow:auto;
}
.brand{font-size:24px;font-weight:800;margin-bottom:4px}
.version{color:var(--muted);font-size:13px;margin-bottom:20px}

.nav button{
  width:100%;text-align:left;
  background:transparent;border:0;color:var(--text);
  padding:11px 12px;border-radius:9px;margin:3px 0;
  cursor:pointer;font-size:15px;
}
/* .active vient de la 2.1, .actif est la classe posée par app.js. */
.nav button:hover,
.nav button.active,
.nav button.actif{background:#12345a}

/* Mention discrète des rubriques réservées, comme en 2.1. Purement
   informative : le droit réel est vérifié par le serveur à chaque appel,
   et l'entrée elle-même est retirée par app.js via data-droit. */
.nav button[data-protected="direction"]::after{content:"  Direction";float:right;font-size:10px;color:#8fb3d9}
.nav button[data-protected="admin"]::after{content:"  Admin";float:right;font-size:10px;color:#8fb3d9}

/* Panneau du compte connecté, en bas de la barre latérale. */
.userbox{
  margin-top:16px;padding:10px;
  border:1px solid var(--line);border-radius:9px;
  font-size:12px;
}
.userbox-title{font-weight:700;margin-bottom:5px}
.userbox .btn{margin-top:5px}
.full,.bouton--large{width:100%}

.main{padding:22px;min-width:0}

.topbar{
  display:flex;gap:10px;align-items:center;justify-content:space-between;
  flex-wrap:wrap;margin-bottom:18px;
}
.search{
  flex:1;min-width:240px;max-width:650px;
  background:#0b1929;border:1px solid var(--line);color:var(--text);
  padding:11px 13px;border-radius:10px;
}


/* ===========================================================================
   4. Panneaux de rubrique

   La 2.1 montrait une section en lui ajoutant la classe .active. app.js
   bascule désormais l'attribut hidden, qui dit la même chose au navigateur
   ET aux lecteurs d'écran. Les deux écritures sont acceptées ici : un
   panneau porteur de data-panneau est visible par défaut et disparaît dès
   qu'il est hidden (règle [hidden] plus haut, en !important).
   =========================================================================== */

.section{display:none}
.section.active{display:block}
.section[data-panneau]{display:block}


/* ===========================================================================
   5. Blocs de contenu
   =========================================================================== */

.card,.carte{
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:13px;
  padding:17px;margin-bottom:15px;
}
.carte__titre{margin:0 0 4px;font-size:22px;font-weight:800}
.carte__sous-titre{margin:0 0 16px;color:var(--muted);font-size:14px}
.carte__aide{margin:14px 0 0;color:var(--muted);font-size:12px}
.carte__actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}

.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
.metric strong{font-size:28px;display:block}

.muted,.note{color:var(--muted)}
.note{font-size:13px}
.spaced{margin-bottom:10px}
.count{margin-bottom:10px;font-size:13px}

/* Une valeur et son étiquette sur une même ligne, utilisé par app.js. */
.ligne{display:flex;align-items:center;justify-content:space-between;gap:12px}

.toolbar{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}

/* Lien vers un site fournisseur. Il s'ouvre dans un onglet neuf ; le
   Referrer-Policy du serveur empêche de divulguer l'adresse interne. */
.lien-externe::after{content:" ↗";font-size:11px;color:var(--muted)}


/* ===========================================================================
   6. Boutons
   =========================================================================== */

.btn,.bouton,select,input,textarea{font:inherit}

.btn,.bouton{
  border:1px solid var(--line);
  background:#10233a;color:var(--text);
  padding:9px 12px;border-radius:9px;
  cursor:pointer;text-decoration:none;display:inline-block;
}
.btn.primary,.bouton--principal{background:linear-gradient(90deg,#168ee8,#16c5e0);border:0;color:white}
.btn.danger,.bouton--danger{background:#381923;border-color:#71303c;color:var(--text)}
.bouton--secondaire{background:#10233a}
.btn:disabled,.bouton:disabled{opacity:.5;cursor:not-allowed}

/* En-tête de colonne triable : le tri est une action, donc un vrai bouton,
   atteignable au clavier. */
.bouton--tri{
  background:transparent;border:0;color:inherit;
  font-weight:700;padding:0;cursor:pointer;
}
.bouton--tri:hover{color:var(--accent2)}


/* ===========================================================================
   7. Champs et formulaires
   =========================================================================== */

select,input,textarea,.saisie{
  background:#0b1929;color:var(--text);
  border:1px solid var(--line);border-radius:8px;padding:9px;
}
textarea{min-height:90px;width:100%}

.field,.champ{margin-bottom:2px}
.field label,.champ__libelle{display:block;color:var(--muted);font-size:12px;margin-bottom:4px}
.field input,.field select,.field textarea,
.champ .saisie{width:100%}

/* L'astérisque des champs obligatoires est décorative : elle porte
   aria-hidden, et c'est l'attribut required qui informe réellement. */
.champ__requis{color:var(--danger)}
.champ__aide{margin:4px 0 0;color:var(--muted);font-size:12px}

/* Message d'erreur d'un champ, rempli par le serveur (réponse 422). */
.champ__erreur,.field-error{
  color:#ff8b96;font-size:12px;margin:4px 0 0;
}
input[aria-invalid="true"],
select[aria-invalid="true"],
textarea[aria-invalid="true"],
.saisie[aria-invalid="true"]{border-color:var(--danger)}

/* Case à cocher : ni pleine largeur, ni fond sombre. */
.saisie--case{width:auto;accent-color:var(--accent);padding:0}

/* Message portant sur tout un formulaire. La hauteur est réservée pour que
   son apparition ne fasse pas sauter le bouton sous le curseur. */
.message,.formerror{
  margin:10px 0 0;font-size:13px;min-height:18px;
  border-radius:9px;
}
.message{padding:9px 11px;border:1px solid var(--line);background:#10233a}
.message--erreur{color:#ffd7db;border-color:#71303c;background:#381923}
.message--attention{color:#ffe6c2;border-color:#6b4a17;background:#33260f}
.message--succes{color:#cdf7e4;border-color:#1f6b4c;background:#123324}
.formerror{color:#ff8b96}

.formgrid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.formgrid.single{grid-template-columns:1fr}
.wide{grid-column:1/-1}

/* Champ posé dans une barre d'outils : libellé et champ côte à côte.
   Remplace les <label class="btn">Mois <input style="margin-left:8px"> de la
   2.1, dont le style en ligne est désormais interdit. */
.field.inline{display:flex;align-items:center;gap:8px;margin-bottom:0}
.field.inline label{margin:0;white-space:nowrap}
.field.inline input,.field.inline select{width:auto}

/* Champ fichier : masqué à l'œil, mais toujours atteignable au clavier.
   Le <label for> associé lui sert de bouton, sans attribut onchange. */
.filefield{position:absolute;width:1px;height:1px;opacity:0;overflow:hidden;z-index:-1}
label.btn:has(+ .filefield:focus-visible){outline:2px solid var(--accent2);outline-offset:2px}


/* ===========================================================================
   8. Tableaux
   =========================================================================== */

.tablewrap,.tableau-defilant{
  overflow:auto;
  border:1px solid var(--line);border-radius:12px;
  background:var(--panel);
}
table,.tableau{border-collapse:collapse;width:100%;min-width:900px}
th,td{padding:10px 11px;border-bottom:1px solid #1f3852;text-align:left;vertical-align:top}
th{position:sticky;top:0;background:#102640;z-index:1}
tbody tr:hover{background:#101f33}

th[aria-sort]{color:var(--accent2)}

/* Colonnes de nombres : alignées à droite pour se comparer d'un coup d'œil. */
td.num,th.num{text-align:right;font-variant-numeric:tabular-nums}

/* Ligne ouvrant une fiche. Elle porte data-id, jamais son rang : en 2.1,
   Modifier et Supprimer agissaient par POSITION dans le tableau affiché, si
   bien qu'un tri suffisait à faire porter l'action sur la mauvaise ligne. */
.ligne-cliquable{cursor:pointer}
.ligne-cliquable:hover{background:#12253c}
.ligne-cliquable:focus-visible{outline:2px solid var(--accent2);outline-offset:-2px}

/* Boutons d'action d'une ligne. */
.rowactions{display:flex;gap:6px;flex-wrap:wrap}
.rowactions .btn,.rowactions .bouton{padding:5px 9px;font-size:13px}


/* ===========================================================================
   9. Badges, criticités et pastilles de statut
   =========================================================================== */

.badge{
  display:inline-block;
  padding:4px 8px;border-radius:20px;
  background:#17304b;font-size:12px;white-space:nowrap;
}

/* Les cinq familles posées par app.js. Un seul endroit décide de la couleur
   d'un état, au lieu des correspondances éparpillées de la 2.1. */
.badge--succes{background:#174b35}
.badge--attention{background:#5c3a12}
.badge--danger{background:#5b1d2a}
.badge--info{background:#1b3d63}
.badge--neutre{background:#17304b;color:var(--muted)}

/* Criticités et priorités : mêmes valeurs, même code couleur qu'en 2.1. */
.crit-Critique{background:#5b1d2a}
.crit-Haute{background:#5c3a12}
.crit-Normale{background:#17304b}
.crit-Faible{background:#182c3f;color:var(--muted)}

/* États d'équipement (2.1) et statuts de ticket (nouveaux), sous la même
   convention de nommage : le statut, espaces remplacés par des tirets. */
.status-Hors-service,.status-À-réparer{background:#5b1d2a}
.status-Intervention-en-cours,.status-En-cours{background:#5c3a12}
.status-À-contrôler,.status-En-attente{background:#4a3212}
.status-Fonctionnel,.status-Reçu,.status-Terminée,.status-Terminé{background:#174b35}
.status-Nouveau{background:#1b3d63}
.status-Assigné{background:#173a5c}
.status-Annulé{background:#2a2330;color:var(--muted)}
.status-À-faire,.status-À-reprendre{background:#4a3212}

/* Statuts de commande. « À commander » existe enfin : en 2.1, trois listes
   divergentes cohabitaient et cette valeur n'était sélectionnable nulle part. */
.status-À-définir,.status-À-commander{background:#2f3f55}
.status-Demandé,.status-Commandé{background:#1b3d63}

/* Pastille ronde devant un libellé, pour repérer un état sans le lire. */
.pastille{
  display:inline-block;width:9px;height:9px;border-radius:50%;
  margin-right:6px;background:currentColor;
}
.pastille.ouvert{background:var(--warn)}
.pastille.retard{background:var(--danger)}
.pastille.clos{background:var(--ok)}


/* ===========================================================================
   10. Arborescence (Vue d'ensemble)
   =========================================================================== */

.tree{display:grid;gap:10px}
.treegroup{background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden}
.treetitle{background:var(--panel2);padding:12px 14px;font-weight:800}
.treezone{
  padding:10px 14px;border-top:1px solid var(--line);
  display:flex;justify-content:space-between;gap:12px;
  cursor:pointer;width:100%;text-align:left;
  background:transparent;color:inherit;font:inherit;
  border-left:0;border-right:0;border-bottom:0;
}
.treezone:hover{background:#102640}


/* ===========================================================================
   11. Écrans de connexion et de mot de passe

   Plein cadre : tant que la session n'est pas établie, il n'y a rien
   derrière. En 2.1, « l'accès Direction » n'était qu'un panneau masqué
   par-dessus une application déjà chargée, et il suffisait d'écrire une
   valeur dans le stockage du navigateur pour passer outre.

   .modalback est conservée : c'est la classe de la 2.1, et elle sert encore
   à tout fond plein cadre.
   =========================================================================== */

.modalback,.ecran{
  position:fixed;inset:0;
  display:flex;align-items:center;justify-content:center;
  z-index:5000;padding:20px;overflow:auto;
}
.modalback{background:#0009}
.ecran{background:linear-gradient(135deg,#07111f,#12325a)}

.carte--connexion,.carte--motdepasse,.authmodal{width:min(460px,96vw);margin:auto}
.carte--connexion .champ,.carte--motdepasse .champ{margin-bottom:12px}


/* ===========================================================================
   12. Fenêtres modales et boîtes de confirmation

   Ce sont des <dialog> natifs : Échap ferme, le focus reste piégé à
   l'intérieur, le fond est assombri par ::backdrop. La <div> modale de la
   2.1 ne faisait rien de cela, et le clavier continuait de circuler dans la
   page située derrière.
   =========================================================================== */

.modal,.dialogue{
  background:var(--panel);
  border:1px solid var(--line);border-radius:14px;
  width:min(760px,96vw);max-height:90vh;overflow:auto;padding:20px;
  color:var(--text);
}
dialog.modal,dialog.dialogue{margin:auto}
dialog.modal::backdrop,dialog.dialogue::backdrop{background:#000a}

/* Une confirmation est courte : elle ne doit pas occuper tout l'écran. */
.dialogue{width:min(460px,96vw)}
.dialogue.modal{width:min(760px,96vw)}
.dialogue--danger{border-color:#71303c}
.dialogue__titre{margin:0 0 8px;font-size:19px}
.dialogue__message{margin:0 0 16px;color:var(--muted)}
.dialogue__actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end;margin-top:15px}

.modal-large{width:min(980px,96vw)}
.modal-small{width:min(460px,96vw)}
.modalhead{display:flex;justify-content:space-between;align-items:flex-start;gap:15px}
.modalactions{margin-top:15px}


/* ===========================================================================
   13. Fil de suivi d'un ticket

   Commentaires et changements de statut sont des événements horodatés et
   signés, affichés dans l'ordre. L'historique ne se réécrit pas : c'est ce
   qui manquait en 2.1, où modifier une intervention effaçait silencieusement
   sa durée, son coût et ses pièces.
   =========================================================================== */

.thread{display:grid;gap:10px;margin:10px 0 16px}

.comment{
  border:1px solid var(--line);border-left:3px solid var(--accent);
  border-radius:10px;background:#10233a;padding:10px 12px;
}
.comment-head{
  display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;
  font-size:12px;margin-bottom:6px;
}
.comment-author{font-weight:700}
.comment-date{color:var(--muted)}
/* pre-wrap : les retours à la ligne saisis sont conservés, et le texte reste
   du texte — il est posé par textContent, jamais interprété comme du HTML. */
.comment-body{white-space:pre-wrap;word-break:break-word}

/* Événement système (assignation, changement de statut, clôture) : plus
   discret qu'un commentaire écrit par quelqu'un. */
.comment.event{
  border-left-color:var(--line);
  background:transparent;color:var(--muted);font-size:13px;
}
.comment.cloture{border-left-color:var(--ok)}
.comment.reouverture{border-left-color:var(--warn)}

.comment-form{border-top:1px solid var(--line);padding-top:12px}
.comment-form label{display:block;color:var(--muted);font-size:12px;margin-bottom:4px}


/* ===========================================================================
   14. Pagination

   Nouvelle : la 2.1 affichait toutes les lignes d'un coup, ce qui devenait
   illisible et lent au-delà de quelques centaines d'éléments.
   =========================================================================== */

.pagination{
  display:flex;gap:6px;align-items:center;justify-content:center;flex-wrap:wrap;
  margin-top:12px;
}
.pagination:empty{display:none}
.pagination .btn,.pagination .bouton{padding:6px 11px;font-size:13px}
.pagination .btn[aria-current="page"],
.pagination .bouton[aria-current="page"]{background:var(--panel2);border-color:var(--accent)}
.pageinfo{color:var(--muted);font-size:13px;padding:0 8px}


/* ===========================================================================
   15. Notifications, chargement, états vides
   =========================================================================== */

/* Pile de bandeaux, en haut à droite. Remplace les alert() bloquants de la
   2.1 : sur le poste tactile de l'atelier, chaque enregistrement obligeait à
   cliquer sur « OK » avant de pouvoir continuer la saisie. */
.notifications{
  position:fixed;top:14px;right:14px;z-index:6000;
  display:grid;gap:8px;
  max-width:min(420px,92vw);
}
.notification,.notice{
  display:flex;align-items:flex-start;gap:10px;
  background:var(--panel2);
  border:1px solid var(--line);border-left:4px solid var(--accent);
  border-radius:11px;padding:12px 14px;
  box-shadow:0 6px 22px #0007;
}
.notice{position:fixed;top:14px;right:14px;z-index:6000;max-width:min(420px,92vw)}

.notification--succes,.notice.ok{border-left-color:var(--ok)}
.notification--erreur,.notice.erreur{border-left-color:var(--danger)}
.notification--attention,.notice.avertissement{border-left-color:var(--warn)}
.notification--info{border-left-color:var(--accent)}

.notification__texte{flex:1;font-size:14px;word-break:break-word}
.notification__fermer,.notice-close{
  background:transparent;border:0;color:var(--muted);
  font-size:18px;line-height:1;cursor:pointer;padding:0 2px;
}
.notification__fermer:hover,.notice-close:hover{color:var(--text)}

/* Barre de chargement, en haut de la fenêtre. */
.chargement,.loading{position:fixed;top:0;left:0;right:0;height:3px;z-index:7000;background:#163152}
.chargement__barre,.loading-bar{
  display:block;height:100%;width:35%;
  background:linear-gradient(90deg,var(--accent),var(--accent2));
  animation:glisse 1s linear infinite;
}
@keyframes glisse{from{transform:translateX(-100%)}to{transform:translateX(400%)}}

/* Conteneur en cours de rafraîchissement : atténué et inerte, pour qu'on ne
   clique pas sur des données qui vont être remplacées. */
.est-en-chargement,.is-loading{opacity:.45;pointer-events:none}

/* Un tableau vide dit pourquoi il est vide plutôt que de ne rien afficher. */
.message-vide,.empty{
  border:1px dashed var(--line);border-radius:12px;
  padding:26px;text-align:center;color:var(--muted);
}


/* ===========================================================================
   16. Tableau de bord
   =========================================================================== */

.dashlink{cursor:pointer;transition:.15s transform,.15s border-color}
.dashlink:hover{transform:translateY(-2px);border-color:var(--accent)}
.dashlink small{display:block;color:var(--muted);margin-top:7px}
/* Une carte cliquable est un vrai bouton : elle doit donc s'afficher comme
   une carte, et non comme un bouton de formulaire. */
button.dashlink{width:100%;text-align:left;color:inherit;font:inherit}

.watchlist{display:grid;gap:8px}
.watchrow{display:flex;align-items:center;justify-content:space-between;gap:15px;border-top:1px solid var(--line);padding:10px 0}


/* ===========================================================================
   17. Rapports et statistiques
   =========================================================================== */

.report-metrics{display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin:14px 0}
.report-metrics>div{border:1px solid var(--line);border-radius:10px;padding:12px;background:#10233a}
.report-metrics strong{font-size:24px;display:block}
.report-metrics span{color:var(--muted);font-size:12px}

.monthly-report h3{margin-top:24px}
.report-head{display:flex;justify-content:space-between;align-items:flex-start}

/* Aperçu avant impression : feuille blanche au format A4 posée sur un fond
   gris, pour voir ce qui sortira réellement de l'imprimante. */
.report-preview-card{background:#dfe4ea;padding:22px;overflow:auto}
.preview-label{color:#52606d;font-size:12px;font-weight:700;letter-spacing:1px;margin-bottom:10px}
.report-preview-card .printable-report{background:white;color:#111;width:min(210mm,100%);min-height:240mm;margin:auto;padding:16mm;box-shadow:0 2px 15px #0004}
.report-preview-card .printable-report .muted{color:#555}
.report-preview-card .printable-report .tablewrap,
.report-preview-card .printable-report .tableau-defilant{background:white;border-color:#bbb}
.report-preview-card .printable-report table,
.report-preview-card .printable-report .tableau{color:#111;background:white}
.report-preview-card .printable-report th{background:#eef3f8;color:#111}
.report-preview-card .printable-report td{border-color:#ccc}
.report-preview-card .printable-report .badge{color:#111;background:#f0f0f0;border:1px solid #aaa}

.report-title-block{display:flex;justify-content:space-between;gap:20px;border-bottom:2px solid #1b5790;padding-bottom:12px;margin-bottom:16px}
.report-title-block h2,.report-title-block h3{margin:0 0 4px}
.report-period{font-size:14px;white-space:nowrap}

.two-col-stats{display:grid;grid-template-columns:1fr 1fr;gap:12px}


/* ===========================================================================
   18. Écrans étroits — même rupture qu'en 2.1 (900 px)
   =========================================================================== */

@media(max-width:900px){
  .app{grid-template-columns:1fr}
  .sidebar{position:static;height:auto}
  .grid{grid-template-columns:1fr 1fr}
  .formgrid{grid-template-columns:1fr}
  .report-metrics{grid-template-columns:1fr 1fr}
  .watchrow{align-items:flex-start;flex-direction:column}
  .two-col-stats{grid-template-columns:1fr}

  /* Ajouts : la barre d'outils et les messages doivent rester utilisables
     sur le téléphone posé à côté d'une machine en panne. */
  .toolbar{gap:6px}
  .toolbar .btn,.toolbar .bouton,.toolbar select,.toolbar input{flex:1 1 auto;min-width:0}
  .notifications{left:14px;right:14px;max-width:none}
  .notice{left:14px;right:14px;max-width:none}
  .modal,.dialogue{padding:15px}
}

/* Sur un vrai écran de téléphone, un tableau de dix colonnes ne se lit pas,
   même en le faisant défiler : chaque ligne devient un petit bloc, et
   l'en-tête de colonne est repris devant la valeur. C'est à cela que sert
   l'attribut data-colonne posé sur chaque cellule par app.js. */
@media(max-width:700px){
  .tablewrap,.tableau-defilant{border:0;background:transparent}
  table,.tableau{min-width:0;display:block}
  table thead,.tableau thead{display:none}
  table tbody,table tr,table td,
  .tableau tbody,.tableau tr,.tableau td{display:block;width:auto}
  table tr,.tableau tr{
    background:var(--panel);border:1px solid var(--line);
    border-radius:12px;margin-bottom:10px;padding:4px 6px;
  }
  table td,.tableau td{border-bottom:0;padding:6px 8px;display:flex;gap:10px;justify-content:space-between}
  table td[data-colonne]::before,
  .tableau td[data-colonne]::before{
    content:attr(data-colonne);
    color:var(--muted);font-size:12px;flex:0 0 40%;
  }
}


/* ===========================================================================
   19. Impression

   Seul le rapport s'imprime. Toute la navigation disparaît, ainsi que les
   éléments ajoutés en version 3 (bandeaux, pagination, modales), qui n'ont
   aucun sens sur papier.
   =========================================================================== */

@media print{
  .sidebar,.topbar,.toolbar,.btn,.bouton,
  .pagination,.notifications,.notice,.chargement,.loading,
  .modalback,.ecran,.userbox,dialog{display:none!important}

  .app{display:block}
  .main{padding:0}
  .section{display:none!important}
  #reports.active,
  [data-panneau="rapports"]:not([hidden]){display:block!important}

  body{background:white;color:black}
  .monthly-report .tablewrap,.monthly-report table,
  .monthly-report .tableau-defilant,.monthly-report .tableau{border-color:#aaa}
  .monthly-report th,.monthly-report td{color:black;border-color:#ccc}
  .monthly-report th{background:#eee}
  .monthly-report .badge{color:black;background:#eee}
  .report-metrics>div{background:white;border-color:#aaa}
  .muted,.note{color:#555}

  /* La carte d'aperçu redevient une simple feuille : ni fond gris, ni ombre,
     ni marge d'écran, sinon l'imprimante les reproduit. */
  .report-preview-card{background:white;padding:0}
  .preview-label{display:none}
  .report-preview-card .printable-report{box-shadow:none;width:auto;min-height:0;padding:0;margin:0}
}


/* ===========================================================================
   20. Écrans construits par assets/modules.js
   ===========================================================================

   Les treize modules (tableau de bord, vue d'ensemble, équipements, tickets,
   historique, maintenance, commandes, décorations, nettoyage, rapports,
   statistiques, corbeille, administration) posent un second jeu de classes,
   entièrement en français, que les sections 1 à 19 ne connaissaient pas
   encore. Elles sont définies ici, dans le MÊME vocabulaire visuel : les
   couleurs viennent toutes des variables de la section 1, les rayons et les
   espacements reprennent ceux des cartes et des boutons existants. Personne
   ne doit voir la couture entre les deux moitiés de cette feuille.

   Aucune valeur hexadécimale n'est écrite en dur : les nuances intermédiaires
   sont obtenues par color-mix() à partir de --danger, --warn, --ok, --accent,
   --accent2, --panel2 et --bg. Corriger une couleur de la charte se répercute
   donc partout, y compris ici — c'est l'erreur de la 2.1, où chaque écran
   redéfinissait ses propres teintes de statut.

   Sommaire de la section
     20.1  Nuances dérivées de la palette
     20.2  Fragments partagés par tous les écrans
     20.3  Champs, cases à cocher et messages d'erreur
     20.4  Tableaux : enveloppe, colonnes, lignes teintées
     20.5  Badges : les six familles de listes fermées
     20.6  Tableau de bord — cartes compteurs
     20.7  Vue d'ensemble — arborescence espace puis zone
     20.8  Tickets — liste et fiche
     20.9  Maintenance préventive — retards
     20.10 Commandes — relecture et reprises de l'ancien classeur
     20.11 Décorations et historique
     20.12 Nettoyage — puces de statut et création rapide
     20.13 Rapports — le document imprimable
     20.14 Statistiques — répartitions et chiffres vedettes
     20.15 Administration — onglets et états de compte
     20.16 Connexion et changement de mot de passe
     20.17 Écrans étroits
     20.18 Mouvement réduit
     20.19 Impression du rapport mensuel
   =========================================================================== */


/* ===========================================================================
   20.1 Nuances dérivées de la palette

   Quatre teintes sombres reprises de la 2.1 : rouge, orange, vert et neutre.
   Elles y étaient écrites en dur et recopiées d'un écran à l'autre. Elles
   sont ici RECALCULÉES à partir des couleurs vives de la charte, si bien
   qu'elles suivent automatiquement toute correction de --danger, --warn ou
   --ok, au lieu de diverger silencieusement.

   Les teintes « voile » servent aux fonds de ligne de tableau : assez pour
   signaler du coin de l'œil, assez peu pour que le texte reste lisible.
   =========================================================================== */

:root{
  /* Fonds de badge : les quatre couleurs sombres de la 2.1, reconstruites. */
  --fond-danger:color-mix(in srgb,var(--danger) 32%,var(--bg));
  --fond-warn:color-mix(in srgb,var(--warn) 34%,var(--bg));
  --fond-ok:color-mix(in srgb,var(--ok) 32%,var(--bg));
  --fond-info:color-mix(in srgb,var(--accent) 26%,var(--bg));
  --fond-neutre:var(--panel2);
  --fond-sourdine:color-mix(in srgb,var(--panel2) 70%,var(--bg));

  /* Voiles de ligne : une transparence faible sur la couleur d'alerte. */
  --voile-danger:color-mix(in srgb,var(--danger) 9%,transparent);
  --voile-warn:color-mix(in srgb,var(--warn) 9%,transparent);
  --voile-ok:color-mix(in srgb,var(--ok) 7%,transparent);
  --voile-accent:color-mix(in srgb,var(--accent) 7%,transparent);

  /* Surfaces intermédiaires entre --panel et --panel2. */
  --survol:color-mix(in srgb,var(--panel2) 55%,var(--panel));
  --rail:color-mix(in srgb,var(--line) 60%,var(--bg));
}


/* ===========================================================================
   20.2 Fragments partagés par tous les écrans

   En-tête d'écran, barres de filtres et d'actions, sections, paires
   « libellé : valeur ». Ces briques sont construites une seule fois dans
   modules.js et réutilisées par les treize modules : leur style est donc
   défini une seule fois ici.
   =========================================================================== */

/* modules.js réemploie le nom .section pour ses sous-sections de contenu,
   alors que la section 4 réserve .section aux panneaux de rubrique, masqués
   par défaut. Une sous-section est toujours DESCENDANTE d'un panneau : la
   règle ci-dessous la rend visible sans rien changer au comportement des
   panneaux eux-mêmes, qui restent pilotés par l'attribut hidden. */
[data-panneau] .section,
.section .section{display:block}

.entete-ecran{
  display:flex;align-items:flex-start;justify-content:space-between;
  gap:15px;flex-wrap:wrap;margin-bottom:16px;
}
.entete-titres{min-width:0;flex:1 1 260px}
.entete-titres h1{margin:0;font-size:24px;font-weight:800;line-height:1.25}
.sous-titre{margin:4px 0 0;color:var(--muted);font-size:14px}

/* Fiche d'un ticket : même en-tête, plus un fil d'Ariane de retour et la
   rangée de badges d'état. */
.entete-fiche{align-items:flex-start}
.fil-ariane{margin:0 0 6px}
.entete-badges{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}

.barre-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center}

/* Bandeau de filtres : posé comme une carte, pour se distinguer du contenu
   qu'il pilote. Il s'enroule dès qu'il manque de place. */
.barre-filtres{
  display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end;
  background:var(--panel);border:1px solid var(--line);border-radius:13px;
  padding:12px;margin-bottom:15px;
}
/* Variante posée à l'intérieur d'une section (fil d'un ticket) : le cadre
   ferait sinon une carte dans une carte. */
.barre-filtres-compacte{
  background:transparent;border:0;padding:0;margin-bottom:10px;
}

.filtre{display:flex;flex-direction:column;gap:4px;min-width:0;flex:0 1 200px}
.filtre>label{color:var(--muted);font-size:12px}
.filtre .saisie,.filtre select,.filtre input{width:100%}

.section-entete{
  display:flex;align-items:center;justify-content:space-between;
  gap:12px;flex-wrap:wrap;margin-bottom:10px;
}
.section-entete h1,.section-entete h2,.section-entete h3{margin:0}

/* Titre d'un bloc court, sans l'espace d'un titre de section. */
.titre-compact{margin:0 0 10px;font-size:16px;font-weight:700}

/* Paires « libellé : valeur » des fiches. */
.grille-donnees{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;
}
.donnee{display:flex;flex-direction:column;gap:3px;min-width:0}
.donnee-libelle{color:var(--muted);font-size:12px}
.donnee-valeur{word-break:break-word}

/* Cartouche d'identification d'un équipement, repris à l'identique d'un écran
   à l'autre pour qu'on reconnaisse la même machine du premier coup d'œil. */
.cartouche{display:flex;gap:8px;flex-wrap:wrap;align-items:baseline;min-width:0}
.cartouche-code{font-weight:700;font-variant-numeric:tabular-nums}
.cartouche-nom{min-width:0;word-break:break-word}
.cartouche-lieu{color:var(--muted);font-size:12px}

/* Précision secondaire glissée après une valeur, entre parenthèses. */
.precision{color:var(--muted);font-size:12px}

/* Boutons construits par modules.js. Ils portent toujours .bouton, plus un de
   ces modificateurs : la déclaration ne reprend donc que la couleur. Même
   rendu que .bouton--principal et .bouton--danger de la section 6. */
.bouton-principal{
  background:linear-gradient(90deg,
    color-mix(in srgb,var(--accent) 88%,var(--bg)),
    color-mix(in srgb,var(--accent2) 92%,var(--bg)));
  border-color:transparent;color:var(--text);font-weight:600;
}
.bouton-principal:hover:not(:disabled){
  background:linear-gradient(90deg,var(--accent),var(--accent2));
}
.bouton-danger{
  background:color-mix(in srgb,var(--danger) 20%,var(--bg));
  border-color:color-mix(in srgb,var(--danger) 45%,var(--line));
  color:var(--text);
}
.bouton-danger:hover:not(:disabled){
  background:color-mix(in srgb,var(--danger) 32%,var(--bg));
}
.bouton-discret{
  background:transparent;border-color:var(--line);color:var(--muted);
}
.bouton-discret:hover:not(:disabled){color:var(--text);background:var(--survol)}

/* En-tête de colonne triable : un vrai bouton, mais qui doit ressembler à un
   intitulé de colonne et non à un bouton de formulaire. */
.bouton-entete{
  background:transparent;border:0;padding:0;border-radius:4px;
  color:inherit;font-weight:700;text-align:left;cursor:pointer;
}
.bouton-entete:hover{color:var(--accent2)}

/* Position dans la pagination : « Page 2 sur 7 — 134 éléments ». */
.pagination-position{
  color:var(--muted);font-size:13px;padding:0 8px;
  font-variant-numeric:tabular-nums;
}

/* État vide : la même phrase, au même endroit, sur les treize écrans. Elle
   est discrète — c'est une information, pas une alerte. */
.etat-vide{
  border:1px dashed var(--line);border-radius:12px;
  padding:26px;margin:0;text-align:center;color:var(--muted);font-size:14px;
}

/* modules.js pose .chargement sur un paragraphe d'attente, alors que la
   section 15 réserve ce nom à la barre fixe de trois pixels. Le sélecteur
   p.chargement est plus précis : il rend au paragraphe son flux normal. */
p.chargement{
  position:static;height:auto;background:transparent;
  padding:22px;text-align:center;color:var(--muted);font-size:14px;
}


/* ===========================================================================
   20.3 Champs, cases à cocher et messages d'erreur

   La validation vient du serveur (réponse 422) : chaque message est posé sous
   le champ qu'il concerne, jamais dans une alerte globale qu'il faudrait
   relire en entier pour retrouver la ligne fautive.
   =========================================================================== */

.formulaire{display:block}
.aide{margin:4px 0 0;color:var(--muted);font-size:12px}

/* Message rattaché à un champ précis. Il reste masqué par hidden tant que le
   serveur n'a rien reproché à ce champ. */
.erreur-champ{
  margin:4px 0 0;font-size:12px;
  color:color-mix(in srgb,var(--danger) 65%,var(--text));
}

/* Message portant sur tout un formulaire, ou sur un chargement qui a échoué. */
.message-erreur{
  margin:10px 0 0;font-size:13px;
  color:color-mix(in srgb,var(--danger) 65%,var(--text));
}
/* Dans un formulaire de dialogue, il fait bandeau au-dessus des champs. */
.formulaire>.message-erreur,
.creation-rapide>.message-erreur{
  margin:0 0 12px;padding:9px 11px;border-radius:9px;
  border:1px solid color-mix(in srgb,var(--danger) 45%,var(--line));
  background:color-mix(in srgb,var(--danger) 14%,var(--bg));
}

/* Case à cocher : ni pleine largeur, ni fond sombre, ni bordure de saisie. */
.case{
  width:auto;padding:0;margin:0;
  accent-color:var(--accent);background:transparent;border:0;
  vertical-align:middle;
}
.case-libelle{
  display:inline-flex;align-items:center;gap:7px;
  cursor:pointer;font-size:14px;
}


/* ===========================================================================
   20.4 Tableaux : enveloppe, colonnes, lignes teintées

   .tableau-enveloppe est le nom que modules.js donne à ce que la 2.1 appelait
   .tablewrap : même cadre, même défilement, même fond.

   Les classes .ligne-* teintent une ligne entière pour signaler un retard,
   une criticité ou une ligne à relire. La teinte reste volontairement très
   faible : elle doit se voir du coin de l'œil sans jamais gêner la lecture,
   et elle s'efface au survol au profit du surlignage habituel (la règle
   tbody tr:hover de la section 8 est plus précise, elle passe devant).
   =========================================================================== */

.tableau-enveloppe{
  overflow:auto;
  border:1px solid var(--line);border-radius:12px;
  background:var(--panel);
}

/* Colonnes de nombres : alignées à droite, chiffres de largeur fixe, pour se
   comparer en colonne sans avoir à compter les caractères. */
.colonne-nombre{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
th.colonne-nombre .bouton-entete{width:100%;text-align:right}

/* Colonne de boutons : elle prend le moins de place possible et ne se coupe
   jamais en deux lignes. */
.colonne-actions{width:1%;white-space:nowrap}

/* Colonne de texte libre (observations, pièces) : large, mais bornée, pour
   qu'une seule remarque n'écrase pas toutes les autres colonnes. */
.colonne-longue{min-width:200px;max-width:340px}

/* Texte coupé à la largeur de sa colonne ; la valeur entière reste lisible au
   survol, portée par l'attribut title. */
.texte-tronque{
  display:inline-block;max-width:100%;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  vertical-align:bottom;
}

.actions-ligne{display:flex;gap:6px;flex-wrap:wrap}
.actions-ligne .bouton{padding:5px 9px;font-size:13px}

/* --- Lignes signalées ---------------------------------------------------- */

/* Échéance dépassée : maintenance en retard. Le serveur a déjà tranché en
   heure suisse ; l'interface se contente de le montrer. */
.ligne-retard{background:var(--voile-danger)}
.date-retard{color:var(--danger);font-weight:700;font-variant-numeric:tabular-nums}
.marque-retard{color:var(--danger);font-size:12px;font-weight:700;white-space:nowrap}

/* Criticité d'un équipement. Normale et Faible ne se teintent pas : elles
   sont la majorité des lignes, et une couleur partout ne dit plus rien. */
.ligne-criticite-critique{background:var(--voile-danger)}
.ligne-criticite-haute{background:var(--voile-warn)}
.ligne-criticite-normale{background:transparent}
.ligne-criticite-faible{background:transparent}

/* Priorité d'un ticket : même échelle, même code couleur. */
.ligne-priorite-critique{background:var(--voile-danger)}
.ligne-priorite-haute{background:var(--voile-warn)}
.ligne-priorite-normale{background:transparent}
.ligne-priorite-faible{background:transparent}

/* État d'une décoration ou d'un équipement. */
.ligne-etat-bon-etat{background:transparent}
.ligne-etat-fonctionnel{background:transparent}
.ligne-etat-a-controler{background:var(--voile-warn)}
.ligne-etat-a-reparer{background:var(--voile-danger)}
.ligne-etat-hors-service{background:var(--voile-danger)}

/* Statut d'un contrôle de nettoyage. */
.ligne-statut-a-faire{background:transparent}
.ligne-statut-en-cours{background:var(--voile-warn)}
.ligne-statut-termine{background:var(--voile-ok)}
.ligne-statut-a-reprendre{background:var(--voile-danger)}

/* Ligne terminée ou reçue : le travail est fait, la ligne s'apaise. */
.ligne-close{background:var(--voile-ok)}

/* Ligne dont l'import a signalé une valeur douteuse, à relire avant de s'y
   fier. La 2.1 ne distinguait pas ces lignes des autres. */
.ligne-a-relire{background:var(--voile-warn)}
.marque-relecture{color:var(--warn);cursor:help;font-weight:700}

/* Reprise de l'ancien classeur : information de provenance, pas une alerte. */
.ligne-historique{background:var(--voile-accent)}
.marque-historique{color:var(--muted);font-size:11px;font-weight:700;cursor:help}

/* Ticket bloqué par une commande non reçue ; compte désactivé. */
.ligne-bloquee{background:var(--voile-danger)}
.ligne-inactive{color:var(--muted)}

/* Valeur restée en texte libre : la conversion en nombre, en date ou en
   francs n'a pas abouti. La saisie d'origine est consultable au survol. */
.valeur-texte{
  color:var(--muted);font-style:italic;cursor:help;
  border-bottom:1px dotted var(--line);
}


/* ===========================================================================
   20.5 Badges : les six familles de listes fermées

   modules.js construit chaque badge à partir de la valeur elle-même :
   badge(valeur, famille, valeur) donne « badge badge-statut badge-statut-recu ».
   Les suffixes correspondent donc exactement aux listes de
   src/Core/Enums.php, accents retirés et espaces en tirets.

   Code couleur repris de la 2.1, à l'identique :
     rouge  — critique, hors service, à réparer, à reprendre, annulé
     orange — haute, à contrôler, en cours, en attente, à faire
     vert   — fonctionnel, bon état, reçu, terminé
     neutre — tout le reste
   =========================================================================== */

.badge-etat,
.badge-criticite,
.badge-statut,
.badge-priorite,
.badge-categorie,
.badge-role,
.badge-action{
  display:inline-block;
  padding:4px 8px;border-radius:20px;font-size:12px;
  white-space:nowrap;background:var(--fond-neutre);
}

/* --- État d'un équipement et d'une décoration ---------------------------- */
.badge-etat-fonctionnel,
.badge-etat-bon-etat{background:var(--fond-ok)}
.badge-etat-a-controler{background:var(--fond-warn)}
.badge-etat-a-reparer,
.badge-etat-hors-service{background:var(--fond-danger)}
.badge-etat-intervention-en-cours{background:var(--fond-warn)}

/* --- Criticité et priorité : même échelle sur les deux familles ---------- */
.badge-criticite-critique,
.badge-priorite-critique{background:var(--fond-danger)}
.badge-criticite-haute,
.badge-priorite-haute{background:var(--fond-warn)}
.badge-criticite-normale,
.badge-priorite-normale{background:var(--fond-neutre)}
.badge-criticite-faible,
.badge-priorite-faible{background:var(--fond-sourdine);color:var(--muted)}

/* --- Statuts : ticket, maintenance, commande, nettoyage ------------------ */
/* Ticket */
.badge-statut-nouveau{background:var(--fond-info)}
.badge-statut-assigne{background:var(--fond-info)}
.badge-statut-en-cours{background:var(--fond-warn)}
.badge-statut-en-attente{background:var(--fond-warn)}
.badge-statut-terminee,
.badge-statut-termine{background:var(--fond-ok)}
.badge-statut-annule{background:var(--fond-sourdine);color:var(--muted)}
/* Maintenance */
.badge-statut-planifiee{background:var(--fond-info)}
/* Commande. « À commander » existe enfin : en 2.1, trois listes divergentes
   cohabitaient et cette valeur n'était sélectionnable nulle part. */
.badge-statut-a-definir,
.badge-statut-a-commander{background:var(--fond-sourdine)}
.badge-statut-demande,
.badge-statut-commande{background:var(--fond-info)}
.badge-statut-recu{background:var(--fond-ok)}
/* Nettoyage */
.badge-statut-a-faire{background:var(--fond-warn)}
.badge-statut-a-reprendre{background:var(--fond-danger)}

/* --- Catégorie d'une décoration ------------------------------------------ */
.badge-categorie-noel{background:var(--fond-ok)}
.badge-categorie-halloween{background:var(--fond-warn)}
.badge-categorie-paques{background:var(--fond-info)}
.badge-categorie-autre{background:var(--fond-sourdine);color:var(--muted)}

/* --- Rôle d'un compte. Les rôles étendus se repèrent d'un coup d'œil dans
       la liste des comptes : c'est le premier écran qu'on relit en audit. --- */
.badge-role-lecture{background:var(--fond-sourdine);color:var(--muted)}
.badge-role-technicien{background:var(--fond-neutre)}
.badge-role-responsable{background:var(--fond-info)}
.badge-role-direction{background:color-mix(in srgb,var(--accent2) 26%,var(--bg))}
.badge-role-qualite{background:var(--fond-ok)}
.badge-role-admin{background:var(--fond-danger)}

/* Badge d'un ticket clos, posé à côté du statut sur la fiche. */
.badge-clos{background:var(--fond-sourdine);color:var(--muted)}


/* ===========================================================================
   20.6 Tableau de bord — cartes compteurs

   La rangée de compteurs de la 2.1, reprise telle quelle : quatre colonnes,
   le nombre en très gros au-dessus d'un libellé discret. Chaque carte est un
   VRAI bouton — elle se clique, se tabule et s'annonce à un lecteur d'écran —
   mais elle doit ressembler à une carte, pas à un bouton de formulaire.
   =========================================================================== */

.cartes-compteurs{
  display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:15px;
}

.carte-compteur{
  display:flex;flex-direction:column;gap:5px;
  background:var(--panel);border:1px solid var(--line);border-radius:13px;
  padding:15px;text-align:left;color:inherit;font:inherit;
  cursor:pointer;min-width:0;
  transition:.15s transform,.15s border-color;
}
.carte-compteur:hover{transform:translateY(-2px);border-color:var(--accent)}
.carte-compteur:focus-visible{outline:2px solid var(--accent2);outline-offset:2px}

.carte-valeur{
  font-size:28px;font-weight:800;line-height:1.1;
  font-variant-numeric:tabular-nums;
}
.carte-libelle{color:var(--muted);font-size:13px}

/* Les trois teintes de compteur. Seule la VALEUR change de couleur : teinter
   la carte entière ferait un tableau de bord bariolé, où rien ne ressort. */
.carte-attention .carte-valeur{color:var(--accent2)}
.carte-critique .carte-valeur{color:var(--danger)}
.carte-retard .carte-valeur{color:var(--warn)}

/* Carte de compteur non cliquable : dans un rapport ou une page de
   statistiques, rien ne se clique. Elle ne doit donc ni lever le doigt au
   survol, ni afficher un curseur de main. */
.cartes-statiques{margin-bottom:15px}
.carte-statique{cursor:default;transition:none}
.carte-statique:hover{transform:none;border-color:var(--line)}


/* ===========================================================================
   20.7 Vue d'ensemble — arborescence espace puis zone

   Un bloc par espace, repliable ; les zones dessous, séparées par un filet.
   Les compteurs sont alignés à droite en chiffres tabulaires, pour se lire en
   colonne d'un espace à l'autre. Le repli passe par l'attribut hidden et
   aria-expanded : aucun style en ligne, comme partout ailleurs en version 3.
   =========================================================================== */

.arbre{display:grid;gap:10px;list-style:none;margin:0 0 14px;padding:0}

.arbre-espace{
  background:var(--panel);border:1px solid var(--line);
  border-radius:12px;overflow:hidden;
}

/* En-tête de l'espace : plus clair que le corps, comme .treetitle en 2.1. */
.arbre-ligne{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  background:var(--panel2);padding:12px 14px;
}

.arbre-bascule{
  display:flex;align-items:center;gap:8px;
  background:transparent;border:0;padding:0;border-radius:6px;
  color:inherit;font:inherit;font-weight:800;text-align:left;cursor:pointer;
  min-width:0;
}
.arbre-bascule:hover{color:var(--accent2)}
.arbre-marque{color:var(--muted);width:1em;flex:0 0 auto}
.arbre-nom{min-width:0;word-break:break-word}

.arbre-compteurs{
  display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  margin-left:auto;font-size:13px;
  font-variant-numeric:tabular-nums;
  justify-content:flex-end;text-align:right;
}
.compteur-total{color:var(--muted)}
.compteur-surveiller{
  color:var(--warn);font-weight:700;white-space:nowrap;
}

.arbre-zones{list-style:none;margin:0;padding:0}

.arbre-zone{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  border-top:1px solid var(--line);padding:10px 14px;
}
.arbre-zone:hover{background:var(--survol)}

.arbre-lien{
  background:transparent;border:0;padding:0;border-radius:6px;
  color:var(--accent);font:inherit;text-align:left;cursor:pointer;min-width:0;
}
.arbre-lien:hover{color:var(--accent2);text-decoration:underline}

/* Un espace sans zone se dit, plutôt que de laisser un bloc vide. */
.arbre-zone-vide{
  border-top:1px solid var(--line);padding:10px 14px;
  color:var(--muted);font-size:13px;font-style:italic;
}


/* ===========================================================================
   20.8 Tickets — liste et fiche
   =========================================================================== */

.titre-ticket{font-weight:700}

/* Un ticket sans destinataire n'est pris en charge par personne : le dire en
   gris italique, pas en rouge — ce n'est pas une erreur, c'est un état. */
.non-assigne{color:var(--muted);font-style:italic}

/* Le fil de suivi : commentaires et événements, dans l'ordre. */
.journal{display:grid;gap:10px;margin:10px 0 16px}


/* ===========================================================================
   20.9 Maintenance préventive — retards

   Le compteur de retards porte sur les MÊMES filtres que la liste : le
   chiffre affiché correspond toujours à ce qu'on voit en dessous.
   =========================================================================== */

.resume-ligne{
  display:flex;gap:16px;flex-wrap:wrap;align-items:baseline;
  border-bottom:1px solid var(--line);
  padding:0 0 10px;margin:0 0 12px;
  color:var(--muted);font-size:13px;
  font-variant-numeric:tabular-nums;
}
.resume-alerte{color:var(--danger);font-weight:700}
.resume-calme{color:var(--ok)}
.resume-total{
  margin:10px 0 0;color:var(--muted);font-size:13px;
  font-variant-numeric:tabular-nums;
}


/* ===========================================================================
   20.10 Commandes — relecture et reprises de l'ancien classeur

   « Commandes à traiter » inclut désormais les lignes reprises du classeur :
   en 2.1, elles étaient exclues du bloc parce que leur numéro contenait
   « HIST », et on ne les relançait jamais. Le liseré orange rappelle que
   cette section demande une action.
   =========================================================================== */

.section-a-traiter{
  border-left:3px solid var(--warn);
  padding-left:13px;
}


/* ===========================================================================
   20.11 Décorations et historique

   Rien de propre à ces deux écrans : ils réemploient les tableaux, les badges
   de catégorie et d'état, et les colonnes longues définis plus haut. Ce
   commentaire est là pour qu'on ne les cherche pas ailleurs.
   =========================================================================== */


/* ===========================================================================
   20.12 Nettoyage — puces de statut et création rapide

   Chaque puce est un raccourci vers son filtre : le chiffre et le mot sont
   cliquables ensemble. La création rapide reste en haut de l'écran, en
   pointillé, parce qu'elle n'est pas un contenu mais un formulaire.
   =========================================================================== */

.puces-statuts{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}

.puce-statut{
  display:inline-flex;align-items:baseline;gap:8px;
  background:var(--panel2);border:1px solid var(--line);border-radius:20px;
  padding:6px 13px;color:inherit;font:inherit;cursor:pointer;
  transition:.15s border-color,.15s background;
}
.puce-statut:hover{border-color:var(--accent)}
.puce-valeur{font-weight:800;font-variant-numeric:tabular-nums}
.puce-libelle{color:var(--muted);font-size:13px}

/* La couleur du chiffre reprend celle du statut, comme les badges. */
.puce-a-faire .puce-valeur{color:var(--warn)}
.puce-en-cours .puce-valeur{color:var(--accent2)}
.puce-termine .puce-valeur{color:var(--ok)}
.puce-a-reprendre .puce-valeur{color:var(--danger)}

/* Puce dont le filtre est actif : c'est elle qui explique ce qu'on voit. */
.puce-active{
  border-color:var(--accent);
  background:color-mix(in srgb,var(--accent) 16%,var(--panel2));
}

.creation-rapide{
  background:var(--panel);
  border:1px dashed color-mix(in srgb,var(--accent) 45%,var(--line));
  border-radius:13px;padding:15px;margin-bottom:15px;
}
.creation-rapide-champs{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));
  gap:12px;margin-bottom:12px;
}


/* ===========================================================================
   20.13 Rapports — le document imprimable

   Un seul rendu : celui qu'on voit à l'écran part tel quel à l'imprimante.
   La 2.1 réécrivait le rapport dans une fenêtre neuve avec document.write ;
   le papier ne montrait donc pas ce qui avait été relu.

   À l'écran, le rapport garde la charte sombre mais prend la largeur d'une
   feuille A4, pour qu'on voie déjà où tomberont les coupures. Le passage au
   blanc se fait en 20.19, dans @media print.
   =========================================================================== */

.rapport-imprimable{
  display:block;
  background:var(--panel);border:1px solid var(--line);border-radius:13px;
  padding:22px;max-width:210mm;margin:0 auto;
}

.rapport-entete{
  border-bottom:2px solid var(--accent);
  padding-bottom:12px;margin-bottom:18px;
}
.rapport-entete h1{margin:0 0 6px;font-size:22px;font-weight:800}
.rapport-periode,
.rapport-date{
  margin:2px 0;color:var(--muted);font-size:13px;
  font-variant-numeric:tabular-nums;
}

.rapport-metriques{margin:0 0 20px}
.rapport-metriques h2{margin:0 0 10px}

.rapport-imprimable section{margin-bottom:20px}
.rapport-imprimable h2{font-size:17px;margin:0 0 10px}

.rapport-pied{
  border-top:1px solid var(--line);
  margin-top:22px;padding-top:12px;
  color:var(--muted);font-size:12px;
}


/* ===========================================================================
   20.14 Statistiques — répartitions et chiffres vedettes

   Les barres de proportion remplacent les camemberts de la 2.1, illisibles
   au-delà de quatre parts. La longueur est portée par data-part (0 à 100 par
   pas de 5), calculée par le serveur : la Content-Security-Policy interdit le
   style en ligne, donc pas de width écrite par le JavaScript.
   =========================================================================== */

.encart-chiffre{
  background:var(--panel2);border:1px solid var(--line);border-radius:13px;
  padding:16px;
}
.chiffre-vedette{
  margin:0 0 6px;font-size:34px;font-weight:800;line-height:1.1;
  color:var(--accent2);font-variant-numeric:tabular-nums;
}

.repartition{display:grid;gap:8px;list-style:none;margin:0;padding:0}

.repartition-ligne{
  display:grid;
  grid-template-columns:minmax(110px,1fr) minmax(90px,2fr) 4.5em 4em;
  align-items:center;gap:10px;
}
.repartition-libelle{
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

/* La barre : un rail sombre, un remplissage en dégradé accent vers accent2,
   exactement le dégradé des boutons principaux et de la barre de chargement. */
.repartition-barre{
  display:block;height:10px;border-radius:20px;
  background:var(--rail);overflow:hidden;
}
.repartition-barre::before{
  content:"";display:block;height:100%;width:0;border-radius:inherit;
  background:linear-gradient(90deg,var(--accent),var(--accent2));
  transition:.2s width;
}
.repartition-barre[data-part="0"]::before{width:0}
.repartition-barre[data-part="5"]::before{width:5%}
.repartition-barre[data-part="10"]::before{width:10%}
.repartition-barre[data-part="15"]::before{width:15%}
.repartition-barre[data-part="20"]::before{width:20%}
.repartition-barre[data-part="25"]::before{width:25%}
.repartition-barre[data-part="30"]::before{width:30%}
.repartition-barre[data-part="35"]::before{width:35%}
.repartition-barre[data-part="40"]::before{width:40%}
.repartition-barre[data-part="45"]::before{width:45%}
.repartition-barre[data-part="50"]::before{width:50%}
.repartition-barre[data-part="55"]::before{width:55%}
.repartition-barre[data-part="60"]::before{width:60%}
.repartition-barre[data-part="65"]::before{width:65%}
.repartition-barre[data-part="70"]::before{width:70%}
.repartition-barre[data-part="75"]::before{width:75%}
.repartition-barre[data-part="80"]::before{width:80%}
.repartition-barre[data-part="85"]::before{width:85%}
.repartition-barre[data-part="90"]::before{width:90%}
.repartition-barre[data-part="95"]::before{width:95%}
.repartition-barre[data-part="100"]::before{width:100%}

.repartition-valeur{
  text-align:right;font-weight:700;font-variant-numeric:tabular-nums;
}
.repartition-part{
  text-align:right;color:var(--muted);font-size:13px;
  font-variant-numeric:tabular-nums;
}


/* ===========================================================================
   20.15 Administration — onglets et états de compte

   Les onglets ne cachent rien : ils rangent. Le droit réel est vérifié par le
   serveur à chaque appel, et un onglet inaccessible n'est pas construit.
   =========================================================================== */

.onglets{
  display:flex;gap:4px;flex-wrap:wrap;
  border-bottom:1px solid var(--line);margin-bottom:16px;
}
.onglet{
  background:transparent;border:0;border-bottom:2px solid transparent;
  margin-bottom:-1px;
  padding:10px 14px;border-radius:9px 9px 0 0;
  color:var(--muted);font:inherit;cursor:pointer;
  transition:.15s color,.15s background;
}
.onglet:hover{color:var(--text);background:var(--survol)}
.onglet:focus-visible{outline-offset:-2px}
.onglet-actif{
  color:var(--text);font-weight:700;
  border-bottom-color:var(--accent);
}

/* État d'un compte, dans la liste des comptes. */
.etat-actif{color:var(--ok);font-weight:700}
.etat-inactif{color:var(--muted)}
.etat-verrouille{color:var(--danger);font-weight:700;cursor:help}
.etat-attente{color:var(--warn)}


/* ===========================================================================
   20.16 Connexion et changement de mot de passe

   Les deux écrans pleine page de la section 11, avec leur nuance propre : le
   bleu de la charte pour la connexion, le cyan pour le changement de mot de
   passe, afin qu'on ne confonde pas les deux formulaires.
   =========================================================================== */

.ecran--connexion{
  background:linear-gradient(135deg,
    var(--bg),
    color-mix(in srgb,var(--accent) 24%,var(--bg)));
}
.ecran--motdepasse{
  background:linear-gradient(135deg,
    var(--bg),
    color-mix(in srgb,var(--accent2) 20%,var(--bg)));
}
/* Un liseré de couleur en tête de carte redit de quel écran il s'agit, même
   sans voir le fond. */
.ecran--connexion .carte--connexion{border-top:3px solid var(--accent)}
.ecran--motdepasse .carte--motdepasse{border-top:3px solid var(--accent2)}


/* ===========================================================================
   20.17 Écrans étroits

   Objectif : tout reste utilisable à 400 px de large, sur le téléphone posé à
   côté d'une machine en panne. Les grilles passent à une colonne, les barres
   de filtres s'enroulent, les compteurs s'empilent.
   =========================================================================== */

@media(max-width:900px){
  .cartes-compteurs{grid-template-columns:1fr 1fr}
  .repartition-ligne{grid-template-columns:minmax(90px,1fr) minmax(70px,1.6fr) 3.5em 3.5em;gap:8px}
  .rapport-imprimable{padding:16px}
}

@media(max-width:700px){
  /* Le tableau devient une pile de blocs (section 18) : son enveloppe ne doit
     plus dessiner de cadre autour du vide. */
  .tableau-enveloppe{border:0;background:transparent}
  .colonne-actions{width:auto;white-space:normal}
  .colonne-longue{max-width:none;min-width:0}
  .texte-tronque{white-space:normal;overflow:visible;text-overflow:clip}
}

@media(max-width:520px){
  .cartes-compteurs{grid-template-columns:1fr}
  .entete-ecran{flex-direction:column;align-items:stretch;gap:10px}
  .barre-filtres{padding:10px;gap:10px}
  .filtre{flex:1 1 100%}
  .barre-actions .bouton{flex:1 1 auto;text-align:center}
  .grille-donnees{grid-template-columns:1fr}
  .creation-rapide-champs{grid-template-columns:1fr}
  .arbre-ligne{align-items:flex-start}
  .arbre-compteurs{margin-left:0;width:100%;justify-content:flex-start;text-align:left}
  .arbre-zone{flex-direction:column;align-items:flex-start;gap:4px}
  /* Sous 520 px, la barre de proportion prend toute la largeur et ses deux
     chiffres passent dessous : la barre reste lisible, les nombres aussi. */
  .repartition-ligne{grid-template-columns:1fr auto auto;gap:6px}
  .repartition-barre{grid-column:1/-1;order:2}
  .repartition-libelle{grid-column:1}
  .repartition-valeur{grid-column:2}
  .repartition-part{grid-column:3}
  .entete-titres h1{font-size:20px}
  .carte-valeur{font-size:24px}
  .chiffre-vedette{font-size:28px}
  .rapport-imprimable{padding:12px;border-radius:0;border-left:0;border-right:0}
  .onglets{gap:0}
  .onglet{padding:9px 10px;font-size:14px;flex:1 1 auto;text-align:center}
  .puce-statut{flex:1 1 auto;justify-content:center}
}


/* ===========================================================================
   20.18 Mouvement réduit

   La section 2 neutralise déjà les animations. On y ajoute les déplacements
   propres aux écrans de modules.js : une carte qui se soulève au survol est
   un mouvement, pas une animation, et y échappait.
   =========================================================================== */

@media (prefers-reduced-motion:reduce){
  .carte-compteur,
  .puce-statut,
  .onglet,
  .repartition-barre::before{transition:none}
  .carte-compteur:hover{transform:none}
}


/* ===========================================================================
   20.19 Impression du rapport mensuel

   Un document A4 pour la Direction : fond blanc, texte noir, pas de barre
   latérale, tableaux à filets gris fins. C'est le SEUL endroit de cette
   feuille où !important est employé — l'impression doit passer devant tout le
   reste, y compris les fonds sombres posés à l'écran.

   imprimerRapport() pose la classe impression-rapport sur <body> et appelle
   window.print() : il ne construit rien. Les règles ci-dessous décident seules
   de ce qui part sur le papier.
   =========================================================================== */

@page{size:A4;margin:14mm}

@media print{
  /* Quand l'impression est lancée depuis le bouton, seul le panneau des
     rapports part à l'imprimante, quel que soit l'écran affiché. */
  body.impression-rapport [data-panneau]{display:none!important}
  body.impression-rapport [data-panneau="rapports"]{display:block!important}

  /* Ce qui pilote l'écran n'a aucun sens sur papier. */
  .entete-ecran,
  .barre-filtres,
  .barre-actions,
  .actions-ligne,
  .colonne-actions,
  .onglets,
  .puces-statuts,
  .creation-rapide,
  .pagination-position{display:none!important}

  /* La feuille elle-même. */
  .rapport-imprimable{
    background:white!important;color:black!important;
    border:0!important;border-radius:0!important;
    padding:0!important;margin:0!important;max-width:none!important;
  }
  .rapport-entete{border-bottom:2px solid black!important}
  .rapport-periode,
  .rapport-date,
  .rapport-pied,
  .resume-total,
  .rapport-imprimable .note,
  .rapport-imprimable .muted{color:color-mix(in srgb,black 65%,white)!important}
  .rapport-pied{border-top:1px solid color-mix(in srgb,black 35%,white)!important}

  /* Tableaux : filets gris fins, en-têtes sur fond très clair. Les en-têtes
     collants de l'écran deviendraient des bandes répétées sur le papier. */
  .rapport-imprimable .tableau-enveloppe{
    background:white!important;border:1px solid color-mix(in srgb,black 35%,white)!important;
    border-radius:0!important;overflow:visible!important;
  }
  .rapport-imprimable .tableau{
    color:black!important;background:white!important;min-width:0!important;
  }
  .rapport-imprimable th{
    position:static!important;
    background:color-mix(in srgb,black 8%,white)!important;color:black!important;
    border-bottom:1px solid color-mix(in srgb,black 45%,white)!important;
  }
  .rapport-imprimable td{
    color:black!important;
    border-bottom:1px solid color-mix(in srgb,black 20%,white)!important;
  }
  /* Une ligne teintée à l'écran ne doit pas consommer de l'encre : le rapport
     porte déjà l'information en toutes lettres dans ses colonnes. */
  .rapport-imprimable tr{background:transparent!important}

  /* Badges : lisibles en noir et blanc, sans aplat de couleur. */
  .rapport-imprimable .badge,
  .rapport-imprimable [class*="badge-"]{
    background:white!important;color:black!important;
    border:1px solid color-mix(in srgb,black 45%,white)!important;
  }

  /* Cartes de compteur du résumé : trois par ligne tiennent sur la largeur
     d'une A4 ; quatre serrent trop les libellés. */
  .rapport-imprimable .cartes-compteurs{grid-template-columns:repeat(3,1fr)!important}
  .rapport-imprimable .carte-compteur{
    background:white!important;color:black!important;
    border:1px solid color-mix(in srgb,black 35%,white)!important;
    border-radius:0!important;
  }
  .rapport-imprimable .carte-valeur{color:black!important}
  .rapport-imprimable .carte-libelle{color:color-mix(in srgb,black 65%,white)!important}

  /* Un titre ne doit pas rester seul en bas de page, ni un tableau se couper
     au milieu d'une ligne. */
  .rapport-imprimable section{break-inside:auto;page-break-inside:auto}
  .rapport-imprimable h2{break-after:avoid;page-break-after:avoid}
  .rapport-imprimable tr{break-inside:avoid;page-break-inside:avoid}
  .rapport-imprimable thead{display:table-header-group}

  /* Les états vides et les messages d'écran restent utiles sur papier : ils
     expliquent pourquoi un tableau du rapport est vide ce mois-là. */
  .etat-vide{
    border:1px dashed color-mix(in srgb,black 35%,white)!important;
    color:color-mix(in srgb,black 65%,white)!important;
  }
}

/* ===========================================================================
   Pièces jointes (photos, PDF) — tickets, commandes, nettoyages
   =========================================================================== */
.pieces{display:flex;flex-direction:column;gap:14px}
.galerie{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px;margin:0}
.vignette{margin:0;border:1px solid var(--line);border-radius:10px;overflow:hidden;background:var(--panel);display:flex;flex-direction:column}
.vignette-lien{display:block;aspect-ratio:4/3;background:var(--panel2);text-decoration:none}
.vignette-image{width:100%;height:100%;object-fit:cover;display:block}
.vignette-doc{display:flex;align-items:center;justify-content:center;height:100%;font-weight:700;font-size:20px;letter-spacing:.08em;color:var(--muted)}
.vignette-legende{display:flex;flex-direction:column;gap:4px;padding:8px 10px;font-size:12px}
.vignette-nom{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vignette-meta{color:var(--muted)}
.bouton-mini{padding:3px 8px;font-size:12px;align-self:flex-start}
.formulaire-pieces{display:flex;flex-wrap:wrap;align-items:flex-end;gap:10px}
.pieces-libelle{display:flex;flex-direction:column;gap:6px;font-size:13px;flex:1 1 320px}
.saisie-fichier{padding:6px}

/* ===========================================================================
   Injection de tickets par mots-clés
   =========================================================================== */
.injection-texte{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;min-height:220px}
.injection-resume{padding:12px 14px;border-radius:10px;background:var(--panel);border:1px solid var(--line);margin:14px 0}
.injection-resume p{margin:4px 0}
.injection-alerte{color:var(--danger);font-weight:600}
.injection-lot .barre-filtres{align-items:flex-end}
.tableau-injection .saisie{min-width:120px;font-size:13px;padding:5px 7px}
.tableau-injection textarea.saisie{min-width:200px;min-height:48px}
.tableau-injection td{vertical-align:top}
.tableau-injection .colonne-etroite{width:48px;text-align:center}
.a-completer{background:var(--fond-warn);border-color:var(--warn)}
.a-corriger{border-color:var(--danger)}
.ligne-exclue{opacity:.45}
.ligne-erreur td{background:var(--voile-danger)}
.injection-validation{margin-top:14px}
.injection-bilan{margin-top:8px}

/* Encarts déjà utilisés par les fiches, mais sans style jusque là. */
.encart-blocage{padding:10px 14px;border-left:4px solid var(--warn);background:var(--voile-warn);border-radius:6px}
.encart-relecture{padding:8px 12px;border-left:4px solid var(--danger);background:var(--voile-danger);border-radius:6px}
.tableau-injection input[type="text"].saisie{min-width:260px}
