/*------------------------------------------------------------------
[Variables héritées de theme.css]

Rapatriées lors de la disparition de theme.css. Ce ne sont PAS des jetons de la
refonte — ce sont les variables du thème acheté.

⚠️ LES DÉCOMPTES QUI FIGURAIENT ICI ÉTAIENT PÉRIMÉS. Ils annonçaient
« --primary-color 440 fois, --gray-color 189, --page-bg-color 122… » : c'étaient les
emplois relevés QUAND theme.css EXISTAIT ENCORE, et la feuille se citait elle-même
abondamment. Recomptés sur le dépôt réel (18/08/2026, tous .css/.razor/.cs) :
    --primary-color 57 | --gray-color 26 | --page-bg-color 10 | --lightgray-color 7
    --secondary-color 2 | --blue-color 2 | --lightgray-color-rgb 6 | --gray-color-rgb 4
Seule --primary-color reste réellement répandue.
Les supprimer aujourd'hui casserait la moitié des feuilles ; elles DÉRIVENT donc
désormais de leur jeton --kd-* (voir le bloc ci-dessous), ce qui rend leurs emplois
pilotables sans avoir à les migrer un par un.

⚠️ Placées EN TÊTE de ce fichier à dessein : elles vivaient dans theme.css, qui
était chargé AVANT tokens.css. Les mettre plus bas leur donnerait un arbitrage
qu'elles n'ont jamais eu.

NON reprises car tokens.css les déclare déjà et gagnait donc déjà : --bs-primary,
--bs-info, --sidebar-pinned-width, --sidebar-unpinned-width,
--sidebar-unpinned-widthcalc. Les omettre ne change rien (vérifié par mesure).

NON reprises car jamais consommées : --lightwhitegray-color, --darkblue-color.

DEUX COQUILLES HÉRITÉES, CORRIGÉES ICI (elles ont traversé tout le
démantèlement telles quelles, pour qu'aucune tranche ne mélange déménagement
et correctif) :

1. --primary-color-darker s'écrivait « #72942c# », le # final rendant la valeur
   invalide. ✔ CLOS AUTREMENT : la variable a été SUPPRIMÉE avec le § 12
   d'app.css (Choices.js), ses deux seules consommatrices — Cockpit n'utilise
   pas cette bibliothèque. Il n'y avait donc rien à corriger, seulement à
   retirer.

2. --lightgray-color-rgb était déclarée DEUX fois dans theme.css, avec deux
   valeurs différentes — 227,228,228 puis 234,234,234. La seconde gagnait, si
   bien que la variable portait la teinte d'--lightwhitegray-color (#EAEAEA) et
   non celle d'--lightgray-color (#E3E4E4) dont son nom est pourtant le décalque.
   Remise sur #E3E4E4. Effet : le zébrage, le survol et la ligne focalisée des
   grilles DevExpress s'assombrissent de 7 unités (§ 10).

UNE TROISIÈME, VOISINE — ✔ CORRIGÉE DEPUIS (commit ca138401). app.css écrivait
`--dxbl-grid-header-bg: var(--lightgray-color-rgb)`, c'est-à-dire « 227, 228,
228 » nu, sans `rgb()`. Ce n'est pas une couleur : la variable est invalide et
l'en-tête des grilles retombe sur le gris par défaut de DevExpress. Les trois
lignes voisines, elles, enveloppent bien la valeur (`rgba(var(--…), 1)`).
Corrigée en `rgb(var(--lightgray-color-rgb))`, l'en-tête de toutes les grilles a
retrouvé sa bande grise — et la couleur de son texte a dû suivre (§ 10).
*/
:root {
    /* ⚠️ CINQ D'ENTRE ELLES DÉRIVENT DÉSORMAIS DU JETON ÉQUIVALENT, et ce n'est
       pas cosmétique : c'est le prérequis de l'habillage par client
       (docs/architecture/habillage-client.md). Elles portaient la MÊME valeur que
       leur jeton, écrite deux fois et déclarée indépendamment — si bien qu'un
       habillage posant les seuls --kd-* aurait laissé une part des surfaces au vert
       d'origine — mesuré en posant --kd-primary: #0d6efd : de +11 éléments repeints
       sur la console d'alarmes à +83 sur l'explorateur Patrimoine, soit 2 à 13 %
       selon l'écran. Moins que ce que le décompte d'emplois laissait croire, mais
       ces surfaces-là étaient sinon hors de portée définitivement.
       Le câblage est un NO-OP à valeur constante, vérifié par relevé A/B sur les
       9 propriétés de couleur des 1272 éléments de la page d'accueil : 0 écart. */
    --primary-color: var(--kd-primary);              /* était #8EB937 = --kd-primary */
    /* ⚠️ SEULE DE CES CINQ À NE PLUS ÊTRE UN NO-OP depuis le 12/09/2026 : --kd-primary-border
       est passé de son littéral d'usine à la dérivation par règle, donc cette héritée suit
       de #7BB224 à #80A732. Ses 2 emplois sont la couleur du lien survolé/focalisé (app.css)
       et l'icône de l'état vide des widgets d'alarmes. */
    --secondary-color: var(--kd-primary-border);     /* était #7BB224, vaut désormais #80A732 */
    --page-bg-color: var(--kd-page-bg);              /* était #f4f4f4 */
    --gray-color: var(--kd-text-muted);              /* était #909298 */
    --lightgray-color: var(--kd-border);             /* était #E3E4E4 */

    /* ✔ DÉCIDÉ (18/08/2026) : --blue-color reste INDÉPENDANTE et HORS du périmètre
       de l'habillage client. Elle vaut #2D313C quand --kd-nav-bg vaut #2b303b : les
       deux teintes sombres diffèrent réellement, les confondre changerait le rendu.
       L'enjeu est faible — 2 emplois, tous deux dans AlarmDetails.razor.css. La
       conséquence assumée : ces deux surfaces ne suivront pas un habillage client. */
    --blue-color: #2D313C;

    /* ⚠️ UN TRIPLET RESTE UN LITTÉRAL EN BOUT DE CHAÎNE : CSS ne sait pas décomposer
       un hexadécimal en composantes, c'est donc au serveur de poser la couleur ET son
       triplet (comme pour --kd-primary-rgb). Ce qui a changé au lot B (12/09/2026) :
       le triplet a désormais son jeton --kd-*, et ces deux-là s'y branchent au lieu de
       porter une seconde fois la même valeur. Sans ce câblage, un habillage repeignant
       --kd-border laissait le zébrage, le survol et la ligne focalisée des grilles
       DevExpress au gris d'origine — 10 emplois, 6 + 4, qui pèsent le plus gros
       gisement de couleur du produit. Valeurs inchangées : voir les jetons. */
    --gray-color-rgb: var(--kd-text-muted-rgb);        /* était 144, 146, 152 */
    --lightgray-color-rgb: var(--kd-border-rgb);       /* était 227, 228, 228, cf. coquille 2 */
}

/*------------------------------------------------------------------
KD Cockpit — couche de tokens
Pilote Bootstrap 5.3 par variables plutôt que par surcharges de sélecteurs.

POURQUOI CE FICHIER
Le socle historique (thème « Pages v6 ») réécrivait .btn, .card, .form-control…
à coups de sélecteurs, en luttant contre Bootstrap sur son propre terrain.
Bootstrap 5.3 expose des variables PAR COMPOSANT (--bs-card-*, --bs-btn-*…) :
régler la variable obtient le même résultat sans entrer en guerre de
spécificité, et sans casser les composants DevExpress, dont le thème
« bootstrap-external » lit les mêmes variables.

ORDRE DE CHARGEMENT
bootstrap.min.css → devexpress → **tokens.css** → app.css → CSS scopé.
theme.css a disparu ; les sections qui en viennent vivent désormais EN TÊTE
d'app.css (§§ 43-44-45), ce qui leur conserve l'arbitrage qu'elles avaient.

MODE SOMBRE
Tout est déclaré sous `:root, [data-bs-theme="light"]`. Le sombre n'est donc
pas une réécriture mais un bloc `[data-bs-theme="dark"]` à ajouter, qui
redéfinit les mêmes noms. Rien n'est câblé aujourd'hui (hors périmètre) —
seule l'écriture est préparée.

RÈGLE D'USAGE
Les --kd-* sont la source de vérité de la marque. Les --bs-* ne font que s'y
brancher. Ne jamais écrire une couleur en dur dans app.css ou un .razor.css :
référencer le token.

HABILLAGE PAR CLIENT (évolution prévue)
Ce fichier est le point d'extension d'une personnalisation par client : une
feuille chargée APRÈS lui, redéfinissant les seuls --kd-*, rehabille toute
l'application sans toucher un sélecteur. D'où la discipline ci-dessus — une
couleur écrite en dur quelque part est une couleur qu'un client ne pourra pas
changer. Les --kd-* doivent donc rester exhaustifs.
Forme retenue (18/08/2026) : une PAGE DE CONFIGURATION dans l'application, qui
écrit un bloc <style> après cette feuille avec les seuls jetons modifiés — et non
un artefact de build par module. ⚠️ Prérequis : les variables héritées en tête de
ce fichier (--primary-color, --gray-color, --page-bg-color…) sont déclarées
INDÉPENDAMMENT de leur jeton --kd-* équivalent, à la même valeur. Un override des
seuls --kd-* ne repeindrait donc qu'une partie de l'application : sur le primaire,
49 emplois suivraient et 57 non. Cf. docs/architecture/habillage-client.md.
-------------------------------------------------------------------*/

:root,
[data-bs-theme="light"] {

    /*--- Marque ----------------------------------------------------------*/
    --kd-primary: #8eb937;              /* vert Kardham — actions principales */

    /* ⚠️ LITTÉRAL, et pour de bon : CSS ne sait pas décomposer un hexadécimal en
       composantes. C'est au serveur de poser le triplet en même temps que la
       couleur. Consommé par --kd-hover, l'anneau de focus des boutons et le
       survol des tables. */
    --kd-primary-rgb: 142, 185, 55;

    /* État pressé. DÉRIVÉ : 80 % du primaire mêlés à du noir. L'hexadécimal qui
       précède vaut EXACTEMENT le mélange — 142 / 185 / 55 × 0,8 = 113,6 / 148 / 44,
       soit #72942c, la valeur littérale d'avant (le 0,4 du canal rouge disparaît à la
       rastérisation). Dérivé, il suit un primaire client sans que personne ait à le
       recalculer.
       ⚠️ L'hexadécimal n'est PAS un repli exécutable : cf. la note du bloc « voiles du
       menu » plus bas — sur une propriété personnalisée, la déclaration color-mix()
       gagne toujours, y compris sur un moteur qui ne sait pas la calculer. Il documente
       la valeur d'usine, rien de plus. */
    --kd-primary-dark: #72942c;
    --kd-primary-dark: color-mix(in srgb, var(--kd-primary) 80%, black);

    /* Bordure des boutons pleins. DÉRIVÉE depuis le 12/09/2026 : 90 % du primaire mêlés
       à du noir, EXACTEMENT la règle que le serveur applique quand l'administrateur pose
       une couleur.
       ⚠️ LA VALEUR D'USINE CHANGE : #7bb224 → #80a732 (142/185/55 × 0,9 = 127,8 / 166,5 /
       49,5, arrondi au plus proche). L'ancienne était une couleur choisie À LA MAIN —
       0,866 / 0,962 / 0,654 selon le canal rapportée au primaire, donc aucun
       assombrissement, donc irreproductible par une règle. La garder condamnait le jeton
       à DEUX VÉRITÉS : l'aperçu de l'écran d'apparence, qui applique la règle, montrait
       #80a732 là où un retour à l'usine rendait #7bb224. On aligne l'usine sur la règle
       plutôt que d'entretenir l'écart, et le serveur n'a plus ce jeton à émettre.
       ⚠️ AUCUN LITTÉRAL AU-DESSUS, à dessein — cf. la note du bloc « voiles du menu » :
       sur une propriété personnalisée il ne serait pas un repli mais un doublon muet, la
       déclaration color-mix() gagnant toujours à l'analyse. La valeur d'usine se lit
       donc ici, en commentaire, et nulle part ailleurs. */
    --kd-primary-border: color-mix(in srgb, var(--kd-primary) 90%, black);

    /* Couleur du TEXTE posé SUR le primaire — bouton plein, pastille, entrée active
       d'un menu déroulant, onglet en pilule.
       DEUX CONVENTIONS circulaient, et c'est la majoritaire qui est retenue pour ne
       rien déplacer : `#fff`, rendu par 15 blocs (tout .btn-primary et ses états,
       .btn-outline-primary survolé, --bs-dropdown-link-active-color,
       --bs-nav-pills-link-active-color, la console d'alarmes, trois widgets), contre
       `var(--kd-primary-text)` dans 3 règles du rail d'outils.
       ⚠️ Ces 3 règles GARDENT --kd-primary-text : ce jeton-là est le vert très sombre
       destiné aux fonds PÂLES (pastilles bg-primary-subtle), et les basculer ici les
       ferait passer de #394a16 à du blanc — ce ne serait pas un no-op.
       ⚠️ L'usine est elle-même en échec de contraste : blanc sur #8eb937 = 2,3:1.
       C'est la raison pour laquelle ce jeton SE SAISIT dans l'écran d'apparence, avec
       son rapport de contraste en regard, au lieu d'être dérivé du primaire. */
    --kd-on-primary: #ffffff;

    /* Couleur SECONDAIRE. Exposée TELLE QUELLE à l'administrateur (décision du
       12/09/2026) : on ne lui invente pas de doctrine, c'est lui qui en dispose, et
       elle vaut le primaire tant qu'il ne la change pas. Proposition maison, à figer
       dans la doc fonctionnelle : le primaire pour les ACTIONS (boutons,
       interrupteurs), le secondaire pour ce qui est SÉLECTIONNÉ.
       ⚠️ À ne pas confondre avec --secondary-color, du bloc hérité en tête de fichier :
       celle-là pointe la bordure des boutons pleins et ne porte pas ce sens.
       Le triplet suit la même règle que --kd-primary-rgb : posé par le serveur. */
    --kd-accent: var(--kd-primary);
    --kd-accent-rgb: var(--kd-primary-rgb);

    /*--- Logo -------------------------------------------------------------
      Un seul emplacement depuis que la barre supérieure a disparu (10/09/2026) :
      la boîte de menu, sur fond sombre — dépliée, elle montre le logo complet ;
      repliée, le picto ci-dessous. Les trois jetons --kd-logo-topbar* ont été
      retirés au lot B, en même temps que la règle .kd-logo--topbar d'app.css qui
      les lisait encore sans qu'aucun markup ne porte la classe.

      Hauteurs figées volontairement : sans elles, l'élément qui porte le logo
      n'aurait aucune hauteur intrinsèque — un background-image ne pousse pas
      son conteneur, contrairement à un <img>. Les valeurs respectent le ratio
      des fichiers (500x60 et 200x19) ; changer l'un sans l'autre déforme.

      Pour rehabiller un client : redéfinir ces variables dans une feuille
      chargée après celle-ci. Aucun markup à toucher. */
    --kd-logo-sidebar: url("/assets/img/kd_cockpit-500px.png");
    --kd-logo-sidebar-width: 140px;
    --kd-logo-sidebar-height: 17px;     /* 140 / 8.333 */

    /* Marque réduite, pour la sidebar repliée : 140 px de logo ne tiennent pas
       dans une boîte de 72 px, et le rogner donnerait un fragment illisible. */
    --kd-logo-collapsed: url("/assets/img/kd_cockpit-picto.png");

    /*--- Shell « boxed » ---------------------------------------------------
      La sidebar est une BOÎTE DÉTACHÉE : gouttière tout autour, coins
      arrondis, ombre portée. Repliée, elle reste une boîte étroite — elle ne
      glisse plus hors de l'écran comme dans l'ancien thème.

      UNE SEULE VARIABLE PILOTE L'ÉTAT : --kd-sidebar-w. Elle vaut la largeur
      repliée par défaut, et body.menu-pin la passe à la largeur dépliée (voir
      plus bas). Tout le reste s'en déduit par calcul : décalage du contenu,
      position du viewer 3D, largeur des offcanvas plein écran. Changer d'état
      = changer une variable ; aucun sélecteur n'a besoin de le savoir.

      L'ancien mécanisme faisait exactement l'inverse : une boîte de 280 px
      figée à left:-210px, ramenée par un transform de +210 px déclaré en
      !important à DEUX endroits, plus un transform en ligne posé par le JS au
      survol. Trois écritures concurrentes de la même position. */
    /* ⚠️ CE N'EST PLUS LA GOUTTIÈRE AUTOUR DES BOÎTES DU SHELL — il n'y en a plus.
       Les surfaces (menu, rail des explorateurs, rail d'outils) sont BORD À BORD :
       elles partent du bord de la fenêtre et vont d'un bord à l'autre en hauteur.
       Ce qui reste est le RETRAIT DE LA ZONE DE CONTENU : ce que `.content` laisse
       respirer entre le menu, le rail d'outils et les cartes d'une page ordinaire
       (app.css § 28). Les écrans à rail l'annulent en se déclarant `.kd-bleed`.
       Il sert aussi d'espacement interne là où deux éléments du shell se côtoient
       (le panneau d'outils et son rail). */
    --kd-shell-gap: 10px;
    /* ⚠️ `--kd-shell-radius` (14 px) A ÉTÉ SUPPRIMÉ, et non laissé sans emploi. Le
       shell est bord à bord : ni le menu, ni le cadre des explorateurs, ni le
       panneau d'outils n'ont d'arrondi — un rayon contre un bord de fenêtre ou
       contre une surface voisine creuse une encoche. Plus aucune règle ne le lisait
       (vérifié sur tout le dépôt, modules compris) ; un jeton que personne ne
       consomme laisse croire qu'il pilote quelque chose.
       La feuille du mobile, elle, garde son propre rayon de 18 px en dur : c'est
       une valeur de cette feuille-là, pas du shell. */
    --kd-sidebar-w-collapsed: 72px;
    --kd-sidebar-w-expanded: 260px;
    --kd-sidebar-w: var(--kd-sidebar-w-collapsed);

    /* Hauteur de l'en-tête de la boîte de menu. Elle donne l'ordonnée de la
       pastille de bascule, et elle vaut la hauteur de la barre supérieure : les
       deux boîtes commencent à la même gouttière, donc le logo et le titre de
       l'écran s'alignent sur la même ligne. */
    --kd-sidebar-header-h: var(--kd-header-h);

    /* Hauteur de la BARRE SUPÉRIEURE. Elle avait été créée parce que le nombre était
       recopié en dur à six endroits qui ne se parlaient pas : le padding-top du
       contenu, le top du viewer 3D, .card-maximized, la barre de chargement héritée,
       le tiroir de sonde Pictet, et — implicitement — les classes h-100vh*.
       ⚠️ CE N'EST PLUS SON RÔLE. La barre supérieure a disparu le 10/09/2026, la
       barre de chargement héritée avec le lot A (commit 355441b2, 12/09), et il ne
       reste à cette variable qu'UN SEUL consommateur : --kd-sidebar-header-h, juste
       au-dessus, à qui elle donne la hauteur de l'en-tête de la boîte de menu. Elle
       n'est donc plus conservée que comme la constante de cette hauteur-là ; le jour
       où on la renomme, il n'y a qu'une ligne à suivre. */
    --kd-header-h: 48px;

    /* Où commence le contenu. Ce n'était plus qu'une seule gouttière depuis que
       LA BARRE SUPÉRIEURE A DISPARU : le titre de l'écran est descendu dans la
       colonne de contenu et défile avec elle, il ne réserve donc plus rien en
       permanence. La valeur passe de 68 px (gouttière + barre + gouttière) à 10 —
       68 px rendus au contenu sur les ~74 écrans.

       ⚠️ ELLE RESTE LIÉE À h-100vh-withheader, qui vaut calc(100vh - celle-ci).
       L'invariant est que le rembourrage haut de .content ÉGALE ce qui est
       retranché : sans quoi la colonne dépasse ou flotte de la différence.

       Elle est dérivée d'une constante qui ne dépend d'aucune classe d'état, elle
       peut donc vivre sur :root (contrairement à --kd-shell-offset, cf.
       l'avertissement plus haut). */
    --kd-content-top: var(--kd-shell-gap);

    /* Le retrait GAUCHE du contenu, détaché de la gouttière le 14/09/2026 : à 10 px
       les cartes restaient trop près du menu (demande de Jérémy). C'est le seul côté
       qui borde une surface pleine hauteur ; le haut et la droite gardent
       --kd-shell-gap. 20 px = l'écart entre deux cartes empilées (`.card`,
       margin-bottom).
       Consommé par le rembourrage de .content, l'écart entre la colonne des réglages
       et leur contenu, et le `left` de .card-maximized.
       ⚠️ Ramené à la gouttière sous 576 px et à l'impression (app.css) : le menu n'y
       borde plus le contenu, et le retrait redevient symétrique de la droite. */
    --kd-content-left: 20px;

    /* RAIL D'OUTILS — la troisième boîte du shell, à droite. Même largeur que le
       rail de navigation des explorateurs (.railnav-tabs en colonne, 60 px) : c'est la même
       primitive, un rail d'icônes, et deux largeurs voisines mais distinctes se
       verraient sur un écran qui porte les deux (les explorateurs, justement).
       --kd-tools-offset est le MIROIR EXACT de --kd-shell-offset côté droit, et
       depuis que le shell est bord à bord il vaut, comme lui, la seule largeur de
       la surface. C'est lui que consomment le rembourrage du contenu,
       .card-maximized et l'hôte du viewer 3D — plus aucun `right` ne se compte
       depuis le bord de la fenêtre.
       ⚠️ Il est redéfini à 0 sous 576 px (app.css, régime mobile), où le rail cède
       la place à un bouton flottant qui ne prend aucune largeur. Sans cette
       redéfinition le contenu garderait 60 px de vide à droite sur un téléphone. */
    --kd-tools-w: 60px;
    --kd-tools-offset: var(--kd-tools-w);

    /* Pastille de bascule replié/déplié. Elle est le SEUL moyen de déplier le
       menu depuis le mode replié : le survol n'ouvre plus rien (deux états
       francs, pas trois). D'où une cible toujours visible, posée à cheval sur
       le bord droit de la boîte.
       L'avancée reste inférieure à la gouttière (10 px) : la pastille occupe
       l'espace libre entre la boîte et le contenu, sans jamais le recouvrir. */
    --kd-sidebar-toggle-size: 26px;

    /* Retrait de la pastille depuis le bord DROIT de la boîte, dépliée seulement :
       elle s'y range à droite de l'en-tête, sur la ligne du logo.
       ⚠️ `--kd-sidebar-toggle-overhang` (9 px) a été SUPPRIMÉ. Repliée, la pastille
       dépassait du bord droit faute de place — un rail de 72 px n'accueillait pas
       à la fois le picto de marque et une pastille. Elle PREND maintenant LA PLACE
       DU LOGO, centrée, et ne dépasse plus : c'est ce qui permet au rail des
       explorateurs de venir se coller au menu, et ce qui a fait tomber le terme de
       débord de --kd-shell-offset. */
    --kd-sidebar-toggle-inset: -12px;

    /* Largeur de l'ascenseur du CONTENU (.page-content-wrapper). Publiée par
       theme.js — le CSS ne peut pas la connaître : la barre supérieure est posée
       EN DEHORS du conteneur qui défile, et aucune propriété ne donne la largeur
       d'ascenseur d'un frère. Repli à 0 : si le script ne tourne pas, on retombe
       exactement sur le comportement d'avant, sans décalage surprise.
       Vaut 0 pour de bon avec les ascenseurs en surimpression (macOS), ce qu'une
       valeur codée en dur ne saurait pas rendre. */
    --kd-scrollbar-w: 0px;

    /* Hauteur d'une ligne de menu. Elle sert DEUX FOIS : hauteur minimale du
       lien, et hauteur de la colonne d'icône. Les deux doivent rester égales —
       l'icône est posée en absolu sur un <li> qui, lui, s'étire à la hauteur de
       la branche ouverte : calée sur top/bottom, elle se centrait au milieu de
       tout le sous-arbre au lieu de sa propre ligne. */
    --kd-nav-item-h: 44px;
    --kd-nav-subitem-h: 36px;

    /* Taille des icônes de NAVIGATION des onglets des explorateurs (RailNav). Elle
       réglait aussi le menu de gauche jusqu'au 14/09/2026 : des valeurs écrites
       séparément avaient divergé — 16 px au menu, 17 en bande et 21 en colonne dans
       le rail des explorateurs — et les onglets, juste à côté du menu, pesaient
       visiblement plus lourd que lui. Le menu a désormais ses jetons (ci-dessous). */
    --kd-nav-icon: 16px;

    /* Icônes et libellés du MENU DE GAUCHE, un cran au-dessus des 16 / 12 px qu'ils
       partageaient avec le reste (demande de Jérémy, 14/09/2026 : « augmente d'un
       cran les icônes et textes du menu gauche »). Des jetons à part, comme
       --kd-tool-icon, pour que l'écart avec les onglets des explorateurs reste un
       choix déclaré. Les libellés n'avaient aucune taille propre : ils héritaient
       des 12 px du body. Sous-menus compris — les deux niveaux étaient déjà égaux. */
    --kd-menu-icon: 18px;
    --kd-menu-font: 13px;

    /* Taille des icônes du RAIL D'OUTILS (panier, alarmes, QR…). Il lisait
       --kd-nav-icon jusqu'au 13/09/2026, et c'était trop peu : là où le menu pose
       son icône sur une ligne à côté d'un libellé, le rail n'a QUE l'icône, seule
       dans une pastille de 40 px — 16 px y flottaient (demande de Jérémy :
       « augmente la police des icônes du rail de droite »). Un jeton à part, et
       non une valeur en dur, pour que l'écart reste un choix déclaré et ne
       redevienne pas une dérive. */
    --kd-tool-icon: 20px;

    /* ⚠️ --kd-shell-offset et les ponts de compatibilité NE sont PAS déclarés
       ici mais sur `body` (plus bas). Une variable dérivée est résolue avec la
       valeur que sa source a SUR LE MÊME ÉLÉMENT : déclarée sur :root, elle
       fige la largeur repliée, et `body.menu-pin` redéfinissant --kd-sidebar-w
       ne la met plus à jour. Erreur commise puis mesurée : le menu épinglé
       s'élargissait sans que le contenu se décale. */

    /* Une seule durée pour tout le mouvement du shell. L'ancien en avait
       trois désaccordées (400 ms sidebar, 300 ms contenu, 250 ms mobile) :
       le contenu arrivait avant le menu. */
    --kd-shell-transition: 300ms cubic-bezier(.4, 0, .2, 1);

    --kd-shell-shadow: 0 4px 24px rgba(15, 18, 26, .18);

    /* LE VOILE DES SURFACES EN SURIMPRESSION — ce qui flotte au-dessus d'un plan
       ou d'une maquette : le panneau et la bande des explorateurs (RailNav), le
       rail d'outils et son tiroir, le drawer d'aperçu (asset / espace). Ils
       doivent se lire comme UNE matière : la même recette était recopiée à la
       main dans deux feuilles (RailNav.razor.css, app.css), et le drawer, oublié, restait opaque à côté d'un
       panneau translucide (relevé le 13/09/2026).
       Deux jetons et non une couleur toute faite : le fond se compose chez chaque
       consommateur — color-mix(in srgb, <sa surface> var(--kd-veil-alpha),
       transparent) — parce que chacun a SA surface (--kd-surface, --kd-tools-bg),
       et qu'une couleur figée ici serait résolue sur :root, sourde à tout
       habillage redéfini plus bas dans l'arbre.
       Le flou fait la transparence LISIBLE : sans lui, les traits du plan
       passeraient sous le texte aussi nets que lui. */
    --kd-veil-alpha: 84%;
    --kd-veil-filter: blur(10px) saturate(1.1);

    /*--- Chrome sombre (sidebar, en-têtes) -------------------------------*/
    --kd-nav-bg: #2b303b;
    /* ⚠️ ÉTAT RÉEL APRÈS LE LOT B, relevé et non annoncé :
       --kd-nav-header-bg n'a TOUJOURS aucun consommateur, et n'en aura pas : l'en-tête
       de la boîte de menu est rendu TRANSPARENT à dessein (app.css § 28 — un fond propre
       dessinerait des angles vifs dans les coins arrondis de la boîte). Conservée comme
       simple déclaration de la teinte, à ne pas prendre pour un levier.
       --kd-nav-border, elle, EST consommée depuis le lot B (app.css l. 625, le filet sous
       l'en-tête) — mais cette déclaration est MASQUÉE par le § 28 l. 1879, qui repose
       border-bottom-color avec --kd-nav-header-rule. Le jeton ne pilotera donc le filet
       que le jour où cette règle du § 28 disparaîtra. */
    --kd-nav-header-bg: #272b35;
    --kd-nav-border: #232730;
    --kd-nav-fg: #929aac;
    --kd-nav-fg-active: #ffffff;

    /* LES VOILES DU MENU — pilules, filets, pastille de bascule.
       Ils étaient écrits en `rgba(255, 255, 255, …)` en dur dans app.css, ce qui
       collait le menu à un fond SOMBRE pour toujours : sur un menu clair, l'élément
       actif et le survol seraient blanc sur blanc. Ils DÉRIVENT donc de la couleur de
       texte active, qui est la seule chose que l'administrateur saisit — poser un
       menu clair repeint les voiles tout seul, et le serveur n'a pas à les émettre.

       --kd-nav-fg-active valant #ffffff, `color-mix(in srgb, … N%, transparent)` redonne
       EXACTEMENT `rgba(255, 255, 255, .0N)` : le mélange se fait en alpha prémultipliée,
       donc α = N/100 et les canaux restent ceux du blanc. Vérifié jeton par jeton.
       Chaque correspondance a été relevée dans app.css, règle par règle — aucune valeur
       n'est arrondie ni unifiée : les deux filets diffèrent réellement de 1 % (.06 et
       .07), et les rapprocher changerait le rendu.

       ⚠️ LE LITTÉRAL QUI PRÉCÈDE N'EST PAS UN REPLI, contrairement à ce qu'on lit ailleurs
       dans ce fichier : une propriété PERSONNALISÉE accepte n'importe quelle suite de
       jetons à l'analyse, donc la déclaration color-mix() est toujours valide à ce stade
       et gagne par ordre de cascade — y compris sur un moteur qui ignore color-mix().
       L'échec, s'il survient, arrive au CALCUL, chez le consommateur : `background-color`
       retombe alors sur sa valeur initiale (transparent), pas sur le littéral. Le littéral
       ne vaut donc que comme DOCUMENTATION de la valeur d'usine. Un vrai repli demanderait
       un bloc @supports. Sans conséquence sur les navigateurs visés (color-mix : Chrome et
       Edge 111, Firefox 113, Safari 16.2 — tous 2023). Même remarque pour --kd-primary-dark,
       --kd-danger-bg, --kd-warning-bg, --bs-primary-border-subtle et les cinq --kd-*-text. */
    --kd-nav-pill-bg-hover: rgba(255, 255, 255, .08);
    --kd-nav-pill-bg-hover: color-mix(in srgb, var(--kd-nav-fg-active) 8%, transparent);
    --kd-nav-pill-bg-active: rgba(255, 255, 255, .12);
    --kd-nav-pill-bg-active: color-mix(in srgb, var(--kd-nav-fg-active) 12%, transparent);

    /* Le déclencheur du compte, au pied de la boîte : un voile à lui, un cran
       au-dessus du survol d'une entrée de menu. */
    --kd-nav-account-bg-hover: rgba(255, 255, 255, .10);
    --kd-nav-account-bg-hover: color-mix(in srgb, var(--kd-nav-fg-active) 10%, transparent);

    /* Filets de séparation : sous l'en-tête, au-dessus du pied. */
    --kd-nav-header-rule: rgba(255, 255, 255, .06);
    --kd-nav-header-rule: color-mix(in srgb, var(--kd-nav-fg-active) 6%, transparent);
    --kd-nav-footer-rule: rgba(255, 255, 255, .07);
    --kd-nav-footer-rule: color-mix(in srgb, var(--kd-nav-fg-active) 7%, transparent);

    /* Pastille de bascule replié/déplié. Elle est TOUJOURS posée sur le fond du menu
       depuis qu'elle est rentrée dans la boîte : ses quatre voiles suivent donc le
       même texte que le reste. */
    --kd-nav-toggle-bg: rgba(255, 255, 255, .08);
    --kd-nav-toggle-bg: color-mix(in srgb, var(--kd-nav-fg-active) 8%, transparent);
    --kd-nav-toggle-bg-hover: rgba(255, 255, 255, .18);
    --kd-nav-toggle-bg-hover: color-mix(in srgb, var(--kd-nav-fg-active) 18%, transparent);
    --kd-nav-toggle-border: rgba(255, 255, 255, .14);
    --kd-nav-toggle-border: color-mix(in srgb, var(--kd-nav-fg-active) 14%, transparent);
    --kd-nav-toggle-border-hover: rgba(255, 255, 255, .24);
    --kd-nav-toggle-border-hover: color-mix(in srgb, var(--kd-nav-fg-active) 24%, transparent);

    /* PAS de --kd-nav-fg-rgb : aucune règle du dépôt ne consomme le gris des libellés
       en composantes (vérifié — ni `rgba(146, 154, 172, …)` ni `#929aac` ailleurs que
       dans cette déclaration et celle d'--kd-info). Un jeton sans consommateur laisse
       croire qu'il pilote quelque chose. À créer le jour où une règle en a besoin. */

    /* Pastille du compte, au pied du menu. Volontairement neutre et non pas
       verte : le vert de marque signale une ACTION, et un avatar n'en est pas
       une. Un habillage client peut la redéfinir sans toucher au reste. */
    --kd-avatar-bg: #cfd6e0;
    --kd-avatar-fg: #3d4553;

    /*--- Sémantique ------------------------------------------------------*/
    --kd-success: #19ad79;
    --kd-warning: #ffd945;
    --kd-danger: #d83c31;
    --kd-info: #929aac;

    /* Couleur de TEXTE de chaque teinte sémantique. Une teinte suffisamment
       saturée pour un FOND ne l'est jamais pour du TEXTE : sur #19ad79 en
       texte, le contraste sur blanc tombe à 2,9:1. Le thème acheté tenait donc
       deux valeurs par teinte, mais en dur dans theme.css — donc hors de portée
       d'un habillage client.
       Ces quatre jetons rétablissent la seconde valeur, DÉRIVÉE PAR RÈGLE et
       non choisie à la main : c'est la « shade 60 % » de Bootstrap 5.3, celle
       qui définit --bs-*-text-emphasis. Un client qui change --kd-danger
       obtient donc automatiquement la couleur de texte assortie.
       Pourquoi 60 % et pas moins : le jaune est la contrainte. À 30 % de
       noircissement --kd-warning est encore à 2,5:1 ; il faut aller à ~50 %
       pour franchir 4,5:1, et 60 % laisse de la marge pour une teinte client
       plus pâle encore.
       Contrastes mesurés APRÈS (blanc / --kd-page-bg / le bg-*-subtle assorti,
       les trois fonds réels de ces textes dans Cockpit) :
           success  11,0  9,9  8,4      (avant : 4,1  3,7  3,2  — échec AA)
           warning   7,1  6,4  6,4      (avant : 4,5  4,0  4,0  — échec AA)
           danger   13,7 12,3 10,2      (avant : 6,1  5,5  4,6)
           info     10,7  9,7  9,1      (avant : 9,5  8,5  8,1)
       Le hex qui précède chaque color-mix() vaut exactement le résultat du mélange.
       ⚠️ Il ne REMPLACE toutefois rien à l'exécution : une propriété personnalisée
       accepte n'importe quelle suite de jetons à l'analyse, donc la déclaration
       color-mix() gagne par ordre de cascade même sur un moteur qui ne sait pas la
       calculer, et c'est chez le consommateur que `color: var(--kd-success-text)`
       s'effondrerait alors sur la couleur héritée. Cf. la note du bloc « voiles du
       menu ». Sans conséquence sur les navigateurs visés (color-mix : 2023). */
    /* Le primaire suit la même règle, mais pour une raison de plus : contrairement
       aux quatre autres, sa couleur de texte n'avait JAMAIS été assombrie —
       `.text-primary` sert le vert de marque tel quel. Sur le fond pâle d'une
       pastille cela donnait 1,75:1 (mesuré), soit un texte illisible sur les
       porteurs de `badge bg-primary-subtle`. À 8,4:1 après.
       ⚠️ Ce jeton ne redéfinit PAS `.text-primary`, qui garde le vert vif sur ses
       26 porteurs (icônes, liens, éléments sélectionnés). Il alimente
       --bs-primary-text-emphasis, et ce sont les PASTILLES qui ont basculé sur
       `text-primary-emphasis` — l'appariement que Bootstrap prévoit avec
       `bg-*-subtle`. */
    --kd-primary-text: #394a16;
    --kd-primary-text: color-mix(in srgb, var(--kd-primary) 40%, black);
    --kd-success-text: #0a4530;
    --kd-success-text: color-mix(in srgb, var(--kd-success) 40%, black);
    --kd-warning-text: #66571c;
    --kd-warning-text: color-mix(in srgb, var(--kd-warning) 40%, black);
    --kd-danger-text: #561814;
    --kd-danger-text: color-mix(in srgb, var(--kd-danger) 40%, black);
    --kd-info-text: #3a3e45;
    --kd-info-text: color-mix(in srgb, var(--kd-info) 40%, black);

    /* Fond d'un état survolé destructeur (déconnexion, suppression). Assez pâle
       pour rester lisible en texte rouge par-dessus. */
    --kd-danger-soft: rgba(216, 60, 49, .08);

    /* ⚠️ CES TROIS JETONS CORRIGENT UN BUG, ce n'est donc PAS un no-op de rendu.
       Ils sont consommés depuis longtemps par l'onglet Qualité de l'air
       (ReportsCenter/WebReports/AirQualityComfort/Tabs/TabOverview.razor, pastilles
       de dépassement de seuil) SANS ÊTRE DÉCLARÉS NULLE PART : `var()` sur une
       variable inexistante rend la déclaration invalide, les fonds ne s'appliquaient
       pas et le texte orange retombait sur la couleur héritée (#4b4b4b).

       Intention relevée dans le markup : la pastille « pas de dépassement » porte
       déjà `rgba(var(--gray-color-rgb), 0.08)` en fond et `var(--bs-secondary)` en
       texte ; celle qui alerte doit être la même chose dans sa teinte. D'où la même
       forme et la même dose que --kd-danger-soft ci-dessus, mais DÉRIVÉE du jeton
       pour suivre un habillage (littéral en repli, valeur identique au mélange :
       --kd-danger #d83c31 = 216, 60, 49 et --kd-warning #ffd945 = 255, 217, 69). */
    --kd-danger-bg: rgba(216, 60, 49, .08);
    --kd-danger-bg: color-mix(in srgb, var(--kd-danger) 8%, transparent);
    --kd-warning-bg: rgba(255, 217, 69, .08);
    --kd-warning-bg: color-mix(in srgb, var(--kd-warning) 8%, transparent);

    /* L'orange de l'écran, distinct du rouge pour que « alerte » et « dépassement »
       ne se confondent pas dans la même rangée de pastilles. Valeur MESURÉE sur le
       même écran, qui l'emploie déjà en dur pour la colonne P90 de ses tableaux
       (MetricTabBase.razor.cs) : #fd7e14.
       ⚠️ 2,9:1 sur son fond pâle — sous le seuil AA, comme le reste de la famille
       orange. Retenu quand même parce que l'alternative (--kd-warning-text, olive)
       cesserait de se lire comme un orange et se confondrait avec le texte courant.
       À arbitrer au lot des primitives, pas ici. */
    --kd-orange: #fd7e14;

    /*--- Neutres et surfaces ---------------------------------------------*/
    --kd-page-bg: #f4f4f4;
    --kd-surface: #ffffff;

    /* Fond du RAIL D'OUTILS et de son tiroir — la troisième boîte du shell, à droite.
       Il vaut la surface des cartes, comme aujourd'hui (app.css § 46 :
       `.kd-tools-rail` et `.kd-tools-panel` peignent l'un et l'autre --kd-surface),
       mais il porte enfin son propre nom : sans lui, changer le fond du rail
       changeait AUSSI toutes les cartes, modales et listes déroulantes. */
    --kd-tools-bg: var(--kd-surface);

    --kd-text: #4b4b4b;                 /* corps de texte */
    --kd-text-strong: #212121;          /* saisie, valeurs */
    --kd-text-muted: #909298;
    /* Le même gris en composantes, pour les `rgba(…, α)` qui en ont besoin (grilles
       DevExpress, pastilles neutres). Littéral obligé : CSS ne décompose pas un
       hexadécimal — le serveur pose les deux. #909298 = 144, 146, 152. */
    --kd-text-muted-rgb: 144, 146, 152;

    --kd-border: #e3e4e4;
    /* Idem pour la bordure : c'est ce triplet qui porte l'en-tête, le zébrage, le
       survol et la ligne focalisée de TOUTES les grilles DevExpress — le plus gros
       gisement de couleur du produit. #e3e4e4 = 227, 228, 228.
       ⚠️ C'est désormais la source unique : --lightgray-color-rgb (bloc hérité en tête
       de fichier) s'y branche au lieu de porter une seconde fois la même valeur.
       Même chose pour --gray-color-rgb vers --kd-text-muted-rgb. */
    --kd-border-rgb: 227, 228, 228;
    --kd-border-light: #eaeaea;

    /* Survol d'une ligne cliquable dans une surface claire — menus, listes,
       lignes de tableau. Une teinte, pas un gris : elle prend la couleur de la
       marque à très faible dose, ce qui la rend cohérente d'un habillage
       client à l'autre. */
    --kd-hover: rgba(var(--kd-primary-rgb), .08);

    /*--- Palette des explorateurs ----------------------------------------
      Teintes des panneaux d'explorateurs (arbres de types, cases, recherche,
      filtres par seuils) : 2D, 3D, Patrimoine. Valeurs du lot B, INCHANGÉES,
      seulement nommées — aucune ne coïncide avec un jeton --kd-* d'usine (le
      bleu d'accent n'est pas le vert primaire, #1a1a1a n'est pas
      --kd-text-strong) : les y brancher changerait le rendu, en attendant
      l'arbitrage graphique.
      ⚠️ ELLES VIVAIENT EN DEUX COPIES LOCALES, sur .ex-plans-root et .ex-bim-root.
      L'Explorateur Patrimoine, qui partage l'arbre de types du 2D sans être sous
      l'une ou l'autre racine, les lisait INDÉFINIES : `border: 1.5px solid
      var(--ex-control-border)` est alors invalide au calcul, la bordure retombe
      à 0 et la case blanche disparaissait sur le panneau blanc (mesuré le
      14/09/2026). Déclarées ici, elles valent pour tout hôte, présent ou à venir.
      Les satellites de l'accent restent littéraux dans les feuilles (sélections
      #f4f8fd, #eef4ff/#c7ddfb…) : ils resteront bleus si l'accent change. */
    --ex-accent: #1178d8;               /* accent des explorateurs */
    --ex-on-accent: #ffffff;            /* texte/glyphe posé SUR --ex-accent */
    --ex-accent-bg: #e8f4ff;            /* fond d'une ligne sélectionnée */
    --ex-accent-hover-bg: #eef2f7;      /* fond de survol des boutons icône qui virent à l'accent */
    --ex-text: #1a1a1a;                 /* libellé de ligne */
    --ex-text-2: #4c4c5c;               /* en-tête de section, texte secondaire */
    --ex-text-muted: #9499a1;           /* légendes, icônes au repos */
    --ex-text-faint: #aab0b8;           /* compteurs de section */
    --ex-row-hover: #f6f7f9;            /* survol d'une ligne */
    --ex-fill: #f1f2f4;                 /* fond neutre (segment, résumé) et filet de séparation */
    --ex-control-border: #c4c9cf;       /* bordure de case à cocher, icône inactive */
    --ex-partial: #6b7078;              /* état « partiellement coché » */

    /*--- Élévation --------------------------------------------------------*/
    --kd-shadow-card: 0 6px 16px 0 rgba(33, 33, 33, .09), 0 2px 3px 0 rgba(0, 0, 0, .06);
    --kd-shadow-menu: 0 0 0 1px rgba(15, 15, 15, .05), 0 3px 6px 0 rgba(15, 15, 15, .1), 0 9px 24px 0 rgba(15, 15, 15, .2);

    /* Élévation d'un widget d'accueil, distincte de celle d'une carte ordinaire.
       Une carte posée dans une page se détache du fond ; un widget est déjà
       délimité par la trame de GridStack, et vingt ombres alignées font du bruit.
       D'où `none` par défaut.
       C'était d'ailleurs l'intention d'origine : `.grid-stack-item-content
       .card-default` privait les widgets d'ombre, mais le commit defaa90c
       (08/01/2026) a ôté `card-default` du markup de CardExtended et la règle ne
       rencontre plus son porteur depuis — les widgets portaient donc une ombre
       que personne n'avait décidée.
       ⚠️ `none` et non une valeur vide : `box-shadow: var(--kd-shadow-widget)`
       s'effondrerait sur une variable invalide (le repli d'un var() ne rattrape
       pas l'invalidité) et la carte reprendrait… rien, mais par accident.
       Un habillage client qui veut l'ombre pose
       `--kd-shadow-widget: var(--kd-shadow-card)` — le débordement des
       conteneurs GridStack reste ouvert exprès pour ça (app.css § 1). */
    --kd-shadow-widget: none;

    /*--- Typographie ------------------------------------------------------*/
    /* Inter pour le texte, Montserrat pour les titres de carte en capitales.
       Les deux sont HÉBERGÉES LOCALEMENT (assets/fonts/inter/,
       assets/fonts/montserrat/, déclarées dans App.razor) : elles arrivaient
       de Google Fonts par un @import de theme.css, qui rompait le déploiement
       air-gapped. Graisses disponibles : 300 à 600 (police variable). */
    --kd-font-body: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
    --kd-font-title: Montserrat, var(--kd-font-body);

    /* KD Cockpit est une UI dense : 12 px de base, là où Bootstrap vise 16.
       Exprimé en rem pour rester accessible au zoom navigateur. */
    --kd-fs-base: .75rem;               /* 12px */
    --kd-fs-sm: .6875rem;               /* 11px */
    --kd-fs-lg: 1.125rem;               /* 18px */
    --kd-lh-base: 1.6667;               /* 20px sur 12px */
    --kd-tracking-body: .01em;
    --kd-tracking-caps: .06em;          /* titres de carte en capitales */

    /* Échelle de tailles de l'application (utilitaires .fs-10 … .fs-40, cf.
       app.css § 29). Douze crans en pixels, là où Bootstrap n'en propose que
       six et en rem — Cockpit est une UI dense, et ses écarts de 1 px entre
       fs-11 et fs-12 n'ont pas d'équivalent dans l'échelle Bootstrap.

       Elles vivent ICI pour la même raison que les couleurs : un habillage
       client redéfinit ces douze valeurs et toute la densité de l'application
       suit, sans qu'aucun sélecteur ni aucun markup ne bouge.

       ⚠️ NE PAS déclarer --kd-fs-1 à --kd-fs-6 : .fs-1 … .fs-6 appartiennent à
       BOOTSTRAP (tailles de titre en rem). theme.css ne les a jamais définies,
       et 91 usages du markup (fs-4, fs-5, fs-6) s'appuient aujourd'hui sur
       Bootstrap. Les reprendre ici changerait leur rendu. */
    --kd-fs-10: 10px;
    --kd-fs-11: 11px;
    --kd-fs-12: 12px;
    /* fs-13 rend 12 px : ce n'est pas une coquille de transcription mais la
       valeur du thème acheté, sur laquelle 14 écrans se sont réglés à l'œil.
       La « corriger » les décalerait tous. Conservée telle quelle. */
    --kd-fs-13: 12px;
    --kd-fs-14: 14px;
    --kd-fs-15: 15px;
    --kd-fs-16: 16px;
    --kd-fs-18: 18px;
    --kd-fs-20: 20px;
    --kd-fs-24: 24px;
    --kd-fs-30: 30px;
    --kd-fs-40: 40px;

    /*--- Rayons ------------------------------------------------------------*/
    --kd-radius-sm: 2px;                /* champs de saisie */
    --kd-radius: 3px;                   /* boutons, menus déroulants */
    --kd-radius-lg: 8px;                /* cartes */


    /*====================================================================
      Correspondance Bootstrap — à partir d'ici, on ne fait que brancher
      les tokens ci-dessus sur les variables que Bootstrap consomme déjà.
      ====================================================================*/

    /*--- Global ------------------------------------------------------------*/
    --bs-body-font-family: var(--kd-font-body);
    --bs-body-font-size: var(--kd-fs-base);
    --bs-body-line-height: var(--kd-lh-base);
    --bs-body-color: var(--kd-text);
    --bs-body-color-rgb: 75, 75, 75;
    --bs-body-bg: var(--kd-page-bg);
    --bs-body-bg-rgb: 244, 244, 244;

    --bs-primary: var(--kd-primary);
    --bs-primary-rgb: var(--kd-primary-rgb);
    --bs-success: var(--kd-success);
    --bs-success-rgb: 25, 173, 121;
    --bs-warning: var(--kd-warning);
    --bs-warning-rgb: 255, 217, 69;
    --bs-danger: var(--kd-danger);
    --bs-danger-rgb: 216, 60, 49;
    --bs-info: var(--kd-info);
    --bs-info-rgb: 146, 154, 172;

    /* Bootstrap 5.3 nomme lui-même la couleur de texte d'une teinte :
       --bs-*-text-emphasis, exposée par les utilitaires .text-*-emphasis.
       Sans ce câblage, ces utilitaires servaient la palette d'usine de
       Bootstrap et non la nôtre : `text-success-emphasis` mesurait #0a3622,
       dérivé du #198754 de Bootstrap, alors que le vert de Cockpit est
       #19ad79. Visible sur les badges d'état de service
       (ServiceHealthDisplay.cs) et les widgets de santé. */
    --bs-primary-text-emphasis: var(--kd-primary-text);
    --bs-success-text-emphasis: var(--kd-success-text);
    --bs-warning-text-emphasis: var(--kd-warning-text);
    --bs-danger-text-emphasis: var(--kd-danger-text);
    --bs-info-text-emphasis: var(--kd-info-text);

    /* Fonds pâles des pastilles — `badge bg-*-subtle`, 25 porteurs dans les
       Réglages, les onglets de configuration et la console d'alarmes.
       Ils restaient ceux d'USINE de Bootstrap, dérivés de SA palette : un
       `bg-primary-subtle` rendait #cfe2ff, un bleu pâle, alors que le primaire
       de Cockpit est vert. C'est la moitié manquante des couleurs de texte
       ci-dessus — Bootstrap conçoit `bg-*-subtle` et `text-*-emphasis` comme une
       PAIRE, l'un servant de fond à l'autre.
       Même dérivation dans l'autre sens : le « tint 80 % » de Bootstrap, soit
       20 % de la teinte dans du blanc. Repli hexadécimal pour la même raison que
       plus haut.
       Contrastes mesurés, texte d'emphase sur son fond pâle assorti :
           primary 8,4   success 9,0   warning 6,6   danger 10,2   info 9,0
       --bs-secondary-bg-subtle n'est PAS repris : il n'existe pas de
       --kd-secondary (le `.btn-secondary` emprunte --kd-info), et le gris neutre
       d'usine convient à un état « par défaut ». Le reprendre sur --kd-info
       rendrait les pastilles « secondary » et « info » indiscernables.
       ⚠️ En revanche la mesure a montré que cette paire laissée d'usine était la
       SEULE à échouer : `text-secondary` (#6c757d) sur `bg-secondary-subtle`
       donnait 3,65:1. Corrigé sans nouveau jeton, en basculant ses 3 porteurs sur
       `text-secondary-emphasis` — l'appariement que Bootstrap prévoit, et qu'un
       quatrième porteur employait déjà. `text-secondary` reste juste partout
       ailleurs : sur blanc il donne 4,7:1. */
    --bs-primary-bg-subtle: #e8f1d7;
    --bs-primary-bg-subtle: color-mix(in srgb, var(--kd-primary) 20%, white);
    --bs-success-bg-subtle: #d1efe4;
    --bs-success-bg-subtle: color-mix(in srgb, var(--kd-success) 20%, white);
    --bs-warning-bg-subtle: #fff7da;
    --bs-warning-bg-subtle: color-mix(in srgb, var(--kd-warning) 20%, white);
    --bs-danger-bg-subtle: #f7d8d6;
    --bs-danger-bg-subtle: color-mix(in srgb, var(--kd-danger) 20%, white);
    --bs-info-bg-subtle: #e9ebee;
    --bs-info-bg-subtle: color-mix(in srgb, var(--kd-info) 20%, white);

    /* ⚠️ CORRECTION DE RENDU ASSUMÉE — la bordure bleue de l'explorateur BIM.
       La troisième pièce du triptyque de Bootstrap (`bg-*-subtle` en fond,
       `text-*-emphasis` en texte, `border-*-subtle` en bordure) n'était pas reprise :
       --bs-primary-border-subtle servait donc encore #9ec5fe, dérivé du BLEU d'usine
       de Bootstrap. Résultat mesuré dans ExplorerBimRailPanel.razor.css : l'étage
       sélectionné et l'interrupteur multi-étages portaient un fond vert pâle (celui-là
       était bien câblé) cerné d'un liseré BLEU. Troisième porteur : la ligne de
       statistiques des cartes KPI (app.css, § KpiProgress).
       Même dérivation que ses voisines, dans la règle de Bootstrap pour un
       `border-subtle` : le « tint 60 % », soit 40 % de la teinte dans du blanc
       (vérifié sur le bleu d'usine : 13 / 110 / 253 → 158 / 197 / 254 = #9ec5fe).
       Sur le vert de Cockpit : 142 / 185 / 55 → 210 / 227 / 175, soit #d2e3af, qui est
       le repli écrit ci-dessous. Les trois porteurs passent donc du bleu au vert pâle.
       ⚠️ Aucun porteur de l'utilitaire `.border-primary-subtle` ni de `.alert-primary`
       ou `.list-group-item-primary` dans le dépôt : le changement s'arrête à ces trois
       règles, il ne se propage pas par les composants Bootstrap. */
    --bs-primary-border-subtle: #d2e3af;
    --bs-primary-border-subtle: color-mix(in srgb, var(--kd-primary) 40%, white);

    --bs-border-color: var(--kd-border);
    --bs-border-radius-sm: var(--kd-radius-sm);
    --bs-border-radius: var(--kd-radius);
    --bs-border-radius-lg: var(--kd-radius-lg);

    --bs-link-color: var(--kd-primary);
    --bs-link-color-rgb: var(--kd-primary-rgb);
    --bs-link-hover-color: var(--kd-primary-dark);
    --bs-link-decoration: none;

    --bs-emphasis-color: var(--kd-text-strong);

    /* ⚠️ Cette ligne corrige une erreur d'analyse : on avait écrit ici que Bootstrap
       dérivait --bs-secondary-color de --bs-body-color-rgb et qu'il suivait donc le texte
       courant tout seul. C'est FAUX. Bootstrap 5.3.6 la déclare EN DUR :
       `--bs-secondary-color: rgba(33, 37, 41, 0.75)` — la valeur d'usine, pas la nôtre.
       Mesuré sur l'app : .text-body-secondary rendait rgba(33,37,41,.75), un gris plus
       sombre et plus bleu que le corps de texte de Cockpit (#4b4b4b).

       On la redérive donc explicitement, en gardant le RATIO de Bootstrap (.75) mais sur
       NOTRE couleur de texte. Ce n'est pas --kd-text-muted : le forcer là éclaircissait
       nettement tous les paragraphes secondaires (mesuré : #4b4f53 → #909298).

       L'enjeu a grandi : les 89 anciens `hint-text` (opacity .76 du thème acheté) sont
       passés à .text-body-secondary. Sans cette ligne, ils changeaient de teinte. */
    --bs-secondary-color: rgba(var(--bs-body-color-rgb), .75);

    /* LES TROIS NEUTRES DE BOOTSTRAP QUE CE PONT AVAIT OUBLIÉES.
       Bootstrap les déclare, elles n'étaient donc pas « invalides » — elles servaient
       simplement la palette D'USINE de Bootstrap, et pas la nôtre, à 34 emplois : 22
       pour --bs-tertiary-bg (panneau du rail BIM, vues sauvegardées, Options
       spatiales), 8 pour --bs-secondary-bg (rail BIM, viewer Forge, onglet Qualité de
       l'air), 4 pour --bs-tertiary-color (vues sauvegardées). Un habillage client ne
       les atteignait pas.
       ⚠️ LES BRANCHER CHANGE LE RENDU, et c'est signalé plutôt que fait en silence :
           --bs-tertiary-bg     #f8f9fa (248,249,250) → --kd-page-bg     #f4f4f4
           --bs-secondary-bg    #e9ecef (233,236,239) → --kd-border-light #eaeaea
           --bs-tertiary-color  rgba(33, 37, 41, .5)  → .5 de NOTRE texte (#4b4b4b)
       Écarts de 4 à 6 unités sur les deux fonds, une teinte plus neutre sur le texte.
       On prend nos jetons : c'est la seule façon qu'un habillage les emporte, et
       l'alternative — trois valeurs figées de plus — est exactement ce que ce fichier
       existe pour supprimer.
       --bs-tertiary-color reprend le PROCÉDÉ d'--bs-secondary-color juste au-dessus :
       le ratio de Bootstrap (.5) appliqué à notre couleur de corps.
       ⚠️ Elles irriguent aussi des composants Bootstrap, au-delà des emplois comptés :
       champ désactivé et .input-group-text (3 fichiers), .form-range (1), .pagination
       (3), en-tête de .popover, bordure de survol des .nav-tabs. Les mêmes écarts de
       4 à 6 unités s'y appliquent. .dropdown-menu et .progress, eux, ne bougent pas :
       ce fichier leur pose déjà --bs-dropdown-link-hover-bg et --bs-progress-bg. */
    --bs-tertiary-bg: var(--kd-page-bg);
    --bs-secondary-bg: var(--kd-border-light);
    --bs-tertiary-color: rgba(var(--bs-body-color-rgb), .5);
}


/*------------------------------------------------------------------
[Composants]
Bootstrap 5.3 résout ses variables de composant au niveau du composant :
--bs-card-* n'a d'effet que posé sur .card. D'où ces blocs, qui ne sont
PAS des surcharges de style — aucune propriété CSS n'y est déclarée,
seulement des variables que Bootstrap lit ensuite lui-même.
*/

/*--- Cartes ---------------------------------------------------------*/
.card {
    --bs-card-bg: var(--kd-surface);
    --bs-card-color: var(--kd-text-strong);
    --bs-card-border-width: 0;
    --bs-card-border-radius: var(--kd-radius-lg);
    --bs-card-inner-border-radius: var(--kd-radius-lg);
    --bs-card-box-shadow: var(--kd-shadow-card);
    --bs-card-spacer-x: 15px;
    --bs-card-spacer-y: 15px;
    --bs-card-cap-bg: transparent;
    --bs-card-cap-padding-x: 15px;
    --bs-card-cap-padding-y: 10px;
    --bs-card-title-color: var(--kd-text-strong);
}

/*--- Boutons ---------------------------------------------------------*/
/* Le bouton par défaut de Cockpit est BLANC à fine bordure, pas le gris
   « secondary » de Bootstrap. Les variantes colorées se déclarent ensuite et
   l'emportent par ordre de source (même spécificité, déclarées après).

   La géométrie (inline-flex, min-height 32px) n'est PAS ici : Bootstrap
   n'expose aucune variable pour elle. Elle vit dans app.css, section Boutons. */
.btn {
    --bs-btn-font-family: var(--kd-font-body);
    --bs-btn-font-size: var(--kd-fs-base);
    --bs-btn-font-weight: 500;
    --bs-btn-line-height: 13px;
    --bs-btn-border-radius: var(--kd-radius);
    --bs-btn-border-width: 1px;
    --bs-btn-padding-x: 9px;
    --bs-btn-padding-y: 0;
    --bs-btn-color: var(--kd-text);
    --bs-btn-bg: var(--kd-surface);
    --bs-btn-border-color: rgba(33, 33, 33, .17);
    --bs-btn-hover-color: var(--kd-text);
    --bs-btn-hover-bg: var(--kd-page-bg);
    --bs-btn-hover-border-color: rgba(33, 33, 33, .17);
    --bs-btn-active-color: var(--kd-text);
    --bs-btn-active-bg: #e9ebeb;
    --bs-btn-active-border-color: rgba(33, 33, 33, .2);
    --bs-btn-disabled-color: var(--kd-text);
    --bs-btn-disabled-bg: var(--kd-surface);
    --bs-btn-disabled-border-color: rgba(33, 33, 33, .17);
    --bs-btn-box-shadow: none;
    /* Anneau de focus : on le TEINTE, on ne le supprime pas. L'ancien thème
       posait `outline: none !important` avec son box-shadow commenté — plus
       aucun repère visible au clavier. On rend l'anneau de Bootstrap, en vert. */
    --bs-btn-focus-shadow-rgb: var(--kd-primary-rgb);
}

.btn-sm { --bs-btn-padding-x: 8px; }
.btn-lg { --bs-btn-padding-x: 13px; --bs-btn-font-size: var(--kd-fs-base); }

/* Variantes pleines. Chaque état désactivé reprend la couleur de sa variante :
   le thème laissait ressortir des restes VIOLETS du gabarit d'origine
   (btn-primary désactivé bordé de rgba(78,55,182,.5), btn-outline-primary
   désactivé écrit en rgba(114,82,211,.8)), jamais rebrandés. */
.btn-primary {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--kd-primary);
    --bs-btn-border-color: var(--kd-primary-border);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--kd-primary-dark);
    --bs-btn-hover-border-color: var(--kd-primary-dark);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--kd-primary-dark);
    --bs-btn-active-border-color: var(--kd-primary-dark);
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--kd-primary);
    --bs-btn-disabled-border-color: var(--kd-primary-border);
}

.btn-success {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--kd-success);
    --bs-btn-border-color: rgba(13, 147, 91, .5);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #148f64;
    --bs-btn-hover-border-color: #148f64;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #148f64;
    --bs-btn-active-border-color: #148f64;
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--kd-success);
    --bs-btn-disabled-border-color: rgba(13, 147, 91, .5);
}

.btn-danger {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--kd-danger);
    --bs-btn-border-color: rgba(185, 30, 30, .5);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #b91e1e;
    --bs-btn-hover-border-color: #b91e1e;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #b91e1e;
    --bs-btn-active-border-color: #b91e1e;
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--kd-danger);
    --bs-btn-disabled-border-color: rgba(185, 30, 30, .5);
}

/* Warning / info / secondary n'étaient stylés QUE désactivés dans le thème :
   actifs ils tombaient sur le bouton blanc, désactivés ils repassaient aux
   couleurs brutes de Bootstrap (#ffc107, #0dcaf0, #6c757d). On les aligne
   sur la palette Cockpit, dans les deux états. */
.btn-warning {
    --bs-btn-color: var(--kd-text-strong);
    --bs-btn-bg: var(--kd-warning);
    --bs-btn-border-color: var(--kd-warning);
    --bs-btn-hover-color: var(--kd-text-strong);
    --bs-btn-hover-bg: #f0c92e;
    --bs-btn-hover-border-color: #f0c92e;
    --bs-btn-disabled-color: var(--kd-text-strong);
    --bs-btn-disabled-bg: var(--kd-warning);
    --bs-btn-disabled-border-color: var(--kd-warning);
}

.btn-info,
.btn-secondary {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--kd-info);
    --bs-btn-border-color: var(--kd-info);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #7d8598;
    --bs-btn-hover-border-color: #7d8598;
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--kd-info);
    --bs-btn-disabled-border-color: var(--kd-info);
}

/*--- Boutons en contour ------------------------------------------------*/
.btn-outline-primary {
    --bs-btn-color: var(--kd-primary);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--kd-primary);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--kd-primary);
    --bs-btn-hover-border-color: var(--kd-primary);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--kd-primary-dark);
    --bs-btn-active-border-color: var(--kd-primary-dark);
    --bs-btn-disabled-color: var(--kd-primary);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--kd-primary);
}

.btn-outline-danger {
    --bs-btn-color: var(--kd-danger);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--kd-danger);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--kd-danger);
    --bs-btn-hover-border-color: var(--kd-danger);
    --bs-btn-disabled-color: var(--kd-danger);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--kd-danger);
}

/*--- Bouton-lien --------------------------------------------------------*/
.btn-link {
    --bs-btn-color: var(--kd-primary);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-border-width: 0;
    --bs-btn-padding-x: 6px;
    --bs-btn-hover-color: var(--kd-primary-dark);
    --bs-btn-hover-bg: transparent;
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-bg: transparent;
    --bs-btn-disabled-color: #757575;
    /* sinon le bouton-lien désactivé hérite du fond blanc de .btn */
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: transparent;
}

/*--- Badges -----------------------------------------------------------*/
/* Pas de --bs-badge-padding-* ici : les badges de Cockpit tiennent leur
   rembourrage de app.css et des composants, avec des valeurs asymétriques
   selon l'usage. Les uniformiser depuis les tokens les resserrait
   visiblement (mesuré : 3,85 px → 2,42 px en vertical). */
.badge {
    --bs-badge-font-size: var(--kd-fs-sm);
    --bs-badge-font-weight: 600;
    /* ⚠️ Ces deux-là étaient MASQUÉES par theme.css, qui écrivait
       `border-radius: 3px` et `color: #4b4b4b` en propriétés DIRECTES sur
       .badge — imbattables par une variable, quel que soit l'ordre de
       chargement. La pilule que ce fichier décrivait ne s'est donc jamais
       affichée. Valeurs alignées sur le rendu réel d'avant la fusion.
       (Un badge explicitement en .rounded-pill reste rond : l'utilitaire
       Bootstrap est !important.) À arbitrer au lot des primitives. */
    --bs-badge-border-radius: 3px;
    --bs-badge-color: #4b4b4b;
}

/*--- Menus déroulants --------------------------------------------------*/
.dropdown-menu {
    --bs-dropdown-font-size: var(--kd-fs-base);
    --bs-dropdown-bg: var(--kd-surface);
    --bs-dropdown-border-width: 0;
    --bs-dropdown-border-radius: var(--kd-radius);
    --bs-dropdown-padding-y: 4px;
    --bs-dropdown-link-color: var(--kd-text);
    --bs-dropdown-link-hover-bg: var(--kd-page-bg);
    --bs-dropdown-link-active-bg: var(--kd-primary);
    --bs-dropdown-link-active-color: #fff;
}

/* Le bloc de variables du fil d'Ariane est parti avec lui : plus aucun
   `.breadcrumb` dans le markup depuis que le titre et le retour vivent dans la
   barre supérieure. */

/*--- Champs de saisie ---------------------------------------------------*/
.form-control,
.form-select {
    --bs-border-radius: var(--kd-radius-sm);
}

/*--- Modales -----------------------------------------------------------*/
/* (le fond est déclaré plus bas, section « Surfaces ») */
.modal {
    --bs-modal-border-width: 0;
    --bs-modal-border-radius: var(--kd-radius-lg);
    --bs-modal-box-shadow: var(--kd-shadow-menu);
}

/*--- Onglets -----------------------------------------------------------*/
.nav-tabs {
    --bs-nav-tabs-border-width: 1px;
    --bs-nav-tabs-border-color: var(--kd-border);
    --bs-nav-tabs-link-active-color: var(--kd-primary);
    --bs-nav-tabs-link-active-border-color: transparent transparent var(--kd-primary);
    --bs-nav-link-font-size: var(--kd-fs-base);
    --bs-nav-link-color: var(--kd-text);
    --bs-nav-link-hover-color: var(--kd-primary);
}

.nav-pills {
    --bs-nav-pills-border-radius: var(--kd-radius);
    --bs-nav-pills-link-active-bg: var(--kd-primary);
    --bs-nav-link-font-size: var(--kd-fs-base);
}

/*--- Tables ------------------------------------------------------------*/
.table {
    --bs-table-color: var(--kd-text);
    --bs-table-border-color: var(--kd-border);
    --bs-table-striped-bg: rgba(0, 0, 0, .015);
    --bs-table-hover-bg: rgba(var(--kd-primary-rgb), .06);
    /* transparent, pas --kd-surface : une table vit presque toujours dans une
       carte et doit en prendre le fond. Voir la note sur --bs-body-bg ci-dessous. */
    --bs-table-bg: transparent;
}


/*------------------------------------------------------------------
[Surfaces]
⚠️ PIÈGE : Bootstrap fait dériver le fond de nombreux composants de
--bs-body-bg. Comme celui-ci porte ici le GRIS DE PAGE (#f4f4f4) et non
du blanc, tout composant qui ne redéclare pas son fond ressort en gris.
Constaté à la mesure : modales et tables passaient en #f4f4f4 après
retrait des surcharges du thème.

Ces composants doivent donc nommer explicitement leur surface. Toute
nouvelle surface flottante ajoutée plus tard est à inscrire ici.
*/
.modal { --bs-modal-bg: var(--kd-surface); }
.offcanvas { --bs-offcanvas-bg: var(--kd-surface); }
.toast { --bs-toast-bg: var(--kd-surface); }
.popover { --bs-popover-bg: var(--kd-surface); }
/* L'élément actif : theme.css le peignait en #26bf93, un turquoise venu du
   thème acheté et étranger à la charte. Le retirer sans rien mettre à la
   place aurait fait ressortir le bleu #0d6efd de Bootstrap — pas mieux.
   D'où le passage au primaire. Aucun .list-group-item.active n'est rendu
   aujourd'hui : le changement est invisible, il évite juste qu'un bleu
   Bootstrap surgisse le jour où l'état sera utilisé. */
.list-group {
    --bs-list-group-bg: var(--kd-surface);
    --bs-list-group-active-bg: var(--kd-primary);
    --bs-list-group-active-border-color: var(--kd-primary);
}
.accordion { --bs-accordion-bg: var(--kd-surface); }
.pagination { --bs-pagination-bg: var(--kd-surface); }

/*--- Alertes -----------------------------------------------------------*/
.alert {
    --bs-alert-border-radius: var(--kd-radius);
    --bs-alert-border-width: 0;
    --bs-alert-padding-x: 15px;
    --bs-alert-padding-y: 10px;
}

/*--- Barres de progression ---------------------------------------------*/
/* ⚠️ Ces valeurs étaient MASQUÉES jusqu'ici : theme.css écrivait `height: 4px`
   et `border-radius: 0` en propriétés directes sur .progress, qui l'emportent
   toujours sur un `height: var(--bs-progress-height)`, quel que soit l'ordre
   de chargement. Le jeu de jetons ci-dessous ne s'appliquait donc à rien.
   Les valeurs sont ici alignées sur le rendu RÉEL d'avant la fusion, pour que
   le déménagement de theme.css ne change rien à l'écran.

   À arbitrer au lot des primitives : la barre déclarait 10px, en pilule et en
   vert primaire — une barre bien plus présente que les 4px gris d'aujourd'hui.
   C'est un choix graphique, pas une correction ; il n'est pas pris ici. */
.progress {
    --bs-progress-height: 4px;
    --bs-progress-font-size: var(--kd-fs-sm);
    --bs-progress-bg: rgba(75, 75, 75, .2);
    --bs-progress-border-radius: 0;
    --bs-progress-bar-bg: var(--kd-text);
}

.progress.progress-small {
    --bs-progress-height: 3px;
}


/*------------------------------------------------------------------
[États du shell]
Les seules règles de ce fichier portant sur un sélecteur autre que :root —
et elles ne déclarent, elles aussi, que des variables.

Le HTML servi par App.razor part toujours en `menu-pin` ; menusidebar.js
corrige ensuite depuis localStorage. L'état « aucune des deux classes »
existe donc réellement (profil neuf) : c'est pourquoi la valeur REPLIÉE est
la valeur par défaut de :root et non une classe.
`menu-unpinned` a disparu avec le lot A (commit 21e2e066) : plus personne ne
la lisait, et le JavaScript ne l'écrit plus non plus. Il ne reste que
`menu-pin`, structurelle, sur laquelle se branchera le réglage d'état de
départ du menu (habillage client, lot E).
*/

/* Épinglé : la boîte s'élargit ET pousse le contenu. */
body.menu-pin {
    --kd-sidebar-w: var(--kd-sidebar-w-expanded);
}

/* Les variables DÉRIVÉES vivent ici, sur body, et non sur :root — voir
   l'avertissement dans la section « Shell boxed ». Elles se recalculent donc
   avec la valeur que body.menu-pin vient de poser.
   (Une déclaration sur `body` s'applique aussi quand body porte `menu-pin` :
   les deux règles ciblent le même élément, et la substitution des variables se
   fait sur les valeurs calculées de CET élément.) */
body {
    /* Décalage du contenu = gouttière + boîte + ce que la pastille déborde + gouttière.
       Porté par un SEUL élément (.page-container), dans tous les états — l’ancien
       shell le faisait porter par .page-container en replié et par .content en
       épinglé, ce qui obligeait chaque consommateur à connaître l’état courant.

       ⚠️ IL N’Y A PLUS NI TERME DE DÉBORD NI GOUTTIÈRE : le décalage vaut EXACTEMENT
       la largeur du menu. Deux changements l’ont amené — la pastille de bascule est
       rentrée dans la boîte (elle était à cheval sur le bord droit en rail, et il
       fallait dégager ce qu’elle dépassait), puis le shell est passé BORD À BORD,
       le menu partant de 0 et n’ayant plus de gouttière autour de lui.
       Le retrait de la zone de contenu, lui, n’a pas disparu : il est simplement
       devenu ce qu’il aurait toujours dû être, un rembourrage de `.content`
       (app.css § 28), que les écrans à rail annulent en se déclarant `.kd-bleed`. */
    --kd-shell-offset: var(--kd-sidebar-w);

    /* Ponts de compatibilité. Ces trois noms viennent de l'ancien thème et sont
       encore lus ailleurs (viewer 3D BIM, offcanvas plein écran). On les fait
       toutes pointer sur le DÉCALAGE, car c'est ce que leurs consommateurs
       voulaient réellement : « où commence la zone de contenu ». Elles suivent
       donc l'état toutes seules, ce qui rend inutile la règle
       `body.menu-pin .forge-host`.
       ⚠️ Ne pas les réintroduire dans du code neuf : utiliser --kd-shell-offset. */
    --sidebar-pinned-width: var(--kd-shell-offset);
    --sidebar-unpinned-width: var(--kd-shell-offset);
    --sidebar-unpinned-widthcalc: var(--kd-shell-offset);
}


/* La redéclaration `body.menu-pin { --kd-shell-offset }` a été retirée : elle ne
   servait qu’à retrancher le débord de la pastille, qui n’existe plus. Une seule
   formule vaut désormais pour les deux états. */


/* Ce fichier ne déclare AUCUNE propriété CSS — uniquement des variables.
   Tout ce que Bootstrap ne sait pas exprimer ainsi (géométrie des boutons,
   ombre et marge des cartes, titres en capitales) vit dans app.css, sections
   25 à 27. Garder cette séparation : c'est elle qui permet à un habillage
   client de ne surcharger que des valeurs. */
