/* ==========================================================================
   Ardoise — Couche de composants visuels (étape B)
   --------------------------------------------------------------------------
   Se charge APRÈS theme.css. Ce fichier ajoute la personnalité visuelle :
   icônes de navigation, illustrations d'état vide, squelettes de chargement,
   animations et différences de mise en page entre thèmes.

   Principe identique à l'étape A : tout est ici, jamais dans les 21 pages.
   ========================================================================== */

/* ==========================================================================
   1. Icônes de navigation
   Les icônes sont injectées par ui.js ; ce bloc ne fait que les mettre en page.
   ========================================================================== */
.nav-item {
  justify-content: flex-start;
  gap: 11px;
}
.nav-libelle {
  flex: 1;
  min-width: 0;
}
.nav-icone {
  width: 19px;
  height: 19px;
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.75;
  transition: opacity var(--transition-base), transform var(--transition-base);
}
.nav-item:hover .nav-icone { opacity: 1; }
.nav-item.actif .nav-icone { opacity: 1; color: var(--ocre); }

/* ==========================================================================
   1 bis. Badge de messages non lus
   Posé sur le lien « Messages » de la navigation (voir ui.js, section
   « Badge de messages non lus »). En rail compact (icônes seules), le
   badge devient un simple point ancré sur l'icône : le pastille complète
   serait rognée par l'`overflow:hidden` du rail resserré. Il redevient
   une pastille normale dès que le rail se déploie au survol.
   ========================================================================== */
.nav-item { position: relative; }

.badge-nav-messages {
  display: none;
  background: var(--rouge);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1.5;
  border-radius: 20px;
  padding: 1px 7px;
  min-width: 17px;
  text-align: center;
  flex: none;
}

html[data-nav-compact="oui"] .badge-nav-messages {
  position: absolute;
  top: 6px;
  right: 8px;
  min-width: 8px;
  height: 8px;
  padding: 0;
  font-size: 0;
  border-radius: 50%;
  line-height: 0;
}
html[data-nav-compact="oui"] .barre-laterale:hover .badge-nav-messages,
html[data-nav-compact="oui"] .barre-laterale:focus-within .badge-nav-messages {
  position: static;
  top: auto;
  right: auto;
  min-width: 17px;
  height: auto;
  padding: 1px 7px;
  font-size: 0.68rem;
  border-radius: 20px;
  line-height: 1.5;
}

/* Même badge dans le tiroir « Tous les menus » : la ligne est déjà large,
   la pastille se contente de se plaquer à droite. */
.tiroir-ligne .lien .badge-nav-messages { margin-left: auto; }

/* ==========================================================================
   2. Illustrations d'état vide
   Technique du masque : une seule illustration prend automatiquement la
   couleur du thème actif, au lieu d'en stocker une version par thème.
   ========================================================================== */
.etat-vide::before,
.etat-vide-tableau::before {
  content: '';
  display: block;
  height: 84px;
  margin: 0 auto 16px auto;
  background-color: var(--texte-att);
  opacity: 0.3;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-image: var(--illustration-vide);
  mask-image: var(--illustration-vide);
}
.etat-vide-tableau::before { height: 64px; margin-bottom: 12px; }

/* Illustration par défaut : une feuille de classeur vide, trait souple. */
:root {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 8h20l12 12v36H16z'/%3E%3Cpath d='M36 8v12h12'/%3E%3Cpath d='M24 34h16'/%3E%3Cpath d='M24 43h10'/%3E%3C/svg%3E");
}

/* Pure : marque géométrique, aucune rondeur, cohérente avec ses angles nets. */
html[data-theme="pure"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='square'%3E%3Crect x='12' y='12' width='40' height='40'/%3E%3Cpath d='M12 26h40'/%3E%3Cpath d='M26 26v26'/%3E%3C/svg%3E");
}
html[data-theme="pure"] .etat-vide::before,
html[data-theme="pure"] .etat-vide-tableau::before { opacity: 0.22; }

/* Yohali : illustration plus généreuse et plus ronde, à l'image du thème. */
:is(html[data-theme="yohali"], html[data-theme="kivu"]) {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='10' y='18' width='44' height='32' rx='7'/%3E%3Cpath d='M10 30h44'/%3E%3Cpath d='M24 18v-4a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v4'/%3E%3Ccircle cx='32' cy='40' r='4'/%3E%3C/svg%3E");
  --illustration-taille: 96px;
}
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .etat-vide::before { height: 96px; opacity: 0.34; }

html[data-theme="nuit"] .etat-vide::before,
html[data-theme="nuit"] .etat-vide-tableau::before { opacity: 0.42; }

/* ==========================================================================
   2 bis. L'illustration d'état vide dit CE QUI manque

   LE DÉFAUT QU'ON CORRIGE
   -----------------------
   Jusqu'ici les 38 écrans partageaient la même feuille de classeur. « Aucune
   classe créée », « aucun mouvement de caisse » et « aucun message » se
   ressemblaient donc trait pour trait, alors que ce sont trois situations
   sans rapport et trois gestes différents à faire ensuite. Un dessin qui
   n'informe pas est un dessin de trop.

   COMMENT
   -------
   `ui.js` pose `data-ecran` sur <html> ; chaque écran redéfinit
   `--illustration-vide`. Rien à changer dans les pages, et le mécanisme du
   masque reste celui d'origine : une seule couleur, celle du thème actif.

   CE QU'ON N'A PAS FAIT
   ---------------------
   Ces dessins restent des pictogrammes de 84 px à 30 % d'opacité, pas des
   scènes. Un écran de travail n'est pas une page d'accueil : le directeur qui
   ouvre sa comptabilité veut voir ses chiffres, pas une illustration.
   ========================================================================== */

/* Classes, cours, emploi du temps : la grille de l'établissement. */
html[data-ecran="classes"],
html[data-ecran="cours"],
html[data-ecran="cours-classe-titulaire"],
html[data-ecran="emploi-du-temps"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='14' width='46' height='36' rx='4'/%3E%3Cpath d='M9 26h46M25 26v24M41 26v24'/%3E%3C/svg%3E");
}

/* Élèves, inscriptions, utilisateurs : des personnes. */
html[data-ecran="eleves"],
html[data-ecran="inscriptions"],
html[data-ecran="utilisateurs"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='24' cy='24' r='9'/%3E%3Cpath d='M9 47a15 15 0 0 1 30 0'/%3E%3Ccircle cx='45' cy='27' r='7'/%3E%3Cpath d='M38 47a12 12 0 0 1 19-9'/%3E%3C/svg%3E");
}

/* Notes : la grille de cotes. */
html[data-ecran="notes"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='11' y='10' width='42' height='44' rx='4'/%3E%3Cpath d='M11 22h42M39 22v32'/%3E%3Cpath d='M19 32h13M19 42h9'/%3E%3C/svg%3E");
}

/* Bulletins et archives : la feuille remise à la famille. */
html[data-ecran="bulletins"],
html[data-ecran="bulletin-annuel"],
html[data-ecran="generateur-modeles"],
html[data-ecran="archives"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 8h22l12 12v36H15z'/%3E%3Cpath d='M37 8v12h12'/%3E%3Cpath d='M23 32h18M23 40h18M23 48h10'/%3E%3C/svg%3E");
}

/* Présences et discipline : l'appel coché. */
html[data-ecran="presences"],
html[data-ecran="discipline"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='13' y='9' width='38' height='46' rx='4'/%3E%3Cpath d='M25 5h14v8H25z'/%3E%3Cpath d='M21 28l4 4 8-8M21 42l4 4 8-8'/%3E%3Cpath d='M39 28h6M39 42h6'/%3E%3C/svg%3E");
}

/* Argent : frais scolaires, comptabilité. Un billet, pas une pièce : c'est
   sous cette forme que l'écrasante majorité du minerval est encaissée. */
html[data-ecran="frais-scolaires"],
html[data-ecran="comptabilite"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='6' y='19' width='52' height='28' rx='3'/%3E%3Cpath d='M32 27v12M35 30a4 4 0 0 0-3-2h-1a3 3 0 0 0 0 6h2a3 3 0 0 1 0 6h-1a4 4 0 0 1-3-2'/%3E%3Cpath d='M14 27v12M50 27v12'/%3E%3C/svg%3E");
}

/* Calendrier et année scolaire. */
html[data-ecran="calendrier"],
html[data-ecran="annee-scolaire"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='10' y='14' width='44' height='40' rx='4'/%3E%3Cpath d='M10 26h44'/%3E%3Cpath d='M21 10v8M43 10v8'/%3E%3Cpath d='M21 38h6M37 38h6'/%3E%3C/svg%3E");
}

/* Messages. */
html[data-ecran="messages"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='8' y='15' width='48' height='34' rx='5'/%3E%3Cpath d='M8 20l24 16 24-16'/%3E%3C/svg%3E");
}

/* Rapports et pilotage : des chiffres, pas une page. */
html[data-ecran="rapports"],
html[data-ecran="dashboard-directeur"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 52h44'/%3E%3Cpath d='M19 52V34M31 52V20M43 52V28'/%3E%3C/svg%3E");
}

/* Journal d'activité : la trace horodatée. */
html[data-ecran="journal"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='32' cy='32' r='22'/%3E%3Cpath d='M32 18v14l10 6'/%3E%3C/svg%3E");
}

/* Concours et orientation : le classement. */
html[data-ecran="orientation"],
html[data-ecran="repechage"] {
  --illustration-vide: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13 52h38'/%3E%3Crect x='13' y='30' width='11' height='22'/%3E%3Crect x='26' y='18' width='11' height='34'/%3E%3Crect x='39' y='38' width='11' height='14'/%3E%3C/svg%3E");
}

/* ==========================================================================
   2 ter. Pas d'illustration pendant le chargement ni dans les panneaux serrés

   Le `::before` s'appliquait indistinctement à tout `.etat-vide`, y compris
   au « Chargement… » qui porte un spinner et aux lignes de trois mots des
   colonnes latérales du tableau de bord. Résultat : un dessin de 84 px
   apparaissait une demi-seconde puis disparaissait, et les encarts étroits
   du dashboard étaient occupés à 80 % par une icône décorative.

   `:has()` permet de distinguer l'attente du vide réel. Sur un navigateur
   qui l'ignore, la règle est simplement sautée et le comportement reste
   celui d'avant — aucune régression possible.
   ========================================================================== */

.etat-vide:has(.spinner)::before,
.etat-vide-tableau:has(.spinner)::before {
  display: none;
}

/* Les espaces de travail des enseignants et le tableau de bord affichent
   leurs états vides dans des colonnes de 300 px. L'illustration y est
   réduite de moitié : elle situe, sans meubler. */
html[data-ecran="dashboard-directeur"] .etat-vide::before,
html[data-ecran="espace-secretaire"] .etat-vide::before {
  height: 40px;
  margin-bottom: 10px;
  opacity: 0.22;
}

/* ==========================================================================
   3. Chargement : le squelette remplace l'attente vide
   ========================================================================== */
.squelette {
  display: block;
  height: 11px;
  border-radius: var(--r-petit);
  background: linear-gradient(90deg, var(--bordure) 25%, var(--craie-2) 50%, var(--bordure) 75%);
  background-size: 200% 100%;
  animation: squelette-onde 1.3s ease-in-out infinite;
}
@keyframes squelette-onde {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}
.ligne-squelette td { padding-top: 14px; padding-bottom: 14px; }

/* Le rythme du chargement change selon le thème : Pure reste sec,
   Yohali respire davantage. */
html[data-theme="pure"] .squelette { animation-duration: 1s; border-radius: 1px; }
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .squelette { animation-duration: 1.7s; height: 13px; }

/* ==========================================================================
   4. Cartes statistiques
   Un filet d'accent et un chiffre qui s'anime : le tableau de bord cesse
   d'être une liste de nombres posés à plat.
   ========================================================================== */
.carte-stat, .carte-recap {
  position: relative;
  overflow: hidden;
  transition: transform var(--transition-base), border-color var(--transition-base);
}
.carte-stat::after, .carte-recap::after {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--ocre);
  opacity: 0;
  transition: opacity var(--transition-base);
}
.carte-stat:hover::after, .carte-recap:hover::after { opacity: 1; }
.carte-stat .valeur, .carte-recap .valeur { font-variant-numeric: tabular-nums; }

html[data-theme="pure"] .carte-stat::after,
html[data-theme="pure"] .carte-recap::after { opacity: 1; width: 2px; background: var(--bordure); }
html[data-theme="pure"] .carte-stat:hover::after,
html[data-theme="pure"] .carte-recap:hover::after { background: var(--ocre); }

:is(html[data-theme="yohali"], html[data-theme="kivu"]) .carte-stat:hover,
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .carte-recap:hover { transform: translateY(-3px); }

html[data-theme="nuit"] .carte-stat:hover,
html[data-theme="nuit"] .carte-recap:hover { border-color: var(--ocre); }

/* ==========================================================================
   5. Tableaux : apparition échelonnée des lignes
   Les douze premières lignes arrivent en cascade ; au-delà, l'effet
   s'arrête pour ne pas retarder l'affichage d'une longue liste.
   ========================================================================== */
@keyframes ligne-apparition {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}
.conteneur-tableau tbody tr { animation: ligne-apparition 0.26s ease both; }
.conteneur-tableau tbody tr:nth-child(1) { animation-delay: 0.00s; }
.conteneur-tableau tbody tr:nth-child(2) { animation-delay: 0.03s; }
.conteneur-tableau tbody tr:nth-child(3) { animation-delay: 0.06s; }
.conteneur-tableau tbody tr:nth-child(4) { animation-delay: 0.09s; }
.conteneur-tableau tbody tr:nth-child(5) { animation-delay: 0.12s; }
.conteneur-tableau tbody tr:nth-child(6) { animation-delay: 0.15s; }
.conteneur-tableau tbody tr:nth-child(7) { animation-delay: 0.18s; }
.conteneur-tableau tbody tr:nth-child(8) { animation-delay: 0.21s; }
.conteneur-tableau tbody tr:nth-child(9) { animation-delay: 0.24s; }
.conteneur-tableau tbody tr:nth-child(10) { animation-delay: 0.27s; }
.conteneur-tableau tbody tr:nth-child(11) { animation-delay: 0.30s; }
.conteneur-tableau tbody tr:nth-child(n+12) { animation-delay: 0.33s; }

.conteneur-tableau tbody tr { transition: background var(--transition-base); }

/* ==========================================================================
   6. Personnalité de mise en page, thème par thème
   C'est ici que les thèmes cessent d'être quatre palettes pour devenir
   quatre présentations distinctes.
   ========================================================================== */

/* ---- Pure : densité administrative, en-têtes en petites capitales ---- */
html[data-theme="pure"] .conteneur-tableau th {
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--texte-att);
}
html[data-theme="pure"] .conteneur-tableau td,
html[data-theme="pure"] .conteneur-tableau th { padding-top: 9px; padding-bottom: 9px; }
html[data-theme="pure"] .conteneur-tableau tbody tr:hover { background: #F7F8FA; }
html[data-theme="pure"] .entete-page h1 { font-size: 1.55rem; }
html[data-theme="pure"] .carte-section,
html[data-theme="pure"] .carte-cours { border-width: 1px; }

/* ---- Ardoise : le comportement d'origine, simplement fluidifié ---- */
html[data-theme="ardoise"] .conteneur-tableau tbody tr:hover { background: rgba(201, 138, 62, 0.05); }

/* ---- Nuit : les surfaces se détachent par la lumière, pas par l'ombre ---- */
html[data-theme="nuit"] .conteneur-tableau tbody tr:hover { background: rgba(95, 176, 140, 0.07); }
html[data-theme="nuit"] .carte-section,
html[data-theme="nuit"] .carte-cours,
html[data-theme="nuit"] .carte-stat { border-color: #2A3439; }
html[data-theme="nuit"] .bouton-principal { color: #0C1013; font-weight: 600; }

/* ---- Yohali : tout respire, tout s'arrondit, tout réagit ---- */
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .conteneur-tableau { overflow: hidden; }
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .conteneur-tableau tbody tr:hover { background: rgba(194, 84, 47, 0.05); }
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .conteneur-tableau td,
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .conteneur-tableau th { padding-top: 14px; padding-bottom: 14px; }
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .entete-page h1 { font-size: 2.1rem; }
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .carte-cours { transition: transform var(--transition-base), box-shadow var(--transition-base); }
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .carte-cours:hover { transform: translateY(-3px); box-shadow: var(--ombre-flottante); }
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .bouton { font-weight: 600; }

/* ==========================================================================
   7. Boutons : retour tactile commun
   ========================================================================== */
.bouton-principal { position: relative; overflow: hidden; }
.bouton:focus-visible {
  outline: 2px solid var(--ocre);
  outline-offset: 2px;
}

/* ==========================================================================
   8. Respect du réglage système « réduire les animations »
   Rappelé ici car ce fichier introduit l'essentiel des animations.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .conteneur-tableau tbody tr,
  .squelette { animation: none !important; }
  .carte-stat:hover, .carte-recap:hover, .carte-cours:hover { transform: none !important; }
}

/* ==========================================================================
   9. Widgets — anneaux de progression et barres comparatives
   Composants réutilisables : les pages ne décrivent que leurs données,
   jamais leur dessin.
   ========================================================================== */

.grille-widgets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px;
  margin-bottom: 28px;
}

.widget {
  background: var(--craie-2);
  border: 1px solid var(--bordure);
  border-radius: var(--r-carte);
  padding: 20px;
  box-shadow: var(--ombre-carte);
}
.widget .widget-titre {
  font-size: 0.78rem;
  color: var(--texte-att);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 0 0 16px 0;
  font-weight: 600;
}

/* ---- Anneau de progression ---- */
.widget-anneau { display: flex; align-items: center; gap: 18px; }
.anneau-graphe { position: relative; width: 104px; height: 104px; flex: none; }
.anneau-graphe svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.anneau-fond { fill: none; stroke: var(--bordure); stroke-width: 9; }
.anneau-trait {
  fill: none;
  stroke: var(--ocre);
  stroke-width: 9;
  stroke-linecap: round;
  transition: stroke-dashoffset 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}
.anneau-centre {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
}
.anneau-valeur {
  font-family: var(--police-titre);
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--texte-sombre);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.anneau-detail { font-size: 0.82rem; color: var(--texte-att); line-height: 1.45; }
.anneau-detail strong { color: var(--texte-sombre); font-weight: 600; }

/* Pure : l'anneau devient un arc net, sans extrémité arrondie. */
html[data-theme="pure"] .anneau-trait { stroke-linecap: butt; stroke-width: 7; }
html[data-theme="pure"] .anneau-fond { stroke-width: 7; }
html[data-theme="pure"] .anneau-valeur { font-weight: 600; letter-spacing: -0.02em; }
/* Yohali : trait plus épais, plus généreux. */
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .anneau-trait,
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .anneau-fond { stroke-width: 11; }

/* ---- Barres comparatives ---- */
.mini-barres { display: flex; flex-direction: column; gap: 11px; }
.mini-barre { display: grid; grid-template-columns: minmax(64px, 30%) 1fr auto; align-items: center; gap: 11px; }
.mb-libelle {
  font-size: 0.83rem; color: var(--texte-att);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mb-piste { height: 9px; background: var(--bordure); border-radius: var(--r-pilule); overflow: hidden; }
.mb-remplissage {
  display: block; height: 100%;
  background: var(--ocre);
  border-radius: var(--r-pilule);
  width: 0;
  transition: width 0.8s cubic-bezier(0.22, 1, 0.36, 1);
}
.mb-remplissage.complet { background: var(--vert-ok); }
.mb-valeur {
  font-size: 0.83rem; font-weight: 600; color: var(--texte-sombre);
  font-variant-numeric: tabular-nums; min-width: 30px; text-align: right;
}

html[data-theme="pure"] .mb-piste,
html[data-theme="pure"] .mb-remplissage { border-radius: 0; }
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .mb-piste { height: 12px; }

/* ---- Anneau compact, posé dans une carte existante ---- */
.anneau-compact { width: 46px; height: 46px; flex: none; position: relative; }
.anneau-compact svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.anneau-compact .anneau-fond,
.anneau-compact .anneau-trait { stroke-width: 7; }
.anneau-compact .etiquette-centre {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.66rem; font-weight: 600; color: var(--texte-sombre);
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .anneau-trait, .mb-remplissage { transition: none !important; }
}

/* ==========================================================================
   10. Bandeau de fonctionnement hors ligne
   Posé en bas d'écran pour ne jamais masquer la barre d'actions du haut.
   ========================================================================== */
#bandeau-hors-ligne {
  position: fixed;
  left: 16px; right: 16px; bottom: 16px;
  z-index: 900;
  display: none;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-radius: var(--r-carte);
  font-family: var(--police-corps);
  font-size: 0.87rem;
  box-shadow: var(--ombre-flottante);
  animation: bhl-apparition 0.25s ease both;
}
#bandeau-hors-ligne.visible { display: flex; }
#bandeau-hors-ligne .bhl-texte { flex: 1; min-width: 0; }
#bandeau-hors-ligne .bhl-action {
  border: 1px solid currentColor;
  background: none;
  color: inherit;
  border-radius: var(--r-bouton);
  padding: 6px 14px;
  font-family: var(--police-corps);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  flex: none;
}
#bandeau-hors-ligne .bhl-action:hover { background: rgba(255, 255, 255, 0.14); }

#bandeau-hors-ligne.hors-ligne { background: var(--ardoise); color: var(--craie); }
#bandeau-hors-ligne.en-attente { background: var(--ocre); color: #fff; }
#bandeau-hors-ligne.succes     { background: var(--vert-ok); color: #fff; }
#bandeau-hors-ligne.erreur     { background: var(--rouge); color: #fff; }

@keyframes bhl-apparition {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

@media (min-width: 700px) {
  #bandeau-hors-ligne { left: auto; right: 24px; bottom: 24px; max-width: 460px; }
}
@media (prefers-reduced-motion: reduce) {
  #bandeau-hors-ligne { animation: none; }
}

/* ==========================================================================
   11. Navigation mobile
   Sur téléphone, la barre latérale se réduisait à un bandeau où la liste des
   menus était purement et simplement masquée : la navigation devenait
   impossible sans basculer en paysage. Un bouton à trois traits ouvre
   désormais le menu en panneau déroulant.
   ========================================================================== */
.bouton-menu {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 42px;
  height: 38px;
  padding: 0 9px;
  background: none;
  border: 1px solid var(--nav-separateur);
  border-radius: var(--r-petit);
  cursor: pointer;
}
.bouton-menu span {
  display: block;
  height: 2px;
  width: 100%;
  background: var(--nav-texte-fort);
  border-radius: 2px;
  transition: transform var(--transition-base), opacity var(--transition-base);
}
/* Les trois traits se transforment en croix quand le menu est ouvert. */
.barre-laterale.nav-ouverte .bouton-menu span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.barre-laterale.nav-ouverte .bouton-menu span:nth-child(2) { opacity: 0; }
.barre-laterale.nav-ouverte .bouton-menu span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

@media (max-width: 780px) {
  .bouton-menu { display: flex; }

  .barre-laterale { flex-wrap: wrap; align-items: center; row-gap: 0; }
  /* Le conteneur intermédiaire est dissous : le titre et la liste deviennent
     des enfants directs de la barre, ce qui permet de les ordonner librement. */
  .barre-laterale > div { display: contents; }

  .barre-laterale .marque { order: 1; }
  .bouton-menu { order: 2; margin-left: auto; }
  .nav-liste { order: 3; }
  .nav-deconnexion { order: 4; }

  .nav-liste { display: none; }
  .nav-deconnexion { display: none; }

  .barre-laterale.nav-ouverte .nav-liste {
    display: flex;
    width: 100%;
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid var(--nav-separateur);
    animation: menu-deroule 0.22s ease both;
  }
  .barre-laterale.nav-ouverte .nav-deconnexion {
    display: block;
    width: 100%;
    margin-top: 6px;
    border-top: 1px solid var(--nav-separateur);
  }
  /* Cibles tactiles plus généreuses au doigt. */
  .barre-laterale.nav-ouverte .nav-item { padding: 13px 12px; font-size: 0.95rem; }
}

@keyframes menu-deroule {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}

/* ==========================================================================
   12. Lisibilité générale sur téléphone
   Les tableaux étaient posés dans un conteneur en overflow:hidden : au lieu
   de défiler, les colonnes de droite étaient tout simplement coupées.
   ========================================================================== */
@media (max-width: 780px) {
  .conteneur-tableau {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .conteneur-tableau table { min-width: 560px; }

  .barre-selecteurs { flex-direction: column; align-items: stretch; gap: 12px; }
  .barre-selecteurs .champ-selecteur { width: 100%; }
  .barre-selecteurs select,
  .barre-selecteurs input { width: 100%; }

  .grille-stats,
  .grille-recap,
  .grille-widgets { grid-template-columns: 1fr !important; }
  .widget { grid-column: auto !important; }

  .barre-actions { flex-direction: column; align-items: stretch; }
  .barre-actions .bouton { width: 100%; margin-left: 0 !important; }

  .entete-page h1 { font-size: 1.5rem; }
  .conteneur-grille { -webkit-overflow-scrolling: touch; }

  /* Le bandeau hors ligne ne doit pas recouvrir le dernier bouton de la page. */
  body { padding-bottom: 68px; }
}

@media (max-width: 480px) {
  .contenu { padding: 20px 14px 44px 14px; }
  .conteneur-tableau table { min-width: 480px; }
}

/* ==========================================================================
   13. Lanceur d'actions rapides
   Vingt-huit entrées de menu, c'est trop pour une barre latérale. Le lanceur
   les regroupe par domaine sur la page d'accueil, avec un filtre et les
   raccourcis les plus utilisés remontés en tête.
   ========================================================================== */
.lanceur { margin-bottom: 32px; }

.lanceur-entete {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin-bottom: 18px;
}
.lanceur-entete h2 {
  font-family: var(--police-titre); font-weight: 600;
  font-size: 1.2rem; margin: 0;
}
.lanceur-tout {
  background: none; border: 1px solid var(--bordure); border-radius: var(--r-bouton);
  padding: 8px 15px; font-family: var(--police-corps); font-size: 0.86rem;
  color: var(--texte-sombre); cursor: pointer;
}
.lanceur-tout:hover { border-color: var(--ocre); }
.lanceur-recherche {
  border: 1px solid var(--bordure); border-radius: var(--r-bouton);
  padding: 9px 14px; font-family: var(--police-corps); font-size: 0.9rem;
  background: var(--craie-2); color: var(--texte-sombre);
  min-width: 220px; flex: 1; max-width: 340px;
}
.lanceur-recherche:focus { outline: 2px solid var(--ocre); outline-offset: 1px; }

.lanceur-groupe { margin-bottom: 24px; }
.lanceur-groupe > .titre {
  font-size: 0.73rem; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--texte-att); font-weight: 600; margin-bottom: 11px;
}

/* Cinq tuiles sur une seule ligne : une grille qui descend gaspillerait la
   hauteur, qui est la ressource rare sur un écran de travail. */
.lanceur-grille {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  padding-bottom: 4px;
  scrollbar-width: thin;
}
.lanceur-grille > .tuile { flex: 1 1 0; min-width: 150px; }

.tuile {
  display: flex; flex-direction: row; align-items: center; gap: 12px;
  background: var(--craie-2); border: 1px solid var(--bordure);
  border-radius: var(--r-carte); padding: 16px;
  text-decoration: none; color: inherit; cursor: pointer; text-align: left;
  font-family: var(--police-corps);
  transition: transform var(--transition-base), border-color var(--transition-base), box-shadow var(--transition-base);
  position: relative;
}
.tuile:hover { border-color: var(--ocre); transform: translateY(-2px); box-shadow: var(--ombre-carte); }
.tuile:focus-visible { outline: 2px solid var(--ocre); outline-offset: 2px; }

.tuile .jeton {
  width: 38px; height: 38px; border-radius: var(--r-bouton);
  background: rgba(201, 138, 62, 0.14);
  display: flex; align-items: center; justify-content: center;
  flex: none;
}
.tuile .textes { min-width: 0; }
.tuile .jeton svg { width: 20px; height: 20px; stroke: var(--ocre); fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.tuile .nom { font-weight: 600; font-size: 0.93rem; color: var(--texte-sombre); line-height: 1.3; }
.tuile .desc { font-size: 0.78rem; color: var(--texte-att); line-height: 1.4; }

/* Compteur vivant : c'est ce qui distingue un lanceur d'un simple menu. */
.tuile .pastille-vive {
  position: absolute; top: 12px; right: 12px;
  background: var(--rouge); color: #fff;
  font-size: 0.7rem; font-weight: 700;
  padding: 2px 8px; border-radius: 20px;
}
.tuile .pastille-vive.calme { background: var(--vert-ok); }

html[data-theme="pure"] .tuile .jeton { border-radius: 2px; background: #EEF2F7; }
:is(html[data-theme="yohali"], html[data-theme="kivu"]) .tuile:hover { transform: translateY(-3px); }
html[data-theme="nuit"] .tuile .jeton { background: rgba(95, 176, 140, 0.16); }

@media (max-width: 640px) {
  .lanceur-grille { gap: 10px; }
  .lanceur-grille > .tuile { min-width: 128px; flex: 0 0 auto; }
  .tuile { padding: 12px; }
  .tuile .desc { display: none; }
}

/* ==========================================================================
   14. Rail réduit et tiroir « Tous les menus »
   Vingt-huit entrées dans une barre latérale, personne ne s'y retrouve. Le
   rail n'affiche que les écrans épinglés par l'utilisateur ; le reste vit
   dans un tiroir, groupé par domaine et cherchable.
   ========================================================================== */
.nav-item.nav-tiroir {
  width: 100%; text-align: left; cursor: pointer;
  background: none; border: none; font-family: inherit;
  display: flex; align-items: center; gap: 11px;
}
.nav-item.nav-tiroir .chevron { margin-left: auto; opacity: 0.55; font-size: 0.8rem; }

/* Page pleine plutôt que fenêtre : « Tous les menus » EST un écran de
   navigation, pas une boîte de dialogue. Une modale étroite donnerait
   l'impression d'un réglage annexe. */
.voile-tiroir {
  position: fixed; inset: 0; z-index: 200;
  background: var(--craie);
  display: none;
  overflow-y: auto;
}
.voile-tiroir.ouvert { display: block; }

.tiroir {
  max-width: 1080px;
  margin: 0 auto;
  padding: 40px 32px 60px 32px;
}
.tiroir-entete {
  display: flex; align-items: center; gap: 14px;
  flex-wrap: wrap; margin-bottom: 8px;
  position: sticky; top: 0; z-index: 2;
  background: var(--craie);
  padding: 16px 0;
}
.tiroir-entete h2 {
  font-family: var(--police-titre); font-weight: 600;
  font-size: 1.6rem; margin: 0;
}
.tiroir-recherche {
  flex: 1; min-width: 200px;
  border: 1px solid var(--bordure); border-radius: var(--r-bouton);
  padding: 9px 13px; font-family: var(--police-corps); font-size: 0.9rem;
  background: var(--craie); color: var(--texte-sombre);
}
.tiroir-recherche:focus { outline: 2px solid var(--ocre); outline-offset: 1px; }
.tiroir-fermer {
  background: none; border: 1px solid var(--bordure);
  border-radius: var(--r-bouton); padding: 8px 13px;
  font-family: var(--police-corps); font-size: 0.85rem;
  color: var(--texte-att); cursor: pointer;
}
.tiroir-aide { font-size: 0.83rem; color: var(--texte-att); margin: 0 0 18px 0; }

.tiroir-groupe { margin-bottom: 20px; }
.tiroir-groupe > .titre {
  font-size: 0.71rem; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--texte-att); font-weight: 600; margin-bottom: 9px;
}
.tiroir-grille {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(258px, 1fr)); gap: 14px;
}
.tiroir-groupe > .titre { font-size: 0.76rem; margin-bottom: 12px; }

.tiroir-ligne {
  display: flex; align-items: center; gap: 13px;
  border: 1px solid var(--bordure); border-radius: var(--r-carte);
  padding: 16px 16px; background: var(--craie-2);
  transition: border-color var(--transition-base), box-shadow var(--transition-base);
}
.tiroir-ligne:hover { box-shadow: var(--ombre-carte); }
.tiroir-ligne:hover { border-color: var(--ocre); }
.tiroir-ligne .lien {
  flex: 1; text-decoration: none; color: var(--texte-sombre);
  font-size: 0.95rem; font-weight: 500;
  display: flex; align-items: center; gap: 12px; min-width: 0;
}
/* Le pictogramme reçoit son propre jeton coloré, comme sur les tuiles du
   lanceur : les deux surfaces appartiennent au même produit. */
.tiroir-ligne .lien .jeton-menu {
  width: 36px; height: 36px; border-radius: var(--r-bouton); flex: none;
  background: rgba(201, 138, 62, 0.13);
  display: flex; align-items: center; justify-content: center;
}
.tiroir-ligne .lien svg { width: 19px; height: 19px; flex: none; stroke: var(--ocre); fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.tiroir-ligne .lien .texte { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Épingle : c'est elle qui fait entrer ou sortir un écran du rail. */
.epingle {
  background: none; border: none; cursor: pointer;
  padding: 7px; border-radius: var(--r-petit);
  color: var(--texte-att); flex: none; line-height: 0;
}
.epingle svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.epingle:hover { background: rgba(201, 138, 62, 0.14); color: var(--ocre-dark); }
.epingle.active { color: var(--ocre); }
.epingle.active svg { fill: currentColor; fill-opacity: 0.18; }

.tiroir-pied {
  border-top: 1px solid var(--bordure); padding-top: 14px; margin-top: 6px;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  font-size: 0.83rem; color: var(--texte-att);
}
.tiroir-pied .lien-reinit {
  background: none; border: none; color: var(--encre);
  cursor: pointer; font-family: var(--police-corps); font-size: 0.84rem;
  margin-left: auto;
}

@media (max-width: 640px) {
  .tiroir { padding: 16px 16px 40px 16px; }
  .tiroir-entete h2 { font-size: 1.3rem; }
  .tiroir-grille { grid-template-columns: 1fr; }
}

/* ==========================================================================
   15. Composants de densité — Studio
   Trois éléments qui séparent une interface correcte d'un produit : le
   pictogramme coloré sur les cartes de chiffres, l'avatar dans les listes,
   et le badge d'état dans les tableaux.
   ========================================================================== */

/* ---------- Carte de chiffre avec pictogramme ---------- */
.carte-stat { position: relative; }
.carte-stat .pictogramme {
  position: absolute; top: 18px; right: 18px;
  width: 42px; height: 42px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
}
.carte-stat .pictogramme svg {
  width: 21px; height: 21px; fill: none;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
/* Chaque famille de chiffre a sa teinte : on reconnaît la carte avant de
   lire son libellé. Les couleurs restent tirées du thème actif. */
.pictogramme.pict-bleu    { background: rgba(76, 95, 213, 0.12); }
.pictogramme.pict-bleu svg    { stroke: #4C5FD5; }
.pictogramme.pict-vert    { background: rgba(18, 183, 106, 0.12); }
.pictogramme.pict-vert svg    { stroke: var(--vert-ok); }
.pictogramme.pict-ocre    { background: rgba(201, 138, 62, 0.14); }
.pictogramme.pict-ocre svg    { stroke: var(--ocre); }
.pictogramme.pict-rouge   { background: rgba(240, 68, 56, 0.11); }
.pictogramme.pict-rouge svg   { stroke: var(--rouge); }
.pictogramme.pict-violet  { background: rgba(126, 86, 212, 0.12); }
.pictogramme.pict-violet svg  { stroke: #7E56D4; }

.carte-stat .valeur { padding-right: 52px; }
.carte-stat .variation {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 0.78rem; font-weight: 600; margin-top: 7px;
}
.carte-stat .variation.hausse { color: var(--vert-ok); }
.carte-stat .variation.baisse { color: var(--rouge); }

/* ---------- Avatars ---------- */
.avatar {
  width: 34px; height: 34px; border-radius: 50%;
  object-fit: cover; flex: none;
  background: var(--bordure);
}
.avatar-initiales {
  width: 34px; height: 34px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.78rem; font-weight: 600; color: #fff;
  flex: none; letter-spacing: 0.02em;
}
.cellule-personne { display: flex; align-items: center; gap: 11px; min-width: 0; }
.cellule-personne .identite { min-width: 0; }
.cellule-personne .nom-personne {
  font-weight: 500; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.cellule-personne .sous-nom { font-size: 0.77rem; color: var(--texte-att); }

/* Pile d'avatars : montre une équipe sans occuper une ligne par personne. */
.pile-avatars { display: flex; }
.pile-avatars .avatar,
.pile-avatars .avatar-initiales {
  width: 28px; height: 28px; margin-left: -9px;
  border: 2px solid var(--craie-2); font-size: 0.68rem;
}
.pile-avatars > :first-child { margin-left: 0; }
.pile-avatars .reste {
  width: 28px; height: 28px; border-radius: 50%; margin-left: -9px;
  border: 2px solid var(--craie-2); background: var(--bordure);
  display: flex; align-items: center; justify-content: center;
  font-size: 0.68rem; font-weight: 600; color: var(--texte-att);
}

/* ---------- Badges d'état ---------- */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 0.74rem; font-weight: 600;
  padding: 3px 10px; border-radius: 999px; white-space: nowrap;
}
.badge::before {
  content: ''; width: 6px; height: 6px;
  border-radius: 50%; background: currentColor; flex: none;
}
.badge-succes  { background: rgba(18, 183, 106, 0.12); color: var(--vert-ok); }
.badge-alerte  { background: rgba(201, 138, 62, 0.15); color: var(--ocre-dark); }
.badge-danger  { background: rgba(240, 68, 56, 0.11); color: var(--rouge); }
.badge-neutre  { background: rgba(102, 112, 133, 0.12); color: var(--texte-att); }
.badge-info    { background: rgba(76, 95, 213, 0.12); color: #4C5FD5; }
.badge.sans-point::before { display: none; }

html[data-theme="pure"] .tiroir-ligne .lien .jeton-menu { border-radius: 3px; }
html[data-theme="pure"] .badge { border-radius: 3px; }
html[data-theme="pure"] .carte-stat .pictogramme { border-radius: 3px; }
html[data-theme="nuit"] .cellule-personne .sous-nom { color: var(--texte-att); }
html[data-theme="nuit"] .pile-avatars .avatar,
html[data-theme="nuit"] .pile-avatars .avatar-initiales,
html[data-theme="nuit"] .pile-avatars .reste { border-color: var(--craie-2); }

@media (max-width: 640px) {
  .carte-stat .pictogramme { width: 34px; height: 34px; top: 14px; right: 14px; }
  .carte-stat .valeur { padding-right: 42px; }
  .avatar, .avatar-initiales { width: 30px; height: 30px; }
}

/* ==========================================================================
   NOTIFICATIONS FLOTTANTES (toast)

   Ces règles surchargent volontairement les `.message-flash` définis en ligne
   dans chaque page : ui.css est chargé APRÈS les <style> inline, une règle de
   même spécificité y gagne donc la priorité. Corriger ici évite d'éditer 23
   pages une par une — et surtout évite qu'une page oubliée conserve l'ancien
   comportement.

   Problème résolu : le message était un bloc DANS le flux de la page, en haut
   du contenu. Après un enregistrement déclenché depuis le bas d'un long
   formulaire, la confirmation s'affichait hors écran — l'utilisateur ne savait
   pas si son action avait abouti, et recliquait parfois.
   ========================================================================== */

.message-flash {
  /* Toujours dans le DOM, mais hors écran : c'est ce qui permet d'ANIMER
     l'entrée. Un display:none ne peut pas se animer. */
  display: block !important;
  /* !important sur le positionnement lui-même : la réparation dans ui.js
     (rattachement à <body>) supprime la cause structurelle du problème, mais
     ces règles restent la seconde garantie — aucune autre règle, présente ou
     future, ne doit pouvoir repositionner ce toast dans le flux de la page. */
  position: fixed !important;
  top: 18px !important;
  right: 18px !important;
  left: auto !important;
  bottom: auto !important;
  z-index: 9999 !important;
  max-width: min(380px, calc(100vw - 36px));
  margin: 0 !important;
  padding: 13px 17px;
  border-radius: var(--r-carte, 10px);
  font-size: 0.88rem;
  line-height: 1.45;
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.16);
  border: 1px solid transparent;

  /* Caché par défaut : glissé vers la droite, hors de l'écran. */
  opacity: 0;
  transform: translateX(115%);
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.28s ease;
  /* Sans ceci, un toast invisible intercepterait les clics sur la zone qu'il
     occupe, y compris quand il est hors écran sur certains navigateurs. */
  pointer-events: none;
}

.message-flash.visible {
  display: block !important;
  opacity: 1;
  transform: translateX(0);
  pointer-events: auto;
}

/* Couleurs pleines plutôt que translucides : sur un toast qui flotte au-dessus
   d'un tableau, un fond semi-transparent rend le texte illisible. */
.message-flash.succes {
  background: #eaf6ee;
  color: #1c6b3c;
  border-color: rgba(28, 107, 60, 0.22);
}
.message-flash.erreur {
  background: #fdeceb;
  color: #a12b1f;
  border-color: rgba(161, 43, 31, 0.22);
}
.message-flash.info {
  background: #eaf0fb;
  color: #2f4d92;
  border-color: rgba(47, 77, 146, 0.22);
}

html[data-theme="nuit"] .message-flash.succes { background: #14301f; color: #7fd6a0; }
html[data-theme="nuit"] .message-flash.erreur { background: #331716; color: #f2a099; }
html[data-theme="nuit"] .message-flash.info   { background: #16223d; color: #9db6ec; }

/* Sur mobile, le toast occupe la largeur : à 380px de large sur un écran de
   360px, il déborderait ou serait tronqué. */
@media (max-width: 520px) {
  .message-flash {
    top: 12px !important;
    right: 12px !important;
    left: 12px !important;
    max-width: none;
    transform: translateY(-140%);
  }
  .message-flash.visible { transform: translateY(0); }
}

/* Respecte le réglage système « réduire les animations » : le toast apparaît
   alors franchement, sans glissement. */
@media (prefers-reduced-motion: reduce) {
  .message-flash { transition: opacity 0.15s ease; transform: none; }
  .message-flash.visible { transform: none; }
}

/* ==========================================================================
   FLOU D'ARRIÈRE-PLAN DES MODALES

   Surcharge volontaire du `.voile` défini en ligne dans chaque page (ui.css
   étant chargé après, une règle de même spécificité y gagne). Le voile n'était
   qu'un aplat sombre translucide : le contenu restait net derrière, ce qui
   disperse le regard au lieu de le concentrer sur la fenêtre ouverte.
   ========================================================================== */

.voile {
  background: rgba(24, 34, 28, 0.42);
  /* -webkit- reste nécessaire pour Safari, y compris récent. */
  -webkit-backdrop-filter: blur(4px) saturate(0.9);
  backdrop-filter: blur(4px) saturate(0.9);
}

/* Apparition en fondu plutôt que d'un coup : sans transition, le flou surgit
   brutalement et donne une impression de saut. */
.voile.visible { animation: voile-apparition 0.18s ease-out; }
.voile.visible .modale { animation: modale-apparition 0.22s cubic-bezier(0.22, 1, 0.36, 1); }

@keyframes voile-apparition {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes modale-apparition {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .voile.visible, .voile.visible .modale { animation: none; }
}

/* ==========================================================================
   MODALE DE CONFIRMATION

   Remplace les `confirm()` natifs du navigateur, qui affichent une boîte grise
   au style imposé par le système — police, boutons et position ignorent
   totalement la charte du site, et sur mobile la fenêtre surgit collée en haut
   de l'écran.
   ========================================================================== */

.voile-confirmation {
  position: fixed; inset: 0; z-index: 10000;
  display: none; align-items: center; justify-content: center; padding: 20px;
  background: rgba(24, 34, 28, 0.42);
  -webkit-backdrop-filter: blur(4px) saturate(0.9);
  backdrop-filter: blur(4px) saturate(0.9);
}
.voile-confirmation.visible { display: flex; animation: voile-apparition 0.18s ease-out; }

.boite-confirmation {
  background: var(--craie-2, #fff);
  border-radius: var(--r-carte, 12px);
  padding: 24px 26px;
  max-width: 440px; width: 100%;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.22);
  animation: modale-apparition 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}
.boite-confirmation h3 {
  font-family: var(--police-titre, inherit);
  font-size: 1.12rem; font-weight: 600; margin: 0 0 10px 0;
}
.boite-confirmation p {
  margin: 0 0 20px 0; font-size: 0.9rem; line-height: 1.6; color: var(--texte-att, #555);
  white-space: pre-line; /* respecte les retours à la ligne des messages existants */
}
.boite-confirmation .actions {
  display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap;
}
.boite-confirmation .actions button {
  padding: 9px 18px; border-radius: var(--r-bouton, 8px);
  font-size: 0.88rem; font-family: inherit; cursor: pointer;
  border: 1px solid var(--bordure, #ddd); background: transparent; color: inherit;
}
.boite-confirmation .actions .valider {
  background: var(--ocre, #c6964a); border-color: var(--ocre, #c6964a); color: #fff; font-weight: 600;
}
/* Une suppression est irréversible : le bouton doit le dire visuellement,
   pour qu'on ne confirme pas par réflexe. */
.boite-confirmation.danger .actions .valider {
  background: var(--rouge, #b23a2e); border-color: var(--rouge, #b23a2e);
}

@media (max-width: 520px) {
  .boite-confirmation .actions { flex-direction: column-reverse; }
  .boite-confirmation .actions button { width: 100%; }
}

/* ==========================================================================
   STYLE DE BASE DES BOUTONS — FILET DE SÉCURITÉ

   Problème corrigé : le style de `.bouton` était dupliqué dans le <style> de
   chaque page, et 12 des 34 pages l'avaient tout simplement oublié (archives,
   rapports, orientation, inscriptions, messages, présences, discipline...).
   Leurs boutons s'affichaient donc avec l'apparence NATIVE du navigateur —
   fond gris, coins carrés, bordure système — au milieu d'une interface au
   design soigné. `theme.css` ne définissait qu'une transition sur `.bouton`,
   pas son apparence, ce qui laissait le trou invisible.

   POURQUOI `:where()` : ce sélecteur a une spécificité de ZÉRO. Les 22 pages
   qui définissent déjà leur `.bouton` en local gardent donc exactement leur
   apparence actuelle — rien n'est écrasé, aucune régression possible. Seules
   les pages qui n'avaient aucun style reçoivent celui-ci. Une règle `.bouton`
   ordinaire dans ce fichier aurait, elle, écrasé les 22 autres (ui.css étant
   chargé après les styles inline).
   ========================================================================== */

:where(.bouton) {
  padding: 9px 15px;
  border-radius: var(--r-bouton, 8px);
  font-family: var(--police-corps, inherit);
  font-size: 0.86rem;
  font-weight: 600;
  cursor: pointer;
  border: 1.5px solid transparent;
  background: none;
  color: inherit;
  line-height: 1.35;
}
:where(.bouton:disabled) { opacity: 0.55; cursor: not-allowed; }

:where(.bouton-principal) { background: var(--ocre); color: var(--craie-2); }
:where(.bouton-principal:hover:not(:disabled)) { background: var(--ocre-dark); }

:where(.bouton-secondaire) {
  background: none;
  border-color: var(--bordure);
  color: var(--texte-sombre);
}
:where(.bouton-secondaire:hover:not(:disabled)) { border-color: var(--ardoise); }

:where(.bouton-danger) { background: var(--rouge); color: #fff; border-color: var(--rouge); }
:where(.bouton-petit) { padding: 5px 10px; font-size: 0.78rem; }

/* Même raisonnement pour les champs de saisie : une page qui oublie de les
   styler affiche des champs au rendu système, incohérents avec le reste. */
:where(input[type="text"], input[type="number"], input[type="date"],
       input[type="time"], input[type="email"], input[type="password"],
       input[type="search"], input[type="tel"], select, textarea) {
  font-family: var(--police-corps, inherit);
  font-size: 0.88rem;
  padding: 9px 11px;
  border: 1.5px solid var(--bordure, #ddd);
  border-radius: var(--r-bouton, 8px);
  background: var(--craie-2, #fff);
  color: inherit;
}
:where(input:disabled, select:disabled, textarea:disabled) {
  opacity: 0.6;
  cursor: not-allowed;
}


/* ==========================================================================
   NAVIGATION CIBLÉE (voir installerFocus dans ui.js)
   Le surlignage doit rester lisible dans les deux thèmes : on s'appuie sur les
   variables, jamais sur une couleur en dur.
   ========================================================================== */
.ard-bandeau-focus {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: rgba(201, 138, 62, 0.12);
  border-left: 3px solid var(--ocre, #c98a3e);
  border-radius: var(--r-petit, 6px);
  padding: 10px 14px; margin: 0 0 18px 0;
  font-size: 0.86rem; color: var(--texte-sombre, #22302a);
}
.ard-bandeau-focus a { color: var(--encre, #2f4f4f); font-weight: 600; }

.ard-ligne-ciblee {
  background: rgba(201, 138, 62, 0.16) !important;
  outline: 2px solid var(--ocre, #c98a3e);
  outline-offset: -2px;
}
/* L'animation attire l'œil au moment de l'arrivée, puis s'efface : un
   surlignage permanent finirait par être ignoré. */
@media (prefers-reduced-motion: no-preference) {
  .ard-ligne-ciblee { animation: ard-focus-pulse 1.2s ease-out 2; }
}
@keyframes ard-focus-pulse {
  0%, 100% { background: rgba(201, 138, 62, 0.16); }
  50% { background: rgba(201, 138, 62, 0.34); }
}


/* ==========================================================================
   TABLEAUX EN CARTES SUR TÉLÉPHONE
   Voir `surveillerTableaux` dans ui.js, qui pose `data-libelle` sur chaque
   cellule. Sans lui, ces règles ne s'appliqueraient à rien.

   Le point de rupture est à 700 px et non 780 : entre les deux, une tablette
   en portrait affiche encore un tableau correctement, et le transformer en
   cartes gaspillerait la place disponible.
   ========================================================================== */
@media (max-width: 700px) {
  .conteneur-tableau { overflow-x: visible; }
  /* La largeur minimale imposait le défilement horizontal : c'est elle qu'on
     annule, sinon les cartes resteraient hors écran. */
  .conteneur-tableau table { min-width: 0 !important; width: 100%; }

  .conteneur-tableau thead { display: none; }
  .conteneur-tableau table,
  .conteneur-tableau tbody,
  .conteneur-tableau tr,
  .conteneur-tableau td { display: block; width: 100%; }

  .conteneur-tableau tr {
    border: 1px solid var(--bordure, #e2ded4);
    border-radius: var(--r-petit, 6px);
    padding: 10px 12px;
    margin-bottom: 10px;
    background: var(--craie-2, #fbfaf7);
  }
  .conteneur-tableau td {
    border: none;
    padding: 5px 0;
    display: flex;
    justify-content: space-between;
    gap: 14px;
    align-items: baseline;
    font-size: 0.87rem;
  }
  .conteneur-tableau td + td { border-top: 1px solid var(--bordure, #e2ded4); }

  /* L'intitulé de colonne devient le libellé de la ligne : c'est ce qui rend
     la carte lisible sans en-tête. */
  .conteneur-tableau td[data-libelle]::before {
    content: attr(data-libelle);
    color: var(--texte-att, #6b7269);
    font-size: 0.76rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    flex: 0 0 auto;
    max-width: 45%;
  }
  .conteneur-tableau td[data-libelle] { text-align: right; }

  /* Une cellule vide n'occupe pas de ligne : sur une carte, six tirets
     alignés n'apprennent rien et allongent la lecture. */
  .conteneur-tableau td.ard-cellule-vide { display: none; }

  /* Ligne d'état (« Aucun résultat ») : elle reste pleine largeur et centrée. */
  .conteneur-tableau tr.ard-ligne-pleine { border: none; background: none; padding: 0; }
  .conteneur-tableau tr.ard-ligne-pleine td { display: block; text-align: center; }
  .conteneur-tableau tr.ard-ligne-pleine td::before { content: none; }

  /* Les boutons d'action d'une ligne se retrouvent seuls en bas de carte :
     on leur donne une cible tactile correcte. */
  .conteneur-tableau td .lien-action { padding: 4px 0; display: inline-block; }
}

/* ==========================================================================
   DURCISSEMENT MOBILE DES ÉCRANS RÉCENTS
   Classes introduites par les lots 10 à 14, absentes des règles d'origine.
   ========================================================================== */
@media (max-width: 640px) {
  .grille-tresorerie,
  .grille-periodes,
  .grille-liens,
  .grille-formulaire,
  .ligne-champs,
  .grille-2col { grid-template-columns: 1fr !important; }

  .barre-filtres-compta,
  .barre-filtres-resultats,
  .barre-filtres { flex-direction: column; align-items: stretch; }
  .barre-filtres-compta input,
  .barre-filtres-compta label,
  .barre-filtres select,
  .barre-filtres input { width: 100%; }

  /* Une modale doit occuper l'écran sur téléphone : centrée avec des marges,
     elle réduit encore une surface déjà petite. */
  .voile { padding: 0; align-items: flex-end; }
  .modale, .modale-large {
    max-width: 100%; max-height: 92vh;
    border-radius: var(--r-carte, 10px) var(--r-carte, 10px) 0 0;
    padding: 22px 18px;
  }
  .actions-modale { flex-direction: column-reverse; }
  .actions-modale .bouton { width: 100%; }

  /* Les onglets défilent horizontalement plutôt que de se replier sur
     plusieurs lignes, ce qui ferait sauter le contenu. */
  .onglets, .barre-onglets {
    overflow-x: auto; flex-wrap: nowrap;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .onglets::-webkit-scrollbar, .barre-onglets::-webkit-scrollbar { display: none; }
  .onglet { white-space: nowrap; }
}


/* ==========================================================================
   MODALES — mécanique partagée

   POURQUOI CE BLOC EXISTE
   Les règles qui font FONCTIONNER une modale (`position: fixed`, `display:
   none`, `.visible { display: flex }`) vivaient dans le <style> de chaque
   page. Quatre pages les avaient perdues en cours de route : le voile et sa
   modale étaient alors rendus DANS LE FLUX, en permanence — un bloc sombre au
   milieu de l'écran dès le chargement, et un bouton qui semblait ne rien faire
   puisque rien n'avait jamais été fermé.

   Le défaut a dû être rattrapé quatre fois : tableau de bord, puis
   annee-scolaire, bulletins et ecole, puis cours. Il ne se reproduira plus :
   une page qui oublie ces règles les reçoit désormais d'ici.

   CE QUI EST CENTRALISÉ, ET CE QUI NE L'EST PAS
   La MÉCANIQUE est imposée : elle ne varie jamais et son absence casse tout.
   L'APPARENCE — largeur maximale, marge intérieure — reste à la page : une
   boîte de confirmation à 420 px et un formulaire à 720 px sont deux besoins
   différents, et les uniformiser dégraderait les deux.

   `:where()` a une spécificité NULLE. Les valeurs ci-dessous ne sont donc que
   des défauts : toute règle écrite dans une page l'emporte, même si sa feuille
   est chargée avant celle-ci. C'est ce qui permet de poser un filet sans rien
   écraser.
   ========================================================================== */

/* Mécanique — imposée, car son absence rend la modale inutilisable.
   Seul ce qui DOIT être vrai partout figure ici : le positionnement et le
   mécanisme d'ouverture. */
.voile {
  position: fixed;
  inset: 0;
  display: none;
  z-index: 10;
}
.voile.visible { display: flex; }

/* Défauts d'apparence — cédés à la moindre règle de page.
   `padding` et `align-items` sont ICI et non ci-dessus : le bloc mobile plus
   haut dans ce fichier les redéfinit (modale en feuille montante sur
   téléphone), et une règle imposée les écraserait — annulant la correction du
   lot 16. */
:where(.voile) {
  background: rgba(31, 43, 36, 0.45);
  align-items: center;
  justify-content: center;
  padding: 20px;
}
:where(.modale) {
  background: var(--craie-2, #fbfaf7);
  border-radius: var(--r-carte, 10px);
  padding: 28px;
  max-width: 560px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
}
:where(.modale-large) { max-width: 720px; }
:where(.modale h2) {
  font-family: var(--police-titre, serif);
  font-weight: 600;
  font-size: 1.3rem;
  margin: 0 0 18px 0;
}
:where(.actions-modale) {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 14px;
}

/* ==========================================================================
   UNE CASE À COCHER N'EST PAS UN CHAMP DE SAISIE

   LE DÉFAUT
   ---------
   Presque toutes les pages écrivent une règle du type :

       .champ input, .champ select { width: 100%; … }

   Elle est juste pour un nom, une date, un montant. Elle est destructrice
   pour une case à cocher : la case s'étire sur toute la largeur du
   formulaire — 618 px sur `parametres.html`, 446 px sur `repechage.html`,
   534 px sur le formulaire de contact — et son libellé, chassé, se retrouve
   à la ligne suivante. Plus rien n'indique alors quelle case correspond à
   quelle option ; on coche à l'aveugle.

   Le défaut se voyait mal parce que le sélecteur d'attribut `[type="checkbox"]`
   et le sélecteur `.champ input` ont EXACTEMENT la même spécificité (0,1,1) :
   c'est l'ordre de lecture qui tranche. Écrite ici, en fin de la feuille
   partagée — donc après les styles en ligne de chaque page — la règle
   ci-dessous l'emporte partout, sans qu'aucune des trente-huit pages soit
   touchée.

   POURQUOI 18 PX ET UN ALIGNEMENT NÉGATIF
   ---------------------------------------
   18 px plutôt que les 13 px du navigateur : au doigt, 13 px se rate. Et une
   case reste un élément EN LIGNE, posé sur la ligne de base du texte :
   l'agrandir sans corriger `vertical-align` la fait flotter au-dessus de son
   libellé. Les deux vont ensemble, toujours.
   ========================================================================== */
input[type="checkbox"],
input[type="radio"] {
  width: 18px;
  height: 18px;
  flex: none;                 /* ne s'étire pas dans une rangée flexible */
  margin: 0 8px 0 0;
  padding: 0;
  vertical-align: -0.22em;    /* centrée sur la ligne de texte, pas posée dessus */
  accent-color: var(--ocre, #c98a3e);
  cursor: pointer;
}

/* Un libellé qui enveloppe sa case devient une rangée : le texte qui passe à
   la ligne s'aligne sur la case plutôt que sous elle, et toute la surface
   reste une seule cible. */
label:where(.case, .case-a-cocher, .champ-case, .interrupteur-controle) {
  display: flex;
  align-items: center;
  cursor: pointer;
}

/* Arrondi interieur du panneau : suit le bord visible du rail, y compris
   son deploiement au survol/au clavier, et reste en haut pendant le defilement.
   Le decor fixe n'intercepte aucun clic et ne rogne aucun contenu. */
@media screen and (min-width: 781px) {
  html:not([data-theme="public"], [data-theme="yohali"], [data-theme="kivu"], [data-theme="elan"], [data-theme="perspective"], [data-theme="recre"], [data-theme="nexus"], [data-theme="orbite"], [data-theme="fluide"], [data-theme="vibra"]):is([data-nav-position="gauche"], :not([data-nav-position])) .mise-en-page > .barre-laterale::after {
    content: "";
    position: fixed;
    top: 0;
    left: 240px;
    width: 24px;
    height: 24px;
    background: radial-gradient(circle at 24px 24px, transparent 24px, var(--nav-fond) 24.5px);
    pointer-events: none;
    z-index: 41;
  }
  html:not([data-theme="public"], [data-theme="yohali"], [data-theme="kivu"], [data-theme="elan"], [data-theme="perspective"], [data-theme="recre"], [data-theme="nexus"], [data-theme="orbite"], [data-theme="fluide"], [data-theme="vibra"]):is([data-nav-position="gauche"], :not([data-nav-position]))[data-nav-compact="oui"] .mise-en-page > .barre-laterale::after {
    left: 72px;
    transition: left 0.22s cubic-bezier(0.22, 0.61, 0.36, 1);
  }
  html:not([data-theme="public"], [data-theme="yohali"], [data-theme="kivu"], [data-theme="elan"], [data-theme="perspective"], [data-theme="recre"], [data-theme="nexus"], [data-theme="orbite"], [data-theme="fluide"], [data-theme="vibra"]):is([data-nav-position="gauche"], :not([data-nav-position]))[data-nav-compact="oui"] .mise-en-page > .barre-laterale:is(:hover, :focus-within)::after {
    left: 240px;
  }
}
@media (prefers-reduced-motion: reduce) {
  html:not([data-theme="public"], [data-theme="yohali"], [data-theme="kivu"], [data-theme="elan"], [data-theme="perspective"], [data-theme="recre"], [data-theme="nexus"], [data-theme="orbite"], [data-theme="fluide"], [data-theme="vibra"]):is([data-nav-position="gauche"], :not([data-nav-position])) .mise-en-page > .barre-laterale::after {
    transition: none;
  }
}
