/* Kuma — coquille d'administration (« kc- »)
 *
 * Le système de la coquille : jetons, mise en page (barre latérale sombre + barre
 * supérieure), composants de tableau de bord (cartes, indicateurs, tableaux, graphiques
 * serveur, panneau latéral, panneau de détail `<aside>`). Les neuf feuilles historiques
 * (`kuma.css`, `material.css`, `ecrans-*.css`) gardent le vocabulaire de chaque écran de
 * détail ; celle-ci porte la coquille qui les entoure et les composants partagés par tous
 * les tableaux de bord.
 *
 * Issue de la fusion de `kc-direction.css` (direction artistique validée par Bruno,
 * 2026-10-10) dans cette feuille : une seule convention, pas deux — `kc-direction.css` a
 * disparu. Les classes encore utilisées par des écrans non migrés (`.kc-kpi` en boîte
 * autonome, `.kc-grid--2/--3/--4`, l'empilement `<td data-label>` sous 640 px) restent
 * prises en charge avec les nouveaux jetons, le temps que chaque lot de
 * `docs/maquettes/overhaul/DIRECTION.md` migre son écran vers `.kc-card`/`.kc-row-compact`.
 *
 * Police : uniquement la pile déjà auto-hébergée par le dépôt (« Roboto Kuma », servie par
 * `material.css`) et des piles système en repli — aucune police distante, aucun `@font-face`
 * supplémentaire.
 *
 * CSP stricte : aucun style ni script en ligne, aucune ressource externe. Le repli mobile de
 * la barre latérale et le panneau de sélection groupée sont des sélecteurs CSS purs
 * (`:has()`), sans script ; le panneau `<aside id="kc-aside">` s'ouvre sans script par un
 * lien de navigation normal (page complète), et s'enrichit avec `kc-admin.js` (fragment
 * chargé en place, fermeture au clavier/à la souris, focus piégé).
 */

/* ====================== Jetons ====================== */

:root {
  /* — Couleurs de marque — un vert profond, plus soutenu que l'ancien primaire, qui porte
     aussi bien un dégradé de sidebar sombre qu'un bouton clair. */
  --kc-color-primary-900: #06251b;
  --kc-color-primary-800: #0c2620;
  --kc-color-primary-700: #0e3c2d;
  --kc-color-primary-600: #0e5a41;
  --kc-color-primary: #0b6e4f;
  --kc-color-primary-hover: #095a40;
  --kc-color-primary-active: #084a34;
  --kc-color-primary-soft: #e3efe8;
  --kc-color-primary-soft-2: #d3e9df;
  --kc-color-on-primary: #ffffff;

  /* — Neutres et surfaces — */
  --kc-color-bg: #eef2f4;
  --kc-color-bg-2: #e6ebee;
  --kc-color-surface: #ffffff;
  --kc-color-surface-2: #f6f9f8;
  --kc-color-surface-3: #eef4f1;
  --kc-color-border: #d8e0e3;
  --kc-color-border-soft: #e6ecee;
  /* Bordure des CHAMPS DE FORMULAIRE (input/select/textarea), distincte de
     `--kc-color-border` (décorative, cartes/tableaux/puces) : 3,56:1 contre
     `--kc-color-surface` (#fff), seuil WCAG 1.4.11 pour une limite de composant
     d'interface — `--kc-color-border` seule (#d8e0e3, 1,34:1) y échouait (#168). */
  --kc-color-border-interactif: #7c8a90;
  --kc-color-text: #111827;
  --kc-color-text-soft: #4b5563;
  --kc-color-text-muted: #54606d;

  /* — Sidebar sombre : dégradé diagonal entre une ardoise profonde et le vert de marque,
     jamais du noir pur — c'est ce qui le distingue d'une coquille d'admin générique. */
  --kc-color-sidebar-grad: linear-gradient(165deg, #101b2b 0%, #0c2620 55%, #06251b 100%);
  --kc-color-sidebar-text: #e8edf0;
  --kc-color-sidebar-text-muted: #aab6bd;
  --kc-color-sidebar-active-bg: linear-gradient(135deg, #1c5c45 0%, #164a38 100%);
  --kc-color-sidebar-hover-bg: rgba(255, 255, 255, 0.07);
  --kc-color-sidebar-border: rgba(255, 255, 255, 0.1);

  /* — États : succès, attention, critique, information, neutre — et un septième ton,
     « attente », réservé à `non_demontre` : une absence de preuve n'est pas un échec, elle ne
     peut donc pas partager la couleur de l'écart constaté (`danger`). Violet ardoise plutôt
     qu'un rouge ou un ambre, pour qu'il se lise comme « à instruire », pas « en faute ». */
  --kc-color-success: #0f5132;
  --kc-color-success-soft: #e3efe8;
  --kc-color-warn: #7a4e05;
  --kc-color-warn-soft: #fbf0da;
  --kc-color-danger: #7c2017;
  --kc-color-danger-soft: #f8e6e3;
  --kc-color-info: #134a6b;
  --kc-color-info-soft: #e3eef8;
  --kc-color-neutral: #374049;
  --kc-color-neutral-soft: #edf0f2;
  --kc-color-attente: #3a3470;
  --kc-color-attente-soft: #eceafc;

  /* — Espacement : échelle régulière 4 → 64, exprimée en rem, plus deux jetons fluides pour
     les marges latérales (360 → 1920 px) et les gouttières de grille. — */
  --kc-space-1: 0.25rem;
  --kc-space-2: 0.5rem;
  --kc-space-3: 0.75rem;
  --kc-space-4: 1rem;
  --kc-space-5: 1.5rem;
  --kc-space-6: 2rem;
  --kc-space-7: 3rem;
  --kc-space-8: 4rem;
  --kc-gutter-fluide: clamp(1rem, 1vw + 0.6rem, 2rem);
  --kc-marge-fluide: clamp(1rem, 3vw + 0.25rem, 4rem);

  /* — Système unique de surfaces (décision du coordinateur, 10.10.2026) : la pile de
     page espace tous les blocs de premier niveau d'un écran par un seul jeton, jamais une
     marge posée par le bloc lui-même ; `--kc-section-gap` fait la même chose à l'intérieur
     d'une `.kc-section` ; `--kc-grid-gap` est le jeton de gouttière des grilles de cartes
     (alias de `--kc-gutter-fluide`, nommé pour sa fonction plutôt que son implémentation) ;
     `--kc-card-pad` est le padding interne de toute carte, réduit sous 640 px ;
     `--kc-border-thin` est la seule largeur de bordure d'une surface. */
  --kc-page-gap: var(--kc-space-6);
  --kc-section-gap: var(--kc-space-4);
  --kc-grid-gap: var(--kc-gutter-fluide);
  --kc-card-pad: var(--kc-space-5);
  --kc-border-thin: 1px;

  /* — Rayons et élévations — trois niveaux, une ombre plus marquée au survol/focus plutôt
     qu'une bordure qui change de couleur. — */
  --kc-radius-sm: 6px;
  --kc-radius-md: 10px;
  --kc-radius-lg: 16px;
  --kc-radius-pill: 999px;
  --kc-shadow-1: 0 1px 2px rgba(6, 20, 15, 0.06), 0 1px 1px rgba(6, 20, 15, 0.04);
  --kc-shadow-2: 0 6px 16px rgba(6, 20, 15, 0.08), 0 2px 6px rgba(6, 20, 15, 0.05);
  --kc-shadow-3: 0 16px 40px rgba(6, 20, 15, 0.14), 0 6px 16px rgba(6, 20, 15, 0.08);

  /* — Typographie — même pile déjà auto-hébergée par `material.css`, échelle élargie pour
     marquer la hiérarchie page / section / chiffre clé / libellé. — */
  --kc-font: "Roboto Kuma", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --kc-font-size-xs: 0.78rem;
  --kc-font-size-sm: 0.86rem;
  --kc-font-size-md: 0.95rem;
  --kc-font-size-lg: 1.15rem;
  --kc-font-size-xl: 1.5rem;
  --kc-font-size-2xl: clamp(1.5rem, 1.1rem + 1vw, 2rem);
  --kc-font-size-kpi: clamp(2rem, 1.6rem + 1.4vw, 2.75rem);
  --kc-line-height-tight: 1.15;
  --kc-line-height-normal: 1.5;
  --kc-weight-regular: 400;
  --kc-weight-medium: 500;
  --kc-weight-bold: 700;

  /* — Mise en page — */
  --kc-sidebar-width: 18rem;
  --kc-sidebar-width-collapsed: 4.5rem;
  --kc-topbar-height: 3.75rem;
  --kc-content-max: 1920px;

  /* — Animation : jetons de durée et de courbe, jamais une valeur en dur dans une règle. — */
  --kc-dur-rapide: 150ms;
  --kc-dur-normale: 220ms;
  --kc-dur-lente: 300ms;
  --kc-ease-sortie: cubic-bezier(0.16, 1, 0.3, 1);
  --kc-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --kc-ease-entree: cubic-bezier(0.34, 1.56, 0.64, 1);
  --kc-decalage-cascade: 40ms;
}

/* Padding de carte réduit sous 640 px — même jeton partout, une seule redéfinition. */
@media (max-width: 639.98px) {
  :root {
    --kc-card-pad: var(--kc-space-4);
  }
}

/* Tout le monde éteint : aucune animation, aucune transition, aucun défilement progressif de
   jauge, quelle que soit la raison du réglage système (vestibulaire, batterie, préférence). */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

* {
  box-sizing: border-box;
}

/* `<fieldset>` natif porte une bordure `groove` de 2px par défaut, jamais demandée par le
   dépôt : premier écran à utiliser un fieldset sans classe de présentation propre à le
   masquer (acceptation.html, groupe de radios « Base opposable »). Le contrôle de
   cohérence refuse toute bordure gauche > 1px hors `.kc-page-header` (règle e) ; un
   fieldset qui a besoin d'un filet le redéclare explicitement avec les jetons `--kc-*`. */
fieldset {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

body {
  margin: 0;
  font-family: var(--kc-font);
  color: var(--kc-color-text);
  background: var(--kc-color-bg);
  -webkit-font-smoothing: antialiased;
}

/* Couleur de lien par défaut — tout le texte de contenu (« Revoir les risques… », « Retour
   au dossier »…) qui ne porte pas de classe dédiée (`.kc-nav-lien`, `.kc-btn`, `.kc-chip`…)
   passe par ici plutôt que par le bleu de visite par défaut du navigateur. Les composants
   qui ont besoin d'une autre couleur (lien de sidebar sombre, bouton) le font déjà par une
   règle plus spécifique, posée après celle-ci dans ce même fichier ou dans `kuma.css`. */
a {
  color: var(--kc-color-primary);
  text-decoration-color: color-mix(in srgb, var(--kc-color-primary) 40%, transparent);
  text-underline-offset: 0.15em;
}

a:hover {
  color: var(--kc-color-primary-hover);
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--kc-color-primary);
  outline-offset: 2px;
}

/* ====================== Keyframes ====================== */

@keyframes kc-entree-douce {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes kc-entree-lateral {
  from {
    opacity: 0;
    transform: translateX(16px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes kc-patte-respire {
  0%, 100% {
    opacity: 0.55;
  }
  50% {
    opacity: 1;
  }
}

/* Pas de custom property par instance (ce serait un `style=""`, interdit par la CSP) :
   l'anneau démarre toujours du cercle entier (circonférence fixe, rayon 50 — même rayon que
   `_graphique_anneau.html`) et l'animation ne déclare que son état `from` : son état `to`
   retombe implicitement sur la valeur déjà posée par l'attribut `stroke-dashoffset` de
   chaque `<circle>`, calculé côté serveur comme les graphiques existants. */
@keyframes kc-jauge-anneau {
  from {
    stroke-dashoffset: 314;
  }
}

@keyframes kc-jauge-barre {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

@keyframes kc-voile-entree {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* ====================== Accessibilité ====================== */

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

/* Case masquée qui pilote le tiroir mobile et le rail d'icônes (≥ 1024 px) de la barre
   latérale, par `:has()` — sans script. */
.kc-sidebar-toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

/* ====================== Mise en page ====================== */

.kc-app {
  display: grid;
  grid-template-columns: var(--kc-sidebar-width) minmax(0, 1fr);
  min-height: 100vh;
}

.kc-sidebar {
  grid-row: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: var(--kc-space-5);
  padding: var(--kc-space-5) var(--kc-space-4);
  background: var(--kc-color-sidebar-grad);
  color: var(--kc-color-sidebar-text);
  overflow-y: auto;
}

.kc-sidebar-overlay {
  display: none;
}

.kc-marque {
  display: flex;
  align-items: center;
  gap: var(--kc-space-2);
  font-size: var(--kc-font-size-lg);
  font-weight: var(--kc-weight-bold);
  color: var(--kc-color-sidebar-text);
  text-decoration: none;
  letter-spacing: 0.01em;
}

/* Le sceau Kizyun se suffit de sa propre couleur (fond + marque imprimés dans le SVG, coins
   déjà arrondis par son tracé) : contrairement aux icônes nues de `icones.py`, il n'hérite
   rien de `currentColor`. Posé en ligne par `svg_en_ligne` (jamais `<img src="...">`, voir
   `missions/templatetags/actifs.py`) : le `<svg>` qu'il enveloppe n'a ni `width` ni `height`
   propres (seulement son `viewBox`), donc cette classe fixe la boîte et l'enfant la remplit
   entièrement. 28px minimum (constat de Bruno, #119) : en dessous, le sceau perdait son
   caractère 基 à l'œil dans le rail de la barre latérale. */
.kc-marque-symbole {
  display: inline-flex;
  flex: none;
  width: 28px;
  height: 28px;
}

.kc-marque-symbole svg {
  width: 100%;
  height: 100%;
  display: block;
}

.kc-sidebar-dossier {
  padding: var(--kc-space-3) var(--kc-space-3);
  border-radius: var(--kc-radius-md);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--kc-color-sidebar-border);
  font-size: var(--kc-font-size-sm);
}

/* 2.5.8 : texte seul ~16 px de haut (`font-size-sm`), sous les 24 px requis — zone cliquable
   agrandie par `padding-block`/`margin-block` négatif de même ampleur : la boîte visible du
   lien ne bouge pas (la marge négative annule la poussée du padding sur le flux), seule sa
   boîte de bordure — donc la zone cliquable — grandit (#168). `inline-block` est requis : un
   `<a>` `inline` ignore `margin-block` (sans effet sur le flux, impossible à compenser). */
.kc-sidebar-dossier a {
  display: inline-block;
  padding-block: 5px;
  margin-block: -5px;
  color: #8fe3bd;
  text-decoration: none;
}

.kc-sidebar-dossier strong {
  display: block;
  margin-top: var(--kc-space-1);
  color: var(--kc-color-sidebar-text);
  font-size: var(--kc-font-size-md);
}

.kc-nav {
  display: flex;
  flex-direction: column;
  gap: var(--kc-space-5);
  flex: 1;
}

.kc-nav-groupe-titre {
  margin: 0 0 var(--kc-space-2);
  padding: 0 var(--kc-space-2);
  font-size: var(--kc-font-size-xs);
  font-weight: var(--kc-weight-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--kc-color-sidebar-text-muted);
}

.kc-nav-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.kc-nav-lien {
  display: flex;
  align-items: center;
  gap: var(--kc-space-3);
  min-height: 40px;
  padding: var(--kc-space-2) var(--kc-space-3);
  border-radius: var(--kc-radius-sm);
  color: var(--kc-color-sidebar-text);
  text-decoration: none;
  font-size: var(--kc-font-size-sm);
  transition: background var(--kc-dur-rapide) var(--kc-ease-standard),
    transform var(--kc-dur-rapide) var(--kc-ease-standard);
}

.kc-nav-lien:hover {
  background: var(--kc-color-sidebar-hover-bg);
  transform: translateX(2px);
}

.kc-nav-lien:focus-visible {
  outline: 2px solid #5fd6a8;
  outline-offset: 2px;
}

.kc-nav-lien[aria-current="page"] {
  background: var(--kc-color-sidebar-active-bg);
  font-weight: var(--kc-weight-medium);
  box-shadow: var(--kc-shadow-1);
}

.kc-icone {
  flex: none;
  width: 18px;
  height: 18px;
}

/* Empreinte Kuma : ≈ 1em, posée dans le fil du texte qui l'entoure (bulle, badge, libellé de
   nav) — jamais dans la grille d'icônes de navigation, donc jamais figée à 18px comme
   `.kc-icone` ci-dessus. `em` suit la taille de police du contexte qui la porte plutôt qu'une
   valeur fixe : la même icône reste discrète dans un petit texte de méta comme dans un titre. */
.kc-icone--patte {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  margin-inline-start: 0.3em;
}

/* Option de l'énoncé #119 : une apparition très légère quand Kuma est en train d'agir (atelier
   demandé, réponse en attente) — jamais sur une empreinte simplement posée à côté d'un libellé
   stable. Coupée comme toute animation du dépôt par la règle `prefers-reduced-motion` globale
   (tout en haut de ce fichier), sans script dédié. */
.kc-icone--patte-actif {
  animation: kc-patte-respire 1400ms var(--kc-ease-standard) infinite;
}

.kc-main-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.kc-topbar {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: var(--kc-space-4);
  min-height: var(--kc-topbar-height);
  min-width: 0;
  padding-inline: var(--kc-marge-fluide);
  background: var(--kc-color-surface);
  border-bottom: 1px solid var(--kc-color-border);
}

/* 2.4.11 : `position: sticky` recouvre le haut du viewport en permanence — sans
   `scroll-margin-top`, le défilement automatique du navigateur au focus (`Tab`) amène
   l'élément focalisé exactement au bord haut du viewport, SOUS la barre collante, masqué
   (constaté sur `a.kc-table__clamp`, table « Documents », mais vrai de tout focalisable
   proche du haut d'une longue page). Règle universelle plutôt que ciblée : n'importe quel
   élément focalisable peut un jour se retrouver en haut de page (#168). */
:focus-visible {
  scroll-margin-top: calc(var(--kc-topbar-height) + var(--kc-space-2));
}

.kc-topbar-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: var(--kc-radius-md);
  cursor: pointer;
  color: var(--kc-color-text);
}

.kc-topbar-toggle:hover {
  background: var(--kc-color-neutral-soft);
}

.kc-sidebar-toggle:focus-visible ~ .kc-app .kc-topbar-toggle,
.kc-topbar-toggle:focus-visible {
  outline: 2px solid var(--kc-color-primary);
  outline-offset: 2px;
}

/* Tout menu déroulant de la barre supérieure (cloche de notifications, menu de compte) est
   un `<details>` natif sans marqueur de triangle — sinon le navigateur en dessine un
   (« ▶ ») à côté de l'icône qu'il porte déjà, un glyphe parasite que rien dans le gabarit ne
   demande. */
.kc-topbar details > summary {
  list-style: none;
  cursor: pointer;
}

.kc-topbar details > summary::-webkit-details-marker {
  display: none;
}

.kc-fil-ariane {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--kc-space-2);
  flex: 1;
  min-width: 0;
  font-size: var(--kc-font-size-sm);
  color: var(--kc-color-text-soft);
}

.kc-fil-ariane a {
  color: var(--kc-color-text-soft);
  text-decoration: none;
  /* Cible >= 24px de haut (WCAG 2.2, 2.5.8) : la marge négative annule l'empreinte visuelle
     du padding ajouté dans la barre (`align-items: center`), seule la zone cliquable grandit. */
  display: inline-block;
  padding-block: 0.3rem;
  margin-block: -0.3rem;
}

.kc-fil-ariane a:hover,
.kc-fil-ariane a:focus-visible {
  text-decoration: underline;
}

/* 2.5.8 : lien « Annuler » isolé sous un formulaire (`appareils_ajouter.html`,
   `appareils_confirmer.html`, `appareils_passkey_ajouter.html`), ~18 px de haut — même
   technique que `.kc-fil-ariane a` ci-dessus. */
.kc-lien-annuler {
  display: inline-block;
  padding-block: 4px;
  margin-block: -4px;
}

.kc-fil-ariane .kc-fil-courant {
  color: var(--kc-color-text);
  font-weight: var(--kc-weight-medium);
}

.kc-topbar-spacer {
  flex: 1;
}

/* Pleine largeur : plus de `max-width` figé — le contenu respire jusqu'au bord de la zone
   disponible, bordé seulement par une marge fluide qui grandit avec l'écran (360 → 1920 px).
   `--kc-content-max` existe pour empêcher une table de données de dépasser 1920 px, pas pour
   recentrer le contenu en colonne étroite sur un grand écran.

   Pile de page (règle 1 du système de surfaces) : chaque bloc de premier niveau rendu par
   `{% block contenu %}` (en-tête, grille de KPI, carte, barre d'outils, table, section…)
   est un enfant direct de `.kc-main`, espacé uniquement par ce `gap` — plus aucune marge
   verticale posée par le bloc lui-même. */
.kc-main {
  display: flex;
  flex-direction: column;
  gap: var(--kc-page-gap);
  flex: 1;
  width: 100%;
  max-width: var(--kc-content-max);
  margin-inline: auto;
  min-width: 0;
  padding: var(--kc-space-6) var(--kc-marge-fluide) var(--kc-space-8);
}

/* Sous 1024 px, la barre latérale devient un tiroir : repliée hors écran, ouverte par la
   case à cocher d'en-tête via `:has()`, sans script. */
@media (max-width: 1023.98px) {
  .kc-app {
    display: block;
  }

  .kc-sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 20;
    width: min(85vw, var(--kc-sidebar-width));
    transform: translateX(-100%);
    box-shadow: var(--kc-shadow-3);
    transition: transform var(--kc-dur-lente) var(--kc-ease-sortie);
  }

  body:has(.kc-sidebar-toggle:checked) .kc-sidebar {
    transform: translateX(0);
  }

  .kc-sidebar-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 15;
    background: rgba(6, 20, 15, 0.45);
    border: 0;
    padding: 0;
    cursor: pointer;
  }

  body:has(.kc-sidebar-toggle:checked) .kc-sidebar-overlay {
    display: block;
  }
}

/* À partir de 1024 px, la même case replie la barre latérale en rail d'icônes plutôt que de
   la masquer : la navigation reste accessible, elle prend juste moins de place. */
@media (min-width: 1024px) {
  body:has(.kc-sidebar-toggle:checked) .kc-app {
    grid-template-columns: var(--kc-sidebar-width-collapsed) minmax(0, 1fr);
  }

  body:has(.kc-sidebar-toggle:checked) .kc-sidebar-dossier,
  body:has(.kc-sidebar-toggle:checked) .kc-nav-groupe-titre,
  body:has(.kc-sidebar-toggle:checked) .kc-nav-lien span.kc-libelle,
  body:has(.kc-sidebar-toggle:checked) .kc-marque-texte {
    display: none;
  }

  body:has(.kc-sidebar-toggle:checked) .kc-nav-lien {
    justify-content: center;
  }
}

@media (max-width: 639.98px) {
  .kc-main {
    padding: var(--kc-space-4) var(--kc-space-3) var(--kc-space-5);
  }

  .kc-topbar {
    padding-inline: var(--kc-space-3);
  }

  .kc-fil-ariane {
    flex-wrap: nowrap;
    min-width: 0;
    flex: 1 1 auto;
  }

  .kc-fil-ariane .kc-fil-courant {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
  }
}

/* ====================== En-tête de page ====================== */

/* Dégradé SUBTIL — surface très légèrement teintée vers le primaire, jamais le bandeau vert
   plein : à côté d'une barre latérale déjà sombre, un en-tête tout aussi encré alourdissait
   l'écran sans rien ajouter. Le titre reste en encre sombre, un liseré fin signale la page. */
.kc-page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--kc-space-5);
  padding: var(--kc-space-5) var(--kc-space-6);
  border: 1px solid var(--kc-color-border);
  border-inline-start: 4px solid var(--kc-color-primary);
  border-radius: var(--kc-radius-lg);
  background: linear-gradient(135deg, var(--kc-color-surface) 0%, var(--kc-color-primary-soft-2) 140%);
  color: var(--kc-color-text);
  box-shadow: var(--kc-shadow-1);
  animation: kc-entree-douce var(--kc-dur-lente) var(--kc-ease-sortie) both;
}

.kc-page-header-eyebrow {
  margin: 0 0 var(--kc-space-1);
  font-size: var(--kc-font-size-xs);
  font-weight: var(--kc-weight-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--kc-color-primary-hover);
}

.kc-page-header h1 {
  margin: 0;
  font-size: var(--kc-font-size-2xl);
  line-height: var(--kc-line-height-tight);
  font-weight: var(--kc-weight-bold);
  color: var(--kc-color-text);
}

.kc-page-header-sous-titre {
  margin: var(--kc-space-2) 0 0;
  color: var(--kc-color-text-soft);
  font-size: var(--kc-font-size-md);
  max-width: 48rem;
}

.kc-page-header-sous-titre a {
  color: var(--kc-color-primary);
}

.kc-page-header-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kc-space-2);
}

.kc-section-titre {
  margin: 0;
  font-size: var(--kc-font-size-lg);
  font-weight: var(--kc-weight-bold);
  color: var(--kc-color-text);
}

/* Un titre de section n'a plus besoin de `margin-top` : il est un enfant direct de la pile
   de page (`.kc-main`), qui l'espace par `--kc-page-gap`, au même titre que n'importe quel
   autre bloc de premier niveau. */

/* Regroupe un titre de section et son contenu en une seule pile à écart plus serré
   (`--kc-section-gap`) quand plusieurs blocs forment visuellement un seul ensemble —
   l'ensemble reste un unique enfant direct de `.kc-main`, espacé des sections voisines par
   `--kc-page-gap` comme n'importe quel autre bloc de premier niveau. */
.kc-section {
  display: flex;
  flex-direction: column;
  gap: var(--kc-section-gap);
}

/* Pile générique (règle 1 du système de surfaces) : certains gabarits d'écran posent un
   `<div>`/`<form>` englobant entre `.kc-main` et leurs blocs de premier niveau (ex. une mise
   en page qui doit rester un bloc unique pour une raison de structure — un formulaire qui
   doit tout envelopper, un conteneur nommé par l'historique…). Un tel conteneur n'est jamais
   un simple passe-plat : il reste lui-même une pile à l'écart de page, jamais un bloc qui
   pose sa propre marge verticale entre ses enfants. */
.kc-stack {
  display: flex;
  flex-direction: column;
  gap: var(--kc-page-gap);
}

/* ====================== Grilles et cartes ====================== */

/* `auto-fit`/`minmax` fluide pour une grille libre : plus de colonnes apparaissent
   naturellement à mesure que l'écran s'élargit, sans rupture de media query posée en dur.
   `align-items: stretch` (défaut de la grille, posé explicitement) : les cartes d'une même
   rangée partagent toujours la même hauteur — règle 4 du système de surfaces. */
.kc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: var(--kc-grid-gap);
  align-items: stretch;
}

/* Pile verticale de cartes pleine largeur (`<ul class="kc-liste-cartes"><li class="kc-card">`,
   formations.html, entretiens.html, incidents.html, efficacite_nc.html) : cette classe
   n'avait jusqu'ici AUCUNE règle propre — la liste gardait le retrait et les puces par
   défaut du `<ul>`, qui désalignaient chaque carte du titre de section au-dessus d'elle
   (constat sur capture, #174). `.kc-card` pose déjà `display: flex`, qui supprime la puce du
   `<li>` lui-même ; seule la pile l'entourait encore.

   `controle-coherence.py` (règle g, #218) reconnaît désormais qu'un `<ul>`/`<ol>` dont
   CHAQUE `<li>` enfant direct porte lui-même une surface (bordure/ombre/rayon — un `<li
   class="kc-card">`, même si `<li>` reste par ailleurs exclu des surfaces mesurées) est une
   liste purement structurelle, jamais « contenu à nu » : le contournement `kc-stack` posé
   précédemment sur `incidents.html`/`efficacite_nc.html` pour ce seul signalement a donc
   disparu, inutile une fois la règle corrigée. */
.kc-liste-cartes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--kc-grid-gap);
}

/* `.kc-card` ne porte pas de padding propre (voir plus bas : la variante composite —
   `.kc-card__header`/`__body`/`__footer` — le porte à sa place, jamais `.kc-card` lui-même).
   Un `<li class="kc-card">` de cette liste avec du contenu placé directement dedans
   (formations.html, entretiens.html — sans `.kc-card__body`, contrairement à
   incidents.html/efficacite_nc.html) se retrouvait donc sans aucun padding, collé à ses
   propres bords (#218, padding mesuré 0 au lieu du jeton `--kc-card-pad`). Seules les cartes
   SIMPLES de cette liste précise (pas de `.kc-card__header`/`__body`/`__footer` en enfant
   direct) reçoivent le jeton ici : une carte composite garde son padding porté par son
   enfant, jamais doublé. */
.kc-liste-cartes > .kc-card:not(:has(> .kc-card__header, > .kc-card__body, > .kc-card__footer)) {
  padding: var(--kc-card-pad);
}


/* Grille d'indicateurs : nombre de colonnes figé par palier plutôt que fluide — un
   `auto-fit`/`minmax` pur cassait 8 cartes en 5 + 3 à 1920 px (une rangée orpheline). Trois
   paliers couvrent proprement 4 et 8 cartes (les deux comptes les plus courants) : jamais de
   reste, à aucune largeur. */
.kc-grid--kpi {
  grid-template-columns: repeat(1, 1fr);
}

@media (min-width: 360px) {
  .kc-grid--kpi {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .kc-grid--kpi {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* Toujours 4 cartes (tableau_de_bord.html) : `auto-fit` laissait une rangée incomplète à
   1536 px (3 colonnes y tiennent, 4 cartes n'en remplissent pas un multiple exact, #168) —
   un palier fixe à 2 colonnes (jamais 3) garde chaque rangée pleine à toute largeur. */
.kc-grid--graphiques {
  grid-template-columns: 1fr;
}

@media (min-width: 768px) {
  .kc-grid--graphiques {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Paliers hérités, encore utilisés par les écrans qui n'ont pas migré vers `.kc-grid--kpi` /
   `.kc-card` (voir le plan de lots, `docs/maquettes/overhaul/DIRECTION.md`). */
.kc-grid--2 {
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.kc-grid--3 {
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}

.kc-grid--4 {
  grid-template-columns: repeat(4, 1fr);
}

@media (max-width: 1023.98px) {
  .kc-grid--4 {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 639.98px) {
  .kc-grid--4 {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Surface unique (règle 2 du système de surfaces) : `.kc-card` est LE bloc visuel partagé
   par tout le tableau de bord — KPI (`.kc-kpi`), barre d'outils (`.kc-toolbar`), alerte
   (`.kc-alert`), ligne compacte mobile (`.kc-row-compact`), état vide (`.kc-empty`)
   partagent exactement ce même fond, cette même bordure, ce même rayon et cette même
   ombre, posés une seule fois ici ; chacun garde sa propre mise en page (`display`, `gap`,
   `padding`) ci-dessous. */
.kc-card,
.kc-kpi,
.kc-toolbar,
.kc-alert,
.kc-row-compact,
.kc-empty {
  background: linear-gradient(180deg, var(--kc-color-surface) 0%, var(--kc-color-surface-2) 100%);
  border: var(--kc-border-thin) solid var(--kc-color-border-soft);
  border-radius: var(--kc-radius-lg);
  box-shadow: var(--kc-shadow-1);
}

.kc-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transition: box-shadow var(--kc-dur-normale) var(--kc-ease-standard),
    transform var(--kc-dur-normale) var(--kc-ease-standard);
  animation: kc-entree-douce var(--kc-dur-lente) var(--kc-ease-sortie) both;
}

/* Seule variante de surface (règle 2) : un encadré imbriqué DANS une carte — fond en
   retrait, sans bordure ni ombre propres, jamais une seconde carte dans une carte. */
.kc-card--plat {
  background: var(--kc-color-surface-2);
  border: 0;
  border-radius: var(--kc-radius-md);
  box-shadow: none;
  padding: var(--kc-card-pad);
}

/* Cascade légère : chaque carte d'une grille entre un peu après la précédente — jamais plus
   de 8 incréments, au-delà l'œil ne distingue plus le décalage et ça ralentirait l'affichage
   d'une longue liste pour rien. */
.kc-grid > .kc-card:nth-child(1) { animation-delay: calc(var(--kc-decalage-cascade) * 0); }
.kc-grid > .kc-card:nth-child(2) { animation-delay: calc(var(--kc-decalage-cascade) * 1); }
.kc-grid > .kc-card:nth-child(3) { animation-delay: calc(var(--kc-decalage-cascade) * 2); }
.kc-grid > .kc-card:nth-child(4) { animation-delay: calc(var(--kc-decalage-cascade) * 3); }
.kc-grid > .kc-card:nth-child(5) { animation-delay: calc(var(--kc-decalage-cascade) * 4); }
.kc-grid > .kc-card:nth-child(6) { animation-delay: calc(var(--kc-decalage-cascade) * 5); }
.kc-grid > .kc-card:nth-child(7) { animation-delay: calc(var(--kc-decalage-cascade) * 6); }
.kc-grid > .kc-card:nth-child(8) { animation-delay: calc(var(--kc-decalage-cascade) * 7); }

a.kc-card {
  text-decoration: none;
  color: inherit;
}

a.kc-card:hover,
a.kc-card:focus-visible {
  box-shadow: var(--kc-shadow-3);
  transform: translateY(-2px);
}

a.kc-card:focus-visible {
  outline: 2px solid var(--kc-color-primary);
  outline-offset: 2px;
}

.kc-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--kc-space-3);
  padding: var(--kc-space-4) var(--kc-card-pad) 0;
}

/* Un titre de carte peut porter n'importe quel niveau de titre (`h2` ou `h3` selon le
 * gabarit) : `body.theme-md h2`/`body.theme-md h3` (`material.css`) ont la même
 * spécificité (1 classe + 1 élément) qu'une `.kc-card__title` seule et chargent avant
 * `kc-admin.css`, mais un lien perdu dans un autre gabarit pourrait inverser cet ordre —
 * `:is(h1, h2, h3, h4, h5, h6).kc-card__title` porte la même spécificité à la cible exacte,
 * sans dépendre de l'ordre de chargement des feuilles. `body.theme-md h2` pèse une classe et
 * deux éléments : seul `body.theme-md :is(...).kc-card__title` (deux classes, un élément)
 * l'emporte, sinon un `h2.kc-card__title` retombe à 1.375rem (contrôle visuel, (l)). */
.kc-card__title,
:is(h1, h2, h3, h4, h5, h6).kc-card__title,
body.theme-md :is(h1, h2, h3, h4, h5, h6).kc-card__title {
  margin: 0;
  font-size: var(--kc-font-size-md);
  font-weight: var(--kc-weight-bold);
  font-family: inherit;
  letter-spacing: normal;
  text-transform: none;
  border: 0;
  padding: 0;
}

.kc-card__body {
  padding: var(--kc-card-pad);
  flex: 1;
}

.kc-card__footer {
  padding: var(--kc-space-3) var(--kc-card-pad);
  border-top: var(--kc-border-thin) solid var(--kc-color-border-soft);
  font-size: var(--kc-font-size-sm);
  color: var(--kc-color-text-soft);
}

/* Boîte héritée : un indicateur autonome, pas encore enveloppé dans `.kc-card` — encore
   utilisée par une quinzaine d'écrans non migrés. Hérite strictement de la surface carte
   (bloc partagé ci-dessus) : seule sa mise en page (pile, gap, padding) lui est propre. */
.kc-kpi {
  display: flex;
  flex-direction: column;
  gap: var(--kc-space-1);
  padding: var(--kc-card-pad);
}

/* ====================== Indicateurs ====================== */

.kc-kpi-tete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--kc-space-2);
}

/* Hauteur réservée à deux lignes, quel que soit le nombre réel de mots du libellé : sans
   ça, une carte dont le libellé tient sur une ligne (« Retenus ») et sa voisine dont le
   libellé déborde sur deux (« Score de fiabilité IA ») n'alignaient pas leurs chiffres sur
   la même rangée — mesuré sur la grille KPI du tableau de bord. */
.kc-kpi__label {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 0;
  min-height: calc(var(--kc-font-size-sm) * 1.3 * 2);
  font-size: var(--kc-font-size-sm);
  font-weight: var(--kc-weight-medium);
  color: var(--kc-color-text-soft);
  line-height: 1.3;
}

.kc-kpi__value {
  margin: var(--kc-space-1) 0 0;
  font-size: var(--kc-font-size-kpi);
  line-height: 1.05;
  font-weight: var(--kc-weight-bold);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.kc-kpi__delta {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  width: fit-content;
  font-size: var(--kc-font-size-xs);
  font-weight: var(--kc-weight-bold);
}

.kc-kpi__delta--hausse {
  color: var(--kc-color-success);
}

.kc-kpi__delta--baisse {
  color: var(--kc-color-danger);
}

.kc-kpi__hint {
  margin: var(--kc-space-2) 0 0;
  font-size: var(--kc-font-size-xs);
  color: var(--kc-color-text-muted);
  line-height: var(--kc-line-height-normal);
}

/* Frise du parcours d'une version (#3, US-10.2, `_frise_parcours.html`) : le nom d'un acteur
   (ex. un identifiant de démo à tirets) se coupait sur trois lignes dans la colonne étroite
   du panneau latéral (rapport de l'orchestrateur du 15.10.2026) — `overflow-wrap: anywhere`
   laisse le mot se rompre proprement à son point de coupe le plus lisible au lieu de laisser
   le navigateur choisir chaque tiret comme un point de rupture équivalent. Chaque étape
   garde un espacement vertical net (`kc-space-2`) entre son libellé et ses détails.

   `display: block` (rapport de l'orchestrateur du 15.10.2026, capture `captures/3-signature/
   signe-empreinte-1440.png`) : `.kc-list > li` (plus bas dans ce fichier) impose une ligne
   flex (`justify-content: space-between`), pensée pour un libellé + une métadonnée côte à
   côte — la frise empile plusieurs blocs sous le même `<li>` (horodatage, empreintes), qui
   devenaient alors des items flex compressés à leur largeur minimale : l'horodatage se
   repliait caractère par caractère (`overflow-wrap: anywhere` hérité, ci-dessous) au lieu de
   s'écouler sur sa propre ligne. Sélecteur local à la frise seule (`ol.kc-frise-parcours`),
   jamais une règle touchée dans `.kc-list` elle-même, partagée par d'autres écrans. */
.kc-frise-parcours li,
ol.kc-frise-parcours > li {
  display: block;
  margin-block-end: var(--kc-space-3);
  line-height: var(--kc-line-height-normal);
}

.kc-frise-parcours .kc-kpi__hint {
  overflow-wrap: anywhere;
}

/* Les deux empreintes scellées de l'étape « signé » (contenu, PDF — rapport de
   l'orchestrateur du 15.10.2026) : chacune sur sa propre ligne, libellé + empreinte + bouton
   de copie alignés, plutôt qu'un bouton flottant sans repère visuel de ce qu'il copie. */
.kc-frise-parcours__empreinte {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kc-space-2);
  margin-block-start: var(--kc-space-2);
}

.kc-frise-parcours__empreinte .kc-kpi__hint {
  margin: 0;
  min-width: 6rem;
}

.kc-kpi-pastille {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
  box-shadow: 0 0 0 3px transparent;
  transition: box-shadow var(--kc-dur-normale) var(--kc-ease-standard);
}

.kc-kpi-pastille--ok { background: var(--kc-color-success); }
.kc-kpi-pastille--warn { background: var(--kc-color-warn); }
.kc-kpi-pastille--danger { background: var(--kc-color-danger); }
.kc-kpi-pastille--info { background: var(--kc-color-info); }
.kc-kpi-pastille--attente { background: var(--kc-color-attente); }

a.kc-card:hover .kc-kpi-pastille,
a.kc-card:focus-visible .kc-kpi-pastille {
  box-shadow: 0 0 0 4px var(--kc-color-primary-soft);
}

/* ====================== Pastilles de statut ====================== */

.kc-badge {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  gap: var(--kc-space-1);
  padding: 0.2rem var(--kc-space-2);
  border-radius: var(--kc-radius-pill);
  font-size: var(--kc-font-size-xs);
  font-weight: var(--kc-weight-medium);
  line-height: 1.4;
  white-space: nowrap;
  background: var(--kc-color-neutral-soft);
  color: var(--kc-color-neutral);
}

.kc-badge--ok { background: var(--kc-color-success-soft); color: var(--kc-color-success); }
.kc-badge--warn { background: var(--kc-color-warn-soft); color: var(--kc-color-warn); }
.kc-badge--danger { background: var(--kc-color-danger-soft); color: var(--kc-color-danger); }
.kc-badge--info { background: var(--kc-color-info-soft); color: var(--kc-color-info); }
.kc-badge--neutral { background: var(--kc-color-neutral-soft); color: var(--kc-color-neutral); }

/* Septième ton, propre à `non_demontre` : ni succès ni échec, une absence de preuve en
   attente d'instruction. Distinct visuellement de `--danger` (rouge brique) et de `--warn`
   (ambre) — violet ardoise, pour ne jamais se lire comme un écart. */
.kc-badge--attente { background: var(--kc-color-attente-soft); color: var(--kc-color-attente); }

/* ====================== Alerte composée ======================
 * Un bandeau à deux niveaux (titre court, phrase d'action) pour les blocages qui exigent une
 * action avant de continuer — p. ex. « aucun critère de risque en vigueur » sur `risques.html`.
 * Remplace un `<p class="constat">` isolé par un composant lisible, cohérent avec les tons
 * `--kc-color-warn/danger/info` déjà posés par `.kc-badge`. Hérite de la surface carte (fond,
 * rayon, ombre) ; le ton se lit par une bordure fine et un fond teinté — règle 3 du système
 * de surfaces : aucun accent de bordure gauche, réservé à `.kc-page-header`. */
.kc-alert {
  display: flex;
  flex-direction: column;
  gap: var(--kc-space-1);
  padding: var(--kc-space-4);
}

/* `.kc-alert` pose `display: flex` inconditionnellement : sans cette règle, l'attribut
   `hidden` (#135, zone d'erreur JS vide tant qu'aucune erreur n'est survenue) resterait
   visible — une règle d'auteur l'emporte toujours sur le `display: none` de la feuille
   utilisateur, quel que soit l'ordre. */
.kc-alert[hidden] {
  display: none;
}

.kc-alert p {
  margin: 0;
  color: var(--kc-color-text-soft);
}

.kc-alert__titre {
  font-weight: var(--kc-weight-bold);
  color: var(--kc-color-text);
}

.kc-alert--warn { border-color: var(--kc-color-warn); background: var(--kc-color-warn-soft); }
.kc-alert--warn .kc-alert__titre { color: var(--kc-color-warn); }
.kc-alert--danger { border-color: var(--kc-color-danger); background: var(--kc-color-danger-soft); }
.kc-alert--danger .kc-alert__titre { color: var(--kc-color-danger); }
.kc-alert--info { border-color: var(--kc-color-info); background: var(--kc-color-info-soft); }
.kc-alert--info .kc-alert__titre { color: var(--kc-color-info); }
.kc-alert--ok { border-color: var(--kc-color-success); background: var(--kc-color-success-soft); }
.kc-alert--ok .kc-alert__titre { color: var(--kc-color-success); }
.kc-alert--attente { border-color: var(--kc-color-attente); background: var(--kc-color-attente-soft); }
.kc-alert--attente .kc-alert__titre { color: var(--kc-color-attente); }

/* Contenu d'alerte dont la longueur suit des données (ex. résumé des conflits d'indépendance
   par membre, audit interne US-12.6) : hauteur bornée, défilement interne — même patron que
   `.kc-code-bloc` — pour qu'un compte très actif sur la mission n'étire jamais la hauteur de
   l'alerte au gré du nombre de ses conflits. */
.kc-alert__liste-bornee {
  max-height: 12rem;
  overflow-y: auto;
}

/* ====================== Messages applicatifs (django.contrib.messages) ======================
 * `missions/_messages.html`, inclus une fois en tête du bloc de contenu
 * (`.kc-main`/`.kc-public-main`, `missions/base.html`/`base_public.html`) — un enfant de
 * pile ordinaire, sous la barre supérieure, jamais posé en recouvrement (une première
 * version en bandeau `position: fixed` cachait le fil d'Ariane à 1440 px et l'en-tête — y
 * compris le bouton de fermeture — du panneau `#kc-aside` à 390 px, le rendant impossible à
 * fermer ; constat de Bruno). Chaque message réutilise `.kc-alert` (fond/bordure/rayon déjà
 * posés ci-dessus) ; son rôle ARIA (`status`/`alert`) est posé par le gabarit selon le
 * niveau Django. Un bouton de fermeture (`kc-admin.js`, amélioration progressive — sans
 * script, le message disparaît de toute façon à la requête suivante, déjà consommé côté
 * serveur) — Issue #124.
 *
 * Un écran dont une action du panneau `#kc-aside` peut elle-même poser un message (ex.
 * vérifier l'intégrité d'une version de preuve, `_preuves_aside_ligne.html`) inclut la même
 * zone une seconde fois, sous l'en-tête du panneau (`.kc-aside__tete`) : sous 640 px, le
 * panneau couvre tout l'écran (`.kc-aside`, plus bas) et la zone de page devient invisible —
 * le message doit rester lisible sans fermer le panneau. `{% include %}` deux fois le même
 * contexte `messages` est sûr (la liste chargée par `django.contrib.messages` ne se vide
 * qu'à la fin de la réponse, pas à la première itération) ; cette règle masque la zone de
 * page dès qu'une zone existe déjà dans un panneau ouvert, pour qu'une seule copie reste
 * exposée aux technologies d'assistance à la fois — jamais une double lecture. */
.kc-messages {
  display: flex;
  flex-direction: column;
  gap: var(--kc-space-2);
}

body:has(#kc-aside.kc-aside--ouvert .kc-messages) .kc-main > .kc-messages {
  display: none;
}

.kc-messages__ligne {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--kc-space-3);
}

.kc-messages__fermer {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  inline-size: 1.75rem;
  min-block-size: 1.75rem;
  margin: calc(var(--kc-space-1) * -1) calc(var(--kc-space-1) * -1) 0 0;
  border: 0;
  border-radius: var(--kc-radius-sm);
  background: transparent;
  color: inherit;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.kc-messages__fermer:hover,
.kc-messages__fermer:focus-visible {
  background: rgba(0, 0, 0, 0.08);
}

/* ====================== Boutons ====================== */

.kc-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--kc-space-2);
  min-height: 40px;
  padding: 0.55rem var(--kc-space-4);
  border-radius: var(--kc-radius-md);
  border: 1px solid transparent;
  font-size: var(--kc-font-size-sm);
  font-weight: var(--kc-weight-medium);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--kc-dur-rapide) var(--kc-ease-standard),
    box-shadow var(--kc-dur-rapide) var(--kc-ease-standard),
    transform var(--kc-dur-rapide) var(--kc-ease-standard);
}

.kc-btn:active {
  transform: translateY(1px);
}

.kc-btn:focus-visible {
  outline: 2px solid var(--kc-color-primary);
  outline-offset: 2px;
}

.kc-btn--primary {
  background: linear-gradient(135deg, var(--kc-color-primary) 0%, var(--kc-color-primary-600) 100%);
  color: var(--kc-color-on-primary);
  box-shadow: var(--kc-shadow-1);
}

.kc-btn--primary:hover {
  background: linear-gradient(135deg, var(--kc-color-primary-hover) 0%, var(--kc-color-primary-active) 100%);
  box-shadow: var(--kc-shadow-2);
}

.kc-btn--primary:active {
  background: var(--kc-color-primary-active);
}

.kc-btn--secondary {
  background: var(--kc-color-surface);
  color: var(--kc-color-text);
  border-color: var(--kc-color-border);
}

.kc-btn--secondary:hover {
  background: var(--kc-color-neutral-soft);
}

.kc-btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--kc-color-text);
}

.kc-btn--ghost:hover {
  background: var(--kc-color-neutral-soft);
}

.kc-btn--danger {
  background: var(--kc-color-danger);
  color: #ffffff;
}

.kc-btn--danger:hover {
  background: #611810;
}

/* `body.theme-md button` (material.css) porte la spécificité (0,1,2) — un élément `button` de
   plus que `.kc-btn--primary`/`--secondary`/`--ghost`/`--danger` (0,1,0) — et l'emporte
   silencieusement sur tout `<button class="kc-btn …">`. Ces quatre règles reprennent chaque
   variante avec un sélecteur aussi spécifique que celui qu'elles doivent battre. */
body.theme-md button.kc-btn--primary {
  background: linear-gradient(135deg, var(--kc-color-primary) 0%, var(--kc-color-primary-600) 100%);
  color: var(--kc-color-on-primary);
}

body.theme-md button.kc-btn--primary:hover {
  background: linear-gradient(135deg, var(--kc-color-primary-hover) 0%, var(--kc-color-primary-active) 100%);
}

body.theme-md button.kc-btn--secondary {
  background: var(--kc-color-surface);
  border-color: var(--kc-color-border);
  color: var(--kc-color-text);
}

body.theme-md button.kc-btn--secondary:hover {
  background: var(--kc-color-neutral-soft);
}

body.theme-md button.kc-btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--kc-color-text);
}

body.theme-md button.kc-btn--ghost:hover {
  background: var(--kc-color-neutral-soft);
}

body.theme-md button.kc-btn--danger {
  background: var(--kc-color-danger);
  border-color: var(--kc-color-danger);
  color: #fff;
}

body.theme-md button.kc-btn--danger:hover {
  background: #611810;
  border-color: #611810;
}

.kc-btn--desactive {
  opacity: 0.5;
  pointer-events: none;
}

/* Un libellé déjà raccourci peut rester trop long pour tenir sur une ligne sous ~400 px
   (ex. écran Attestations, ticket I3) : cette variante autorise le retour à la ligne et
   prend toute la largeur disponible, plutôt que de déborder du viewport. */
@media (max-width: 480px) {
  .kc-btn--pleine-largeur-mobile {
    white-space: normal;
    width: 100%;
    text-align: center;
  }
}

/* ====================== État vide ====================== */

.kc-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--kc-space-2);
  padding: var(--kc-card-pad);
  margin: 0;
  text-align: center;
  color: var(--kc-color-text-muted);
}

/* Variante sans surface : état vide (ou message informatif atténué) déjà placé à
 * l'intérieur d'une `.kc-card` — la carte porte déjà fond/bordure/rayon/ombre, `.kc-empty`
 * ne doit pas en ajouter une seconde ni répéter le padding généreux de la carte englobante. */
.kc-empty--inline {
  display: block;
  padding: 0;
  margin: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  text-align: left;
  color: var(--kc-color-text-muted);
}

/* Une ligne d'état vide de table ne porte jamais `kc-empty` directement sur le `<td>` : poser
 * `display: flex` sur un `<td>` désactive son rôle de cellule de tableau (l'UA insère une
 * cellule anonyme sans l'attribut `colspan`), et le message retombe dans la première colonne au
 * lieu de couvrir toute la largeur — mesuré sur `echeances.html` et `risques.html` (dossier
 * vide). `kc-empty` reste donc sur un `<p>`/`<div>` interne au `<td colspan="N">` ; cette règle
 * retire le padding propre à la cellule pour laisser le padding généreux de `.kc-empty` seul
 * gouverner l'espacement, sans le doubler. */
.kc-table td:has(> .kc-empty) {
  padding: 0;
}

/* Le registre des activités de traitement (#115) porte deux fois plus de colonnes que
 * `risques.html`/`echeances.html` : son `<td colspan>` vide dépasse alors la largeur visible
 * de `.kc-table-defilement` (mesuré 3389 px pour 1057 px visibles à 1440 px), et le centrage
 * flex de `.kc-empty` plaçait le message hors champ, sans défilement nécessaire pour le
 * révéler — un tableau pourtant vide semblait vide de tout message. Épingler le message au
 * bord gauche du conteneur de défilement le garde visible quelle que soit la largeur réelle
 * du tableau, sans toucher aux usages de `.kc-empty` hors tableau.
 */
.kc-table td:has(> .kc-empty) > .kc-empty {
  position: sticky;
  left: 0;
  width: fit-content;
}

.kc-empty strong {
  color: var(--kc-color-text);
  font-size: var(--kc-font-size-md);
}


/* ====================== Liste de définitions dans une carte ====================== */

/* Primitive partagée pour un `<dl>` « brut » posé dans une carte (ex. la valeur en vigueur
   d'une saisie) : sous 768 px, le libellé est un petit texte atténué au-dessus de la valeur
   (`display: contents` sur un éventuel `<div>` d'emballage laisse `dt`/`dd` participer
   directement à la grille, que le gabarit les enveloppe ou non) ; à partir de 768 px, deux
   colonnes (libellé ~16 rem, valeur), écart par jeton. Les écrans qui ont déjà leur propre
   style de `<dl>` dédié (`.securite-details`, `.acceptation-details`, `.guide-faits`…)
   n'ont pas besoin de cette classe : elle est réservée aux listes qui n'en ont aucun. */
.kc-dl {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--kc-space-1) var(--kc-space-4);
  margin: 0;
}

.kc-dl > div {
  display: contents;
}

.kc-dl dt {
  font-size: var(--kc-font-size-xs);
  color: var(--kc-color-text-muted);
  font-weight: var(--kc-weight-bold);
}

.kc-dl dd {
  margin: 0;
}

@media (min-width: 768px) {
  .kc-dl {
    grid-template-columns: 16rem 1fr;
  }

  .kc-dl dt {
    padding-block-start: 0.2em;
  }
}

/* ====================== Barre de progression ====================== */

.kc-progress {
  appearance: none;
  display: block;
  width: 100%;
  height: 0.6rem;
  border: none;
  border-radius: var(--kc-radius-pill);
  background: var(--kc-color-neutral-soft);
  overflow: hidden;
}

.kc-progress > span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--kc-color-primary) 0%, #35a97c 100%);
  border-radius: inherit;
}

.kc-progress::-webkit-progress-bar {
  background: var(--kc-color-neutral-soft);
}

.kc-progress::-webkit-progress-value {
  background: linear-gradient(90deg, var(--kc-color-primary) 0%, #35a97c 100%);
  border-radius: var(--kc-radius-pill);
  transition: width var(--kc-dur-lente) var(--kc-ease-sortie);
}

.kc-progress::-moz-progress-bar {
  background: linear-gradient(90deg, var(--kc-color-primary) 0%, #35a97c 100%);
  border-radius: var(--kc-radius-pill);
}

/* ====================== Graphiques serveur ======================
 *
 * Même convention que les gabarits `_graphique_*.html` : la géométrie (`stroke-dasharray`,
 * `stroke-dashoffset`, `width`, `x`) est posée en attributs SVG ordinaires, calculés côté
 * serveur — jamais en `style=""`. La couleur vient d'une classe de ton (`--ok/--warn/
 * --danger/--info/--neutral/--attente`), jamais d'une couleur posée en ligne.
 */

.kc-chart-figure {
  margin: 0;
}

.kc-chart {
  display: block;
  width: 100%;
  height: auto;
}

.kc-chart-anneau {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--kc-space-3);
}

.kc-chart-anneau svg {
  width: 11rem;
  height: 11rem;
}

/* Même classe `.kc-chart-fond` pour le rond de l'anneau (un trait, pas un disque) et le rail
   des barres (un rectangle plein) : le même nom, deux rendus, jamais les deux propriétés sur
   le même élément. */
.kc-chart-anneau .kc-chart-fond {
  stroke: var(--kc-color-neutral-soft);
}

.kc-chart-svg--barre .kc-chart-fond,
.kc-chart-svg--empile .kc-chart-fond {
  fill: var(--kc-color-neutral-soft);
}

.kc-chart-texte-centre {
  font-size: 1.1rem;
  font-weight: var(--kc-weight-bold);
  fill: var(--kc-color-text);
}

/* L'anneau et les barres empilées se remplissent une seule fois, à l'entrée de la carte :
   l'état `from` est déclaré par les keyframes, l'état `to` est déjà celui que l'attribut SVG
   a posé — aucune valeur dupliquée en CSS. Pas de `transform-origin` ici : pour l'anneau, le
   centre de rotation est déjà celui que porte l'attribut `transform="rotate(-90 60 60)"` de
   chaque `<circle>` ; en ajouter un second ici (même « center ») entrait en conflit avec le
   premier et poussait l'arc hors du cadre visible (mesuré : la carte ne montrait plus que
   l'anneau gris). Le contexte barre/empile pose son propre `transform-origin`, ci-dessous. */
.kc-chart-segment {
  animation: kc-jauge-anneau 900ms var(--kc-ease-sortie) 150ms both;
}

/* Un `<circle>` d'anneau reste un trait : les tons ci-dessous posent aussi `fill` (utile au
   rectangle plein des barres), donc l'anneau le remet explicitement à `none`, sans quoi
   chaque arc se peignait en disque plein au lieu d'un simple trait. */
.kc-chart-anneau .kc-chart-segment {
  fill: none;
}

.kc-chart-svg--barre .kc-chart-segment,
.kc-chart-svg--empile .kc-chart-segment,
.kc-chart-svg--frise .kc-chart-segment {
  transform-origin: left center;
  transform: scaleX(1);
  animation: kc-jauge-barre var(--kc-dur-lente) var(--kc-ease-sortie) both;
}

.kc-chart-segment--ok { stroke: var(--kc-color-success); fill: var(--kc-color-success); }
.kc-chart-segment--warn { stroke: var(--kc-color-warn); fill: var(--kc-color-warn); }
.kc-chart-segment--danger { stroke: var(--kc-color-danger); fill: var(--kc-color-danger); }
.kc-chart-segment--info { stroke: var(--kc-color-info); fill: var(--kc-color-info); }
.kc-chart-segment--neutral { stroke: var(--kc-color-neutral); fill: var(--kc-color-neutral); }
.kc-chart-segment--attente { stroke: var(--kc-color-attente); fill: var(--kc-color-attente); }

.kc-chart-teinte--ok { background: var(--kc-color-success); }
.kc-chart-teinte--warn { background: var(--kc-color-warn); }
.kc-chart-teinte--danger { background: var(--kc-color-danger); }
.kc-chart-teinte--info { background: var(--kc-color-info); }
.kc-chart-teinte--neutral { background: var(--kc-color-neutral); }
.kc-chart-teinte--attente { background: var(--kc-color-attente); }

.kc-chart-teinte--ok, .kc-chart-teinte--warn, .kc-chart-teinte--danger,
.kc-chart-teinte--info, .kc-chart-teinte--neutral, .kc-chart-teinte--attente {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 2px;
  margin-inline-end: 0.3rem;
}

/* Espacement depuis les barres/l'anneau qu'elle légende : collée au graphique, elle se lisait
   comme en faisant partie plutôt que comme sa légende. */
.kc-chart-legende {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kc-space-1) var(--kc-space-4);
  margin: var(--kc-space-3) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--kc-font-size-xs);
  color: var(--kc-color-text-soft);
}

.kc-chart-barres {
  display: flex;
  flex-direction: column;
  gap: var(--kc-space-3);
}

.kc-chart-barres-ligne {
  display: grid;
  grid-template-columns: 7rem 1fr 2.5rem;
  align-items: center;
  gap: var(--kc-space-3);
}

.kc-chart-barres-libelle {
  font-size: var(--kc-font-size-sm);
  color: var(--kc-color-text-soft);
}

.kc-chart-svg--barre,
.kc-chart-svg--empile,
.kc-chart-svg--frise {
  width: 100%;
  height: 0.85rem;
  border-radius: var(--kc-radius-pill);
  overflow: hidden;
}

.kc-chart-barres-valeur {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: var(--kc-font-size-sm);
  color: var(--kc-color-text);
}

/* La table de données équivalente à un graphique : accessible, mais retirée de l'écran tant
   qu'on n'en a pas besoin — le graphique porte déjà l'information visuellement. */
.kc-chart-donnees {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.kc-chart-donnees:focus-within {
  position: static;
  width: auto;
  height: auto;
  clip: auto;
  margin-top: var(--kc-space-2);
}

/* ====================== Barre d'outils, filtres ====================== */

.kc-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kc-space-3);
  padding: var(--kc-card-pad);
}

.kc-toolbar__recherche {
  display: flex;
  flex: 1 1 14rem;
  gap: var(--kc-space-2);
}

.kc-toolbar__recherche input[type="search"] {
  flex: 1;
  min-width: 0;
  padding: 0.5rem var(--kc-space-3);
  border: 1px solid var(--kc-color-border-interactif);
  border-radius: var(--kc-radius-md);
  font-size: var(--kc-font-size-sm);
  color: var(--kc-color-text);
  background: var(--kc-color-surface);
  transition: border-color var(--kc-dur-rapide) var(--kc-ease-standard),
    box-shadow var(--kc-dur-rapide) var(--kc-ease-standard);
}

.kc-toolbar__recherche input[type="search"]:focus-visible {
  outline: none;
  border-color: var(--kc-color-primary);
  box-shadow: 0 0 0 3px var(--kc-color-primary-soft);
}

.kc-toolbar__filtres {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kc-space-2);
}

.kc-chip {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 0.3rem var(--kc-space-3);
  border-radius: var(--kc-radius-pill);
  border: 1px solid var(--kc-color-border);
  background: var(--kc-color-surface);
  color: var(--kc-color-text-soft);
  font-size: var(--kc-font-size-xs);
  font-weight: var(--kc-weight-medium);
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--kc-dur-rapide) var(--kc-ease-standard),
    color var(--kc-dur-rapide) var(--kc-ease-standard),
    border-color var(--kc-dur-rapide) var(--kc-ease-standard);
}

.kc-chip:hover {
  background: var(--kc-color-neutral-soft);
}

.kc-chip:focus-visible {
  outline: 2px solid var(--kc-color-primary);
  outline-offset: 1px;
}

.kc-chip--actif {
  background: var(--kc-color-primary);
  border-color: var(--kc-color-primary);
  color: #ffffff;
}

.kc-toolbar__compte {
  margin: 0;
  color: var(--kc-color-text-muted);
  font-size: var(--kc-font-size-xs);
  white-space: nowrap;
}

.kc-toolbar__reinit {
  color: var(--kc-color-primary);
  font-size: var(--kc-font-size-xs);
  font-weight: var(--kc-weight-medium);
  text-decoration: none;
  white-space: nowrap;
}

.kc-toolbar__reinit:hover {
  text-decoration: underline;
}

.kc-toolbar__actions {
  display: flex;
  gap: var(--kc-space-2);
  margin-inline-start: auto;
}

@media (max-width: 639.98px) {
  .kc-toolbar {
    flex-direction: column;
    align-items: stretch;
  }

  .kc-toolbar__recherche {
    flex: 0 0 auto;
  }

  .kc-toolbar__actions {
    margin-inline-start: 0;
  }
}

/* ====================== Tableaux ====================== */

.kc-table-defilement {
  overflow-x: auto;
  border-radius: var(--kc-radius-lg);
  border: 1px solid var(--kc-color-border);
  box-shadow: var(--kc-shadow-1);
}

.kc-table-defilement:focus-visible {
  outline: 2px solid var(--kc-color-primary);
  outline-offset: 2px;
}

/* Imbriqués dans une `.kc-card` (le tableau ou la liste compacte EST la carte ailleurs,
 * ex. SoA, Preuves), `.kc-table-defilement` et `.kc-row-compact` ne doivent pas empiler une
 * seconde surface par-dessus la carte englobante (règle 2 du système de surfaces, surface
 * imbriquée = `.kc-card--plat` uniquement) : ils redeviennent plats. */
.kc-card .kc-table-defilement,
.kc-card .kc-row-compact {
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.kc-card .kc-table-defilement:focus-visible {
  outline-offset: -2px;
}

.kc-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--kc-font-size-sm);
  background: var(--kc-color-surface);
}

.kc-table caption {
  text-align: left;
  padding: var(--kc-space-2) var(--kc-space-4);
  color: var(--kc-color-text-soft);
  font-size: var(--kc-font-size-xs);
}

.kc-table th,
.kc-table td {
  padding: var(--kc-space-3) var(--kc-space-4);
  text-align: left;
  border-bottom: 1px solid var(--kc-color-border-soft);
}

.kc-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--kc-color-surface-3);
  font-weight: var(--kc-weight-bold);
  white-space: nowrap;
}

.kc-table tbody tr {
  transition: background var(--kc-dur-rapide) var(--kc-ease-standard);
}

.kc-table tbody tr:nth-child(even) {
  background: var(--kc-color-surface-2);
}

.kc-table tbody tr:hover {
  background: var(--kc-color-primary-soft);
}

.kc-table td.kc-numerique,
.kc-table th.kc-numerique {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.kc-table__date {
  white-space: nowrap;
  color: var(--kc-color-text-muted);
}

/* Une colonne qui ne doit jamais se faire rogner par la largeur disponible (ex. la colonne
   « Actions » d'une table dont une autre colonne est flexible) : largeur minimale garantie,
   jamais de retour à la ligne pour son contenu. */
.kc-table__action {
  white-space: nowrap;
  text-align: right;
}

/* Colonne « Actions » de la table « Versions » d'un produit (`produits.html`, #191/#192) :
   jusqu'à quatre liens (Annexe I, Dossier technique et déclaration, SBOM et composants,
   Déclarer la fin de support) dans la même cellule — `.kc-table__action` ci-dessus impose
   `white-space: nowrap` pensé pour un bouton unique, qui les alignait tous sur une seule
   ligne débordant la colonne à 1440 px (« Déclarer la fin de support » rogné en « Dé… »).
   Même patron que `.ecran-ecarts` (ecrans-corpus.css) : retour à la ligne normal, scopé à
   cet écran, jamais une règle partagée par tous les `.kc-table__action` du dépôt.
   `kc-table-defilement` autour de la table garde son défilement horizontal pour 390 px. */
.ecran-produits .kc-table--versions-produit td[data-label="Actions"].kc-table__action {
  white-space: normal;
  text-align: left;
}

/* Une cellule de texte long (justification, commentaire…) : limitée à deux lignes plutôt que
   d'écraser sa colonne ou de gonfler la hauteur de ligne — le texte complet reste accessible
   ailleurs (le panneau de détail). Pose-la sur un élément interne (`<span>`/`<p>`), jamais
   directement sur un `<td>` : un `<td>` reste « table-internal », le navigateur « blockifie »
   alors `display: -webkit-box` en `flow-root` et `-webkit-line-clamp` n'a plus d'effet. */
.kc-table__clamp {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-width: 28rem;
}

/* 2.4.11 : `overflow: hidden` ci-dessus clippe le contour de focus par défaut
   (`outline-offset: 2px`, hors de la boîte) — observé sur `a.kc-table__clamp`
   (`arbre_clauses.html`, `arbre_documents.html`, `donnees_personnelles.html`), jamais annoncé
   visuellement au clavier. Un `outline-offset` négatif garde le contour DANS la boîte
   peinte, donc dans la zone non clippée (#168). */
a.kc-table__clamp:focus-visible {
  outline-offset: -2px;
}

.kc-code {
  padding: 0.1rem 0.4rem;
  border-radius: var(--kc-radius-sm);
  background: var(--kc-color-surface-3);
  font-size: 0.82em;
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  white-space: nowrap;
}

/* Un code (référence documentaire libre, ex. FOR-SMSI-001) ou une pastille de statut peut
   rester plus large que sa cellule dans une colonne étroite : l'ellipse remplace le
   débordement, le texte complet reste lisible via `title` sur l'élément. Partagée par toutes
   les tables — les trois qui ont des colonnes dimensionnées pour accueillir le badge en
   entier (SoA, Plan, Risques) la remplacent ci-dessous par un repli entre mots. */
.kc-table td .kc-code,
.kc-table td .kc-badge {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

/* Modificateur : sur la SoA, le Plan et les Risques, les colonnes à badge ont été
   dimensionnées (largeurs `.kc-col-soa-*` ci-dessous, Plan et Risques de même) pour que la
   pastille de statut tienne toujours sur une ou deux lignes — son libellé entier est
   l'information (« à décider »/« à valider », « Critique ») et ne doit jamais être ellipsé.
   Repli SEULEMENT entre deux mots (espace déjà présente) — jamais `overflow-wrap: break-word`
   /`anywhere`, qui couperait un mot en son milieu (« reten »/« u »), pire que l'ellipse.
   Les autres tables (Demandes, Gouvernance, Documents, …) gardent l'ellipse ci-dessus : leurs
   colonnes à badge ne sont pas dimensionnées pour un repli, qui y cassait les mots en plein
   milieu (« à confirmer » → « conf/irme/r », constat d'écart 1280/1440 px, Refs #86). */
.kc-table--soa td .kc-badge,
.kc-table--plan td .kc-badge,
.kc-table--risques td .kc-badge,
.kc-table--tableau-cra td .kc-badge {
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
}

/* Modificateur : un identifiant long (référence documentaire composée) ne doit pas forcer sa
   colonne à s'étaler sur toute la largeur de la table — contrairement à un code court
   (« A.8.13 »), il se rompt comme du texte normal. */
.kc-code--rompt {
  white-space: normal;
  overflow-wrap: anywhere;
}

/* Un nom de compte (« demo-consultant ») peut dépasser la largeur raisonnable d'une colonne
   « Auteur »/« Approuvé par » et s'y couper sur deux lignes au lieu d'une seule — constat du
   coordinateur sur la fiche d'un script personnalisé (US-16.6). Même patron que
   `.kc-table td .kc-code` ci-dessus : ellipse, texte complet lisible via `title`. */
.kc-table__nom-compte {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* Largeurs de colonnes de la table des versions d'un script personnalisé (US-16.6,
   script_collecte_detail.html) : « Auteur » resserré pour laisser la place à « Statut » et
   « Hôtes de sortie », avec `.kc-table__nom-compte` ci-dessus qui clampe le débordement. */
.kc-col-scripts-version { width: 28%; }
.kc-col-scripts-statut { width: 14%; }
.kc-col-auteur { width: 12%; }
.kc-col-scripts-hotes { width: 16%; }
.kc-col-scripts-execution { width: 16%; }
.kc-col-scripts-actions { width: 8%; }

/* Bloc de code multi-ligne en lecture seule (ex. code source entier d'un script personnalisé,
   schéma JSON — `#kc-aside`, US-16.6) : `.kc-code` reste réservé à un identifiant court en
   ligne (`white-space: nowrap`), ce composant couvre le cas complémentaire. */
.kc-code-bloc {
  margin: 0;
  padding: var(--kc-space-3);
  border-radius: var(--kc-radius-md);
  background: var(--kc-color-surface-3);
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 0.82em;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 24rem;
  overflow-y: auto;
}

/* Bloc de code multi-ligne SANS surface propre (#65, US-6.9, documentation du contrat de
 * données) : `.kc-code-bloc` ci-dessus n'a jamais été utilisé ailleurs qu'à l'intérieur d'un
 * `#kc-aside` (hors du périmètre mesuré par `controle-coherence.py`, règle 7) — son fond et
 * son rayon >= 8px en font une « surface » qui doit alors correspondre à l'une des références
 * de la page (carte, page-header, dialogue, alerte), ce que son propre ton ne fait jamais.
 * `.kc-pre-texte` couvre le même besoin (texte préformaté long, enroulé) À L'INTÉRIEUR d'une
 * `.kc-card` existante : aucun fond ni rayon propres, donc jamais détecté comme une surface
 * imbriquée.
 */
.kc-pre-texte {
  margin: 0;
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 0.82em;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ====================== Tables à colonnes fixes — en-têtes trop longs ======================
 * `.kc-table thead th` (ci-dessus) impose `white-space: nowrap` à tous les en-têtes —
 * adapté à l'algorithme `auto`, qui élargit une colonne pour loger son en-tête, mais pas à
 * `table-layout: fixed` : la largeur de colonne y est figée par le `<colgroup>`, donc un
 * en-tête qui ne tient pas sur une ligne déborde sur son voisin plutôt que de l'élargir —
 * invisible à l'œil (le fond opaque du `<th>` suivant repeint la fin du texte, lu comme une
 * troncature) mais mesurable (`scrollWidth > clientWidth`) à 1280 px, mesuré sur quatre
 * tables : « Exigence ou risque lié » → « lié » recouvert sur Plan, « Identifiant » →
 * « Identifian » sur Risques, « Dernier effacement » sur le registre de données
 * personnelles (ancienne redite scopée de `ecrans-donnees-personnelles.css`, remplacée ici),
 * et « Couverture documentaire » → « Date » recouvert sur la SoA (contre-revue #94, F94,
 * captures aux 3 largeurs : les colonnes `.kc-col-soa-*` ci-dessous ont été redimensionnées
 * depuis que ce bloc a été mesuré pour la dernière fois, et « Couverture documentaire » n'y
 * tient plus à 1280/1440 px).
 * Les autres tables à colonnes fixes (Priorités, Échéances, Écarts, Documents,
 * Approbation) ont des en-têtes qui tiennent déjà sur une ligne à ces largeurs — mesuré, pas
 * de règle ajoutée pour elles : un `white-space: normal` non nécessaire y ferait passer un
 * en-tête court sur deux lignes pour rien.
 * SEULEMENT `white-space: normal`, JAMAIS `overflow-wrap: anywhere`/`break-word` : ce dernier
 * coupe un mot en son milieu (« Identif »/« iant ») dès qu'il manque un pixel, pire que le
 * recouvrement qu'il remplace. « Exigence ou risque lié » (4 mots) et « Couverture
 * documentaire » (2 mots) se replient entre deux mots sans aide. Un en-tête d'un seul mot
 * sans espace (« Identifiant », « Dernier effacement ») n'a lui aucun point de coupure
 * normal : sa colonne est dimensionnée (ci-dessous et dans
 * `ecrans-donnees-personnelles.css`) pour qu'il tienne toujours sur une seule ligne, mesuré
 * largeur naturelle du mot <= largeur de la colonne. La ligne d'en-tête, collante
 * (`position: sticky`), peut grandir sans rien bousculer en dessous si un mot long passe
 * malgré tout sur deux lignes. */
.kc-table--plan thead th,
.kc-table--risques thead th,
.kc-table--soa thead th,
.kc-table--consolide thead th,
.kc-table--donnees-personnelles thead th,
.kc-table--tableau-cra thead th {
  white-space: normal;
}

/* Table « Produits » du tableau de bord CRA (#192, `tableau_cra.html`) : même patron que
   `.kc-table--consolide` ci-dessus — sans `table-layout: fixed` et un `<colgroup>` en
   pourcentages, `.ecran` (ecrans-corpus.css, `overflow-wrap: anywhere` sur toute la page)
   laissait l'algorithme `auto` écraser les colonnes Dossier/Produit à une largeur inférieure
   à leur plus long mot ("Passerelle", "Connectée") pour loger les en-têtes longs des
   colonnes voisines — mesuré 1280/1440 px, chaque mot se repliait alors en plein milieu. */
.kc-table--tableau-cra {
  table-layout: fixed;
}

.kc-col-cra-dossier { width: 16%; }
.kc-col-cra-produit { width: 16%; }
.kc-col-cra-classe { width: 20%; }
.kc-col-cra-exigences { width: 18%; }
.kc-col-cra-vulnerabilites { width: 16%; }
.kc-col-cra-fin-support { width: 14%; }

/* `.ecran` (ecrans-corpus.css) pose `overflow-wrap: anywhere` sur toute la page, hérité par
   ces cellules : même avec `table-layout: fixed` et le `<colgroup>` ci-dessus, 1280 px laisse
   une colonne trop proche de la largeur de son mot le plus long ("Connectée"), que l'algorithme
   accepte alors de rompre en son milieu faute de plancher — mesuré, Refs #109/#87 déjà
   rencontré ailleurs (ecrans-administration.css, kc-admin.css « Tables à colonnes fixes »).
   `overflow-wrap: normal` restaure le repli uniquement entre mots, jamais en plein mot. */
.kc-table--tableau-cra td {
  overflow-wrap: normal;
}

/* Écart h2.kc-section-titre -> première `.kc-card` de la grille des échéances du règlement
   (#192, `tableau_cra.html`) : `.kc-section` espace ses enfants directs de
   `--kc-section-gap` (16px) via son `gap` flex, mais la carte mesurée n'est pas cet enfant
   direct — c'est `.kc-grid > .kc-card`, un niveau plus profond — donc le contrôle de
   cohérence retombe sur le plancher `--kc-grid-gap` (22-32px responsive) plutôt que sur le
   gap du parent commun. `margin-top` additionnel pour atteindre ce plancher, scopé à cet
   écran : jamais une règle partagée par tous les `.kc-section-titre + .kc-grid` du dépôt. */
.ecran-tableau-cra .kc-section-titre + .kc-grid {
  margin-top: var(--kc-grid-gap);
}

/* Répartition des colonnes de la table de décision SoA (`soa.html`) : `table-layout: fixed`
   plus un `<colgroup>` en pourcentages, plutôt que l'algorithme `auto` par défaut — mesuré
   sans lui, l'intitulé héritait une colonne trop étroite (toute la largeur libérée par le
   retrait de la colonne Thème partait ailleurs), repassant sur 3 lignes à lui seul. Classes
   posées sur des `<col>`, jamais sur les `<td>` : aucun style en ligne, conforme à la CSP.
   Scopé à `.kc-table--soa` seul — les autres tables gardent l'algorithme `auto`. */
.kc-table--soa {
  table-layout: fixed;
}

.kc-col-soa-select { width: 2.75rem; }
.kc-col-soa-intitule { width: 38%; }
.kc-col-soa-decision { width: 13%; }
/* Décision élargie (10 → 13 %, #218) : une mesure « non démontrée » (Décision + État de
   mise en œuvre empilent alors deux pastilles de deux mots chacune dans la même colonne)
   dépassait sa colonne à 1280 px — latent avec le jeu de démo par défaut, qui ne porte
   aucune décision de ce type. Reprise sur Justification (17 → 14 %), qui clampe déjà son
   texte à 2 lignes (`.kc-table__clamp`). */
.kc-col-soa-justification { width: 14%; }
/* Couverture élargie (11 → 13 %, contre-revue #94, F94) : « Couverture documentaire »
   (2 mots, replie normalement depuis la règle ci-dessus) débordait encore à 1280 px sur
   « documentaire » seul — repris sur Justification (19 → 17 %), qui clampe déjà son texte
   à 2 lignes (`.kc-table__clamp`). */
.kc-col-soa-couverture { width: 13%; }
/* Date élargie (8 → 11 %, ticket troncatures 1280 px) : « jj.mm.aaaa » (10 caractères, aucun
   point de coupure) débordait sur le bouton Décider — repris sur Justification (22 → 19 %),
   qui clampe déjà son texte à 2 lignes (`.kc-table__clamp`). */
.kc-col-soa-date { width: 11%; }
.kc-col-soa-actions { width: 7.5rem; }

/* Répartition des colonnes de la table « Vue consolidée » (#183, `organisme_client_consolide.html`) :
   même patron que `.kc-table--soa` ci-dessus — `table-layout: fixed` plus un `<colgroup>` en
   pourcentages, mesuré sans lui le nom du dossier et le libellé du référentiel se coupaient en
   plein mot à 1280 px (l'algorithme `auto` compressait ces deux colonnes sous leur largeur de
   contenu pour loger les quatre colonnes de compte). */
.kc-table--consolide {
  table-layout: fixed;
}

.kc-col-consolide-dossier { width: 20%; }
.kc-col-consolide-referentiel { width: 18%; }
.kc-col-consolide-avancement { width: 14%; }
.kc-col-consolide-compte { width: 12%; }

/* Par défaut (bureau/tablette), la table ordinaire s'affiche et la liste compacte reste
   masquée : les deux rendus existent côte à côte dans le même gabarit, c'est la largeur
   d'écran seule qui choisit, sans script. */
.kc-rows-compact {
  display: none;
}

@media (max-width: 639.98px) {
  /* Repli hérité : empilement `<td data-label>`, une ligne de texte par colonne — encore le
     seul rendu mobile des écrans qui n'ont pas reçu de carte compacte (`.kc-rows-compact`).
     Un écran qui fournit les deux rendus (voir ci-dessous) bascule entièrement sur la carte
     via `:has()` : les deux techniques ne s'affichent jamais ensemble. */
  .kc-table thead,
  .kc-table thead tr,
  .kc-table thead th {
    display: block;
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    padding: 0;
    border: 0;
  }

  .kc-table,
  .kc-table tbody,
  .kc-table tr,
  .kc-table td {
    display: block;
    width: 100%;
  }

  .kc-table tr {
    margin-bottom: var(--kc-space-3);
    border: 1px solid var(--kc-color-border);
    border-radius: var(--kc-radius-md);
    overflow: hidden;
  }

  .kc-table td {
    display: flex;
    justify-content: space-between;
    gap: var(--kc-space-3);
    text-align: right;
    border-bottom: 1px solid var(--kc-color-border);
  }

  .kc-table td:last-child {
    border-bottom: 0;
  }

  .kc-table td::before {
    content: attr(data-label);
    display: block;
    margin-bottom: var(--kc-space-1);
    font-weight: 700;
    text-align: left;
    color: var(--kc-color-text-soft);
  }

  /* Une cellule qui porte plusieurs champs (ex. état + détenteur + note d'une déclaration)
     ne peut pas suivre la même rangée flexible qu'une valeur unique : les trois champs s'y
     écrasent côte à côte, chacun illisible. Elle repasse en colonne, étiquette au-dessus. */
  .kc-table td.kc-table__champs {
    display: block;
    text-align: left;
  }

  .kc-table td.kc-table__champs > :not(.kc-visually-hidden) {
    width: 100%;
    margin-top: var(--kc-space-1);
  }

  /* Carte compacte : un écran qui fournit `.kc-rows-compact` juste après sa table remplace
     entièrement l'empilement ci-dessus par cette carte dense — SoA est le premier (voir
     `soa.html`). 2–3 lignes de texte, le statut en pastille dans l'en-tête ; aucun
     `<details>` replié pour les champs qui ouvrent une action : ils ouvrent le panneau
     `#kc-aside`, comme sur bureau — un geste, pas deux. */
  .kc-table-defilement:has(+ .kc-rows-compact) {
    display: none;
  }

  .kc-rows-compact {
    display: flex;
    flex-direction: column;
    gap: var(--kc-space-3);
  }

  .kc-row-compact {
    overflow: hidden;
    padding: var(--kc-card-pad);
  }

  a.kc-row-compact {
    display: block;
    color: inherit;
    text-decoration: none;
  }

  a.kc-row-compact:focus-visible {
    outline: 2px solid var(--kc-color-primary);
    outline-offset: -2px;
  }

  .kc-row-compact__titre {
    margin: 0;
    font-size: var(--kc-font-size-sm);
    font-weight: var(--kc-weight-medium);
    color: var(--kc-color-text);
  }

  .kc-row-compact__meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--kc-space-2);
    margin: 0.2rem 0 0;
    font-size: var(--kc-font-size-xs);
    color: var(--kc-color-text-muted);
  }

  .kc-row-compact__justification {
    margin: 0.2rem 0 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--kc-font-size-xs);
    color: var(--kc-color-text-muted);
  }

  /* Action visuelle en fin de ligne compacte (ex. « Signer… ») : la ligne entière
   * (`a.kc-row-compact`) porte déjà le lien, ce pastille n'est qu'un rappel visuel de
   * l'équivalent bureau (bouton dans la colonne Actions de `.kc-table`) — jamais un second
   * élément interactif imbriqué dans l'ancre. */
  .kc-row-compact__action {
    display: inline-flex;
    margin-top: var(--kc-space-2);
  }

  /* Repli facultatif, pour un écran sans action associée à la ligne (rien à ouvrir dans
     `#kc-aside`) : un `<details>` natif reste une option valide — seule la ligne qui *ouvre
     une action* ne doit plus en dépendre (voir ci-dessus). */
  .kc-row-compact__details > summary {
    list-style: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--kc-space-2);
    padding: var(--kc-space-2) var(--kc-space-4);
    border-top: 1px solid var(--kc-color-border-soft);
    background: var(--kc-color-surface-3);
    font-size: var(--kc-font-size-xs);
    color: var(--kc-color-primary);
    cursor: pointer;
  }

  .kc-row-compact__details > summary::-webkit-details-marker {
    display: none;
  }

  .kc-row-compact__details > summary::after {
    content: "";
    width: 8px;
    height: 8px;
    border-right: 1.6px solid currentColor;
    border-bottom: 1.6px solid currentColor;
    transform: rotate(45deg);
    transition: transform var(--kc-dur-rapide) var(--kc-ease-standard);
  }

  .kc-row-compact__details[open] > summary::after {
    transform: rotate(-135deg);
  }

  .kc-row-compact__details > .kc-row-compact__corps {
    animation: kc-entree-douce var(--kc-dur-rapide) var(--kc-ease-standard) both;
  }

  /* En-tête de page et indicateurs : même information, moins de respiration verticale —
     c'est la densité d'une liste de 25 lignes qui doit rester lisible sans défiler sur des
     mètres, pas le confort d'un écran qui n'affiche qu'une seule carte. */
  .kc-page-header {
    padding: var(--kc-space-4) var(--kc-space-5);
  }

  .kc-page-header > div {
    min-width: 0;
  }

  .kc-page-header-eyebrow {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .kc-page-header h1 {
    font-size: var(--kc-font-size-xl);
  }

  .kc-page-header-sous-titre a {
    font-size: var(--kc-font-size-xs);
  }

  .kc-grid--kpi {
    gap: var(--kc-space-3);
  }

  .kc-grid--kpi .kc-card__body {
    padding: var(--kc-space-3);
  }

  .kc-grid--kpi .kc-kpi__value {
    font-size: var(--kc-font-size-xl);
  }
}

/* ====================== Listes ====================== */

.kc-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.kc-list > li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--kc-space-3);
  padding: var(--kc-space-3) 0;
  border-bottom: 1px solid var(--kc-color-border-soft);
}

.kc-list > li:last-child {
  border-bottom: 0;
}

.kc-list a {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--kc-color-text);
  text-decoration: none;
  font-weight: var(--kc-weight-medium);
}

.kc-list a:hover {
  color: var(--kc-color-primary);
}

.kc-list-meta {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--kc-space-2);
  font-size: var(--kc-font-size-xs);
  color: var(--kc-color-text-muted);
  white-space: nowrap;
}

@media (max-width: 639.98px) {
  .kc-list > li {
    flex-wrap: wrap;
  }

  .kc-list-meta {
    flex: 1 1 100%;
    white-space: normal;
  }
}

/* Sous 480 px, un lien long et deux pastilles de statut ne tiennent plus sur une seule ligne
   sans se tronquer : la ligne repasse en colonne, les pastilles sous le libellé. */
@media (max-width: 479.98px) {
  .kc-list > li {
    flex-flow: column nowrap;
    align-items: flex-start;
    gap: var(--kc-space-1);
  }

  .kc-list > li > a {
    align-self: stretch;
  }
}

/* ====================== Pagination ====================== */

.kc-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--kc-space-3);
}

.kc-pagination__rang {
  margin: 0;
  color: var(--kc-color-text-muted);
  font-size: var(--kc-font-size-xs);
}

.kc-pagination__liens {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kc-space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.kc-pagination__lien,
.kc-pagination__courante {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
  height: 2rem;
  border-radius: var(--kc-radius-sm);
  font-size: var(--kc-font-size-sm);
  font-weight: var(--kc-weight-medium);
  text-decoration: none;
}

.kc-pagination__lien {
  color: var(--kc-color-text);
  transition: background var(--kc-dur-rapide) var(--kc-ease-standard);
}

.kc-pagination__lien:hover {
  background: var(--kc-color-neutral-soft);
}

.kc-pagination__lien:focus-visible {
  outline: 2px solid var(--kc-color-primary);
  outline-offset: 2px;
}

.kc-pagination__courante {
  background: var(--kc-color-primary);
  color: #ffffff;
  font-weight: var(--kc-weight-bold);
}

/* ====================== Panneau latéral (`dialog.kc-panel`) ======================
 *
 * Le patron hérité : le serveur ouvre la boîte par l'attribut `open`, `kc-admin.js` la fait
 * passer en vraie modale (voile, piège de focus, fermeture par Échap). Encore utilisé par
 * les écrans qui n'ont pas de panneau `#kc-aside` dédié (voir `.kc-aside` ci-dessous pour le
 * nouveau patron, utilisé par la SoA). Les deux coexistent : rien n'oblige à migrer un
 * panneau existant tant que son écran n'a pas son propre lot.
 */
.kc-panel {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  inset-inline-start: auto;
  z-index: 30;
  margin: 0;
  width: min(30rem, 100vw);
  max-width: 100vw;
  height: 100vh;
  max-height: 100vh;
  border: none;
  border-inline-start: 1px solid var(--kc-color-border);
  border-radius: 0;
  padding: 0;
  background: var(--kc-color-surface);
  box-shadow: var(--kc-shadow-3);
  animation: kc-entree-lateral var(--kc-dur-lente) var(--kc-ease-sortie) both;
}

.kc-panel::backdrop {
  background: rgba(6, 20, 15, 0.35);
  animation: kc-voile-entree var(--kc-dur-normale) var(--kc-ease-standard) both;
}

.kc-panel__corps {
  height: 100%;
  overflow-y: auto;
  padding: var(--kc-space-5);
  display: flex;
  flex-direction: column;
  gap: var(--kc-space-4);
}

.kc-panel__tete {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--kc-space-3);
}

.kc-panel__tete h2 {
  margin: 0;
  font-size: var(--kc-font-size-lg);
}

.kc-panel__fermer {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  inline-size: 2.25rem;
  min-block-size: 2.25rem;
  margin: calc(var(--kc-space-2) * -1) calc(var(--kc-space-2) * -1) 0 0;
  border-radius: var(--kc-radius-sm);
  color: var(--kc-color-text-soft);
  font-size: 1.25rem;
  line-height: 1;
  text-decoration: none;
  transition: background var(--kc-dur-rapide) var(--kc-ease-standard);
}

.kc-panel__fermer:hover,
.kc-panel__fermer:focus-visible {
  background: var(--kc-color-neutral-soft);
  color: var(--kc-color-text);
}

@media (max-width: 639.98px) {
  .kc-panel {
    width: 100vw;
  }
}

/* ====================== Panneau de détail — `<aside id="kc-aside">` ======================
 *
 * Exigence de Bruno : les détails s'affichent dans un panneau qui glisse depuis la droite,
 * jamais en dépliant la ligne ni en changeant de page. Contrat complet, exemple de bout en
 * bout : `docs/ui-composants.md`, section « Panneau de détail ». Un seul `<aside
 * id="kc-aside">` existe par page (posé par `missions/base.html`) ; chaque écran qui l'utilise
 * ne fournit que son contenu, par un gabarit inclus à la fois en page complète (repli sans
 * JavaScript) et comme fragment HTMX-compatible (`missions.aside.reponse_aside_ou_page`).
 *
 * État ouvert porté par une classe, jamais par une propriété calculée en ligne : le serveur
 * la pose dès le rendu (repli sans script, page complète) ; `kc-admin.js` la pose/l'enlève à
 * l'ouverture/fermeture enrichie (fragment chargé en place). Le voile réagit par sélecteur de
 * frère général (`~`), sans code JS dédié à son affichage.
 */
.kc-aside {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  inset-inline-start: auto;
  z-index: 30;
  width: min(30rem, 100vw);
  max-width: 100vw;
  height: 100vh;
  max-height: 100vh;
  overflow-y: auto;
  background: var(--kc-color-surface);
  border-inline-start: 1px solid var(--kc-color-border);
  box-shadow: var(--kc-shadow-3);
  transform: translateX(100%);
  transition: transform var(--kc-dur-normale) var(--kc-ease-sortie);
}

.kc-aside--ouvert {
  transform: translateX(0);
}

.kc-aside:empty {
  border: 0;
  box-shadow: none;
}

.kc-aside-voile {
  position: fixed;
  inset: 0;
  z-index: 29;
  background: rgba(6, 20, 15, 0.35);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--kc-dur-normale) var(--kc-ease-standard);
}

.kc-aside.kc-aside--ouvert ~ .kc-aside-voile {
  opacity: 1;
  pointer-events: auto;
}

.kc-aside__corps {
  display: flex;
  flex-direction: column;
  gap: var(--kc-space-4);
  padding: var(--kc-space-5);
}

.kc-aside__tete {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--kc-space-3);
}

.kc-aside__tete h2 {
  margin: 0;
  font-size: var(--kc-font-size-lg);
}

.kc-aside__meta {
  margin: 0;
  font-size: var(--kc-font-size-xs);
  color: var(--kc-color-text-muted);
}

.kc-aside__section {
  padding-block-start: var(--kc-space-3);
  border-top: 1px solid var(--kc-color-border-soft);
}

.kc-aside__section-titre {
  margin: 0 0 var(--kc-space-2);
  font-size: var(--kc-font-size-sm);
  font-weight: var(--kc-weight-bold);
  color: var(--kc-color-text-soft);
}

.kc-aside__pied {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kc-space-2);
  padding-block-start: var(--kc-space-3);
  border-top: 1px solid var(--kc-color-border-soft);
}

.kc-aside__fermer {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  inline-size: 2.25rem;
  min-block-size: 2.25rem;
  margin: calc(var(--kc-space-2) * -1) calc(var(--kc-space-2) * -1) 0 0;
  border: 0;
  border-radius: var(--kc-radius-sm);
  background: transparent;
  color: var(--kc-color-text-soft);
  font-size: 1.25rem;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--kc-dur-rapide) var(--kc-ease-standard);
}

.kc-aside__fermer:hover,
.kc-aside__fermer:focus-visible {
  background: var(--kc-color-neutral-soft);
  color: var(--kc-color-text);
}

@media (max-width: 639.98px) {
  .kc-aside {
    width: 100vw;
  }
}

/* ====================== Formulaire de panneau (`kc-panel`/`kc-aside`) ====================== */

.kc-panel__formulaire,
.kc-aside__formulaire {
  display: flex;
  flex-direction: column;
  gap: var(--kc-space-2);
  padding-block-start: var(--kc-space-3);
  border-top: 1px solid var(--kc-color-border-soft);
}

/* 2.5.8 : une étiquette de champ ordinaire n'est jamais une cible interactive (elle n'entoure
   pas un `<input>`) — SAUF ici (`_soa_aside_decider.html`, listes de risques/actions à
   cocher : `<label><input type="checkbox">… texte</label>`), où elle l'est, à ~21,5 px de
   haut. `padding-block-start` ajouté est repris sur `margin-block-start` (8 → 4 px) : la
   position visuelle du contenu ne bouge pas (padding + marge réduite = même décalage total
   depuis l'élément précédent), seule la boîte mesurée grandit — `margin-block-end` reste à 0
   pour ne pas changer l'espacement avec l'étiquette suivante (#168). */
.kc-panel__formulaire label,
.kc-aside__formulaire label {
  display: block;
  margin-block-start: calc(var(--kc-space-2) - 4px);
  padding-block-start: 4px;
  font-size: var(--kc-font-size-sm);
  font-weight: var(--kc-weight-medium);
  color: var(--kc-color-text);
}

.kc-panel__formulaire select,
.kc-panel__formulaire textarea,
.kc-panel__formulaire input:not([type="hidden"]):not([type="checkbox"]),
.kc-aside__formulaire select,
.kc-aside__formulaire textarea,
.kc-aside__formulaire input:not([type="hidden"]):not([type="checkbox"]) {
  width: 100%;
  padding: var(--kc-space-2) var(--kc-space-3);
  border: 1px solid var(--kc-color-border-interactif);
  border-radius: var(--kc-radius-md);
  font: var(--kc-font-size-sm) var(--kc-font);
  color: var(--kc-color-text);
  background: var(--kc-color-surface);
  transition: border-color var(--kc-dur-rapide) var(--kc-ease-standard),
    box-shadow var(--kc-dur-rapide) var(--kc-ease-standard);
}

.kc-panel__formulaire select:focus-visible,
.kc-panel__formulaire textarea:focus-visible,
.kc-panel__formulaire input:focus-visible,
.kc-aside__formulaire select:focus-visible,
.kc-aside__formulaire textarea:focus-visible,
.kc-aside__formulaire input:focus-visible {
  outline: none;
  border-color: var(--kc-color-primary);
  box-shadow: 0 0 0 3px var(--kc-color-primary-soft);
}

/* 1.4.11 : `<p class="champ">` (de nombreux gabarits, ex. `tests_controle.html`) enveloppe un
   champ de formulaire SANS le contexte `.kc-panel__formulaire`/`.kc-aside__formulaire` ci-
   dessus ni `.ecran` (ecrans-corpus.css) — il retombe sur la bordure non interactive
   générique (`kuma.css`/material.css, 2,4:1). `body.theme-md` est nécessaire : sans lui,
   cette règle perd en spécificité contre `body.theme-md input[type=...]`/`select`/`textarea`
   (material.css) et la bordure reste visuellement inchangée (#168). Seule la couleur de bord
   est reprise, jamais `padding`/`width` : zéro changement de densité sur ces écrans. */
body.theme-md .champ input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
body.theme-md .champ select,
body.theme-md .champ textarea {
  border-color: var(--kc-color-border-interactif);
}

/* ====================== Sélection et action groupée ====================== */

/* Toujours visible — jamais un panneau qui apparaît de nulle part — mais discrète tant
   qu'aucune ligne n'est cochée : `:has()` l'accentue sans script dès la première case
   cochée, comme le tiroir de la barre latérale le fait déjà dans ce fichier. Carte normale
   (règle 2 du système de surfaces) : plus de pointillé ni de fond différent, compacte, sur
   une seule ligne (champ + bouton) à partir de 768 px — en dessous, empilée proprement
   (`.kc-card` pose déjà `flex-direction: column`, repris explicitement ici par clarté). */
.kc-table-selection__barre {
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  gap: var(--kc-space-3);
  padding: var(--kc-card-pad);
  opacity: 0.72;
  transition: opacity var(--kc-dur-rapide) var(--kc-ease-standard);
}

@media (min-width: 768px) {
  .kc-table-selection__barre {
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
  }
}

/* Pas de case à cocher dans la carte compacte (`.kc-rows-compact`) : sélectionner plusieurs
   lignes n'est possible que sur le rendu bureau de la table. La barre d'action groupée n'a
   donc aucun rôle sous 640 px — l'afficher ajouterait un champ et un bouton sans case à
   cocher pour les remplir. */
@media (max-width: 639.98px) {
  .kc-table-selection__barre {
    display: none;
  }
}

.kc-table-selection:has(input[name="controles"]:checked) .kc-table-selection__barre {
  opacity: 1;
}

/* 2.5.8 : case à cocher native nue (13x13 px) dans une table dense (SoA, 93 lignes) — un
   `padding` sur l'input lui-même n'a aucun effet (élément remplacé). La case est enveloppée
   dans un `<label>`, dont la zone cliquable est agrandie par un `::after` positionné en
   absolu (pas par `padding`, qui gonflerait la taille INTRINSÈQUE du `<label>` : constaté sur
   `table-layout: fixed`, où un padding dessus faisait déborder la colonne « Sélection » —
   ticket #168, régression du premier essai) — zéro changement visuel, zéro changement de
   largeur de colonne. `outils_qualite/audit_accessibilite.py` sait mesurer cette zone (`::after`). */
.kc-table-selection__case {
  position: relative;
  display: inline-flex;
}

/* La marge par défaut de l'agent utilisateur autour d'une case à cocher (~3-4px de chaque
   côté) s'ajoutait à la largeur réclamée par ce `<label>` flex — assez pour faire déborder
   la colonne « Sélection » (`table-layout: fixed`, ~44px utiles) de quelques pixels, qu'il
   y ait un `<label>` ou non (#168). */
.kc-table-selection__case input {
  margin: 0;
}

.kc-table-selection__case::after {
  content: "";
  position: absolute;
  inset: calc(var(--kc-space-3) * -1) calc(var(--kc-space-4) * -1);
}

/* `body.theme-md textarea`/`select` (material.css) ont pour sélecteur deux éléments + une
   classe — plus spécifique qu'une seule classe + un élément ici : sans le préfixe
   `body.theme-md`, cette règle perdait silencieusement contre material.css et le bord restait
   `--md-sys-color-outline` (2,4:1, 1.4.11) malgré cette déclaration (#168). Le `<select>`
   voisin (`#id_mise_en_oeuvre_selection`) a le même bord non spécifique — mais pas sa taille
   propre : seule la couleur de bord est reprise ici, sans toucher au `padding`/`width`
   hérités de la règle générique (`kuma.css`), pour ne rien changer d'autre à sa densité. */
body.theme-md .kc-table-selection__barre textarea {
  flex: 1 1 18rem;
  min-height: 40px;
  max-height: 40px;
  padding: var(--kc-space-2) var(--kc-space-3);
  border: 1px solid var(--kc-color-border-interactif);
  border-radius: var(--kc-radius-md);
  font: var(--kc-font-size-sm) var(--kc-font);
  color: var(--kc-color-text);
  resize: vertical;
}

body.theme-md .kc-table-selection__barre select {
  border-color: var(--kc-color-border-interactif);
}

.kc-resultats-groupe {
  margin-block-end: var(--kc-space-4);
}

.kc-resultats-groupe .kc-list code {
  margin-inline-end: var(--kc-space-2);
}

/* ====================== Page publique (porte d'entrée) ====================== */

/* `base_public.html` : connexion, enrôlement/vérification du second facteur, acceptation
   d'invitation — aucune session complète, donc pas de coquille applicative (barre latérale,
   fil d'Ariane, menu de compte). Colonne centrée : logo sobre en tête, carte au centre ;
   même fond de page et mêmes jetons que le reste de la coquille. */
body.theme-md .kc-public-page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background: var(--kc-color-bg);
}

/* `.kc-marque` est pensé pour la sidebar sombre (texte clair) ; posé sur le fond clair de
   l'en-tête public, ce même texte devient quasi invisible (contraste mesuré < 2:1). Encre
   sombre, scopée à ce seul contexte — la sidebar garde son `.kc-marque` clair. */
body.theme-md .kc-public-tete .kc-marque {
  color: var(--kc-color-text);
}

body.theme-md .kc-public-tete {
  display: flex;
  justify-content: center;
  padding: var(--kc-space-5) var(--kc-marge-fluide);
}

/* Les portes qui affichent déjà la marque dans leur panneau (connexion, second facteur)
   masquent ce logo d'en-tête redondant — et gagnent au passage l'espace vide qu'il laissait
   au-dessus de la carte sous 640 px. Les autres pages publiques (ex. acceptation
   d'invitation) gardent l'en-tête, lisible par la règle ci-dessus. */
body.theme-md.kc-public--porte .kc-public-tete {
  display: none;
}

body.theme-md .kc-public-main {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--kc-space-4) var(--kc-marge-fluide);
}

@media (max-width: 639.98px) {
  body.theme-md .kc-public-main {
    padding-block: var(--kc-space-3);
  }
}

/* ====================== Utilitaires de mise en page ====================== */

.kc-card__body--centre {
  display: flex;
  justify-content: center;
}

/* Deux colonnes inégales (2fr/1fr), ex. « À faire maintenant » / « Activité récente » —
   repasse en une colonne sous 768 px plutôt que d'écraser la plus étroite. */
.kc-grid--todo {
  grid-template-columns: 2fr 1fr;
}

@media (max-width: 767.98px) {
  .kc-grid--todo {
    grid-template-columns: 1fr;
  }
}

/* Mise en page à colonne latérale (règle 4 du système de surfaces) : contenu principal
   (2fr) + colonne latérale (1fr, 18rem mini) démarrant EXACTEMENT au même haut
   (`align-items: start`, aucune marge de tête sur l'une ou l'autre colonne) — repasse en
   une seule colonne, latérale après le principal, en dessous du palier choisi par l'écran
   (voir sa propre media query, le seuil varie d'un gabarit à l'autre). */
.kc-layout-aside {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(18rem, 1fr);
  align-items: start;
  gap: var(--kc-grid-gap);
}

/* La colonne latérale est elle-même une pile, au même jeton d'écart que la grille qui la
   contient — jamais une marge posée par l'un de ses blocs. */
.kc-layout-aside > :last-child {
  display: flex;
  flex-direction: column;
  gap: var(--kc-grid-gap);
}

/* ====================== Frise de versions (panneau Preuves) ====================== */

.preuves-frise {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--kc-space-3);
  border-inline-start: 2px solid var(--kc-color-border);
}

.preuves-frise > li {
  padding: 0 0 var(--kc-space-3) var(--kc-space-4);
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  font-size: var(--kc-font-size-sm);
}

.preuves-frise-courante {
  border-inline-start: 2px solid var(--kc-color-primary);
  margin-inline-start: -2px;
}

.preuves-frise .meta {
  font-size: var(--kc-font-size-xs);
  color: var(--kc-color-text-muted);
}

.kc-texte-attenue {
  color: var(--kc-color-text-soft);
  max-width: none;
}

/* ====================== Comparaison de versions (US-9.2) ======================
   `.kc-diff` affiche un diff ligne à ligne, calculé par `missions.comparaison` (difflib).
   Accessibilité : chaque ligne ajoutée/supprimée porte un symbole (+/−) en plus de la
   couleur — jamais la couleur seule (CLAUDE.md, règle générale d'accessibilité). Une ligne
   « égale » garde un symbole espace de même largeur pour que le texte reste aligné, sans
   dépendre de la couleur pour distinguer les trois statuts. */
.kc-diff {
  list-style: none;
  margin: 0;
  padding: var(--kc-space-2) 0;
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: var(--kc-font-size-sm);
  border: 1px solid var(--kc-color-border);
  border-radius: var(--kc-radius-md);
  overflow-x: auto;
}

.kc-diff__ligne {
  display: flex;
  gap: var(--kc-space-2);
  padding: 0.1rem var(--kc-space-3);
  white-space: pre-wrap;
  word-break: break-word;
}

.kc-diff__symbole {
  flex: none;
  width: 1.1em;
  text-align: center;
  font-weight: 700;
}

.kc-diff__ligne--ajout {
  background: var(--kc-color-success-soft);
  color: var(--kc-color-success);
}

.kc-diff__ligne--suppression {
  background: var(--kc-color-danger-soft);
  color: var(--kc-color-danger);
  text-decoration: line-through;
}

.kc-diff__ligne--egal {
  color: var(--kc-color-text-soft);
}
