/* Kincy design tokens + Bootstrap overrides.
 *
 * Source de vérité : design-system/design.md (submodule git).
 * Convention : toute valeur de couleur, font, spacing ou radius doit
 * référencer ces tokens — pas de valeurs magiques dans les templates.
 */

:root {
  /* Primary palette */
  --kincy-cyan: #00ADCE;
  --kincy-blue: #0077BD;
  --kincy-slate: #44546A;

  /* Secondary accents */
  --kincy-magenta: #E7167D;
  --kincy-purple: #6F2282;

  /* Tertiary functional */
  --kincy-green: #6BB52D;
  --kincy-orange: #F08300;

  /* Étoile de favori. Écart assumé à la charte : le design system n'a pas de
   * jaune, et les deux couleurs disponibles sont pires — l'orange est le signal
   * « à surveiller » de tout Link (dépassement de contrat, facture échue), le
   * magenta ne se lit pas comme un favori. L'étoile jaune est une convention
   * d'interface universelle, pas une couleur de marque. Arbitrage 2026-08-28. */
  --kincy-star: #F5B301;

  /* Rouge de dépassement — ÉCART ASSUMÉ à la charte, qui n'a aucun rouge
   * (cyan, bleu, slate, magenta, purple, vert, orange). Même statut que
   * `--kincy-star` : une convention d'interface, pas une couleur de marque.
   *
   * Pourquoi pas une couleur existante : l'orange est le signal « à
   * surveiller » de tout Link, et il porte déjà sur cette page le type
   * « Projet à commander » — les deux signaux deviendraient indiscernables sur
   * une même ligne. Le magenta est explicitement réservé par `design.md` aux
   * accents « non fonctionnels » et interdit pour les statuts métier ; un
   * dépassement EST un statut métier.
   *
   * Valeur choisie sur mesure de contraste (2026-09-03) : 6,54:1 sur le blanc
   * du tableau et 5,57:1 sur `--kincy-neutral-light`. Les candidats plus vifs
   * (#D32F2F, 4,98:1) tenaient moins bien le second fond. */
  --kincy-red: #B3261E;

  /* Sarcelle de l'AXE « agence » — TROISIÈME écart assumé à la charte, après
   * `--kincy-star` et `--kincy-red`, et de même statut : une convention
   * d'interface, pas une couleur de marque.
   *
   * ⚠️ Il a fallu un token NEUF parce qu'aucune teinte n'était libre. Le
   * budget de la page « Rentabilité des projets », relevé le 2026-09-09 :
   * bleu = rôle RUN, magenta = rôle BUILD, slate = rôle MANAGE, gris foncé =
   * rôle « autres » des chips, violet = seau « ni RUN ni BUILD ni MANAGE » des
   * graphes (et il y dépense déjà quatre éclaircissements), cyan = l'état
   * « sélectionné » de tout Link, orange = « à surveiller », rouge =
   * « dépassé », vert exclu de la différenciation de catégorie par
   * `design.md` §3.6. Il ne restait rien.
   *
   * ⚠️ Et il marque l'AXE, pas la valeur : TOUTES les chips d'agence sont de
   * cette teinte, sentinelle « Sans agence » comprise. C'est ce qui règle le
   * défaut d'origine — « Sans département » et « Madagascar » avaient la même
   * tête — sans ouvrir un second axe catégoriel de six couleurs, ce que la
   * page ne peut pas porter. Le département dit QUELLE VALEUR par sa teinte,
   * l'agence dit QUEL AXE par la sienne : deux métiers, deux mécanismes, aucune
   * collision possible par construction.
   *
   * Valeur choisie sur mesure (2026-09-09) : 5,79:1 sur blanc, donc le texte
   * tient WCAG 1.4.3 au repos ET le blanc tient sur l'aplat une fois la chip
   * cochée. Ce n'est pas du cyan : 15 points de clarté en dessous, et surtout
   * 5,79:1 contre 2,67:1 — c'est le CONTRASTE qui les sépare à l'œil, pas la
   * teinte (9° d'écart). Candidats plus sombres (#005F5F, 7,49:1) écartés :
   * ils tiraient vers le vert bouteille et perdaient l'identité sarcelle. */
  --kincy-teal: #0F7173;

  /* Neutral palette */
  --kincy-neutral-dark: #595959;
  --kincy-neutral-medium: #c4c4c4;
  --kincy-neutral-light: #E2EFF4;

  /* Spacing (8px grid) */
  /* ⚠️ `xs` MANQUAIT, et six declarations l'employaient (ajoute le 2026-09-17).
     Une `var()` non definie n'est pas ignoree : la declaration entiere devient
     invalide au calcul, donc `gap: var(--kincy-spacing-xs)` ne vaut pas 0 par
     defaut, il vaut « pas de gouttiere du tout ». Les six vivaient toutes dans
     le bloc « Occupation des agents » : gouttiere des chips, marge sous une
     legende de cadre, marge d'un en-tete de depliage. Aucune erreur, aucun test
     rouge, et `docs/conventions/ui.md` §4.1 CITAIT ce token comme la mesure de
     reference. C'est la troisieme regle inerte relevee sur cette page, apres
     les deux fonds recouverts par Bootstrap (§4.3 et §4.8). Un test refuse
     desormais toute `var(--kincy-*)` sans definition. */
  --kincy-spacing-xs: 4px;
  --kincy-spacing-sm: 8px;
  --kincy-spacing-md: 16px;
  --kincy-spacing-lg: 24px;
  --kincy-spacing-xl: 32px;
  --kincy-spacing-2xl: 48px;

  /* Border radius */
  --kincy-radius-sm: 4px;
  --kincy-radius-md: 8px;
  --kincy-radius-lg: 16px;

  /* --- ÉLÉVATION : trois niveaux, et c'est tout -------------------------
   *
   * Standard du dépôt depuis le 2026-09-14, à la demande de Rodolphe : « que
   * ceci deviennent des standards CSS si on les reprend sur d'autres projets ».
   * Avant lui, quatre ombres portées vivaient en valeurs magiques dans quatre
   * blocs sans rapport, avec trois opacités et deux encres différentes.
   *
   * ⚠️ **Le design system ne dit RIEN de l'élévation** (vérifié dans
   * `design-system/design.md` le 2026-09-14 : aucune entrée ombre, relief ou
   * élévation). Ces tokens ne contredisent donc aucune doctrine amont — ils en
   * créent une, ici, là où vivent déjà les tokens de couleur, d'espacement et
   * de rayon.
   *
   * **Trois niveaux, jamais quatre**, et chacun répond à une question
   * différente. Un quatrième se négocie, il ne s'ajoute pas :
   *
   * - `raised`  : un contrôle qui FLOTTE au-dessus de son fond sans le quitter
   *               (une pastille d'action). Deux couches — un contact serré plus
   *               une diffusion douce : une ombre à une seule couche se lit
   *               comme un flou, deux se lisent comme une hauteur.
   * - `overlay` : une surface qui a QUITTÉ le plan (panneau ancré, menu). Très
   *               diffuse et peu opaque : elle dit « au-dessus », pas « épais ».
   * - `stuck`   : une surface qui vient de SE DÉTACHER en défilant (barre
   *               collante). Décalée vers le bas et **resserrée par un rayon
   *               négatif**, pour que le relief ne déborde pas latéralement —
   *               une barre pleine largeur avec une ombre qui bave sur les côtés
   *               se lit comme un défaut de rendu.
   *
   * ⚠️ L'encre est le SLATE de la charte, en composantes, pour que les trois
   * niveaux partagent une teinte et qu'aucun ne retombe sur du noir pur. Le noir
   * a une seule occurrence restante dans cette feuille, sur `.kincy-ms-bar-tip`
   * (une infobulle à fond slate) : l'y remplacer par du slate rendrait l'ombre
   * invisible sur son propre fond. Écart **assumé et daté**, pas un oubli. */
  --kincy-shadow-ink: 68, 84, 106;   /* = --kincy-slate, en composantes */
  --kincy-elevation-raised:
    0 1px 2px rgba(var(--kincy-shadow-ink), 0.22),
    0 3px 8px rgba(var(--kincy-shadow-ink), 0.3);
  --kincy-elevation-overlay: 0 8px 24px rgba(var(--kincy-shadow-ink), 0.18);
  --kincy-elevation-stuck: 0 6px 10px -6px rgba(var(--kincy-shadow-ink), 0.45);

  /* Bootstrap overrides — Bootstrap puise dans --bs-* */
  --bs-primary: var(--kincy-cyan);
  --bs-primary-rgb: 0, 173, 206;
  --bs-link-color: var(--kincy-blue);
  --bs-link-color-rgb: 0, 119, 189;
  --bs-link-hover-color: var(--kincy-slate);
  --bs-border-radius: var(--kincy-radius-sm);
  --bs-border-radius-lg: var(--kincy-radius-md);
  --bs-body-color: #1d1d1b;
  --bs-body-bg: #ffffff;
  /* Bootstrap habille <code> d'un rose (#d63384) absent de la charte Kincy.
     Recalé sur le gris neutre : le monospace suffit à signaler la valeur
     technique, la couleur n'a rien à ajouter. Vaut pour toutes les pages. */
  --bs-code-color: var(--kincy-neutral-dark);
}

body {
  font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Helvetica, Arial, sans-serif;
  background-color: #fafbfc;
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI",
    sans-serif;
}

.navbar-brand {
  color: var(--kincy-slate);
  letter-spacing: 0.02em;
}

/* Boutons : variante primaire alignée sur la charte (cyan → blue au hover). */
.btn-primary {
  --bs-btn-color: #fff;
  --bs-btn-bg: var(--kincy-cyan);
  --bs-btn-border-color: var(--kincy-cyan);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--kincy-blue);
  --bs-btn-hover-border-color: var(--kincy-blue);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: var(--kincy-blue);
  --bs-btn-active-border-color: var(--kincy-blue);
}

/* Variante en contour, même charte. Bootstrap 5.3 compile les `.btn-outline-*`
   avec des hex littéraux : l'override `--bs-primary` posé sur `:root` ne les
   atteint pas, et un `btn-outline-primary` sortait en bleu Bootstrap au milieu
   de boutons cyan. Même mécanisme que celui déjà traité pour `.list-group`
   plus bas — la règle est ici globale, pas dans une section de page. */
.btn-outline-primary {
  --bs-btn-color: var(--kincy-cyan);
  --bs-btn-border-color: var(--kincy-cyan);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--kincy-cyan);
  --bs-btn-hover-border-color: var(--kincy-cyan);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: var(--kincy-blue);
  --bs-btn-active-border-color: var(--kincy-blue);
  --bs-btn-disabled-color: var(--kincy-neutral-medium);
  --bs-btn-disabled-border-color: var(--kincy-neutral-medium);
}

/* Bannière sombre type "hero" — fond slate + accent cyan. */
.kincy-hero {
  background-color: var(--kincy-slate);
  color: #fff;
  border-radius: var(--kincy-radius-md);
  padding: var(--kincy-spacing-xl) var(--kincy-spacing-2xl);
}

.kincy-hero .lead {
  color: rgba(255, 255, 255, 0.85);
}

.kincy-hero .kincy-accent {
  color: var(--kincy-cyan);
}

/* Cartes : surface principale blanche, contour léger neutre. */
.card {
  border-color: rgba(68, 84, 106, 0.15);
  border-radius: var(--kincy-radius-md);
}

.card-title {
  color: var(--kincy-slate);
}

/* Layout principal : sidebar fixe à gauche + main content fluide. */
.kincy-layout {
  display: flex;
  align-items: stretch;
  min-height: calc(100vh - 73px); /* navbar height ~57px + border */
}

.kincy-sidebar {
  flex: 0 0 240px;
  background-color: #ffffff;
  border-right: 1px solid rgba(68, 84, 106, 0.12);
  padding: var(--kincy-spacing-lg) var(--kincy-spacing-md);
}

.kincy-sidebar-section {
  margin-bottom: var(--kincy-spacing-lg);
}

.kincy-sidebar-title {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--kincy-neutral-dark);
  margin-bottom: var(--kincy-spacing-sm);
  padding-left: var(--kincy-spacing-sm);
}

.kincy-sidebar .nav-link {
  color: var(--kincy-slate);
  padding: 6px var(--kincy-spacing-sm);
  border-radius: var(--kincy-radius-sm);
  font-size: 0.9375rem;
}

.kincy-sidebar .nav-link:hover {
  background-color: var(--kincy-neutral-light);
  color: var(--kincy-slate);
}

.kincy-sidebar .nav-link.active {
  background-color: var(--kincy-cyan);
  color: #ffffff;
  font-weight: 500;
}

.kincy-main {
  flex: 1 1 auto;
  padding: var(--kincy-spacing-xl) var(--kincy-spacing-xl);
  max-width: 100%;
  /* ⚠️ `min-width: 0` n'est PAS décoratif, et son absence a été un défaut réel
   * jusqu'au 2026-09-04 : la taille minimale automatique d'un élément flex est
   * `min-content`, et `max-width: 100%` ne l'écrase pas. Un tableau large
   * poussait donc TOUTE la mise en page hors du cadre — mesuré au navigateur
   * sur « Rentabilité des projets » (11 colonnes) : `document.scrollWidth`
   * 1499 px pour une fenêtre de 1440, et c'est `<main>` lui-même qui débordait
   * (1259 px de large, bord droit à 1499). Le `.table-responsive` du tableau
   * était impuissant : un `overflow-x` sur un DESCENDANT ne contraint rien si
   * l'ancêtre flex, lui, a le droit de grandir.
   *
   * Le défaut se rend en HTTP 200 parfaitement valide, et il ne se voit qu'à
   * partir d'un contenu assez large — les pages voisines y échappaient de peu.
   * Contrairement à un `overflow` ici, `min-width: 0` ne crée AUCUN conteneur
   * de défilement, donc le `position: sticky` de la barre de filtres continue
   * de fonctionner. */
  min-width: 0;
  /* Pas d'overflow ici : un conteneur de scroll casserait le `position:
   * sticky` de la barre de filtres. Le scroll horizontal des tableaux
   * larges est porté localement par `.table-responsive`. */
}

/* KPI : grand chiffre cyan sur surface secondaire (neutral-light). */
.kincy-kpi {
  background-color: var(--kincy-neutral-light);
  border: none;
}

.kincy-kpi .kincy-kpi-value {
  font-size: 2.5rem;
  font-weight: 700;
  color: var(--kincy-cyan);
  line-height: 1;
}

.kincy-kpi .kincy-kpi-label {
  color: var(--kincy-slate);
  font-weight: 500;
}

/* --- Annuaire clients : barre de filtres + tableau ------------------- */

/* Barre de filtres collante : reste visible quand le tableau défile. */
.kincy-filter-bar {
  position: sticky;
  top: 0;
  z-index: 1020;
  display: flex;
  flex-wrap: wrap;
  gap: var(--kincy-spacing-lg);
  background-color: var(--bs-body-bg);
  padding: var(--kincy-spacing-md) 0;
  margin-bottom: var(--kincy-spacing-md);
  border-bottom: 1px solid rgba(68, 84, 106, 0.12);
}

.kincy-filter-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-sm);
}

.kincy-filter-label {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--kincy-neutral-dark);
}

/* Capsule de filtre : contour neutre au repos, fond cyan une fois active. */
.kincy-chip {
  border: 1px solid rgba(68, 84, 106, 0.3);
  background-color: transparent;
  color: var(--kincy-slate);
  border-radius: var(--kincy-radius-lg);
  padding: 4px var(--kincy-spacing-md);
  font-size: 0.875rem;
  line-height: 1.4;
  cursor: pointer;
  transition: background-color 0.12s ease, color 0.12s ease,
    border-color 0.12s ease;
}

.kincy-chip:hover {
  border-color: var(--kincy-cyan);
  color: var(--kincy-cyan);
}

.kincy-chip--active {
  background-color: var(--kincy-cyan);
  border-color: var(--kincy-cyan);
  color: #fff;
}

.kincy-chip--active:hover {
  color: #fff;
}

/* Capsule d'AVERTISSEMENT (ajoutée le 2026-09-03, spec §8.7).
 *
 * Porte le type « Projet (en attente de commande) » — un projet travaillé sans
 * commande Odoo, donc une anomalie commerciale et non une catégorie neutre.
 * C'est le premier usage de `--kincy-orange` sur la page « Suivi des projets »,
 * et il lève l'interdiction que le §8.7 y posait : il n'y avait alors aucune
 * anomalie à signaler, il y en a une maintenant.
 *
 * L'orange l'emporte sur le cyan de `--active` : les deux classes coexistent
 * sur le badge de filtre (le serveur pose l'une, Alpine l'autre) et un badge
 * coché doit rester reconnaissable comme l'avertissement qu'il est. */
.kincy-chip--warning {
  border-color: var(--kincy-orange);
  color: var(--kincy-orange);
}

.kincy-chip--warning:hover {
  border-color: var(--kincy-orange);
  color: var(--kincy-orange);
  background-color: rgba(240, 131, 0, 0.08);
}

.kincy-chip--warning.kincy-chip--active,
.kincy-chip--warning.kincy-chip--active:hover {
  background-color: var(--kincy-orange);
  border-color: var(--kincy-orange);
  color: #fff;
}

/* Capsule de filtre porteuse d'un picto (sans texte) : padding resserré et
 * picto à 20px, pour les boutons icône-only (ex. filtre Atera). */
.kincy-chip--icon {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
}

.kincy-chip--icon img {
  width: 20px;
  height: 20px;
}

/* Vignette logo client : taille fixe, logo contenu sans déformation. */
.kincy-logo-thumb {
  width: 36px;
  height: 36px;
  object-fit: contain;
  border-radius: var(--kincy-radius-sm);
}

/* Avatar de repli : initiale sur fond neutre quand aucun logo n'est dispo. */
.kincy-logo-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--kincy-radius-sm);
  background-color: var(--kincy-neutral-light);
  color: var(--kincy-slate);
  font-weight: 600;
}

/* Pastille de statut : pleine, sans texte. Verte si actif, gris clair
 * si inactif. */
.kincy-status-dot {
  display: inline-block;
  width: 0.875rem;
  height: 0.875rem;
  border-radius: 50%;
  vertical-align: middle;
}

.kincy-status-dot--ok {
  background-color: var(--kincy-green);
}

.kincy-status-dot--off {
  background-color: var(--kincy-neutral-medium);
}

/* Logo Atera : en couleur si le client a Atera, en niveaux de gris sinon. */
.kincy-atera-logo {
  width: 24px;
  height: 24px;
  object-fit: contain;
}

.kincy-atera-logo--off {
  filter: grayscale(1);
  opacity: 0.45;
}

/* Lien vers la fiche Halo : picto « lien externe » discret, cyan charte. */
.kincy-halo-link {
  display: inline-flex;
  align-items: center;
  color: var(--kincy-cyan);
}

.kincy-halo-link:hover {
  color: var(--kincy-blue);
}

/* Microsoft CSP : une seule capsule verte contenant le check + le nombre
 * de tenants (token charte, pas le vert Bootstrap) ; croix atténuée quand
 * aucun tenant. */
.kincy-chip-ok {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background-color: var(--kincy-green);
  color: #fff;
  border-radius: var(--kincy-radius-lg);
  padding: 2px var(--kincy-spacing-sm);
  font-size: 0.8125rem;
  font-weight: 600;
  white-space: nowrap; /* une capsule ne se casse pas sur deux lignes */
}

.kincy-check {
  display: block;
}

/* Pastille ronde du nombre de tenants, logée DANS la capsule verte :
 * fond blanc + chiffre vert pour ressortir sur le fond vert. */
.kincy-tenant-count {
  background-color: #fff;
  color: var(--kincy-green);
}

/* CSP orphelin : tenant déclaré côté Halo mais absent de notre base Microsoft.
 * Même capsule, fond orange charte ; la pastille reprend l'orange. */
.kincy-chip-ok--orphan {
  background-color: var(--kincy-orange);
}

.kincy-chip-ok--orphan .kincy-tenant-count {
  color: var(--kincy-orange);
}

.kincy-csp-none {
  color: var(--kincy-neutral-dark);
  opacity: 0.6;
}

/* En-têtes de colonnes collants : restent visibles au scroll, calés juste
 * sous la barre de filtres (offset --annuaire-thead-top mesuré à l'init,
 * fallback 64px). Fond opaque obligatoire pour que les lignes ne
 * transparaissent pas sous le thead figé. */
.kincy-annuaire-table thead th {
  position: sticky;
  top: var(--annuaire-thead-top, 64px);
  z-index: 3;
  background-color: var(--kincy-neutral-light);
  border-bottom: 2px solid rgba(68, 84, 106, 0.15);
}

/* --- Étoile de favori (annuaire + cartouche de fiche client) ---------- */

/* Colonne étoile : étroite et non extensible, c'est un contrôle, pas une
 * donnée. */
.kincy-star-cell {
  width: 2.5rem;
}

/* Bouton nu : pas de fond, pas de bordure, mais une cible cliquable de 32px
 * alors que l'étoile n'en fait que 18 — une étoile de 18px est trop petite à
 * viser. Le contrôle reste utilisable au clavier, d'où le :focus-visible.
 *
 * La transition ne porte QUE des propriétés posées sur le bouton lui-même
 * (corrigé le 2026-08-31) : elle déclarait `stroke` et `fill`, qui vivent sur
 * le <svg> enfant, donc elle n'animait rien du tout. Une déclaration inerte est
 * pire qu'une absence de déclaration — on la croit à l'œuvre. */
.kincy-star-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  background-color: transparent;
  border-radius: var(--kincy-radius-sm);
  cursor: pointer;
  transition: background-color 0.12s ease, box-shadow 0.12s ease;
}

.kincy-star-btn:focus-visible {
  outline: 2px solid var(--kincy-cyan);
  outline-offset: 2px;
}

/* Un seul tracé d'étoile : c'est le REMPLISSAGE qui porte l'état.
 *
 * Le contour est passé de --kincy-neutral-medium à --kincy-slate le 2026-08-31.
 * Mesure (WCAG 2.1, formule de luminance relative, fond --bs-body-bg #ffffff) :
 * l'ancien contour #c4c4c4 donnait 1,74:1 et le remplissage #F5B301 1,85:1 —
 * les deux états étaient donc quasi équilumineux (L=0,5520 contre L=0,5165) et
 * le glyphe se détachait mal du fond. Le contour #44546A donne 7,71:1 sur blanc.
 * La teinte jaune n'est PAS touchée (arbitrage §9.1).
 *
 * Contour du repos ATTÉNUÉ le 2026-08-31 (demande de Rodolphe : une étoile au
 * repos moins contrastée). L'atténuation passe par stroke-opacity et non par une
 * autre teinte : le token reste --kincy-slate, et la pâleur devient un réglage
 * unique et mesurable au lieu d'une couleur en dur — aucun token de la charte ne
 * tombe entre #44546A (7,71:1) et #c4c4c4 (1,74:1).
 * Mesure à 0,65 : composite #85909E, soit 3,24:1 sur le blanc de l'annuaire et
 * 3,01:1 sur le --kincy-neutral-light du cartouche de fiche client. C'est la
 * valeur la plus pâle qui tienne les 3:1 de WCAG 1.4.11 sur les DEUX fonds ;
 * 0,60 donnait 2,71:1 sur le cartouche et passait sous le plancher. */
.kincy-star-btn svg {
  fill: none;
  stroke: var(--kincy-slate);
  stroke-opacity: 0.65;
  transition: fill 0.12s ease, stroke 0.12s ease, stroke-opacity 0.12s ease;
}

/* Survol : pastille de fond, la convention des boutons-pictos de Link
 * (cf. .kincy-card-expand). Remplace l'ancien aperçu par recoloration du
 * contour en jaune, devenu contre-productif : il ÉCLAIRCISSAIT le glyphe
 * (1,85:1 au lieu de 7,71:1) au moment précis où l'on vise.
 *
 * --kincy-neutral-medium et non --kincy-neutral-light : l'étoile a DEUX fonds.
 * Sur le cartouche de la fiche client, le fond est déjà --kincy-neutral-light,
 * une pastille de cette teinte y serait à 1,00:1 — littéralement invisible (et
 * seulement 1,17:1 sur le blanc de l'annuaire). Mesures : 1,74:1 sur blanc et
 * 1,49:1 sur le cartouche pour --kincy-neutral-medium, le contour du glyphe
 * restant à 4,42:1 par-dessus.
 *
 * *Corrigé le 2026-08-31* : le contour du repos étant désormais atténué
 * (stroke-opacity 0,65), il ne vaut plus 4,42:1 par-dessus la pastille de survol
 * mais **2,27:1** (composite #85909E sur #c4c4c4). Le survol reste lisible : ce
 * qu'il signale est l'apparition de la pastille elle-même, pas un changement du
 * glyphe. */
.kincy-star-btn:hover {
  background-color: var(--kincy-neutral-medium);
}

/* Étoile pleine : contour de la MÊME teinte que le remplissage, donc un glyphe
 * jaune uni (demande de Rodolphe le 2026-08-31 — le contour --kincy-slate se
 * lisait comme une bordure bleue). stroke-opacity revient à 1, la règle de base
 * ci-dessus l'ayant abaissée pour le seul état de repos.
 *
 * Conséquence assumée, mesurée : le glyphe plein retombe à 1,85:1 sur blanc et
 * 1,58:1 sur le cartouche, sous les 3:1 de WCAG 1.4.11. L'information d'état ne
 * repose pour autant PAS sur la couleur — la distinction creux/plein est une
 * différence de FORME (`fill: none` contre un aplat), doublée d'`aria-pressed`
 * et de l'infobulle. C'est la perceptibilité du contour qui baisse, pas la
 * lisibilité de l'état. Arbitrage de Rodolphe, cf. §9.1 de la spec. */
.kincy-star-btn--on svg {
  fill: var(--kincy-star);
  stroke: var(--kincy-star);
  stroke-opacity: 1;
}

/* Variante du cartouche de fiche client (spec §8) : le cartouche est un titre,
 * l'étoile y est donc un cran plus grande que dans l'annuaire. La cible
 * cliquable suit, elle ne descend jamais sous les 32px de la variante de base. */
.kincy-star-btn--lg {
  width: 40px;
  height: 40px;
}

.kincy-star-btn--lg svg {
  width: 24px;
  height: 24px;
}

/* Échec d'enregistrement : signal transitoire de 2 s posé par window.kincyStarError
 * (static/js/app.js, appelée par hx-on::response-error / send-error). Pas de
 * toast — un toast pour un clic d'étoile est disproportionné, et l'étoile qui
 * ne se remplit pas est déjà le message.
 *
 * Le signal est ADDITIF (corrigé le 2026-08-31) : un halo autour du bouton, le
 * glyphe intact. Il posait `fill: none` sur le <svg>, ce qui VIDAIT une étoile
 * pleine pendant 2 s — donc, quand l'échec portait sur le retrait d'un favori,
 * l'utilisateur voyait exactement ce qu'un retrait réussi aurait affiché. Un
 * signal d'erreur ne se substitue jamais à l'état.
 *
 * Le halo est en --kincy-orange, seul code couleur d'alerte de Link : 2,64:1 sur
 * blanc, sous les 3:1 de WCAG 1.4.11 — comme le --kincy-cyan du :focus-visible
 * juste au-dessus (2,67:1), c'est une propriété de la palette et non un choix
 * local. Le signal ne repose pas sur ce halo seul : l'infobulle passe à « Échec
 * de l'enregistrement, réessayez. » et l'étoile n'a pas changé d'état. */
.kincy-star-btn--error {
  box-shadow: 0 0 0 2px var(--kincy-orange);
}

/* Pastille de filtre « Favoris » : picto ET libellé, contrairement au filtre
 * Atera qui est picto seul — d'où l'écart explicite entre les deux et le
 * padding droit rendu à la pastille standard. */
.kincy-star-chip {
  gap: var(--kincy-spacing-sm);
  padding-right: var(--kincy-spacing-md);
}

.kincy-star-chip svg {
  fill: currentColor;
  stroke: currentColor;
}

/* --- Page synthèse client : cartouche + cartes ----------------------- */

/* Cartouche de titre : logo mis en avant + nom du client. */
.kincy-client-header {
  display: flex;
  align-items: center;
  gap: var(--kincy-spacing-lg);
  background-color: var(--kincy-neutral-light);
  border-radius: var(--kincy-radius-md);
  padding: var(--kincy-spacing-lg);
}

.kincy-client-logo {
  width: 72px;
  height: 72px;
  object-fit: contain;
  border-radius: var(--kincy-radius-sm);
  background-color: #fff;
}

.kincy-client-logo--fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  font-weight: 700;
  color: var(--kincy-slate);
}

/* Liens du cartouche vers Halo et Odoo (2026-09-24), poussés à droite.
 * Logo GRISÉ au repos, en couleur au survol : même effet que `.kincy-build-logo`
 * et pour la même raison (le pictogramme Halo est rouge, la couleur d'alerte de
 * Link). Le cadre blanc reprend la surface du logo client, à sa gauche.
 * `--off` : rien à ouvrir (instance non configurée, ou aucune entité Odoo), donc
 * aucun retour au survol, rien ne doit inviter au clic. */
.kincy-client-links {
  display: flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  margin-left: auto;
}

.kincy-client-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  background-color: #fff;
  border: 1px solid transparent;
  border-radius: var(--kincy-radius-sm);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.kincy-client-link-logo {
  width: 28px;
  height: 28px;
  object-fit: contain;
  filter: grayscale(1);
  opacity: 0.6;
  transition: filter 0.12s ease, opacity 0.12s ease;
}

a.kincy-client-link:hover,
a.kincy-client-link:focus-visible,
button.kincy-client-link:hover,
button.kincy-client-link:focus-visible,
button.kincy-client-link[aria-expanded="true"] {
  border-color: var(--kincy-cyan);
  box-shadow: var(--kincy-elevation-raised);
}

a.kincy-client-link:hover .kincy-client-link-logo,
a.kincy-client-link:focus-visible .kincy-client-link-logo,
button.kincy-client-link:hover .kincy-client-link-logo,
button.kincy-client-link:focus-visible .kincy-client-link-logo,
button.kincy-client-link[aria-expanded="true"] .kincy-client-link-logo {
  filter: none;
  opacity: 1;
}

.kincy-client-link--off .kincy-client-link-logo {
  opacity: 0.3;
}

/* Nombre d'entités Odoo, quand il y en a plusieurs : c'est lui qui annonce
 * qu'un clic ouvre un menu et non une fiche. */
.kincy-client-link-count {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 9px;
  background-color: var(--kincy-slate);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}

/* Carte de synthèse : format fixe partagé par toutes les cartes.
 * Hauteur fixe ; bandeau + toolbar figés en tête ; corps scrollable. */
.kincy-card {
  display: flex;
  flex-direction: column;
  height: 480px;
  background-color: #fff;
  border: 1px solid rgba(68, 84, 106, 0.15);
  border-radius: var(--kincy-radius-md);
  overflow: hidden;
}

.kincy-card--loading {
  align-items: center;
  justify-content: center;
}

.kincy-card-bandeau {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--kincy-spacing-md);
  border-bottom: 1px solid rgba(68, 84, 106, 0.12);
}

.kincy-card-title {
  font-weight: 600;
  color: var(--kincy-slate);
}

/* Picto « agrandir » vers la vue détaillée : pastille de fond neutre discrète
 * pour le signaler comme interactif, cyan charte au survol. */
.kincy-card-expand {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--kincy-radius-sm);
  background-color: var(--kincy-neutral-light);
  color: var(--kincy-slate);
  transition: background-color 0.12s ease, color 0.12s ease;
}

.kincy-card-expand:hover {
  background-color: var(--kincy-cyan);
  color: #fff;
}

/* Zone KPI : synthèse visuelle épinglée sous les filtres, hauteur fixe,
 * ne défile pas. Masquée quand la carte n'a pas de KPI. */
.kincy-card-kpi {
  flex: 0 0 auto;
  padding: var(--kincy-spacing-md) var(--kincy-spacing-md) 0;
}

.kincy-card-kpi:empty {
  display: none;
}

/* Grille KPI 2 colonnes (invisible) : camembert à gauche, tuile à droite. */
.kincy-card-kpi-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--kincy-spacing-md);
  align-items: center;
}

/* Tuile « Licences surnuméraires » : gros chiffre + sous-titre. Orange en
 * alerte (>0, cliquable vers le détail), neutre atténué à zéro. */
.kincy-ms-surplus {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 2px;
  height: 140px;
  border-radius: var(--kincy-radius-md);
  text-decoration: none;
}

/* Même correction que `.kincy-kpi-tile--alert` (2026-10-29, `CK-D35`) : la tuile
 * « Licences surnuméraires » porte SA classe, pas la tuile partagée, et l'arbitrage
 * de Rodolphe la nommait ; orange encre 4,22:1 sur l'aplat, orange 2,41:1. */
.kincy-ms-surplus--alert {
  background-color: rgba(240, 131, 0, 0.1); /* --kincy-orange à 10 % */
  color: var(--kincy-orange-ink);
}

.kincy-ms-surplus--alert:hover {
  background-color: rgba(240, 131, 0, 0.18);
}

.kincy-ms-surplus--zero {
  background-color: var(--kincy-neutral-light);
  color: var(--kincy-neutral-dark);
}

.kincy-ms-surplus-value {
  font-size: 2.5rem;
  font-weight: 700;
  line-height: 1;
}

.kincy-ms-surplus-label {
  font-size: 0.8125rem;
  color: var(--kincy-slate);
}

/* Toolbar : emplacement fixe en tête de carte (ne défile pas avec le corps).
 * Masquée quand la carte n'a pas de toolbar. */
.kincy-card-toolbar {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--kincy-spacing-sm);
  padding: var(--kincy-spacing-sm) var(--kincy-spacing-md);
  border-bottom: 1px solid rgba(68, 84, 106, 0.08);
}

.kincy-card-toolbar:empty {
  display: none;
}

.kincy-card-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--kincy-spacing-md);
}

/* Conteneur du camembert : hauteur fixe (Chart.js responsive non proportionnel).
 * Logé dans la zone KPI ; pas de marge basse (la zone gère son padding). */
.kincy-chart-wrap {
  position: relative;
  height: 140px;
}

/* Carré de couleur en 1re colonne du tableau, synchro avec le camembert. */
.kincy-color-col {
  width: 1.5rem;
}

.kincy-color-chip {
  display: inline-block;
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 3px;
  vertical-align: middle;
}

/* Licences attribuées < achetées : sièges payés non utilisés, mis en alerte. */
.kincy-ms-underused {
  color: var(--kincy-orange);
  font-weight: 600;
}

/* --- Carte « Contrats & heures prépayées » ---
 * Pattern de carte « KPI1 résumé, KPI2 anomalies » : tuile KPI générique
 * (réutilisable hors MS) + résumé par contrat dans la zone KPI gauche. */

/* Tuile KPI générique : gros chiffre + sous-titre, hauteur alignée sur la zone
 * KPI. Variante --alert (orange, valeur à surveiller) / --zero (neutre atténué).
 * Décline le visuel de .kincy-ms-surplus en composant non spécifique à MS. */
.kincy-kpi-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 2px;
  height: 140px;
  border-radius: var(--kincy-radius-md);
  text-decoration: none;
}

/* Le chiffre d'alerte est en `--kincy-orange-ink`, PAS en `--kincy-orange`
 * (corrigé le 2026-10-29, `docs/client-teach-card-spec.md` `CK-D35`, validé par
 * Rodolphe, dans la tuile PARTAGÉE et jamais par une variante de carte). Mesuré avec
 * `apps/core/contrast.py` sur l'aplat composé sur blanc (#fef3e6) : orange 2,41:1,
 * orange encre 4,22:1 (texte large, 40 px gras : au-dessus de 3:1). Touche aussi
 * « Encours » (Facturation) et « Responsabilité Kincy » (Contrats). */
.kincy-kpi-tile--alert {
  background-color: rgba(240, 131, 0, 0.1); /* --kincy-orange à 10 % */
  color: var(--kincy-orange-ink);
}

.kincy-kpi-tile--zero {
  background-color: var(--kincy-neutral-light);
  color: var(--kincy-neutral-dark);
}

.kincy-kpi-tile-value {
  font-size: 2.5rem;
  font-weight: 700;
  line-height: 1;
}

.kincy-kpi-tile-label {
  font-size: 0.8125rem;
  color: var(--kincy-slate);
}

/* Ligne secondaire d'une tuile KPI, intercalée entre le chiffre et le libellé
 * (ex. « Échu : … » sur la carte Facturation). Aucune couleur propre : elle
 * hérite celle de la tuile, donc orange en --alert et neutre en --zero. */
.kincy-kpi-tile-note {
  font-size: 0.875rem;
  font-weight: 600;
}

/* Résumé par contrat (zone KPI gauche) : barres divergentes, 0 au centre, une
 * ligne par contrat. Bornée en hauteur, défile au besoin (ne pousse pas la zone
 * KPI). Solde positif → barre cyan à droite ; dépassement → orange à gauche ;
 * forfait (consommation) → neutre à droite. */
.kincy-contract-bars {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 140px;
  overflow-y: auto;
}

.kincy-contract-bar-row {
  display: flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  font-size: 0.8125rem;
}

/* Libellé abrégé, largeur fixe pour aligner les pistes entre elles. */
.kincy-contract-bar-label {
  flex: 0 0 3.25rem;
  color: var(--kincy-slate);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Piste portant la ligne 0 centrale. */
.kincy-contract-bar-track {
  position: relative;
  flex: 1 1 auto;
  height: 12px;
}

.kincy-contract-bar-track::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -1px;
  bottom: -1px;
  width: 1px;
  background-color: rgba(68, 84, 106, 0.35);
}

.kincy-contract-bar {
  position: absolute;
  top: 1px;
  height: 10px;
  border-radius: 2px;
  max-width: 50%;
}

.kincy-contract-bar--pos {
  left: 50%;
  background-color: var(--kincy-cyan);
}

.kincy-contract-bar--neg {
  right: 50%;
  background-color: var(--kincy-orange);
}

.kincy-contract-bar--forfait {
  left: 50%;
  background-color: var(--kincy-neutral-medium);
}

.kincy-contract-bar-value {
  flex: 0 0 3.5rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.kincy-contract-bar-value--neg {
  color: var(--kincy-orange);
}

/* Pastille d'abréviation de catégorie (statique, contrairement à .kincy-chip
 * cliquable). Sert au résumé KPI et à la 1re colonne du tableau. */
.kincy-contract-tag {
  flex: 0 0 auto;
  border: 1px solid rgba(68, 84, 106, 0.3);
  border-radius: var(--kincy-radius-lg);
  padding: 2px var(--kincy-spacing-sm);
  font-size: 0.75rem;
  line-height: 1.4;
  color: var(--kincy-slate);
  white-space: nowrap;
}

/* Valeur d'état (heures), chiffres tabulaires pour un alignement net. Orange
 * quand le solde prépayé est négatif (dépassement à surveiller — le design
 * system n'a pas de rouge, l'orange porte le signal d'alerte fonctionnel). */
.kincy-contract-hours {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
}

.kincy-contract-hours--negative {
  color: var(--kincy-orange);
}

/* --- Carte « Facturation » (domaine Odoo) ---
 * Réutilise .kincy-card-kpi-grid, .kincy-kpi-tile et .kincy-chip tels quels ;
 * seuls le mois en cours et les notes de pied appellent un complément. */

/* Montant mensuel : chiffres tabulaires pour que les colonnes s'alignent,
 * insécable pour que « 1 234 € » ne se coupe pas en deux lignes. */
.kincy-billing-amount {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* M0 est un mois partiel : affiché (c'est la première question qu'on se pose
 * devant la carte), mais atténué — et exclu de la moyenne, côté service. */
.kincy-billing-row--current td {
  color: var(--kincy-neutral-dark);
}

.kincy-billing-current {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Notes de pied de carte : fraîcheur du snapshot Odoo (jamais optionnelle) et
 * date de dernière facture quand la fenêtre de six mois est vide. */
.kincy-billing-note {
  margin-top: var(--kincy-spacing-sm);
  margin-bottom: 0;
  font-size: 0.8125rem;
  color: var(--kincy-neutral-dark);
}

/* Variante d'attaque : la même note posée *au-dessus* du tableau. */
.kincy-billing-note--lead {
  margin-top: 0;
  margin-bottom: var(--kincy-spacing-sm);
}

/* Champ couleur hex de l'écran MS Products : largeur juste pour « #RRGGBB ». */
.kincy-ms-color-input {
  max-width: 7rem;
}

/* Masque les éléments pilotés par Alpine tant qu'il n'a pas hydraté la page. */
[x-cloak] {
  display: none !important;
}

/* --- Page détail licences Microsoft (drill-down par utilisateur) --- */

/* Cartouche de chips figé en haut au scroll ; sa hauteur alimente l'offset
 * sticky de l'entête du tableau (--kincy-ms-sticky-h, posée en JS). */
/* Barre de filtres de la page licences : elle hérite de `.kincy-run-filterbar`
 * (collante, z-index 1020) et n'ajoute qu'une gouttière entre ses deux cadres.
 * ⚠️ C'est ELLE qui alimente `--kincy-ms-sticky-h`, dont dépend l'offset de
 * l'entête de tableau : le cartouche `.kincy-ms-sticky` qui la posait avant a
 * disparu, et sans rebranchement l'entête se collerait SOUS la barre, au repli
 * `0`, sans erreur ni test rouge (spec ML-A34). */
/* ⚠️ DOUBLE CLASSE, et ce n'est pas un ornement : `.kincy-run-filterbar` vit
 * PLUS BAS dans cette feuille, donc à spécificité égale c'est elle qui gagne et
 * ce `gap` serait inerte. Le dépôt a déjà payé ce mécanisme deux fois — une
 * chip qui sortait neutre au lieu de cyan, et une barre qui cessait de coller.
 * (0,2,0) ne dépend plus de l'ordre des règles. */
.kincy-run-filterbar.kincy-ms-filterbar {
  gap: var(--kincy-spacing-lg);
  align-items: flex-start;
}

/* Filtres de TABLEAU : hors du formulaire, donc hors de tout aller-retour. */
.kincy-ms-tablefilters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-md);
  margin: var(--kincy-spacing-md) 0;
}

.kincy-ms-tablefilters .kincy-ms-search {
  width: 16rem;
  max-width: 100%;
}

.kincy-ms-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* ⚠️ `.kincy-ms-chip--active` a été SUPPRIMÉ le 2026-09-14 (spec ML-A35).
 * Il surchargeait `.kincy-chip--active` à spécificité égale (0,1,0) et venait
 * 723 lignes plus bas dans cette feuille : à spécificité égale, l'ordre
 * tranche, donc une chip cochée sortait NEUTRE au lieu de cyan et l'état
 * « sélectionné » devenait invisible. Même mécanisme que le `position:
 * relative` qui a décollé la barre de la page rentabilité. Les chips de cette
 * page emploient désormais `.kincy-chip--active`, comme partout ailleurs.
 * Ne pas le rétablir. */

.kincy-ms-chip-dot {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
}

.kincy-ms-chip-count {
  background-color: var(--kincy-neutral-light);
  color: var(--kincy-slate);
  border-radius: var(--kincy-radius-lg);
  padding: 0 8px;
  font-size: 0.78rem;
  font-weight: 600;
}

.kincy-ms-reset {
  white-space: nowrap;
}

/* Conteneur du graphe d'évolution : hauteur fixe (Chart.js non proportionnel). */
/* ⚠️ La hauteur fixe appartient au CANVAS, pas au cadre. Elle était sur le
 * cadre jusqu'au 2026-09-14 : quand les intertitres ont grandi, le titre a mangé
 * la hauteur disponible et le canvas a débordé — sa légende chevauchait la note
 * placée dessous. Chart.js a besoin d'un conteneur à hauteur déterminée
 * (`maintainAspectRatio: false`) ; la lui donner directement le rend
 * indépendant de ce qui l'accompagne dans le cadre. */
.kincy-ms-chart-wrap {
  position: relative;
  margin: var(--kincy-spacing-md) 0;
}

.kincy-ms-chart-wrap > canvas {
  height: 280px;
  max-height: 280px;
}

/* Entête du tableau figée juste sous le cartouche de chips. */
.kincy-ms-table thead th {
  position: sticky;
  top: var(--kincy-ms-sticky-h, 0);
  background-color: var(--kincy-neutral-light);
  z-index: 10;
}

.kincy-ms-th {
  vertical-align: top;
}

.kincy-ms-search {
  max-width: 22rem;
}

/* Chips de licence dans le tableau : pastille colorée (couleur de la licence),
 * texte noir/blanc choisi pour le contraste côté vue. */
.kincy-ms-licence-chip {
  display: inline-block;
  border-radius: var(--kincy-radius-lg);
  padding: 2px 10px;
  font-size: 0.8rem;
  line-height: 1.4;
}

/* Compte Microsoft désactivé : ligne atténuée, sans colonne dédiée. */
.kincy-ms-user-disabled {
  opacity: 0.55;
}

/* Champ de recherche + lien de réinitialisation dans la barre de filtres. */
.kincy-filter-group--grow {
  flex: 1 1 220px;
}

.kincy-filter-search {
  min-width: 200px;
}

/* Décompte des lignes visibles dans une barre de filtres (2026-09-18).
 *
 * Classe PROPRE plutôt que `.kincy-run-bar-note`, qui porte `margin-left: auto` :
 * deux marges automatiques dans une barre `flex-wrap` se disputent l'espace
 * restant et renvoient le lien « Réinitialiser » à la ligne suivante — mesuré à
 * la capture de l'annuaire RH. Ici le décompte suit le champ de recherche, et
 * l'unique marge automatique reste celle du lien. */
.kincy-filter-count {
  align-self: center;
  font-size: 0.8125rem;
  color: var(--kincy-neutral-dark);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.kincy-filter-reset {
  margin-left: auto;
  align-self: center;
  border: none;
  background: none;
  padding: 0;
  color: var(--kincy-slate);
  font-size: 0.8125rem;
  text-decoration: underline;
  cursor: pointer;
}

.kincy-filter-reset:hover {
  color: var(--kincy-cyan);
}


/* --- Barre de pilotage sticky (zone RUN, puis page détail Facturation) ----
 *
 * Écrite pour le rapport rentabilité ; reprise telle quelle par la page détail
 * « Facturation » le 2026-08-28 (barre + panneau ancré + slider double
 * poignée). Le préfixe `kincy-run-` est donc historique, pas un périmètre :
 * toute modification ici touche les deux pages. */

/* Barre de filtres sticky : même contrat que .kincy-filter-bar (annuaire).
 * Fonctionne parce que .kincy-main n'a pas de conteneur d'overflow. */
.kincy-run-filterbar {
  position: sticky;
  top: 0;
  z-index: 1020;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  background-color: var(--bs-body-bg);
  padding: var(--kincy-spacing-sm) 0;
  margin-bottom: var(--kincy-spacing-md);
  border-bottom: 1px solid rgba(68, 84, 106, 0.12);
}

/* Filet cyan : signal global « au moins un filtre dévie du défaut ». */
.kincy-run-filterbar--dirty {
  border-bottom: 2px solid var(--kincy-cyan);
}

/* --- RELIEF À L'ACCROCHE : le standard, et son mode d'emploi --------------
 *
 * Généralisé le 2026-09-14, à la demande de Rodolphe. Le mécanisme existait
 * depuis la page « Licences Microsoft », en classes `.kincy-ms-*` : il n'était
 * donc réutilisable qu'en recopiant, ce que la règle DRY du dépôt interdit. Il
 * est désormais **sans périmètre**, comme `.kincy-run-*` l'est devenu.
 *
 * Trois choses à poser, et rien d'autre :
 *
 *   1. la classe `kincy-stickybar` sur l'élément collant ;
 *   2. **juste avant lui, dans le même parent**, un nœud
 *      `<div class="kincy-sticky-sentinel" data-kincy-sticky-sentinel
 *       aria-hidden="true"></div>` ;
 *   3. rien du tout côté script : `app.js` est chargé partout et s'en occupe.
 *
 * ⚠️ **L'adjacence est le contrat**, parce que le script prend le FRÈRE SUIVANT
 * de la sentinelle. Un test le tient sur chacune des deux pages : glisser un
 * nœud entre les deux ne lèverait rien, le relief cesserait simplement
 * d'apparaître.
 *
 * ⚠️ **Pourquoi une sentinelle et pas un simple écouteur de défilement** :
 * `position: sticky` n'expose AUCUN état, ni en CSS (il n'existe pas de
 * `:stuck`) ni en JavaScript — un élément collé a exactement le même
 * `getComputedStyle` qu'un élément au repos. Le nœud d'un pixel posé juste
 * au-dessus sort du haut de la fenêtre exactement quand la barre se colle, et
 * un `IntersectionObserver` ne se réveille qu'aux deux franchissements là où un
 * écouteur travaillerait à chaque image.
 *
 * ⚠️ **LA TEINTE RESTE NEUTRE.** Le filet cyan de la même barre est déjà un
 * signal — « au moins un filtre est posé ». Deux traits colorés de 2 px sous la
 * même barre ne se distinguent pas d'un coup d'œil, et le second aurait vidé le
 * premier de son sens : une couleur de signal ne se partage pas entre deux
 * significations sur une même page. Le relief vient donc de l'OMBRE, qui ne
 * signale rien.
 *
 * Dégradation : sans `IntersectionObserver`, sans sentinelle ou sans script, la
 * barre garde son filet et tout fonctionne. **Un relief est un confort de
 * lecture, jamais une condition d'usage.** */

/* La sentinelle a une hauteur RÉELLE d'un pixel, et ce n'est pas un détail :
 * pour un élément de surface nulle, la spécification d'IntersectionObserver
 * force le ratio à 1 ou 0 selon `isIntersecting`, et le cas « rectangle vide
 * posé exactement sur le bord haut » est justement celui qu'on observe ici. Un
 * pixel lève l'ambiguïté et ne se voit pas. */
.kincy-sticky-sentinel { height: 1px; }

.kincy-stickybar--stuck {
  box-shadow: var(--kincy-elevation-stuck);
}

/* Le trait ne s'épaissit QUE si le filet cyan n'est pas là. `:not()` plutôt
 * qu'un ordre de feuille : deux règles qui touchent la même propriété ne doivent
 * jamais dépendre de leur position, ce que ce dépôt a payé deux fois — un
 * `position: relative` posé plus bas avait décollé cette barre-là. */
.kincy-stickybar--stuck:not(.kincy-run-filterbar--dirty) {
  border-bottom: 2px solid rgba(var(--kincy-shadow-ink), 0.38);
}

.kincy-run-group { position: relative; }   /* ancre du panneau */

/* Bouton-résumé de groupe : grammaire de .kincy-chip, radius-sm (commande,
 * pas capsule de valeur). */
.kincy-run-group-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  border: 1px solid rgba(68, 84, 106, 0.3);
  background-color: #fff;
  color: var(--kincy-slate);
  border-radius: var(--kincy-radius-sm);
  padding: 6px var(--kincy-spacing-md);
  font-size: 0.875rem;
  transition: border-color 0.12s ease, color 0.12s ease;
}
.kincy-run-group-btn:hover { border-color: var(--kincy-cyan); color: var(--kincy-cyan); }
.kincy-run-group-btn--open {
  border-color: var(--kincy-cyan);
  box-shadow: 0 0 0 1px var(--kincy-cyan);
}

.kincy-run-caret { font-size: 0.7em; transition: transform 0.12s ease; }
.kincy-run-group-btn--open .kincy-run-caret { transform: rotate(180deg); }

/* Badge « N/total » : cyan = filtre actif ; orange = sélection vide (signal
 * fonctionnel). Cliquable = réinitialise le groupe (le « × »). */
.kincy-run-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background-color: var(--kincy-cyan);
  color: #fff;
  border-radius: var(--kincy-radius-lg);
  padding: 0 var(--kincy-spacing-sm);
  font-size: 0.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.kincy-run-badge:hover { background-color: var(--kincy-blue); }
.kincy-run-badge--empty { background-color: var(--kincy-orange); }
.kincy-run-badge-x { font-weight: 700; }

/* Libellé vivant du bouton Période. */
.kincy-run-period-value { font-variant-numeric: tabular-nums; color: var(--kincy-neutral-dark); }
.kincy-run-period-value--narrowed { color: var(--kincy-cyan); font-weight: 600; }

/* Microcopy d'état neutre en fin de barre. */
.kincy-run-bar-note {
  margin-left: auto;
  font-size: 0.8125rem;
  color: var(--kincy-neutral-dark);
}
/* .kincy-run-reset : réutilise .kincy-filter-reset (margin-left auto inclus). */

/* Panneau ancré : popover Alpine, ombre slate, radius-md. */
.kincy-run-panel {
  position: absolute;
  top: calc(100% + var(--kincy-spacing-sm));
  left: 0;
  z-index: 1030;
  min-width: 340px;
  background-color: #fff;
  border: 1px solid rgba(68, 84, 106, 0.15);
  border-radius: var(--kincy-radius-md);
  box-shadow: var(--kincy-elevation-overlay);
  padding: var(--kincy-spacing-md);
}
.kincy-run-panel--narrow { min-width: 260px; }
.kincy-run-panel--agents { width: min(660px, calc(100vw - 280px)); } /* 280 ≈ sidebar + marge */
.kincy-run-panel--period { min-width: 460px; left: auto; right: 0; } /* dernier groupe, ancré à droite */
.kincy-run-panel--left { left: 0; right: auto; }   /* premier groupe de la barre (Facturation) */

.kincy-run-panel-actions {
  display: flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  margin-bottom: var(--kincy-spacing-sm);
}

.kincy-run-hint { font-size: 0.75rem; color: var(--kincy-neutral-dark); margin: 0 0 var(--kincy-spacing-sm); }
.kincy-run-warn { font-size: 0.75rem; color: var(--kincy-orange); margin: var(--kincy-spacing-sm) 0 0; }

/* Chips checkbox (transposition claire des chips sombres du proto). */
.kincy-run-chips {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
}
.kincy-run-chips--single { grid-template-columns: minmax(0, 1fr); }
.kincy-run-chips--agents { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.kincy-run-chip {
  display: flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  border: 1px solid rgba(68, 84, 106, 0.3);
  border-radius: var(--kincy-radius-sm);
  padding: 6px 10px;
  font-size: 0.8125rem;
  color: var(--kincy-slate);
  cursor: pointer;
}
.kincy-run-chip:hover { background-color: var(--kincy-neutral-light); }
.kincy-run-chip--off { opacity: 0.45; }            /* état décoché du proto */
.kincy-run-chip input { accent-color: var(--kincy-cyan); margin: 0; flex: 0 0 auto; }
.kincy-run-chip-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kincy-run-chip-hours {
  font-size: 0.6875rem;
  color: var(--kincy-neutral-dark);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Panneau agents : recherche + grille 3 colonnes scrollable + départements. */
.kincy-run-agent-search { flex: 1 1 auto; max-width: 240px; }
.kincy-run-agent-scroll { max-height: 320px; overflow-y: auto; padding-right: var(--kincy-spacing-sm); }
.kincy-run-agent { display: flex; align-items: center; gap: 4px; }
.kincy-run-agent .kincy-run-chip { flex: 1 1 auto; min-width: 0; }

.kincy-run-dept {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin: var(--kincy-spacing-sm) 0 4px;
}
.kincy-run-dept-name {            /* même recette que .kincy-sidebar-title */
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--kincy-neutral-dark);
}

/* Toggle « Afficher les inactifs » dans le panneau agents. */
.kincy-run-inactive-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.75rem;
  color: var(--kincy-neutral-dark);
  white-space: nowrap;
  cursor: pointer;
}
.kincy-run-inactive-toggle input { accent-color: var(--kincy-cyan); margin: 0; }

/* Suffixe « (inactif) » sur un agent désactivé/parti. */
.kincy-run-agent-inactive { font-size: 0.6875rem; color: var(--kincy-neutral-dark); font-style: italic; }

/* Slider période (double poignée, Alpine pur). */
.kincy-run-slider-row { display: flex; align-items: center; gap: var(--kincy-spacing-md); }
.kincy-run-slider-bound { font-size: 0.75rem; color: var(--kincy-neutral-dark); white-space: nowrap; }
.kincy-run-slider { position: relative; flex: 1 1 auto; height: 32px; }
.kincy-run-slider-track {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 4px;
  transform: translateY(-50%);
  border-radius: var(--kincy-radius-lg);
  background-color: var(--kincy-neutral-light);
}
.kincy-run-slider-range {
  position: absolute;
  top: 50%;
  height: 4px;
  transform: translateY(-50%);
  border-radius: var(--kincy-radius-lg);
  background-color: var(--kincy-cyan);
}
.kincy-run-slider-handle {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 16px;
  height: 16px;
  padding: 0;
  border-radius: 50%;
  background-color: #fff;
  border: 2px solid var(--kincy-cyan);
  cursor: grab;
  touch-action: none;      /* sinon le drag tactile scrolle la page */
}
.kincy-run-slider-handle:active { cursor: grabbing; border-color: var(--kincy-blue); }
.kincy-run-slider-handle:focus-visible { outline: 2px solid var(--kincy-cyan); outline-offset: 2px; }
.kincy-run-slider-labels {
  display: flex;
  justify-content: space-between;
  margin-top: 4px;
  font-size: 0.75rem;
  color: var(--kincy-slate);
  font-variant-numeric: tabular-nums;
}
.kincy-run-slider-count { color: var(--kincy-neutral-dark); }

/* Zone rapport + overlay hx-indicator : le LOADER DE RAPPORT, élément graphique
 * de référence depuis le 2026-09-25 (docs/conventions/ui.md §4.12). Son balisage
 * ne s'écrit que dans templates/partials/_report_loader.html.
 *
 * ⚠️ La hauteur minimale n'est pas décorative. Au premier chargement, la zone
 * d'un écran sans graphe en coquille (« Suivi des projets ») est VIDE : 48 px de
 * haut, et le voile, cadré par `inset: 0`, écrasait le spinner en ovale sous son
 * libellé. Quatre pas de 2xl couvrent le retrait haut, le spinner, l'espace et le
 * libellé, avec la même marge basse. */
.kincy-run-report {
  position: relative;
  min-height: calc(var(--kincy-spacing-2xl) * 4);
}
.kincy-run-spinner {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 5;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: var(--kincy-spacing-md);
  padding-top: var(--kincy-spacing-2xl);
  background-color: rgba(255, 255, 255, 0.7);
  border-radius: var(--kincy-radius-md);
}
.kincy-run-spinner.htmx-request { display: flex; }
.kincy-run-spinner-label { color: var(--kincy-slate); font-weight: 500; }
/* Le spinner garde son cercle quelle que soit la hauteur de la zone : un enfant
 * de flex rétrécit par défaut, et c'est lui qui cédait en premier. */
.kincy-run-spinner > .spinner-border { flex-shrink: 0; }

/* L'ÉTAT D'ERREUR d'un écran de rapport (docs/conventions/ui.md §4.13) : posé par
 * app.js dans la zone du formulaire `data-report-form` dont la requête a échoué.
 * ⚠️ Il MASQUE le contenu de la zone au lieu de s'afficher à côté : après un
 * changement de filtre raté, ce contenu porte les chiffres d'AVANT sous les
 * filtres neufs. Le loader reste visible, pour qu'un « Réessayer » se voie partir.
 * Grammaire de la tuile de tête (§4.11) : le filet gauche porte la couleur, le
 * texte reste en encre courante. */
.kincy-run-report--error > :not(.kincy-report-error):not(.kincy-run-spinner) { display: none; }
.kincy-report-error {
  margin-top: var(--kincy-spacing-lg);
  padding: var(--kincy-spacing-md) var(--kincy-spacing-lg);
  background-color: var(--bs-body-bg);
  border: 1px solid var(--kincy-neutral-medium);
  border-left: 5px solid var(--kincy-red);
  border-radius: var(--kincy-radius-md);
}
.kincy-report-error-title { margin-bottom: var(--kincy-spacing-xs); font-weight: 600; }
.kincy-report-error-message { margin-bottom: var(--kincy-spacing-md); color: var(--kincy-slate); }

/* Conteneur global des toasts (base.html). Au-dessus des barres collantes et des
 * offcanvas, comme le style en ligne qu'il remplace depuis le 2026-09-28. */
.kincy-toast-container { z-index: 1080; }
/* Le toast d'échec parle la même couleur que le filet du bloc d'erreur : le rouge
 * Bootstrap (#dc3545) n'est pas surchargé dans Link, et deux rouges pour un même
 * échec se liraient comme deux signaux. Blanc sur `--kincy-red` : 6,54:1, contre
 * 4,53:1 sur le rouge Bootstrap. `!important` parce que `.text-bg-danger` en
 * porte un. Borné au conteneur : aucune autre surface `danger` ne bouge. */
.kincy-toast-container .text-bg-danger { background-color: var(--kincy-red) !important; }

/* Graphes : hauteur fixe (Chart.js non proportionnel), précédent .kincy-ms-chart-wrap.
 * Partagé avec le graphe de la page détail Facturation (2026-08-28). */
.kincy-run-chart-wrap { position: relative; height: 300px; }
.kincy-run-chart-wrap--ratio { height: 260px; }

/* ⚠️ `.kincy-run-card-title` (slate, 600) a été RETIRÉE le 2026-09-17 : ses deux
   seules pages — « Rentabilité des projets » et « Rapport rentabilité » — sont
   des écrans de reporting, donc du périmètre de la convention d'intertitre
   `.kincy-report-h2`. Deux classes pour un même rôle, c'est la porte ouverte à
   deux grammaires de titre selon la page qu'on ouvre. Le `.h6` de Bootstrap est
   parti avec elle : la taille vient désormais de la convention. */
.kincy-run-card-meta { font-size: 0.8125rem; font-weight: 400; color: var(--kincy-neutral-dark); }

/* Tableaux : entête slate/cyan signature du proto, portée en classe. */
.kincy-run-table thead th {
  background-color: var(--kincy-slate);
  color: #fff;
  font-weight: 500;
  border-bottom: 2px solid var(--kincy-cyan);
  white-space: nowrap;
}
.kincy-run-table tfoot th,
.kincy-run-table tfoot td {
  background-color: var(--kincy-neutral-light);
  color: var(--kincy-slate);
  font-weight: 700;
}
.kincy-run-num { font-variant-numeric: tabular-nums; }
.kincy-run-num--total { font-weight: 700; color: var(--kincy-slate); }
.kincy-run-num--kincy { color: var(--kincy-blue); }
.kincy-run-colgrp { border-left: 2px solid var(--kincy-neutral-light); }

.kincy-run-note { margin-top: var(--kincy-spacing-md); font-size: 0.75rem; color: var(--kincy-neutral-dark); }
.kincy-run-note b { color: var(--kincy-slate); }

/* Teinte d'année de l'entête de ligne : progression slate -> blue -> cyan.
 * La base est un fallback : l'axe des mois court jusqu'au mois courant sans
 * borne haute, donc une année non mappée (2027+) reste teintée au lieu de
 * perdre fond et liseré. Les classes d'année ne surchargent que l'accent. */
.kincy-run-year {
  white-space: nowrap;
  background-color: var(--kincy-neutral-light);
  border-left: 4px solid var(--kincy-cyan);
}
.kincy-run-year--2024 { border-left-color: var(--kincy-slate); }
.kincy-run-year--2025 { border-left-color: var(--kincy-blue); }
.kincy-run-year--2026 { background-color: rgba(0, 173, 206, 0.08); }


/* --- Bandeau « Boîte à outils » de la fiche client ------------------------ */

/* Bande horizontale de tuiles d'outils, au-dessus des grandes cartes. Générique
 * dès l'origine : elle accueillera d'autres tuiles que « Pare-feux ». */
.kincy-tool-band {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kincy-spacing-md);
}

/* Tuile d'outil : décline .kincy-kpi-tile (même empilement chiffre + libellé,
 * mêmes variantes d'état) en format carré de bandeau. L'état « zéro » réutilise
 * tel quel .kincy-kpi-tile--zero ; l'état actif est cyan (marque, navigation) et
 * non orange, qui porterait à tort le signal « à surveiller ». */
.kincy-tool-tile {
  width: 128px;
  height: 128px;
  padding: var(--kincy-spacing-sm);
}

.kincy-tool-tile--filled {
  background-color: rgba(0, 173, 206, 0.1); /* --kincy-cyan à 10 % */
  color: var(--kincy-cyan);
}

.kincy-tool-tile--filled:hover {
  background-color: rgba(0, 173, 206, 0.18);
}


/* --- Pare-feux WatchGuard : management + page utilisateur ------------------ */

/* Capsule d'avertissement : même grammaire que .kincy-chip-ok, sémantique
 * inverse (« à corriger », pas « ok »). Porte l'alerte « compte en écriture »
 * de la page Firewalls WG — un garde-fou de sécurité, pas une décoration. */
.kincy-chip-warn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background-color: var(--kincy-orange);
  color: #fff;
  border-radius: var(--kincy-radius-lg);
  padding: 2px var(--kincy-spacing-sm);
  font-size: 0.8125rem;
  font-weight: 600;
  white-space: nowrap;
}

/* Troisième état de la pastille de statut : injoignable. Orange charte — le
 * design system n'a pas de rouge, l'orange porte le signal fonctionnel. */
.kincy-status-dot--alert {
  background-color: var(--kincy-orange);
}

/* Second étage d'une cellule du parc : l'hôte sous le nom du pare-feu, la date
 * sous le mot de passe masqué. Une seule règle pour les deux — c'est le même
 * rôle (information secondaire rattachée à la ligne du dessus), et deux classes
 * auraient divergé. Le `code` interne garde sa graisse mais suit la taille. */
.kincy-fw-sub {
  font-size: 0.8125rem;
  color: var(--kincy-neutral-dark);
  line-height: 1.3;
}

.kincy-fw-sub code {
  font-size: inherit;
  color: inherit;
}

/* Nom du client tronqué : le `truncatechars` du gabarit coupe le TEXTE, cette
 * règle empêche la cellule de s'étirer si la troncature venait à être relevée.
 * Les deux se tiennent — le gabarit garantit la bulle, le CSS la mise en page. */
.kincy-fw-client {
  display: inline-block;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}

/* Picto « ouvrir la fiche client ». Discret au repos, cyan au survol : c'est un
 * raccourci, pas l'action principale de la ligne. */
.kincy-fw-client-link {
  display: inline-flex;
  align-items: center;
  margin-left: 4px;
  color: var(--kincy-slate);
  vertical-align: middle;
}

.kincy-fw-client-link:hover,
.kincy-fw-client-link:focus-visible {
  color: var(--kincy-cyan);
}

.kincy-fw-client-link svg {
  width: 14px;
  height: 14px;
}

/* Barre d'actions en pictos. Le libellé n'est plus rendu : il vit dans
 * `aria-label` (nom accessible) et dans `data-kincy-tip` (bulle visible).
 * ⚠️ Largeur fixe : sans elle les quatre boutons prennent la largeur de leur
 * seul picto et la barre danse d'une ligne à l'autre selon la présence du
 * bouton rouge. */
.kincy-fw-actions .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  padding-inline: 0;
}

.kincy-fw-actions .btn svg {
  width: 16px;
  height: 16px;
}

/* Le rouge de la suppression. Bootstrap compile `btn-outline-danger` sur son
 * propre rouge ; on le recale sur `--kincy-red`, le token déjà en service pour
 * les anomalies chiffrées des rapports. Seul bouton rouge de la barre : il est
 * le seul irréversible. */
.kincy-fw-actions .btn-outline-danger {
  --bs-btn-color: var(--kincy-red);
  --bs-btn-border-color: var(--kincy-red);
  --bs-btn-hover-bg: var(--kincy-red);
  --bs-btn-hover-border-color: var(--kincy-red);
  --bs-btn-active-bg: var(--kincy-red);
  --bs-btn-active-border-color: var(--kincy-red);
}

/* Liste courte scrollable (clients porteurs d'un pare-feu, pare-feux d'un
 * client). Même recette que .kincy-run-agent-scroll. */
.kincy-fw-list {
  max-height: 320px;
  overflow-y: auto;
}

/* Bootstrap fige l'état actif d'une list-group sur son bleu compilé
 * (--bs-list-group-active-bg), hors de portée de l'override --bs-primary posé
 * sur :root. On le recale sur le cyan charte. */
.list-group {
  --bs-list-group-active-bg: var(--kincy-cyan);
  --bs-list-group-active-border-color: var(--kincy-cyan);
}

/* Indicateur de chargement en flux : masqué au repos, affiché le temps de la
 * requête HTMX. Même contrat que .kincy-run-spinner, sans l'overlay. */
.kincy-inline-spinner {
  display: none;
  justify-content: center;
  padding: var(--kincy-spacing-lg) 0;
}

.kincy-inline-spinner.htmx-request {
  display: flex;
}

/* Cadre « Vérifier une IP » : encart proéminent en tête des résultats. Décliné
 * de .kincy-client-header (surface neutral-light, radius-md) avec un liseré
 * cyan qui l'isole du reste de la page. */
.kincy-ip-check {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-md);
  background-color: var(--kincy-neutral-light);
  border-left: 4px solid var(--kincy-cyan);
  border-radius: var(--kincy-radius-md);
  padding: var(--kincy-spacing-md) var(--kincy-spacing-lg);
}

.kincy-ip-check-input {
  max-width: 16rem;
}

.kincy-ip-verdict {
  font-weight: 600;
  color: var(--kincy-slate);
}

.kincy-ip-verdict--blocked {
  color: var(--kincy-orange);
}

.kincy-ip-verdict--clear {
  color: var(--kincy-green);
}

/* Entête de colonne triable : bouton nu dans le <th>, même recette que
 * .kincy-filter-reset, avec un chevron d'état. */
.kincy-sort-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: none;
  background: none;
  padding: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.kincy-sort-btn:hover {
  color: var(--kincy-cyan);
}

.kincy-sort-caret {
  font-size: 0.7em;
  color: var(--kincy-cyan);
}

/* Colonne non triée : le chevron reste visible (affordance) mais s'efface. */
.kincy-sort-caret--idle {
  color: var(--kincy-neutral-medium);
}

/* --- Page détail « Facturation » (drill-down pièce par pièce) -------------
 *
 * Réutilise la barre de pilotage sticky, le panneau ancré et le slider double
 * poignée de la zone RUN (`.kincy-run-*`), ainsi que `.kincy-chip` pour les
 * entités et `.kincy-billing-amount` pour les montants : seules la pastille de
 * règlement, les entêtes triables et la barre d'outils du tableau appellent un
 * complément. */

/* Marge propre au graphe, la hauteur venant de .kincy-run-chart-wrap. */
.kincy-billing-chart-wrap {
  margin: var(--kincy-spacing-md) 0 var(--kincy-spacing-lg);
}

/* Entête du tableau figée sous la barre sticky. Deux lignes, donc deux
 * offsets mesurés en JS : la ligne d'outils (recherche + toggle) se cale sous
 * la barre, la ligne de colonnes sous la ligne d'outils. Les redériver en CSS
 * supposerait des hauteurs fixes, que la moindre retouche ferait mentir. */
.kincy-billing-table thead th {
  position: sticky;
  background-color: var(--kincy-neutral-light);
  z-index: 10;
  vertical-align: middle;
}

.kincy-billing-tools th {
  top: var(--kincy-billing-sticky-h, 0);
  z-index: 11;
}

.kincy-billing-head th {
  top: var(--kincy-billing-head-h, 0);
}

.kincy-billing-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-md);
}

.kincy-billing-search {
  max-width: 26rem;
}

.kincy-billing-count {
  margin-left: auto;
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--kincy-neutral-dark);
  font-variant-numeric: tabular-nums;
}

/* Entête triable : un vrai bouton (clavier, lecteurs d'écran), sans habillage —
 * il doit se lire comme le titre de colonne qu'il est. */
.kincy-billing-sort {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: none;
  background: none;
  padding: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.kincy-billing-sort:hover {
  color: var(--kincy-cyan);
}

.kincy-billing-sort-mark {
  font-size: 0.625rem;
  color: var(--kincy-cyan);
}

/* Pastille de règlement : quatre états, tous neutres. Seul le retard est
 * orange — c'est le seul signal « à surveiller » de la colonne, et le design
 * system n'a pas de rouge. */
.kincy-billing-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  font-size: 0.8125rem;
  color: var(--kincy-slate);
}

.kincy-billing-status-mark {
  font-size: 0.9rem;
  line-height: 1;
}

.kincy-billing-status--in-payment,
.kincy-billing-status--partial {
  color: var(--kincy-neutral-dark);
}

.kincy-billing-status--unsettled {
  color: var(--kincy-slate);
  font-weight: 600;
}

/* Surcharge de retard, posée par-dessus l'état de règlement. */
.kincy-billing-overdue {
  display: inline-block;
  margin-left: var(--kincy-spacing-sm);
  border-radius: var(--kincy-radius-lg);
  padding: 0 var(--kincy-spacing-sm);
  background-color: rgba(240, 131, 0, 0.12); /* --kincy-orange à 12 % */
  color: var(--kincy-orange);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
}

/* --- Facturation : nature des pièces (abonnement / matériel et projets) ---
 * Deux états seulement : une pièce mixte est rangée « abonnement » côté
 * service (arbitrage du 2026-08-28). Aucune valeur magique : la pastille
 * reprend les tokens de la charte, comme les aires du graphe. */
.kincy-billing-nature {
  display: inline-flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  white-space: nowrap;
}

.kincy-billing-nature-dot {
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 50%;
  flex: 0 0 auto;
  background-color: var(--kincy-neutral-dark);
}

.kincy-billing-nature--sub {
  background-color: var(--kincy-cyan);
}

.kincy-billing-nature--oneoff {
  background-color: var(--kincy-slate);
}

.kincy-billing-nature-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
}

/* Le numéro de pièce ouvre Odoo dans un nouvel onglet. Souligné seulement au
 * survol : une colonne entière de liens soulignés alourdit la lecture du
 * tableau. */
.kincy-billing-link {
  color: var(--kincy-cyan);
  text-decoration: none;
}

.kincy-billing-link:hover,
.kincy-billing-link:focus-visible {
  text-decoration: underline;
}

/* Chip de nature active : le fond passe au cyan, or la pastille « Abonnement »
 * EST cyan — elle disparaissait dessus. On la repeint en blanc quand la chip
 * est active : la couleur n'a plus rien à distinguer à ce moment-là, puisque la
 * sélection est déjà portée par le fond, et le libellé reste lisible. */
.kincy-chip--active .kincy-billing-nature-dot {
  background-color: var(--bs-body-bg);
}

/* Ligne secondaire atténuée d'une tuile KPI : elle porte une grandeur de
 * contexte sous la valeur principale, sans lui disputer la lecture. */
.kincy-kpi-tile-note--muted {
  color: var(--kincy-neutral-dark);
  font-weight: 400;
}

/* Message d'erreur brut d'une ligne de recalcul de facturation. Le corps d'une
 * réponse Halo est long et sans retours à la ligne : sans ces deux règles, il
 * déborde du tableau et fait défiler la page horizontalement. La couleur reste
 * celle du corps de texte — l'échec est déjà porté par le badge de la ligne. */
.kincy-recalc-error-body {
  white-space: pre-wrap;
  word-break: break-word;
}

/* ---------------------------------------------------------------------------
 * Zone BUILD — Suivi des projets
 *
 * Aucune classe structurelle nouvelle : la barre, le tableau et le spinner
 * réutilisent `.kincy-run-*` et `.kincy-filter-*`. Rappel du commentaire plus
 * haut : le préfixe `kincy-run-` n'est PLUS un périmètre — toute retouche à
 * `.kincy-run-filterbar` touche RUN, Facturation détail ET cette page.
 *
 * `--kincy-orange` n'apparaît nulle part ici : c'est le signal « à surveiller »
 * de tout Link, et cette page n'a encore aucune anomalie à signaler (spec §4.4).
 * Le dépenser sur un badge le dévaluerait avant qu'il ait servi. C'est aussi
 * pourquoi `.kincy-run-badge--empty`, qui le porte, n'est pas réutilisé.
 *
 * Corrigé le 2026-09-04 : le paragraphe ci-dessus est FAUX depuis le
 * 2026-09-03 — l'orange est dépensé sur cette page par `.kincy-chip--warning`
 * (badge « Projet à commander », plus haut dans ce fichier ; spec BUILD §8.7,
 * encadré daté). Ce qui reste vrai : `.kincy-run-badge--empty` n'est toujours
 * pas réutilisé, et l'orange n'y porte qu'un seul sens (projet travaillé sans
 * commande) en plus du chronomètre « à surveiller ». Relevé lors de la revue
 * de la spec « Rentabilité des projets ».
 * --------------------------------------------------------------------------- */

/* Couleur stable par type de projet (spec §8.5). Le sélecteur porte l'ID Halo,
 * seul endroit du dépôt où c'est le cas — assumé : un type renuméroté retombe
 * sur le `.kincy-chip` neutre, ce qui est une dégradation visuelle et jamais
 * une donnée fausse. */
.kincy-run-table [data-type="33"] .kincy-chip {
  border-color: var(--kincy-blue);
  color: var(--kincy-blue);
}

.kincy-run-table [data-type="32"] .kincy-chip {
  border-color: var(--kincy-purple);
  color: var(--kincy-purple);
}

.kincy-run-table [data-type="62"] .kincy-chip {
  border-color: var(--kincy-green);
  color: var(--kincy-green);
}

/* Le type 51 (« Projet en attente de commande ») n'est PLUS colorié ici :
 * il porte `.kincy-chip--warning`, posé par le serveur d'après le NOM du type
 * (2026-09-03). Un sélecteur de moins indexé sur un identifiant Halo. */

/* Un badge de tableau n'est pas cliquable : il informe. Le curseur de
 * `.kincy-chip` (pointer) mentirait.
 *
 * `nowrap` : une capsule ne se casse pas sur deux lignes — même règle que
 * `.kincy-chip-ok`. Devenu nécessaire le 2026-09-03, quand le tableau est passé
 * de six à huit colonnes : « Projet à commander » se coupait après « Projet à »
 * et le contour de la capsule se dédoublait, ce qui se lit comme deux badges. */
.kincy-run-table .kincy-chip {
  cursor: default;
  white-space: nowrap;
}

/* Logo d'outil externe, cliquable — colonnes « Halo » et « Odoo » (spec §8.5).
 * UNE classe pour les deux : les deux logos se comportent à l'identique, et
 * `.kincy-build-halo-logo` s'est trouvée fausse dès que le second est arrivé
 * (renommée le 2026-09-03).
 *
 * Les fichiers servis sont `img/halo.png` et `img/odoo.png`, copies 64 px
 * (2,6 et 1,9 Ko) des références `*_large` (900 et 640 px, 35 et 27,5 Ko) — voir
 * la convention des logos dans CLAUDE.md. La contrainte de taille reste portée
 * ici ET par les attributs `width`/`height` du gabarit, qui réservent la place
 * avant le décodage : sur 6 103 lignes, l'absence de dimensions intrinsèques
 * ferait sauter la mise en page pendant le chargement.
 *
 * `--off` : instance Halo non configurée. Le logo reste rendu, atténué et sans
 * lien — un emplacement vide se lirait comme un bug de gabarit. */
.kincy-build-logo {
  width: 20px;
  height: 20px;
  object-fit: contain;
  border-radius: 2px;
  vertical-align: middle;
  /* GRISÉ AU REPOS, coloré au survol (demandé le 2026-09-03). Le logo Halo est
   * un pictogramme rouge : répété sur 6 103 lignes, il faisait du rouge la
   * couleur la plus banale de la page, juste au moment où la pastille de
   * dépassement en fait un signal. Une couleur d'alerte ne survit pas à sa
   * banalisation. */
  filter: grayscale(1);
  opacity: 0.55;
  transition: filter 0.12s ease, opacity 0.12s ease;
}

.kincy-halo-link:hover .kincy-build-logo,
.kincy-halo-link:focus-visible .kincy-build-logo {
  filter: none;
  opacity: 1;
}

/* Instance non configurée : plus pâle encore, et SANS retour au survol — il
 * n'y a rien à ouvrir, donc rien ne doit inviter au clic. À ne pas confondre
 * avec l'ABSENCE de logo dans la colonne Odoo, qui dit « ce projet n'a aucune
 * commande » (44 % des lignes) : deux causes, deux rendus. */
.kincy-build-logo--off {
  opacity: 0.3;
}

/* Un lien de tableau ne doit pas voler la largeur de sa colonne : la cellule
 * « Halo » est la plus étroite du tableau. */
.kincy-run-table .kincy-halo-link {
  display: inline-block;
  line-height: 1;
}

/* Pastille de la colonne « Heures théoriques » (spec §8.5).
 *
 * Le CERCLAGE slate n'est pas décoratif : les deux fonds pleins de la charte
 * échouent au 3:1 de WCAG 1.4.11 sur le blanc du tableau (`--kincy-green` à
 * 2,54:1, `--kincy-orange` à 2,64:1, mesuré le 2026-09-03). Le contour, lui,
 * porte 7,7:1 — c'est donc lui qui rend la forme perceptible, la couleur ne
 * faisant que la qualifier. Même parade que le contour du glyphe de l'étoile
 * des favoris.
 *
 * Trois canaux portent l'information, jamais la couleur seule : la présence de
 * la pastille, son infobulle, et un texte pour lecteur d'écran. Les deux
 * nombres comparés sont d'ailleurs affichés côte à côte dans la ligne. */
.kincy-build-hours-dot {
  display: inline-block;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 1px solid var(--kincy-slate);
  vertical-align: baseline;
}

/* Créneau de la pastille, à DROITE du nombre (déplacée le 2026-09-03 depuis les
 * heures théoriques, où elle était en préfixe). Sa largeur est FIXE et il est
 * rendu même vide : dans une colonne alignée à droite, une pastille qui
 * n'existe que sur certaines lignes décalerait leurs chiffres et la colonne
 * cesserait d'être comparable d'une ligne à l'autre. C'est la même raison qui
 * garde le chevron de tri dans le flux. */
.kincy-build-dot-slot {
  display: inline-block;
  width: 17px;
  text-align: right;
}

.kincy-build-hours-dot--ok {
  background-color: var(--kincy-green);
}

.kincy-build-hours-dot--over {
  background-color: var(--kincy-red);
}

/* Entête de colonne triable (2026-09-03). Un <button> et non un <th> cliquable :
 * une cellule d'entête n'est pas focalisable, donc le tri serait inatteignable
 * au clavier. Le bouton hérite la couleur du <th> (blanc sur slate) et n'a ni
 * fond ni bordure : c'est l'entête qui reste l'objet visible.
 *
 * ⚠️ `.kincy-run-table thead th` est PARTAGÉ par trois pages (RUN, Facturation
 * détail, BUILD) : tout ce qui suit est donc préfixé `kincy-build-`, et rien
 * n'est posé sur le sélecteur partagé.
 *
 * *Amendé le 2026-09-18 : le préfixe `kincy-build-` n'est plus un périmètre.*
 * L'annuaire RH réutilise ces classes telles quelles pour sa colonne
 * « Personne » — le dépôt portait déjà DEUX rédactions d'un entête triable
 * (celle-ci et `.kincy-billing-sort`), une troisième aurait été la faute. Toute
 * retouche ici touche donc « Suivi des projets » ET l'annuaire RH. Le bouton
 * hérite la couleur de son `<th>`, ce qui le rend lisible aussi bien sur le
 * slate de `.kincy-run-table` que sur le `table-light` de l'annuaire. */
.kincy-build-sort {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: none;
  background: none;
  padding: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.kincy-build-sort:hover .kincy-build-sort-caret,
.kincy-build-sort:focus-visible .kincy-build-sort-caret {
  opacity: 0.7;
}

/* Le chevron existe TOUJOURS dans le flux, à opacité nulle quand la colonne
 * n'est pas triée : le faire apparaître décalerait le libellé au clic, et un
 * entête qui bouge sous le curseur fait rater la colonne suivante. */
.kincy-build-sort-caret {
  opacity: 0;
  transition: opacity 0.12s ease;
}

.kincy-build-sort-caret::before {
  content: "▼";
  font-size: 0.625rem;
  line-height: 1;
}

.kincy-build-sorted .kincy-build-sort-caret {
  opacity: 1;
}

.kincy-build-sorted--asc .kincy-build-sort-caret::before {
  content: "▲";
}

/* Champ de recherche de « Suivi des projets » : borné à 50 caractères et posé
 * à DROITE des badges (demandé le 2026-09-03) — pleine largeur, il séparait les
 * boutons de portée de leurs propres badges.
 *
 * Classe PROPRE et non une retouche de `.kincy-filter-search`, qui est partagée
 * avec l'annuaire : y poser une largeur redimensionnerait un champ qu'on n'a pas
 * regardé.
 *
 * `flex: 1 1 200px` + `max-width: 50ch`, et l'ordre de ces deux valeurs n'est
 * pas indifférent — mesuré au navigateur le 2026-09-03. Un `flex: 0 1 50ch`
 * (base 50 caractères, rétrécissable) renvoyait le champ SEUL à la ligne
 * suivante : dans un conteneur `flex-wrap`, la coupure de ligne se décide sur la
 * taille HYPOTHÉTIQUE des éléments, avant tout rétrécissement — un élément qui
 * ne rentre pas passe à la ligne au lieu de se resserrer. Une petite base qui
 * GRANDIT jusqu'à un plafond tient donc la ligne, là où une grande base qui
 * rétrécit la casse. */
.kincy-build-search {
  /* Base ramenée à 150 px le 2026-09-03, quand les trois chips d'activité sont
   * entrées dans la barre : à 200 px, le champ repartait à la ligne sur un
   * écran de 1 440 px, alors qu'il doit rester à DROITE des chips. Il grandit
   * de toute façon jusqu'à son plafond dès qu'il y a la place. */
  flex: 1 1 150px;
  max-width: 50ch;
}

/* Barre d'avancement en FOND de la cellule « Durée écoulée » (2026-09-03).
 *
 * Pourquoi en fond et non dans une colonne à elle : la donnée
 * (`halo_tickets.project_progression`) n'existe que sur 330 des 6 105 projets,
 * et 18 des 243 actifs — une colonne dédiée serait vide 19 fois sur 20. Le fond
 * ne coûte aucune largeur et disparaît quand il n'y a rien à montrer.
 *
 * Le remplissage est un ÉLÉMENT À PART, pour que son `opacity` ne touche pas le
 * texte : `--kincy-green` à 45 % laisse les chiffres slate à 5,18:1 sur le fond
 * obtenu (mesuré le 2026-09-03 ; 4,5:1 est le seuil du texte courant). Un vert
 * plein les aurait fait tomber à 2,9:1 — la barre aurait mangé la colonne
 * qu'elle décore.
 *
 * `position: relative` sur la cellule et `inset` sur la piste : la barre suit la
 * hauteur de la ligne, y compris quand un nom de projet passe sur deux lignes. */
.kincy-build-elapsed {
  position: relative;
}

.kincy-build-progress {
  position: absolute;
  inset: 4px 4px;
  /* Contour à 0,4 et fond de piste à 0,05 : ce sont les BORNES demandées, et
   * elles doivent se lire. Une première version à 0,25 sans fond de piste ne
   * montrait que le remplissage (contrôlé à la capture le 2026-09-03) — or un
   * remplissage seul ne dit pas de quoi il est la fraction. Le fond de piste
   * fait exister les 30 % restants. */
  border: 1px solid rgba(68, 84, 106, 0.4);
  background-color: rgba(68, 84, 106, 0.05);
  border-radius: 2px;
  overflow: hidden;
  pointer-events: none;
}

.kincy-build-progress-fill {
  display: block;
  height: 100%;
  background-color: var(--kincy-green);
  opacity: 0.45;
}

/* Le texte repasse AU-DESSUS de la piste, qui est en position absolue.
 *
 * Les 3 px de retrait valent pour TOUTES les lignes, avec barre ou sans : c'est
 * ce qui garde les chiffres alignés d'une ligne à l'autre tout en les empêchant
 * de toucher le contour de la piste. Les décaler seulement sur les lignes à
 * barre casserait l'alignement de la colonne. */
.kincy-build-elapsed-value {
  position: relative;
  padding-right: 3px;
}

/* Chronomètre de FRAÎCHEUR — colonne sans titre, à droite de « Durée écoulée »
 * (2026-09-03). Trois états sur les projets ACTIFS seulement.
 *
 * MASQUE CSS et non <svg> en ligne : le tracé du chronomètre pèse ~600 octets,
 * que la portée « Tous » répéterait jusqu'à 6 103 fois — un demi-mégaoctet de
 * balisage pour un pictogramme. Le masque le met UNE fois dans la feuille de
 * style, et `background-color` en fait la couleur de l'état.
 *
 * Les trois couleurs disent la même chose que partout ailleurs dans Link : gris
 * = rien à signaler, `--kincy-orange` = à surveiller, `--kincy-red` = anomalie.
 * C'est une échelle, pas trois décorations — et c'est pourquoi le rouge de la
 * pastille de dépassement et celui d'un projet dormant sont le MÊME token. */
.kincy-build-fresh {
  display: inline-block;
  width: 16px;
  height: 16px;
  vertical-align: text-bottom;
  background-color: var(--kincy-neutral-dark);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5V5.6z'/%3E%3Cpath d='M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64a.715.715 0 0 1 .012-.013l.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354a.512.512 0 0 1-.013.012A7 7 0 1 1 7 2.071V1.5A.5.5 0 0 1 6.5 1zm1 1.5v.57a.5.5 0 0 1-.42.494A6 6 0 1 0 14 9a6 6 0 0 0-5.08-5.936.5.5 0 0 1-.42-.494V2.5h-1z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5V5.6z'/%3E%3Cpath d='M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64a.715.715 0 0 1 .012-.013l.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354a.512.512 0 0 1-.013.012A7 7 0 1 1 7 2.071V1.5A.5.5 0 0 1 6.5 1zm1 1.5v.57a.5.5 0 0 1-.42.494A6 6 0 1 0 14 9a6 6 0 0 0-5.08-5.936.5.5 0 0 1-.42-.494V2.5h-1z'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* Frais : VERT CLAIR, atténué (demandé le 2026-09-03 ; c'était un gris estompé
 * jusque-là). L'atténuation reste le signal — un projet suivi n'a pas besoin
 * d'attirer l'oeil, et 43 des 243 projets actifs sont dans ce cas.
 *
 * 0,75 n'est pas choisi au jugé : le vert y pèse 1,98:1 sur blanc, contre
 * 2,07:1 pour le gris estompé qu'il remplace. Le changement est donc une
 * TEINTE, pas une mise en avant — les trois états gardent leurs poids
 * relatifs. Et l'échelle y gagne en évidence : vert / orange / rouge se lit
 * sans légende là où le gris demandait de l'apprendre. */
.kincy-build-fresh--fresh {
  background-color: var(--kincy-green);
  opacity: 0.75;
}

.kincy-build-fresh--watch {
  background-color: var(--kincy-orange);
}

.kincy-build-fresh--stale {
  background-color: var(--kincy-red);
}

/* Dans l'entête, sur fond slate : le pictogramme prend la couleur du texte. */
.kincy-build-fresh--head {
  background-color: currentColor;
}

/* Légende du chronomètre : une ligne, les trois états séparés, jamais coupée
 * au milieu d'un item. */
.kincy-build-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kincy-spacing-md);
  align-items: center;
}

.kincy-build-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}

.kincy-build-legend-note {
  color: var(--kincy-neutral-dark);
}

/* Bandeau d'indicateurs, au-dessus de la barre de filtres (2026-09-03).
 * Deux tuiles : le récapitulatif à gauche, l'actionnable à droite — convention
 * des zones KPI de tout Link, « l'œil cherche à droite ce sur quoi il peut
 * agir ». Vide tant que le premier fragment n'est pas arrivé (le shell ne lit
 * jamais le dataset), donc `:empty` ne doit pas laisser de marge fantôme. */
.kincy-build-tiles {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--kincy-spacing-md);
  margin-bottom: var(--kincy-spacing-md);
}

.kincy-build-tiles:empty {
  margin-bottom: 0;
}

.kincy-build-tile {
  flex: 1 1 320px;
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-md);
  padding: var(--kincy-spacing-sm) var(--kincy-spacing-md);
  background-color: #fff;
}

.kincy-build-tile-title {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--kincy-neutral-dark);
  margin-bottom: 6px;
}

/* Barre empilée : trois segments, dont les largeurs somment EXACTEMENT 100 %
 * (calculées côté Python par la méthode des restes). `overflow: hidden` sur la
 * piste pour que les coins arrondis coupent les segments. */
.kincy-build-spread {
  display: flex;
  height: 14px;
  border-radius: var(--kincy-radius-sm);
  overflow: hidden;
  background-color: var(--kincy-neutral-medium);
}

.kincy-build-spread-part {
  display: block;
  height: 100%;
}

/* Le segment « à jour » prend le vert PLEIN, là où le pictogramme l'atténue :
 * dans une barre, une opacité réduite se lirait comme un vide. La règle est
 * inchangée depuis le gris (2026-09-03), seule la teinte a suivi celle du
 * pictogramme. */
.kincy-build-spread-part--fresh {
  background-color: var(--kincy-green);
}

.kincy-build-spread-part--watch {
  background-color: var(--kincy-orange);
}

.kincy-build-spread-part--stale {
  background-color: var(--kincy-red);
}

.kincy-build-spread-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kincy-spacing-md);
  margin: 6px 0 0;
  font-size: 0.8125rem;
  color: var(--kincy-neutral-dark);
}

/* Tuile actionnable : le logo tient lieu de phrase. */
.kincy-build-tile--action {
  display: flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  flex: 0 1 auto;
  text-decoration: none;
  color: var(--kincy-slate);
  transition: border-color 0.12s ease;
}

.kincy-build-tile--action:hover {
  border-color: var(--kincy-cyan);
  color: var(--kincy-slate);
}

/* Grisé au repos, coloré au survol — même grammaire que les logos du tableau,
 * pour que le geste « ouvrir dans Halo » ait partout la même apparence. */
.kincy-build-tile-logo {
  width: 28px;
  height: 28px;
  object-fit: contain;
  filter: grayscale(1);
  opacity: 0.55;
  transition: filter 0.12s ease, opacity 0.12s ease;
}

.kincy-build-tile--action:hover .kincy-build-tile-logo,
.kincy-build-tile--action:focus-visible .kincy-build-tile-logo {
  filter: none;
  opacity: 1;
}

.kincy-build-tile-value {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
}

.kincy-build-tile-label {
  font-size: 0.8125rem;
  color: var(--kincy-neutral-dark);
}

/* Flèche de TENDANCE, à droite des heures théoriques (2026-09-03).
 *
 * Deux pictogrammes masqués, comme le chronomètre, et pour la même raison : un
 * <svg> en ligne pèserait ~500 octets par ligne. Les emoji 📈/📉 ont été
 * écartés — ils portent leurs propres couleurs, qu'on ne peut pas piloter, et
 * la couleur EST l'information ici.
 *
 * La colonne des heures théoriques porte donc la tendance, celle des heures
 * notées porte l'état déjà constaté. Une colonne, un sens : la pastille dit
 * « le vendu est dépassé », la flèche dit « au rythme déclaré, il le sera ».
 * Le demandeur a tranché en faveur des deux, contre une fusion. */
.kincy-build-trend {
  display: inline-block;
  width: 14px;
  height: 14px;
  vertical-align: middle;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.kincy-build-trend--ahead {
  background-color: var(--kincy-green);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M0 0h1v15h15v1H0V0Zm14.817 3.113a.5.5 0 0 1 .07.704l-4.5 5.5a.5.5 0 0 1-.74.037L7.06 6.767l-3.656 5.027a.5.5 0 0 1-.808-.588l4-5.5a.5.5 0 0 1 .758-.06l2.609 2.61 4.15-5.073a.5.5 0 0 1 .704-.07Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M0 0h1v15h15v1H0V0Zm14.817 3.113a.5.5 0 0 1 .07.704l-4.5 5.5a.5.5 0 0 1-.74.037L7.06 6.767l-3.656 5.027a.5.5 0 0 1-.808-.588l4-5.5a.5.5 0 0 1 .758-.06l2.609 2.61 4.15-5.073a.5.5 0 0 1 .704-.07Z'/%3E%3C/svg%3E");
}

.kincy-build-trend--behind {
  background-color: var(--kincy-red);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M0 0h1v15h15v1H0V0Zm14.817 11.887a.5.5 0 0 0 .07-.704l-4.5-5.5a.5.5 0 0 0-.74-.037L7.06 8.233 3.404 3.206a.5.5 0 0 0-.808.588l4 5.5a.5.5 0 0 0 .758.06l2.609-2.61 4.15 5.073a.5.5 0 0 0 .704.07Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M0 0h1v15h15v1H0V0Zm14.817 11.887a.5.5 0 0 0 .07-.704l-4.5-5.5a.5.5 0 0 0-.74-.037L7.06 8.233 3.404 3.206a.5.5 0 0 0-.808.588l4 5.5a.5.5 0 0 0 .758.06l2.609-2.61 4.15 5.073a.5.5 0 0 0 .704.07Z'/%3E%3C/svg%3E");
}

/* Tuile « reste à produire » : un chiffre, sa couverture, et le dépassement en
 * note. Le dépassement n'est JAMAIS déduit du chiffre principal. */
.kincy-build-tile-figure {
  display: flex;
  align-items: baseline;
  gap: var(--kincy-spacing-sm);
  margin: 0;
}

/* Le titre de la tuile « en attente de commande » porte le ⚠️ du badge de type
 * qu'elle résume : c'est le même signal, sur la même anomalie — du temps passé
 * sur ce qui n'est pas vendu. */
.kincy-build-tile-title {
  white-space: nowrap;
}

.kincy-build-tile-note {
  margin: 4px 0 0;
  font-size: 0.8125rem;
  color: var(--kincy-neutral-dark);
}

/* Chips de filtrage par activité. Elles portent la MÊME couleur que les
 * pastilles de ligne, et c'est leur raison d'être : la couleur fait le
 * rapprochement, donc elles tiennent lieu de légende. D'où le nom en toutes
 * lettres ici et l'initiale sur la ligne.
 *
 * Décochée : contour et texte de la couleur de l'activité. Cochée : aplat de la
 * même couleur, texte blanc — l'orange de `.kincy-chip--warning` a le même
 * mécanisme, et la spécificité de la double classe l'emporte sur le cyan de
 * `.kincy-chip--active`. */
.kincy-build-activity-chip--run {
  border-color: var(--kincy-blue);
  color: var(--kincy-blue);
}

.kincy-build-activity-chip--build {
  border-color: var(--kincy-magenta);
  color: var(--kincy-magenta);
}

.kincy-build-activity-chip--manage {
  border-color: var(--kincy-slate);
  color: var(--kincy-slate);
}

.kincy-build-activity-chip--run.kincy-chip--active,
.kincy-build-activity-chip--run.kincy-chip--active:hover,
.kincy-build-activity-chip--run:hover {
  background-color: var(--kincy-blue);
  border-color: var(--kincy-blue);
  color: #fff;
}

.kincy-build-activity-chip--build.kincy-chip--active,
.kincy-build-activity-chip--build.kincy-chip--active:hover,
.kincy-build-activity-chip--build:hover {
  background-color: var(--kincy-magenta);
  border-color: var(--kincy-magenta);
  color: #fff;
}

.kincy-build-activity-chip--manage.kincy-chip--active,
.kincy-build-activity-chip--manage.kincy-chip--active:hover,
.kincy-build-activity-chip--manage:hover {
  background-color: var(--kincy-slate);
  border-color: var(--kincy-slate);
  color: #fff;
}

/* Pastilles d'ACTIVITÉ (RUN / BUILD / MANAGE), colonne sans titre après
 * « Type » — 2026-09-03. Un disque par activité, l'initiale au centre.
 *
 * Les trois couleurs sont reprises À L'IDENTIQUE par les chips de filtrage :
 * c'est la couleur qui fait le rapprochement entre la ligne et le filtre, et
 * c'est pourquoi les chips remplacent la légende au lieu de s'y ajouter.
 *
 * Choix des teintes, et la première a été révisée : RUN est en `--kincy-blue`
 * et NON en cyan, qui est la couleur de l'état « sélectionné » de tout Link
 * (`.kincy-chip--active`) — une chip RUN cyan DÉCOCHÉE aurait eu l'air cochée,
 * ce qui est une confusion fonctionnelle et non cosmétique. Le bleu ne sert
 * qu'au badge de type « Presta. contrat », dans une autre colonne et sous une
 * autre forme. BUILD prend le magenta, que `design.md` réserve précisément aux
 * « catégories mises en avant ». MANAGE prend le slate, la couleur de structure.
 * Ni orange ni rouge : ce sont des activités, pas des alertes. */
/* La cellule ne se replie pas : trois activités sur deux lignes se lisent comme
 * deux groupes, et la colonne n'a pas de titre pour démentir. Constaté à la
 * capture le 2026-09-03 sur le ticket qui porte R, B et M. */
td[data-col="activities"] {
  white-space: nowrap;
}

.kincy-build-activity {
  display: inline-block;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 9px;
  margin-right: 3px;
  background-color: var(--kincy-neutral-dark);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  vertical-align: middle;
}

.kincy-build-activity--run {
  background-color: var(--kincy-blue);
}

.kincy-build-activity--build {
  background-color: var(--kincy-magenta);
}

.kincy-build-activity--manage {
  background-color: var(--kincy-slate);
}

/* Activité INCONNUE : nom en toutes lettres, donc capsule et non disque. Elle
 * garde le gris neutre — on ne lui invente pas une couleur de charte. */
.kincy-build-activity--wide {
  border-radius: 9px;
  letter-spacing: 0.02em;
}

.kincy-build-footer {
  border-top: 1px solid var(--kincy-neutral-medium);
  padding-top: var(--kincy-spacing-sm);
}

.kincy-build-reserves {
  list-style: none;
  padding-left: 0;
}

.kincy-build-reserves li + li {
  margin-top: 4px;
}

/* Deuxième ligne d'une tuile de la boîte à outils (« Y terminés »).
 *
 * La tuile reste 128 x 128 : c'est un standard, pas une coïncidence. La place
 * est donc comptée — les valeurs réelles montent à 101 en cours et 767
 * terminés, soit trois chiffres sur les deux lignes (mesuré le 2026-09-02),
 * d'où une taille réduite et un `line-height` serré plutôt qu'une tuile plus
 * haute qui désalignerait la bande entière. */
.kincy-tool-tile-sub {
  font-size: 0.75rem;
  line-height: 1.1;
  color: var(--kincy-neutral-dark);
  margin-top: 2px;
}

/* Pastille du filtre client de la page « Suivi des projets ». Réversible : la
 * croix retire le filtre. C'est un lien, pas un bouton, parce que le filtre est
 * SERVEUR — le retirer est une navigation, pas une mutation d'état client. */
.kincy-build-client-chip {
  text-decoration: none;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
 * Zone BUILD — Rentabilité des projets (2026-09-04)
 *
 * Peu de classes neuves : la barre de filtres, les cartes, le tableau et le
 * spinner réutilisent `.kincy-run-*` et `.kincy-filter-*`. Rappel du
 * commentaire plus haut : le préfixe `kincy-run-` n'est PLUS un périmètre —
 * toute retouche à `.kincy-run-filterbar` touche RUN, Facturation détail,
 * Suivi des projets ET cette page.
 *
 * Ce que cette section ajoute, et pourquoi :
 *   * les chips de DÉPARTEMENT, colorées, qui tiennent lieu de LÉGENDE au
 *     graphe empilé — d'où des couleurs identiques à celles des aires ;
 *   * la zone KPI à trois tuiles ;
 *   * `--kincy-red` sur le dépassement, PARTOUT où il est chiffré.
 *
 * Le rouge porte UN SEUL SENS ici — le vendu est dépassé — et il accompagne ce
 * fait sur les trois surfaces qui le chiffrent (tuile, colonne du tableau,
 * barres du graphe 2). La règle est « une couleur de signal, un sens par
 * page », pas « une seule occurrence » : un écart rouge en haut et le même en
 * noir dans le tableau serait l'inverse du piège qu'on veut éviter.
 *
 * L'orange n'a AUCUN usage sur cette page : le badge de groupe vide du rapport
 * RUN n'existe pas ici (grammaire « rien coché = tout passe »), et il n'y a
 * pas d'autre anomalie à signaler que le dépassement, qui est rouge.
 * --------------------------------------------------------------------------- */

.kincy-build-subtitle {
  margin-top: -0.25rem;
  margin-bottom: var(--kincy-spacing-md);
  font-size: 0.875rem;
}

/* --- Chips de département et d'agence -------------------------------------
 *
 * Ce sont des <label> associés à une case masquée (`.btn-check`), et non des
 * <button> pilotés par Alpine : le filtre est SERVEUR, donc il doit être un
 * champ du formulaire — c'est ce qui fait bulle son `change` jusqu'au
 * hx-trigger sans une ligne de JavaScript.
 *
 * ⚠️ **La pastille carrée a quitté les chips le 2026-09-09.** Elles portent
 * désormais leur couleur de rôle en PLEIN — contour et texte au repos, aplat
 * une fois cochées — c'est-à-dire le mécanisme exact des chips d'activité de
 * « Suivi des projets » (`.kincy-build-activity-chip--*`). Demandé pour garder
 * la cohérence entre les deux écrans de la zone, et parce qu'une pastille de
 * 0,6 rem dans une capsule de contour neutre disait la couleur du bout des
 * lèvres là où la chip EST la légende du graphe empilé.
 *
 * `.kincy-profit-swatch` garde la pastille : dans un TABLEAU il n'y a pas de
 * capsule à peindre, et elle y rappelle la couleur de l'aire ligne par ligne.
 * C'est un pseudo-contenu décoratif (`aria-hidden` côté gabarit), l'information
 * étant portée par le libellé en toutes lettres. */
.kincy-profit-swatch {
  content: "";
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  margin-right: 0.35rem;
  border-radius: 2px;
  vertical-align: baseline;
  background-color: var(--kincy-neutral-dark);
}

.kincy-profit-swatch--run {
  background-color: var(--kincy-blue);
}

.kincy-profit-swatch--build {
  background-color: var(--kincy-magenta);
}

.kincy-profit-swatch--manage {
  background-color: var(--kincy-slate);
}

/* --- Couleur de rôle des chips, en PLEIN (2026-09-09) ---------------------
 *
 * Décochée : contour et texte de la couleur du rôle. Cochée (ou survolée) :
 * aplat de la même couleur, texte blanc. Mécanisme identique à celui des chips
 * d'activité de « Suivi des projets », dont ces règles reprennent les trois
 * teintes à l'identique : ce sont les mêmes mots, ils doivent être de la même
 * couleur d'une page à l'autre.
 *
 * ⚠️ **Les deux niveaux de filtre PARTAGENT la couleur, jamais la classe de
 * base.** C'est la leçon du train 2, payée par une sonde qui lisait la mauvaise
 * chip : `.kincy-build-dept-chip` avait servi aux deux groupes, et son
 * sélecteur les attrapait tous les deux. Chaque modificateur est donc écrit
 * pour les deux classes de base, jamais factorisé sur une classe commune.
 *
 * ⚠️ Le rôle `--others` (Autre, Kaizen, Sans département, seau non renseigné)
 * prend `--kincy-neutral-dark`, la teinte que portait sa pastille. Sans lui,
 * ces chips retomberaient sur le cyan de `.kincy-chip--active`, et une chip
 * « Kaizen » cochée en cyan à côté d'une « Support (Run) » cochée en bleu
 * laisserait croire à deux natures de sélection. */
.kincy-build-dept-chip--run,
.kincy-build-presta-chip--run {
  border-color: var(--kincy-blue);
  color: var(--kincy-blue);
}

.kincy-build-dept-chip--build,
.kincy-build-presta-chip--build {
  border-color: var(--kincy-magenta);
  color: var(--kincy-magenta);
}

.kincy-build-dept-chip--manage,
.kincy-build-presta-chip--manage {
  border-color: var(--kincy-slate);
  color: var(--kincy-slate);
}

.kincy-build-dept-chip--others,
.kincy-build-presta-chip--others {
  border-color: var(--kincy-neutral-dark);
  color: var(--kincy-neutral-dark);
}

/* Les chips d'AGENCE : une seule teinte, celle de l'axe (voir `--kincy-teal`).
 * Elles n'ont donc PAS de modificateur par valeur, contrairement aux deux
 * groupes ci-dessus — c'est voulu, et c'est la différence de mécanisme :
 * ici la couleur répond à « de quel axe est cette chip ? », là à « quelle
 * valeur ? ». La sentinelle « Sans agence » la porte aussi : c'est une valeur
 * de l'axe agence, pas un vide générique. */
.kincy-build-agency-chip {
  border-color: var(--kincy-teal);
  color: var(--kincy-teal);
}

.kincy-build-agency-chip.kincy-chip--active,
.kincy-build-agency-chip.kincy-chip--active:hover,
.kincy-build-agency-chip:hover {
  background-color: var(--kincy-teal);
  border-color: var(--kincy-teal);
  color: #fff;
}

.kincy-build-dept-chip--run.kincy-chip--active,
.kincy-build-dept-chip--run.kincy-chip--active:hover,
.kincy-build-dept-chip--run:hover,
.kincy-build-presta-chip--run.kincy-chip--active,
.kincy-build-presta-chip--run.kincy-chip--active:hover,
.kincy-build-presta-chip--run:hover {
  background-color: var(--kincy-blue);
  border-color: var(--kincy-blue);
  color: #fff;
}

.kincy-build-dept-chip--build.kincy-chip--active,
.kincy-build-dept-chip--build.kincy-chip--active:hover,
.kincy-build-dept-chip--build:hover,
.kincy-build-presta-chip--build.kincy-chip--active,
.kincy-build-presta-chip--build.kincy-chip--active:hover,
.kincy-build-presta-chip--build:hover {
  background-color: var(--kincy-magenta);
  border-color: var(--kincy-magenta);
  color: #fff;
}

.kincy-build-dept-chip--manage.kincy-chip--active,
.kincy-build-dept-chip--manage.kincy-chip--active:hover,
.kincy-build-dept-chip--manage:hover,
.kincy-build-presta-chip--manage.kincy-chip--active,
.kincy-build-presta-chip--manage.kincy-chip--active:hover,
.kincy-build-presta-chip--manage:hover {
  background-color: var(--kincy-slate);
  border-color: var(--kincy-slate);
  color: #fff;
}

.kincy-build-dept-chip--others.kincy-chip--active,
.kincy-build-dept-chip--others.kincy-chip--active:hover,
.kincy-build-dept-chip--others:hover,
.kincy-build-presta-chip--others.kincy-chip--active,
.kincy-build-presta-chip--others.kincy-chip--active:hover,
.kincy-build-presta-chip--others:hover {
  background-color: var(--kincy-neutral-dark);
  border-color: var(--kincy-neutral-dark);
  color: #fff;
}


/* --- Barre de filtres « deux blocs » (2026-09-09) -------------------------
 *
 * Direction retenue au terme d'une recherche graphique à quatre planches. Le
 * principe tient en une phrase : **le cadre encadre un NIVEAU de filtre, jamais
 * un groupe**. La page en porte deux qui ne font pas la même chose, la COHORTE
 * retirant des projets quand la VENTILATION ne retire que des heures, et rien
 * ne le disait : les légendes nommaient les groupes. Les deux cadres portent
 * désormais cette séparation, et c'est leur unique raison d'être.
 *
 * ⚠️ Ces classes sont PROPRES à cette page, contrairement aux `.kincy-run-*`
 * qui servent RUN, Facturation détail et Suivi des projets. Le préfixe
 * `kincy-filter-bloc-*` marque en revanche ce qu'une prochaine zone de pilotage
 * peut reprendre tel quel. */

/* Alignement en HAUT : les colonnes de chips n'ont pas la même hauteur, et un
 * centrage ferait flotter les légendes à des altitudes différentes.
 *
 * Gouttière `md` et non `lg` entre les trois colonnes : 16 px rendus à la
 * rangée. Les trois blocs sont déjà séparés par un CADRE, donc la gouttière
 * n'a pas à porter la séparation en plus — elle la portait quand les groupes
 * n'avaient que leur légende. */
.kincy-profit-filterbar {
  align-items: flex-start;
  column-gap: var(--kincy-spacing-md);
  row-gap: var(--kincy-spacing-sm);
}

/* --- Le CADRAGE : première des trois colonnes de la barre -----------------
 *
 * Période au-dessus de portée, de même largeur (arbitrage A65), et la largeur
 * commune vient de la COLONNE — jamais d'une valeur posée sur chaque contrôle,
 * sinon le segmenté et le bouton de période divergeraient au premier libellé
 * qui change.
 *
 * ⚠️ **Il est passé une demi-heure au niveau du titre, le 2026-09-09, et c'est
 * une erreur qu'il ne faut pas refaire.** Elle paraissait gagnante — la rangée
 * récupérait 272 px de largeur et la barre perdait 106 px de hauteur — et elle
 * a coûté deux défauts que la mesure n'avait pas vus :
 *
 *   1. **La barre cessait de coller.** Mettre le cadrage à hauteur de titre
 *      oblige à sortir le titre du flux normal, donc à déplacer
 *      `position: sticky` sur un `<div>` INTÉRIEUR au formulaire (le
 *      formulaire, lui, doit rester un seul nœud englobant : tous ces champs
 *      sont sérialisés par HTMX). Or **un élément collant ne colle que dans la
 *      boîte de son parent** : le formulaire ne contenant que la tête et la
 *      barre, la barre se décollait dès qu'on quittait sa propre hauteur.
 *      Aucune erreur, aucun test rouge, et c'est l'utilisateur qui l'a vu.
 *   2. **Aligné à droite sur un écran large**, le cadrage devenait un bloc
 *      isolé avec du blanc dessous. `justify-content: space-between` tient sur
 *      1 440 px et se disloque à 1 920.
 *
 * La leçon générale : **ne pas imbriquer l'élément collant**, et se méfier
 * d'une mesure de HAUTEUR qui ne dit rien de la STICKINESS ni des largeurs
 * d'écran qu'on ne rend pas. */
.kincy-profit-frame {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  gap: var(--kincy-spacing-sm);
  /* 15 rem et non 17 : deux rem rendus à la rangée, dont la largeur est la
     contrainte de cette barre (voir `.kincy-filter-bloc-cols--stacked`). Le
     libellé le plus long du bouton de période, « oct. 2025 → sept. 2026 »,
     mesure ~200 px avec son chevron, et les trois segments de portée tiennent
     à 80 px chacun (« Inactifs 6 » en fait ~70). Descendre plus bas
     tronquerait le premier. */
  width: 15rem;
  min-width: 0;
}

.kincy-profit-frame-control {
  width: 100%;
  justify-content: space-between;
}

.kincy-profit-frame .kincy-profit-frame-control {
  /* 35 px : la hauteur INTRINSÈQUE du bouton de période (6+6 de rembourrage,
     21 de ligne, 2 de bordure), relevée par la sonde. 34 px laissaient le
     segmenté un pixel plus plat — imperceptible, mais c'est le genre d'écart
     qui grandit au premier changement de rembourrage. La sonde publie les deux
     hauteurs sur sa ligne `[MES]` : leur égalité se relit, elle ne se déduit
     pas. */
  min-height: 2.1875rem;
}

/* Sélecteur segmenté de portée : une capsule, trois cases jointives de largeur
 * égale. La portée est un choix UNIQUE parmi trois, et trois boutons détachés
 * disaient trois commandes indépendantes.
 *
 * Ce sont trois `<input type="radio">` réels suivis d'un `<label>` : le clavier
 * et le lecteur d'écran voient un groupe de radios, et l'état actif se lit en
 * CSS pur (`.btn-check:checked +`), sans une ligne de JavaScript. */
.kincy-profit-scope {
  display: inline-flex;
  align-items: stretch;
  gap: 2px;
  border: 1px solid rgba(68, 84, 106, 0.3);
  border-radius: var(--kincy-radius-lg);
  background-color: #fff;
  padding: 2px;
}

.kincy-profit-scope-btn {
  display: inline-flex;
  flex: 1 1 0;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  border-radius: var(--kincy-radius-lg);
  padding: 4px 0.5rem;
  color: var(--kincy-slate);
  font-size: 0.8125rem;
  line-height: 1.4;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.kincy-profit-scope-btn:hover {
  color: var(--kincy-cyan);
}

.btn-check:checked + .kincy-profit-scope-btn {
  background-color: var(--kincy-cyan);
  color: #fff;
}

/* La case est masquée par `.btn-check` : sans ce relais, le focus clavier
 * n'aurait AUCUN indicateur visible sur le segment sélectionné. */
.btn-check:focus-visible + .kincy-profit-scope-btn {
  outline: 2px solid var(--kincy-cyan);
  outline-offset: 2px;
}

/* Le compteur de portée n'arrive qu'avec le premier fragment (le shell ne lit
 * pas le dataset) : `:empty` évite une marge fantôme d'ici là. */
.kincy-profit-scope-btn span:not(:empty) {
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
}

/* Cadre de NIVEAU. C'est un `<fieldset>` bordé, donc sa `<legend>` interrompt
 * nativement le tracé : elle appartient visuellement au cadre qu'elle nomme, ce
 * qu'une étiquette posée au-dessus ne fait pas. */
.kincy-filter-bloc {
  min-width: 0;
  margin: 0;
  border: 1px solid rgba(68, 84, 106, 0.22);
  border-radius: var(--kincy-radius-md);
  /* 12 px de rembourrage latéral et non 16 : 24 px rendus à la rangée sur les
     trois cadres, pour un changement que personne ne voit. Mesuré le
     2026-09-09 : à 1 680 px de fenêtre la rangée disposait de 1 376 px et en
     demandait 1 379 — elle se repliait pour TROIS pixels, et la barre passait
     de 195 à 341 px. La marge est maintenant d'une vingtaine de pixels.
     ⚠️ La contrainte de cette barre est la LARGEUR, pas la hauteur : la sonde
     publie les deux (ligne `[MES]`), et la somme des trois cadres plus les
     gouttières doit tenir dans la largeur qu'elle annonce. */
  padding: 0 12px var(--kincy-spacing-sm);
}

.kincy-filter-bloc-legend {
  float: none; /* le défaut navigateur en fait un bloc de pleine largeur */
  width: auto;
  margin-bottom: 0.25rem;
  padding: 0 0.35rem;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--kincy-slate);
}

.kincy-filter-bloc-cols {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--kincy-spacing-lg);
}

/* Le bloc « Cohorte » empile ses deux groupes au lieu de les mettre côte à
 * côte, et c'est une décision de LARGEUR, mesurée (2026-09-09).
 *
 * Les pictos et les trigrammes ont fait passer le besoin de la rangée de
 * 1 334 à 1 568 px, donc le seuil de repliement de ~1 620 à **entre 1 860 et
 * 1 920 px** : la page tenait sur un écran de 1 920 avec une soixantaine de
 * pixels de marge, et un seul nom d'agence de plus la faisait basculer à
 * 300 px de barre. Ce n'est pas une marge sur laquelle on laisse une page.
 *
 * Empiler les deux interrupteurs sous les chips de prestation rend ~200 px de
 * largeur pour ~12 px de hauteur : le seuil retombe sous 1 700 px, donc un
 * écran de 1 680 tient de nouveau. C'était la sortie de secours identifiée au
 * tour précédent, et la mesure a dit quand la prendre.
 *
 * ⚠️ Ça ne change RIEN au sens : les deux interrupteurs restent dans le cadre
 * de la cohorte, parce qu'ils retirent des projets comme les chips au-dessus.
 * Empilés, ils se lisent même plutôt mieux — la colonne dit « et aussi ». */
.kincy-filter-bloc-cols--stacked {
  flex-direction: column;
  gap: var(--kincy-spacing-sm);
}

/* --- Picto de GROUPE dans la chip (2026-09-09) -----------------------------
 *
 * Un bonhomme pour « Produit par », un immeuble pour « Agence », un cube pour
 * « Type de prestation ». En PRÉFIXE et en **couleur de police**
 * (`currentColor`), donc il suit la couleur du rôle au repos et passe en blanc
 * quand la chip se remplit : c'est gratuit et ça ne dépense aucune teinte.
 *
 * ⚠️ Le picto est un canal de FORME, jamais d'information : le libellé porte
 * tout, d'où l'`aria-hidden` côté gabarit. Il dit « de quel axe est cette
 * chip », ce que la légende du groupe dit déjà dans la barre — sa vraie valeur
 * est là où une valeur apparaît SANS son axe (un segment, une légende de
 * graphe, une cellule de tableau).
 *
 * ⚠️ Un `<svg>` en ligne, et non un masque CSS. Le masque était OBLIGATOIRE
 * pour le chronomètre de fraîcheur de « Suivi des projets » — 600 octets de
 * tracé sur 6 103 lignes font un demi-mégaoctet de balisage — mais ici le
 * volume est de seize nœuds. Le tracé vit dans un seul gabarit
 * (`build/_chip_icon.html`), comme l'étoile des favoris. */
.kincy-chip-icon {
  flex: 0 0 auto;
  width: 0.875rem;
  height: 0.875rem;
  margin-right: 0.375rem;
  /* `currentColor` sur le tracé : c'est ce qui fait suivre la couleur du rôle
     sans une seule règle par groupe. */
  color: currentColor;
}

/* Le TRIGRAMME de l'agence, devant son nom. La chip ENSEIGNE le code que les
 * segments du croisement emploient seuls — un code qui n'apparaîtrait que dans
 * un graphe serait un rébus.
 *
 * Le `·` est le séparateur du dépôt depuis le retrait des tirets quadratins ;
 * il évite que les trois lettres ne se lisent comme le premier mot du nom. */
.kincy-agency-code {
  font-weight: 600;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}

/* CARTOUCHE D'AGENCE — le trigramme, et la couleur de son agence (2026-09-28).
 *
 * Employé par la colonne « Agence » de l'annuaire RH. La teinte est celle que
 * la même agence porte sur « Occupation des agents », parce qu'elle est lue
 * dans `apps.core.agencies` PAR LE NOM et non par un rang.
 *
 * ⚠️ LA COULEUR EST UN BANDEAU, PAS DE L'ENCRE, et ce n'est pas un goût : les
 * six teintes de la palette sont calibrées pour un FILET ou un aplat, pas pour
 * être lues. `--kincy-trend-0` est à 3,81:1 sur blanc — sous le seuil AA d'un
 * texte courant. Colorer les trois lettres, ou les poser en blanc sur l'aplat,
 * rendrait la colonne moins lisible que sans couleur. C'est la même règle que
 * les tuiles du rapport, dont le chiffre reste en encre courante et dont
 * l'appartenance se dit par `border-left-color`. La couleur ne dit que « de qui
 * on parle ».
 *
 * Le bandeau se pose par `--kincy-agency-ink`, posée en style en ligne à partir
 * du slot. Son défaut neutre couvre l'agence qu'aucune teinte ne code : la
 * cellule reste alors lisible, simplement sans appartenance. */
.kincy-agency-pill {
  --kincy-agency-ink: var(--kincy-neutral-medium);
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.1rem 0.45rem 0.1rem 0.35rem;
  border-radius: var(--kincy-radius-sm);
  border-left: 3px solid var(--kincy-agency-ink);
  background: var(--kincy-neutral-light);
}

/* ÉCLAT DE CONFIGURATION — l'écran du référentiel, 2026-09-29.
 *
 * ⚠️ Un écran qui fait choisir une couleur doit la MONTRER. La première
 * rédaction offrait « Emplacement 0 », « Emplacement 1 »… sans le moindre
 * retour visuel : il fallait connaître la palette par cœur, ou l'essayer et
 * recharger. Rodolphe l'a relevé le jour de la livraison.
 *
 * ⚠️ La couleur est un ÉCLAT, et le libellé reste en encre courante — la règle
 * de `docs/conventions/ui.md` §1. Colorer le texte de l'option, ou la poser en
 * blanc sur l'aplat, rouvrirait le problème de contraste que ce même chantier
 * vient de fermer, et sur un écran où les six teintes défilent côte à côte. */
.kincy-slot-swatch {
  display: inline-block;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: var(--kincy-radius-sm);
  border: 1px solid var(--kincy-neutral-medium);
  flex: none;
  vertical-align: middle;
}

/* La légende de la palette : les six emplacements, et qui les occupe. C'est
 * elle qui répond à « lequel est libre ? » sans faire ouvrir six menus. */
.kincy-slot-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kincy-spacing-md);
  align-items: center;
}

.kincy-slot-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.8125rem;
}

/* Un emplacement que personne n'occupe : le dire en toutes lettres plutôt que
 * de laisser une case vide, qui se lit comme un défaut de rendu. */
.kincy-slot-free {
  color: var(--kincy-neutral-dark);
  font-style: italic;
}

.kincy-agency-dot {
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--kincy-agency-ink);
  flex: none;
}

/* Chips EMPILÉES. Chaque légende ne surplombe alors que sa propre colonne, ce
 * qui referme le défaut de la barre d'avant : une étiquette posée au-dessus
 * d'une rangée qui se replie finit par nommer la mauvaise chose. */
.kincy-filter-group--stack {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
}

/* Chips en DEUX colonnes. Même intention que `--stack` — une légende ne
 * surplombe que son propre groupe — mais sur deux colonnes au lieu d'une : six
 * valeurs tiennent en trois rangées, quatre en deux, sur une barre qui est
 * COLLANTE et dont chaque pixel est de la fenêtre perdue.
 *
 * `display: grid` écrase le `display: flex` de `.kincy-filter-group` par
 * l'ORDRE des règles à spécificité égale : ce bloc doit rester APRÈS lui.
 *
 * ⚠️ Les `<input class="btn-check">` sont `position: absolute`, donc ils ne
 * sont PAS des éléments de grille : seuls les `<label>` sont placés. Le jour où
 * cette classe Bootstrap cesserait de positionner ses cases, la grille
 * compterait douze éléments au lieu de six et se disloquerait en silence.
 *
 * Remplissage en LIGNES (le défaut) et non en colonnes : l'ordre fixe des
 * départements se lit alors de gauche à droite puis à la ligne, comme un texte.
 * `grid-auto-flow: column` le lirait en serpentin. */
.kincy-filter-group--pairs {
  display: grid;
  grid-template-columns: repeat(2, auto);
  justify-items: start;
  align-items: center;
  gap: 0.25rem 0.5rem;
}

.kincy-filter-switches {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.375rem;
  padding-top: 0.25rem;
}

/* En colonne, la marge de gauche des interrupteurs, posée pour les détacher des
 * chips quand tout tenait sur une ligne, les indenterait sans raison. */
.kincy-filter-switches .kincy-filter-switch {
  margin-left: 0;
}

/* Dans un bloc EMPILÉ, les deux interrupteurs se remettent en LIGNE : le bloc
 * a alors toute sa largeur pour eux, et les empiler en plus l'aurait rendu le
 * plus haut des trois — donc le plafond de la barre. Mesuré le 2026-09-09 :
 * empilés, « Cohorte » fait 305 x 174 et la barre 219 px ; en ligne, 390 x 120
 * et la barre retombe à 183 px. Les libellés courts (« Types Odoo suivis
 * uniquement », « Type Halo renseigné ») le permettent — c'est le renommage du
 * même jour qui a libéré la place. */
.kincy-filter-bloc-cols--stacked .kincy-filter-switches {
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--kincy-spacing-md);
}

/* Pied de barre : la note d'état et le bouton de réinitialisation, sur leur
 * PROPRE ligne (`flex-basis: 100%`). Ils occupaient une colonne à droite des
 * groupes, ce qui leur donnait le poids visuel d'un filtre de plus.
 *
 * La note perd son `margin-left: auto` et ferme la barre à GAUCHE ; le bouton
 * garde le sien, donc il reste à droite. Les deux sont exclusifs
 * (`x-show="!isDirty"` contre `x-show="isDirty"`), et la hauteur minimale évite
 * que la barre ne saute d'un état à l'autre. */
.kincy-profit-barfoot {
  display: flex;
  flex: 1 0 100%;
  align-items: baseline;
  min-height: 1.25rem;
}

.kincy-profit-barfoot .kincy-run-bar-note {
  margin-left: 0;
}

/* --- Zone KPI : trois tuiles ---------------------------------------------- */

.kincy-profit-kpi {
  margin-bottom: var(--kincy-spacing-lg);
}

.kincy-profit-kpi-sub {
  max-width: 80ch;
  margin-bottom: var(--kincy-spacing-md);
  color: var(--kincy-slate);
  font-size: 0.8125rem;
}

.kincy-profit-tiles {
  display: grid;
  /* `auto-fit` plutôt qu'un nombre de colonnes figé : sous 900 px les tuiles
   * se replient l'une sous l'autre sans media query. */
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--kincy-spacing-md);
}

.kincy-profit-tile {
  padding: var(--kincy-spacing-md);
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-md);
  background-color: #fff;
}

/* La tuile du milieu porte LE signal : un filet plus marqué la distingue sans
 * lui donner de couleur — la couleur est réservée au chiffre lui-même. */
.kincy-profit-tile--signal {
  border-color: var(--kincy-slate);
}

.kincy-profit-tile-label {
  margin-bottom: 0.25rem;
  color: var(--kincy-slate);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.kincy-profit-tile-value {
  margin-bottom: 0.25rem;
  color: var(--kincy-slate);
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

/* Un tiret n'est pas un chiffre : il ne mérite pas le poids d'un chiffre. */
.kincy-profit-tile-value--empty {
  color: var(--kincy-neutral-dark);
  font-weight: 400;
}

/* Sous dix projets clos, la médiane s'atténue : elle reste une médiane au sens
 * arithmétique et ne veut rien dire au sens métier. L'atténuation ne suffit
 * pas seule — la mention « effectif faible » l'accompagne en texte. */
.kincy-profit-tile-value--faint {
  opacity: 0.55;
}

.kincy-profit-tile-note {
  margin-bottom: 0.15rem;
  color: var(--kincy-slate);
  font-size: 0.8125rem;
}

.kincy-profit-provisional {
  margin-top: 0.4rem;
  font-style: italic;
}

/* Bandeau « exercice non barémé ». L'orange est emprunté au niveau PAGE, pas au
   niveau cellule : sur cette page il a déjà deux sens dans le tableau (badge
   « à commander », fraîcheur à surveiller), et un bandeau ne concurrence aucune
   cellule. Le fond est un aplat très clair et le texte garde la couleur du
   corps : l'orange porte le signal par la BORDURE, jamais par le texte — un
   corps de texte en --kincy-orange ne tiendrait pas le 4,5:1 de WCAG 1.4.3. */
.kincy-profit-unscaled {
  margin: 0 0 var(--kincy-spacing-md);
  padding: 0.6rem 0.85rem;
  border-left: 4px solid var(--kincy-orange);
  border-radius: var(--kincy-radius-sm);
  background: #fdf3e6;
  font-size: 0.9rem;
  line-height: 1.45;
}

/* La note de tuile garde la couleur du corps — pas d'orange sur du texte, pour
   la raison écrite juste au-dessus. Elle n'est qu'une PRÉCISION de la ligne qui
   la précède ; le signal, lui, est porté par le bandeau. La classe existe comme
   point d'accroche pour la sonde, et pour ne pas avoir à cibler un `nth-child`
   qui se casserait au prochain ajout dans la tuile. */
.kincy-profit-unscaled-note {
  font-weight: 500;
}

/* Le dépassement, partout où il est chiffré. */
.kincy-profit-over {
  color: var(--kincy-red);
}

.kincy-profit-derived {
  margin-top: var(--kincy-spacing-md);
  margin-bottom: 0;
  color: var(--kincy-slate);
  font-size: 0.8125rem;
}

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

/* « en cours » sur un mois dont des projets sont encore ouverts : son
 * dépassement ne peut que monter, et sans cette marque le dernier mois se lit
 * comme une chute. Neutre, pas orange — ce n'est pas une anomalie, c'est un
 * état d'avancement. Grammaire du `M0` de la page Facturation. */
.kincy-profit-inprogress,
.kincy-profit-inactive-flag {
  display: inline-block;
  margin-left: 0.35rem;
  padding: 0 0.3rem;
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-sm);
  color: var(--kincy-neutral-dark);
  font-size: 0.6875rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* Un agent inactif est ATTÉNUÉ, jamais retiré : ses heures restent comptées
 * dans tous les agrégats. Même contrat que le sélecteur d'agents du rapport
 * RUN — l'inactivité est un fait d'affichage, pas une exclusion de calcul.
 * L'opacité ne porte pas seule : le badge « inactif » le dit en mots. */
.kincy-profit-agent--inactive th,
.kincy-profit-agent--inactive td {
  opacity: 0.65;
}

.kincy-profit-footer p {
  margin-bottom: 0.4rem;
}

.kincy-profit-footer p:last-child {
  margin-bottom: 0;
}

/* Le graphe du délai est en JOURS et sur son propre graphe : plus court, parce
 * qu'il porte une seule information et qu'un double axe mêlant heures et jours
 * ferait rapprocher la hauteur d'une ligne de celle d'une barre. */
.kincy-run-chart-wrap--compact {
  height: 200px;
}

/* ⚠️ Les chips de PRESTATION ont leur propre classe de base, et ce n'est pas
 * une redondance. Elles avaient d'abord réutilisé `.kincy-build-dept-chip`
 * pour hériter des trois couleurs — et la sonde a lu la mauvaise chip : son
 * sélecteur `.kincy-build-dept-chip` attrapait les deux groupes, donc
 * `deptChips[0]` désignait « RUN » la prestation et non le premier département.
 * Le clic partait au bon endroit et seule la LECTURE mentait, ce qui est le
 * genre de défaut qui use une preuve sans rien apprendre.
 * Ce que les deux groupes partagent est la COULEUR PAR RÔLE, pas l'identité :
 * les modificateurs `--run/--build/--manage` sont donc communs, la classe de
 * base est propre à chaque niveau de filtre. Deux niveaux de filtre ne peuvent
 * pas partager leur sélecteur. */

/* --- Barre de filtres : groupes NOMMÉS (zone BUILD, rentabilité) ------------
   Les légendes ne sont pas cosmétiques : la page porte trois groupes de chips
   dont deux peuvent afficher le mot « BUILD ». Sans leur nom, l'une filtre le
   projet et l'autre l'heure, et rien ne le dit.

   Le `<fieldset>` est remis à plat (Bootstrap ne le style pas, le navigateur si)
   et la `<legend>` cesse d'être un bloc de pleine largeur pour redevenir une
   étiquette en ligne au-dessus de son groupe. */
.kincy-filter-fieldset {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0; /* même raison que .kincy-main : un fieldset est un conteneur */
}

.kincy-filter-legend {
  float: none; /* le défaut navigateur en fait un bloc de pleine largeur */
  width: auto;
  margin-bottom: 0.125rem;
  padding: 0;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--kincy-neutral-dark);
}

/* L'interrupteur de qualité de donnée n'est pas une chip : il ne prend ni la
   bordure arrondie ni l'état actif cyan, pour qu'on ne le lise pas comme une
   valeur de plus du vocabulaire.

   Il porte `.form-switch` de Bootstrap depuis le 2026-09-09 : la case à cocher
   carrée était l'anomalie de la barre — un interrupteur dit « en marche / à
   l'arrêt » quand une case dit « inclus dans une liste », et c'est bien un état
   binaire qu'on manipule ici. Le composant existait déjà dans Link (page
   détail Facturation, page détail Licences Microsoft).

   ⚠️ Les deux neutralisations ci-dessous ne sont pas cosmétiques.
   `.form-switch` pose `padding-left: 2.5em` sur son conteneur et
   `margin-left: -2.5em` sur la case, un couple prévu pour le `.form-check` de
   Bootstrap ; notre conteneur est un flex avec `gap`, où ces deux valeurs
   décalent le texte hors du label. Elles gagnent par l'ordre des feuilles
   (app.css charge après Bootstrap) à spécificité égale : ne pas les déplacer
   dans une règle moins spécifique. */
.kincy-filter-switch {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  margin-left: var(--kincy-spacing-sm);
  padding-left: 0;
  font-size: 0.75rem;
  color: var(--kincy-slate);
  cursor: pointer;
  white-space: nowrap;
}

.kincy-filter-switch .form-check-input {
  margin: 0;
  flex: 0 0 auto;
  cursor: pointer;
}

/* L'INTERRUPTEUR DE LINK, `.form-switch` de Bootstrap : TROIS DÉFAUTS CORRIGÉS ICI,
   UNE FOIS, pour toutes les pages qui l'emploient (`EC-A52`, 2026-10-12) :
   « Rentabilité des projets » (`.kincy-filter-switch form-switch`), « Facturation »
   et « Comptes & licences Microsoft 365 » (`.form-check form-switch`), et
   l'interrupteur « Échus » de la page Contrats. Mesurés dans `bootstrap.css` 5.3.3 et
   au style calculé (spec `EC-A50`) :
   - au repos, Bootstrap trace le contour en `--bs-border-color` (#dee2e6, 1,30:1 sur
     blanc) et le bouton en `rgba(0, 0, 0, .25)` (1,84:1), sous les 3:1 d'un composant
     (WCAG 1.4.11) : contour et bouton passent à `--kincy-neutral-dark` (7,00:1) ;
   - allumé, il compile `#0d6efd` EN LITTÉRAL, que l'override `--bs-primary` n'atteint
     pas : la piste passe à `--kincy-blue` (bouton blanc, 4,80:1) ;
   - au focus, un halo `#0d6efd` à 25 % et un bouton `#86b7fe` (2,06:1) : le focus
     devient le contour de 2 px `--kincy-blue` du reste de Link, le bouton ne change pas.
   ⚠️ Le bouton est une image SVG en data-URI (`--bs-form-switch-bg`), qui NE PEUT PAS
   lire un token : `%23595959` y est la valeur de `--kincy-neutral-dark`, écrite en
   clair (`PE-7`), et `tests/test_form_switch.py` exige qu'elle lui reste égale.
   ⚠️ Ces règles ne vont JAMAIS sous un sélecteur de page : ce serait corriger une
   page et laisser les autres au bleu de Bootstrap. Ce qui est propre à une page (la
   taille de la piste de D3) va sous le sien. */
.form-switch .form-check-input {
  border-color: var(--kincy-neutral-dark);
  --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23595959'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:focus {
  border-color: var(--kincy-neutral-dark);
  box-shadow: none;
  --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23595959'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:focus-visible {
  outline: 2px solid var(--kincy-blue);
  outline-offset: 2px;
}
/* Après `:focus`, à spécificité égale : allumé ET focalisé reste bleu, bouton blanc. */
.form-switch .form-check-input:checked {
  border-color: var(--kincy-blue);
  background-color: var(--kincy-blue);
  --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
}

/* --- Barres des tableaux par type de prestation (zone BUILD, rentabilité) ---
   Deux visuels de même forme et de codes couleur OPPOSÉS, à dessein : rouge et
   vert sur la marge, une seule teinte neutre sur le délai. Un délai long n'est
   pas une faute — un projet BUILD est légitimement plus long qu'un RUN — et
   colorer inventerait une norme que personne n'a fixée. Le contraste entre les
   deux tableaux EST l'information ; les notes de pied le disent en mots. */
.kincy-profit-barcol {
  width: 12rem;
  min-width: 8rem;
}

.kincy-profit-bars {
  display: flex;
  align-items: center;
  width: 100%;
  height: 0.75rem;
  /* La piste, sans laquelle le remplissage ne dirait pas de quoi il est la
     fraction — même raison que la barre d'avancement de « Suivi des projets ». */
  background: var(--kincy-neutral-light);
  /* ⚠️ Le CONTOUR n'est pas décoratif. `--kincy-green` mesure 2,54:1 sur blanc
     et échoue donc au 3:1 de WCAG 1.4.11 : le bord d'un aplat de la charte ne
     se perçoit pas. Le contour slate porte le contraste, ce qui rend l'étendue
     de la piste mesurable et la longueur de la barre lisible. Même parade que
     le cerclage de la pastille de dépassement de « Suivi des projets ». Les
     valeurs sont de toute façon écrites en clair dans la colonne voisine, d'où
     l'`aria-hidden` : la barre est un renfort, jamais la seule source. */
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: 2px;
  overflow: hidden;
}

/* Barres DIVERGENTES : le zéro est au centre, le déficit pousse à gauche et
   l'excédent à droite. Les deux moitiés sont rendues même vides, sinon une
   ligne sans marge décalerait la colonne suivante. */
.kincy-profit-bars-half {
  display: flex;
  width: 50%;
  height: 100%;
}

.kincy-profit-bars-half--neg {
  justify-content: flex-end;
  border-right: 1px solid var(--kincy-neutral-medium); /* le zéro, visible */
}

.kincy-profit-bar {
  height: 100%;
  border-radius: 1px;
}

.kincy-profit-bar--neg { background: var(--kincy-red); }
.kincy-profit-bar--pos { background: var(--kincy-green); }
.kincy-profit-bar--delay { background: var(--kincy-slate); }

/* Sous le seuil d'effectif, la barre s'atténue comme le chiffre : c'est le même
   signal, et le porter d'un seul côté laisserait croire à une barre fiable. */
.kincy-profit-bar--faint { opacity: 0.4; }

/* ⚠️ La marge POSITIVE n'a PAS de couleur de texte, et c'est délibéré.
   `--kincy-green` mesure 2,54:1 sur blanc : l'employer en texte serait
   illisible, et introduire un vert plus sombre créerait un token de plus pour
   dire « tout va bien ». La règle de la zone est l'inverse : une couleur de
   signal marque l'anomalie, et *vide veut dire « rien à dire »* — c'est celle
   qui a fait garder pastille et flèche séparées sur « Suivi des projets ».
   Seul le déficit est rouge, et le vert vit dans la BARRE, où il est un aplat
   cerclé et non du texte. */
.kincy-profit-under {
  font-weight: 600;
}

.kincy-run-table-note {
  margin: var(--kincy-spacing-sm) 0 0;
  font-size: 0.75rem;
  color: var(--kincy-neutral-dark);
}

/* --- Croisement département × agence, et charge individuelle (zone BUILD) ---
   Disposition deux tiers / un tiers : les petits multiples à gauche, les deux
   camemberts à droite. */
.kincy-profit-split {
  display: flex;
  gap: var(--kincy-spacing-lg);
  align-items: flex-start;
  flex-wrap: wrap;
}

.kincy-profit-split-main {
  flex: 2 1 22rem;
  min-width: 0; /* même règle que .kincy-main : un flex ne rétrécit pas sans */
}

.kincy-profit-split-side {
  flex: 1 1 12rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--kincy-spacing-md);
}

/* Les cercles se dimensionnent sur la LARGEUR (aspectRatio: 1 côté Chart.js),
   donc le conteneur n'a pas de hauteur à donner — mais il a une largeur à
   borner, sans quoi un tiers de page ferait des cercles démesurés. */
.kincy-profit-pie {
  max-width: 11rem;
}

.kincy-profit-pie-title {
  margin: 0 0 0.25rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--kincy-neutral-dark);
}

.kincy-profit-multiple + .kincy-profit-multiple {
  margin-top: var(--kincy-spacing-md);
}

.kincy-profit-multiple-title {
  margin: 0 0 0.25rem;
  font-size: 0.8125rem;
  font-weight: 600;
}

.kincy-profit-multiple-title .kincy-run-muted {
  margin-left: 0.5rem;
  font-weight: 400;
}

/* Une barre empilée par département, segmentée par agence. Les segments
   grandissent par `flex-grow`, proportionnel à la part DU DÉPARTEMENT — pas du
   total : c'est une distribution interne, et la rapporter au total ferait
   paraître les segments de Kaizen aussi larges que ceux de Build. */
.kincy-profit-stack {
  display: flex;
  height: 1.75rem;
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: 3px;
  overflow: hidden;
}

.kincy-profit-seg {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  overflow: hidden;
  padding: 0 0.25rem;
  font-size: 0.6875rem;
  line-height: 1;
  white-space: nowrap;
  /* ⚠️ L'ENCRE N'EST PLUS ICI — corrigé le 2026-09-29, et le motif compte.
     Ce bloc posait `color: #fff` pour tous les segments, au motif que « les
     teintes d'agence sont choisies assez sombres pour le porter, sur six
     valeurs connues d'avance ». C'était vrai de `--kincy-agency-N`, qui avait
     été construite POUR ça. Ça ne l'est plus de `--kincy-trend-N`, calibrée
     pour des filets : trois de ses six teintes échouent sous du blanc.
     L'encre est donc portée par le modificateur `--cN`, avec sa teinte. */
  border-right: 1px solid rgb(0 0 0 / 20%);
}

.kincy-profit-seg:last-child { border-right: 0; }

/* ⚠️ Le décompte d'intervenants est SUR le segment, pas dans une colonne à
   côté : c'est l'accolement de la longueur et du nombre qui fait apparaître
   « 43 % avec 2 personnes ». Séparés, le lecteur ne les rapproche pas.
   Sur un segment trop court, le libellé est simplement coupé par l'overflow —
   la valeur reste dans le `title` et dans le tableau de charge individuelle,
   qui porte les mêmes nombres en clair. */
.kincy-profit-seg-label {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Six emplacements de couleur d'agence, attribués par INDEX de l'univers trié
   (voir `agency_color_slots`). Ils doivent être IDENTIQUES dans les barres et
   dans les deux camemberts : la comparaison des deux cercles repose
   entièrement sur l'identité des teintes.

   Deux contraintes les fixent, et la seconde a écarté trois tokens de la
   charte :

   1. Ni `--kincy-orange` (le signal « à surveiller » de tout Link) ni
      `--kincy-red` (le dépassement de cette page) : une couleur de catégorie
      n'emprunte pas une couleur de signal.
   2. **Chacune doit porter du texte BLANC à 4,5:1** — le décompte
      d'intervenants est écrit SUR le segment, et il est en 11 px. Mesuré le
      2026-09-04 : `--kincy-cyan` rend 2,67:1, `--kincy-green` 2,54:1 et
      `--kincy-magenta` 4,37:1 — les trois échouent. Les trois teintes
      assombries ci-dessous conservent leur teinte de marque et passent
      (5,23:1, 5,11:1 et 6,65:1). Écart assumé à la charte, du même statut que
      `--kincy-red` et `--kincy-star` : c'est une palette CATÉGORIELLE, pas une
      couleur de marque, et la charte n'en propose aucune.
      Les trois autres viennent de la charte telle quelle : slate 7,71:1,
      bleu 4,80:1, violet 9,43:1.

   ⚠️⚠️ CE BLOC N'EST PLUS CELUI DES SEGMENTS DEPUIS LE 2026-09-29, ET IL RESTE.
   Les segments de « Rentabilité des projets » lisent désormais
   `--kincy-trend-N`, comme « Occupation des agents » et l'annuaire RH : une
   agence porte la même teinte sur les trois écrans, ce qu'aucune preuve ne
   tenait avant (Madagascar sortait ardoise ici et bleue ailleurs).

   Ce bloc reste pour deux raisons, et aucune n'est la nostalgie :

   a. `apps/dataquestion/visuals.py` EMPLOIE ces six tokens comme palette de
      ses camemberts, et une preuve les relit ici. Les retirer casserait un
      autre écran ;
   b. il porte la MÉMOIRE de la contrainte 2, qui reste vraie et qui a failli
      faire livrer un écran illisible : la palette `trend` est calibrée pour
      des FILETS, et trois de ses six teintes échouent sous du texte blanc.
      C'est ce constat, remonté par trois lentilles indépendantes d'une revue
      adversariale, qui a fait rendre l'arbitrage sur l'ENCRE ci-dessous.

   ⚠️ La contrainte 1 a été appliquée à `trend` le même jour : Paris a quitté
   l'emplacement 3, dont la teinte frôle `--kincy-orange`. */
:root {
  --kincy-agency-0: var(--kincy-slate);   /* 7,71:1 sur blanc */
  --kincy-agency-1: var(--kincy-blue);    /* 4,80:1 */
  --kincy-agency-2: var(--kincy-purple);  /* 9,43:1 */
  --kincy-agency-3: #b3105f;              /* magenta assombri, 6,65:1 */
  --kincy-agency-4: #00778c;              /* cyan assombri, 5,23:1 */
  --kincy-agency-5: #4a7a2b;              /* vert assombri, 5,11:1 */
}

/* L'ENCRE LISIBLE SUR CHAQUE EMPLACEMENT `trend` — posée le 2026-09-29.
 *
 * ⚠️ Le blanc n'est plus un décret, c'est un RÉSULTAT. La famille `trend` est
 * calibrée pour des filets (~3:1) et non pour porter du texte : mesuré le
 * 2026-09-28, blanc sur la teinte, `trend-0` rend 3,81:1, `trend-1` 3,00:1 et
 * `trend-3` 2,73:1 — trois échecs sur six, dont deux PIRES que le magenta
 * nommément rejeté le 2026-09-04.
 *
 * Chaque token ci-dessous porte donc l'encre qui PASSE sur sa teinte, et son
 * ratio. Méthode : luminance relative sRGB / WCAG 2.x, ÉTALONNÉE — elle
 * reproduit exactement les six ratios annotés sur `--kincy-agency-N` ci-dessus.
 *
 * ⚠️ Déclarés ici plutôt que calculés par segment (`_readable_text_color`, de
 * la page licences) : ces six couleurs sont des CONSTANTES de feuille de style,
 * pas des données. Les calculer à chaque ligne d'un tableau qui en rend des
 * milliers coûterait sans rien apprendre — et un test relit ces paires dans
 * `app.css` et recalcule les ratios, donc elles ne peuvent pas dériver.
 *
 * ⚠️ `trend-2` est le cas SERRÉ : 4,65:1 en noir, 4,52:1 en blanc. Les deux
 * passent, aucun confortablement. Toucher à cette teinte demande de refaire la
 * mesure, pas de supposer. */
:root {
  --kincy-ink-on-trend-0: #000;   /* sur #2D7FF9 : 5,52:1 */
  --kincy-ink-on-trend-1: #000;   /* sur #00A88F : 7,00:1 */
  --kincy-ink-on-trend-2: #000;   /* sur #9B51E0 : 4,65:1 — le plus serré */
  --kincy-ink-on-trend-3: #000;   /* sur #E8830C : 7,68:1 */
  --kincy-ink-on-trend-4: #fff;   /* sur #C2185B : 5,87:1 */
  --kincy-ink-on-trend-5: #fff;   /* sur #5B6B7F : 5,45:1 */
}

.kincy-profit-seg--c0 { background: var(--kincy-trend-0); color: var(--kincy-ink-on-trend-0); }
.kincy-profit-seg--c1 { background: var(--kincy-trend-1); color: var(--kincy-ink-on-trend-1); }
.kincy-profit-seg--c2 { background: var(--kincy-trend-2); color: var(--kincy-ink-on-trend-2); }
.kincy-profit-seg--c3 { background: var(--kincy-trend-3); color: var(--kincy-ink-on-trend-3); }
.kincy-profit-seg--c4 { background: var(--kincy-trend-4); color: var(--kincy-ink-on-trend-4); }
.kincy-profit-seg--c5 { background: var(--kincy-trend-5); color: var(--kincy-ink-on-trend-5); }

/* ⚠️ L'agence QU'AUCUNE TEINTE NE CODE — et c'est un correctif, pas un ajout.
   Le repli était `AGENCY_COLOR_SLOTS - 1`, c'est-à-dire une VRAIE teinte de la
   palette : un inconnu s'y lisait comme une agence. Il prend le neutre, comme
   le `None` de « Occupation des agents » et de l'annuaire RH. */
.kincy-profit-seg--neutral {
  /* ⚠️ Fond CLAIR et encre sombre, et ce n'est pas un choix esthétique : mesuré
     le 2026-09-29, slate sur `--kincy-neutral-medium` (#c4c4c4) ne rend que
     **4,42:1** — sous le seuil. Sur `--kincy-neutral-light` (#E2EFF4) il rend
     **6,57:1**. La première rédaction de ce bloc annonçait 4,58:1 et se
     trompait ; c'est la preuve de contraste qui l'a dit, pas l'œil. */
  background: var(--kincy-neutral-light);
  color: var(--kincy-slate);
}

/* La barre de charge individuelle. Neutre : ce ratio est une CONCENTRATION
   avant d'être une productivité, et le colorer inventerait un jugement. */
.kincy-profit-bar--load { background: var(--kincy-slate); }

/* Le « ≠ » des cellules où la moyenne s'éloigne de la médiane. Il ne masque
   pas la moyenne — il dit que quelques personnes portent la cellule, ce qui
   est en soi une information sur l'organisation. */
.kincy-profit-skewed {
  margin-left: 0.25rem;
  color: var(--kincy-neutral-dark);
  cursor: help;
}

/* La ligne de rapport interne d'un département à plusieurs agences : c'est la
   comparaison VOULUE, donnée en nombres pour qu'elle ne dépende pas de la
   longueur des barres — l'échelle étant partagée entre les groupes. */
.kincy-profit-ratio-row td {
  padding-top: 0;
  border-top: 0;
  font-size: 0.75rem;
}

.kincy-profit-lede {
  margin: 0 0 var(--kincy-spacing-sm);
  padding: var(--kincy-spacing-sm) var(--kincy-spacing-md);
  background: var(--kincy-neutral-light);
  border-radius: 4px;
  font-size: 0.8125rem;
}

/* ---------------------------------------------------------------------------
   Prompts IA des actions Halo (outil de la boîte à outils)

   Classe de tableau NEUVE et non un emprunt à `.kincy-run-table` : le
   `thead th` de cette dernière est partagé par trois pages (rapport
   rentabilité, facturation détail, suivi des projets), et la retoucher pour un
   quatrième écran ferait porter à celui-ci le risque de casser les trois
   autres.

   Le badge « Sans prompt » signale une anomalie de configuration — une action
   qui déclare une opération d'IA sans porter de prompt. Il dépense
   `--kincy-orange`, le signal « à surveiller » de tout Link, et c'est son seul
   usage sur cette page : aucune autre signification ne lui est empruntée ici.
   --------------------------------------------------------------------------- */

.kincy-prompts-table code {
  color: var(--kincy-slate);
  background: var(--kincy-neutral-light);
  padding: 0.1rem 0.35rem;
  border-radius: var(--kincy-radius-sm);
}

.kincy-prompts-warning {
  color: #fff;
  background: var(--kincy-orange);
}

.kincy-prompts-diff {
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 12rem;
  overflow-y: auto;
  background: var(--kincy-neutral-light);
  padding: var(--kincy-spacing-sm);
  border-radius: var(--kincy-radius-sm);
}

/* --- Matrice des rattrapages (zone BUILD, « Rentabilité des projets ») -----
   Lignes = types de prestation, colonnes = départements producteurs.

   ⚠️ `overflow-x: auto` sur SON conteneur, et pas seulement sur la page :
   `.kincy-main` a bien reçu son `min-width: 0` le 2026-09-04 (la taille
   minimale automatique d'un élément flex est celle de son contenu, et
   `max-width: 100%` ne l'écrase pas), mais un tableau non contraint rouvrirait
   le débordement du document par un autre chemin — la colonne « Sans
   département » s'ajoute aux six autres dès qu'un agent sans équipe note une
   heure.

   Une case à zéro s'atténue mais reste un CHIFFRE : sur cette page le tiret
   veut dire « on ne sait pas », or `is_catchup` est un bit jamais NULL. On
   affiche donc `0h00`, en gris.
   --------------------------------------------------------------------------- */

.kincy-catchup-matrix {
  overflow-x: auto;
  max-width: 100%;
}

.kincy-catchup-matrix .kincy-run-muted {
  color: var(--kincy-neutral-dark);
}

/* --- File de correction des rattrapages (zone BUILD) -----------------------
   Un `<details>` fermé, jamais un composant Alpine : un écouteur posé sur un
   bouton d'entête serait perdu au premier swap HTMX, sans erreur, et le
   dépliage cesserait simplement de répondre.

   Le bloc est ABSENT du document quand il n'a rien à dire (garde côté
   gabarit) : replié sur du vide, il ferait cliquer pour rien.

   Pas d'orange ni de rouge : ce sont des saisies à compléter, et les deux
   couleurs de signal de cette page portent déjà deux sens chacune.
   --------------------------------------------------------------------------- */

.kincy-catchup-fixlist {
  margin-top: var(--kincy-spacing-md);
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-sm);
  background: var(--kincy-neutral-light);
}

.kincy-catchup-fixlist > summary {
  cursor: pointer;
  padding: var(--kincy-spacing-sm) var(--kincy-spacing-md);
  font-weight: 600;
  color: var(--kincy-slate);
}

.kincy-catchup-fixlist > summary:hover {
  color: var(--kincy-cyan);
}

/* Le corps se détache du sommaire : ouvert, on doit voir où commence la file. */
.kincy-catchup-fixlist > :not(summary) {
  margin: 0 var(--kincy-spacing-md) var(--kincy-spacing-md);
}

.kincy-catchup-fixlist .table {
  background: var(--bs-body-bg);
}

/* État d'un modèle OpenAI configuré sur une action Halo — TROIS états.

   Ni vert vif ni rouge : l'affirmation que nous portons est « absent du
   catalogue de NOTRE clé », pas « n'existe pas ». Le rouge (`--kincy-red`,
   réservé à l'anomalie constatée) mentirait sur la force de ce que l'on sait ;
   l'orange, signal « à surveiller » de tout Link, dit juste. Et l'état ne passe
   jamais par la couleur seule : glyphe, infobulle et texte pour lecteur
   d'écran l'accompagnent partout. */

.kincy-model-state {
  font-size: 0.85em;
  margin-left: 0.35rem;
}

.kincy-model-state--ok {
  color: var(--kincy-green);
}

.kincy-model-state--absent {
  color: var(--kincy-orange);
}

.kincy-model-state--unknown {
  color: var(--kincy-neutral-medium);
}

/* ---------------------------------------------------------------------------
   Identifiants de bloc — le petit code discret dans le coin du cadre.
   Ajouté le 2026-09-08 : une page de rapport porte quatorze visuels dont
   plusieurs se ressemblent, et les désigner sans ambiguïté demandait un nom.
   Voir templates/_block_id.html pour le POURQUOI (slug et non numéro).

   `float: right` volontairement, et non `position: absolute` : le badge se
   pose sur la ligne du titre sans que le cadre ait à devenir un conteneur
   positionné. Aucune classe partagée n'est touchée, donc aucune mise en page
   d'une autre page ne peut sauter.
   --------------------------------------------------------------------------- */
.kincy-block-id {
  float: right;
  /* Assez pâle pour ne pas concurrencer le titre, assez contrasté pour se
     lire : 4,63:1 sur blanc, au-dessus des 4,5:1 de WCAG 1.4.3 — un code
     qu'on doit recopier n'est pas de la décoration. */
  color: var(--kincy-neutral-dark);
  font-family: var(--bs-font-monospace, monospace);
  font-size: 0.6875rem;
  line-height: 1.4;
  letter-spacing: 0.02em;
  padding: 0 0.3rem;
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-sm, 3px);
  background: var(--kincy-neutral-light);
  /* Un clic sélectionne le code ENTIER, prêt à coller dans une consigne :
     c'est tout l'intérêt du dispositif. */
  user-select: all;
  -webkit-user-select: all;
  cursor: text;
}

/* « Questionner ces données » posé DANS la barre de filtres, en haut à droite
 * (2026-09-09). La barre est une rangée à trois colonnes ; le bouton sort du
 * flux des colonnes et se cale sur l'angle, sinon il en volerait une.
 * Le positionnement absolu vaut mieux qu'une quatrième colonne : celle-ci
 * réserverait sa largeur même quand le bouton est absent — et il l'est pour
 * quiconque n'a pas le cumul des deux rôles, c'est-à-dire presque tout le
 * monde.
 *
 * ⚠️ **DOUBLE CLASSE dans le sélecteur, et c'est obligatoire.** Les règles de
 * base du bouton vivent dans `dataquestion.css`, que le gabarit du module
 * charge par un `<link>` DANS le corps — donc APRÈS `app.css`. À spécificité
 * égale, c'est l'ordre des feuilles qui tranche, et `.kincy-dq-launch` y
 * gagnait : la pastille sortait carrée, fond blanc, picto cyan. Deuxième fois
 * en deux jours que ce bouton est mordu par un conflit d'ordre entre deux
 * feuilles appartenant à deux périmètres (la première avait décollé la barre).
 * `.kincy-dq-launch.kincy-dq-launch--inline` monte à (0,2,0) et ne dépend plus
 * de l'ordre.
 *
 * ⚠️ **IL A FALLU RETIRER UN `position: relative` D'ICI, et il ne faut pas le
 * remettre.** Ce bloc posait `.kincy-profit-filterbar { position: relative }`
 * pour ancrer le bouton — mais cette classe est sur le MÊME nœud que
 * `.kincy-run-filterbar`, qui porte le `position: sticky` de la barre, et elle
 * vient APRÈS dans la feuille. À spécificité égale, `relative` gagnait :
 * **la barre a cessé de coller**, sans erreur, sans test rouge, et personne ne
 * s'en est aperçu avant que l'utilisateur ne le signale — pour la deuxième
 * fois en une journée.
 *
 * Le `relative` était de toute façon **inutile** : un élément `sticky` n'est pas
 * `static`, donc il est déjà le bloc conteneur de ses descendants
 * `position: absolute`. Le bouton se cale exactement pareil sans lui.
 *
 * Une preuve de sonde garde désormais la porte : elle défile et relit le sommet
 * de la barre (« la barre de filtres COLLE quand on défile »). */
.kincy-dq-launch.kincy-dq-launch--inline {
  position: absolute;
  top: var(--kincy-spacing-sm);
  right: var(--kincy-spacing-sm);
  z-index: 2;
  /* Une PASTILLE RONDE, et c'est la contrepartie d'un bouton en absolu : dès
     que la fenêtre se réduit, il passe AU-DESSUS du bloc « Ventilation des
     heures ». Le demandeur l'accepte (2026-09-10) à condition qu'il se lise
     alors comme un objet flottant — assez gros, un aplat franc, un picto
     blanc — et non comme une étiquette qui écrase du texte. Un libellé de
     trois mots posé sur des chips serait illisible pour les deux.
     44 px : le plancher de cible tactile, et la taille qui fait lire un rond
     comme un bouton plutôt que comme une puce. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  background-color: var(--kincy-cyan);
  border-color: var(--kincy-cyan);
  color: #fff;
  /* ⚠️ **Renforcée le 2026-09-14** : elle valait `0 2px 6px` à 28 %, une seule
     couche — assez discrète pour que Rodolphe demande « tu peux lui mettre une
     ombre portée ? » sur un bouton qui en avait déjà une. Une ombre qu'on ne
     voit pas ne remplit pas son office, et c'est exactement ce que le token à
     deux couches corrige : un contact serré dit la hauteur, une diffusion douce
     dit le flottement. */
  box-shadow: var(--kincy-elevation-raised);
}

/* Le survol INVERSE la règle de la variante textuelle (fond cyan au repos,
   donc c'est l'assombrissement qui marque le survol) — la règle générique
   `.kincy-dq-launch:hover` repeindrait un fond cyan déjà cyan, sans effet. */
.kincy-dq-launch.kincy-dq-launch--inline:hover,
.kincy-dq-launch.kincy-dq-launch--inline:focus-visible {
  background-color: var(--kincy-blue);
  border-color: var(--kincy-blue);
  color: #fff;
}

.kincy-dq-launch-icon {
  width: 24px;
  height: 24px;
}

/* Le libellé reste dans le DOM : c'est le NOM ACCESSIBLE du bouton, et un
   bouton réduit à un pictogramme est muet pour un lecteur d'écran. Recette
   `visually-hidden` écrite ici plutôt qu'empruntée à Bootstrap, parce que
   c'est la VARIANTE qui masque, pas le gabarit — celui-ci est partagé et doit
   rendre un bouton textuel partout ailleurs. */
.kincy-dq-launch.kincy-dq-launch--inline .kincy-dq-launch-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------------------
 * Page « Comptes & licences Microsoft 365 » — passe graphique du 2026-09-14.
 * Spec : docs/microsoft-licenses-report-spec.md §5 et §8.
 *
 * ⚠️ LA RÈGLE QUI GOUVERNE TOUT CE BLOC : une teinte de charte peut porter un
 * FILET (élément d'interface, 3:1 suffit) mais pas un CHIFFRE (texte, 4,5:1
 * exigé). Mesuré sur blanc : --kincy-cyan 2,67:1, --kincy-green 2,54:1,
 * --kincy-orange 2,64:1, --kincy-star 1,85:1. D'où une variante assombrie par
 * teinte, calculée pour franchir 4,5:1 et pas davantage — assombrir plus
 * éloignerait de la charte sans rien gagner.
 * ------------------------------------------------------------------------- */
:root {
  /* Variantes TEXTE, mesurées le 2026-09-14. Le commentaire porte le ratio :
   * qui retouche une valeur doit refaire la mesure. */
  --kincy-green-ink: #4c831f;   /* 4,59:1 */
  --kincy-orange-ink: #b45e00;  /* 4,62:1 */
  --kincy-amber-ink: #986c00;   /* 4,69:1 */
  --kincy-blue-ink: #0077bd;    /* 4,80:1 — la charte passe déjà, inchangée */
  --kincy-red-ink: #b3261e;     /* 6,54:1 — idem */
  /* Violet ÉCLAIRCI de 25 %, comme demandé pour les départs : il reste à
   * 5,02:1, donc lisible en texte. À 35 % il tomberait à 3,89:1. */
  --kincy-purple-soft: #9359a1; /* 5,02:1 */
}

/* --- Écart de charte : l'orange est le COÛT, dans le rapport « Suivi des clients forfaitaires » ---
 * Lot L4 de la marge des forfaits, 2026-11-06, décision de Rodolphe (FM-D16 : « des couleurs qui
 * parlent », le CA en vert et le coût en orange). `--kincy-orange` est partout ailleurs le signal
 * « à surveiller », et le budget des couleurs de signal est plein (`docs/conventions/ui.md` §3) :
 * l'écart n'est donc pas emprunté en silence mais NOMMÉ. Deux alias, aucune valeur neuve, qui
 * donnent au sens « coût » son propre nom ; la teinte, elle, ne bouge pas si l'orange de la
 * charte bouge.
 * ⚠️ CONFINÉS : ils ne s'emploient que par le rapport (`static/css/fixedfee.css`,
 * `static/js/fixedfee.js`, `apps/fixedfee/constants.py`) et ne se définissent qu'ici, dans
 * `:root`. Tenu par `tests/test_fixedfee_tokens.py`. Contrastes mesurés le 2026-11-06 avec
 * `apps/core/contrast.py` : `--kincy-cost-ink` sur blanc 4,62:1 (texte, onglet « Coût » : blanc
 * sur l'encre) ; `--kincy-cost` sur blanc 2,64:1, **sous les 3:1 d'un aplat** : les barres de coût
 * sont cernées d'un filet de leur teinte, nommées par la légende et par la bulle, et la valeur
 * se lit dans le tableau (écart assumé, comme la pastille cyan de `EC-A89`). */
:root {
  --kincy-cost: var(--kincy-orange);
  --kincy-cost-ink: var(--kincy-orange-ink);
}

/* --------------------------------------------------------------- Les tuiles */
.kincy-ms-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--kincy-spacing-md);
  margin-bottom: var(--kincy-spacing-lg);
}

.kincy-ms-tile {
  position: relative;
  padding: var(--kincy-spacing-md);
  border: 1px solid var(--kincy-neutral-medium);
  /* Le filet de gauche porte la teinte : un seul côté, comme demandé. */
  border-left-width: 5px;
  border-radius: var(--kincy-radius-md);
  background-color: #fff;
  text-align: center;
}

.kincy-ms-tile-label {
  margin-bottom: 0.15rem;
  color: var(--kincy-neutral-dark);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.kincy-ms-tile-value {
  margin-bottom: 0.15rem;
  font-size: 2.4rem;
  font-weight: 700;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}

.kincy-ms-tile-note {
  margin-bottom: 0;
  color: var(--kincy-neutral-dark);
  font-size: 0.78rem;
}

.kincy-ms-tile-link { text-decoration: none; }
.kincy-ms-tile-link:hover .kincy-ms-tile-value { text-decoration: underline; }

/* Les intentions. Filet = teinte de charte, chiffre = variante texte. */
.kincy-ms-tile--info { border-left-color: var(--kincy-blue); }
.kincy-ms-tile--info .kincy-ms-tile-value { color: var(--kincy-blue-ink); }

.kincy-ms-tile--up { border-left-color: var(--kincy-green); }
.kincy-ms-tile--up .kincy-ms-tile-value { color: var(--kincy-green-ink); }

/* ⚠️ Un départ de collaborateur n'est PAS un gaspillage : violet neutre, jamais
 * l'orange de la page, qui n'a qu'un seul sens — le siège payé non utilisé. */
.kincy-ms-tile--out { border-left-color: var(--kincy-purple-soft); }
.kincy-ms-tile--out .kincy-ms-tile-value { color: var(--kincy-purple-soft); }

.kincy-ms-tile--warn { border-left-color: var(--kincy-orange); }
.kincy-ms-tile--warn .kincy-ms-tile-value { color: var(--kincy-orange-ink); }

.kincy-ms-tile--bad { border-left-color: var(--kincy-red); }
.kincy-ms-tile--bad .kincy-ms-tile-value { color: var(--kincy-red-ink); }

/* --------------------------------------------------- L'échelle d'occupation */
/* ⚠️ UNE SEULE échelle pour toute la page — barres ET tableau. Deux grammaires
 * de couleur sur un même écran seraient pires que l'ancien seuil unique.
 * Quand le taux est inconnu, le gabarit rend `kincy-ms-band-None`, classe qui
 * n'existe pas : aucune couleur ne s'applique. C'est voulu — on ne peint pas un
 * jugement sur une mesure qu'on n'a pas. */
.kincy-ms-band-full { color: var(--kincy-green-ink); font-weight: 600; }
.kincy-ms-band-high { color: var(--kincy-amber-ink); font-weight: 600; }
.kincy-ms-band-mid { color: var(--kincy-orange-ink); font-weight: 600; }
.kincy-ms-band-low,
.kincy-ms-band-over { color: var(--kincy-red-ink); font-weight: 600; }

/* ------------------------------- `mvt-occupation` : les tuiles de tendance */
/* ⚠️ Ces tuiles réutilisent `.kincy-ms-band-*` ci-dessus — la MÊME grammaire de
 * couleur que les barres et le tableau. Le `color` de la bande est hérité par le
 * `<svg>` via `currentColor`, donc la courbe est de la couleur du taux qu'elle
 * décrit, sans une seule teinte de plus. */
.kincy-ms-trend {
  display: grid;
  /* ⚠️ `auto-fill` et non `auto-fit` : `auto-fit` étire les colonnes existantes
   * pour remplir la ligne, donc un client à UNE licence obtiendrait une tuile
   * large comme la page, dont le sparkline s'aplatirait — l'inverse de ce qu'on
   * cherche. `auto-fill` garde la largeur nominale et laisse du vide. */
  grid-template-columns: repeat(auto-fill, minmax(11rem, 13rem));
  gap: var(--kincy-spacing-md);
}

.kincy-ms-trend-tile {
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-sm);
  padding: var(--kincy-spacing-sm);
  background: var(--bs-body-bg);
  min-width: 0;  /* sans lui, un libellé long pousse la grille hors du cadre */
}

.kincy-ms-trend-name {
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.25;
  margin: 0 0 4px;
  /* Deux lignes puis ellipse : un nom de licence va jusqu'à « Microsoft 365 A5
   * for students use benefit », et une seule ligne n'en dirait rien. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 2.05em;  /* réserve les deux lignes : sinon les tuiles se décalent */
}

.kincy-ms-trend-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px;
}

.kincy-ms-trend-rate { font-size: 1.25rem; line-height: 1.1; }

/* ⚠️ L'écart reste en couleur de CORPS, délibérément. La bande dit déjà « ce
 * taux est bon ou mauvais » ; si l'écart portait lui aussi une couleur, la même
 * tuile enverrait deux signaux concurrents sur la même licence. La direction est
 * portée par le GLYPHE et par le signe, donc jamais par la couleur seule. */
.kincy-ms-trend-delta {
  font-size: 0.75rem;
  color: var(--kincy-neutral-dark);
  white-space: nowrap;
}

.kincy-ms-trend-delta--unknown { font-style: italic; }

.kincy-ms-trend-spark { margin-top: 6px; }

/* ⚠️ `height: auto` et mise à l'échelle UNIFORME : la hauteur découle du rapport
 * de la `viewBox` (100 x 26). Forcer une hauteur en pixels obligerait à étirer le
 * dessin, et un étirement non uniforme rend les points en ovales d'un pixel. */
.kincy-ms-trend-spark svg { display: block; width: 100%; height: auto; }

.kincy-ms-trend-foot {
  font-size: 0.75rem;
  color: var(--kincy-neutral-dark);
  margin: 4px 0 0;
}

/* --------------------------------------------------------------- Les barres */
.kincy-ms-bars {
  display: grid;
  grid-template-columns: minmax(7rem, 15rem) 1fr auto;
  align-items: center;
  gap: 6px var(--kincy-spacing-md);
}

.kincy-ms-bar-label {
  font-size: 0.875rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Le conteneur ancre l'infobulle maison. `relative` posé sur une classe qui
 * n'appartient qu'à ce bloc : il ne touche AUCUNE classe partagée — c'est le
 * mécanisme qui a décollé la barre de la page rentabilité. */
.kincy-ms-bar-wrap { position: relative; cursor: help; }

.kincy-ms-bar {
  display: flex;
  height: 1.6rem;
  border-radius: var(--kincy-radius-sm);
  overflow: hidden;
  background-color: var(--kincy-neutral-light);
}

.kincy-ms-bar-seg {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  overflow: hidden;
}

/* ⚠️ Cerclé : --kincy-cyan est à 2,67:1 sur blanc et ne tient pas les 3:1 de
 * WCAG 1.4.11 pour un élément porteur d'information. Le contour slate porte
 * 7,71:1. Même parade que la pastille de dépassement de la zone BUILD. */
.kincy-ms-bar-seg--assigned {
  background-color: var(--kincy-cyan);
  box-shadow: inset 0 0 0 1px var(--kincy-slate);
  color: #fff;
}

/* ⚠️ UNE SEULE couleur pour le segment libre, quel que soit le taux
 * (2026-09-14, ML-A55). Il changeait de teinte avec la bande : d'une ligne à
 * l'autre, la MÊME grandeur — des sièges payés et inutilisés — apparaissait en
 * quatre couleurs, et l'œil lisait quatre choses là où il n'y en a qu'une. Le
 * jugement vit désormais uniquement dans le pourcentage, en fin de ligne.
 *
 * L'orange atténué, et pas un gris : c'est la teinte que la tuile « sièges non
 * attribués » porte déjà. Une seule grandeur, une seule couleur, sur toute la
 * page — et l'orange garde son sens unique. */
.kincy-ms-bar-seg--free {
  background-color: var(--kincy-orange);
  opacity: 0.55;
  color: var(--kincy-slate);
}

.kincy-ms-bar-rate {
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-align: right;
}

/* ⚠️ Infobulle MAISON, et c'est tout le point : le `title` du navigateur
 * n'apparaît qu'après un arrêt du curseur, et rien n'annonce qu'il existe.
 * Celle-ci sort au survol, immédiatement, et le curseur `help` la laisse
 * deviner avant même de s'arrêter. */
.kincy-ms-bar-tip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  transform: translateX(-50%);
  z-index: 30;
  display: none;
  white-space: nowrap;
  padding: 6px 10px;
  border-radius: var(--kincy-radius-sm);
  background-color: var(--kincy-slate);
  color: #fff;
  font-size: 0.85rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}

.kincy-ms-bar-wrap:hover .kincy-ms-bar-tip,
.kincy-ms-bar-wrap:focus-within .kincy-ms-bar-tip { display: block; }

/* -------------------------------------------------------- Tableaux habillés */
.kincy-ms-grid thead th,
.kincy-ms-grid tfoot th,
.kincy-ms-grid tfoot td {
  background-color: var(--kincy-blue);
  color: #fff;
  border-color: var(--kincy-blue);
}

.kincy-ms-grid tbody th[scope="row"] {
  background-color: var(--kincy-neutral-light);
  font-weight: 500;
}

/* Alternance blanc / bleu très clair sur les `td`. */
.kincy-ms-grid tbody tr:nth-child(even) > td {
  background-color: rgba(226, 239, 244, 0.55);
}

/* ⚠️ L'EN-TÊTE DE LIGNE ALTERNE AUSSI, mais DANS SA PROPRE TEINTE et jamais
 * dans celle des valeurs (demande du 2026-09-14). Il était uniformément à
 * `--kincy-neutral-light` : sur une ligne paire, une colonne de gauche immobile
 * coupait la bande que les `td` dessinaient, et l'œil perdait la ligne à
 * mi-parcours. La teinte paire est le même bleu de charte posé à 8 % sur le
 * neutre — mesuré le 2026-09-14, le libellé y tient 11,86:1 contre 13,14:1 sur
 * la teinte impaire : deux fonds distincts à l'œil, tous deux très loin du
 * plancher. */
.kincy-ms-grid tbody tr:nth-child(even) > th[scope="row"] {
  background-color: #d0e5f0;
}

/* ⚠️ La bande de couleur doit rester lisible SUR les lignes alternées : on la
 * remonte après la règle de fond, à spécificité supérieure, plutôt que de
 * compter sur l'ordre. */
.kincy-ms-grid tbody td.kincy-ms-band-full,
.kincy-ms-grid tbody td.kincy-ms-band-high,
.kincy-ms-grid tbody td.kincy-ms-band-mid,
.kincy-ms-grid tbody td.kincy-ms-band-low,
.kincy-ms-grid tbody td.kincy-ms-band-over { font-weight: 600; }


/* ----------------------------------------- parc-optimisation, passe du 14/09 */

/* La pastille de couleur de licence, en tête de ligne. Même grammaire que
 * `.kincy-profit-swatch` de la zone BUILD — 0,6 rem, radius 2px, décorative.
 * ⚠️ Elle est CERCLÉE, et l'anneau n'est pas décoratif : les couleurs de SKU
 * sont arbitraires (elles sortent d'un hash), donc les plus pâles seraient
 * invisibles sur le blanc du tableau. Le contour slate porte 7,71:1. Même
 * parade que le segment attribué des barres. */
.kincy-ms-swatch {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  margin-right: 0.4rem;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px var(--kincy-slate);
  vertical-align: baseline;
}

/* ⚠️ Les deux en-têtes de mouvement sont des PICTOGRAMMES SUR APLAT BLEU, et
 * c'est le MIROIR de la règle des tokens `-ink`. Là on assombrissait une teinte
 * de charte pour qu'un chiffre tienne 4,5:1 sur du blanc ; ici on l'ÉCLAIRCIT
 * pour qu'un pictogramme tienne les 3:1 de WCAG 1.4.11 sur du bleu. Mesuré le
 * 2026-09-14 sur `--kincy-blue` : `--kincy-green` 1,89:1 et `--kincy-orange`
 * 1,82:1, donc les teintes de charte sont inutilisables ici — elles le seraient
 * restées quelle que soit la taille du glyphe. Qui retouche une valeur refait la
 * mesure, comme pour les `-ink`. */
:root {
  --kincy-green-soft: #bce699;  /* 3,41:1 sur --kincy-blue */
  --kincy-orange-soft: #ffce93; /* 3,32:1 sur --kincy-blue */
}

.kincy-ms-mvt {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-weight: 700;
  line-height: 1;
}

.kincy-ms-mvt--in { color: var(--kincy-green-soft); }
.kincy-ms-mvt--out { color: var(--kincy-orange-soft); }

.kincy-ms-mvt-glyph {
  width: 0.85rem;
  height: 0.85rem;
  /* Le glyphe se cale sur la flèche qui le précède, pas sur la ligne de base du
   * texte : deux hauteurs d'x différentes les décalaient d'un pixel. */
  display: block;
}

/* ----------------------------------------------------------- Le bandeau */
/* L'orange est emprunté au niveau PAGE — la BORDURE — et jamais au niveau
 * cellule. Le texte reste en couleur de corps : --kincy-orange sur du texte ne
 * tient pas les 4,5:1 de WCAG 1.4.3. */
.kincy-ms-alert {
  border: 1px solid var(--kincy-orange);
  border-left-width: 4px;
  border-radius: var(--kincy-radius-md);
  padding: var(--kincy-spacing-md);
  margin-bottom: var(--kincy-spacing-lg);
  background-color: #fff;
}

/* De l'air entre le graphe des porteurs et le tableau qui le suit. */
.kincy-ms-chart-wrap--spaced { margin-bottom: var(--kincy-spacing-xl); }

/* Le conteneur collant embarque le formulaire ET les deux filtres de tableau :
 * en défilant, on garde sous les yeux qu'un filtre est posé. ⚠️ DOUBLE CLASSE,
 * pour ne pas dépendre de l'ordre des feuilles (ML-A41). */
.kincy-run-filterbar.kincy-ms-stickybar {
  display: block;
  gap: 0;
}

.kincy-ms-stickybar .kincy-ms-filterbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kincy-spacing-lg);
  align-items: flex-start;
}

/* ⚠️ LE GROUPE DE CHIPS EST PLAFONNÉ, et ce n'est pas un réglage esthétique.
 * Mesuré le 2026-09-14 : sans plafond, la barre collante faisait 282 px à
 * 1440, **320 px à 1285** et 358 px à 1100 — soit 31 %, 36 % puis 40 % de la
 * hauteur utile. Une bande opaque de cette taille, collée en haut, RECOUVRE la
 * page en défilant : les graphes, hauts de 280 px, passaient entièrement
 * dessous. Le symptôme rapporté était « les graphes cessent de fonctionner et
 * le fond se trouve comme étiré » — c'était la barre, pas les graphes.
 *
 * Trois rangées de chips suffisent à voir ce qui est coché ; au-delà, le groupe
 * défile. Le contrat du collant est de RAPPELER qu'un filtre est posé, pas de
 * tout montrer à la fois. */
.kincy-ms-stickybar .kincy-filter-fieldset .kincy-filter-group {
  max-height: 6.6rem;
  overflow-y: auto;
  /* Un liseré discret dit qu'il y a de la matière sous le pli : sans lui, un
   * groupe coupé net se lit comme un groupe complet. */
  scrollbar-width: thin;
}

/* ⚠️ LE RELIEF N'APPARAÎT QU'UNE FOIS LA BARRE COLLÉE, et c'est tout le point.
 * Au repos elle fait partie de la page : un trait marqué et une ombre y seraient
 * du bruit permanent. Collée, elle RECOUVRE le contenu, et l'ombre est la seule
 * chose qui dise « quelque chose passe dessous » — le filet de 1 px à 12 %
 * d'opacité, lui, disparaît sur un fond clair.
 *
 * ⚠️ LA TEINTE RESTE NEUTRE, et ce n'est pas un défaut d'ambition : le filet
 * cyan de cette même barre est déjà un signal, « au moins un filtre est posé ».
 * Deux traits colorés de 2 px sous la même barre ne se distinguent pas d'un coup
 * d'œil, et le second aurait vidé le premier de son sens — c'est la règle du
 * dépôt, une couleur de signal ne se partage pas entre deux significations sur
 * une même page. Le relief vient donc de l'OMBRE, qui ne signale rien.
 *
 * ⚠️ La classe est posée en JavaScript, par un IntersectionObserver sur une
 * sentinelle : `position: sticky` n'expose aucun état au CSS. */
/* ⚠️ Le relief à l'accroche a DÉMÉNAGÉ le 2026-09-14 : il est devenu le standard
 * `.kincy-stickybar` / `.kincy-sticky-sentinel`, en tête de feuille avec les
 * autres règles de barre collante. Les classes `.kincy-ms-stickybar--stuck` et
 * `.kincy-ms-sticky-sentinel` n'existent plus — cette page emploie les
 * génériques, comme « Rentabilité des projets ». Rien à rétablir ici. */

.kincy-ms-stickybar .kincy-ms-tablefilters {
  margin: var(--kincy-spacing-sm) 0 0;
}

/* L'identifiant du bloc de tuiles se pose au-dessus de la rangée : dans la
 * première tuile, il se lisait comme une partie de son libellé. */
.kincy-ms-tiles-id { margin: 0 0 2px; text-align: right; }

/* Les intertitres de bloc. Ils manquaient de présence : le lecteur descendait
 * la page sans voir où un bloc finissait et où le suivant commençait. */
/* ---------------------------------------------------------------------------
 * L'INTERTITRE D'UN ÉCRAN DE REPORTING — convention, `docs/conventions/ui.md`
 * §4.10. Généralisée le 2026-09-17 depuis la page M365, où elle est née le
 * 2026-09-14 : c'est la même grammaire sur « Rentabilité des projets »,
 * « Rapport rentabilité » et « Occupation des agents ».
 *
 * ⚠️ **Typographie SEULE, pas d'espacement.** Un intertitre de section de page
 * (M365) et un titre de carte (BUILD) n'ont pas les mêmes marges : la marge
 * haute `xl` du premier creuserait un trou en haut de chaque carte du second.
 * Chaque page garde donc sa classe d'espacement à côté de celle-ci.
 *
 * ⚠️⚠️ **`--kincy-cyan` est à 2,67:1 sur blanc** : sous les 4,5:1 de WCAG 1.4.3
 * pour du texte courant, MAIS au-dessus des 3:1 exigés pour du **texte large**
 * (>= 18,66 px en gras, ou 24 px). À 1,35 rem en 600, ces intertitres tombent
 * dans cette catégorie — c'est la raison pour laquelle la teinte de charte peut
 * être posée telle quelle ici alors qu'elle ne le peut pas sur un chiffre de
 * tuile. **Réduire leur taille ou leur graisse casserait cette conformité** ;
 * ce n'est pas un réglage esthétique libre.
 * Arbitrage : posé tel quel à la demande de Rodolphe, « on verra si en terme
 * d'accessibilité il faudra ajuster plus tard ».
 * ------------------------------------------------------------------------- */
.kincy-report-h2 {
  color: var(--kincy-cyan);
  font-size: 1.35rem;
  font-weight: 600;
}

/* L'espacement propre aux intertitres de la page M365. */
.kincy-ms-h2 {
  margin-top: var(--kincy-spacing-xl);
  margin-bottom: var(--kincy-spacing-md);
}

/* ===========================================================================
   « Occupation des agents » (zone Rapports) — 2026-09-16
   ---------------------------------------------------------------------------
   La barre de filtres réutilise `.kincy-run-filterbar` + `.kincy-stickybar` du
   socle partagé : aucune règle de position ici.

   ⚠️ NE JAMAIS poser `position: relative` sur `.kincy-occ-filterbar`. Elle
   partage le nœud avec `.kincy-run-filterbar`, qui porte le `sticky` ; à
   spécificité égale, la dernière feuille gagne et la barre CESSE DE COLLER —
   sans erreur, sans test rouge, avec une capture identique. La page BUILD a payé
   exactement ce défaut, et c'est l'utilisateur qui l'a vu.
   =========================================================================== */

.kincy-occ-filterbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--kincy-spacing-md);
}

.kincy-occ-frame { min-width: 0; }
.kincy-occ-frame-control { min-width: 15rem; }
.kincy-occ-reset { margin-left: auto; align-self: center; }

.kincy-occ-chips { display: flex; flex-wrap: wrap; gap: var(--kincy-spacing-xs); }

/* Les chips reprennent les couleurs de RÔLE des deux autres écrans : ce sont
   les mêmes mots, ils doivent être de la même couleur d'une page à l'autre.
   Le cyan est exclu — c'est l'état « sélectionné » de tout Link. */
.kincy-occ-chip { cursor: pointer; }
/* ⚠️⚠️ UN FOND PAR DÉFAUT, et ce n'est pas de la ceinture : cette règle met
   l'encre en BLANC pour toute chip cochée, et chaque modificateur devait
   fournir son fond. Un groupe de chips ajouté sans le sien sortait donc
   **blanc sur blanc** — la chip cochée devenait illisible, sans erreur et sans
   test rouge. Vécu le 2026-10-07 sur les chips de régime, signalé par Rodolphe
   en production. Le défaut était dans la FORME de la règle, pas dans l'oubli :
   une déclaration qui impose une encre doit imposer son fond avec elle.
   Convention : `docs/conventions/ui.md` §4.14. */
.kincy-occ-chip:has(input:checked) {
  color: #fff;
  border-color: transparent;
  background: var(--kincy-slate);
}
.kincy-occ-chip--run:has(input:checked)     { background: var(--kincy-blue); }
.kincy-occ-chip--build:has(input:checked)   { background: var(--kincy-magenta); }
.kincy-occ-chip--manage:has(input:checked)  { background: var(--kincy-slate); }
.kincy-occ-chip--other:has(input:checked)   { background: var(--kincy-neutral-dark); }
/* L'agence marque l'AXE, pas la valeur : une seule teinte pour tout le groupe,
   sentinelle comprise. Le département dit *quelle valeur* par sa teinte,
   l'agence dit *quel axe* — deux métiers, deux mécanismes, aucune collision. */
.kincy-occ-chip--agency:has(input:checked)  { background: var(--kincy-teal); }

.kincy-occ-chip:has(input:focus-visible) {
  outline: 2px solid var(--kincy-cyan);
  outline-offset: 2px;
}

.kincy-occ-section { margin-bottom: var(--kincy-spacing-lg); }
/* ⚠️ Plus de `font-size` ni de `font-weight` ici : ils viennent de
   `.kincy-report-h2`, que ces titres portent aussi. Les redéclarer les ferait
   GAGNER — les deux sélecteurs pèsent (0,1,0) et celui-ci est écrit plus bas
   dans la feuille — donc la convention serait écrite et inerte sur cette page.
   La classe reste : la sonde s'en sert comme sélecteur, et l'espacement est
   propre au rapport. */
.kincy-occ-heading { margin-bottom: var(--kincy-spacing-sm); }

/* --- Les tuiles de tête ----------------------------------------------------
 * Grammaire reprise des tuiles de la page « Comptes & licences M365 »
 * (`.kincy-ms-tile`, 2026-09-14), à la demande du 2026-09-17 : fond blanc, filet
 * de couleur sur le seul bord gauche, libellé en petites capitales, chiffre
 * détaché, notes en dessous.
 *
 * ⚠️⚠️ **LE FILET PORTE LA COULEUR, JAMAIS LE CHIFFRE.** C'est la règle qui
 * gouverne tout le bloc M365 et elle vaut ici telle quelle : une teinte de
 * charte tient les 3:1 d'un élément d'interface, pas les 4,5:1 d'un texte.
 * `--kincy-trend-0` (#2D7FF9) est à 3,81:1 sur blanc : parfait pour un filet,
 * (⚠️ corrigé le 2026-09-28 : ce commentaire annonçait 3,3:1 depuis le
 *  2026-09-16, et le chiffre a été recopié dans quatre autres endroits avant
 *  d'être recalculé. Méthode : luminance relative sRGB / WCAG 2.x, ÉTALONNÉE —
 *  elle reproduit exactement les six ratios annotés sur `--kincy-agency-N`
 *  ci-dessous. La conclusion, elle, n'a jamais changé : sous le seuil AA.)
 * illisible pour un taux. Le chiffre reste donc en encre courante sur toutes
 * les tuiles, et la couleur ne dit que « de qui on parle ».
 *
 * ⚠️ Les règles ne sont PAS mutualisées avec `.kincy-ms-tile` : ce sont deux
 * pages, deux fichiers de gabarit et deux séries de modificateurs, et une
 * classe commune ferait qu'un réglage demandé sur l'une déplacerait l'autre
 * sans qu'on l'ait vu. Ce qui se partage est la grammaire, écrite ici et dans
 * `docs/conventions/ui.md`, pas le sélecteur. */
.kincy-occ-tiles { display: flex; flex-wrap: wrap; gap: var(--kincy-spacing-md); }

.kincy-occ-tile {
  min-width: 12rem;
  padding: var(--kincy-spacing-md);
  border: 1px solid var(--kincy-neutral-medium);
  /* Le filet de gauche porte la teinte de l'axe : un seul côté, comme M365.
     Sa couleur par défaut est neutre — une tuile dont l'axe n'a pas de teinte
     connue garde un filet gris plutôt qu'une couleur inventée. */
  border-left-width: 5px;
  border-radius: var(--kincy-radius-md);
  background-color: #fff;
  text-align: center;
}

.kincy-occ-tile-label {
  margin-bottom: 0.15rem;
  color: var(--kincy-neutral-dark);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.kincy-occ-tile-rate {
  font-size: 2.4rem;
  font-weight: 700;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.kincy-occ-tile-sub   { font-size: .8125rem; color: var(--kincy-neutral-dark); }
.kincy-occ-tile-count { font-size: .75rem; color: var(--kincy-slate); margin-top: .25rem; }

/* Le département dit QUELLE VALEUR par sa teinte : les mêmes couleurs de rôle
   que ses chips et que les pastilles R/B/M du tableau. Les mêmes mots doivent
   être de la même couleur d'un bout à l'autre de la page. */
/* ⚠️ **Ces quatre règles lisent les tokens de RÔLE, et pas les couleurs
   brutes** — corrigé le 2026-09-30. Elles portaient `--kincy-blue`,
   `--kincy-magenta`, `--kincy-slate` et `--kincy-neutral-dark` en direct,
   c'est-à-dire les mêmes valeurs que `--kincy-trend-run/build/manage/other`
   mais par un autre chemin. Les deux étaient d'accord **par coïncidence**, et
   changer la couleur de Manage aurait repeint sa courbe en laissant sa tuile
   sur l'ancienne teinte. Le commentaire ci-dessus promettait « les mêmes
   couleurs d'un bout à l'autre de la page » ; il le promettait sans le tenir. */
.kincy-occ-tile--run    { border-left-color: var(--kincy-trend-run); }
.kincy-occ-tile--build  { border-left-color: var(--kincy-trend-build); }
.kincy-occ-tile--manage { border-left-color: var(--kincy-trend-manage); }
.kincy-occ-tile--other  { border-left-color: var(--kincy-trend-other); }

/* ⚠️ Le trigramme reste en sarcelle : sur cette page la sarcelle marque l'AXE
   agence, et le filet dit QUELLE agence. Deux mécanismes, aucune collision. */

.kincy-occ-code {
  font-family: var(--bs-font-monospace, monospace);
  font-size: .75rem;
  font-weight: 700;
  color: var(--kincy-teal);
}

/* La pastille d'appartenance. Les trois lettres reprennent les couleurs de rôle
   des chips : sur cette page comme sur les deux autres, RUN est bleu. */
.kincy-occ-badge-col { width: 2rem; }
.kincy-occ-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem; height: 1.5rem;
  border-radius: 50%;
  font-size: .75rem; font-weight: 700;
  color: #fff;
}
.kincy-occ-badge--run    { background: var(--kincy-blue); }
.kincy-occ-badge--build  { background: var(--kincy-magenta); }
.kincy-occ-badge--manage { background: var(--kincy-slate); }
.kincy-occ-badge--other  { background: var(--kincy-neutral-medium); color: var(--kincy-slate); }

/* La MÊME pastille, en petit, pour la colonne « Appartenance » du détail
 * mensuel (2026-10-06). Le tableau y est déjà en .8125rem et rend jusqu'à
 * douze lignes : la pastille de 1,5 rem du tableau central y devenait le
 * cartouche que Rodolphe ne voulait pas. Même couleur, même glyphe, même
 * grammaire — seule la taille change, donc on ne redéclare que ça. */
.kincy-occ-badge--sm {
  width: 1.05rem; height: 1.05rem;
  font-size: .625rem;
  vertical-align: baseline;
}

/* La cellule d'appartenance datée. Elle ne s'étire pas : sa largeur est celle
 * de « éclat + trigramme + pastille », et le reste du tableau garde la place
 * pour ses chiffres. */
.kincy-occ-membcol { width: 6.5rem; }
.kincy-occ-memb { white-space: nowrap; }

/* ⚠️ LE CHANGEMENT D'APPARTENANCE, et c'est la raison d'être de la colonne :
 * répéter le trigramme douze fois rend une mutation lisible, mais seulement si
 * l'on compare deux lignes à l'œil. Le filet dit « c'est ici que ça change »,
 * sur le PREMIER mois du nouveau rattachement.
 *
 * Un filet et non une teinte de fond : le fond du `tbody` porte déjà
 * l'alternance, et deux fonds superposés se lisent comme un défaut de rendu. */
.kincy-occ-memb--shift {
  border-top: 2px solid var(--kincy-slate);
}

/* LE MARQUEUR D'ALTERNANCE, dans la colonne d'appartenance (2026-10-06).
 *
 * Un libellé court en encre, PAS un cartouche plein : la ligne porte déjà un
 * éclat et une pastille, et un troisième aplat de couleur ferait une cellule
 * plus bavarde que le chiffre qu'elle qualifie. L'orange est celui des signaux
 * de la page (`.kincy-occ-caveat`), parce que ce marqueur dit la même chose :
 * « ce chiffre a une réserve ». */
.kincy-occ-scheme {
  font-size: .625rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--kincy-orange);
  cursor: help;
}

/* La chip de régime ne porte AUCUN éclat de couleur, contrairement à celle de
 * l'agence : le régime n'a ni courbe, ni tuile, ni anneau à rapprocher. Elle
 * garde donc la géométrie commune et rien d'autre — la règle existe pour dire
 * que son absence d'éclat est voulue, pas oubliée. */
/* Le RÉGIME marque l'AXE, comme l'agence — une seule teinte pour le groupe,
   parce que ses valeurs n'ont de couleur nulle part ailleurs sur la page. Le
   violet, et non le sarcelle de l'agence : deux axes de même teinte se liraient
   comme un seul. Mesuré : blanc sur `--kincy-purple` (#6F2282) rend **9,43:1**,
   recalculé par la méthode étalonnée du bloc des `--kincy-agency-N`.
   ⚠️ Aucun éclat de couleur sur cette chip, en revanche : le régime n'a ni
   courbe, ni tuile, ni anneau à rapprocher. */
.kincy-occ-chip--scheme:has(input:checked) { background: var(--kincy-purple); }

.kincy-occ-name { text-decoration: none; font-weight: 500; }
.kincy-occ-name:hover { text-decoration: underline; }

/* Signal ADDITIF : il s'ajoute au chiffre, il ne le remplace jamais. Un
   pictogramme qui masquerait la valeur ferait perdre l'information qu'il
   qualifie — la leçon du halo d'erreur de l'étoile de favori. */
.kincy-occ-caveat { color: var(--kincy-orange); font-size: .875rem; cursor: help; }
.kincy-occ-days {
  font-size: .625rem; font-weight: 700; cursor: help;
  color: var(--kincy-slate); vertical-align: super;
}

.kincy-occ-detail-row > td { border-top: 0; }
.kincy-occ-detail {
  padding: var(--kincy-spacing-sm) var(--kincy-spacing-md);
  background: var(--kincy-neutral-light);
  border-left: 3px solid var(--kincy-cyan);
}
.kincy-occ-detail-head { font-size: .875rem; margin-bottom: var(--kincy-spacing-xs); }
.kincy-occ-subtable { font-size: .8125rem; background: #fff; }
.kincy-occ-typetable { font-size: .75rem; background: #fff; }
.kincy-occ-itil {
  background: var(--kincy-neutral-medium);
  font-weight: 600;
}
.kincy-occ-dayblock { margin-top: var(--kincy-spacing-sm); }
.kincy-occ-tickets { font-size: .75rem; }
.kincy-occ-fold { font-size: .75rem; }

.kincy-occ-footnote { font-size: .75rem; color: var(--kincy-slate); margin: .25rem 0 0; }

.kincy-occ-notes {
  font-size: .75rem;
  color: var(--kincy-slate);
  border-top: 1px solid var(--kincy-neutral-medium);
  padding-top: var(--kincy-spacing-sm);
  margin-top: var(--kincy-spacing-sm);
}
.kincy-occ-notes p { margin-bottom: .35rem; }

/* --- « Occupation des agents » : retouches d'usage (2026-09-16) -----------
 *
 * Sept demandes de Rodolphe au premier contact avec la page en production.
 * ------------------------------------------------------------------------ */

/* 1. LA BARRE DE FILTRES, harmonisée avec « Rentabilité des projets ».
 *
 * ⚠️ Le filet du bas n'était PAS en cause : mesuré au style CALCULÉ le
 * 2026-09-16, les deux pages portent exactement `1px solid rgba(68,84,106,.12)`
 * et aucune n'était `--dirty`. Ce qui manquait, ce sont les CADRES : BUILD pose
 * ses groupes dans des `fieldset` bordés, donc la structure est portée par eux
 * et le filet disparaît dans le dessin. Ici les groupes n'avaient aucune
 * bordure, et le filet devenait le seul élément visible — d'où la lecture « une
 * barre bleue en bas ». On ne touche donc pas au filet : on rend les cadres. */
/* ⚠️ CONVENTION DES RAPPORTS : la RESPIRATION d'un cadre de filtres.
 *
 * `--kincy-spacing-md` sur les quatre côtés, et la légende décollée du contenu.
 * Relevé le 2026-09-16 sur « Rentabilité des projets », qui avait déjà la bonne
 * mesure : 12 px de rembourrage. Mon premier jet en mettait 4 en haut et 8 sur
 * les côtés, et le filet venait mordre les chips.
 *
 * La règle : un cadre qui encadre des CONTRÔLES (chips, interrupteurs) a besoin
 * d'au moins une gouttière complète, parce que la bordure entre en concurrence
 * visuelle avec celle des chips. Un cadre qui encadre du TEXTE peut serrer. */
.kincy-occ-frame {
  min-width: 0;
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-md);
  padding: var(--kincy-spacing-sm) var(--kincy-spacing-md) var(--kincy-spacing-md);
}

.kincy-occ-frame > .kincy-filter-legend,
.kincy-occ-frame > legend {
  float: none;
  width: auto;
  padding: 0 var(--kincy-spacing-xs);
  /* La légende interrompt le tracé du cadre : sans cette marge, les chips
   * remontent contre elle et le groupe se lit comme un bloc compact. */
  margin-bottom: var(--kincy-spacing-xs);
}

/* 2. L'AGENT QUI A QUITTÉ L'EFFECTIF.
 *
 * ⚠️ Il RESTE dans le tableau : ses heures ont été produites, et les masquer
 * ferait mentir tous les totaux de la période. Seul son nom s'atténue.
 * L'information ne passe pas que par la couleur — il y a un pictogramme, une
 * infobulle datée, et un texte pour lecteur d'écran. */
.kincy-occ-name--left { color: var(--kincy-slate); opacity: .7; font-weight: 400; }
.kincy-occ-name--left:hover { opacity: 1; }
.kincy-occ-left { color: var(--kincy-slate); font-size: .8125rem; cursor: help; }

/* 3. LA SENTINELLE D'AGENCE.
 *
 * « ? » et non « Sans agence » : la colonne est étroite, son intérêt est le
 * trigramme, et six syllabes écrasaient les trois lettres des autres lignes. */
.kincy-occ-code--none { color: var(--kincy-neutral-dark); cursor: help; }

/* 4. LE CHEVRON DE REPLI, sur les deux niveaux de drill-down.
 *
 * Il tourne, il ne change pas de glyphe : deux caractères différents se lisent
 * comme deux boutons différents. */
.kincy-occ-chevron {
  display: inline-block;
  font-size: .75rem;
  transition: transform .15s ease;
  transform: rotate(0deg);
}
.kincy-occ-chevron--open { transform: rotate(90deg); }
.kincy-occ-toggle { text-decoration: none; }

/* 5. L'ALTERNANCE DES LIGNES — une teinte par NIVEAU de lecture.
 *
 * ⚠️ Elle porte sur les `tbody` et non sur les `tr` : chaque agent (ou chaque
 * mois) a le sien, donc la parité compte des AGENTS. Sur des `tr`, une ligne de
 * détail dépliée décalerait la parité de tout ce qui suit — l'alternance
 * sauterait à chaque ouverture.
 *
 * Trois teintes, une par profondeur : neutre pour le parc, cyan pour le mois,
 * violet pour le jour. Elles reprennent les bordures de gauche des encadrés,
 * pour qu'un coup d'œil dise à quel niveau on se trouve. Très basses en
 * saturation : c'est un repère de lecture, pas un code couleur. */
.kincy-occ-table > tbody:nth-of-type(odd) { background: rgba(68, 84, 106, .055); }
/* ⚠️ Teinte ABAISSÉE le 2026-09-16 : le cyan de la charte est une couleur de
 * SIGNAL (« sélectionné »), et à .075 il tirait l'œil sur une simple alternance
 * de lecture. On garde la teinte — elle dit le niveau — en divisant l'intensité
 * par deux et demi. */
.kincy-occ-subtable > tbody:nth-of-type(odd) { background: rgba(0, 174, 239, .03); }
.kincy-occ-dayblock .kincy-occ-subtable tbody tr:nth-of-type(odd) {
  background: rgba(111, 34, 130, .07);
}

/* ⚠️ Bootstrap pose `--bs-table-bg` sur les cellules, qui RECOUVRE le fond du
 * `tbody`. Sans cette neutralisation, l'alternance est écrite et ne se voit
 * pas — une règle inerte, ce que le dépôt tient pour pire qu'une règle absente.
 * On rend les cellules transparentes pour laisser passer le fond du groupe. */
.kincy-occ-table > tbody > tr > td,
.kincy-occ-table > tbody > tr > th,
.kincy-occ-subtable > tbody > tr > td,
.kincy-occ-subtable > tbody > tr > th { background-color: transparent; }

/* 6. LE NIVEAU 2 se distingue du niveau 1 : retrait à gauche et bordure
 *    VIOLETTE, là où le niveau 1 porte la cyan. Deux profondeurs, deux
 *    couleurs, et le retrait dit l'imbrication. */
.kincy-occ-dayblock {
  margin: var(--kincy-spacing-sm) 0 var(--kincy-spacing-sm) var(--kincy-spacing-lg);
  padding: var(--kincy-spacing-sm) var(--kincy-spacing-md);
  background: #fff;
  border-left: 3px solid var(--kincy-purple);
}

/* 7. LE TRI, sur le patron de « Suivi des projets ».
 *
 * ⚠️ Le chevron a une opacité CALCULÉE lisible : un attribut `aria-sort` peut
 * être posé sans que rien ne se voie, et c'est le défaut qu'une sonde lit. */
.kincy-occ-sortbtn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  white-space: nowrap;
}
.kincy-occ-sortbtn:hover { text-decoration: underline; }
/* ⚠️ Une flèche TOUJOURS visible, même au repos : un en-tête qui ne montre
 * rien ne dit pas qu'il est cliquable, et personne ne découvre un tri par
 * hasard. Atténuée à .35 pour rester un indice et non un signal.
 *
 * ⚠️ On reprend les TRIANGLES (U+25B4 / U+25BE), déjà employés par le chevron de
 * repli de cette page — donc dont le rendu est prouvé. La flèche haut-bas
 * U+2195 a été essayée et ÉCARTÉE : la police de Link ne la porte pas, et elle
 * sortait en « 95 » dans chaque en-tête (constaté en capture le 2026-09-16).
 * Un caractère absent ne lève rien, il affiche ses derniers chiffres. */
.kincy-occ-arrow { margin-left: .2rem; opacity: .35; }
.kincy-occ-arrow::after { content: "\25BE"; }
.kincy-occ-sortable[aria-sort="ascending"] .kincy-occ-arrow,
.kincy-occ-sortable[aria-sort="descending"] .kincy-occ-arrow { opacity: 1; }
.kincy-occ-sortable[aria-sort="ascending"] .kincy-occ-arrow::after { content: "\25B4"; }
.kincy-occ-sortable[aria-sort="descending"] .kincy-occ-arrow::after { content: "\25BE"; }


/* --- La DISTRIBUTION du temps noté (2026-09-16) ---------------------------
 *
 * ⚠️ Six teintes de SEGMENT, et elles ne réutilisent aucune couleur de signal
 * de Link : ni le cyan (« sélectionné »), ni l'orange (« à surveiller »), ni le
 * rouge (« dépassé »). Une barre de distribution ne signale rien — elle
 * décrit — donc elle ne doit pas emprunter le vocabulaire de l'alerte.
 *
 * L'ordre visuel va du terrain au diffus, le non facturable fermant la marche
 * en gris : c'est le seul segment qui n'est pas une nature de travail mais une
 * absence de facturation. */
:root {
  /* ⚠️ HORS CHARTE, et c'est une autorisation explicite de Rodolphe
   * (2026-09-16) : « même hors charte, les teintes ne ressortent pas assez ».
   * La première série était désaturée et les segments se confondaient.
   *
   * ⚠️ Les trois couleurs de SIGNAL de Link restent hors du jeu : le cyan
   * (« sélectionné », porté par les chips de cette page même), l'orange
   * (« à surveiller », porté par le ⚠ de la colonne voisine) et le rouge
   * (« dépassé »). Une barre de distribution DÉCRIT, elle ne signale rien —
   * lui prêter le vocabulaire de l'alerte ferait lire une anomalie là où il n'y
   * a qu'une répartition. Les six teintes ci-dessous en sont toutes distantes.
   *
   * ⚠️ Le non facturable reste le seul GRIS, et délibérément : c'est le seul
   * segment qui ne décrit pas une nature de travail mais une absence de
   * facturation. Il est relevé de #C9CFD8 à #8895AA pour se voir, sans devenir
   * une couleur parmi les autres. */
  --kincy-seg-intervention: #00C2A8;  /* turquoise vif : le terrain */
  --kincy-seg-support:      #2D7FF9;  /* bleu franc : le flux quotidien */
  --kincy-seg-mco:          #9B51E0;  /* violet vif : la maintenance */
  --kincy-seg-project:      #F2496B;  /* framboise : le projet */
  --kincy-seg-other:        #FFB020;  /* ambre : le reste facturable */
  --kincy-seg-non_billable: #8895AA;  /* gris franc : hors facturation */
  --kincy-seg-billable:     #2D7FF9;  /* camembert à deux parts */
}

/* Resserree le 2026-09-16 : a 10rem, la 11e colonne poussait le tableau hors
 * du cadre a 1440 px et « Distribution » se retrouvait coupee. Le tableau
 * defile (`.table-responsive`), donc rien n'etait perdu -- mais une colonne
 * qu'il faut aller chercher n'est pas lue. */
.kincy-occ-distcol { width: 7rem; min-width: 5.5rem; }

.kincy-occ-bar {
  display: flex;
  height: .7rem;
  width: 100%;
  border-radius: 999px;
  overflow: hidden;
  background: var(--kincy-neutral-light);
}
.kincy-occ-bar-seg { display: block; height: 100%; }

.kincy-occ-pies {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kincy-spacing-lg);
  margin-top: var(--kincy-spacing-md);
}
.kincy-occ-pie { display: flex; align-items: center; gap: var(--kincy-spacing-sm); margin: 0; }
/* ⚠️ Une hauteur EXPLICITE : un camembert dans un conteneur sans hauteur sort
 * vide, et c'est le défaut que le dépôt a déjà payé côté Chart.js. */
.kincy-occ-pie-svg { width: 7rem; height: 7rem; flex: none; }
.kincy-occ-pie-title { font-size: .8125rem; font-weight: 600; display: block; }
.kincy-occ-legend { list-style: none; margin: .25rem 0 0; padding: 0; font-size: .75rem; }
.kincy-occ-legend li { display: flex; align-items: center; gap: .35rem; }
.kincy-occ-swatch {
  width: .6rem; height: .6rem; border-radius: 2px; flex: none;
}

/* --- `occ-distribution-agences` : la grille de comparaison entre agences ----
 *
 * ⚠️ `auto-fill` et non `auto-fit` : `auto-fit` étire les colonnes existantes
 * pour remplir la ligne, donc deux agences seules occuperaient la page entière
 * et leurs anneaux flotteraient au milieu de deux grands vides. Même piège que
 * les tuiles de tendance de la page M365, et même parade.
 *
 * ⚠️ La taille du `<svg>` est FIXE et déclarée en hauteur comme en largeur : un
 * anneau dans un conteneur sans hauteur sort vide, défaut que le dépôt a déjà
 * payé côté Chart.js. */
.kincy-occ-agency-pies {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: var(--kincy-spacing-md);
  margin-bottom: var(--kincy-spacing-sm);
}

.kincy-occ-agency-pie {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
  margin: 0;
  min-width: 0;  /* sans lui, un nom long pousse la grille hors du cadre */
}

.kincy-occ-agency-pie .kincy-occ-pie-svg { width: 6.5rem; height: 6.5rem; }

.kincy-occ-agency-caption {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .1rem;
  text-align: center;
  min-width: 0;
}

.kincy-occ-agency-name {
  display: flex;
  align-items: center;
  gap: .35rem;
  font-size: .8125rem;
  font-weight: 600;
  line-height: 1.2;
}

/* Le volume, en heures. ⚠️ Il n'est PAS encodé dans la taille de l'anneau :
   tous ont le même diamètre, parce qu'un anneau compare des parts. Le chiffre
   est donc le seul porteur du volume, et il ne doit pas s'effacer. */
.kincy-occ-agency-total {
  font-size: .8125rem;
  color: var(--kincy-neutral-dark);
  font-variant-numeric: tabular-nums;
}

/* La légende du bloc : une seule, en ligne, sous la grille.
   ⚠️ La gouttière HORIZONTALE est large (`lg`) : à `md`, le libellé d'une entrée
   touchait presque l'éclat de la suivante et les quatre seaux se lisaient comme
   une seule phrase. Une légende en ligne a besoin de plus d'air qu'une légende
   en colonne, où le retour à la ligne sépare déjà. */
.kincy-occ-legend--row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--kincy-spacing-xs) var(--kincy-spacing-lg);
  margin-top: var(--kincy-spacing-xs);
}

/* --- Le tableau par type ITIL : en-têtes colorés, colonnes égales ---------- */

/* ⚠️ Les groupes ITIL prennent des fonds DIFFÉRENTS, en alternance sur deux
 * teintes neutres et non une couleur par type : l'univers est OUVERT (neuf
 * types au référentiel, et il en est apparu un le 2026-09-16), donc une table
 * de couleurs par nom se viderait au premier ajout. Ce qu'on veut dire est
 * « ces colonnes vont ensemble », pas « ce groupe est celui-ci ». */
/* ⚠️ Une couleur par GROUPE ITIL, attribuée par INDEX de l'univers observé et
 * jamais par nom. L'univers est OUVERT — le référentiel est passé de huit à
 * neuf types le 2026-09-16 — donc une table couleur↔nom se viderait au premier
 * ajout. Même mécanisme que les couleurs d'agence de la zone BUILD : six
 * emplacements, attribués par position, qui recyclent au-delà.
 *
 * L'en-tête porte la teinte PLEINE, les colonnes sous lui une version PASTEL de
 * la même : c'est ce qui rattache visuellement une colonne à son groupe quand
 * le tableau en porte six et qu'on lit une ligne au milieu. */
:root {
  --kincy-itil-0: #2D7FF9;
  --kincy-itil-1: #00A88F;
  --kincy-itil-2: #9B51E0;
  --kincy-itil-3: #F2496B;
  --kincy-itil-4: #E8830C;
  --kincy-itil-5: #5B6B7F;
  --kincy-itil-0-soft: #E4EEFE;
  --kincy-itil-1-soft: #DEF3EF;
  --kincy-itil-2-soft: #F0E6FA;
  --kincy-itil-3-soft: #FDE6EB;
  --kincy-itil-4-soft: #FCEEDC;
  --kincy-itil-5-soft: #E9ECF0;
}

/* ⚠️⚠️ LE SÉLECTEUR EST PRÉFIXÉ PAR LA TABLE, ET C'EST OBLIGATOIRE.
 *
 * Bootstrap pose `background-color` sur les cellules via
 * `.table > :not(caption) > * > *`, qui pèse **(0,1,1)**. Une classe simple
 * comme `.kincy-occ-itil--0` pèse (0,1,0) : elle PERD, et la règle est écrite
 * sans rien peindre. Mesuré le 2026-09-16 au style calculé — l'en-tête ITIL
 * rendait `rgb(255,255,255)` et du texte noir alors que la feuille demandait un
 * fond bleu et du blanc. Les cellules, elles, marchaient déjà : leur sélecteur
 * portait le préfixe de table, donc (0,2,1).
 *
 * C'est la MÊME famille que l'alternance recouverte par `--bs-table-bg` : sur
 * un tableau Bootstrap, toute règle de fond doit être plus spécifique que
 * (0,1,1), sans quoi elle est inerte — et une règle inerte est pire qu'une
 * règle absente, parce qu'on la croit en place. */
.kincy-occ-typetable th.kincy-occ-itil {
  border-left: 2px solid var(--bs-body-bg);
  color: #fff;
}
.kincy-occ-typetable th.kincy-occ-itil--0 { background-color: var(--kincy-itil-0); }
.kincy-occ-typetable th.kincy-occ-itil--1 { background-color: var(--kincy-itil-1); }
.kincy-occ-typetable th.kincy-occ-itil--2 { background-color: var(--kincy-itil-2); }
.kincy-occ-typetable th.kincy-occ-itil--3 { background-color: var(--kincy-itil-3); }
.kincy-occ-typetable th.kincy-occ-itil--4 { background-color: var(--kincy-itil-4); }
.kincy-occ-typetable th.kincy-occ-itil--5 { background-color: var(--kincy-itil-5); }

/* La version PASTEL, portée par la seconde ligne d'en-tête ET par les cellules
 * de la colonne — c'est elle qui fait tenir le groupe ensemble sur toute la
 * hauteur du tableau. */
.kincy-occ-typetable th.kincy-occ-tint--0,
.kincy-occ-typetable td.kincy-occ-tint--0 { background: var(--kincy-itil-0-soft); }
.kincy-occ-typetable th.kincy-occ-tint--1,
.kincy-occ-typetable td.kincy-occ-tint--1 { background: var(--kincy-itil-1-soft); }
.kincy-occ-typetable th.kincy-occ-tint--2,
.kincy-occ-typetable td.kincy-occ-tint--2 { background: var(--kincy-itil-2-soft); }
.kincy-occ-typetable th.kincy-occ-tint--3,
.kincy-occ-typetable td.kincy-occ-tint--3 { background: var(--kincy-itil-3-soft); }
.kincy-occ-typetable th.kincy-occ-tint--4,
.kincy-occ-typetable td.kincy-occ-tint--4 { background: var(--kincy-itil-4-soft); }
.kincy-occ-typetable th.kincy-occ-tint--5,
.kincy-occ-typetable td.kincy-occ-tint--5 { background: var(--kincy-itil-5-soft); }

/* Colonnes de LARGEUR ÉGALE, quitte à passer le libellé sur deux lignes.
 * `table-layout: fixed` est ce qui le rend vrai : sans lui, le navigateur
 * répartit selon le contenu et « Prestation (hors contrat - forfait) » écrase
 * ses voisines. */
.kincy-occ-typetable { table-layout: fixed; width: 100%; }
.kincy-occ-typetable th:first-child,
.kincy-occ-typetable td:first-child { width: 9rem; }
.kincy-occ-typecol {
  /* ⚠️ `overflow-wrap: anywhere` est ce qui coupe VRAIMENT. Avec
   * `table-layout: fixed`, un libellé qui ne casse pas ne rétrécit pas la
   * colonne : il DÉBORDE sur la voisine — d'où « Projet (en attente de
   * commande) » qui chevauchait « Projet (dans le contrat) » (signalé le
   * 2026-09-16). `word-break: break-word` seul ne suffisait pas.
   * `overflow: hidden` est le filet de sécurité : même un mot indivisible
   * reste dans sa colonne. */
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: normal;
  hyphens: auto;
  overflow: hidden;
  font-size: .6875rem;
  line-height: 1.2;
  vertical-align: bottom;
  padding: .3rem .35rem;
}

/* --- Les INFOBULLES JavaScript (convention des rapports) ------------------
 *
 * ⚠️ Le `title` natif attend ~1 s avant de s'afficher, ne se style pas, et se
 * coupe sur les petits écrans. Sur un rapport dense — où l'infobulle porte
 * souvent la MOITIÉ de l'information (« fin de contrat le… », « forfait jours
 * converti », le détail d'un segment) — ce délai fait manquer la lecture.
 *
 * Convention : tout élément portant `data-occ-tip` reçoit une bulle immédiate.
 * Le `title` natif est CONSERVÉ là où il porte le nom accessible ; ailleurs on
 * ne le double pas, sous peine de voir les deux bulles. */
.kincy-occ-tip {
  position: fixed;
  z-index: 2000;
  max-width: 22rem;
  padding: .3rem .5rem;
  border-radius: var(--kincy-radius-sm, 4px);
  background: var(--kincy-slate);
  color: #fff;
  font-size: .75rem;
  line-height: 1.3;
  pointer-events: none;
  box-shadow: var(--kincy-elevation-overlay);
  /* ⚠️ `pre-line` et non `pre` : on veut les retours à la ligne ÉCRITS (l'aire
   * empilée d'un agent détaille une bande par ligne) sans figer l'indentation
   * du gabarit, qui ferait entrer des paquets d'espaces dans la bulle. Les
   * infobulles d'une seule ligne n'en ressentent rien. */
  white-space: pre-line;
}
[data-occ-tip] { cursor: help; }

/* ⚠️ La colonne AGENT ne se comprime pas. L'ajout de « Distribution » le
 * 2026-09-16 l'a réduite au point que « Alice Martin » passait sur deux lignes
 * — constaté en capture. Un nom de personne coupé en deux est le genre de
 * détail qui fait douter du reste du tableau. */
/* ⚠️ COMBINATEURS D'ENFANT OBLIGATOIRES. Les deux tableaux de drill-down
 * vivent DANS un `<td>` de celui-ci : un sélecteur descendant
 * (`.kincy-occ-table th:nth-child(3)`) attrapait donc aussi leurs colonnes, et
 * posait `nowrap` + `min-width: 10rem` sur la 3e colonne du tableau par type —
 * exactement celle qui doit se couper sur deux lignes. Mesuré le 2026-09-16 :
 * `white-space: nowrap` sur une colonne voisine de deux colonnes en `normal`.
 * Une règle qui fuit dans un tableau imbriqué ne lève rien ; elle déforme. */
.kincy-occ-table > thead > tr > th:nth-child(3),
.kincy-occ-table > tbody > tr > td:nth-child(3) { white-space: nowrap; min-width: 10rem; }

/* --- L'ÉVOLUTION DES TAUX, par agence et par département (2026-09-16) -----
 *
 * ⚠️ SIX TEINTES, partagées par les COURBES et par les PASTILLES des chips de
 * filtre. C'est le rapprochement demandé : on doit retrouver dans le filtre la
 * couleur qu'on voit sur le graphe. Elles ne tiennent que parce que le slot est
 * calculé sur le MÊME univers trié des deux côtés — shell et fragment.
 *
 * ⚠️⚠️ CECI RENVERSE UNE DÉCISION PRÉCÉDENTE, et c'est assumé. Le CSS de cette
 * page portait « l'agence marque l'AXE, pas la valeur : une seule teinte pour
 * tout le groupe » — la sarcelle sur toutes les chips d'agence. La règle était
 * bonne tant qu'aucun visuel ne distinguait les agences entre elles ; elle
 * cesse de l'être dès qu'un graphe leur donne une courbe chacune. Une chip
 * uniformément sarcelle à côté de quatre courbes de couleurs différentes
 * n'aurait plus rien à dire.
 *
 * Les teintes évitent les trois couleurs de SIGNAL de Link (cyan sélectionné,
 * orange à surveiller, rouge dépassé) — même raison que les segments de
 * distribution : une série DÉCRIT, elle ne signale rien. */
:root {
  --kincy-trend-0: #2D7FF9;
  --kincy-trend-1: #00A88F;
  --kincy-trend-2: #9B51E0;
  --kincy-trend-3: #E8830C;
  --kincy-trend-4: #C2185B;
  --kincy-trend-5: #5B6B7F;
}

.kincy-occ-trend {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--kincy-spacing-md);
}
/* ⚠️ Une hauteur EXPLICITE : un SVG dans un conteneur flex sans hauteur se
 * réduit à rien, et le graphe sort vide sans lever la moindre erreur. */
.kincy-occ-trend-svg {
  flex: 1 1 32rem;
  min-width: 22rem;
  /* ⚠️ Montée de 14 à 16rem le 2026-09-16, avec les valeurs sur les points.
   * `preserveAspectRatio="xMidYMid meet"` met le SVG à l'échelle du PLUS PETIT
   * des deux rapports : la hauteur étant ici le facteur limitant, une boîte
   * trop basse rétrécit AUSSI le texte, et un libellé de 9 unités sortait à
   * 7 px. Le graphe respire, et les chiffres redeviennent lisibles. */
  height: 16rem;
}

.kincy-occ-trend-grid { stroke: var(--kincy-neutral-medium); stroke-width: 1; }
.kincy-occ-trend-tick {
  font-size: 10px;
  fill: var(--kincy-slate);
  text-anchor: end;
}
.kincy-occ-trend-xlabel {
  font-size: 10px;
  fill: var(--kincy-slate);
  text-anchor: middle;
}

/* La facturable en PLEIN, la brute ATTÉNUÉE — même teinte, deux lisibilités.
 * L'opacité et l'épaisseur portent la différence à elles deux : l'opacité seule
 * se perd sur un écran peu contrasté, l'épaisseur seule sur une courbe dense. */
.kincy-occ-trend-billable { stroke-width: 2.2; opacity: 1; }
.kincy-occ-trend-gross { stroke-width: 1.4; opacity: .38; stroke-dasharray: 4 3; }

/* Chaque point porte sa valeur FACTURABLE, en petit (demandé le 2026-09-16).
 *
 * ⚠️ `paint-order: stroke fill` pose le liseré SOUS le glyphe au lieu de
 * par-dessus — c'est toute l'astuce. Sans lui, un nombre qui croise sa propre
 * courbe ou celle d'une voisine devient illisible, et le cas n'est pas rare :
 * cinq séries sur douze mois se coupent en permanence.
 *
 * ⚠️ `pointer-events: none` sur le libellé ET la pastille : sans ça, le texte
 * intercepte le survol et VOLE l'infobulle au point qu'il décrit. La règle
 * serait écrite et le survol répondrait « parfois », selon le pixel visé. */
.kincy-occ-trend-value {
  font-size: 9px;
  font-weight: 600;
  text-anchor: middle;
  paint-order: stroke fill;
  stroke: var(--bs-body-bg);
  stroke-width: 2.5px;
  stroke-linejoin: round;
  pointer-events: none;
}
.kincy-occ-trend-dot { pointer-events: none; }

/* La cible de survol : généreuse et invisible. Un disque de 2,4 unités est
 * introuvable à la souris ; celui-ci fait quatre fois ce rayon et ne se voit
 * pas.
 *
 * ⚠️ **Ce qui rend un disque invisible survolable** — mesuré par mutation le
 * 2026-09-16, parce que la première rédaction de ce commentaire affirmait le
 * contraire et se trompait. En SVG, le défaut est
 * `pointer-events: visiblePainted` : un remplissage **transparent est peint**,
 * donc `fill: transparent` reçoit le survol à lui seul. C'est `fill: none` qui
 * ne reçoit rien. Ici les deux déclarations sont là et **chacune suffit** ;
 * l'inerte serait `fill: none` SANS `pointer-events`, seule combinaison qui
 * fasse rougir la sonde.
 *
 * On garde les deux : `fill: none` est ce qu'une main écrit spontanément pour
 * « invisible », et `pointer-events: all` fait que ce geste ne casse rien. */
.kincy-occ-trend-hit {
  fill: transparent;
  pointer-events: all;
  cursor: help;
}

/* La colonne de droite porte DEUX légendes : les couleurs (qui), puis les
 * traits (quoi). Elles se lisent l'une sous l'autre, jamais côte à côte : ce
 * sont deux questions, et les aligner ferait croire à une seule grille. */
.kincy-occ-trend-side {
  display: flex;
  flex-direction: column;
  gap: var(--kincy-spacing-sm);
  min-width: 10rem;
}

.kincy-occ-trend-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: .75rem;
}

/* La clé des deux traits. Séparée de la légende des couleurs par un filet :
 * sans lui, les quatre agences et les deux styles se lisent comme six entrées
 * de la même liste. */
.kincy-occ-trend-key {
  list-style: none;
  margin: 0;
  padding: var(--kincy-spacing-sm) 0 0;
  border-top: 1px solid var(--kincy-neutral-medium);
  font-size: .75rem;
  color: var(--kincy-slate);
}
.kincy-occ-trend-key li {
  display: flex;
  align-items: center;
  gap: .35rem;
  padding: .12rem 0;
}
/* ⚠️ Une largeur ET une hauteur EXPLICITES : un SVG en ligne sans dimensions
 * prend la taille par défaut de 300x150 et fait exploser la légende. Le même
 * piège que la hauteur du graphe, à une autre échelle. */
.kincy-occ-trend-sample {
  width: 26px;
  height: 8px;
  flex: none;
  overflow: visible;
}
.kincy-occ-trend-legend li {
  display: flex;
  align-items: center;
  gap: .35rem;
  padding: .12rem 0;
}

/* La pastille de la chip : le même carré que la légende, posé avant le
 * trigramme. ⚠️ Elle reste visible quand la chip est cochée — le fond passe en
 * sarcelle, et un carré qui disparaîtrait à la sélection perdrait justement le
 * lien avec la courbe au moment où on l'a coché. */
.kincy-occ-swatch--chip {
  display: inline-block;
  margin-right: .3rem;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .8);
  vertical-align: baseline;
}

/* Les départements gardent la couleur de leur RÔLE — celle des pastilles R/B/M
 * du tableau. ⚠️ Ce sont les mêmes mots : « Run » doit être de la même couleur
 * dans une pastille, dans une chip et sur une courbe, sinon la page parle deux
 * langues. Même règle que la zone BUILD. */
:root {
  --kincy-trend-run: var(--kincy-blue);
  --kincy-trend-build: var(--kincy-magenta);
  /* ⚠️ MANAGE prend le VIOLET DE LA CHARTE — arbitré par Rodolphe le 2026-09-30,
   * après que la légende de l'écran du référentiel a mis les quatre pastilles
   * côte à côte pour la première fois. Il portait `--kincy-slate`, à **27,5** de
   * distance RVB de `--kincy-neutral-dark` : sur une courbe, Manage et le
   * fourre-tout se confondaient. Mesuré le jour de l'arbitrage : le violet est à
   * 75,7 de son plus proche voisin parmi les six emplacements d'agence, à 113,9
   * du violet de Papeete (`--kincy-trend-2`), et à 121 du rouge de signal. */
  --kincy-trend-manage: var(--kincy-purple);
  /* ⚠️ Et « autre » garde un gris, MOINS SOMBRE — mais pas n'importe lequel.
   * `#6E6E6E`, la lecture littérale de « légèrement moins sombre », le
   * rapprocherait de l'emplacement d'agence 5 (`#5B6B7F`, un gris BLEUTÉ) :
   * 25,7 au lieu des 42,1 d'aujourd'hui. `#808080` est visiblement plus clair
   * (67,5 de l'ancien) ET garde la distance : 42,6. Il tient 3,95:1 sur blanc,
   * au-dessus du 3:1 d'un filet.
   * ⚠️ Pas `--kincy-neutral-dark` éclairci : ce token sert du TEXTE ailleurs, et
   * l'éclaircir casserait des contrastes sans rapport avec les rôles. */
  --kincy-trend-other: #808080;
}


/* --- L'aire empilée d'un agent (drill-down) -------------------------------
 *
 * Elle réemploie la grille, les graduations et les libellés de mois des
 * courbes d'évolution : même écran, même langage, et une seule règle à changer
 * le jour où l'un des deux bouge. */

.kincy-occ-area {
  margin: 0 0 var(--kincy-spacing-md);
}
.kincy-occ-area-head {
  font-size: .8rem;
  font-weight: 600;
  color: var(--kincy-slate);
  margin-bottom: var(--kincy-spacing-sm);
}
/* ⚠️ Une hauteur EXPLICITE : un SVG sans hauteur dans un conteneur souple se
 * réduit à rien et l'aire sort vide, sans lever la moindre erreur. */
.kincy-occ-area-svg {
  width: 100%;
  height: 11rem;
  display: block;
}

/* Un filet clair entre les bandes : deux teintes voisines de la palette de
 * segments se touchent sinon sans frontière lisible, et la pile devient un
 * dégradé. */
.kincy-occ-area-band {
  stroke: var(--bs-body-bg);
  stroke-width: .5;
}

/* ⚠️ La bande HACHURÉE (le non facturable, 2026-10-07, `OA-A34`) perd le filet
 * clair et prend un contour à l'encre de son segment. Sans contour, le haut
 * d'une zone hachurée n'a pas de bord, or ce haut EST le taux brut : on perdrait
 * l'un des deux chiffres que la forme doit dire (`OA-A24`). */
.kincy-occ-area-band--hatched {
  stroke: var(--kincy-seg-non_billable);
  stroke-width: .8;
}

/* La pastille de légende de la bande hachurée, hachurée elle aussi : une
 * pastille pleine légenderait une teinte que l'aire ne montre plus. Le liseré
 * intérieur garde la forme du carré lisible quand les rayures sont fines. */
.kincy-occ-swatch--hatched {
  background: repeating-linear-gradient(
    45deg, var(--kincy-seg-non_billable) 0 2px, transparent 2px 4px);
  box-shadow: inset 0 0 0 1px var(--kincy-seg-non_billable);
}

/* ⚠️ La frontière du facturable : encre NOIRE et trait fin. Lui donner une
 * couleur de segment la ferait prendre pour une bande de plus.
 *
 * ⚠️ `--bs-body-color` (#1d1d1b) et non `--kincy-slate` (#44546A) : le slate
 * est un bleu-gris, et posé au-dessus d'une bande bleue il se lit comme une
 * SÉRIE de plus, pas comme un repère. Relevé à l'usage le 2026-09-17 — « il y
 * a un trait bleu au dessus de l'aire projet ». L'encre de texte, elle, ne
 * ressemble à aucun segment. */
.kincy-occ-area-boundary {
  stroke: var(--bs-body-color);
  stroke-width: 1.6;
  stroke-linejoin: round;
}

/* --- Le repère vertical du mois survolé -----------------------------------
 *
 * Il va DE PAIRE avec l'infobulle : même déclencheur, même disparition. Sur une
 * aire empilée comme sur un faisceau de courbes, la bulle dit les chiffres mais
 * ne dit pas OÙ on est ; le trait ancre la lecture sur la colonne.
 *
 * ⚠️ `opacity` et non `display` : la ligne garde sa boîte, donc aucun recalcul
 * de mise en page à chaque déplacement du curseur. Et aucune transition — un
 * repère qui s'estompe traîne derrière la souris et donne l'impression d'un
 * écran qui rame.
 *
 * ⚠️ `pointer-events: none` : sinon le repère passe SOUS le curseur, devient la
 * cible, et le `mouseout` de la colonne le fait disparaître aussitôt — il
 * clignoterait. */
.kincy-occ-guide {
  opacity: 0;
  stroke: var(--bs-body-color);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  pointer-events: none;
}
.kincy-occ-guide--on { opacity: .45; }

/* ⚠️ `fill: transparent` et non `fill: none` : un remplissage transparent EST
 * peint, donc il reçoit le survol (défaut SVG `pointer-events: visiblePainted`).
 * `pointer-events: all` le tient aussi si quelqu'un passe le remplissage à
 * `none` — la même ceinture et les mêmes bretelles que les points de courbe. */
.kincy-occ-area-hit {
  fill: transparent;
  pointer-events: all;
  cursor: help;
}

.kincy-occ-area-legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: .1rem var(--kincy-spacing-md);
  margin: var(--kincy-spacing-sm) 0 0;
  padding: 0;
  font-size: .75rem;
  color: var(--kincy-slate);
}
.kincy-occ-area-legend li {
  display: flex;
  align-items: center;
  gap: .35rem;
}


/* --- L'objectif annuel du taux facturable (2026-10-16) ----------------------
 *
 * Spec : `docs/billable-rate-objective-spec.md` (`OBJ-D4` à `OBJ-D9`).
 *
 * LE PALIER (`OBJ-D4`) : un trait par année sur les courbes et l'aire. *Allégé
 * le 2026-10-16* (« moins gras », Rodolphe, après validation en production) : 2
 * unités (3 jusque-là ; 1,4 et à 38 % d'opacité pour le brut, 2,2 pour la courbe
 * facturable), TIRETS 8 / 4 (10 / 5 jusque-là, contre le pointillé fin 4 / 3 du
 * brut), encre ardoise pleine : il reste le trait le plus marqué après la courbe
 * facturable et se distingue du brut par l'épaisseur, le tiret, l'opacité et la
 * teinte. Il ne porte aucun token `--kincy-goal-*`.
 *
 * LE COMPTE-TOURS (`OBJ-D6`, `OBJ-D7`) des tuiles `occ-departements` et
 * `occ-agences`. ⚠️ **ÉCART DÉCIDÉ PAR RODOLPHE** (2026-10-15, Q1 : « on sort de
 * la charte ici, on prend des couleurs standards ») : les trois zones prennent le
 * feu tricolore STANDARD de Bootstrap 5.3. Mesuré le 2026-10-16 sur le blanc de
 * la tuile avec `apps/core/contrast.py` (étalonné : noir sur blanc 21,00:1, slate
 * sur blanc 7,71:1) : rouge #DC3545 **4,53:1**, vert #198754 **4,53:1**, jaune
 * #FFC107 **1,63:1**, sous les 3:1 d'un composant graphique (WCAG 1.4.11) et
 * assumé. Deux compensations inconditionnelles : le CONTOUR ardoise des segments
 * (`--kincy-slate`, 4,73:1 contre le jaune), qui porte la forme, et l'écart signé
 * en texte sous le chiffre. **Confinés** aux sélecteurs `.kincy-goal…` par
 * `tests/test_goal_tokens.py` ; `docs/conventions/ui.md` §3. */
:root {
  --kincy-goal-below: #DC3545;
  --kincy-goal-near: #FFC107;
  --kincy-goal-above: #198754;
}

.kincy-goal-step {
  stroke: var(--kincy-slate);
  stroke-width: 2;
  stroke-dasharray: 8 4;
  fill: none;
  pointer-events: none;
}
.kincy-goal-step-label {
  font-size: 10px;
  font-weight: 700;
  fill: var(--kincy-slate);
  text-anchor: end;
  paint-order: stroke fill;
  stroke: var(--bs-body-bg);
  stroke-width: 2.5px;
  stroke-linejoin: round;
  pointer-events: none;
}

/* Le cadran garde une taille fixe dans la tuile : la grammaire de tuile
 * (`ui.md` §4.11) reste, le compte-tours s'y inscrit au-dessus du chiffre. */
.kincy-goal-gauge {
  display: block;
  width: 9rem;
  height: auto;
  margin: .1rem auto 0;
}
.kincy-goal-zone {
  stroke: var(--kincy-slate);
  stroke-width: 1;
  stroke-linejoin: round;
}
.kincy-goal-zone--below { fill: var(--kincy-goal-below); }
.kincy-goal-zone--near  { fill: var(--kincy-goal-near); }
.kincy-goal-zone--above { fill: var(--kincy-goal-above); }
/* Le cadran NEUTRE (refus franc, `OBJ-D8`) : aucune couleur de zone. */
.kincy-goal-gauge-track {
  fill: var(--kincy-neutral-light);
  stroke: var(--kincy-neutral-medium);
  stroke-width: 1;
}
.kincy-goal-gauge-tick { stroke: var(--kincy-neutral-dark); stroke-width: 1; }
/* ⚠️ La frontière jaune/vert est un trait PROPRE, deux fois le contour : la
 * marche suivante se voit, elle ne se fond pas dans le cerne (`OBJ-D7`). */
.kincy-goal-gauge-frontier { stroke: var(--kincy-slate); stroke-width: 2; }
/* ⚠️ La graduation À L'OBJECTIF : plus longue (elle traverse l'arc) et plus
 * épaisse que les ordinaires, avec sa valeur (`OBJ-D7` amendé, preuve 23). */
.kincy-goal-gauge-mark { stroke: var(--bs-body-color); stroke-width: 3; }
.kincy-goal-gauge-mark-label {
  font-size: 10px;
  font-weight: 700;
  fill: var(--bs-body-color);
  text-anchor: middle;
}
.kincy-goal-gauge-needle {
  stroke: var(--bs-body-color);
  stroke-width: 2.5;
  stroke-linecap: round;
}
/* ⚠️ Le cadran est un DEMI-DISQUE PLEIN (2026-10-16) : l'aiguille sombre se
 * pose SUR le rouge, le jaune et le vert. Sur le vert (#198754) l'encre du corps
 * (#1d1d1b) ne rend que ~4:1 ; un LISERÉ clair (ligne dessous, plus large, blanc)
 * la détache sur les trois, comme celle du moyeu. */
.kincy-goal-gauge-needle-halo {
  stroke: var(--bs-body-bg);
  stroke-width: 5;
  stroke-linecap: round;
}
.kincy-goal-gauge-hub {
  fill: var(--bs-body-color);
  stroke: var(--bs-body-bg);
  stroke-width: 1.5;
}
.kincy-goal-gap {
  font-size: .8125rem;
  color: var(--kincy-slate);
  font-variant-numeric: tabular-nums;
}


/* --- Annuaire RH : le bouton d'accès au rattachement (2026-09-18) ---------
 *
 * Le rattachement n'a plus d'entrée de menu : c'est une FILE D'ATTENTE, vide la
 * plupart du temps, et un menu ne dit pas s'il y a du travail. On y entre donc
 * par ce bouton, qui porte le nombre de personnes en attente.
 *
 * Deux états, et le contraste entre eux EST l'information :
 *   * rien en attente -> fond blanc, contour neutre. Le bouton reste, parce que
 *     la file reste consultable ; il ne réclame simplement rien.
 *   * des comptes en attente -> fond `--kincy-red`, et le nombre entre
 *     parenthèses.
 *
 * ⚠️ `--kincy-red` est le token déjà en service (dépassement d'heures, bouton de
 * suppression des pare-feux) et c'est le SEUL rouge de cet écran : une couleur
 * de signal ne porte qu'un sens par page. Si un jour l'annuaire doit signaler
 * autre chose, ce ne sera pas en rouge.
 *
 * Écrit en variables `--bs-btn-*` plutôt qu'en propriétés nues : c'est ainsi que
 * Bootstrap 5 compose ses boutons, donc le survol, le focus et l'état actif
 * suivent sans qu'on les redécrive. */
.kincy-hr-matching {
  --bs-btn-color: var(--kincy-slate);
  --bs-btn-bg: #fff;
  --bs-btn-border-color: rgba(68, 84, 106, 0.3);
  --bs-btn-hover-color: var(--kincy-cyan);
  --bs-btn-hover-bg: #fff;
  --bs-btn-hover-border-color: var(--kincy-cyan);
  --bs-btn-active-color: var(--kincy-cyan);
  --bs-btn-active-bg: #fff;
  --bs-btn-active-border-color: var(--kincy-cyan);
}

/* Le survol INVERSE l'aplat (rouge sur blanc) au lieu de l'assombrir : un rouge
 * plus sombre serait une valeur magique de plus, et la charte n'en a qu'un. */
.kincy-hr-matching--pending {
  --bs-btn-color: #fff;
  --bs-btn-bg: var(--kincy-red);
  --bs-btn-border-color: var(--kincy-red);
  --bs-btn-hover-color: var(--kincy-red);
  --bs-btn-hover-bg: #fff;
  --bs-btn-hover-border-color: var(--kincy-red);
  --bs-btn-active-color: var(--kincy-red);
  --bs-btn-active-bg: #fff;
  --bs-btn-active-border-color: var(--kincy-red);
}

/* Le badge « À rattacher » de l'annuaire (2026-09-30) : le rouge du bouton, et le
   même sens — un rattachement POSSIBLE attend. « Compte Halo attendu » reste
   neutre, et « sans compte Halo » n'a pas de badge du tout : ni l'un ni l'autre
   n'est un geste qu'on puisse faire dans Link (`docs/hr-matching-intent-spec.md`). */
.kincy-hr-badge-to-link {
  color: #fff;
  background-color: var(--kincy-red);
}

/* ===========================================================================
   Temps travaillés (zone RH) — spec : docs/hr-worked-time-screen-spec.md
   ---------------------------------------------------------------------------
   ⚠️ L'ORANGE DE CETTE PAGE EST PRIS PAR LES TROUS DE SAISIE, et par rien
   d'autre. La convention interdit deux significations d'une même couleur de
   signal sur une même page (docs/conventions/ui.md) : le drapeau « non
   publiable » porte donc un badge neutre, et la quantité nulle s'atténue
   typographiquement au lieu de prendre une cinquième couleur.

   Le choix se mesure, il n'est pas esthétique : 536 lignes incomplètes contre
   ZÉRO non publiable au 2026-09-22. L'orange va à l'anomalie qui existe. Le
   jour où des mois non publiables apparaîtront, l'arbitrage se rejoue.
   =========================================================================== */

/* La ligne dont le mois n'a ni agence ni équipe : un trou de saisie à corriger,
   pas un défaut d'affichage à masquer.

   *Amendé le 2026-09-29* : la ligne ENTIÈRE est teintée, et plus seulement sa
   première cellule. Un filet de 3 px à gauche se perdait dans un tableau de
   soixante lignes (Rodolphe : « ligne du tableau intégralement orange »).
   ⚠️ La teinte passe par `--bs-table-bg`, pas par `background-color` : Bootstrap
   peint le fond de chaque CELLULE depuis cette variable, et un fond posé sur la
   ligne serait recouvert — le défaut déjà vécu deux fois (`ui.md` §4.3, §4.8).
   Même dosage que les autres aplats d'alerte du dépôt. */
.kincy-wt-incomplete {
  --bs-table-bg: rgba(240, 131, 0, 0.1); /* --kincy-orange à 10 % */
}

.kincy-wt-incomplete > td:first-child {
  box-shadow: inset 3px 0 0 0 var(--kincy-orange);
}

/* Le symbole de valeur inconnue. ⚠️ Jamais un libellé : « n/d » ou « aucune »
   se lirait comme une agence dont ce serait le nom. */
.kincy-wt-unknown {
  color: var(--kincy-orange);
  font-weight: 600;
}

/* Le compteur d'anomalies, qui suit les filtres. *Depuis le 2026-09-29, c'est un
   bouton* : il pose le filtre des lignes sans agence ni équipe. Il garde l'allure
   d'un texte orange, souligné au survol, pour ne pas ajouter un bouton de plus à
   une barre qui en compte déjà. */
.kincy-wt-anomaly-count {
  color: var(--kincy-orange);
  font-weight: 600;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.kincy-wt-anomaly-count:hover,
.kincy-wt-anomaly-count--active {
  text-decoration: underline;
}

.kincy-wt-anomaly-count:focus-visible {
  outline: 2px solid var(--kincy-cyan);
  outline-offset: 2px;
}

/* Le champ de recherche. ⚠️ Borné : `--grow` lui donnait toute la largeur restante
   de la barre, soit celle du tableau (Rodolphe, 2026-09-29). */
.kincy-wt-search {
  width: 16rem;
  max-width: 100%;
}

.kincy-wt-search-group {
  gap: var(--kincy-spacing-sm);
}

/* Une chip d'Agence ou de Département porte l'ÉCLAT de sa teinte du référentiel :
   le point de la pastille de l'annuaire, le libellé en encre courante. La teinte
   se pose par `--kincy-agency-ink` en style en ligne ; son défaut neutre couvre une
   valeur que le référentiel ne code pas. */
.kincy-chip--inked {
  --kincy-agency-ink: var(--kincy-neutral-medium);
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
}

/* ⚠️ Une quantité nulle est le plus souvent une LIGNE DE PAIE MORTE, pas un mois
   réellement à zéro : 247 lignes, 45 personnes, dont 12 qui n'ont que ça.
   L'atténuer empêche de la lire comme une vraie mesure, sans dépenser une
   couleur de signal qui n'est plus disponible. */
.kincy-wt-zero {
  color: var(--kincy-slate);
  opacity: 0.55;
  font-style: italic;
}

/* ---------------------------------------------------------------------------
   Temps travaillés : les entrées attendues (2026-09-29, lot B)
   Spec : docs/hr-worked-time-completeness-spec.md §1.2, §2, §3, §4.1.
   ---------------------------------------------------------------------------
   ⚠️ LE ROUGE DE CETTE PAGE VEUT DIRE « DÉPASSÉ », et rien d'autre : un mois
   CLOS sans valeur. Il va au badge « Manquante » et au bandeau qui en compte au
   moins une. L'orange reste aux trous d'agence et de département (bloc
   ci-dessus), le vert est exclu d'une catégorie par design.md §3.6, et le
   budget des couleurs de signal est plein (docs/conventions/ui.md §3) : tout le
   reste de ce bloc est NEUTRE, par construction. */

/* Le bandeau de tête. Neutre par défaut : « aucun mois clos à contrôler »,
   « toutes complètes », ou seulement des non-publiables. Il ne disparaît
   jamais, un bandeau absent ne se distinguant pas d'un bandeau cassé. */
.kincy-wt-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  padding: var(--kincy-spacing-sm) var(--kincy-spacing-md);
  border-left: 4px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-sm);
  background: var(--kincy-neutral-light);
  color: var(--kincy-slate);
}

/* Au moins une entrée MANQUANTE. Le filet ET le nombre passent au rouge, le
   fond reste clair : rouge sur `--kincy-neutral-light` tient 5,57:1 (mesure
   du token, 2026-09-03), le texte reste donc lisible. */
.kincy-wt-banner--alert {
  border-left-color: var(--kincy-red);
}

.kincy-wt-banner--alert .kincy-wt-banner-count {
  color: var(--kincy-red);
}

.kincy-wt-banner-action {
  padding: 0;
  font-weight: 600;
}

/* L'en-tête d'un mois : une ligne de tableau dont la seule cellule est un
   bouton. ⚠️ Un bouton et pas une ligne cliquable : c'est ce qui le rend
   atteignable au clavier et lisible par `aria-expanded`. */
.kincy-wt-month > th {
  background: var(--kincy-neutral-light);
  padding: 0;
}

.kincy-wt-month-toggle {
  display: flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  width: 100%;
  padding: var(--kincy-spacing-xs) var(--kincy-spacing-sm);
  border: 0;
  background: transparent;
  color: var(--kincy-slate);
  font-weight: 600;
  text-align: left;
}

.kincy-wt-month-toggle:focus-visible {
  outline: 2px solid var(--kincy-cyan);
  outline-offset: -2px;
}

/* Le chevron se dessine en bordures : aucune icône à charger, et il tourne
   avec l'état que porte `aria-expanded`, pas avec une classe à tenir en phase. */
.kincy-wt-month-caret {
  width: 0.45rem;
  height: 0.45rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.12s ease;
}

.kincy-wt-month-toggle[aria-expanded="true"] .kincy-wt-month-caret {
  transform: rotate(45deg);
}

.kincy-wt-month-count {
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

/* LES SOMMES DU MOIS (2026-10-06). Le total du mois sur fond BLANC, qui le
   détache de l'en-tête ; les départements À CÔTÉ, plus petits, en gris, sur un
   gris clair : ils détaillent, ils ne concurrencent pas. La teinte reste un
   ÉCLAT de la pastille partagée (`hr/_department_pill.html`), jamais une encre.

   ⚠️ DES COLONNES, PAS UNE PHRASE (repris le 2026-10-06, sur la production) :
   chaque en-tête porte les mêmes places, et chacune a une LARGEUR FIXE — le
   libellé du mois, le décompte, chaque montant. Sans cela, « Mai 2026 » et
   « Décembre 2025 » décalaient toutes les sommes qui les suivent, et dix mois
   empilés ne se lisaient plus en colonnes. Les montants sont en `ch` sur des
   chiffres tabulaires : six chiffres, la virgule et deux espaces étroits font
   « 6 221,94 h » en un peu plus de huit `ch`.

   ⚠️ `contain: inline-size` : sans lui, l'en-tête ne se replie plus et IMPOSE SA
   LARGEUR au tableau entier (`colspan` compte dans la largeur minimale) ; la
   colonne « Quantité » sortait de l'écran, sur un mois en cours sans valeur où
   toutes les places, masquées, gardent pourtant leur largeur. Contenu, le
   bouton prend la largeur du tableau et ne la dicte plus. */
.kincy-wt-month-toggle {
  white-space: nowrap;
  contain: inline-size;
}

/* ⚠️ `flex: none` sur tout ce qui précède les départements : quand la ligne
   déborde, flex RÉTRÉCIT ses éléments, et le chevron (une boîte de bordures)
   perdait sa largeur jusqu'à n'être plus qu'un trait. */
.kincy-wt-month-caret,
.kincy-wt-month-label,
.kincy-wt-month-count,
.kincy-wt-month-sum {
  flex: none;
}

.kincy-wt-month-label {
  min-width: 8.5rem;
}

.kincy-wt-month-count {
  min-width: 9ch;
}

.kincy-wt-month-sum,
.kincy-wt-month-team {
  display: inline-flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  padding: 0 var(--kincy-spacing-sm);
  border-radius: var(--kincy-radius-sm);
}

.kincy-wt-month-sum {
  background: var(--bs-body-bg);
}

/* Les départements prennent le reste de la ligne et PASSENT À LA LIGNE quand il
   manque de place. Les places ayant toutes la même largeur d'un mois à l'autre,
   le retour se fait au même endroit dans chaque en-tête : les colonnes tiennent,
   sur deux lignes au lieu d'une. */
.kincy-wt-month-teams {
  display: inline-flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  min-width: 0;
  align-items: center;
  gap: var(--kincy-spacing-xs) var(--kincy-spacing-sm);
  margin-left: var(--kincy-spacing-sm);
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--kincy-neutral-dark);
}

/* Le gris clair des départements : un mélange de deux tokens, pas une valeur
   magique. Neutre, il se distingue du bleu-gris de l'en-tête sans lui
   disputer l'attention que porte le blanc du total. */
.kincy-wt-month-team {
  gap: var(--kincy-spacing-xs);
  padding-left: 0;
  background: color-mix(in srgb, var(--kincy-neutral-medium) 30%, var(--bs-body-bg));
}

/* La pastille est déjà portée par le fond de sa place : la sienne disparaît. */
.kincy-wt-month-team .kincy-agency-pill {
  background: transparent;
  padding-top: 0;
  padding-bottom: 0;
}

/* Un montant : une colonne, alignée à droite. Heures et jours se séparent par
   la gouttière de leur place, plus le vide de la colonne : aucun « · ». */
.kincy-wt-amount {
  display: inline-block;
  min-width: 8.5ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Les jours, seconde colonne de chaque place : « 106,50 j » tient en sept. */
.kincy-wt-amount + .kincy-wt-amount {
  min-width: 7ch;
}

/* Le total du mois : une place de plus, pour qu'un mois à cinq chiffres
   (« 10 412,50 h ») ne décale pas la colonne des départements. */
.kincy-wt-month-sum .kincy-wt-amount:first-child {
  min-width: 9.5ch;
}

/* Une place sans rien à sommer garde sa LARGEUR : la retirer décalerait les
   suivantes. */
.kincy-wt-month-slot--empty {
  visibility: hidden;
}

/* La cellule « Département » : repère de suivi, éclat, libellé canonique. */
.kincy-wt-department {
  display: inline-flex;
  align-items: center;
  gap: var(--kincy-spacing-xs);
}

/* LE REPÈRE « SUIVI DU TAUX D'OCCUPATION » (spec §4.1). ⚠️ LA FORME porte
   l'information (pleine = suivi, creuse = non suivi), la couleur ne fait que
   l'appuyer : un lecteur qui ne distingue pas les teintes lit encore le repère.
   Slate et PAS vert, arbitré en spec : design.md §3.6 réserve le vert à
   « validation, état OK », et « ce département est suivi » est un attribut.
   La couronne creuse est en `--kincy-neutral-dark` et non en gris clair :
   `--kincy-neutral-medium` rend 1,74:1 sur blanc, sous les 3:1 qu'exige un
   repère graphique porteur de sens (WCAG 1.4.11) ; `--kincy-neutral-dark` rend
   7,00:1. Mesuré le 2026-09-29 avec `apps.core.contrast.contrast_ratio`. */
.kincy-wt-track {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  border: 2px solid var(--kincy-slate);
  flex: none;
}

.kincy-wt-track--on {
  background: var(--kincy-slate);
}

.kincy-wt-track--off {
  border-color: var(--kincy-neutral-dark);
  background: transparent;
}

/* « Manquante » s'écrit désormais `.kincy-state-state--miss` (pictogramme puis texte rouge,
   lot de retouches du 2026-11-05) : l'aplat `.kincy-wt-state-missing` est retiré, son ratio
   est tenu sur la nouvelle règle par tests/test_css_contrast.py. */

/* « Sans contrat », « Saisie impossible » : une mention, pas un signal. Le
   signal est porté par l'état ; la mention dit POURQUOI on ne peut pas le
   corriger depuis la fiche tout de suite. */
.kincy-wt-mention {
  display: inline-block;
  margin-left: var(--kincy-spacing-xs);
  color: var(--kincy-neutral-dark);
  font-size: 0.8125rem;
  font-style: italic;
}

/* PANNEAU « EN TÉLÉTRAVAIL AUJOURD'HUI » de la page d'accueil (S11d, 2026-10-07).
 *
 * Retouché le 2026-10-07 à la demande de Rodolphe : la carte a la MÊME apparence
 * que celle des absences (fond blanc, tableau `table-striped`), plus de teinte
 * bleu ciel. L'ancien fond `--kincy-neutral-light`, la surcharge de
 * `--bs-table-bg` qui le protégeait et le fond blanc forcé de la chip ont donc
 * été supprimés avec lui : ils n'avaient de sens que par cette teinte.
 *
 * ⚠️ AUCUNE COULEUR NEUVE. Le budget des couleurs de signal est plein
 * (docs/conventions/ui.md §3) : les teintes pâles ci-dessous sont DÉRIVÉES des
 * tokens existants par `color-mix`, jamais écrites en dur. */
:root {
  /* Coche et sablier : le vert et l'orange de la charte, ramenés à 55 % vers le
   * blanc. Rodolphe voulait un presque pastel, qui « ressorte moins » : le
   * picto n'est plus qu'un glyphe seul, sans disque ni contour. La forme porte
   * l'information (coche contre sablier) et `aria-label` la nomme ; la couleur
   * ne fait que la qualifier, ce qui est le contraire d'un signal d'alerte.
   * ⚠️ Ces teintes sont SOUS le 3:1 de WCAG 1.4.11 : écart demandé et assumé
   * pour ce picto, qui ne porte jamais seul l'état (forme + libellé). */
  --kincy-remote-approved: color-mix(in srgb, var(--kincy-green) 55%, white);
  --kincy-remote-pending: color-mix(in srgb, var(--kincy-orange) 55%, white);
}

/* Titres des deux cartes de l'accueil, en KINCY Blue (`--kincy-blue`, 4,8:1 sur blanc).
 * Demande de Rodolphe le 2026-10-07 : le surligneur rose était trop estompé, il est
 * remplacé par le bleu de la charte (le cyan, à 2,6:1, ne tenait pas le contraste). */
.kincy-home-card-title {
  color: var(--kincy-blue);
}

/* LA TUILE « Suivi individuel » (2026-10-07, `docs/home-individual-tracking-spec.md`
 * `SI-D10`). L'anneau « Facturable / non facturable » y vit dans un tiers de
 * carte, trop étroit pour sa légende À CÔTÉ de lui : mesuré à la capture
 * (1 440 px), « 86,8 % » passait à la ligne caractère par caractère. Dans la
 * tuile seulement, la légende passe SOUS l'anneau ; le drill-down du rapport ne
 * change pas (même gabarit, autre conteneur). */
.kincy-tracking-card .kincy-occ-pie {
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* LA CARTE « Notation du jour » (2026-10-15, `docs/home-daily-notation-spec.md`).
 * Maquette validée : planche A de Claude Design, dont les couleurs littérales sont
 * remplacées ici par les tokens EXISTANTS : aucune couleur neuve, aucun écart de
 * plus à la charte (`ui.md` §3, budget des couleurs de signal plein). Les segments
 * prennent `var(--kincy-seg-<clé>)` en ligne, posé par le gabarit ; le non
 * facturable est hachuré par `.kincy-occ-swatch--hatched`, la classe de la légende
 * de la vue mensuelle (`SI-D9`), réemployée sur la barre et sur les colonnes. */
.kincy-notation-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--kincy-spacing-sm);
  margin-bottom: var(--kincy-spacing-md);
}
/* Quand Link a lu Halo (fuseau de Link), posé par le fragment (`hx-swap-oob`). */
.kincy-notation-readat {
  font-size: .75rem;
  color: var(--bs-secondary-color);
  text-align: right;
}
.kincy-notation {
  display: flex;
  flex-direction: column;
  gap: var(--kincy-spacing-md);
}
.kincy-notation-figure {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--kincy-spacing-sm);
  margin: 0;
}
.kincy-notation-big {
  font-size: 2.25rem;
  font-weight: 700;
  line-height: 1;
  color: var(--bs-body-color);
}
.kincy-notation-sub { font-size: .875rem; color: var(--kincy-slate); }
.kincy-notation-note,
.kincy-notation-last,
.kincy-notation-week-title {
  font-size: .8125rem;
  color: var(--kincy-slate);
  margin: 0;
}

/* Le grand visuel : la journée, en barre horizontale empilée. Le repère
 * (« 7 h théoriques », Q1) est un trait pointillé posé à sa part de l'échelle
 * commune ; la marge haute lui laisse la place de son libellé. */
.kincy-notation-today { position: relative; }
.kincy-notation-today--ref { padding-top: 1.125rem; }
.kincy-notation-today--obj { padding-bottom: 1.125rem; }
/* L'OBJECTIF du taux facturable (`NQ-D7` amendé le 2026-10-16) : un trait FIN, 1 px,
 * à l'encre du texte, qui dépasse la barre d'un quart de rem de chaque côté. Il se
 * distingue du repère « théoriques » (2 px, slate) et du palier des graphes (2 px,
 * 8 4) par l'épaisseur ET la couleur : le repère dit ce que le contrat prévoit,
 * le trait dit où l'objectif tombe sur la même journée. Son libellé vit SOUS la barre,
 * le repère garde le dessus. */
.kincy-notation-obj {
  position: absolute;
  top: -.25rem;
  bottom: .875rem;
  border-left: 1px dashed var(--bs-body-color);
  pointer-events: none;
}
.kincy-notation-today--ref .kincy-notation-obj { top: .875rem; }
.kincy-notation-obj-label {
  position: absolute;
  top: 100%;
  right: -1px;
  padding-top: .125rem;
  font-size: .6875rem;
  color: var(--bs-body-color);
  white-space: nowrap;
}
.kincy-notation-bar {
  display: flex;
  height: 1.75rem;
  border-radius: var(--kincy-radius-sm);
  overflow: hidden;
  background: var(--kincy-neutral-light);
}
.kincy-notation-seg {
  flex: none;
  height: 100%;
  border-right: 1px solid var(--bs-body-bg);
}
.kincy-notation-ref {
  position: absolute;
  top: .75rem;
  bottom: calc(-1 * var(--kincy-spacing-xs));
  border-left: 2px dashed var(--kincy-slate);
}
.kincy-notation-ref-label {
  position: absolute;
  top: -1.125rem;
  right: -2px;
  font-size: .6875rem;
  color: var(--kincy-slate);
  white-space: nowrap;
}
.kincy-notation-absent-today {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 1.75rem;
  border: 1px dashed var(--kincy-seg-non_billable);
  border-radius: var(--kincy-radius-sm);
  font-size: .75rem;
  color: var(--kincy-slate);
}

/* La légende du jour, sur deux colonnes : segment et durée. */
.kincy-notation-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--kincy-spacing-xs) var(--kincy-spacing-md);
  font-size: .8125rem;
}
.kincy-notation-legend li { display: flex; align-items: center; gap: var(--kincy-spacing-sm); }
.kincy-notation-legend .kincy-occ-swatch { width: .75rem; height: .75rem; }
.kincy-notation-legend-label { flex-grow: 1; color: var(--kincy-slate); }
.kincy-notation-legend-value { font-weight: 500; color: var(--bs-body-color); }
.kincy-notation-last { display: flex; align-items: center; gap: var(--kincy-spacing-xs); }

/* Le petit visuel : sept colonnes empilées, du plus ancien à hier, à la MÊME
 * échelle que la barre du jour (`NQ-D4`). Atténué : un week-end ou un férié sans
 * temps (Q4) ; contour pointillé : une absence sans temps (`NQ-D5`). */
.kincy-notation-week {
  display: flex;
  flex-direction: column;
  gap: var(--kincy-spacing-sm);
  padding-top: var(--kincy-spacing-sm);
  border-top: 1px solid var(--bs-border-color);
}
.kincy-notation-week-title { font-weight: 500; }
.kincy-notation-days {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--kincy-spacing-sm);
  align-items: end;
}
.kincy-notation-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--kincy-spacing-xs);
  min-width: 0;
}
.kincy-notation-day--muted { opacity: .45; }
.kincy-notation-day-total {
  min-height: .875rem;
  font-size: .6875rem;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  color: var(--bs-body-color);
}
.kincy-notation-day-label {
  font-size: .6875rem;
  white-space: nowrap;
  color: var(--bs-secondary-color);
}
.kincy-notation-col {
  position: relative;
  display: flex;
  flex-direction: column-reverse;
  width: 100%;
  max-width: 1.875rem;
  height: 6rem;
  border-radius: var(--kincy-radius-sm);
  overflow: hidden;
  background: var(--kincy-neutral-light);
}
.kincy-notation-col--absent {
  background: transparent;
  border: 1px dashed var(--kincy-seg-non_billable);
}
.kincy-notation-vseg {
  flex: none;
  width: 100%;
  border-top: 1px solid var(--bs-body-bg);
}
.kincy-notation-col-ref {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 2px dashed var(--kincy-slate);
}
/* Le trait d'objectif d'une colonne : même style fin que sur la barre, sans texte. */
.kincy-notation-col-obj {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 1px dashed var(--bs-body-color);
}
.kincy-notation-absent-label {
  margin: auto;
  font-size: .625rem;
  color: var(--kincy-slate);
  writing-mode: vertical-rl;
  transform: rotate(180deg);
}

/* Un agent inconnu de Halo (`NQ-D10`, état 4) : pas de carte. Le fragment porte
 * un marqueur, la colonne de la rangée se replie ; rien de rouge, ce n'est pas
 * une erreur. Elle revient quand un autre agent est choisi. */
.kincy-notation-slot:has([data-notation-unknown]) { display: none; }

/* Le sélecteur de RANGÉE (administrateurs, `NQ-D8`, Q5), au-dessus des deux cartes. */
.kincy-tracking-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-sm) var(--kincy-spacing-md);
}
.kincy-tracking-picker-label { font-size: .875rem; color: var(--kincy-slate); }
.kincy-tracking-picker-select { width: auto; min-width: 14rem; }

/* Colonnes « utiles » du tableau, à la largeur de leur contenu : `width: 1%` +
 * `nowrap` les réduit au contenu, et la dernière colonne, vide, absorbe le
 * reste pour que les rayures aillent jusqu'au bord de la carte, y compris à
 * 390 px. */
.kincy-remote-table .kincy-remote-fit {
  width: 1%;
  white-space: nowrap;
}

/* Le nom du salarié, la colonne qui compte : le reste de la ligne est discret. */
.kincy-remote-table .kincy-remote-name {
  font-weight: 500;
  padding-right: var(--kincy-spacing-md);
}

/* L'agence : le seul point de couleur (`--kincy-agency-ink`, posée en ligne
 * depuis le slot : c'est la teinte du référentiel, D87), puis le trigramme un
 * cran plus petit, en encre courante. Plus de cartouche, de fond ni de filet.
 * Classe propre à ce tableau : `.kincy-agency-pill` reste intacte pour les
 * autres écrans. `.kincy-agency-dot` et `.kincy-agency-code` sont réemployés
 * tels quels. */
.kincy-remote-agency {
  --kincy-agency-ink: var(--kincy-neutral-medium);
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.8125rem;
}

/* Le picto de statut : un glyphe SEUL, sans disque ni contour (le cerclage
 * slate d'avant est retiré à la demande de Rodolphe). Traits arrondis, teintes
 * pâles ci-dessus. `role="img"` + `aria-label` restent sur le `<svg>`. */
.kincy-remote-status {
  vertical-align: -0.2em;
  flex: none;
}

.kincy-remote-status path {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.kincy-remote-status--approved path {
  stroke: var(--kincy-remote-approved);
}

.kincy-remote-status--pending path {
  stroke: var(--kincy-remote-pending);
  stroke-width: 1.6;
}

/* L'ESTAMPILLE DE FRAÎCHEUR, DANS la carte du télétravail, en bas et alignée à
 * droite (déplacée le 2026-10-07 : elle était sous la rangée des deux cartes).
 * Petite et en gris clair (`--kincy-neutral-medium`) : une mention discrète.
 *
 * ⚠️ Comme l'état normal est DÉJÀ gris clair, le gris ne peut plus signaler le
 * retard. En retard, l'estampille passe donc en ENCRE COURANTE et la mention
 * « · mise à jour en retard » est VISIBLE (elle était `visually-hidden`) : le
 * texte porte l'état, la couleur le souligne. Seuil : 2 h en journée, 26 h
 * hors journée, jugé au rendu. */
.kincy-sync-stamp {
  font-size: 0.8125rem;
  text-align: right;
  color: var(--kincy-neutral-medium);
}

.kincy-sync-stamp--stale {
  color: var(--bs-body-color);
}

/* ===== Commandes : la console et l'enveloppe « non publiée » (lot 2) =====
 * docs/commands-panel-spec.md (CM-D9, CM-D12 à CM-D14), maquette Claude Design
 * validée et retouchée par Rodolphe le 2026-10-08. Bloc délimité : ne rien mêler
 * au reste de la feuille.
 *
 * ⚠️ ÉCART ASSUMÉ ET CONFINÉ (docs/conventions/ui.md §3) : les tokens
 * `--kincy-hazard*` et `--kincy-console-*`, et les polices des plaques (Arial),
 * de l'étiquette (Roboto Mono) et du journal (Courier Prime), ne s'emploient QUE
 * dans des sélecteurs `.kincy-console…` ou `.kincy-unreleased…`. Tenu par
 * tests/test_console_tokens.py : un sélecteur de rapport qui en emprunterait un
 * ferait rougir le test. Les arrêts de dégradé des pièces dessinées (vis, levier,
 * écrou, bague de diode) restent en valeurs brutes : ce sont des reflets de
 * rendu, propres à la console, pas des couleurs de la charte. */
:root {
  /* Jaune de signalisation « travaux » et son encre (bande, étiquette). */
  --kincy-hazard: #FFD100;
  --kincy-hazard-ink: #121212;

  /* Le pupitre (fond de la console). */
  --kincy-console-bezel: #1C1F22;
  --kincy-console-bezel-hi: #262A2E;
  --kincy-console-bezel-edge: #0D0F11;
  --kincy-console-ink: #E3E6E9;

  /* La platine : aluminium jauni brossé (retouche de Rodolphe). */
  --kincy-console-alu-hi: #D3CFC1;
  --kincy-console-alu-lo: #B2AC9A;
  --kincy-console-alu-edge: #6F6A5C;

  /* La plaque gravée (traffolyte noir, lettres claires) : 15,17:1. */
  --kincy-console-plate: #18191B;
  --kincy-console-plate-hi: #26272A;
  --kincy-console-plate-lo: #111214;
  --kincy-console-plate-edge: #000000;
  --kincy-console-engrave: #F1EEE6;

  /* L'étiquette d'explication, ruban plastique orange brûlé. ⚠️ #A84C1C et non
   * le #B0501E de la maquette : mesuré à 4,49:1 sous l'encre #F2EDE3, et à
   * 3,34:1 sous son reflet. Assombri à 4,84:1 et reflet borné au rembourrage
   * (apps/core/contrast.py, 2026-10-08). */
  --kincy-console-label: #A84C1C;
  --kincy-console-label-ink: #F2EDE3;

  /* Le capot de protection rouge. */
  --kincy-console-guard: #D2302A;
  --kincy-console-guard-lo: #9C1B16;
  --kincy-console-guard-inner: #6E110E;

  /* Chrome (charnière, écrou, bague). */
  --kincy-console-chrome-hi: #F6F8FA;
  --kincy-console-chrome-mid: #AEB5BC;
  --kincy-console-chrome-lo: #4E545B;

  /* La diode. ⚠️ VERTE, couleur retenue par la maquette (réglage « ledColor »).
   * design.md §3.6 exclut le vert : l'écart reste confiné à la console. */
  --kincy-console-led-on: #3BE472;
  --kincy-console-led-busy: #FFB300;
  --kincy-console-led-error: #FF2D20;

  /* Le journal de bord, papier d'imprimante de cockpit. */
  --kincy-console-paper: #F2EFE6;
  --kincy-console-paper-ink: #23262A;
  --kincy-console-paper-muted: #4A4F55;
  --kincy-console-paper-rule: rgba(35, 38, 42, 0.09);

  /* L'encre des ombres de la console : du NOIR, et c'est voulu. Sur un pupitre
   * presque noir, l'encre slate des ombres de Link (--kincy-shadow-ink) ne se
   * verrait pas. Écart confiné comme le reste de la famille (ui.md §1 et §3). */
  --kincy-console-shadow-ink: 0, 0, 0;

  /* Anneau de focus, sombre sur l'aluminium clair. */
  --kincy-console-focus: #121212;

  /* Les trois polices de la console, chacune un écart confiné (ui.md §3). */
  --kincy-console-font-plate: Arial, "Helvetica Neue", Helvetica, sans-serif;
  --kincy-console-font-caption: "Roboto Mono", ui-monospace, monospace;
  --kincy-console-font-log: "Courier Prime", "Courier New", Courier, monospace;
}

/* ---------- La console ---------- */
.kincy-console {
  position: relative;
  max-width: 1120px;
  box-sizing: border-box;
  padding: var(--kincy-spacing-lg);
  border-radius: 12px;
  color: var(--kincy-console-ink);
  background:
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.018) 0 1px, transparent 1px 4px),
    linear-gradient(180deg, var(--kincy-console-bezel-hi), var(--kincy-console-bezel));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    inset 0 0 0 1px var(--kincy-console-bezel-edge),
    0 10px 30px rgba(20, 24, 28, 0.22);
}

.kincy-console-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-md) var(--kincy-spacing-xl);
  margin-bottom: 20px;
}

.kincy-console-empty {
  margin: 0;
  color: var(--kincy-console-ink);
}

/* ---------- La plaque gravée (traffolyte), en Arial ---------- */
.kincy-console-tag {
  position: relative;
  display: inline-flex;
  align-items: stretch;
  max-width: 100%;
  box-sizing: border-box;
  padding: 0 var(--kincy-spacing-sm);
  border-radius: 3px;
  font-family: var(--kincy-console-font-plate);
  color: var(--kincy-console-engrave);
  background: linear-gradient(180deg, var(--kincy-console-plate-hi), var(--kincy-console-plate) 55%, var(--kincy-console-plate-lo));
  border: 1px solid var(--kincy-console-plate-edge);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 -1px 0 rgba(var(--kincy-console-shadow-ink), 0.6),
    0 1px 0 rgba(255, 255, 255, 0.07),
    0 2px 4px rgba(var(--kincy-console-shadow-ink), 0.55);
}

.kincy-console-tag::before,
.kincy-console-tag::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 5px;
  height: 5px;
  margin-top: -2.5px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #9aa0a6, #2d3034 75%);
  box-shadow: 0 0 0 1px rgba(var(--kincy-console-shadow-ink), 0.6);
}

.kincy-console-tag::before { left: 5px; }
.kincy-console-tag::after { right: 5px; }

/* ⚠️ Porte des <h1>/<h2> : la classe (0,1,0) l'emporte sur la taille, la graisse
 * et la police que Bootstrap et la feuille posent sur les titres (0,0,1). */
.kincy-console-tag-label {
  margin: 0;
  padding: 9px 12px 8px;
  font-family: inherit;
  font-size: 14px;
  line-height: 16px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-align: center;
  text-transform: uppercase;
  color: var(--kincy-console-engrave);
  text-shadow: 0 -1px 0 rgba(var(--kincy-console-shadow-ink), 0.95), 0 1px 0 rgba(255, 255, 255, 0.1);
}

.kincy-console-tag--title .kincy-console-tag-label {
  padding: 11px 16px 10px 18px;
  font-size: 19px;
  line-height: 22px;
  letter-spacing: 0.24em;
}

/* ---------- Les platines : un carré par interrupteur ---------- */
.kincy-console-stack {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--kincy-spacing-md);
}

.kincy-console-plate {
  position: relative;
  isolation: isolate;
  aspect-ratio: 1 / 1;
  box-sizing: border-box;
  padding: var(--kincy-spacing-lg);
  border-radius: 6px;
  scroll-margin-top: var(--kincy-spacing-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  background:
    linear-gradient(115deg, rgba(255, 250, 230, 0) 15%, rgba(255, 250, 230, 0.32) 38%, rgba(255, 250, 230, 0.08) 48%, rgba(255, 250, 230, 0) 64%),
    radial-gradient(120% 120% at 50% 45%, transparent 50%, rgba(150, 120, 50, 0.22) 100%),
    radial-gradient(55% 40% at 18% 82%, rgba(170, 140, 60, 0.16), transparent 70%),
    radial-gradient(140% 90% at 85% 110%, rgba(40, 30, 10, 0.12), transparent 60%),
    linear-gradient(180deg, var(--kincy-console-alu-hi), var(--kincy-console-alu-lo));
  box-shadow:
    inset 0 1px 0 rgba(255, 250, 230, 0.6),
    inset 0 -1px 0 rgba(40, 30, 10, 0.28),
    0 0 0 1px var(--kincy-console-alu-edge),
    0 3px 0 rgba(var(--kincy-console-shadow-ink), 0.45),
    0 6px 12px rgba(var(--kincy-console-shadow-ink), 0.35);
}

/* La platine visée par l'ancre de l'enveloppe (`#commande-<clé>`). */
.kincy-console-plate:target {
  box-shadow:
    inset 0 1px 0 rgba(255, 250, 230, 0.6),
    0 0 0 3px var(--kincy-hazard),
    0 6px 12px rgba(var(--kincy-console-shadow-ink), 0.35);
}

/* Le brossage : un bruit SVG étiré, posé en surimpression. */
.kincy-console-brush {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: 6px;
  pointer-events: none;
  mix-blend-mode: overlay;
  opacity: 0.25;
}

.kincy-console-plate > .kincy-console-cluster,
.kincy-console-plate > .kincy-console-caption {
  position: relative;
}

.kincy-console-screw {
  position: absolute;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #EEF1F3, #9AA1A8 45%, #4C5258 100%);
  box-shadow: 0 1px 1px rgba(var(--kincy-console-shadow-ink), 0.75), inset 0 -1px 1px rgba(var(--kincy-console-shadow-ink), 0.4);
}

.kincy-console-screw::after {
  content: "";
  position: absolute;
  left: 2px;
  right: 2px;
  top: 5px;
  height: 2px;
  border-radius: 1px;
  background: #2C3035;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.35);
}

.kincy-console-screw--tl { top: 10px; left: 10px; transform: rotate(35deg); }
.kincy-console-screw--tr { top: 10px; right: 10px; transform: rotate(-20deg); }
.kincy-console-screw--bl { bottom: 10px; left: 10px; transform: rotate(80deg); }
.kincy-console-screw--br { bottom: 10px; right: 10px; transform: rotate(10deg); }

/* L'étiquette d'explication : ruban plastique, coupe franche, trois lignes au
 * plus. ⚠️ Le reflet ne couvre que le rembourrage haut (7 px) : étendu sous le
 * texte, il ramenait le contraste à 3,34:1. */
.kincy-console-caption {
  margin: 0;
  width: 100%;
  box-sizing: border-box;
  padding: 7px 12px 6px;
  font-family: var(--kincy-console-font-caption);
  font-weight: 300;
  font-size: 13px;
  line-height: 16px;
  text-align: center;
  color: var(--kincy-console-label-ink);
  text-shadow: 0 -0.5px 0 rgba(var(--kincy-console-shadow-ink), 0.6), 0 0.5px 0 rgba(255, 228, 205, 0.28);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0) 7px),
    linear-gradient(0deg, rgba(var(--kincy-console-shadow-ink), 0.12), rgba(var(--kincy-console-shadow-ink), 0) 6px),
    var(--kincy-console-label);
  border: 1px solid rgba(var(--kincy-console-shadow-ink), 0.45);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 1px 2px rgba(var(--kincy-console-shadow-ink), 0.45);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

/* ---------- Le bloc de commande : puits, capot, levier, diode ---------- */
.kincy-console-cluster {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--kincy-spacing-lg);
  padding: 32px var(--kincy-spacing-lg) 20px;
  border-radius: 6px;
  zoom: 1.1;
  background: linear-gradient(180deg, rgba(var(--kincy-console-shadow-ink), 0.2), rgba(var(--kincy-console-shadow-ink), 0.08));
  box-shadow: inset 0 1px 3px rgba(var(--kincy-console-shadow-ink), 0.55), inset 0 -1px 0 rgba(255, 255, 255, 0.05);
}

.kincy-console-well {
  position: relative;
  width: 64px;
  height: 96px;
  border-radius: 10px;
  perspective: 360px;
  perspective-origin: 50% 0%;
  background: radial-gradient(ellipse at 50% 45%, #2A2E33, #141619 72%);
  box-shadow:
    inset 0 2px 6px rgba(var(--kincy-console-shadow-ink), 0.85),
    inset 0 -1px 0 rgba(255, 255, 255, 0.06),
    0 1px 0 rgba(255, 255, 255, 0.07);
}

.kincy-console-hinge {
  position: absolute;
  z-index: 3;
  top: -6px;
  left: 4px;
  right: 4px;
  height: 9px;
  border-radius: 5px;
  pointer-events: none;
  background: linear-gradient(180deg, var(--kincy-console-chrome-lo), var(--kincy-console-chrome-hi) 40%, var(--kincy-console-chrome-mid) 60%, var(--kincy-console-chrome-lo));
  box-shadow: 0 2px 2px rgba(var(--kincy-console-shadow-ink), 0.6);
}

/* Le levier : un vrai <button role="switch">. `aria-disabled` (et non
 * `disabled`) capot fermé : il reste focalisable, et le geste refusé s'annonce. */
.kincy-console-toggle {
  position: absolute;
  inset: 0;
  z-index: 1;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: transparent;
  cursor: pointer;
}

.kincy-console-toggle[aria-disabled="true"] { cursor: not-allowed; }

.kincy-console-toggle:focus-visible {
  outline: 3px solid var(--kincy-console-focus);
  outline-offset: 4px;
}

.kincy-console-nut {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 32px;
  height: 30px;
  margin: -15px 0 0 -16px;
  clip-path: polygon(25% 2%, 75% 2%, 100% 50%, 75% 98%, 25% 98%, 0 50%);
  background: linear-gradient(160deg, var(--kincy-console-chrome-hi), var(--kincy-console-chrome-mid) 45%, var(--kincy-console-chrome-lo));
}

.kincy-console-collar {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -9px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, #1b1d20 0 34%, transparent 36%),
    radial-gradient(circle at 35% 30%, var(--kincy-console-chrome-hi), var(--kincy-console-chrome-mid) 50%, var(--kincy-console-chrome-lo));
  box-shadow: 0 1px 2px rgba(var(--kincy-console-shadow-ink), 0.6);
}

.kincy-console-lever-wrap {
  position: absolute;
  inset: 0;
  filter: drop-shadow(2px 3px 2px rgba(var(--kincy-console-shadow-ink), 0.6));
}

/* Levier HAUT = allumé, BAS = éteint : la position porte l'état sans la couleur. */
.kincy-console-lever {
  position: absolute;
  left: 50%;
  bottom: 50%;
  width: 12px;
  height: 38px;
  margin-left: -6px;
  border-radius: 6px 6px 3px 3px;
  transform-origin: 50% 100%;
  transition: transform 150ms cubic-bezier(0.5, 0, 0.2, 1.5);
  background: linear-gradient(90deg, #5A6067, var(--kincy-console-chrome-hi) 38%, #C2C8CE 58%, #474C52);
}

.kincy-console-lever::before {
  content: "";
  position: absolute;
  top: -6px;
  left: -3px;
  width: 18px;
  height: 14px;
  border-radius: 7px;
  background: radial-gradient(circle at 35% 35%, #ffffff, #C9CFD5 40%, #5B6168 100%);
}

.kincy-console-toggle[aria-checked="false"] .kincy-console-lever { transform: scaleY(-1); }

/* Le capot rabattable : un second bouton, au-dessus du levier tant qu'il est
 * fermé, rabattu en 3D quand il est levé. */
.kincy-console-cover {
  position: absolute;
  z-index: 2;
  top: -2px;
  left: -3px;
  right: -3px;
  bottom: -3px;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  transform-origin: 50% 0;
  transform-style: preserve-3d;
  transition: transform 320ms cubic-bezier(0.3, 1.35, 0.5, 1);
}

.kincy-console-cover[aria-disabled="true"] { cursor: progress; }

.kincy-console-plate[data-cover="open"] .kincy-console-cover { transform: rotateX(100deg); }

.kincy-console-cover-face,
.kincy-console-cover-inner {
  position: absolute;
  inset: 0;
  border-radius: 6px 6px 12px 12px;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.kincy-console-cover-face {
  background:
    linear-gradient(90deg, rgba(var(--kincy-console-shadow-ink), 0.32), rgba(255, 255, 255, 0.14) 28%, rgba(255, 255, 255, 0) 55%, rgba(var(--kincy-console-shadow-ink), 0.32)),
    linear-gradient(180deg, var(--kincy-console-guard), var(--kincy-console-guard-lo));
  box-shadow: inset 0 0 0 1px rgba(var(--kincy-console-shadow-ink), 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.28), 0 3px 6px rgba(var(--kincy-console-shadow-ink), 0.55);
}

.kincy-console-cover-face::before {
  content: "";
  position: absolute;
  top: 10px;
  bottom: 16px;
  left: 9px;
  right: 9px;
  border-radius: 3px;
  background: repeating-linear-gradient(180deg, rgba(var(--kincy-console-shadow-ink), 0) 0 4px, rgba(var(--kincy-console-shadow-ink), 0.26) 4px 6px, rgba(255, 255, 255, 0.16) 6px 7px);
  box-shadow: inset 0 1px 2px rgba(var(--kincy-console-shadow-ink), 0.35);
}

.kincy-console-cover-face::after {
  content: "";
  position: absolute;
  bottom: -4px;
  left: 50%;
  width: 24px;
  height: 7px;
  margin-left: -12px;
  border-radius: 0 0 5px 5px;
  background: linear-gradient(180deg, var(--kincy-console-guard-lo), var(--kincy-console-guard-inner));
}

.kincy-console-cover-inner {
  transform: rotateX(180deg);
  background: linear-gradient(180deg, var(--kincy-console-guard-inner), var(--kincy-console-guard-lo));
  box-shadow: inset 0 0 0 3px rgba(var(--kincy-console-shadow-ink), 0.3), inset 0 0 8px rgba(var(--kincy-console-shadow-ink), 0.5);
}

.kincy-console-cover:focus-visible { outline: none; }

.kincy-console-cover:focus-visible .kincy-console-cover-face,
.kincy-console-cover:focus-visible .kincy-console-cover-inner {
  outline: 3px solid var(--kincy-console-focus);
  outline-offset: 3px;
}

/* La diode : le RETOUR d'état du serveur, jamais l'écho du levier (CM-D12).
 * `data-state` de la platine : off, busy (ambre clignotante, requête en vol),
 * on (allumée), error (rouge clignotante). */
.kincy-console-led {
  --kincy-console-led-current: var(--kincy-console-led-on);
  position: relative;
  display: block;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: conic-gradient(from 210deg, #4E545B, #F6F8FA 18%, #8F969D 35%, #3D4247 52%, #E8ECEF 70%, #6A7077 85%, #4E545B);
  box-shadow: 0 2px 3px rgba(var(--kincy-console-shadow-ink), 0.7), inset 0 0 0 1px rgba(var(--kincy-console-shadow-ink), 0.5);
}

.kincy-console-led::before,
.kincy-console-led::after {
  content: "";
  position: absolute;
  inset: 6px;
  border-radius: 50%;
}

.kincy-console-led::before {
  background:
    radial-gradient(circle at 38% 32%, rgba(255, 255, 255, 0.32) 0 10%, transparent 28%),
    radial-gradient(circle at 50% 55%, color-mix(in srgb, var(--kincy-console-led-current) 26%, #0B0D0E), #050606 88%);
  box-shadow: inset 0 2px 3px rgba(var(--kincy-console-shadow-ink), 0.85), 0 0 0 1px rgb(var(--kincy-console-shadow-ink));
}

.kincy-console-led::after {
  opacity: 0;
  transition: opacity 120ms ease-out;
  background:
    radial-gradient(circle at 38% 32%, #ffffff 0 9%, transparent 26%),
    radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--kincy-console-led-current) 45%, #ffffff) 0 22%, var(--kincy-console-led-current) 58%, color-mix(in srgb, var(--kincy-console-led-current) 62%, rgb(var(--kincy-console-shadow-ink))) 100%);
  box-shadow:
    0 0 0 1px rgb(var(--kincy-console-shadow-ink)),
    0 0 8px 2px var(--kincy-console-led-current),
    0 0 24px 9px color-mix(in srgb, var(--kincy-console-led-current) 45%, transparent);
}

.kincy-console-plate[data-state="on"] .kincy-console-led::after { opacity: 1; }

.kincy-console-plate[data-state="busy"] .kincy-console-led {
  --kincy-console-led-current: var(--kincy-console-led-busy);
}

.kincy-console-plate[data-state="busy"] .kincy-console-led::after {
  opacity: 1;
  box-shadow: 0 0 0 1px rgb(var(--kincy-console-shadow-ink)), 0 0 5px 1px var(--kincy-console-led-current);
  animation: kincy-console-blink 300ms steps(1, end) infinite;
}

.kincy-console-plate[data-state="error"] .kincy-console-led {
  --kincy-console-led-current: var(--kincy-console-led-error);
}

.kincy-console-plate[data-state="error"] .kincy-console-led::after {
  opacity: 1;
  box-shadow: 0 0 0 1px rgb(var(--kincy-console-shadow-ink)), 0 0 6px 2px var(--kincy-console-led-current);
  animation: kincy-console-blink 500ms steps(1, end) infinite;
}

@keyframes kincy-console-blink {
  0% { opacity: 1; }
  50% { opacity: 0; }
}

/* ---------- Le journal de bord : imprimante de cockpit, en Courier Prime ---------- */
.kincy-console-log { margin-top: 28px; }

.kincy-console-log-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
  margin-bottom: 14px;
}

.kincy-console-slot {
  position: relative;
  z-index: 1;
  height: 12px;
  border-radius: 6px;
  background: linear-gradient(180deg, #08090A, #24282C);
  box-shadow: inset 0 2px 3px rgb(var(--kincy-console-shadow-ink)), 0 1px 0 rgba(255, 255, 255, 0.08);
}

.kincy-console-paper {
  position: relative;
  margin: -5px 14px 0;
  padding: 22px 48px 18px;
  color: var(--kincy-console-paper-ink);
  font-family: var(--kincy-console-font-log);
  background:
    repeating-linear-gradient(180deg, transparent 0 35px, var(--kincy-console-paper-rule) 35px 36px),
    var(--kincy-console-paper);
  background-position: 0 23px;
  box-shadow: 0 2px 0 rgba(var(--kincy-console-shadow-ink), 0.35), 0 8px 18px rgba(var(--kincy-console-shadow-ink), 0.45);
}

/* Les perforations d'entraînement, des deux côtés. */
.kincy-console-paper::before,
.kincy-console-paper::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 12px;
  background: radial-gradient(circle, var(--kincy-console-bezel) 0 3.5px, transparent 4px) 0 6px / 12px 24px repeat-y;
}

.kincy-console-paper::before { left: 14px; }
.kincy-console-paper::after { right: 14px; }

.kincy-console-paper-scroll { overflow-x: auto; }

/* Un vrai <table> (la maquette posait des `div role=table` faute de mieux dans
 * son moteur) ; la grille de la maquette devient des largeurs de colonnes. */
.kincy-console-log-table {
  width: 100%;
  min-width: 640px;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: 15px;
  line-height: 20px;
}

.kincy-console-log-table th,
.kincy-console-log-table td {
  height: 36px;
  padding: 0 var(--kincy-spacing-md) 0 0;
  text-align: left;
  vertical-align: middle;
}

.kincy-console-log-table th {
  font-size: 12px;
  line-height: 14px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--kincy-console-paper-muted);
  border-bottom: 1px dashed rgba(35, 38, 42, 0.4);
}

.kincy-console-log-table th:nth-child(1) { width: 188px; }
.kincy-console-log-table th:nth-child(2) { width: 40%; }
.kincy-console-log-table th:nth-child(3) { width: 156px; }

.kincy-console-log-action {
  display: inline-flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.kincy-console-log-action::before {
  content: "";
  width: 9px;
  height: 9px;
  box-sizing: border-box;
  border: 1.5px solid currentColor;
}

.kincy-console-log-action[data-action="on"]::before { background: currentColor; }

/* `prefers-reduced-motion` : la diode ne clignote plus (ambre et rouge fixes),
 * le capot et le levier ne s'animent plus ; le sens se lit sans mouvement. */
@media (prefers-reduced-motion: reduce) {
  .kincy-console-plate[data-state="busy"] .kincy-console-led::after,
  .kincy-console-plate[data-state="error"] .kincy-console-led::after {
    animation: none;
  }

  .kincy-console-led::after,
  .kincy-console-cover,
  .kincy-console-lever {
    transition: none;
  }
}

/* ---------- L'enveloppe « non publiée » (CM-D9) ----------
 * ⚠️ PUREMENT VISUELLE : bandes et étiquette sont en position ABSOLUE, posées
 * SUR la carte. L'enveloppe n'ajoute ni marge, ni rembourrage, ni hauteur : la
 * boîte de la carte est identique au pixel avec et sans elle (mesuré par
 * tools/probe_commands.py). C'est l'écart que le lot 1 avait laissé ouvert. */
.kincy-unreleased { position: relative; }

.kincy-unreleased::before,
.kincy-unreleased::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  z-index: 1;
  height: 10px;
  pointer-events: none;
  background: repeating-linear-gradient(-45deg, var(--kincy-hazard) 0 8px, var(--kincy-hazard-ink) 8px 16px);
}

.kincy-unreleased::before {
  top: 0;
  border-radius: var(--kincy-radius-md) var(--kincy-radius-md) 0 0;
}

.kincy-unreleased::after {
  bottom: 0;
  border-radius: 0 0 var(--kincy-radius-md) var(--kincy-radius-md);
}

.kincy-unreleased-tag {
  position: absolute;
  z-index: 2;
  top: 18px;
  right: var(--kincy-spacing-md);
  max-width: calc(100% - 2 * var(--kincy-spacing-md));
  box-sizing: border-box;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 0;
  padding: 3px 12px;
  border-radius: var(--kincy-radius-sm);
  background: var(--kincy-hazard-ink);
  border: 2px solid var(--kincy-hazard);
  font-size: 12px;
  line-height: 16px;
  box-shadow: 0 2px 6px rgba(29, 29, 27, 0.25);
}

.kincy-unreleased-message {
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--kincy-hazard);
}

.kincy-unreleased-link {
  display: inline-flex;
  align-items: center;
  gap: var(--kincy-spacing-xs);
  color: #ffffff;
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.kincy-unreleased-link:hover,
.kincy-unreleased-link:focus {
  color: var(--kincy-hazard);
}

.kincy-unreleased-link:focus-visible {
  outline: 2px solid var(--kincy-hazard);
  outline-offset: 3px;
  border-radius: 2px;
}
/* ===== fin du bloc Commandes ===== */

/* ===========================================================================
   Page « Contrats : conformité SLA » et page « Réglages du client » (zone
   Clients) · 2026-10-09
   Spec : docs/contracts-sla-spec.md (maquettes D1 et D2, CS-A37).
   ---------------------------------------------------------------------------
   *Réécrit le 2026-10-09 (CS-A59, Rodolphe : « D1 pour la mise en page de bout
   en bout »).* Le premier rendu avait été codé d'après la prose de la spec et
   les composants Link voisins (carte KPI à liseré, h2 KINCY Blue, pastilles
   cyan, table Bootstrap, interrupteurs), sans que la maquette ait été vue.
   Les tailles, espacements, rayons et la typographie de ce bloc sont ceux du
   balisage et du script de D1 et D2 ; chaque couleur y est un token Link
   (correspondance : spec §10 sexies).

   Rendu SERVEUR : barres en <button>, courbe de réponse en SVG (ui.md §4.5),
   géométrie calculée par `apps/clients/sla_page.py` (`chart_view`).
   Tout sélecteur de ce bloc commence par `.kincy-sla-`.

   ⚠️ `--kincy-sla-in` est un ÉCART À LA CHARTE (ui.md §3, CS-A27) : `design.md`
   §3.6 exclut le vert. Il ne s'emploie QUE dans les sélecteurs `.kincy-sla-…`
   ci-dessous, comme les autres tokens `--kincy-sla-*` (CS-A60), et
   `tests/test_sla_tokens.py` les tient (emploi, définition, fuite par :root,
   gabarits). S'il échoue, on corrige le sélecteur, on n'élargit pas le test.

   Valeur : le vert de charte (#6BB52D) ne tient que 2,54:1 sur blanc, sous les
   3:1 d'un élément graphique (WCAG 1.4.11) ; #5A9E24 en tient 3,31:1 et reste
   nettement plus clair que le rouge `--kincy-red` (6,54:1), ce qui garde l'écart
   de luminosité qui rend l'échelle vert / orange / rouge lisible aux
   daltoniens. Les libellés doublent toujours la couleur.

   ⚠️ Pas de Roboto Mono (la police des slugs et des numéros dans D1) : elle est
   confinée à la console « Commandes » par `tests/test_console_tokens.py`. Les
   numéros de ticket et les slugs prennent la pile monospace de Bootstrap.
   =========================================================================== */
:root {
  --kincy-sla-in: #5A9E24; /* 3,31:1 sur blanc */
  /* Le gris bleuté de D1 (#8A96A6) : courbe de réponse, ses points, la ligne de
     base du graphe, le filet de la bande « Avant contrat ». Aucun token de Link
     n'en approche (`--kincy-neutral-dark` est deux fois plus sombre, et la courbe
     doit rester un éclairage, CS-A28). 3,00:1 sur blanc : élément graphique
     seulement, jamais une encre de texte. */
  --kincy-sla-muted: #8A96A6;
  /* Les fonds pleins des pastilles et du badge (D1) ; l'encre « In » de D1 car
     `--kincy-green-ink` (#4c831f) ne tient que 3,95:1 sur ce fond. L'encre « Out »
     reste `--kincy-red-ink` (5,24:1 sur son fond). */
  --kincy-sla-in-soft: #E6F2DB;
  --kincy-sla-in-ink: #3F6E1A; /* 5,23:1 sur --kincy-sla-in-soft, 6,08:1 sur blanc */
  --kincy-sla-out-soft: #F8E1DF;
  /* Le gris des contrôles segmentés, des slugs et des lignes du tableau (D1
     #EEF3F6) : dérivé de `--kincy-neutral-light`, aucune teinte neuve. */
  --kincy-sla-chrome: color-mix(in srgb, var(--kincy-neutral-light) 65%, var(--bs-body-bg));
}

/* ------------------------------------------------------- La page, sa surface */
.kincy-sla-page {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 1200px;
  margin: 0 auto;
  color: var(--kincy-slate);
  font-variant-numeric: tabular-nums;
}
.kincy-sla-back {
  align-self: flex-start;
  font-size: 14px;
  text-decoration: none;
}
.kincy-sla-page-title {
  margin: 0 0 10px;
  color: var(--kincy-slate);
  font-size: 28px;
  font-weight: 700;
  line-height: 36px;
  letter-spacing: -0.01em;
}
.kincy-sla-surface {
  display: flex;
  flex-direction: column;
  gap: 28px;
  padding: 28px 32px 32px;
  border-radius: 12px;
  background-color: var(--bs-body-bg);
  box-shadow:
    0 1px 2px rgba(var(--kincy-shadow-ink), 0.1),
    0 6px 20px rgba(var(--kincy-shadow-ink), 0.07);
}

/* L'en-tête du bloc : titre slate (D1, et non `.kincy-report-h2`, CS-A59), slug
   en pastille à côté, filtres à droite. */
.kincy-sla-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.kincy-sla-heading { display: flex; flex-direction: column; gap: 2px; }
.kincy-sla-heading-line { display: flex; align-items: center; gap: 10px; }
.kincy-sla-title {
  margin: 0;
  color: var(--kincy-slate);
  font-size: 20px;
  font-weight: 700;
  line-height: 28px;
}
/* *2026-10-12 (`EC-A85`, Rodolphe) :* les titres de l'État contractuel et de la Conformité
   SLA portent AUSSI `.kincy-report-h2`, la convention d'intertitre des rapports : seule
   la COULEUR en vient (cyan de la charte). Taille, graisse et interligne restent ceux de
   ce bloc : `.kincy-sla-title` est écrit plus bas dans la feuille, à spécificité égale
   il gagne. Ce sélecteur à deux classes ne laisse pas le slate recouvrir le cyan.
   La page des réglages, qui porte `.kincy-sla-title` seule, reste slate. */
.kincy-sla-title.kincy-report-h2 { color: var(--kincy-cyan); }
.kincy-sla-subtitle {
  margin: 0;
  color: var(--kincy-neutral-dark);
  font-size: 13px;
}
/* Le slug de bloc (`_block_id.html`), en pastille grise à côté de son titre. */
.kincy-sla-page .kincy-block-id {
  float: none;
  padding: 1px 6px;
  border: 0;
  border-radius: var(--kincy-radius-sm);
  background-color: var(--kincy-sla-chrome);
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  letter-spacing: 0;
}
.kincy-sla-label-line .kincy-block-id { padding: 0 5px; font-size: 10px; }

/* Les filtres du bloc : deux contrôles segmentés, des RADIOS stylées (HTMX lit
   le formulaire ; une case désactivée n'est pas envoyée, CS-A11). */
.kincy-sla-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 0;
}
.kincy-sla-segmented {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border-radius: var(--kincy-radius-md);
  background-color: var(--kincy-sla-chrome);
}
.kincy-sla-segmented-option {
  margin: 0;
  padding: 5px 12px;
  border-radius: 6px;
  background-color: transparent;
  color: var(--kincy-slate);
  font-size: 13px;
  font-weight: 400;
  line-height: 18px;
  cursor: pointer;
  user-select: none;
}
.kincy-sla-segmented .btn-check:checked + .kincy-sla-segmented-option {
  background-color: var(--bs-body-bg);
  color: var(--kincy-slate);
  font-weight: 700;
  box-shadow: 0 1px 2px rgba(var(--kincy-shadow-ink), 0.18);
}
.kincy-sla-segmented .btn-check:focus-visible + .kincy-sla-segmented-option {
  outline: 2px solid var(--kincy-blue);
  outline-offset: 1px;
}
/* Désactivée en vue Années, jamais masquée (CS-A11). */
.kincy-sla-segmented .btn-check:disabled + .kincy-sla-segmented-option {
  cursor: default;
  opacity: 0.45;
}
.kincy-sla-window { display: inline-flex; align-items: center; gap: 8px; }
.kincy-sla-window-label { color: var(--kincy-neutral-dark); font-size: 12px; }

/* Le contenu du fragment : la même respiration que la surface. */
.kincy-sla-body { display: flex; flex-direction: column; gap: 28px; }
.kincy-sla-empty { margin: 0; color: var(--kincy-neutral-dark); font-size: 14px; }
/* L'état vide tient en deux lignes : la zone ne garde la hauteur du loader
   (ui.md §4.12) que pendant une requête. */
.kincy-sla-surface > .kincy-run-report:has(.kincy-sla-empty):not(:has(> .htmx-request)) {
  min-height: 0;
}

/* ----------------------------------------------------------------------------
   La page « Contrats » en onglets : la barre des onglets principaux et la feuille
   de l'État contractuel (maquette D3 de `docs/maquettes/contracts-state/`,
   `EC-A50`, `docs/contracts-state-spec.md` §2.1).

   Palette `plein` : l'onglet actif en KINCY Blue et texte blanc (4,80:1), les
   inactifs sur un gris-bleu clair, la barre posée sur une bande de 3 px. La
   graisse est la même sur les deux états : la distinction tient à l'ÉCART DE
   LUMINANCE des deux fonds (3,98:1, au-dessus des 3:1 d'un composant d'interface,
   WCAG 1.4.11) et à `aria-selected`, pas à la couleur seule (§2.5).

   ⚠️ Trois valeurs seulement n'avaient aucun équivalent dans Link et deviennent
   des tokens `--kincy-state-*` (`EC-A52`, `PE-6`) : le fond des onglets inactifs,
   leur survol, le filet de la feuille. Le bleu, le slate, le blanc, l'ombre et le
   gris atténué sont ceux qui existent déjà. **Confinés** aux sélecteurs
   `.kincy-state…` par `tests/test_state_tokens.py` ; ce ne sont pas des couleurs
   de signal, donc hors du budget plein des signaux (`ui.md` §3).
   ---------------------------------------------------------------------------- */
:root {
  --kincy-state-tab-bg: #E2EBF2;       /* slate dessus : 6,39:1 */
  --kincy-state-tab-hover: #CBDAE6;    /* slate dessus : 5,40:1 */
  --kincy-state-sheet-border: #D5DEE5; /* filet de la feuille et de l'état vide */
}
.kincy-state-main {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}
.kincy-state-tabs {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  border-bottom: 3px solid var(--kincy-blue);
}
.kincy-state-tab {
  display: flex;
  align-items: center;
  gap: 10px;
  box-sizing: border-box;
  min-height: 56px;
  margin: 0;
  padding: 0 24px;
  border: 0;
  border-radius: 4px 4px 0 0;
  background-color: var(--kincy-state-tab-bg);
  color: var(--kincy-slate);
  font-family: inherit;
  font-size: 17px;
  font-weight: 700;
  line-height: 24px;
  cursor: pointer;
}
.kincy-state-tab svg { flex: 0 0 auto; }
.kincy-state-tab[aria-selected="false"]:hover { background-color: var(--kincy-state-tab-hover); }
.kincy-state-tab[aria-selected="true"] {
  background-color: var(--kincy-blue);
  color: var(--bs-body-bg);
}
.kincy-state-tab:focus-visible {
  outline: 2px solid var(--kincy-blue);
  outline-offset: 2px;
}

/* La feuille de l'onglet (D3 : filet, rayon de 4 px, ombre légère). Son en-tête
   réemploie celui du bloc SLA (`.kincy-sla-head`, `-heading`, `-title`,
   `-subtitle`, le slug) : même grammaire de titre slate, `ui.md` §4.10. */
.kincy-state-sheet {
  display: flex;
  flex-direction: column;
  gap: 22px;
  box-sizing: border-box;
  min-width: 0;
  padding: 24px;
  border: 1px solid var(--kincy-state-sheet-border);
  border-radius: var(--kincy-radius-sm);
  background-color: var(--bs-body-bg);
  box-shadow: 0 1px 3px rgba(var(--kincy-shadow-ink), 0.08);
}
/* L'état vide d'`EC-A13` : trois lignes en pointillé, comme D3. */
.kincy-state-empty {
  margin: 0;
  padding: 28px 16px;
  border: 1px dashed var(--kincy-state-sheet-border);
  border-radius: var(--kincy-radius-md);
  color: var(--kincy-neutral-dark);
  font-size: 13px;
  line-height: 19px;
  text-align: center;
}
/* L'état vide tient en trois lignes : la zone ne garde la hauteur du loader
   (ui.md §4.12) que pendant une requête, comme la page SLA. *Amendé le 2026-10-12
   (lot 3)* : l'emplacement neutre du lot 2 (`.kincy-state-placeholder`) est retiré,
   remplacé par les intercalaires, la Synthèse et l'onglet d'un contrat. */
.kincy-state-body > .kincy-run-report:has(.kincy-state-empty):not(:has(> .htmx-request)) {
  min-height: 0;
}

/* ----------------------------------------------------------------------------
   L'onglet « État contractuel » (lot 3, 2026-10-12) : le rail des intercalaires,
   l'interrupteur « Échus », le tableau `etat-synthese` et le graphe `etat-contrat`
   (maquette D3, `docs/maquettes/contracts-state/`, spec §2.2, §5, §6).

   ⚠️ RÉEMPLOI D'ABORD (`EC-A52`). Un neutre de D3 prend un token existant, ou un
   mélange de tokens existants, quand l'écart est sous le seuil de perception
   (ΔE 2000 < 2,3, `EC-A76`) ; seules trois valeurs sans équivalent deviennent des
   tokens à valeur neuve (`#A35400`, `#66707D`, `#C4CDD5`), et trois autres sont
   des MÉLANGES de `--kincy-neutral-light` sans teinte neuve :
     - fond des intercalaires inactifs (#DDE5EC) et leur survol (#D0DAE3) :
       `--kincy-state-tab-bg` et `-tab-hover` du lot 2 (ΔE 1,33 et 1,93) ;
     - fond de l'en-tête du groupe Interventions (#E4EBF0) : `--kincy-state-tab-bg`
       (ΔE 1,06) ; Période des lignes paires (#E3EDF3) : `--kincy-neutral-light`
       (ΔE 1,50) ; filet du conteneur et de l'en-tête (#E2EFF4) : idem, exact ;
     - alternance des lignes (#EFF6F9) : EXACTEMENT celle de `.kincy-ms-grid`
       (`--kincy-neutral-light` à 55 %), rendue opaque (`--kincy-state-row-alt`) ;
     - grille du graphe, halo de la courbe, bandes « Avant / Après le contrat » :
       les classes du graphe SLA de la même page (`.kincy-sla-grid`,
       `.kincy-sla-response-halo`, `.kincy-sla-band`), mêmes valeurs dans D1 et D3 ;
     - titres, sous-titres, légende et libellé du graphe : `.kincy-sla-incidents-title`,
       `-heading`, `.kincy-sla-subtitle`, `.kincy-sla-legend`, `.kincy-sla-label`.
   Les couleurs du graphe sont celles d'`EC-A53` (§12 quinquies), PAS celles de D3 :
   crédit `--kincy-cyan` plein cerclé de `--kincy-blue` (le cyan ne tient que
   2,67:1 sur blanc, parade de `.kincy-profit-bars`), consommé `--kincy-blue`, solde
   `--kincy-magenta`. **Confinés** aux sélecteurs `.kincy-state…` par
   `tests/test_state_tokens.py`. Aucune couleur de signal de plus : `#A35400` est
   l'orange « à surveiller » assombri pour tenir 4,5:1 sur l'alternance (`EC-A42`).
   ---------------------------------------------------------------------------- */
:root {
  --kincy-state-negative-ink: #A35400; /* 5,49:1 sur blanc, 5,02:1 sur l'alternance */
  --kincy-state-muted-ink: #66707D;    /* zéro atténué : 5,03:1 sur blanc, 4,60:1 sur l'alternance */
  --kincy-state-chip-border: #C4CDD5;  /* le filet de l'étiquette « échu » : un trait, jamais une encre */
  /* EC-A85 : l'encre ET le contour de la pastille « +Xh / mois » du rail. Encre sur son
     fond (`--kincy-green-soft`, #BCE699) : 5,71:1. Contour sur les TROIS fonds d'un
     intercalaire : blanc (actif) 8,04:1, repos (#E2EBF2) 6,66:1, survol (#CBDAE6) 5,63:1,
     donc la pastille se distingue des fonds (WCAG 1.4.11, 3:1). Ni `--kincy-green-ink`
     (3,80:1 au repos, 3,22:1 au survol sur l'intercalaire nu) ni `--kincy-sla-in-ink`
     (confiné au SLA) ne conviennent.
     Amendé le 2026-10-12 (EC-A88, maquette « A · Compteur ») : la pastille est retirée ;
     le token reste l'encre de l'UNITÉ du crédit (« h / mois », 10 px, 600), texte courant
     qui tient 4,5:1 sur les trois fonds (8,04 / 6,66 / 5,63:1). Le gros chiffre (34 px, 700,
     texte large) prend `--kincy-green-ink`, à qui 3:1 suffit (4,59 / 3,80 / 3,22:1). */
  --kincy-state-credit-ink: #245C0F;
  /* Le fond du mois lu : celui de sa colonne dans le graphe ET de sa ligne de lecture
     (EC-A85). Le bleu de la charte à 8 %, jamais une valeur neuve. */
  --kincy-state-month-fill: color-mix(in srgb, var(--kincy-blue) 8%, transparent);
  /* D3 #EEF3F6 (ΔE 1,78) : en-tête du groupe Prépayés, abrégé d'un contrat, filets de lignes. */
  --kincy-state-chrome: color-mix(in srgb, var(--kincy-neutral-light) 65%, var(--bs-body-bg));
  /* D3 #F6F8FA (ΔE 1,01) : en-têtes de colonne, Période des lignes impaires, encadré d'alerte. */
  --kincy-state-head-bg: color-mix(in srgb, var(--kincy-neutral-light) 30%, var(--bs-body-bg));
  /* L'alternance de `.kincy-ms-grid` (#EFF6F9), opaque. */
  --kincy-state-row-alt: color-mix(in srgb, var(--kincy-neutral-light) 55%, var(--bs-body-bg));
}

/* ------------------------------------------------- La disposition : rail + feuille */
.kincy-state-layout {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  min-width: 0;
}
/* Au-dessus de la feuille : l'intercalaire actif recouvre son filet d'un pixel. */
.kincy-state-rail {
  position: relative;
  z-index: 1;
  display: flex;
  flex: 0 0 200px; /* 156 px (D3) puis 200 px (maquette A, EC-A88) : spec §5.8 */
  flex-direction: column;
  min-width: 0;
  padding-top: 20px;
}
.kincy-state-rail-inner { display: flex; flex-direction: column; gap: 14px; }
.kincy-state-rail-tabs { display: flex; flex-direction: column; gap: 6px; }
.kincy-state-rail-tab {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  box-sizing: border-box;
  min-height: 44px;
  margin: 0;
  padding: 10px 14px;
  border: 1px solid transparent;
  border-radius: var(--kincy-radius-sm) 0 0 var(--kincy-radius-sm);
  background-color: var(--kincy-state-tab-bg);
  color: var(--kincy-slate);
  font-family: inherit;
  font-size: 14px; /* 13 / 17 px (D3), puis 14 / 20 px (maquette A, EC-A88) */
  font-weight: 400;
  line-height: 20px;
  text-align: left;
  cursor: pointer;
}
/* EC-A88 (maquette « A · Compteur ») : l'intercalaire d'un CONTRAT a deux colonnes, le type
   et le nom à gauche, le crédit récurrent en grand à droite. 76 px au moins ; le rembourrage
   de 10 / 11 px plus le filet transparent de 1 px rend les 11 / 12 px de la maquette, et
   rien ne bouge d'un pixel quand l'intercalaire devient actif. */
.kincy-state-rail-tab--contract {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 0;
  min-height: 76px;
  padding: 10px 11px;
}
.kincy-state-rail-tab[aria-selected="false"]:hover { background-color: var(--kincy-state-tab-hover); }
/* L'actif : fond blanc raccordé à la feuille, sans liseré (EC-A45), graisse 700. */
.kincy-state-rail-tab[aria-selected="true"] {
  margin-right: -1px;
  border-color: var(--kincy-state-sheet-border);
  border-right: 0;
  background-color: var(--bs-body-bg);
  font-weight: 700;
}
.kincy-state-rail-tab:focus-visible { outline: 2px solid var(--kincy-blue); outline-offset: -3px; }
/* La colonne de gauche : la pastille du type, puis le nom, puis la fin d'un échu. */
.kincy-state-rail-text {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  align-items: flex-start;
  gap: 7px;
  min-width: 0;
}
.kincy-state-rail-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 6px;
}
/* EC-A88 / EC-A89 : la pastille du TYPE, la même sur tous les contrats (forfait et échu
   compris), au cyan de la charte, texte blanc. ⚠️ ÉCART ASSUMÉ (EC-A89, décision de
   Rodolphe du 2026-10-12) : blanc sur `--kincy-cyan` rend 2,67:1, sous les 4,5:1 d'un texte
   de 11 px (WCAG 1.4.3). Le blanc sur `--kincy-blue` (4,80:1) n'en est PAS un précédent.
   Borné à CE SEUL sélecteur par `tests/test_state_tokens.py` ; `docs/conventions/ui.md` §3. */
.kincy-state-rail-chip {
  display: inline-block;
  padding: 2px 7px;
  border-radius: var(--kincy-radius-sm);
  background-color: var(--kincy-cyan);
  color: var(--bs-body-bg);
  font-size: 11px;
  font-weight: 700;
  line-height: 15px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* « · échu », « · n° 4021 » (EC-A80, EC-A81) : à la suite de la pastille, jamais du nom.
   La maquette A ne les dessine pas ; l'encre et la casse de l'ancien abrégé (D3). */
.kincy-state-rail-marks {
  color: var(--kincy-neutral-dark);
  font-size: 10px;
  font-weight: 500;
  line-height: 13px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.kincy-state-rail-name {
  display: -webkit-box;
  max-height: 40px;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}
/* Le nom d'un contrat, en petit : 11,5 / 15 px, 3 lignes au plus, 600 quand il est actif. */
.kincy-state-rail-tab--contract .kincy-state-rail-name {
  max-height: 45px;
  font-size: 11.5px;
  font-weight: 400;
  line-height: 15px;
  -webkit-line-clamp: 3;
}
.kincy-state-rail-tab--contract[aria-selected="true"] .kincy-state-rail-name { font-weight: 600; }
/* EC-A88 : le crédit récurrent EN GRAND, à droite (le chiffre de `rail_credit`, jamais de
   « + »), son unité en petit dessous. Le chiffre est un texte LARGE (34 px, 700) :
   `--kincy-green-ink` y tient 3:1 sur les trois fonds (4,59 / 3,80 / 3,22:1) ; l'unité,
   texte courant de 10 px, prend `--kincy-state-credit-ink` (8,04 / 6,66 / 5,63:1). */
.kincy-state-rail-credit {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  align-items: flex-end;
  padding-left: 8px;
}
.kincy-state-rail-credit-value {
  color: var(--kincy-green-ink);
  font-size: 34px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: -0.03em;
}
.kincy-state-rail-credit-unit {
  margin-top: 5px;
  color: var(--kincy-state-credit-ink);
  font-size: 10px;
  font-weight: 600;
  line-height: 12px;
  white-space: nowrap;
}
.kincy-state-rail-end {
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  font-weight: 400;
  line-height: 15px;
}

/* L'interrupteur « Échus » : `.kincy-filter-switch form-switch` RÉEMPLOYÉ, et ses
   couleurs sont celles du composant partagé (corrigé plus haut, EC-A52). Ce qui est
   propre à D3 reste ici, et seulement cela : la taille de la piste (28 × 16 px,
   contour de 1,5 px), le libellé (13 px, 500), la cible de 44 px, le survol. */
.kincy-state-switch-wrap { display: flex; align-items: center; min-height: 44px; }
.kincy-filter-switch.kincy-state-switch {
  gap: 8px;
  min-height: 30px;
  margin-left: 0;
  padding: 4px 12px 4px 8px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 500;
  line-height: 18px;
}
.kincy-state-switch:not(:has(.form-check-input:disabled)):hover {
  background-color: var(--kincy-state-chrome);
}
.kincy-state-switch:has(.form-check-input:disabled) {
  color: var(--kincy-state-muted-ink);
  cursor: default;
}
.kincy-state-switch .form-check-input { width: 28px; height: 16px; border-width: 1.5px; }

.kincy-state-sheet { flex: 999 1 480px; }
/* D3 : un filet sépare l'en-tête de la feuille de l'intercalaire ouvert. */
.kincy-state-body {
  padding-top: 22px;
  border-top: 1px solid var(--kincy-neutral-light);
}
.kincy-state-view:focus-visible { outline: 2px solid var(--kincy-blue); outline-offset: 2px; }
.kincy-state-section { display: flex; flex-direction: column; gap: 12px; }
.kincy-state-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 0;
  color: var(--kincy-neutral-dark);
  font-size: 12px;
  line-height: 18px;
}
.kincy-state-note svg { flex: 0 0 14px; margin-top: 2px; }

/* --------------------------------------------------- Le tableau etat-synthese */
.kincy-state-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--kincy-neutral-light);
  border-radius: var(--kincy-radius-md);
  background-color: var(--bs-body-bg);
}
.kincy-state-table-wrap:focus-visible { outline: 2px solid var(--kincy-blue); outline-offset: 2px; }
/* ⚠️ `separate` et non `collapse` : une cellule collante d'un tableau à bordures
   fusionnées laisse ses filets derrière elle au défilement. */
.kincy-state-table {
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 13px;
  line-height: 18px;
}
.kincy-state-col-period,
.kincy-state-col-prepaid { width: 100px; }
.kincy-state-col-outcome { width: 72px; }
.kincy-state-group {
  padding: 7px 10px;
  border-bottom: 1px solid var(--kincy-neutral-light);
  box-shadow: inset 1px 0 0 var(--kincy-state-sheet-border);
  color: var(--kincy-slate);
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
  letter-spacing: 0.05em;
  text-align: left;
  text-transform: uppercase;
}
/* Deux teintes NEUTRES de groupe, jamais une couleur par groupe (ui.md §4.7). */
.kincy-state-group--prepaid { background-color: var(--kincy-state-chrome); }
.kincy-state-group--outcome { background-color: var(--kincy-state-tab-bg); }
.kincy-state-colhead {
  vertical-align: bottom;
  border-bottom: 1px solid var(--kincy-neutral-light);
  background-color: var(--kincy-state-head-bg);
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  font-weight: 500;
  line-height: 15px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.kincy-state-colhead--prepaid { padding: 8px 10px; text-align: right; }
.kincy-state-colhead--outcome { padding: 8px 6px; text-align: center; }
.kincy-state-colhead > span { display: block; }
.kincy-state-colhead > .kincy-state-colhead-abbr { font-size: 10px; line-height: 14px; }
.kincy-state-colhead > .kincy-state-colhead-name {
  display: -webkit-box;
  max-height: 32px;
  overflow: hidden;
  color: var(--kincy-slate);
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0;
  text-transform: none;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}
.kincy-state-colhead > .kincy-state-colhead-code { color: var(--kincy-slate); font-weight: 700; }
.kincy-state-colhead > .kincy-state-chip {
  width: fit-content;
  margin: 2px 0 0 auto;
  padding: 0 5px;
  border: 1px solid var(--kincy-state-chip-border);
  border-radius: var(--kincy-radius-sm);
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  letter-spacing: 0;
  text-transform: none;
}
/* L'alerte d'écart : pictogramme et encre slate, aucune couleur de signal (EC-A30). */
.kincy-state-colhead > .kincy-state-gap {
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 4px;
  margin-top: 3px;
  color: var(--kincy-slate);
  font-size: 11px;
  line-height: 14px;
  letter-spacing: 0;
  text-transform: none;
}
.kincy-state-gap svg { flex: 0 0 12px; margin-top: 1px; }
/* ⚠️ La colonne Période est COLLANTE et OPAQUE dans les deux teintes de l'alternance
   (EC-A28) : sans fond, les chiffres défilent au travers. Filet seul, sans ombre. */
.kincy-state-period {
  position: sticky;
  left: 0;
  z-index: 1;
  padding: 9px 12px;
  box-shadow: inset -1px 0 0 var(--kincy-state-sheet-border);
  background-color: var(--kincy-state-head-bg);
  color: var(--kincy-slate);
  font-weight: 500;
  text-align: left;
  white-space: nowrap;
}
.kincy-state-period--head {
  z-index: 2;
  vertical-align: bottom;
  padding: 8px 12px;
  border-bottom: 1px solid var(--kincy-neutral-light);
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
/* Alternance sur les `tr` (EC-A29) ; l'en-tête de ligne alterne dans SA teinte. */
.kincy-state-table tbody tr:nth-child(even) > td { background-color: var(--kincy-state-row-alt); }
.kincy-state-table tbody tr:nth-child(even) > .kincy-state-period {
  background-color: var(--kincy-neutral-light);
}
.kincy-state-table tbody tr:not(:last-child) > * {
  border-bottom: 1px solid var(--kincy-state-chrome);
}
.kincy-state-row--as-of > .kincy-state-period { font-weight: 600; }
.kincy-state-row--as-of > .kincy-state-period > span { display: block; width: fit-content; }
.kincy-state-asof {
  margin-top: 4px;
  padding: 0 6px;
  border-radius: var(--kincy-radius-sm);
  background-color: var(--kincy-neutral-light);
  color: var(--kincy-slate);
  font-size: 11px;
  font-weight: 500;
  line-height: 16px;
}
.kincy-state-num,
.kincy-state-count {
  color: var(--kincy-slate);
  white-space: nowrap;
}
.kincy-state-num { padding: 9px 10px; text-align: right; }
.kincy-state-count { padding: 9px 6px; text-align: center; }
.kincy-state-first { box-shadow: inset 1px 0 0 var(--kincy-state-sheet-border); }
/* La ligne « à date » en graisse 600, sauf ses zéros (D3). */
.kincy-state-row--as-of > td:not(.kincy-state-num--zero) { font-weight: 600; }
/* Les encres : un négatif à l'orange assombri (EC-A42), un zéro atténué (EC-A26).
   Préfixées par la page pour l'emporter sur l'encre de base des `strong`. */
.kincy-state-section .kincy-state-num--negative { color: var(--kincy-state-negative-ink); }
.kincy-state-section .kincy-state-num--zero { color: var(--kincy-state-muted-ink); }

/* ------------------------------------------------- L'onglet d'un contrat (etat-contrat) */
.kincy-state-contract-head { display: flex; flex-direction: column; gap: 4px; }
.kincy-state-abbr {
  padding: 1px 6px;
  border-radius: var(--kincy-radius-sm);
  background-color: var(--kincy-state-chrome);
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  font-weight: 500;
  line-height: 16px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.kincy-state-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 18px;
  margin: 0;
  color: var(--kincy-neutral-dark);
  font-size: 13px;
}
.kincy-state-facts strong { color: var(--kincy-slate); font-weight: 500; }
.kincy-state-facts .kincy-state-strong { font-weight: 700; }
.kincy-state-alert {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 14px;
  border: 1px solid var(--kincy-state-sheet-border);
  border-radius: var(--kincy-radius-md);
  background-color: var(--kincy-state-head-bg);
  color: var(--kincy-slate);
  font-size: 13px;
  line-height: 19px;
}
.kincy-state-alert svg { flex: 0 0 16px; margin-top: 1px; }
.kincy-state-alert-text { display: flex; flex-wrap: wrap; gap: 2px 8px; }
.kincy-state-alert-text > span { color: var(--kincy-neutral-dark); }
.kincy-state-chart-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 18px;
  margin-top: 6px;
}
/* La légende réemploie `.kincy-sla-legend` et `.kincy-sla-swatch` ; seules les
   couleurs d'EC-A53 sont d'ici. */
.kincy-state-swatch--credit {
  box-sizing: border-box;
  border: 1px solid var(--kincy-blue);
  background-color: var(--kincy-cyan);
}
.kincy-state-swatch--consumed { background-color: var(--kincy-blue); }
.kincy-state-swatch--current { box-sizing: border-box; border: 1.5px dashed var(--kincy-slate); }
.kincy-state-swatch-line {
  position: relative;
  display: inline-block;
  width: 18px;
  height: 2px;
  background-color: var(--kincy-magenta);
}
.kincy-state-swatch-dot {
  position: absolute;
  top: -3px;
  left: 5px;
  box-sizing: border-box;
  width: 8px;
  height: 8px;
  border: 2px solid var(--kincy-magenta);
  border-radius: 50%;
  background-color: var(--bs-body-bg);
}
.kincy-state-swatch-negative {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--kincy-state-negative-ink);
}
/* La ligne de lecture au clic sur un mois (EC-A44). */
.kincy-state-readout { min-height: 22px; font-size: 13px; line-height: 20px; }
.kincy-state-readout-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 16px;
  /* EC-A85 : le même fond que la colonne du mois lu (faute d'infobulle, il montre ce qu'on
     regarde), sans filet. Le rembourrage est compensé par une marge négative de même
     valeur : le texte garde sa place, à gauche et en hauteur (22 px = 20 + 2 x 1). */
  box-sizing: border-box;
  min-height: 22px;
  margin: 0 -8px;
  padding: 1px 8px;
  border-radius: 4px;
  background-color: var(--kincy-state-month-fill);
}
.kincy-state-readout-line[hidden] { display: none; }
.kincy-state-readout-item { display: inline-flex; gap: 6px; }
.kincy-state-readout-key { color: var(--kincy-neutral-dark); }
.kincy-state-readout-value { color: var(--kincy-slate); font-weight: 500; }
.kincy-state-readout-value.kincy-state-strong { font-weight: 700; }
.kincy-state-readout-note { color: var(--kincy-neutral-dark); font-size: 12px; font-style: italic; }
/* Le graphe : 260 px (D3 `PH`), un seul axe en heures (EC-A44). Les positions
   viennent de `contracts_state_page.contract_chart_view`. */
.kincy-state-chart-row { display: flex; gap: 10px; padding-top: 14px; }
.kincy-state-axis {
  position: relative;
  flex: 0 0 34px;
  height: 260px;
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  line-height: 14px;
  text-align: right;
}
.kincy-state-axis > span { position: absolute; right: 0; white-space: nowrap; }
.kincy-state-axis > .kincy-state-axis-zero { color: var(--kincy-slate); font-weight: 500; }
.kincy-state-plot { position: relative; flex: 1 1 auto; min-width: 0; height: 260px; }
.kincy-state-zero {
  position: absolute;
  right: 0;
  left: 0;
  height: 1px;
  background-color: var(--kincy-slate);
}
.kincy-state-cols { position: absolute; top: 0; right: 0; left: 0; display: flex; height: 260px; }
.kincy-state-col {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  height: 260px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background-color: transparent;
  font: inherit;
  cursor: pointer;
}
/* Le mois lu : un fond bleu pâle, pas un cadre (D3, comme la colonne choisie du SLA). */
.kincy-state-col[aria-pressed="true"] {
  background-color: var(--kincy-state-month-fill);
}
.kincy-state-col:hover .kincy-state-pair { filter: brightness(0.94); }
.kincy-state-col:focus-visible { outline: 2px solid var(--kincy-blue); outline-offset: -2px; }
.kincy-state-pair {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: min(64%, 36px);
  transform: translateX(-50%);
}
.kincy-state-pair--single { width: min(44%, 24px); }
.kincy-state-bar {
  position: absolute;
  box-sizing: border-box;
  width: calc(50% - 1px);
  border-radius: 3px 3px 1px 1px;
}
/* Un mois de crédit net négatif dessine sa barre SOUS l'axe (EC-A21). */
.kincy-state-bar--below { border-radius: 1px 1px 3px 3px; }
/* ⚠️ Le CONTOUR n'est pas décoratif : le cyan ne tient que 2,67:1 sur blanc (WCAG
   1.4.11), le contour bleu 4,80:1 porte le bord (parade de `.kincy-profit-bars`). */
.kincy-state-bar--credit {
  left: 0;
  border: 1px solid var(--kincy-blue);
  background-color: var(--kincy-cyan);
}
.kincy-state-bar--consumed { right: 0; background-color: var(--kincy-blue); }
.kincy-state-pair--single > .kincy-state-bar--consumed { width: 100%; }
/* Le mois en cours : estompé et cerné d'un pointillé (D3, patron du SLA). */
.kincy-state-col--current .kincy-state-bar--consumed {
  outline: 1.5px dashed var(--kincy-slate);
  outline-offset: 1px;
  opacity: 0.55;
}
.kincy-state-curve {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  width: 100%;
  height: 260px;
  overflow: hidden;
  pointer-events: none;
}
/* Le solde en Magenta (EC-A53) sur le halo blanc de `.kincy-sla-response-halo`. */
.kincy-state-balance-line {
  fill: none;
  stroke: var(--kincy-magenta);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.kincy-state-dots { position: absolute; top: 0; right: 0; left: 0; height: 260px; pointer-events: none; }
.kincy-state-dot {
  position: absolute;
  box-sizing: border-box;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border: 2px solid var(--kincy-magenta);
  border-radius: 50%;
  background-color: var(--bs-body-bg);
}
.kincy-state-dot--last { width: 10px; height: 10px; margin: -5px 0 0 -5px; }
.kincy-state-dot--negative {
  border-color: var(--kincy-state-negative-ink);
  background-color: var(--kincy-state-negative-ink);
}
.kincy-state-end {
  position: absolute;
  padding: 1px 6px;
  border-radius: var(--kincy-radius-sm);
  background-color: var(--bs-body-bg);
  color: var(--kincy-slate);
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}
/* L'axe des mois : sous le graphe, calé sur ses colonnes (34 px d'axe + 10 de gouttière). */
.kincy-state-xaxis { display: flex; margin-left: 44px; }
.kincy-state-xlabel {
  flex: 1 1 0;
  min-width: 0;
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  font-weight: 400;
  line-height: 15px;
  text-align: center;
  white-space: nowrap;
}
.kincy-state-xlabel--selected { color: var(--kincy-slate); font-weight: 700; }
.kincy-state-xsub { color: var(--kincy-neutral-dark); font-size: 10px; font-weight: 400; }
.kincy-state-xsub--note { font-style: italic; }

/* ------------------------------------------- La tuile sla-kpi : trois colonnes */
.kincy-sla-kpi {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 24px 56px;
  padding-bottom: 28px;
  border-bottom: 1px solid var(--kincy-neutral-light);
}
.kincy-sla-kpi p { margin: 0; }
.kincy-sla-kpi-main { display: flex; flex-direction: column; gap: 6px; min-width: 260px; }
.kincy-sla-label-line { display: flex; align-items: center; gap: 8px; }
.kincy-sla-label {
  color: var(--kincy-neutral-dark);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.kincy-sla-kpi-value { display: flex; align-items: baseline; gap: 10px; }
.kincy-sla-kpi-number {
  color: var(--kincy-slate);
  font-size: 80px;
  font-weight: 700;
  line-height: 76px;
  letter-spacing: -0.03em;
}
.kincy-sla-kpi-of {
  color: var(--kincy-neutral-dark);
  font-size: 30px;
  font-weight: 500;
  line-height: 32px;
}
.kincy-sla-kpi-since { font-size: 14px; }
.kincy-sla-kpi-note { color: var(--kincy-neutral-dark); font-size: 12px; }
.kincy-sla-kpi-strip {
  display: flex;
  flex: 1 1 360px;
  flex-direction: column;
  justify-content: flex-end;
  gap: 10px;
  min-width: 0;
}
.kincy-sla-strip { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px 18px; }
.kincy-sla-strip-year { display: flex; flex-direction: column; gap: 6px; }
.kincy-sla-strip-cells { display: flex; gap: 4px; }
.kincy-sla-strip-year-label { color: var(--kincy-neutral-dark); font-size: 11px; }
/* Un carré par mois : la couleur ET le libellé de l'infobulle disent l'état. */
.kincy-sla-sq { display: block; width: 16px; height: 16px; border-radius: 3px; }
.kincy-sla-sq--compliant { background-color: var(--kincy-sla-in); }
.kincy-sla-sq--breached { background-color: var(--kincy-red); }
/* Le mois en cours : pointillé, non coloré (hors de Y, CS-A33). */
.kincy-sla-sq--current {
  border: 1.5px dashed var(--kincy-slate);
  background-color: var(--bs-body-bg);
}
.kincy-sla-strip-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  color: var(--kincy-neutral-dark);
  font-size: 12px;
}
.kincy-sla-strip-legend > span { display: inline-flex; align-items: center; gap: 6px; }
.kincy-sla-key { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
.kincy-sla-key--compliant { background-color: var(--kincy-sla-in); }
.kincy-sla-key--breached { background-color: var(--kincy-red); }
.kincy-sla-key--current { border: 1.5px dashed var(--kincy-slate); }
.kincy-sla-kpi-rule {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 6px;
  max-width: 280px;
}
.kincy-sla-rule-text { font-size: 15px; line-height: 22px; }
.kincy-sla-rule-text strong { font-size: 22px; }
.kincy-sla-rule-link { font-size: 12px; }

/* ---------------------------------------------------------- Légende du graphe */
.kincy-sla-chart { display: flex; flex-direction: column; gap: 10px; }
.kincy-sla-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 18px;
  min-height: 26px;
  margin: 0;
  padding: 0;
  color: var(--kincy-slate);
  font-size: 12px;
  list-style: none;
}
.kincy-sla-legend > li { display: inline-flex; align-items: center; gap: 6px; }
.kincy-sla-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 3px; }
.kincy-sla-swatch--in { background-color: var(--kincy-sla-in); }
.kincy-sla-swatch--within,
.kincy-sla-swatch--year { background-color: var(--kincy-orange); }
.kincy-sla-swatch--breached { background-color: var(--kincy-red); }
.kincy-sla-legend-note { color: var(--kincy-neutral-dark); font-style: italic; }
.kincy-sla-legend-muted { color: var(--kincy-neutral-dark); }
.kincy-sla-swatch-line {
  position: relative;
  display: inline-block;
  width: 18px;
  height: 2px;
  background-color: var(--kincy-sla-muted);
}
.kincy-sla-swatch-dot {
  position: absolute;
  top: -2px;
  left: 6px;
  box-sizing: content-box;
  width: 6px;
  height: 6px;
  border: 1.5px solid var(--kincy-sla-muted);
  border-radius: 50%;
  background-color: var(--bs-body-bg);
}
.kincy-sla-scope { margin-left: auto; color: var(--kincy-neutral-dark); font-style: italic; }

/* ---------------------------------------------------------------- Le graphe */
/* 276 px : 240 px de barres (CH) sous 36 px pour les taux (TOP), comme D1. Les
   positions des graduations et des points viennent de `sla_page.chart_view`. */
.kincy-sla-chart-row { display: flex; gap: 10px; }
.kincy-sla-axis {
  position: relative;
  height: 276px;
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  line-height: 14px;
}
.kincy-sla-axis > span { position: absolute; white-space: nowrap; }
.kincy-sla-axis--left { flex: 0 0 30px; text-align: right; }
.kincy-sla-axis--left > span { right: 0; }
.kincy-sla-axis--right { flex: 0 0 40px; }
.kincy-sla-axis--right > span { left: 0; }
.kincy-sla-plot { position: relative; flex: 1 1 auto; min-width: 0; height: 276px; }
.kincy-sla-grid {
  position: absolute;
  right: 0;
  left: 0;
  height: 1px;
  background-color: var(--kincy-sla-chrome);
}
/* Avant la date contractuelle : une bande discrète, et des barres estompées
   (CS-A6, CS-A59). Les hachures ne signifient que cela (CS-A12). */
.kincy-sla-band {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  overflow: hidden;
  border-right: 1.5px dashed var(--kincy-sla-muted);
  background-image: repeating-linear-gradient(
    135deg,
    color-mix(in srgb, var(--kincy-slate) 10%, transparent) 0 1px,
    transparent 1px 7px
  );
  pointer-events: none;
}
/* *Ajouté le 2026-10-12 (lot 3 de l'État contractuel)* : la même bande, à DROITE,
   pour « Après le contrat » d'un échu (D3, `EC-A49`). Le graphe d'un contrat de la
   même page RÉEMPLOIE `.kincy-sla-band` (mêmes hachures, même filet dans D1 et D3) ;
   ce modificateur vit ici parce que seul un sélecteur `.kincy-sla…` lit
   `--kincy-sla-muted` (`tests/test_sla_tokens.py`). */
.kincy-sla-band--after {
  right: 0;
  left: auto;
  border-right: 0;
  border-left: 1.5px dashed var(--kincy-sla-muted);
}
/* Coupée à la largeur de la bande : sur une bande étroite, elle chevaucherait le
   repère « Début du SLA contractuel » posé juste après. */
.kincy-sla-band-label {
  position: absolute;
  top: 0;
  left: 8px;
  max-width: calc(100% - 12px);
  overflow: hidden;
  padding: 0 4px;
  background-color: var(--bs-body-bg);
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  font-weight: 500;
  line-height: 14px;
  letter-spacing: 0.06em;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}
.kincy-sla-band-mark {
  position: absolute;
  top: 0;
  margin-left: 8px;
  padding: 0 4px;
  background-color: var(--bs-body-bg);
  color: var(--kincy-slate);
  font-size: 11px;
  font-weight: 500;
  line-height: 14px;
  white-space: nowrap;
  pointer-events: none;
}
/* Passé 60 % de la largeur, le repère se termine à la frontière (CS-A62). */
.kincy-sla-band-mark--end { margin-left: -8px; transform: translateX(-100%); }
.kincy-sla-cols {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  align-items: flex-end;
  height: 276px;
  border-bottom: 1px solid var(--kincy-sla-muted);
}
.kincy-sla-bar {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  min-width: 0;
  height: 276px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 8px 8px 0 0;
  background-color: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.kincy-sla-bar--empty { cursor: default; }
/* La période choisie : un fond de colonne bleu pâle, pas un cadre (D1). */
.kincy-sla-bar--selected {
  background-color: color-mix(in srgb, var(--kincy-blue) 8%, transparent);
}
.kincy-sla-bar:focus-visible { outline: 2px solid var(--kincy-blue); outline-offset: -2px; }
.kincy-sla-bar:hover .kincy-sla-stack { filter: brightness(0.94); }
.kincy-sla-rate {
  margin-bottom: 5px;
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  font-weight: 500;
  line-height: 14px;
  white-space: nowrap;
}
.kincy-sla-rate--breached { color: var(--kincy-red-ink); font-weight: 700; }
.kincy-sla-stack {
  display: flex;
  flex-direction: column;
  width: 58%;
  max-width: 36px;
  overflow: hidden;
  border-radius: 5px 5px 2px 2px;
}
/* Le mois en cours : estompé, contour pointillé, sans taux (CS-A59, CS-A12). */
.kincy-sla-bar--current .kincy-sla-stack {
  outline: 1.5px dashed var(--kincy-slate);
  outline-offset: 2px;
  opacity: 0.55;
}
/* Avant contrat : plus estompé encore ; écrit APRÈS le mois en cours, qui ne
   l'emporte pas (ordre de D1). */
.kincy-sla-bar--muted .kincy-sla-stack { opacity: 0.4; }
.kincy-sla-bar--muted .kincy-sla-rate { opacity: 0.6; }
.kincy-sla-seg { display: block; flex: none; }
.kincy-sla-seg--in { background-color: var(--kincy-sla-in); }
/* Années : le hors SLA est orange, d'une seule couleur (CS-A59 révoque CS-A51). */
.kincy-sla-seg--within,
.kincy-sla-seg--year { background-color: var(--kincy-orange); }
.kincy-sla-seg--breached { background-color: var(--kincy-red); }
/* Le filet blanc entre les deux segments, AJOUTÉ à la hauteur comme dans D1. */
.kincy-sla-seg + .kincy-sla-seg--in {
  box-sizing: content-box;
  border-top: 2px solid var(--bs-body-bg);
}
/* La courbe de réponse : indicative, sans seuil, grise (CS-A28), lissée et
   doublée d'un halo blanc qui la détache des barres (D1). */
.kincy-sla-curve {
  position: absolute;
  top: 36px;
  right: 0;
  left: 0;
  width: 100%;
  height: 240px;
  overflow: visible;
  pointer-events: none;
}
.kincy-sla-response-halo,
.kincy-sla-response-line {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.kincy-sla-response-halo { stroke: var(--bs-body-bg); stroke-width: 5; }
.kincy-sla-response-line { stroke: var(--kincy-sla-muted); stroke-width: 1.75; }
.kincy-sla-dots {
  position: absolute;
  top: 36px;
  right: 0;
  left: 0;
  height: 240px;
  pointer-events: none;
}
.kincy-sla-dot {
  position: absolute;
  width: 7px;
  height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border: 1.5px solid var(--kincy-sla-muted);
  border-radius: 50%;
  background-color: var(--bs-body-bg);
}
.kincy-sla-dot--muted { opacity: 0.5; }
.kincy-sla-response-last {
  position: absolute;
  right: 0;
  padding: 1px 6px;
  border-radius: var(--kincy-radius-sm);
  background-color: var(--bs-body-bg);
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  font-weight: 500;
  line-height: 14px;
  white-space: nowrap;
}
/* L'axe des périodes : sous le graphe, calé sur ses colonnes (30 px d'axe + 10 de
   gouttière à gauche, 10 + 40 à droite). */
.kincy-sla-xaxis { display: flex; margin: 0 50px 0 40px; }
.kincy-sla-xlabel {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  font-weight: 400;
  line-height: 15px;
  text-align: center;
  white-space: nowrap;
}
.kincy-sla-xlabel--selected {
  background-color: transparent;
  color: var(--kincy-slate);
  font-weight: 700;
}
.kincy-sla-xsub { color: var(--kincy-neutral-dark); font-size: 10px; font-weight: 400; }
.kincy-sla-xsub--note { font-style: italic; }

/* ------------------------------------------------------- Le tableau sla-incidents */
.kincy-sla-incidents-block {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-top: 22px;
  border-top: 1px solid var(--kincy-neutral-light);
}
/* L'invite « Cliquez sur une barre… » est courte : la zone ne garde la hauteur
   du loader (ui.md §4.12) que pendant une requête. */
.kincy-sla-incidents-block > .kincy-run-report:has(.kincy-sla-hint):not(:has(> .htmx-request)) {
  min-height: 0;
}
.kincy-sla-incidents,
.kincy-sla-incidents-content { display: flex; flex-direction: column; gap: 14px; }
.kincy-sla-incidents-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.kincy-sla-incidents-heading { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; }
.kincy-sla-incidents-title {
  margin: 0;
  color: var(--kincy-slate);
  font-size: 17px;
  font-weight: 700;
  line-height: 24px;
}
.kincy-sla-hint {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  color: var(--kincy-neutral-dark);
  font-size: 13px;
}
.kincy-sla-badge {
  padding: 2px 10px;
  border-radius: 10px;
  background-color: var(--kincy-sla-chrome);
  color: var(--kincy-slate);
  font-size: 12px;
  font-weight: 700;
}
.kincy-sla-badge--all_in,
.kincy-sla-badge--within {
  background-color: var(--kincy-sla-in-soft);
  color: var(--kincy-sla-in-ink);
}
.kincy-sla-badge--breached {
  background-color: var(--kincy-sla-out-soft);
  color: var(--kincy-red-ink);
}
/* Vue Années : neutre, sans verdict (CS-A30). */
.kincy-sla-badge--neutral {
  background-color: var(--kincy-sla-chrome);
  color: var(--kincy-slate);
}
.kincy-sla-count { color: var(--kincy-neutral-dark); font-size: 13px; }
/* Les filtres du tableau : des pastilles, « clic = montrer » (ui.md §1). */
.kincy-sla-incident-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.kincy-sla-chip {
  padding: 4px 12px;
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: 14px;
  background-color: var(--bs-body-bg);
  color: var(--kincy-slate);
  font: inherit;
  font-size: 12px;
  font-weight: 400;
  line-height: 16px;
  cursor: pointer;
}
.kincy-sla-chip[aria-pressed="true"] {
  border-color: var(--kincy-slate);
  background-color: var(--kincy-slate);
  color: #fff;
  font-weight: 700;
}
.kincy-sla-chip--alert[aria-pressed="true"] {
  border-color: var(--kincy-red);
  background-color: var(--kincy-red);
  color: #fff;
}
.kincy-sla-chip:focus-visible,
.kincy-sla-collapse:focus-visible,
.kincy-sla-sort:focus-visible,
.kincy-sla-more:focus-visible {
  outline: 2px solid var(--kincy-blue);
  outline-offset: 2px;
}
.kincy-sla-chip-sep {
  width: 1px;
  height: 20px;
  margin: 0 4px;
  background-color: var(--kincy-neutral-light);
}
.kincy-sla-collapse {
  margin-left: 4px;
  padding: 5px 4px;
  border: 0;
  background-color: transparent;
  color: var(--kincy-blue);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
/* Le tableau, dans une boîte arrondie. ⚠️ Un `<table>` sans la classe `.table` de
   Bootstrap : aucune règle de fond n'a donc à peser plus que (0,1,1) (ui.md
   §4.8), et la teinte d'une ligne Out se pose sur ses cellules. */
.kincy-sla-table-box {
  overflow-x: auto;
  border: 1px solid var(--kincy-neutral-light);
  border-radius: var(--kincy-radius-md);
}
.kincy-sla-table {
  width: 100%;
  min-width: 900px;
  border-collapse: collapse;
  table-layout: fixed;
  color: var(--kincy-slate);
  font-size: 13px;
  line-height: 18px;
}
/* Les colonnes de D1 (76 / auto / 72 / 132 / 132 / 88 / 104 px), gouttières comprises. */
.kincy-sla-col-ticket { width: 96px; }
.kincy-sla-col-priority { width: 84px; }
.kincy-sla-col-date { width: 144px; }
.kincy-sla-col-response { width: 100px; }
.kincy-sla-col-resolution { width: 124px; }
.kincy-sla-table th,
.kincy-sla-table td {
  padding: 9px 6px;
  text-align: left;
  vertical-align: middle;
}
.kincy-sla-table th:first-child,
.kincy-sla-table td:first-child { padding-left: 14px; }
.kincy-sla-table th:last-child,
.kincy-sla-table td:last-child { padding-right: 14px; }
.kincy-sla-table thead th {
  border-bottom: 1px solid var(--kincy-neutral-light);
  background-color: color-mix(in srgb, var(--kincy-neutral-light) 35%, var(--bs-body-bg));
  color: var(--kincy-neutral-dark);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.kincy-sla-table tbody td { border-bottom: 1px solid var(--kincy-sla-chrome); }
/* Ligne hors SLA teintée. 3 % de rouge et non les 4 % de D1 : le numéro de ticket
   (lien bleu) y tient encore 4,5:1 (4,56 contre 4,49, apps/core/contrast.py). */
.kincy-sla-table .kincy-sla-row--out > td {
  background-color: color-mix(in srgb, var(--kincy-red) 3%, var(--bs-body-bg));
}
.kincy-sla-ticket { font-family: var(--bs-font-monospace, monospace); font-size: 12px; }
.kincy-sla-ticket a { color: var(--kincy-blue); text-decoration: none; }
.kincy-sla-summary { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kincy-sla-priority { font-weight: 700; }
.kincy-sla-date { color: var(--kincy-neutral-dark); white-space: nowrap; }
.kincy-sla-sort {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0;
  border: 0;
  background-color: transparent;
  color: var(--kincy-neutral-dark);
  font: inherit;
  font-weight: 500;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}
.kincy-sla-sort--active {
  background-color: transparent;
  color: var(--kincy-slate);
  font-weight: 700;
}
/* Pastilles In / Out pleines (D1) ; « — » (non déterminé) en gris neutre. */
.kincy-sla-pill {
  display: inline-block;
  min-width: 34px;
  padding: 2px 8px;
  border-radius: 10px;
  background-color: var(--kincy-sla-chrome);
  color: var(--kincy-slate);
  font-size: 12px;
  line-height: 16px;
  text-align: center;
}
.kincy-sla-pill--in {
  background-color: var(--kincy-sla-in-soft);
  color: var(--kincy-sla-in-ink);
}
.kincy-sla-pill--out {
  background-color: var(--kincy-sla-out-soft);
  color: var(--kincy-red-ink);
  font-weight: 700;
}
.kincy-sla-no-match {
  margin: 0;
  padding: 18px 14px;
  color: var(--kincy-neutral-dark);
  font-size: 13px;
}
.kincy-sla-more {
  align-self: flex-start;
  padding: 8px 14px;
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-md);
  background-color: var(--bs-body-bg);
  color: var(--kincy-slate);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

/* ------------------------------------------- La page « Réglages du client » (D2) */
.kincy-sla-settings-top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 10px;
}
.kincy-sla-settings-top .kincy-sla-page-title { margin: 0; }
.kincy-sla-settings-roles { color: var(--kincy-neutral-dark); font-size: 13px; }
.kincy-sla-settings { max-width: 760px; }
.kincy-sla-settings .kincy-sla-heading { gap: 4px; }
.kincy-sla-settings-row {
  display: grid;
  grid-template-columns: minmax(0, 220px) minmax(0, 1fr);
  gap: 8px 32px;
  align-items: start;
  min-width: 0;
  margin: 0;
  padding: 0 0 24px;
  border: 0;
  border-bottom: 1px solid var(--kincy-neutral-light);
}
.kincy-sla-settings-label {
  margin: 0;
  padding-top: 9px;
  font-size: 14px;
  font-weight: 500;
}
/* ⚠️ La légende flotte (D2) : c'est ce qui en fait un élément de la grille du
   fieldset. Bootstrap la fait aussi flotter, mais en pleine largeur et en grand. */
.kincy-sla-settings-row > legend.kincy-sla-settings-label {
  float: left;
  width: auto;
  line-height: inherit;
}
.kincy-sla-settings-row > .kincy-sla-settings-field { grid-column: 2; }
.kincy-sla-settings-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.kincy-sla-settings-inline { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.kincy-sla-input {
  min-height: 40px;
  padding: 8px 10px;
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: 6px;
  background-color: var(--bs-body-bg);
  color: var(--kincy-slate);
  font: inherit;
  font-size: 14px;
}
.kincy-sla-input--threshold { width: 88px; text-align: right; }
.kincy-sla-input:focus-visible,
.kincy-sla-settings-box:focus-visible,
.kincy-sla-settings-clear:focus-visible,
.kincy-sla-button:focus-visible {
  outline: 2px solid var(--kincy-blue);
  outline-offset: 2px;
}
.kincy-sla-settings-clear {
  padding: 6px 4px;
  border: 0;
  background-color: transparent;
  color: var(--kincy-blue);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.kincy-sla-settings-unit { font-size: 14px; }
.kincy-sla-settings-help { color: var(--kincy-neutral-dark); font-size: 12px; line-height: 18px; }
.kincy-sla-settings-error { color: var(--kincy-red-ink); font-size: 12px; }
.kincy-sla-settings-prios { display: flex; flex-wrap: wrap; gap: 8px; }
.kincy-sla-settings-prio {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  margin: 0;
  padding: 0 14px;
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-md);
  background-color: var(--bs-body-bg);
  color: var(--kincy-slate);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.kincy-sla-settings-prio:has(input:checked) {
  border-color: var(--kincy-blue);
  background-color: color-mix(in srgb, var(--kincy-blue) 8%, var(--bs-body-bg));
  color: var(--kincy-slate);
}
.kincy-sla-settings-box { width: 16px; height: 16px; margin: 0; accent-color: var(--kincy-blue); }
.kincy-sla-settings-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.kincy-sla-settings-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  color: var(--kincy-neutral-dark);
  font-size: 12px;
}
.kincy-sla-settings-actions { display: flex; gap: 10px; }
.kincy-sla-button {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  border-radius: 6px;
  font: inherit;
  font-size: 14px;
  text-decoration: none;
  cursor: pointer;
}
.kincy-sla-button--secondary {
  padding: 0 16px;
  border: 1px solid var(--kincy-neutral-medium);
  background-color: var(--bs-body-bg);
  color: var(--kincy-slate);
}
.kincy-sla-button--secondary:hover {
  border-color: var(--kincy-slate);
  background-color: var(--bs-body-bg);
  color: var(--kincy-slate);
}
/* Bleu de charte et non le cyan du `.btn-primary` de Link : D2 le veut, et le
   blanc tient 4,80:1 sur `--kincy-blue` contre 2,67:1 sur le cyan. */
.kincy-sla-button--primary {
  padding: 0 20px;
  border: 0;
  background-color: var(--kincy-blue);
  color: #fff;
  font-weight: 500;
}
.kincy-sla-button--primary:hover {
  background-color: var(--kincy-slate);
  color: #fff;
}

/* ===========================================================================
   Jauge d'humeur du bandeau d'accueil et de l'annuaire RH · 2026-10-09
   Spec : docs/home-mood-gauge-spec.md (HM-D6 à HM-D9, HM-D15, lots B et C).
   ---------------------------------------------------------------------------
   ⚠️ ÉCART ASSUMÉ À LA CHARTE (Rodolphe, 2026-10-09 : « c'est un outil
   spécifique, il n'est pas totalement aligné à la charte et c'est acté »).
   QUATRE ancres, aux niveaux 1, 3, 4 et 5 de l'échelle de Halo ; la couleur
   entre deux ancres est un `color-mix(in oklch, …)` dont la part est calculée
   côté serveur, par segment ([1,3], [3,4], [4,5]). `design.md` §3.6 exclut le
   vert, et le magenta est la teinte de BUILD : les tokens `--kincy-mood-*` sont
   CONFINÉS aux sélecteurs `.kincy-mood…`, tenus par `tests/test_mood_tokens.py`
   (ui.md §3).

   *Amendé le 2026-10-09, à la demande de Rodolphe* (« Le kaki est pas génial en
   effet, avec une valeur de 4 on devrait être vert clair, et tirer vers un vert
   plus foncé à 5 ») : l'ancre du niveau 4 est un vert CLAIR lumineux, celle du 5
   le vert de charte, « plus foncé » s'entendant RELATIVEMENT au vert du 4 (sur
   l'aplat sombre du bandeau, un vert foncé dans l'absolu tomberait sous 3:1).
   L'interpolation passe en OKLCH : en sRGB, orange vers vert traversait un kaki.

   ⚠️ CONTRASTE, MESURÉ le 2026-10-09 avec `apps/core/contrast.py` contre le
   fond RÉEL du bandeau (`--kincy-slate`, #44546A, et non du blanc) : les
   couleurs de charte ne tiennent pas 3:1 (WCAG 1.4.11, élément graphique) sur
   cet aplat sombre, magenta 1,77:1 et orange 2,92:1 (vert 3,04:1 tient). Elles
   sont donc ÉCLAIRCIES, jamais assombries (précédent des `-soft`) : magenta ->
   #F27FB8 (3,12:1), orange -> #F9A030 (3,71:1), vert clair du 4 -> #A8DC5E
   (4,80:1), le vert de charte #6BB52D du 5 reste (3,04:1). Le pire point de
   chaque segment interpolé en OKLCH (pas de 1 %) est une ancre : 3,12:1, 3,71:1
   et 3,04:1. L'annuaire RH rendait la même jauge dans une CAPSULE ardoise
   (`.kincy-mood--h`), pour que cette mesure y vaille aussi.

   *Amendé le 2026-10-12, à la demande de Rodolphe* (« le fond gris est trop
   foncé, éclaircis fortement le fond du cartouche ») : le cartouche HORIZONTAL
   passe sur `--kincy-neutral-light` (#E2EFF4). Les ancres n'y tiennent plus 3:1
   (rose 2,10:1, orange 1,77:1, vert clair 1,37:1, vert 2,16:1) : c'est donc le
   CONTOUR ardoise (`--kincy-slate`, 6,57:1 sur le fond) de la piste et de la
   partie remplie qui porte la forme (WCAG 1.4.11) ; la couleur n'est plus qu'un
   indice redondant avec la valeur écrite. La verticale du bandeau ne bouge pas.

   *Amendé le 2026-10-12, à la demande de Rodolphe* (« le filet ardoise autour de
   la jauge n'est plus aussi utile. Soit tu l'enlèves, soit tu l'éclaircis
   fortement ») : le contour passe de `--kincy-slate` à `--kincy-neutral-medium`
   (#c4c4c4, token existant), le même sur la piste et sur la partie remplie.
   Mesuré avec `apps/core/contrast.py` : 1,49:1 contre le cartouche, 1,42:1 contre
   le rose, 1,19:1 contre l'orange, 1,09:1 contre le vert clair, 1,46:1 contre le
   vert, 1,74:1 contre la piste blanche. Le contour ne porte donc PLUS la forme à
   3:1 (WCAG 1.4.11), conséquence assumée par Rodolphe : la valeur numérique
   affichée et la longueur de la barre portent l'information, la couleur reste un
   indice. Sans contour, la piste blanche disparaîtrait du cartouche (1,17:1).
   =========================================================================== */
:root {
  --kincy-mood-low: #F27FB8;
  --kincy-mood-mid: #F9A030;
  --kincy-mood-good: #A8DC5E;
  --kincy-mood-high: #6BB52D;

  /* Les DIMENSIONS, tokenisées. Verticale (bandeau) : élargie le 2026-10-09
     (« le visuel pas assez large, c'est un peu petit ») de 8 à 14 px de piste,
     d'emojis de 14 à 20 px, de valeur de 12 à 16 px, 72 à 84 px de haut. */
  --kincy-mood-height: 84px;
  --kincy-mood-track: 14px;
  --kincy-mood-emoji: 20px;
  --kincy-mood-value-size: 16px;
  /* Horizontale (annuaire RH) : la piste est la même, les emojis plus petits
     pour tenir dans une ligne de tableau. */
  --kincy-mood-width-h: 132px;
  --kincy-mood-emoji-h: 16px;
}

/* L'emplacement RÉSERVÉ (HM-D9) : hauteur fixe, pour qu'aucun décalage ne
   survienne quand le fragment arrive. La cible `#mood-gauge` colle à la jauge,
   l'invite s'étend vers la gauche. */
.kincy-mood-slot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-width: 96px;
  min-height: var(--kincy-mood-height);
}

/* Le bloc de droite du bandeau : l'emplacement, puis le sélecteur d'agent SOUS
   la jauge (HM-D2, 2026-10-09 : posé sous le nom, il laissait croire qu'il
   changeait toute la page d'accueil). */
.kincy-mood-block {
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-width: 100%;
}

/* L'aperçu « non publiée » dans l'emplacement : l'étiquette absolue de
   l'enveloppe, pensée pour une carte, recouvrirait l'isotype. Ici elle passe
   DANS le flux, à gauche de la jauge, et les bandes encadrent l'ensemble.
   Aucun token de l'enveloppe n'est employé : géométrie seule. */
.kincy-mood-slot .kincy-unreleased {
  display: flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  padding: 14px var(--kincy-spacing-sm);
}

.kincy-mood-slot .kincy-unreleased-tag {
  position: static;
  flex: 0 1 auto;
  max-width: 150px;
}

.kincy-mood-slot,
.kincy-mood-target {
  max-width: 100%;
}

.kincy-mood-target {
  min-width: 0;
}

.kincy-mood-target {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--kincy-spacing-md);
  height: var(--kincy-mood-height);
}

/* Le sélecteur d'agent : petit, sous la jauge, avec un libellé VISIBLE court
   qui dit ce qu'il règle (la jauge seule). */
.kincy-mood-picker {
  display: flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
  margin-top: var(--kincy-spacing-sm);
}

.kincy-mood-picker-label {
  flex: 0 0 auto;
  margin: 0;
  font-size: 12px;
  line-height: 16px;
  color: rgba(255, 255, 255, 0.8);
}

.kincy-mood-select {
  max-width: 14rem;
}

/* L'invite : une pastille légère, contour et petite taille. */
.kincy-mood-invite {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 1 auto;
  max-width: 240px;
  padding: 3px 10px;
  font-size: 12px;
  line-height: 16px;
  color: #fff;
  text-decoration: none;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 999px;
}

.kincy-mood-invite:hover,
.kincy-mood-invite:focus-visible {
  color: #fff;
  background-color: rgba(255, 255, 255, 0.12);
  border-color: #fff;
}

.kincy-mood-invite svg {
  flex: 0 0 auto;
}

/* La jauge VERTICALE (bandeau). `plot` est la zone utile, avec une marge d'une
   demi-emoji en haut et en bas pour que les emojis des niveaux 1 et 5, centrés
   sur les bouts de la piste, ne sortent pas du cadre. `--at` est la position de
   chaque repère sur l'axe : la verticale l'emploie en `bottom`, l'horizontale
   en `left`, ce qui évite d'écrire deux fois les repères. */
.kincy-mood {
  position: relative;
  flex: 0 0 96px;
  width: 96px;
  height: var(--kincy-mood-height);
}

.kincy-mood-plot {
  position: absolute;
  inset: calc(var(--kincy-mood-emoji) / 2) 0;
}

.kincy-mood-mark {
  position: absolute;
  left: 0;
  bottom: var(--at);
  width: var(--kincy-mood-emoji);
  font-size: var(--kincy-mood-emoji);
  line-height: 1;
  text-align: center;
  transform: translateY(50%);
}

.kincy-mood-mark--1 { --at: 0%; }
.kincy-mood-mark--3 { --at: 50%; }
.kincy-mood-mark--5 { --at: 100%; }

/* Les niveaux 2 et 4 : une graduation SANS emoji. */
.kincy-mood-tick {
  position: absolute;
  left: calc(var(--kincy-mood-emoji) / 2 - 4px);
  bottom: var(--at);
  width: 8px;
  height: 1px;
  background-color: rgba(255, 255, 255, 0.5);
}

.kincy-mood-tick--2 { --at: 25%; }
.kincy-mood-tick--4 { --at: 75%; }

.kincy-mood-track {
  position: absolute;
  left: calc(var(--kincy-mood-emoji) + 10px);
  top: 0;
  bottom: 0;
  width: var(--kincy-mood-track);
  overflow: hidden;
  background-color: rgba(255, 255, 255, 0.18);
  border-radius: 999px;
}

/* Le remplissage part du bas : hauteur = (valeur - 1) / 4, posée par le serveur.
   Plancher d'une piste de large : une valeur de 1,0 donnerait sinon une piste
   vide, que rien ne distinguerait de l'état dormant.
   La COULEUR : `color-mix` en OKLCH entre deux ancres, la part de l'ancre
   d'arrivée (`--gauge-share`, 0 à 100 %) étant calculée par le serveur ; la
   classe `--seg-N` du conteneur choisit le segment ([1,3], [3,4], [4,5]). */
.kincy-mood-fill {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  min-height: var(--kincy-mood-track);
  height: var(--gauge-fill, 0%);
  border-radius: 999px;
  background-color: color-mix(in oklch, var(--kincy-mood-mid) var(--gauge-share, 0%), var(--kincy-mood-low));
}

.kincy-mood--seg-2 .kincy-mood-fill {
  background-color: color-mix(in oklch, var(--kincy-mood-good) var(--gauge-share, 0%), var(--kincy-mood-mid));
}

.kincy-mood--seg-3 .kincy-mood-fill {
  background-color: color-mix(in oklch, var(--kincy-mood-high) var(--gauge-share, 0%), var(--kincy-mood-good));
}

/* La valeur, à hauteur du sommet de la barre (bornée à la zone). */
.kincy-mood-value {
  position: absolute;
  left: calc(var(--kincy-mood-emoji) + 10px + var(--kincy-mood-track) + 8px);
  bottom: clamp(0px, calc(var(--gauge-fill, 0%) - 9px), calc(100% - 18px));
  font-size: var(--kincy-mood-value-size);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  line-height: 18px;
  color: #fff;
}

/* La jauge HORIZONTALE (annuaire RH, HM-D15) : MÊME balisage, MÊME logique de
   présentation, seule l'orientation change. Les emojis passent en abscisse
   (gauche = pire), SOUS la piste, et la valeur à droite. Rendue dans un
   CARTOUCHE clair (2026-10-12, était une capsule ardoise) : `--kincy-neutral-light`,
   encre ardoise (6,57:1), piste et remplissage cernés d'un filet clair
   `--kincy-neutral-medium` (2026-10-12, était ardoise : voir l'amendement du
   bloc des ancres, le filet ne porte plus 3:1). */
.kincy-mood--h {
  display: inline-block;
  flex: 0 0 auto;
  width: auto;
  height: auto;
  padding: 4px 44px 3px calc(var(--kincy-mood-emoji-h) / 2 + 10px);
  background-color: var(--kincy-neutral-light);
  border-radius: 999px;
  vertical-align: middle;
}

.kincy-mood--h .kincy-mood-tick {
  background-color: var(--kincy-slate);
}

/* La piste VIDE et la partie REMPLIE sont cernées d'un filet clair (2026-10-12,
   était ardoise 6,57:1) : il délimite la forme sans la porter à 3:1. */
.kincy-mood--h .kincy-mood-track {
  background-color: #fff;
  box-shadow: inset 0 0 0 1px var(--kincy-neutral-medium);
}

.kincy-mood--h .kincy-mood-fill {
  box-shadow: inset 0 0 0 1px var(--kincy-neutral-medium);
}

.kincy-mood--h .kincy-mood-plot {
  position: relative;
  inset: auto;
  width: var(--kincy-mood-width-h);
  height: calc(var(--kincy-mood-track) + 2px + var(--kincy-mood-emoji-h));
}

.kincy-mood--h .kincy-mood-mark {
  top: calc(var(--kincy-mood-track) + 2px);
  bottom: auto;
  left: var(--at);
  width: auto;
  font-size: var(--kincy-mood-emoji-h);
  transform: translateX(-50%);
}

.kincy-mood--h .kincy-mood-tick {
  top: calc(var(--kincy-mood-track) + 2px);
  bottom: auto;
  left: var(--at);
  width: 1px;
  height: 5px;
}

.kincy-mood--h .kincy-mood-track {
  left: 0;
  right: 0;
  bottom: auto;
  width: auto;
  height: var(--kincy-mood-track);
}

.kincy-mood--h .kincy-mood-fill {
  top: 0;
  right: auto;
  bottom: 0;
  height: auto;
  min-height: 0;
  min-width: var(--kincy-mood-track);
  width: var(--gauge-fill, 0%);
}

.kincy-mood--h .kincy-mood-value {
  left: calc(100% + 10px);
  bottom: auto;
  top: calc(var(--kincy-mood-track) / 2 - 9px);
  color: var(--kincy-slate);
}

/* Dormante (rien en 20 jours) et « Choisissez un agent » : piste vide, emojis
   en niveaux de gris atténués, aucune valeur. */
.kincy-mood--dormant .kincy-mood-mark,
.kincy-mood--choose .kincy-mood-mark {
  opacity: 0.45;
  filter: grayscale(1);
}

/* ---------------------------------------------------------------------------
   Teach (admin) : écran « Teach (admin) » (lot L2, apps/teach), RETIRÉ le 2026-10-21 (lot L-M2,
   sous-lot C) ; les tuiles ci-dessous sont RÉEMPLOYÉES par RH > Teach et la page Parcours.
   UN bloc contigu, en fin de feuille : les tuiles de tête, sur la grammaire de
   `docs/conventions/ui.md` §4.11 (fond blanc, bordure fine, filet gauche, libellé
   en petites capitales, chiffre détaché). Le filet reste NEUTRE : aucun axe de
   couleur ici, donc aucune teinte inventée, et le chiffre garde l'encre courante.
   Le dépliage d'un district n'a pas de style propre (Bootstrap seul).
   --------------------------------------------------------------------------- */
.kincy-teach-tiles { display: flex; flex-wrap: wrap; gap: var(--kincy-spacing-md); }
.kincy-teach-tile {
  min-width: 12rem;
  padding: var(--kincy-spacing-md);
  border: 1px solid var(--kincy-neutral-medium);
  border-left: 5px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-md);
  background-color: var(--bs-body-bg);
  text-align: center;
}
.kincy-teach-tile-label {
  margin-bottom: var(--kincy-spacing-xs);
  color: var(--kincy-neutral-dark);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.kincy-teach-tile-value {
  font-size: 2.4rem;
  font-weight: 700;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
   Accueil : rangée d'outils, et reprise Teach (Q15).
   UN bloc contigu, en fin de feuille, tokens seulement, aucune couleur neuve.
   1. La rangée d'outils de l'accueil (`core/_home_tools.html`, apps/core/DECISIONS.md
      §13) : tuiles carrées de 96 px, logo inscrit dans 48 px, élévation `raised`.
   2. (Retiré le 2026-10-16, lot D Teach.) L'interrupteur maison `.kincy-teach-switch` de
      l'annuaire RH est remplacé par le `.form-switch` de Bootstrap, sur la ligne de chaque
      membre de RH > Départements (`docs/conventions/ui.md` §4.15) ; l'annuaire ne montre
      plus qu'un état en lecture seule, sans style propre.
   3. (Retiré le 2026-10-21, lot L-M2 sous-lot C.) La règle `.kincy-teach-detail`, qui ne servait
      qu'au dépliage d'un district de l'écran « Teach (admin) », est partie avec l'écran
      (`apps/teach/DECISIONS.md` §13).
   --------------------------------------------------------------------------- */
.kincy-home-tools {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--kincy-spacing-md);
  margin-top: var(--kincy-spacing-lg);
}
.kincy-home-tool {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--kincy-spacing-sm);
  width: 96px;
  height: 96px;
  padding: var(--kincy-spacing-sm) var(--kincy-spacing-xs);
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-md);
  background-color: var(--bs-body-bg);
  color: var(--kincy-slate);
  text-decoration: none;
  box-shadow: var(--kincy-elevation-raised);
  transition: border-color 0.12s ease, box-shadow 0.12s ease, background-color 0.12s ease;
}
.kincy-home-tool:hover {
  border-color: var(--kincy-cyan);
  background-color: var(--kincy-neutral-light);
  color: var(--kincy-slate);
  box-shadow: var(--kincy-elevation-overlay);
}
.kincy-home-tool:focus-visible {
  outline: 2px solid var(--kincy-cyan);
  outline-offset: 2px;
}
.kincy-home-tool-logo,
.kincy-home-tool-icon {
  display: block;
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  object-fit: contain;
}
.kincy-home-tool-label {
  max-width: 100%;
  font-size: 0.7rem;
  font-weight: 500;
  line-height: 1.2;
  text-align: center;
}

/* Annuaire RH, colonne « Département » (lot D Teach, 2026-10-16) : le département
   DÉDUIT de l'affectation se lit en pastille évidée (fond transparent, contour
   pointillé, italique), le département EXPLICITE (posé dans RH > Départements) en
   pastille pleine. Aucune couleur neuve, et le contraste du texte n'est pas touché
   (pas d'opacité) ; le texte pour lecteur d'écran porte la distinction. */
.kincy-department-deduced .kincy-agency-pill {
  border: 1px dashed var(--kincy-neutral-medium);
  border-left: 3px solid var(--kincy-agency-ink);
  background: transparent;
  font-style: italic;
}

/* --- Portrait du client : la carte de la fiche et sa page détails -----------
 * Spec : `docs/client-teach-card-spec.md` (Lot 2, 2026-10-29). Maquette : canvas
 * Claude Design v5. AUCUNE couleur neuve (`CK-D25`) : tokens existants, et le
 * jaune de l'échelle `chaleur` DÉRIVÉ par `color-mix`, comme le dégradé de taux
 * de Squads (`apps/squads/DECISIONS.md` A3 / C12), jamais `--kincy-star`, réservé
 * à l'étoile de favori (`docs/conventions/ui.md` §3). Tenu par
 * `apps/clients/tests/test_portrait.py`. */

/* Carte : KPI droit N >= 1. Fond cyan des tuiles d'outil RÉEMPLOYÉ tel quel, mais
 * le chiffre passe en `--kincy-blue` (`CK-D11` amendée, validé par Rodolphe) :
 * 4,36:1 sur l'aplat #e6f7fa, contre 2,42:1 pour le cyan. Règle propre à la carte :
 * `.kincy-tool-tile--filled` sert aussi la boîte à outils et ne change pas. */
.kincy-portrait-card .kincy-tool-tile--filled .kincy-kpi-tile-value {
  color: var(--kincy-blue);
}

.kincy-portrait-cert {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* « Aucune certification » : chapeau estompé, pas de chiffre (`CK-D11`). */
.kincy-portrait-cert-none .kincy-mortarboard {
  opacity: 0.3;
}

.kincy-portrait-cert-none .kincy-kpi-tile-label {
  margin-top: 6px;
}

.kincy-portrait-unknown {
  font-size: 1.5rem;
}

.kincy-portrait-insufficient {
  font-weight: 400;
  font-style: italic;
  color: var(--kincy-neutral-dark);
}

.kincy-portrait-insufficient--kpi {
  font-size: 1.25rem;
  font-weight: 500;
  line-height: 1.2;
}

/* Zone données : cinq lignes, sans ascenseur (`CK-D15` amendée). */
.kincy-portrait-metrics {
  font-size: 0.875rem;
}

.kincy-portrait-metrics th {
  font-weight: 400;
}

.kincy-portrait-metrics td {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
}

.kincy-portrait-sub {
  color: var(--kincy-neutral-dark);
  font-size: 0.75rem;
}

/* Page détails */
.kincy-portrait-page {
  display: flex;
  flex-direction: column;
  gap: var(--kincy-spacing-md);
  max-width: 1200px;
  font-variant-numeric: tabular-nums;
}

.kincy-portrait-title {
  color: var(--kincy-slate);
}

.kincy-portrait-lead {
  margin: 0;
  color: var(--kincy-neutral-dark);
  font-size: 0.875rem;
}

/* Sur le CONTENU du fragment, jamais sur la cible : l'état d'erreur commun
 * (`ui.md` §4.13) masque la cible, et un `display` posé sur elle par son
 * identifiant l'emportait (vu par `probe_report_errors.py`, 2026-10-29). */
.kincy-portrait-blocks {
  display: flex;
  flex-direction: column;
  gap: var(--kincy-spacing-lg);
}

.kincy-portrait-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(460px, 100%), 1fr));
  gap: var(--kincy-spacing-lg);
}

.kincy-portrait-sheet {
  display: flex;
  flex-direction: column;
  gap: var(--kincy-spacing-md);
  min-width: 0;
  padding: var(--kincy-spacing-lg);
  border: 1px solid rgba(var(--kincy-shadow-ink), 0.15);
  border-radius: var(--kincy-radius-sm);
  background-color: var(--bs-body-bg);
}

.kincy-portrait-h2 {
  margin: 0;
}

.kincy-portrait-period {
  margin: 2px 0 0;
  color: var(--kincy-neutral-dark);
  font-size: 0.8125rem;
}

.kincy-portrait-note {
  margin: 0;
  color: var(--kincy-neutral-dark);
  font-size: 0.8125rem;
}

.kincy-portrait-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--kincy-spacing-md);
}

.kincy-portrait-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-md);
  color: var(--kincy-slate);
  font-size: 0.8125rem;
}

.kincy-portrait-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.kincy-portrait-line-swatch {
  display: inline-block;
  width: 18px;
  height: 3px;
  border-radius: 2px;
}

.kincy-portrait-line-swatch--created {
  background-color: var(--kincy-blue);
}

/* Bloc `certifies` */
.kincy-portrait-count {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--kincy-slate);
}

.kincy-portrait-count-value {
  font-size: 2.5rem;
  font-weight: 700;
  line-height: 1;
}

.kincy-portrait-count-label {
  font-size: 0.9375rem;
}

/* 0 certifié : l'orange encre de la tuile partagée (`CK-D35`), 4,62:1 sur blanc. */
.kincy-portrait-count--zero .kincy-portrait-count-value {
  color: var(--kincy-orange-ink);
}

.kincy-portrait-table {
  font-size: 0.9375rem;
}

.kincy-portrait-table thead th,
.kincy-portrait-channels td:first-child {
  white-space: nowrap;
}

.kincy-portrait-table thead th {
  color: var(--kincy-neutral-dark);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.kincy-portrait-agency {
  --kincy-agency-ink: var(--kincy-neutral-medium);
  display: inline-flex;
  align-items: center;
  gap: var(--kincy-spacing-sm);
}

.kincy-portrait-agency .kincy-agency-dot {
  width: 10px;
  height: 10px;
}

.kincy-portrait-certifies-foot {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.kincy-portrait-renew {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--kincy-slate);
  font-size: 0.875rem;
}

.kincy-portrait-renew svg {
  color: var(--kincy-orange-ink);
}

/* Bloc `creneaux` */
.kincy-portrait-bars {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.kincy-portrait-bar-row {
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr) 92px;
  gap: 12px;
  align-items: center;
  font-size: 0.875rem;
}

.kincy-portrait-bar-name {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}

.kincy-portrait-bar-span {
  color: var(--kincy-neutral-dark);
  font-size: 0.75rem;
}

.kincy-portrait-bar-track {
  height: 18px;
  overflow: hidden;
  border-radius: 3px;
  background-color: color-mix(in srgb, var(--kincy-neutral-light) 65%, var(--bs-body-bg));
}

.kincy-portrait-bar-fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background-color: var(--kincy-blue);
}

.kincy-portrait-bar-value {
  text-align: right;
}

.kincy-portrait-bar-value b {
  font-weight: 600;
}

/* Bloc `evolution` et petits multiples de `charge` : SVG serveur (`ui.md` §4.5). */
.kincy-portrait-svg {
  display: block;
  width: 100%;
  height: auto;
}

.kincy-portrait-evo-window {
  fill: color-mix(in srgb, var(--kincy-neutral-light) 65%, var(--bs-body-bg));
}

.kincy-portrait-evo-window-label,
.kincy-portrait-bar-label {
  fill: var(--kincy-slate);
}

.kincy-portrait-axis {
  fill: var(--kincy-neutral-dark);
}

.kincy-portrait-grid-line {
  stroke: var(--kincy-slate);
  stroke-opacity: 0.12;
  stroke-width: 1;
}

.kincy-portrait-grid-line--base {
  stroke-opacity: 0.35;
}

.kincy-portrait-evo-created {
  stroke: var(--kincy-blue);
  stroke-width: 2.5;
}

.kincy-portrait-evo-resolved {
  stroke: var(--kincy-slate);
  stroke-width: 2;
  stroke-dasharray: 6 4;
}

.kincy-portrait-dot--created {
  fill: var(--kincy-blue);
}

.kincy-portrait-dot--resolved {
  fill: var(--kincy-slate);
}

.kincy-portrait-stock {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--kincy-spacing-sm);
  margin: 0;
  font-size: 0.9375rem;
}

.kincy-portrait-stock b {
  font-size: 1.25rem;
  font-weight: 700;
}

.kincy-portrait-multiples {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: var(--kincy-spacing-lg);
}

.kincy-portrait-mult-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--kincy-spacing-sm);
  margin-bottom: 6px;
  font-size: 0.875rem;
}

.kincy-portrait-mult-title {
  color: var(--kincy-slate);
  font-weight: 600;
}

.kincy-portrait-mult-total b {
  font-size: 1.25rem;
  font-weight: 700;
}

.kincy-portrait-bar {
  fill: var(--kincy-blue);
}

.kincy-portrait-bar--zero {
  fill: var(--kincy-neutral-medium);
}

/* Bloc `canaux` : anneau (`ui.md` §4.9) ; teinte portée par la CLÉ du canal. */
.kincy-portrait-donut-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-lg);
}

.kincy-portrait-donut {
  width: 200px;
  height: 200px;
  flex: none;
}

.kincy-portrait-donut-track {
  stroke: var(--kincy-neutral-light);
}

.kincy-portrait-donut-total {
  fill: var(--kincy-slate);
  font-weight: 700;
}

.kincy-portrait-channels {
  flex: 1 1 260px;
  width: auto;
}

.kincy-portrait-channel--phone { --kincy-portrait-channel: var(--kincy-blue); }
.kincy-portrait-channel--email { --kincy-portrait-channel: var(--kincy-cyan); }
.kincy-portrait-channel--web { --kincy-portrait-channel: var(--kincy-purple); }
.kincy-portrait-channel--other { --kincy-portrait-channel: var(--kincy-neutral-medium); }

.kincy-portrait-donut [class*="kincy-portrait-channel--"] {
  stroke: var(--kincy-portrait-channel);
}

.kincy-portrait-swatch {
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-right: var(--kincy-spacing-sm);
  border-radius: 3px;
  vertical-align: -1px;
  background-color: var(--kincy-portrait-channel);
}

.kincy-portrait-total td {
  font-weight: 600;
}

/* Bloc `chaleur` : échelle à cinq teintes, gris, vert, jaune, orange, rouge
 * (`CK-D17` amendée). La couleur porte le VOLUME du client dans la case, l'encre
 * du nombre se choisit PAR CLASSE (aucune ne tient sur toute l'échelle). Mesuré le
 * 2026-10-29 avec `apps/core/contrast.py` (jaune résolu = #CE9800, mélange OKLCH
 * calculé en Python et relu au style calculé du navigateur) :
 *   0 neutre clair #E2EFF4, gris foncé #595959   5,97:1
 *   1 vert #6BB52D, encre courante #1d1d1b       6,65:1
 *   2 jaune dérivé #CE9800, encre courante       6,52:1
 *   3 orange #F08300, encre courante             6,39:1
 *   4 rouge #B3261E, blanc                       6,54:1
 * ⚠️ Le rouge et le vert ne valent ici que comme INTENSITÉ, confinés à ce bloc :
 * ailleurs l'alerte reste l'orange, et `design.md` §3.6 exclut le vert. */
.kincy-portrait-heat {
  width: 100%;
  border-collapse: separate;
  border-spacing: 4px;
  font-size: 0.8125rem;
}

.kincy-portrait-heat th {
  padding: 2px 6px;
  color: var(--kincy-slate);
  font-weight: 500;
  text-align: left;
  white-space: nowrap;
}

.kincy-portrait-heat thead th {
  line-height: 1.2;
  text-align: center;
}

.kincy-portrait-heat-span {
  display: block;
  color: var(--kincy-neutral-dark);
  font-size: 0.6875rem;
  font-weight: 400;
}

.kincy-portrait-heat td {
  height: 40px;
  border-radius: 3px;
  font-weight: 500;
  line-height: 1.1;
  text-align: center;
}

.kincy-portrait-heat-idx {
  display: block;
  font-size: 0.6875rem;
  font-weight: 700;
}

.kincy-portrait-heat-0 {
  background-color: var(--kincy-neutral-light);
  color: var(--kincy-neutral-dark);
}

.kincy-portrait-heat-1 {
  background-color: var(--kincy-green);
  color: var(--bs-body-color);
}

.kincy-portrait-heat-2 {
  background-color: color-mix(in oklch, var(--kincy-green) 40%, var(--kincy-orange));
  color: var(--bs-body-color);
}

.kincy-portrait-heat-3 {
  background-color: var(--kincy-orange);
  color: var(--bs-body-color);
}

.kincy-portrait-heat-4 {
  background-color: var(--kincy-red);
  color: var(--bs-body-bg);
}

.kincy-portrait-heat-summary {
  margin: 0;
  font-size: 0.9375rem;
}

.kincy-portrait-heat-legend {
  display: flex;
  flex-direction: column;
  gap: var(--kincy-spacing-sm);
}

.kincy-portrait-heat-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  height: 22px;
  padding: 0 6px;
  border-radius: 3px;
  font-size: 0.75rem;
  font-weight: 500;
}
/* Bascule Jour / Semaine / Mois et bloc `calendrier` (`CK-D39`). Aucune couleur neuve : l'échelle
 * est celle de `chaleur` (`.kincy-portrait-heat-N` ci-dessus) ; le hachuré d'un mois en cours
 * se compose avec le fond de page. */
.kincy-portrait-head-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--kincy-spacing-md);
}

.kincy-portrait-evo--thin {
  stroke-width: 1.25;
}

.kincy-portrait-cal {
  width: 100%;
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 3px;
  font-size: 0.8125rem;
}

.kincy-portrait-cal--day {
  border-spacing: 1px 3px;
}

.kincy-portrait-cal th {
  padding: 2px 6px;
  overflow: hidden;
  color: var(--kincy-slate);
  font-weight: 500;
  text-align: left;
  white-space: nowrap;
}

.kincy-portrait-cal thead th {
  padding: 2px 0;
  font-size: 0.75rem;
}

.kincy-portrait-cal-corner {
  width: 6.5rem;
}

.kincy-portrait-cal td {
  height: 36px;
  padding: 0;
  overflow: hidden;
  border-radius: 3px;
  font-weight: 500;
  line-height: 1.1;
  text-align: center;
}

.kincy-portrait-cal--day td {
  border-radius: 1px;
}

.kincy-portrait-cal-top {
  outline: 2px solid var(--bs-body-color);
  outline-offset: 0;
}

.kincy-portrait-cal-partial {
  background-image: repeating-linear-gradient(
    135deg,
    transparent 0 4px,
    color-mix(in srgb, var(--bs-body-bg) 55%, transparent) 4px 8px
  );
}

/* Filtre par canal du `calendrier` (`CK-D40`) : les chips du dépôt (`.kincy-chip--inked`), avec la
 * teinte du bloc `canaux` (`--kincy-portrait-channel`, posée par `.kincy-portrait-channel--<code>`)
 * en éclat. Aucun token neuf. Le point d'une chip active se repeint (le cyan du canal e-mail
 * disparaîtrait sur le fond cyan de la chip), précédent : `.kincy-billing-nature-dot`. */
.kincy-chip--inked[class*="kincy-portrait-channel--"] {
  --kincy-agency-ink: var(--kincy-portrait-channel);
}

.kincy-portrait-cal-filter .kincy-chip--active .kincy-agency-dot {
  background-color: var(--bs-body-bg);
}

.kincy-portrait-cal-filter {
  margin: 0 0 var(--kincy-spacing-sm);
}

.kincy-portrait-cal-peaks {
  margin: 0;
  padding-left: 1.25rem;
  color: var(--kincy-slate);
  font-size: 0.8125rem;
}


/* --- fin du bloc « Portrait du client » --- */

/* ---------------------------------------------------------------------------
   Teach : RH > Teach et page Parcours (lot L-M2, sous-lot B1, apps/teach).
   UN bloc contigu, en fin de feuille, tokens seulement, AUCUNE couleur de signal neuve.
   1. Les tuiles de tête réemploient `.kincy-teach-tile` (ci-dessus, grammaire de
      `docs/conventions/ui.md` §4.11) ; trois modificateurs ne changent que la couleur du FILET :
      le filet porte la couleur, jamais le chiffre. Vert = atteint ou actif dans les temps,
      rouge = non atteint (`docs/11-backoffice-link.md` §8.6), orange = à renouveler. Le mot
      (« Atteint », « Non atteint ») double toujours la couleur : `--kincy-green` ne tient pas
      3:1 sur blanc (2,54:1), écart borné ici à un filet décoratif.
   2. L'objectif (liste des membres, page Parcours) : un filet de 4 ou 5 px selon la tonalité
      (`data-tone` : green, red, neutral), le texte reste en encre courante.
   3. Les tuiles de district : une grille, un état par attribut (`data-state`). L'estompage
      (atteint, nouvelle version, sans contenu) est celui de la TUILE entière ; une tuile
      estompée est inerte ou secondaire, pas un texte de lecture courante. La sélection
      (`data-selected`) n'a de rendu que dans un conteneur `.kincy-teach-districts--select`
      (le formulaire des objectifs) : en lecture, toutes les tuiles de l'objectif seraient
      « sélectionnées » et se confondraient.
   --------------------------------------------------------------------------- */
.kincy-teach-tile--green { border-left-color: var(--kincy-green); }
.kincy-teach-tile--red { border-left-color: var(--kincy-red); }
.kincy-teach-tile--orange { border-left-color: var(--kincy-orange); }
.kincy-teach-tile--neutral { border-left-color: var(--kincy-neutral-medium); }
.kincy-teach-tile-value--text { font-size: 1.6rem; line-height: 1.3; }

.kincy-teach-objective-cell {
  padding-left: var(--kincy-spacing-sm);
  border-left: 4px solid var(--kincy-neutral-medium);
}
.kincy-teach-objective-cell[data-tone="green"] { border-left-color: var(--kincy-green); }
.kincy-teach-objective-cell[data-tone="red"] { border-left-color: var(--kincy-red); }

.kincy-teach-objective {
  margin-bottom: var(--kincy-spacing-md);
  padding: var(--kincy-spacing-md);
  border: 1px solid var(--kincy-neutral-medium);
  border-left: 5px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-md);
  background-color: var(--bs-body-bg);
}
.kincy-teach-objective[data-tone="green"] { border-left-color: var(--kincy-green); }
.kincy-teach-objective[data-tone="red"] { border-left-color: var(--kincy-red); }

.kincy-teach-districts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: var(--kincy-spacing-sm);
}
.kincy-teach-district {
  padding: var(--kincy-spacing-sm) var(--kincy-spacing-md);
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-md);
  background-color: var(--bs-body-bg);
}
.kincy-teach-district-title { font-weight: 600; }
.kincy-teach-district-tagline {
  margin-top: var(--kincy-spacing-xs);
  color: var(--kincy-neutral-dark);
  font-size: 0.8rem;
  line-height: 1.3;
}
.kincy-teach-district-state,
.kincy-teach-district-chip {
  margin-top: var(--kincy-spacing-xs);
  font-size: 0.75rem;
}
.kincy-teach-district-chip { display: inline-block; }
.kincy-teach-district[data-state="achieved"] { opacity: 0.45; }
.kincy-teach-district[data-state="renew"] { opacity: 0.8; }
.kincy-teach-district[data-state="no_content"] { opacity: 0.6; }
.kincy-teach-district[data-state="missing"] {
  border-style: dashed;
  border-color: var(--kincy-neutral-dark);
}
.kincy-teach-districts--select .kincy-teach-district[data-toggleable="true"] { cursor: pointer; }
.kincy-teach-districts--select .kincy-teach-district[data-selected="true"] {
  border-color: var(--kincy-blue);
  background-color: var(--kincy-neutral-light);
}

/* Un cursus retiré qui garde une progression : ses mesures s'estompent (11 §7.2). */
.kincy-teach-cursus--off td[data-teach-measure] { opacity: 0.55; }

/* Le formulaire des objectifs (lot L-M2, sous-lot B2) : un cadre neutre sous l'objectif de
   référence, et l'anneau de focus d'une tuile cliquable. La case d'une tuile est masquée
   (`visually-hidden`) mais reste au clavier : c'est la TUILE qui montre le focus, avec le cyan
   du :focus-visible de Link. Aucune couleur de signal neuve. */
.kincy-teach-objective-form {
  margin-bottom: var(--kincy-spacing-md);
  padding: var(--kincy-spacing-md);
  border: 1px solid var(--kincy-neutral-medium);
  border-radius: var(--kincy-radius-md);
  background-color: var(--bs-body-bg);
}
.kincy-teach-districts--select .kincy-teach-district:has(> input:focus-visible) {
  outline: 2px solid var(--kincy-cyan);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   Accueil : bloc Teach (lot L-M2, sous-lot C, `apps/core/templates/core/_teach_block.html`).
   UN bloc contigu, en fin de feuille, tokens seulement, AUCUNE couleur de signal neuve. Le cadre
   de l'objectif est `.kincy-teach-objective` (ci-dessus, filet vert, rouge ou neutre selon
   `data-tone`) : réemploi, rien de recopié. Ici, seule la largeur et la place sous la rangée d'outils.
   --------------------------------------------------------------------------- */
.kincy-teach-home {
  max-width: 36rem;
  margin: var(--kincy-spacing-lg) auto 0;
}

/* ---------------------------------------------------------------------------
   Certification Teach d'un client : annuaire et tuile de la fiche (2026-10-29,
   apps/teach/DECISIONS.md §14). Tokens seulement, AUCUNE couleur de signal neuve :
   c'est le PICTOGRAMME qui distingue les états (coche, anneau, flèches, cadenas),
   la couleur ne fait que le doubler (green et orange existent déjà, voir les
   tuiles RH > Teach ci-dessus). « Pas de certification » est le « — » de valeur
   absente, en encre estompée.
   --------------------------------------------------------------------------- */
.kincy-cert-glyph { vertical-align: middle; }
.kincy-cert-glyph--achieved { color: var(--kincy-green); }
.kincy-cert-glyph--renew { color: var(--kincy-orange); }
.kincy-cert-glyph--available { color: var(--kincy-blue); }
.kincy-cert-glyph--locked,
.kincy-cert-glyph--none { color: var(--kincy-neutral-dark); opacity: 0.55; }

/* Pastille pleine de l'annuaire (2026-10-29, apps/teach/DECISIONS.md §14) : disque de la couleur de
   l'état, pictogramme blanc (`--bs-body-bg`) dedans. Le groupe porte `color`, donc trait ET remplissage
   (`currentColor`) du pictogramme deviennent blancs. Ni la tuile (pictogramme nu) ni « grisé » / « pas de
   certification » ne la prennent. Fond du disque vert et orange = `-ink` (blanc dessus 4,59:1 et 4,62:1), bleu inchangé (4,80:1) :
   aucun écart au budget des signaux (ui.md §3). */
.kincy-cert-glyph--disc .kincy-cert-pict { color: var(--bs-body-bg); }
.kincy-cert-glyph--disc.kincy-cert-glyph--achieved { color: var(--kincy-green-ink); }
.kincy-cert-glyph--disc.kincy-cert-glyph--renew { color: var(--kincy-orange-ink); }

/* Passe graphique du 2026-10-29, variante A retenue (apps/teach/DECISIONS.md §14) : chapeau
   universitaire plein (`clients/_mortarboard.html`, sans cordon ni gland) en en-tête de la
   colonne de l'annuaire, et en filigrane de la tuile de la fiche. Aucune couleur neuve : le
   chapeau est en `currentColor`. L'en-tête hérite l'encre des autres en-têtes (`currentColor`, aucune règle propre). Le filigrane
   tient dans la zone au-dessus du libellé (hauteur de 64 px), derrière le pictogramme de 36 px ;
   seul (« Pas de certification »), il est estompé à 0,30 et la tuile garde sa hauteur. */
.kincy-mortarboard { vertical-align: middle; flex: none; }
.kincy-cert-tile { position: relative; overflow: hidden; }
.kincy-cert-tile .kincy-kpi-tile-value { position: relative; min-height: 36px; }
.kincy-cert-tile .kincy-kpi-tile-label { position: relative; }
.kincy-cert-watermark { position: absolute; top: 4px; left: 0; right: 0; height: 64px; display: flex; align-items: center; justify-content: center; opacity: 0.10; pointer-events: none; }
.kincy-cert-watermark--solo { opacity: 0.30; }

/* ============================================================================
   FICHE RH, PASSE 2 (lot P3a, 2026-11-04, maquette finale validée par Rodolphe)
   `annuaire-rh-v2-passe2/maquette-finale.html`. Aucune valeur de couleur, de rayon
   ni d'ombre n'est écrite ici : tout est un token existant. Le préfixe
   `.kincy-state-` n'est pas un choix de nom : `tests/test_state_tokens.py` confine
   les tokens `--kincy-state-*` aux sélecteurs `.kincy-state…`, et la fiche réemploie
   deux d'entre eux (fond des onglets inactifs, filet de la feuille). Aucun token
   neuf : `ui.md` §3 n'a pas bougé.
   ---------------------------------------------------------------------------- */

/* Les onglets sont des LIENS (une page par onglet, `aria-current`), pas les boutons
   `aria-selected` de la page « Contrats » : on ne change que ce qui diffère. */
.kincy-state-tabs--link { flex-wrap: wrap; }
.kincy-state-tabs--link .kincy-state-tab {
  position: relative;
  min-height: 46px;
  padding: 0 18px;
  font-size: 16px;
  line-height: 22px;
  text-decoration: none;
  white-space: nowrap;
}
.kincy-state-tabs--link .kincy-state-tab:hover {
  background-color: var(--kincy-state-tab-hover);
  color: var(--kincy-slate);
}
.kincy-state-tabs--link .kincy-state-tab[aria-current="page"] {
  background-color: var(--kincy-blue);
  color: var(--bs-body-bg);
}

/* Le compteur de l'onglet Temps : rouge = mois MANQUANTS seulement, le sens du badge
   « Manquante » (`_entry_state.html`). Le halo blanc le détache de l'onglet actif. */
.kincy-state-tab-count {
  display: inline-grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 10px;
  background: var(--kincy-red);
  color: var(--bs-body-bg);
  font-size: 12px;
  font-weight: 700;
  box-shadow: 0 0 0 2px var(--bs-body-bg);
}

/* La pochette fermée : le cadre prend LE bleu de l'onglet actif, sans bord haut. */
.kincy-state-folder {
  border: 1px solid var(--kincy-blue);
  border-top: 0;
  border-radius: 0 0 var(--kincy-radius-md) var(--kincy-radius-md);
  background: var(--bs-body-bg);
  box-shadow: 0 1px 3px rgba(var(--kincy-shadow-ink), 0.08);
}
.kincy-state-folder-body { padding: 22px 24px 24px; min-width: 0; }

/* Titre de zone : tuile neutre à pictogramme bleu, titre BLEU sur blanc (4,80:1,
   comme `.kincy-home-card-title` ; le cyan ne s'emploie jamais en titre, 2,67:1). */
.kincy-state-zone { scroll-margin-top: 90px; }
.kincy-state-zone + .kincy-state-zone {
  margin-top: 24px;
  padding-top: 22px;
  border-top: 1px solid var(--kincy-state-sheet-border);
}
.kincy-state-zone-title { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0 0 14px; }
.kincy-state-zone-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: var(--kincy-radius-sm);
  background: var(--kincy-neutral-light);
  color: var(--kincy-blue);
}
.kincy-state-zone-title h2 { margin: 0; font-size: 18px; font-weight: 600; line-height: 24px; color: var(--kincy-blue); }
.kincy-state-zone-title h2 a { color: inherit; text-decoration: none; }
.kincy-state-zone-title h2 a:hover { text-decoration: underline; }

/* Les pictogrammes : la teinte dit l'état, le texte voisin le redit toujours. */
.kincy-state-ico-ok { color: var(--kincy-green-ink); }
.kincy-state-ico-miss { color: var(--kincy-red); }
.kincy-state-ico-wait,
.kincy-state-ico-off { color: var(--kincy-neutral-dark); }

/* Cartes d'état : un liseré haut, un geste en pied. */
.kincy-state-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
.kincy-state-card {
  --kincy-card-ink: var(--kincy-slate);
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  padding: 14px 16px;
  border: 1px solid var(--kincy-state-sheet-border);
  border-top: 4px solid var(--kincy-card-ink);
  border-radius: var(--kincy-radius-sm);
  background: var(--bs-body-bg);
}
.kincy-state-card--alert { --kincy-card-ink: var(--kincy-red); }
.kincy-state-card--ok { --kincy-card-ink: var(--kincy-green); }
.kincy-state-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--kincy-slate);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.kincy-state-card-head svg { color: var(--kincy-blue); }
.kincy-state-card-value { font-size: 21px; font-weight: 700; line-height: 1.2; }
.kincy-state-card--alert .kincy-state-card-value { color: var(--kincy-red); }
.kincy-state-card-note { color: var(--kincy-neutral-dark); font-size: 13px; }
.kincy-state-card-go {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: auto;
  padding-top: 6px;
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
}
.kincy-state-card-go:hover { text-decoration: underline; }
.kincy-state-pills { display: flex; flex-wrap: wrap; gap: 6px; }

/* « Dans Link » : une ligne = un état, son pictogramme, son geste. */
.kincy-state-list { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; font-size: 14px; }
.kincy-state-list li {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
  padding: 4px 0;
  border-bottom: 1px solid var(--kincy-state-sheet-border);
}
.kincy-state-list li:last-child { border-bottom: 0; }
.kincy-state-list-key { color: var(--kincy-neutral-dark); }
.kincy-state-list-value { color: var(--bs-body-color); font-weight: 500; }
.kincy-state-list a { font-size: 13px; text-align: right; }

/* La frise des douze mois : chaque case est un lien vers CE mois. */
.kincy-state-months { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 6px; }
.kincy-state-month {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  min-width: 0;
  padding: 8px 2px 6px;
  border: 1px solid var(--kincy-state-sheet-border);
  border-radius: var(--kincy-radius-sm);
  background: var(--bs-body-bg);
  color: var(--bs-body-color);
  font-size: 12px;
  text-decoration: none;
}
a.kincy-state-month:hover {
  border-color: var(--kincy-blue);
  box-shadow: var(--kincy-elevation-raised);
  color: var(--bs-body-color);
}
.kincy-state-month b { color: var(--kincy-slate); font-size: 13px; font-weight: 600; }
.kincy-state-month small { color: var(--kincy-neutral-dark); font-size: 11px; font-variant-numeric: tabular-nums; }
.kincy-state-month--miss { border: 2px solid var(--kincy-red); }
.kincy-state-month--miss b,
.kincy-state-month--miss small { color: var(--kincy-red); font-weight: 600; }
.kincy-state-month--wait { border-style: dashed; border-color: var(--kincy-neutral-dark); }
.kincy-state-month--none { border-style: dotted; }
.kincy-state-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 10px;
  color: var(--kincy-neutral-dark);
  font-size: 13px;
}
.kincy-state-legend span { display: inline-flex; align-items: center; gap: 6px; }

/* Le nom de l'agence à côté de son code, dans la cartouche. */
.kincy-agency-name { color: var(--kincy-neutral-dark); font-size: 13px; }

/* Sous 1100 px (menu latéral de 240 px compris), douze cases ne tiennent plus : « Manquant »
   était rogné à 1024 px (capture du 2026-11-04). Six par rangée. */
@media (max-width: 1100px) {
  .kincy-state-months { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .kincy-state-months { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .kincy-state-folder-body { padding: 18px 16px; }
}

/* --- Lot P3b (2026-11-04) : onglets Emploi et Temps, sans carte dans la pochette ------
   Même règle que le bloc ci-dessus : aucune valeur de couleur écrite ici, que des tokens. */

/* Action de zone : en haut à droite du titre ; complément du titre en gris. */
.kincy-state-zone-aside { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-left: auto; }
.kincy-state-zone-meta { color: var(--kincy-neutral-dark); font-size: 13px; }

/* Grille à deux colonnes d'une zone : une valeur par cellule, son nom au-dessus. */
.kincy-state-kv { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 32px; margin: 0; }
.kincy-state-kv > div { display: grid; grid-template-columns: 138px minmax(0, 1fr); gap: 0 12px; align-items: baseline; min-width: 0; }
.kincy-state-kv dt {
  margin: 0;
  color: var(--kincy-neutral-dark);
  font-size: 14px;
  font-weight: 400;
}
.kincy-state-kv dd { margin: 0; font-weight: 600; }
@media (max-width: 900px) { .kincy-state-kv { grid-template-columns: minmax(0, 1fr); } }

/* Sommaire d'ancres, collé sous la barre d'onglets (relief : `kincy-stickybar` + sentinelle). */
.kincy-state-subnav {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  align-items: center;
  padding: 6px 24px;
  background: var(--kincy-neutral-light);
  border-bottom: 1px solid var(--kincy-state-sheet-border);
  font-size: 13px;
}
.kincy-state-subnav > span {
  margin-right: 8px;
  color: var(--kincy-slate);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.kincy-state-subnav a { padding: 2px 10px; border-radius: 12px; color: var(--kincy-blue); text-decoration: none; }
.kincy-state-subnav a:hover { background: var(--bs-body-bg); text-decoration: underline; }
.kincy-state-subnav small { color: var(--kincy-neutral-dark); font-size: inherit; }

/* Repli d'une zone (contrats précédents, périodes à compléter). */
.kincy-state-more { margin-top: 12px; }
.kincy-state-more > summary { color: var(--kincy-blue); cursor: pointer; font-size: 14px; }
.kincy-state-more[open] > summary { margin-bottom: 10px; }

/* Les tableaux des zones : pas de filet épais entre deux mois (Bootstrap en met un). */
.table.kincy-state-table > :not(:first-child) { border-top: 0; }
.kincy-state-table th.kincy-state-num,
.kincy-state-table td.kincy-state-num { text-align: right; font-variant-numeric: tabular-nums; }
.kincy-state-table tr.kincy-state-row--target > * { background: var(--bs-tertiary-bg); }

/* « Publiable » : l'état et son pictogramme, vert encre ; le même fichier sert la fiche et
   l'écran « Temps travaillés » (`hr/_publishable_state.html`). */
.kincy-state-state { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.kincy-state-state--ok { color: var(--kincy-green-ink); font-weight: 600; }
.kincy-state-state--off,
.kincy-state-state--wait { color: var(--kincy-neutral-dark); }
.kincy-state-state--miss { color: var(--kincy-red); font-weight: 600; }

/* Décompte de l'onglet Temps : les pastilles « manquants » et « à saisir » sont des ancres. */
.kincy-state-tally { display: flex; flex-wrap: wrap; gap: 8px 18px; margin: 0 0 14px; font-size: 14px; }
.kincy-state-tally > a,
.kincy-state-tally > span { display: inline-flex; align-items: center; gap: 6px; }
.kincy-state-tally > a { font-weight: 600; text-decoration: none; }
.kincy-state-tally > a:hover { text-decoration: underline; }

/* Saisie en tête de l'onglet Temps : une bande, pas une carte. */
.kincy-state-entry {
  margin: 0 0 16px;
  padding: 12px 16px;
  border: 1px solid var(--kincy-state-sheet-border);
  border-radius: var(--kincy-radius-sm);
  background: var(--bs-tertiary-bg);
  scroll-margin-top: 90px;
}
.kincy-state-entry-title { margin: 0; font-size: 15px; font-weight: 600; }
/* Bandeau compact d'une ligne (revue finale R2) : titre, état, champ, boutons ; le liseré rouge
   dit que le mois est manquant. */
section.kincy-state-entry { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 20px; }
.kincy-state-entry--missing { border-left: 4px solid var(--kincy-red); }
.kincy-state-entry-form { display: flex; flex: 1 1 auto; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 0; }
.kincy-state-entry-field { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.kincy-state-entry-field .input-group { width: auto; flex-wrap: nowrap; }
.kincy-state-entry-field .form-control { width: 9rem; }
.kincy-state-entry-form > .alert { flex: 1 0 100%; margin: 0; }

/* Détail déplié sur place : chevron, puis grille de faits. */
.kincy-state-rowtoggle {
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-right: 4px;
  padding: 0;
  border: 0;
  border-radius: var(--kincy-radius-sm);
  background: transparent;
  color: var(--kincy-blue);
  vertical-align: middle;
}
.kincy-state-rowtoggle:hover { background: var(--bs-tertiary-bg); }
.kincy-state-rowtoggle svg { transition: transform 0.15s; }
.kincy-state-rowtoggle[aria-expanded="true"] svg { transform: rotate(90deg); }
.kincy-state-rowspacer { display: inline-block; width: 26px; }
.kincy-state-detail > td { background: var(--bs-tertiary-bg); padding: 12px 16px 14px 36px; }
.kincy-state-facts { display: flex; flex-wrap: wrap; gap: 8px 28px; margin: 0 0 8px; }
.kincy-state-facts dt { color: var(--kincy-neutral-dark); font-size: 14px; font-weight: 400; }
.kincy-state-facts dd { margin: 0; font-weight: 600; }
.kincy-state-detail ul { margin: 0 0 10px; padding-left: 18px; font-size: 13px; }

/* Ouvrir un autre mois : champ et bouton sur une ligne, dans l'action de zone. */
.kincy-state-month-picker { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0; }
.kincy-state-month-picker label { color: var(--kincy-neutral-dark); font-size: 13px; }
.kincy-state-month-picker input { width: auto; }

/* --- Lot P3c (2026-11-04) : onglet Réglages Link, interrupteur Teach, finitions ---------
   Même règle que les blocs ci-dessus : aucune valeur de couleur écrite ici, que des tokens. */

/* Sommaire : la section courante (posée par `hr_person_subnav.js`, `aria-current="location"`). */
.kincy-state-subnav a[aria-current="location"] {
  background: var(--bs-body-bg);
  font-weight: 600;
}

/* Action de zone au ton neutre : le bouton « sans couleur » de la maquette (contour et encre
   `--kincy-neutral-dark`, 7,00:1), à la place du gris de Bootstrap. */
.kincy-state-zone-aside .btn-outline-secondary,
.kincy-state-neutral-btn {
  --bs-btn-color: var(--kincy-neutral-dark);
  --bs-btn-border-color: var(--kincy-neutral-dark);
  --bs-btn-hover-color: var(--bs-body-bg);
  --bs-btn-hover-bg: var(--kincy-neutral-dark);
  --bs-btn-hover-border-color: var(--kincy-neutral-dark);
  --bs-btn-active-color: var(--bs-body-bg);
  --bs-btn-active-bg: var(--kincy-neutral-dark);
  --bs-btn-active-border-color: var(--kincy-neutral-dark);
}

/* Retouche R1 de la revue finale (2026-11-05, décision de Rodolphe) : sur la FICHE seulement, les
   actions « outline primary » passent du cyan (2,67:1 sur blanc) au bleu de la charte. Le
   `btn-outline-primary` global et les tokens cyan de Link ne bougent pas. */
.kincy-state-folder .btn-outline-primary {
  --bs-btn-color: var(--kincy-blue);
  --bs-btn-border-color: var(--kincy-blue);
  --bs-btn-hover-color: var(--bs-body-bg);
  --bs-btn-hover-bg: var(--kincy-blue);
  --bs-btn-hover-border-color: var(--kincy-blue);
  --bs-btn-active-color: var(--bs-body-bg);
  --bs-btn-active-bg: var(--kincy-blue);
  --bs-btn-active-border-color: var(--kincy-blue);
}

/* Réglages : deux colonnes, des rangées à interrupteur SANS boîte. L'interrupteur lui-même est le
   `.form-switch` de Link (`ui.md` §4.15) : rien n'est redessiné ici. */
.kincy-state-settings { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 32px; }
.kincy-state-set {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  gap: 2px 12px;
  align-items: center;
  align-content: start;
  min-width: 0;
  margin: 0;
  padding: 4px 0;
}
.kincy-state-set-switch { grid-row: span 2; align-self: start; min-height: 0; margin: 2px 0 0; padding-left: 0; }
.kincy-state-set-switch .form-check-input { float: none; margin-left: 0; margin-top: 0; }
.kincy-state-set-title {
  display: inline-flex;
  gap: 8px;
  align-items: center;
  color: var(--kincy-slate);
  font-weight: 600;
}
.kincy-state-set-title svg { color: var(--kincy-blue); }
.kincy-state-set-state { color: var(--kincy-blue); font-size: 14px; font-weight: 500; }
.kincy-state-set-state .small { font-size: inherit; }
.kincy-state-set-state--off,
.kincy-state-set-state--off .text-muted { color: var(--kincy-neutral-dark); }
.kincy-state-set-note { grid-column: 2 / 4; color: var(--kincy-neutral-dark); font-size: 13px; }
.kincy-state-set-extra { grid-column: 2 / 4; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 6px; }
.kincy-state-set-extra[hidden] { display: none; }
.kincy-state-set-extra .form-control { flex: 1 1 220px; min-width: 0; }
@media (max-width: 900px) { .kincy-state-settings { grid-template-columns: minmax(0, 1fr); } }

/* Comptes reliés : deux tuiles basses ; un lien établi tient sur une ligne, les propositions
   se rangent dessous. */
.kincy-state-links { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start; }
.kincy-state-link {
  min-width: 0;
  padding: 8px 12px;
  border: 1px solid var(--kincy-state-sheet-border);
  border-radius: var(--kincy-radius-sm);
  font-size: 13px;
  scroll-margin-top: 90px;
}
.kincy-state-link-head { display: flex; gap: 10px; align-items: center; }
.kincy-state-link-body { flex: 1 1 auto; min-width: 0; }
.kincy-state-link h3 {
  margin: 0;
  color: var(--kincy-slate);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.kincy-state-link p { margin: 0; overflow-wrap: anywhere; }
.kincy-state-link-meta,
.kincy-state-link-note { color: var(--kincy-neutral-dark); }
.kincy-state-link-note { margin-top: 6px; }
.kincy-state-link-act { margin: 0 0 0 auto; flex: none; }
/* Les explications d'une tuile sont repliées : la tuile garde sa ligne d'état (revue finale R12). */
.kincy-state-link-more { margin-top: 4px; }
.kincy-state-link-more > summary { color: var(--kincy-blue); cursor: pointer; font-size: 12.5px; }
@media (max-width: 900px) { .kincy-state-links { grid-template-columns: minmax(0, 1fr); } }
