/* 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) */
  --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;

  /* 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);
}

/* 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;
}

.kincy-ms-surplus--alert {
  background-color: rgba(240, 131, 0, 0.1); /* --kincy-orange à 10 % */
  color: var(--kincy-orange);
}

.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;
}

.kincy-kpi-tile--alert {
  background-color: rgba(240, 131, 0, 0.1); /* --kincy-orange à 10 % */
  color: var(--kincy-orange);
}

.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). */
.kincy-ms-sticky {
  position: sticky;
  top: 0;
  z-index: 20;
  background-color: #fff;
  padding: var(--kincy-spacing-sm) 0;
}

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

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

.kincy-ms-chip--active {
  background-color: var(--kincy-neutral-light);
  border-color: var(--kincy-slate);
  color: var(--kincy-slate);
  font-weight: 600;
}

.kincy-ms-chip--active:hover {
  color: var(--kincy-slate);
}

.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-chip--active .kincy-ms-chip-count {
  background-color: #fff;
}

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

/* Conteneur du graphe d'évolution : hauteur fixe (Chart.js non proportionnel). */
.kincy-ms-chart-wrap {
  position: relative;
  height: 280px;
  margin: var(--kincy-spacing-md) 0;
}

/* 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;
}

.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);
}

.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: 0 8px 24px rgba(68, 84, 106, 0.18);
  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. */
.kincy-run-report { position: relative; }
.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; }

/* 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 { color: var(--kincy-slate); font-weight: 600; }
.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);
}

/* 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é. */
.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;
}

/* 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;
}

/* --- 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;
  /* Le texte est BLANC sur toutes les teintes d'agence : elles sont choisies
     assez sombres pour le porter, ce qui évite un calcul de luminance par
     segment (`_readable_text_color` de la page licences) sur six valeurs
     connues d'avance. */
  color: #fff;
  border-right: 1px solid rgb(255 255 255 / 45%);
}

.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. */
: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 */
}

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

/* 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/build/_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 grille à 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. */
.kincy-profit-filterbar {
  position: relative;
}

.kincy-dq-launch--inline {
  position: absolute;
  top: var(--kincy-spacing-sm);
  right: var(--kincy-spacing-sm);
  z-index: 2;
}
