/* ================================================================== */
/* SECTION 0: DESIGN TOKENS — CHARTE GRAPHIQUE JS BROKERS             */
/* ------------------------------------------------------------------ */
/* Source unique de la palette (skill charte-couleurs). Ces variables  */
/* sont globales (Asset Mapper charge app.css + interactive-menu.css)  */
/* et donc réutilisables dans tous les fichiers CSS du projet.         */
/* NB : le :root des squelettes (--sk-*) reste défini plus bas, à part.*/
/* ================================================================== */
:root {
  /* --- Marque (cobalt) --- */
  --cobalt: #0047AB;
  --cobalt-dark: #003380;
  --cobalt-hover: #0a58ca;
  --blue-primary: #0d6efd;

  /* --- Surfaces sombres (col-1, barre de recherche, panneaux) --- */
  --bg-dark: #212529;
  --bg-dark-secondary: #343a40;
  --bg-dark-hover: #495057;

  /* --- Surfaces claires (col-2/3/4) --- */
  --bg-white: #ffffff;
  --bg-body: #f3f3f3;
  --bg-light: #f8f9fa;
  --bg-muted: #e9ecef;
  --bg-cobalt-subtle: #e8f0fb;

  /* --- Textes --- */
  --text-dark: #212529;
  --text-dark-secondary: #343a40;
  --text-body: #495057;
  --text-muted: #6c757d;
  --text-label: #adb5bd;
  --text-white: #ffffff;

  /* --- Bordures & séparateurs --- */
  --border-light: #dee2e6;
  --border-medium: #ced4da;
  --border-subtle: #e0e0e0;
  --border-dark: #495057;

  /* --- États sémantiques (retours utilisateur uniquement) --- */
  --success: #198754;
  --warning: #ffc107;
  --danger: #dc3545;

  /* --- Étapes du pipe commercial (Pistes) : UNE couleur par étape, appliquée à
     l'identique au marqueur de la liste, au chip et au Kanban. Des ALIAS de jetons
     existants, aucune couleur nouvelle : gris tant que rien n'est engagé, cobalt qui
     fonce à mesure que l'offre se précise, vert — réservé — pour la police souscrite. --- */
  --etape-a_coter: var(--text-label);
  --etape-en_cotation: var(--cobalt-hover);
  --etape-offres_chiffrees: var(--cobalt-dark);
  --etape-transformees: var(--success);
  --success-dark: #0f5132;
  --danger-dark: #842029;
  --danger-bg: #f8d7da;

  /* --- Focus & échelle de finition (additifs, non colorimétriques) --- */
  --cobalt-focus-ring: rgba(0, 71, 171, 0.35);
  --cobalt-shadow: rgba(0, 71, 171, 0.25);
  --overlay-dark: rgba(0, 0, 0, 0.65);
  --overlay-light: rgba(248, 249, 250, 0.85);
  --nav-radius: 10px;
  --nav-radius-sm: 8px;
  --nav-transition: 0.15s ease;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.10);

  /* --- Géométrie du workspace (source unique des largeurs de colonnes) ---
     Ces largeurs étaient écrites en dur dans interactive-menu.css. La poignée
     de repli de la colonne 2 se pose sur le JOINT des colonnes : elle doit
     calculer sa position à partir des mêmes nombres, sinon elle dérive dès que
     quelqu'un retouche une largeur. */
  --ws-col1-width: 130px;
  --ws-col2-width: 350px;
  --ws-col4-width: 450px;
  /* Gouttière réservée à la poignée quand la colonne 2 est repliée : le panneau
     flottant démarre après elle, la poignée reste donc cliquable en permanence
     sans jamais se déplacer selon que le panneau est ouvert ou non. */
  --ws-handle-gutter: 20px;
  /* Au-dessus des entêtes collants (100) et des barres d'outils (1020), sous
     les modales Bootstrap (1050) et les toasts (1100) : le panneau recouvre les
     listes, jamais un dialogue. Sans z-index explicite il passerait SOUS
     `.menu-col-4 .tab-content` (position:absolute, postérieur dans le DOM). */
  --ws-flyout-z: 1045;
}

/* ================================================================== */
/* SECTION 1: STYLES GLOBAUX ET UTILITAIRES DE BASE                   */
/* ================================================================== */

/* --- 1.1 Styles de base (body, liens) --- */
body {
    background-color: var(--bg-body);
}

a:link {
    text-decoration: none;
}

a:visited {
    text-decoration: none;
}

a:hover {
    text-decoration: none;
    font-weight: bold;
}

a:active {
    text-decoration: underline;
}

/* --- 1.2 Styles globaux pour les barres de défilement --- */
/* Style de la barre de défilement par défaut pour toute l'application */
::-webkit-scrollbar {
  width: 8px;
  height: 8px; /* Ajout pour les barres horizontales */
}
::-webkit-scrollbar-track {
  background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
  background: #888;
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
  background: #555;
}

/* ============================================================
   Bascule de langue FR/EN unifiée (.cs-lang) — modèle de la barre de
   titre Console, réutilisée partout via templates/components/_lang_switch.html.twig.
   Conçue pour les fonds clairs (texte gris neutre, actif cobalt léger).
   ============================================================ */
.cs-lang { display: inline-flex; align-items: center; gap: .15rem; }
.cs-lang a {
    display: inline-flex; align-items: center; gap: .25rem; padding: .25rem .4rem;
    border-radius: 6px; text-decoration: none; color: #6c757d; font-size: .78rem; font-weight: 700;
}
.cs-lang a.is-active { background-color: #eef2fb; color: #0047AB; }
.cs-lang svg { width: 18px; height: 13px; border-radius: 2px; }
.cs-lang a:focus-visible { outline: 2px solid #0047AB; outline-offset: 2px; }

/* --- 1.3 Classes utilitaires génériques --- */
.sensible:hover {
    background-color: #ececec;
    border: 4px solid darkgray;
}

.sensible {
    background-color: white;
    border-radius: 10px;
    border: 4px solid white;
    padding: 10px;
    margin: 3px;
    transition: border-color 0.3s ease-in-out;
}

.bg-income {
    background-color: #e0f9ee;
}

/* NOUVEAU : Couleur Bleu Cobalt Joseara */
.bg-cobalt {
    background-color: #0047AB !important;
}

.btn-cobalt {
    background-color: #0047AB;
    border-color: #0047AB;
    color: #ffffff;
}
.btn-cobalt:hover, .btn-cobalt:focus {
    background-color: #003380;
    border-color: #003380;
    color: #ffffff;
}
.btn-cobalt:focus {
    box-shadow: 0 0 0 0.25rem rgba(0, 71, 171, 0.35);
}

.cacherComposant {
    display: none;
}

.afficherComposant {
    display: block;
}

.gras {
    font-weight: bold;
}

.parent-a-options {
    cursor: pointer;
    position: relative;
}

.options-du-parent {
    display: none;
}

.parent-a-options:hover .options-du-parent {
    display: block;
}

.shadow-bottom {
    box-shadow: 0 8px 6px -6px rgba(0, 0, 0, 0.1);
}

/* NOUVEAU : Classe utilitaire pour un alignement vertical parfait dans un conteneur flex */
.align-middle-flex-item {
    vertical-align: middle;
}

/*
================================================================== */
/* SECTION 2: STYLES GLOBAUX POUR LES ÉLÉMENTS DE FORMULAIRE          */
/* ================================================================== */

/* --- 2.1 Style "flat" pour les champs (input, select, textarea) --- */
/*
===================================================================
 SECTION: STYLE GLOBAL POUR LES CHAMPS DE FORMULAIRE ("FLAT" AVEC SURVOL)
===================================================================
*/

/* --- Style par défaut pour tous les champs --- */
.form-control,
.form-select,
.editeur-riche,
/* NOUVEAU : Cible tous les types d'input pertinents */
input[type="text"], input[type="date"], input[type="password"], input[type="number"], input[type="email"], input[type="file"],
input[type=text],
input[type=date],
input[type=password],
input[type=number],
input[type=email],
input[type=file],
select,
textarea,
.tom-select-control {
    border: 3px solid transparent; 
    border-radius: 10px;
    background-color: transparent !important;
    box-shadow: none !important;
    transition: border-color 0.2s ease-in-out; 
    /* NOUVEAU : Style pour le contenu des champs */
    color: #000000; /* Noir */
    font-weight: bold;
}

/* --- Style au survol de la souris --- */
.form-control:hover,
.form-select:hover,
.editeur-riche:hover,
input[type=text]:hover,
input[type=date]:hover,
input[type=password]:hover,
input[type=number]:hover,
input[type=email]:hover,
input[type=file]:hover,
select:hover,
textarea:hover,
.tom-select-control:hover {
    border-color: #6c757d;
}

/* --- Style au focus (quand on clique dedans) --- */
.form-control:focus,
.form-select:focus,
.editeur-riche:focus,
.tom-select-control.focus {
    border-color: #0d6efd !important;
    box-shadow: none !important;
}
.input-group span {
    border: none;
    background-color: transparent;
}

/*
 * Liste déroulante d'autocomplétion (Tom Select).
 * TOUS les champs relation reçoivent `dropdownParent: 'body'` via le hook global
 * `autocomplete:pre-connect` (assets/autocomplete-dropdown-global.js) : le menu est
 * rattaché à <body> pour échapper aux conteneurs défilants de la fiche
 * (.form-column { overflow-y:auto }, .modal-body { overflow-y:hidden }) qui le tronquaient.
 * Le z-index 1060 ci-dessous est le repli (au-dessus d'une modale simple à 1055) ;
 * dans les dialogues imbriqués (1075, 1095…), le même hook pose à l'ouverture du menu
 * un z-index inline !important au-dessus de la modale ouverte la plus haute.
 */
.ts-dropdown {
    z-index: 1060 !important;
    /* Carte flottante moderne : coins arrondis, ombre douce, cadre discret. */
    border: 1px solid #dee2e6 !important;
    border-radius: 12px !important;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06) !important;
    background-color: #ffffff !important;
    padding: 6px !important;
    margin-top: 6px !important;
    overflow: hidden !important;
}

/* Zone défilante interne : scrollbar fine et discrète (charte gris). */
.ts-dropdown .ts-dropdown-content {
    padding: 2px;
    scrollbar-width: thin;
    scrollbar-color: #ced4da transparent;
}
.ts-dropdown .ts-dropdown-content::-webkit-scrollbar {
    width: 8px;
}
.ts-dropdown .ts-dropdown-content::-webkit-scrollbar-thumb {
    background-color: #ced4da;
    border-radius: 8px;
}

/*
 * Chaque option est une carte cliquable.
 * padding:0 → l'espacement interne est porté par .jsb-autocomplete-item, afin que
 * l'état de survol/sélection épouse exactement la carte.
 */
.ts-dropdown .option {
    padding: 0 !important;
    margin-bottom: 4px;
    border-radius: 8px;
    border: 1px solid transparent;
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}
.ts-dropdown .option:last-child {
    margin-bottom: 0;
}

/*
 * État actif (navigation clavier ↑/↓) ET survol souris.
 * Double indice — fond cobalt clair + bordure cobalt — pour ne pas reposer sur
 * la seule couleur (WCAG 1.4.1 / Bastien & Scapin > Guidage : focus visible).
 */
.ts-dropdown .option.active,
.ts-dropdown .option:hover {
    background-color: #e8f0fb;
    border-color: #0047AB;
}

/* Messages système du menu (aucun résultat, en-têtes de groupe). */
.ts-dropdown .no-results,
.ts-dropdown .optgroup-header {
    color: #6c757d;
    padding: 10px 12px;
    font-size: 0.85rem;
}

/* --- 2.2 Labels de formulaire --- */
/* NOUVEAU : Style pour les titres des champs (labels) */
.form-label {
    color: #adb5bd; /* Gris clair */
    font-weight: normal;
    /* Assure que le label ne soit pas affecté par le style bold des inputs */
    display: block; 
}

/* --- 2.3 Messages d'aide --- */
/*
===================================================================
 SECTION: STYLE POUR LES MESSAGES D'AIDE DES FORMULAIRES
===================================================================
*/

.form-text {
    color: #0d6efd !important;
}

/* --- 2.4 Composant VichUploader (champ fichier) --- */
/*
===================================================================
 SECTION: STYLE POUR LE LIEN DE TÉLÉCHARGEMENT VICHUPLOADER
===================================================================
*/

/* Conteneur pour le champ de fichier */
.vich-file {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

/* Style du lien de téléchargement */
.vich-file a {
    font-style: italic;
    color: #0d6efd; /* Bleu cobalt */
    font-weight: bold;
    text-decoration: none;
    padding: 8px;
    border-radius: 4px;
    background-color: rgba(13, 110, 253, 0.1);
    border: 1px dashed rgba(13, 110, 253, 0.4);
    display: inline-block;
    max-width: fit-content;
}
.vich-file a:hover {
    background-color: rgba(13, 110, 253, 0.2);
    text-decoration: underline;
}

/* --- 2.5 Éditeur de texte riche --- */
.editeur-riche {
    height: 150px;
    padding: 10px;
    border: none;
    border-bottom: 2px solid gray;
    border-radius: 10px;
    font-family: sans-serif;
    font-size: 14px;
    font-weight: bold;
    line-height: 1.5;
    resize: vertical;
}

/* ================================================================== */
/* SECTION 3: STYLES POUR LES COMPOSANTS DE L'INTERFACE PRINCIPALE    */
/* ================================================================== */

/* --- 3.1 Interface à onglets (listes principales) --- */
/* ================================================================== */
/* SECTION : NOUVELLE INTERFACE À ONGLETS                             */
/* ================================================================== */

/* Structure de la page et header fixe */
.sticky-header {
    position: sticky;
    top: 0;
    z-index: 1020;
    border-bottom: 1px solid #dee2e6;
}

.rubrique-title {
    font-weight: bold;
    color: #343a40;
    font-size: 1.1rem;
    /* 0 et non 7 px : la barre d'onglets n'est plus dans ce bloc — elle traverse
       l'entête, sous la rangée du titre. Les 7 px n'espaçaient plus rien, ils
       épaississaient la rangée. */
    margin-bottom: 0;
}

/* Icône de la rubrique (entête du view-manager) — remplace les styles inline.
   NE PAS nommer `.rubrique-icon` : cette classe existe déjà dans
   interactive-menu.css (icônes 18px du menu) et gagnerait la cascade. */
.rubrique-header-icon {
    width: 40px;
    height: 40px;
    color: #343a40; /* --text-dark-secondary : couleur des titres */
}

/* Pastille de contexte : rappelle l'élément sélectionné dont les onglets
   contextuels sont ouverts (Guidage / visibilité de l'état du système). */
.rubrique-context {
    display: inline-block;
    max-width: 40ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
    margin-left: 8px;
    padding: 1px 10px;
    border-radius: 999px;
    background-color: #e8f0fb; /* --bg-cobalt-subtle */
    color: #0047AB;            /* --text-cobalt */
    font-size: 0.8rem;
    font-weight: 500;
}

/* ================================================================== */
/* COMPOSANT D'ONGLET — partagé par les DEUX barres du workspace        */
/* ================================================================== */
/* Celle des RUBRIQUES ouvertes (colonne 3, `.workspace-tab-*`) et celle des onglets
   d'une rubrique (`.list-tab*` : Principal + collections de la ligne sélectionnée).

   Elles portaient deux grammaires d'onglets différentes dans le même écran : encadrée
   en haut, soulignée en bas. Deux façons d'être un onglet obligent à réapprendre ce
   qu'on sait déjà faire (Bastien & Scapin > Cohérence) — et garantissaient qu'en
   retouchant l'une, on oublierait l'autre.

   ⚠ `.workspace-tab-item`, `.list-tab`, `.workspace-tab-title`… SUBSISTENT : ce sont
   des points d'accroche JavaScript (querySelector, closest) et des cibles de tests.
   Elles ne portent plus que ce qui leur est PROPRE — voir interactive-menu.css, qui se
   charge après ce fichier et garde donc le dernier mot sur les spécificités. */

/* Le bandeau : il porte le fond, la bordure basse et le bouton « + N », et sert de
   référent de position au panneau des onglets repliés. */
.jsb-onglets-barre {
    position: relative;
    display: flex;
    align-items: flex-end;
    gap: 4px;
    min-width: 0;
    flex-shrink: 0;
    padding: 6px 8px 0;
    background-color: var(--bg-light);
    border-bottom: 1px solid var(--border-light);
}

/* La rangée. Les onglets qui ne tiennent pas sont REPLIÉS derrière « + N » (cf.
   `.is-tab-replie`), jamais défilés : une barre de défilement de 3 px ne se voit pas,
   et ce qu'elle cachait était à la fois invisible ET introuvable. */
.jsb-onglets-rangee {
    display: flex;
    flex-direction: row;
    overflow: hidden;
    min-height: 38px;
    align-items: flex-end;
    flex: 1 1 auto;
    min-width: 0;
    gap: 2px;
}

.jsb-onglet {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px 7px;
    background-color: var(--bg-muted);
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: var(--nav-radius-sm) var(--nav-radius-sm) 0 0;
    color: var(--text-muted);
    /* `font-family`, `font-size` et `line-height` sont ÉCRITS, pas hérités : la barre
       contextuelle emploie un <button>, qui n'hérite ni de la police ni de l'interligne
       du corps. 1.5 est la valeur que le <div> du workspace calcule aujourd'hui
       (--bs-body-line-height) : les deux barres restent au pixel. */
    font-family: inherit;
    font-size: 0.82rem;
    line-height: 1.5;
    white-space: nowrap;
    user-select: none;
    cursor: pointer;
    max-width: 200px;
    transition: background-color var(--nav-transition), color var(--nav-transition), box-shadow var(--nav-transition);
}

.jsb-onglet:hover {
    /* #d0d5dd : exception assumée, héritée de la barre du workspace. La reprendre garde
       les deux barres identiques — en inventer une seconde les séparerait. */
    background-color: #d0d5dd;
    color: var(--text-dark-secondary);
}

/* ACTIF : fond, liseré cobalt EN HAUT et graisse — jamais la couleur seule (WCAG 1.4.1). */
.jsb-onglet.active {
    background-color: var(--bg-white);
    border-color: var(--border-light);
    box-shadow: inset 0 2px 0 0 var(--cobalt);
    color: var(--cobalt);
    font-weight: 600;
}

/* Anneau de focus EN INSET, et non extérieur : la rangée est en `overflow: hidden`, un
   halo posé dehors y serait rogné sur les bords (WCAG 2.4.7). */
.jsb-onglet:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 2px var(--cobalt-focus-ring);
}
/* Sur l'onglet actif, l'anneau s'AJOUTE au liseré : le remplacer ferait disparaître le
   repère d'état au moment précis où le clavier s'y pose. */
.jsb-onglet.active:focus-visible {
    box-shadow: inset 0 2px 0 0 var(--cobalt), inset 0 0 0 2px var(--cobalt-focus-ring);
}

.jsb-onglet-icone {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
}
/* La TAILLE est écrite ici, pas laissée au serveur. Les icônes arrivent du circuit du
   cerveau avec leurs propres attributs `width`/`height`, et les deux barres ne les
   demandaient pas à la même taille : les onglets d'une rubrique se retrouvaient avec des
   icônes plus grosses que ceux du workspace, pour la seule raison qu'un chiffre différait
   dans deux appels. */
.jsb-onglet-icone svg {
    width: 16px;
    height: 16px;
}

.jsb-onglet-titre {
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

/* Onglet replié : retiré de la rangée, atteignable par le panneau « + N ». */
.jsb-onglet.is-tab-replie {
    display: none;
}

/* DÉPLIEMENT DES ONGLETS CONTEXTUELS.
   Cocher une ligne de la liste principale fait naître six onglets d'un coup. Sans
   transition, le changement est si net qu'il ne se rattache pas au geste qui l'a causé :
   on voit un écran différent, pas une conséquence. La cascade depuis la gauche raconte
   l'ordre dans lequel ils arrivent (Nielsen 1, visibilité de l'état du système).

   ⚠ `opacity` et `transform` UNIQUEMENT. `DebordementOnglets.recalculer()` mesure
   `offsetWidth` pour décider de ce qui se replie, et cette mesure a lieu PENDANT
   l'animation. Un `transform` ne change pas `offsetWidth` ; animer `width`, `padding`,
   `max-width` ou `margin` ferait mesurer des onglets rétrécis, afficherait un « + N »
   faux, puis se corrigerait tout seul — un scintillement qu'on ne sait plus expliquer. */
@keyframes jsb-onglet-deplie {
    from { opacity: 0; transform: translateY(5px) scaleX(.9); }
    to   { opacity: 1; transform: none; }
}

.jsb-onglet.se-deplie {
    /* `bottom left` : l'onglet se déplie depuis le bord du précédent, pas depuis son
       centre — c'est le sens de lecture de la rangée. */
    transform-origin: bottom left;
    animation: jsb-onglet-deplie 220ms ease-out both;
}

/* WCAG 2.3.3 : on retire le MOUVEMENT, pas le retour visuel. `animation: none` annule
   aussi le `both`, donc aucun onglet ne reste figé à `opacity: 0`. */
@media (prefers-reduced-motion: reduce) {
    .jsb-onglet { transition: none !important; }
    .jsb-onglet.se-deplie { animation: none !important; }
}

/* ONGLETS D'UN FORMULAIRE DE SAISIE — troisieme consommateur du composant ci-dessus.
   Chaque collection d'un dialogue y est un onglet, et les champs ordinaires forment
   « Principal ». RIEN N'EST REDESSINE ICI : on n'ajoute que ce qui est propre a cette
   barre-la — des panneaux, un bandeau qui tient en haut d'une colonne qui defile, un
   compte et une alerte. */

.jsb-onglets-panneau {
    padding-top: 12px;
}
/* `est-cache` et non `d-none` : `dialog-instance#displayErrors` deplie les ancetres
   `.d-none` pour montrer un champ fautif, et ferait alors apparaitre deux panneaux a
   la fois. Un panneau ne s'ouvre que par son onglet. */
.jsb-onglets-panneau.est-cache {
    display: none;
}

/* Le nombre d'elements, lisible sans ouvrir l'onglet. */
.jsb-onglet-compte {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 999px;
    background-color: var(--bg-white);
    color: var(--text-muted);
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1;
}
.jsb-onglet.active .jsb-onglet-compte {
    background-color: var(--cobalt);
    color: var(--bg-white);
}
/* LE NOMBRE ARRIVE, PUIS IL ARRIVE VRAIMENT (compte-onglet.js). Une pulsation douce tant
   que le compte est en route ; un léger rebond quand il change — le chiffre, lui, défile.
   Rien que de l'opacité et une échelle : aucune couleur nouvelle, aucune mise en page qui
   bouge. Et rien du tout pour qui a demandé moins de mouvement. */
.jsb-onglet-compte.is-chargement { animation: jsb-compte-attente 1.1s ease-in-out infinite; }
.jsb-onglet-compte.vient-de-changer { animation: jsb-compte-rebond 0.32s ease-out; }
@keyframes jsb-compte-attente { 50% { opacity: 0.4; } }
@keyframes jsb-compte-rebond { 0% { transform: scale(0.85); } 60% { transform: scale(1.15); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
    .jsb-onglet-compte.is-chargement,
    .jsb-onglet-compte.vient-de-changer { animation: none; }
}

/* UN ONGLET QUI CACHE UNE ERREUR LE DIT — par un SIGNE, et non par un `box-shadow` :
   celui-ci porte deja le lisere cobalt de `.active` ET l'anneau de focus, et une
   troisieme ecriture en effacerait une autre. Le « ! » est double d'un texte masque
   pour les lecteurs d'ecran (WCAG 1.4.1 : jamais la couleur seule). */
.jsb-onglet-alerte,
.list-tabs-more.a-une-erreur::before {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background-color: #dc3545;
    color: #ffffff;
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 1;
}
/* Un onglet fautif replie derriere « + N » emporterait son marqueur hors de vue : le
   bouton le reprend a son compte, comme il reprend deja l'onglet courant. */
.list-tabs-more.a-une-erreur::before {
    content: '!';
    margin-right: 2px;
}

/* LA BARRE TIENT EN HAUT DU FORMULAIRE. Le conteneur defilant du dialogue est
   `.form-column` (et non `.modal-body`, qui est en `overflow-y: hidden`) : sans cela,
   un long onglet « Principal » emporterait la barre hors de l'ecran, et changer
   d'onglet obligerait a remonter. Les marges negatives reprennent le padding de la
   colonne, pour que le contenu ne defile pas dans les gouttieres de la barre. */
.form-column .jsb-onglets-barre {
    position: sticky;
    /* LE DÉCALAGE VAUT LA GOUTTIÈRE HAUTE DE `.form-column`, en négatif. Avec une
       gouttière, le bandeau se calait sous le bord visible de la colonne et une bande
       restait à découvert : le contenu y défilait à côté de lui, comme s'il passait
       PAR-DESSUS. La colonne n'a plus de gouttière (grammaire du workspace) : le bandeau
       se cale donc à 0. Ce nombre suit le padding de `.form-column` : changer l'un oblige
       à changer l'autre. */
    top: 0;
    /* 110, ET NON 3 : le bandeau cree un CONTEXTE D'EMPILEMENT, dans lequel son panneau
       « + N » (z-index 1050) se retrouve enferme. Tout le groupe vaut donc ce seul nombre
       face au reste du formulaire — et l'entete collant du tableau d'une collection, lui,
       est a 100 (`thead.sticky-header`). A 3, il passait DEVANT le panneau : la liste des
       onglets replies etait coupee par la ligne « MONTANT FINAL / ACTIONS ».

       Ne pas ramener ce nombre sous 100 en croyant ranger : c'est l'entete du tableau
       qu'il doit dominer, pas un empilement arbitraire. */
    z-index: 110;
    /* DEUX SOURCES DIFFERENTES, ET C'EST VOULU : le decalage VERTICAL ci-dessus se cale
       sur la colonne qui defile, le debord HORIZONTAL ci-dessous sur le CADRE qui entoure
       (cf. `--gouttiere-cadre`). Les deux valent 0 depuis que le formulaire suit la
       grammaire du workspace ; le mecanisme reste, pour qu'un cadre qui retrouverait une
       gouttiere ne recoupe pas sa propre bordure. */
    margin-inline: calc(-1 * var(--gouttiere-cadre, 0rem));
    padding-inline: calc(8px + var(--gouttiere-cadre, 0rem));
}

/* Bouton de repli, commun aux deux barres. État ouvert marqué par le fond cobalt ET la
   rotation du chevron — jamais par la couleur seule (WCAG 1.4.1).

   38 px pour s'asseoir sur la ligne des onglets, comme eux : à 44 px il dépassait de la
   rangée et la barre contextuelle ne ressemblait à rien de connu. La CIBLE au doigt,
   elle, reste à 44 px (WCAG 2.5.5) — voir le bloc `@media (pointer: coarse)` plus bas. */
.list-tabs-more {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-height: 38px;
    padding: 8px 12px;
    margin-bottom: 0;
    border: 1px solid #dee2e6;
    border-radius: 999px;
    background-color: #ffffff;
    color: #495057;
    font-weight: 600;
    font-size: 0.875rem;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.list-tabs-more:hover {
    background-color: #e9ecef;
    color: #212529;
}
.list-tabs-more:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: 2px;
}
/* L'onglet COURANT est parmi les repliés : le bouton le dit par sa couleur ET par son
   liseré inférieur — le même trait cobalt qui souligne un onglet actif dans la rangée,
   pour que le repère soit le même où qu'il se trouve (WCAG 1.4.1 : jamais la couleur seule). */
.list-tabs-more.has-current {
    background-color: #e8f0fb;
    border-color: #0047AB;
    color: #0047AB;
    box-shadow: inset 0 -4px 0 #0047AB;
}

.list-tabs-more[aria-expanded="true"] {
    background-color: #0047AB;
    border-color: #0047AB;
    color: #ffffff;
}
.list-tabs-more-chevron {
    width: 14px;
    height: 14px;
    transition: transform .15s ease;
}
.list-tabs-more[aria-expanded="true"] .list-tabs-more-chevron {
    transform: rotate(180deg);
}

/* 38 px de DESSIN pour tenir la ligne des onglets, 44 px de CIBLE au doigt : les 3 px
   manquants de chaque côté sont repris par une zone invisible, sans déplacer le bouton
   d'un pixel (WCAG 2.5.5). Même procédé que `.context-toolbar .btn::after`.
   `position: relative` est posé ICI seulement : le panneau des onglets repliés s'ancre
   sur le BANDEAU, pas sur le bouton — il n'est donc pas affecté. */
@media (pointer: coarse) {
    .list-tabs-more {
        position: relative;
    }
    .list-tabs-more::after {
        content: '';
        position: absolute;
        inset: -3px;
    }
}

/* Panneau des onglets repliés : surcouche ancrée sous le bouton, jamais une modale —
   le geste est un simple aiguillage, il ne mérite pas de piéger le focus. */
.list-tabs-overflow-panel {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: 1050;
    min-width: 240px;
    max-width: 320px;
    max-height: 60vh;
    overflow-y: auto;
    padding: 6px;
    background-color: #ffffff;
    border: 1px solid #dee2e6;
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .12);
}
/* UNE RANGÉE : l'entrée qui mène à l'onglet, et la croix qui le ferme.
   L'entrée occupait toute la largeur et était elle-même un bouton ; un onglet replié
   n'avait donc AUCUN moyen d'être fermé — il fallait d'abord le ramener dans la barre,
   donc élargir la fenêtre ou fermer un autre onglet. La croix vit à côté, hors du
   bouton : un bouton dans un bouton n'est pas du HTML valide. */
.list-tabs-overflow-row {
    display: flex;
    align-items: center;
    gap: 2px;
}
/* La croix ne se révèle qu'au survol de SA rangée — visible en permanence, elle
   ferait d'un panneau de navigation une liste d'actions destructrices. Elle reste
   toutefois montrée dès qu'elle a le focus, sans quoi elle serait inatteignable au
   clavier (WCAG 2.4.7). */
.list-tabs-overflow-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    padding: 0;
    border: none;
    border-radius: 8px;
    background-color: transparent;
    color: #6c757d;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
}
.list-tabs-overflow-row:hover .list-tabs-overflow-close,
.list-tabs-overflow-close:focus-visible {
    opacity: 1;
}
.list-tabs-overflow-close:hover {
    background-color: #f8d7da;
    color: #dc3545;
}
.list-tabs-overflow-close:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: -2px;
}
.list-tabs-overflow-close svg {
    width: 14px;
    height: 14px;
}
.list-tabs-overflow-item {
    display: flex;
    align-items: center;
    gap: 8px;
    /* `flex: 1` et non `width: 100%` : la rangée doit pouvoir réserver la place de
       la croix sans que le libellé la pousse dehors. */
    flex: 1 1 auto;
    min-width: 0;
    min-height: 44px;
    padding: 8px 12px;
    border: none;
    border-radius: 8px;
    background-color: transparent;
    color: #212529;
    font-size: 0.9rem;
    text-align: left;
    cursor: pointer;
}
.list-tabs-overflow-item:hover {
    background-color: #e8f0fb;
    color: #0047AB;
}
/* Rubrique COURANTE dans le panneau : même retour visuel que la rubrique ouverte du
   menu de navigation — gras, cobalt, fond cobalt léger. Le liseré de gauche double
   l'information pour qui ne distingue pas les couleurs (WCAG 1.4.1). */
.list-tabs-overflow-item.is-current {
    background-color: #e8f0fb;
    color: #0047AB;
    font-weight: 600;
    box-shadow: inset 3px 0 0 #0047AB;
}
.list-tabs-overflow-item.is-current:hover {
    background-color: #e8f0fb;
}

.list-tabs-overflow-item:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: -2px;
}
.list-tabs-overflow-item .jsb-onglet-icone svg {
    width: 1.05em;
    height: 1.05em;
}


/* --- 3.2 Barre d'outils contextuelle --- */
/* ================================================================== */
/* SECTION : STYLE DE LA BARRE D'OUTILS                               */
/* ================================================================== */

.context-toolbar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem;
    /* La barre passe à la ligne plutôt que de sortir du panneau. Une dizaine de
       boutons tenaient sur une ligne unique tant que la fenêtre était large ; plus
       étroite, les derniers — souvent « Supprimer », « Exporter » — quittaient
       l'écran sans retour possible. Les barres voisines (contrôles de liste, chips
       de filtre) enroulent déjà : on aligne celle-ci sur elles.
       `width: fit-content` est conservé : sans lui, la barre s'étirerait sur toute
       la largeur et ses séparateurs flotteraient loin des boutons. */
    flex-wrap: wrap;
    width: fit-content;
    max-width: 100%;
}

/* --- Base commune de tous les boutons toolbar --- */
.context-toolbar .btn {
    border-radius: 6px;
    border: none;
    /* padding: 0.4rem → zone cliquable ≈ 35px (22px icône + 2×6.4px) — compact mais utilisable */
    padding: 0.4rem;
    line-height: 1;
    background-color: transparent;
    transition: background-color 0.15s ease-in-out, transform 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
    position: relative;
    overflow: hidden;
}

/* --- Focus clavier visible — Cobalt Joseara — WCAG 2.4.7 --- */
.context-toolbar .btn:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: 2px;
    box-shadow: none;
}

.context-toolbar .btn:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
}

/* Boutons DISABLED : opacité sans masquage — signale l'action non disponible */
.context-toolbar .btn:disabled,
.context-toolbar .btn[aria-disabled="true"] {
    opacity: 0.4;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}

/* --- Variante neutre : .btn-default (actions standard) --- */
.context-toolbar .btn-default .toolbar-icon {
    color: #495057; /* contraste ≥ 6.3:1 sur fond blanc — WCAG 1.4.11 */
}
.context-toolbar .btn-default:hover {
    background-color: rgba(0, 71, 171, 0.08); /* cobalt #0047AB à 8% */
}
.context-toolbar .btn-default:hover .toolbar-icon {
    color: #0047AB; /* Cobalt Joseara — identité de marque au survol */
}

/* --- Variante danger : .btn-icon-danger (Quitter, Supprimer) --- */
.context-toolbar .btn-icon-danger .toolbar-icon {
    color: #c9302c; /* rouge danger — contraste ≥ 5.1:1 sur fond blanc */
}
.context-toolbar .btn-icon-danger:hover {
    background-color: rgba(220, 53, 69, 0.08);
}
.context-toolbar .btn-icon-danger:hover .toolbar-icon {
    color: #a02020;
}

/* --- Séparateur vertical entre groupes --- */
.toolbar-separator {
    width: 1px;
    height: 22px;
    background-color: #dee2e6;
    margin: 0 0.125rem;
    flex-shrink: 0;
}

/* ============================================================================
   FAMILLES D'ACTIONS — bouton déroulant de la barre d'outils
   ----------------------------------------------------------------------------
   Une famille d'actions tient sur UN bouton (icône + chevron) qui déploie ses
   membres, libellés cette fois : la barre garde une largeur prévisible quel que
   soit le nombre d'actions déclarées. Même règle de regroupement que le menu
   contextuel (actions-groupees.js), présentation différente.
   ============================================================================ */
.toolbar-groupe {
    position: relative;
    display: inline-flex;
}

/* Chevron « ce bouton déploie un menu » : pseudo-élément, donc insensible au
   remplacement d'innerHTML opéré par le circuit d'icônes sur le bouton. */
.toolbar-groupe-bouton {
    padding-right: 1.05rem !important;
}
.toolbar-groupe-bouton::after {
    content: "";
    position: absolute;
    right: 0.35rem;
    top: 50%;
    width: 0;
    height: 0;
    border-left: 3.5px solid transparent;
    border-right: 3.5px solid transparent;
    border-top: 4px solid #6c757d;
    transform: translateY(-1px);
    pointer-events: none;
}
.toolbar-groupe-bouton:hover::after,
.toolbar-groupe-bouton[aria-expanded="true"]::after {
    border-top-color: #0047AB;
}
.toolbar-groupe-bouton[aria-expanded="true"] {
    background-color: rgba(0, 71, 171, 0.12);
}

/* POSITION FIXE, POSÉE PAR LE CONTRÔLEUR (menu-flottant.js). En `absolute`, le menu
   restait prisonnier du panneau et s'en faisait rogner ; et la règle qui le rabattait
   « à droite quand il est le dernier bouton » supposait qu'un dernier bouton est en fin
   de barre — faux dès que la barre en porte peu, le bouton étant alors à gauche. */
/* Même règle que le menu contextuel : une entrée par ligne, jamais coupée. */
.toolbar-groupe-menu li {
    white-space: nowrap;
}

.toolbar-groupe-menu {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1050;          /* au-dessus de la liste, sous les modales Bootstrap */
    min-width: 232px;
    margin: 0;
    padding: 4px 0;
    list-style: none;
    background-color: #ffffff;
    border: 1px solid #dee2e6;
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.08);
}


.toolbar-groupe-menu [role="menuitem"] {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-height: 40px;       /* cible confortable (Opquast / WCAG 2.5.8) */
    padding: 0.4rem 0.9rem;
    margin: 1px 4px;
    border-radius: 4px;
    color: #212529;
    font-size: 0.88rem;
    white-space: nowrap;
    cursor: pointer;
    outline: none;
    transition: background-color 0.12s ease-in-out, color 0.12s ease-in-out;
}
.toolbar-groupe-menu [role="menuitem"]:hover,
.toolbar-groupe-menu [role="menuitem"]:focus {
    background-color: rgba(0, 71, 171, 0.08);
    color: #0047AB;
}
.toolbar-groupe-menu [role="menuitem"]:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: -2px;
}

/* Les icônes du menu reprennent la taille du texte, pas celle de la barre :
   .toolbar-icon (22px) est posée par le circuit d'icônes sur tous les SVG. */
.toolbar-groupe-menu-icone {
    display: inline-flex;
    flex-shrink: 0;
    color: #495057;
}
.toolbar-groupe-menu-icone .toolbar-icon {
    width: 18px;
    height: 18px;
}
.toolbar-groupe-menu [role="menuitem"]:hover .toolbar-groupe-menu-icone {
    color: #0047AB;
}

/* --- Icônes SVG dans les boutons toolbar --- */
.toolbar-icon {
    width: 22px;
    height: 22px;
    display: block;
    transition: color 0.15s ease-in-out;
}

/* --- Effet d'onde (Ripple) --- */
.ripple {
    position: absolute;
    border-radius: 50%;
    background-color: rgba(0, 0, 0, 0.12);
    transform: scale(0);
    animation: ripple-effect 0.6s linear;
    pointer-events: none;
}

@keyframes ripple-effect {
    to {
        transform: scale(4);
        opacity: 0;
    }
}

/* --- 3.3 Menu contextuel (clic droit) --- */
/* ================================================================== */
/* SECTION : MENU CONTEXTUEL (MODE SOMBRE)                            */
/* ================================================================== */

.context-target {
    cursor: context-menu;
    user-select: none;
}

/* --- Conteneur principal --- */
.context-menu {
    position: fixed;
    background-color: #1e2227;   /* fond légèrement plus profond pour le contraste */
    color: #f8f9fa;
    border: 1px solid #3d4349;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35), 0 2px 6px rgba(0, 0, 0, 0.2);
    min-width: 200px;
    z-index: 1000;
    border-radius: 8px;          /* arrondi moderne */
    padding: 4px 0;
    outline: none;               /* focus géré programmatiquement */
}

/* --- Liste principale --- */
.context-menu ul,
.context-menu [role="menu"] {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* --- Items actionnables (role="menuitem") --- */
.context-menu [role="menuitem"],
.context-menu li:not(.separator):not(.specific-actions-wrapper) {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 9px 14px;
    /* UNE ENTRÉE DE MENU TIENT SUR UNE LIGNE. « Compteurs de congés » se coupait en
       deux au milieu du groupe nominal, et deux entrées voisines sur deux lignes
       chacune se lisent comme quatre : on cherche alors où finit l'une et où
       commence l'autre. La largeur minimale du menu n'est qu'un plancher — le menu
       s'élargit pour tenir son libellé le plus long. */
    white-space: nowrap;
    cursor: pointer;
    color: #f8f9fa;              /* contraste 16:1 sur #1e2227 — WCAG AA ✓ */
    border-radius: 4px;
    margin: 1px 4px;
    transition: background-color 0.12s ease-in-out, color 0.12s ease-in-out;
    outline: none;
}

/* --- Survol & focus clavier (identiques pour cohérence) --- */
.context-menu [role="menuitem"]:hover,
.context-menu [role="menuitem"]:focus,
.context-menu li:not(.separator):not(.specific-actions-wrapper):hover {
    background-color: #0047AB;  /* Cobalt Joseara — identité de marque */
    color: #ffffff;
}

/* Focus clavier visible — WCAG 2.4.7 */
.context-menu [role="menuitem"]:focus-visible {
    outline: 2px solid rgba(255, 255, 255, 0.7);
    outline-offset: -2px;
}

/* --- Item "Supprimer" : danger visuel distinct --- */
.context-menu [role="menuitem"].is-danger {
    color: #f87171;              /* rouge doux visible sur fond sombre */
}
.context-menu [role="menuitem"].is-danger:hover,
.context-menu [role="menuitem"].is-danger:focus {
    background-color: rgba(220, 53, 69, 0.2);
    color: #fca5a5;
}

/* --- Séparateurs --- */
.context-menu [role="separator"],
.context-menu li.separator {
    border: none;
    border-top: 1px solid #3d4349;
    margin: 4px 0;
    padding: 0;
    cursor: default;
}

/* ============================================================================
   FAMILLES D'ACTIONS — sous-menu du menu contextuel
   ----------------------------------------------------------------------------
   Une famille d'actions (les mouvements d'une police, par exemple) tient sur UN
   item qui déploie ses membres : le menu garde une hauteur lisible quel que soit
   le nombre d'actions déclarées par la rubrique. Pattern WAI-ARIA Menu — l'item
   porte aria-haspopup/aria-expanded, la sous-liste role="menu".
   ============================================================================ */
.context-menu .context-menu-groupe {
    position: relative;
}

/* Chevron « ouvre un sous-menu » : pseudo-élément, donc insensible au
   remplacement d'innerHTML opéré par le circuit d'icônes. */
.context-menu .context-menu-groupe-chevron::after {
    content: "›";
    font-size: 1.1rem;
    line-height: 1;
    opacity: 0.7;
}

.context-menu .context-submenu {
    display: none;
    position: absolute;
    top: -5px;
    left: 100%;
    margin-left: 2px;
    min-width: 220px;
    background-color: #1e2227;
    border: 1px solid #3d4349;
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35), 0 2px 6px rgba(0, 0, 0, 0.2);
    padding: 4px 0;
    z-index: 1;
}

.context-menu .context-menu-groupe.is-open > .context-submenu {
    display: block;
}

/* Rabat à gauche : le menu contextuel s'ouvre au pointeur, souvent près du bord. */
.context-menu .context-menu-groupe.submenu-left > .context-submenu {
    left: auto;
    right: 100%;
    margin-left: 0;
    margin-right: 2px;
}

/* L'item de famille garde le fond cobalt tant que son sous-menu est déployé :
   sans cela, le survol d'un membre « éteint » le parent et le chemin se perd. */
.context-menu .context-menu-groupe.is-open {
    background-color: #0047AB;
    color: #ffffff;
}

/* --- Wrapper pour les actions spécifiques injectées (li > ul) --- */
.context-menu .specific-actions-wrapper {
    padding: 0;
    cursor: default;
}
.context-menu .specific-actions-wrapper ul {
    padding: 0;
    margin: 0;
}

/* --- Icône décorative (masquée aux AT par aria-hidden) --- */
.context-menu-icon {
    width: 20px;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: inherit;
    opacity: 0.85;
}

/* --- Raccourcis clavier : contraste ≈ 5.1:1 sur #1e2227 — WCAG 1.4.3 ✓ --- */
.context-menu-shortcut {
    min-width: 20px;
    text-align: right;
    color: rgba(248, 249, 250, 0.6);
    font-size: 0.8rem;
    font-family: monospace;
    letter-spacing: 0.03em;
    margin-left: auto;
}

/* Au survol cobalt, le raccourci reste lisible */
.context-menu [role="menuitem"]:hover .context-menu-shortcut,
.context-menu [role="menuitem"]:focus .context-menu-shortcut {
    color: rgba(255, 255, 255, 0.75);
}

/* --- 3.4 Moteur de recherche — jsb-search-bar --- */

.jsb-search-bar { width: 100%; }

/* Ligne UNIQUE : saisie + critère + filtres actifs + actions. Le gabarit reprend
   celui des barres sœurs — hauteur --jsb-control-h et rayon 8px de .jsb-control-pill
   (Cohérence, Bastien & Scapin) — mais la bordure reste VISIBLE au repos : un champ
   de saisie doit se reconnaître comme éditable (Signifiance / affordance).
   `nowrap` + hauteur fixe = aucune déformation possible, quel que soit le nombre de
   filtres actifs (au-delà d'un filtre, ils se replient dans le volet). */
.jsb-search-bar__row {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    /* Espacement UNIQUE par `gap` (et non des marges par élément) : il disparaît de
       lui-même quand un élément se replie (zone de filtres vide), ce qui rend la
       répartition de la largeur prévisible dans un panneau étroit. */
    gap: 0.375rem;
    min-height: var(--jsb-control-h);
    background: #ffffff;
    border: 1.5px solid #dee2e6;
    border-radius: 8px;
    padding: 0 0.5rem;
    /* Garde-fou de non-déformation : même dans un panneau pathologiquement étroit,
       aucun contenu ne peut déborder de la pilule. `clip` (et non `hidden`) évite de
       créer un conteneur de défilement ; la marge de 3px préserve les anneaux de focus
       des boutons de bord (WCAG 2.4.7). */
    overflow: clip;
    overflow-clip-margin: 3px;
    /* Conteneur de requête pour la dégradation progressive ci-dessous. Posé sur la
       LIGNE et non sur .jsb-search-bar : `container-type` implique `contain: layout`,
       qui ferait de l'élément le bloc conteneur des descendants `position: fixed` — le
       volet des filtres (frère de la ligne) doit rester ancré au viewport. */
    container-type: inline-size;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

/* Panneau très étroit : plutôt que d'écraser le champ ou de rogner les actions, on
   abandonne d'abord le superflu — le libellé « Avancée » (l'icône et son aria-label
   subsistent, l'action reste identifiable) puis la largeur du sélecteur de critère.
   Adaptabilité (Bastien & Scapin) : l'essentiel — saisir, voir ses filtres, réinitialiser
   — survit à toutes les largeurs. */
@container (max-width: 400px) {
    .jsb-search-bar__btn-label { display: none; }
    .jsb-search-bar__criterion { max-width: 6rem; min-width: 3.5rem; }
}
.jsb-search-bar__row:focus-within {
    border-color: #0047AB;
    box-shadow: 0 0 0 3px rgba(0, 71, 171, 0.15);
}

.jsb-search-bar__icon {
    display: flex;
    align-items: center;
    color: #6c757d;
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    transition: color 0.2s ease;
}
.jsb-search-bar__row:focus-within .jsb-search-bar__icon { color: #0047AB; }

/* Seul élément élastique de la ligne : il absorbe l'espace libre ET, en cas de manque,
   il cède EN PREMIER (base 0 = il ne pèse pas dans la répartition du déficit) jusqu'à
   son plancher de 5rem. C'est ce qui garantit qu'un badge de filtre s'affiche à sa
   largeur naturelle tant que la place existe. */
.jsb-search-bar__input {
    flex: 1 1 0;
    border: none;
    background: transparent;
    font-size: 0.9rem;
    color: #212529;
    padding: 0.25rem 0;
    min-width: 4.5rem;
}
.jsb-search-bar__input:focus { outline: none; box-shadow: none; }
.jsb-search-bar__input::placeholder { color: #adb5bd; font-style: italic; }

.jsb-search-bar__divider {
    width: 1px;
    height: 1.25rem;
    background: #dee2e6;
    flex-shrink: 0;
}

.jsb-search-bar__criterion {
    /* Premier à céder sous la pression (facteur 4) : le critère visé est déjà rappelé
       par le placeholder du champ (« Rechercher dans "Réf. Police"… »), alors qu'un
       libellé de filtre tronqué, lui, n'est plus lisible. */
    flex: 0 4 auto;
    min-width: 4rem;
    border: none;
    background: transparent;
    font-size: 0.82rem;
    color: #495057;
    padding: 0.15rem 0.25rem;
    max-width: 150px;
    cursor: pointer;
}
.jsb-search-bar__criterion:focus { outline: none; }

.jsb-search-bar__actions {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    flex-shrink: 0;
}

.jsb-search-bar__btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    border: 1px solid transparent;
    background: transparent;
    padding: 0.25rem 0.5rem;
    border-radius: 0.375rem;
    cursor: pointer;
    font-size: 0.8rem;
    font-weight: 500;
    color: #6c757d;
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
    /* Tient dans la hauteur unique de la ligne (--jsb-control-h) tout en gardant une
       cible confortable (WCAG 2.5.8 : ≥ 24px). */
    min-height: 27px;
}
.jsb-search-bar__btn svg { width: 15px; height: 15px; }

.jsb-search-bar__btn--advanced {
    color: #0047AB;
    border-color: rgba(0, 71, 171, 0.3);
    background-color: #e8f0fb;
}
.jsb-search-bar__btn--advanced:hover {
    background-color: #0047AB;
    border-color: #0047AB;
    color: #ffffff;
}

.jsb-search-bar__btn--reset:hover {
    background-color: #e9ecef;
    color: #212529;
}

.jsb-search-bar__btn-label { font-size: 0.8rem; }
.jsb-search-bar__btn:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: 1px;
}

/* Zone des filtres actifs, DANS la ligne : largeur naturelle (jamais élastique) et
   nowrap — elle contient au plus UN élément (badge ou pastille compteur). */
.jsb-search-bar__filters {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    /* Largeur naturelle, mais compressible : sous la pression, c'est l'ellipsis du
       badge qui absorbe (le champ de saisie garde son plancher de 5rem). */
    flex: 0 1 auto;
    min-width: 0;
    gap: 0.375rem;
}
/* Aucun filtre → le div interne reste vide → la zone disparaît (pas de séparateur
   ni de trou fantôme dans la ligne). */
.jsb-search-bar__filters:has(div:empty) { display: none; }
.jsb-search-bar__filters > div {
    display: flex;
    align-items: center;
    min-width: 0;
}

.jsb-filter-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    background-color: #e8f0fb;
    color: #0047AB;
    border: 1px solid rgba(0, 71, 171, 0.25);
    border-radius: 2rem;
    padding: 0.1rem 0.35rem 0.1rem 0.6rem;
    font-size: 0.78rem;
    font-weight: 500;
    line-height: 1.4;
    max-width: 15rem;
    /* Indispensable pour que l'ellipsis puisse jouer : sans min-width:0, la largeur
       min-content du libellé empêcherait le badge de se comprimer et pousserait les
       boutons d'action hors de la pilule. */
    min-width: 0;
}
/* Un libellé long (« Mon portefeuille : Portefeuille de Serge ») est tronqué plutôt
   que d'étirer la ligne ; le texte complet reste accessible en infobulle (title). */
.jsb-filter-badge__text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.jsb-filter-badge__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0; /* jamais rogné : c'est la seule sortie du filtre (Contrôle explicite) */
    width: 18px;
    height: 18px;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    opacity: 0.65;
    cursor: pointer;
    padding: 0;
    font-size: 1rem;
    line-height: 1;
    transition: opacity 0.15s, background-color 0.15s;
}
.jsb-filter-badge__remove:hover {
    opacity: 1;
    background-color: rgba(0, 71, 171, 0.18);
}
.jsb-filter-badge__remove:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: 1px;
    opacity: 1;
}

/* Pastille compteur : à partir de deux filtres actifs, le détail se replie ici.
   Nielsen #1 (le nombre de critères reste visible) + #8 (minimalisme : le détail est
   à un clic). Charte : teinte cobalt au repos, cobalt plein à l'ouverture/survol. */
.jsb-filters-chip {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0; /* compacte par nature : jamais rognée (le compteur doit rester lisible) */
    gap: 0.35rem;
    min-height: 25px;
    padding: 0 0.4rem 0 0.6rem;
    border: 1px solid rgba(0, 71, 171, 0.25);
    border-radius: 2rem;
    background-color: #e8f0fb;
    color: #0047AB;
    font-size: 0.78rem;
    font-weight: 600;
    font-family: inherit;
    line-height: 1.4;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
/* Chevron dessiné en bordures CSS plutôt qu'en icône : la pastille est rendue en JS
   (innerHTML), donc hors du circuit d'icônes serveur — aucune icône à y injecter. */
.jsb-filters-chip::after {
    content: "";
    border: 4px solid transparent;
    border-top-color: currentColor;
    margin-left: 0.1rem;
    transform: translateY(2px);
    transition: transform 0.15s ease;
}
.jsb-filters-chip:hover,
.jsb-filters-chip[aria-expanded="true"] {
    background-color: #0047AB;
    border-color: #0047AB;
    color: #ffffff;
}
.jsb-filters-chip[aria-expanded="true"]::after {
    transform: translateY(-2px) rotate(180deg);
}
.jsb-filters-chip:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: 2px;
}
.jsb-filters-chip__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 17px;
    height: 17px;
    padding: 0 0.2rem;
    border-radius: 999px;
    background-color: rgba(0, 71, 171, 0.18);
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
}
.jsb-filters-chip:hover .jsb-filters-chip__count,
.jsb-filters-chip[aria-expanded="true"] .jsb-filters-chip__count {
    background-color: rgba(255, 255, 255, 0.28);
}

/* Volet des critères actifs : même surface flottante que les menus de la barre
   d'outils (blanc, bordure #dee2e6, rayon 8px, ombre portée, z-index 1050 = au-dessus
   des listes, sous les modales Bootstrap). Position `fixed` posée par le contrôleur :
   le volet échappe ainsi au découpage du bandeau collant. */
.jsb-search-bar__popover {
    position: fixed;
    z-index: 1050;
    min-width: 260px;
    max-width: 360px;
    padding: 0.25rem 0;
    background-color: #ffffff;
    border: 1px solid #dee2e6;
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.08);
}
.jsb-search-bar__popover[hidden] { display: none; }

.jsb-search-bar__popover-title {
    margin: 0;
    padding: 0.35rem 0.75rem 0.3rem;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #6c757d;
}

.jsb-search-bar__popover-list {
    margin: 0;
    padding: 0;
    list-style: none;
    max-height: 15rem;
    overflow-y: auto;
}
.jsb-search-bar__popover-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 32px;
    padding: 0.2rem 0.5rem 0.2rem 0.75rem;
    font-size: 0.83rem;
    color: #212529;
}
.jsb-search-bar__popover-item:hover { background-color: rgba(0, 71, 171, 0.06); }
.jsb-search-bar__popover-text {
    flex: 1 1 auto;
    min-width: 0;
    word-break: break-word;
}
/* Cible de retrait confortable dans le volet (WCAG 2.5.8) : 24px, contre 18px sur le
   badge compact de la ligne. */
.jsb-search-bar__popover-item .jsb-filter-badge__remove {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    color: #6c757d;
}
.jsb-search-bar__popover-item .jsb-filter-badge__remove:hover {
    color: #0047AB;
    background-color: rgba(0, 71, 171, 0.12);
}

.jsb-search-bar__popover-footer {
    margin-top: 0.25rem;
    border-top: 1px solid #e9ecef;
    padding-top: 0.25rem;
}
.jsb-search-bar__popover-clear {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    min-height: 34px;
    padding: 0.25rem 0.75rem;
    border: none;
    background: transparent;
    color: #0047AB;
    font-family: inherit;
    font-size: 0.83rem;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    transition: background-color 0.12s ease;
}
.jsb-search-bar__popover-clear:hover { background-color: rgba(0, 71, 171, 0.08); }
.jsb-search-bar__popover-clear:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: -2px;
}
.jsb-search-bar__popover-clear svg { width: 15px; height: 15px; }

.jsb-search-dialog__header {
    background-color: #0047AB;
    color: #ffffff;
}
.jsb-search-dialog__header .modal-title {
    display: flex;
    align-items: center;
    font-size: 1rem;
    font-weight: 600;
}

.jsb-btn-cobalt {
    background-color: #0047AB;
    border-color: #0047AB;
    color: #ffffff;
}
.jsb-btn-cobalt:hover, .jsb-btn-cobalt:focus {
    background-color: #003380;
    border-color: #003380;
    color: #ffffff;
}

/* --- 3.5 Barre des contrôles de liste (pagination + totaux) --- */

/* Socle commun des composants de contrôle (statut, pagination, totaux, tracks de chips).
   Trois garanties partagées (DRY) : hauteur unique (--jsb-control-h), centrage vertical, et
   bordure réservée en permanence (transparente) révélée au survol/focus — retour visuel simple
   et cohérent de la présence du curseur SANS décalage de mise en page (ergonomie : Nielsen #1
   visibilité de l'état du système, Bastien & Scapin > Guidage ; parité clavier via :focus-within
   pour WCAG). */
:root { --jsb-control-h: 35px; }
.jsb-control-pill {
    display: inline-flex;
    align-items: center;
    min-height: var(--jsb-control-h);
    padding: 0 0.5rem;
    border: 1px solid transparent;
    border-radius: 8px;
    transition: border-color .15s ease, background-color .15s ease;
}
.jsb-control-pill:hover,
.jsb-control-pill:focus-within { border-color: #ced4da; }

/* Barre de contrôles adaptative : statut, pagination et totaux s'empilent À GAUCHE et se
   replient sur la ligne suivante (le dernier composant d'abord) quand la place manque —
   plus aucun écrasement ni calage à droite.

   ⚠ ELLE N'EST PLUS COLLANTE, ET N'A PLUS À L'ÊTRE : seul le CORPS du tableau défile
   désormais (voir « SEUL LE TABLEAU DÉFILE » plus bas). Elle est simplement posée
   au-dessus de la zone défilante, où rien ne peut la faire bouger. Le décalage mesuré
   (--jsb-pgbar-h) qui servait à décoller l'entête n'a plus d'objet. */
.jsb-list-controls-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    flex: 0 0 auto;
    background: #ffffff;
    border-bottom: 1px solid #dee2e6;
    padding: 0.25rem 0.75rem;
    min-height: 38px;
}
/* Tous les composants gardent leur largeur naturelle (flex: 0 0 auto) et s'alignent à
   gauche : à défaut de place, c'est le dernier bloc (totaux) qui passe à la ligne. */
.jsb-list-controls-bar__pagination { flex: 0 0 auto; }
/* Liste mono-page : le conteneur pagination est vidé par le list-manager → on masque
   la pastille pour ne pas laisser une pastille vide (padding + survol-bordure fantôme). */
.jsb-list-controls-bar__pagination:empty { display: none; }
.jsb-list-controls-bar__totals { flex: 0 0 auto; }
.jsb-list-controls-bar__display {
    flex: 0 0 auto;
    font-size: 0.8rem;
    color: #6c757d;
    white-space: nowrap;
}

/* Cluster de navigation (saut de page + précédent/suivant) : regroupé visuellement
   dans une pastille grise pour le distinguer du décompte d'éléments (Nielsen #8,
   esthétique/minimalisme). Survol/focus des boutons en teinte cobalt de marque
   (Règle 4 de la charte couleurs) plutôt que le gris Bootstrap par défaut. */
.jsb-pagination-nav {
    background: #f8f9fa;
    border-radius: 999px;
    padding: 0.1rem 0.5rem;
}
/* Contrôles compacts : sans cette réduction, l'input `form-control-sm` (~35px) et les
   `btn-sm` dépassent le socle et rendent la pastille de pagination plus haute que les
   autres. On neutralise le min-height Bootstrap et on réduit police/interligne pour que
   les contrôles tiennent dans la hauteur unique --jsb-control-h (parité avec les chips). */
.jsb-pagination-nav .btn-sm,
.jsb-pagination-nav .form-control-sm {
    min-height: 0;
    padding-top: 0.15rem;
    padding-bottom: 0.15rem;
    font-size: 0.8rem;
    line-height: 1.5;
}
.jsb-pagination-nav .btn-outline-secondary:not(:disabled):hover,
.jsb-pagination-nav .btn-outline-secondary:not(:disabled):focus-visible {
    color: #ffffff;
    background-color: #0047AB;
    border-color: #0047AB;
}
.jsb-pagination-nav .btn-outline-secondary:focus-visible {
    box-shadow: 0 0 0 0.2rem rgba(0, 71, 171, 0.35);
}

/* Badge d'état des lignes de liste (déclaré par le canvas : ex. urgence de recouvrement
   des tranches). Couleurs sémantiques de la charte : danger (retard avéré), orange
   (échéance imminente), warning (échéance proche), neutre (lointaine), succès (réglée). */
.jsb-state-badge {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    vertical-align: middle;
    background: #e9ecef;
    color: #495057;
}
.jsb-state-badge--critique { background: #f8d7da; color: #842029; }
.jsb-state-badge--elevee { background: #e69500; color: #ffffff; }
.jsb-state-badge--moderee { background: #fff3cd; color: #664d03; }
.jsb-state-badge--faible { background: #e9ecef; color: #495057; }
.jsb-state-badge--reglee { background: #d1e7dd; color: #0f5132; }
/* Rétro partenaire à payer : information d'action (décaissement), teinte cobalt de marque. */
.jsb-state-badge--retro { background: #e8f0fb; color: #0047AB; }
/* Commission exigible : prime encaissée par l'assureur → à collecter MAINTENANT (cobalt plein). */
.jsb-state-badge--exigible { background: #0047AB; color: #ffffff; }
/* Étape du pipe (Pistes) : surface claire et liseré de la couleur de l'étape. Le libellé
   porte le sens ; la couleur ne fait que le confirmer (WCAG 1.4.1). */
.jsb-state-badge--etape-a_coter,
.jsb-state-badge--etape-en_cotation,
.jsb-state-badge--etape-offres_chiffrees,
.jsb-state-badge--etape-transformees {
    background: var(--bg-white);
    color: var(--text-dark-secondary);
    border: 1px solid var(--border-light);
    padding-left: 0.6rem;
    box-shadow: inset 3px 0 0 var(--etape);
}
.jsb-state-badge--etape-a_coter { --etape: var(--etape-a_coter); }
.jsb-state-badge--etape-en_cotation { --etape: var(--etape-en_cotation); }
.jsb-state-badge--etape-offres_chiffrees { --etape: var(--etape-offres_chiffrees); }
.jsb-state-badge--etape-transformees { --etape: var(--etape-transformees); }

/* Chips de filtre rapide : rangée dédiée sous la barre de contrôles, juste avant la
   liste (la barre de contrôles — statut + pagination + totaux — était saturée).
   Charte : neutre = surface blanche/bordure standard, actif = cobalt (#0047AB) texte blanc. */
.jsb-preset-filters-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    /* ⚠ PLUS DE `position: sticky` : la rangée est hors de la zone défilante, donc
       toujours visible sans avoir à se recoller à quoi que ce soit. */
    flex: 0 0 auto;
    background: #ffffff;
    border-bottom: 1px solid #dee2e6;
    padding: 0.35rem 0.75rem;
}
/* Groupe de boutons toggle : piste grise en pilule, bouton actif = pastille
   cobalt (#0047AB) texte blanc, survol = teinte cobalt + légère élévation.
   Icône optionnelle (clé `icon` des options du canvas) devant le libellé.
   Logique inchangée (is-active/aria-pressed). */
/* -- QUAND LES CHIPS NE TIENNENT PAS : LES VALEURS DEFILENT ----------------------
   Une rubrique de liste peut aligner dix chips (« Général, Police, Tranche, Prime,
   Commission, Taxe courtier, Taxe assureur… »), et l'écran des colonnes de l'Échange
   autant. Dans une colonne étroite, les derniers sortaient du panneau et le
   DEFORMAIENT : ni visibles, ni atteignables. Un filtre qu'on ne peut pas voir est un
   filtre qui n'existe pas.

   Le retour à la ligne — le comportement d'origine — ne convient pas : la pilule
   devenait un pavé de deux ou trois rangées qui mangeait la liste par le haut, alors
   que c'est justement la place qui manque.

   -- LA STRUCTURE, ET LES DEUX ERREURS QU'ELLE CORRIGE ----------------------------
       .jsb-preset-filters-wrap        <- C'EST ELLE, la pilule : fond, arrondi, marge
            .jsb-preset-filters__titre <- le nom du critere, HORS du defilement
            .jsb-preset-zone           <- referent des fleches, ne defile pas
                 .jsb-preset-filters   <- la seule chose qui defile : les valeurs
                 bouton precedent / suivant, EN SURCOUCHE

   1. LE TITRE EST DEHORS. Il avait d'abord ete laisse dans la zone, COLLE a gauche :
      les valeurs lui passaient dessous, et ce chevauchement de deux textes etait
      desagreable a l'oeil autant qu'illisible une fraction de seconde. Dehors, la zone
      de defilement COMMENCE apres lui : plus rien ne peut passer sous rien.

   2. LES FLECHES SONT EN SURCOUCHE, et c'est ce qui rend le comportement stable. Posees
      dans le flux, elles prenaient une cinquantaine de pixels — ceux-la memes qu'on
      mesurait pour decider s'il fallait defiler. Une pilule qui tenait tout juste se
      mettait donc a defiler A CAUSE de ses propres fleches, et n'en sortait plus : on a
      vu « Tout decocher » ampute alors que la place ne manquait pas. En surcouche,
      les afficher ne change aucune largeur, et la boucle n'existe plus.

   /!\ ET CETTE MISE EN PAGE NE VAUT QUE LA OU ON L'A DEMANDEE : elle est portee par
   `.jsb-preset-filters-wrap`, que `chips-defilement.js` cree autour des groupes qu'il
   equipe. Ailleurs, une pilule reste une pilule ordinaire qui revient a la ligne. */

.jsb-preset-filters {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    background: #f0f2f5;
    border-radius: 26px;
    padding: 4px;
    gap: 0;
    flex-shrink: 0;
}

/* L'enveloppe PREND l'aspect de la pilule : titre et valeurs s'y lisent d'un bloc,
   alors qu'ils appartiennent à deux boîtes différentes. */
.jsb-preset-filters-wrap {
    display: inline-flex;
    align-items: center;
    /* Elle accepte de rétrécir — c'est la condition même du défilement : un élément qui
       ne rétrécit jamais ne déborde jamais de lui-même, et `overflow-x` n'aurait rien à
       faire. */
    flex: 0 1 auto;
    min-width: 0;
    max-width: 100%;
    background: #f0f2f5;
    border-radius: 26px;
    padding: 4px;
}
.jsb-preset-filters-wrap > .jsb-preset-filters__titre {
    flex: 0 0 auto;
    margin: 0 3px 0 0;
}

/* La zone : elle ne défile pas, elle porte les flèches. Sans elle, une flèche posée sur
   l'enveloppe se calerait sur le bord du TITRE et non sur celui des valeurs. */
.jsb-preset-zone {
    position: relative;
    display: flex;
    align-items: center;
    flex: 0 1 auto;
    min-width: 0;
}

/* La seule chose qui défile. */
.jsb-preset-zone > .jsb-preset-filters {
    flex: 1 1 auto;
    min-width: 0;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    /* Le geste ne se propage pas à la liste quand on arrive au bout. */
    overscroll-behavior-x: contain;
    /* Au repos, un chip commence toujours au bord : jamais de libellé coupé en deux. */
    scroll-snap-type: x proximity;
    background: transparent;
    border-radius: 0;
    border-color: transparent;
    padding: 0;
}
.jsb-preset-zone > .jsb-preset-filters > .jsb-preset-chip {
    flex: 0 0 auto;
    scroll-snap-align: start;
}

/* LA BARRE DE DEFILEMENT NATIVE EST RETIREE — pas l'accès. Un filet gris de quatre
   pixels sous des pastilles rondes salissait la pilule sans rien offrir de cliquable ;
   ce sont les flèches et les fondus qui portent l'information, et le geste tactile,
   au pavé ou au clavier reste entier. */
.jsb-preset-zone > .jsb-preset-filters { scrollbar-width: none; -ms-overflow-style: none; }
.jsb-preset-zone > .jsb-preset-filters::-webkit-scrollbar { display: none; }

/* -- LES FLECHES, EN SURCOUCHE ---------------------------------------------------
   24 px de côté — la cible minimale du niveau AA (WCAG 2.5.8) —, posées sur le fondu
   qui les rend lisibles, et absentes tant qu'il n'y a rien à atteindre : une flèche qui
   ne mène nulle part est un mensonge. */
.jsb-preset-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 3;
    display: none;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 1px solid #dee2e6;
    border-radius: 50%;
    background-color: #ffffff;
    color: #495057;
    cursor: pointer;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .12);
    transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.jsb-preset-nav--prev { left: 0; }
.jsb-preset-nav--next { right: 0; }
.jsb-preset-zone.is-defilable > .jsb-preset-nav { display: inline-flex; }
.jsb-preset-nav:disabled {
    opacity: 0;
    pointer-events: none;
}
.jsb-preset-nav:not(:disabled):hover {
    background-color: #0047AB;
    border-color: #0047AB;
    color: #ffffff;
}
.jsb-preset-nav:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: 2px;
}
.jsb-preset-nav svg { width: 14px; height: 14px; }

/* -- LES FONDUS DE BORD -----------------------------------------------------------
   Ils disent qu'il y a une suite avant même qu'on ait touché aux flèches, et
   s'éteignent au bout de la course. Portés par la ZONE et non par le contenu défilant :
   dessinés dedans, ils défileraient avec lui. Décoratifs, donc transparents au clic. */
.jsb-preset-zone::before,
.jsb-preset-zone::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    z-index: 2;
    width: 30px;
    pointer-events: none;
    opacity: 0;
    transition: opacity .15s ease;
}
.jsb-preset-zone::before {
    left: 0;
    background: linear-gradient(to right, #f0f2f5 40%, rgba(240, 242, 245, 0));
}
.jsb-preset-zone::after {
    right: 0;
    background: linear-gradient(to left, #f0f2f5 40%, rgba(240, 242, 245, 0));
}
.jsb-preset-zone.is-defile-gauche::before,
.jsb-preset-zone.is-defile-droite::after { opacity: 1; }

/* Titre du groupe : le CRITÈRE écrit une seule fois, en tête de la pilule, au lieu d'être
   répété sur chaque bouton (« Prime payée », « Prime impayée »…). Les chips ne portent plus
   que la valeur, ce qui divise leur largeur par deux et permet d'aligner les quatre groupes
   de la rubrique Tranches sur une seule ligne.
   Purement TEXTUEL : l'icône appartient au chip, où elle dit l'état — même règle dans toutes
   les rubriques. Couleurs de la charte : #343a40 (texte de titres), #ced4da (séparateur de
   toolbar). */
.jsb-preset-filters__titre {
    display: inline-flex;
    align-items: center;
    padding: 0 9px 0 10px;
    margin-right: 3px;
    border-right: 1px solid #ced4da;
    font-size: 0.78rem;
    font-weight: 700;
    line-height: 1.2;
    color: #343a40;
    white-space: nowrap;
    user-select: none;
}
/* ── PANNEAU DU CHIP-SÉLECTEUR ───────────────────────────────────────────────
   Certains filtres portent sur une RELATION (un agent, un client) et non sur un
   statut : leurs valeurs ne peuvent pas être figées dans un canevas de liste, qui
   est partagé entre entreprises. La chip va donc les chercher au clic, et les
   présente ici. Surcouche ancrée sous la chip, jamais une modale : le geste est un
   aiguillage, il ne mérite pas de piéger le focus. */
.jsb-preset-selecteur {
    /* FIXE, ET DANS LE <body>. Ancré sous la chip, le panneau appartenait au contexte
       d'empilement de la barre de filtres et passait SOUS l'en-tête du tableau : le
       `z-index` ne se compare qu'entre frères d'un même contexte, il ne pouvait rien.
       Le contrôleur le porte donc dans le <body> et calcule `left`/`top` en coordonnées
       viewport (positionnerMenu), en le recalant au défilement. */
    position: fixed;
    z-index: 1050;
    min-width: 220px;
    max-width: 320px;
    max-height: 50vh;
    overflow-y: auto;
    padding: 6px;
    background-color: var(--bg-white, #ffffff);
    border: 1px solid var(--border-light, #dee2e6);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .12);
}
.jsb-preset-selecteur__item {
    display: block;
    width: 100%;
    /* 44 px : la cible cliquable minimale (WCAG 2.5.5). */
    min-height: 44px;
    padding: 8px 12px;
    border: none;
    border-radius: 8px;
    background-color: transparent;
    color: var(--text-dark, #212529);
    font-size: 0.9rem;
    text-align: left;
    cursor: pointer;
}
.jsb-preset-selecteur__item:hover {
    background-color: var(--bg-cobalt-subtle, #e8f0fb);
    color: var(--cobalt, #0047AB);
}
.jsb-preset-selecteur__item:focus-visible {
    outline: 2px solid var(--cobalt, #0047AB);
    outline-offset: -2px;
}
.jsb-preset-selecteur__vide {
    margin: 0;
    padding: 10px 12px;
    color: var(--text-muted, #6c757d);
    font-size: 0.85rem;
}

.jsb-preset-chip,
.jsb-vue-choix {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.88rem;
    font-weight: 500;
    line-height: 1.2;
    padding: 4px 13px;
    border-radius: 20px;
    border: none;
    background: transparent;
    color: #6c757d;
    white-space: nowrap;
    cursor: pointer;
    font-family: inherit;
    transition: background 0.2s, color 0.2s, box-shadow 0.2s, transform 0.15s;
}
/* Chips d'étape du pipe : l'icône porte la couleur de son étape (hors état actif, qui
   reste cobalt plein comme tous les chips). Bornés au critère des Pistes : une autre
   rubrique peut avoir une valeur homonyme. */
[data-preset-criterion="__transformation_piste__"] .jsb-preset-chip[data-criterion-value="a_coter"]:not(.is-active) .jsb-preset-chip__icon { color: var(--etape-a_coter); }
[data-preset-criterion="__transformation_piste__"] .jsb-preset-chip[data-criterion-value="en_cotation"]:not(.is-active) .jsb-preset-chip__icon { color: var(--etape-en_cotation); }
[data-preset-criterion="__transformation_piste__"] .jsb-preset-chip[data-criterion-value="offres_chiffrees"]:not(.is-active) .jsb-preset-chip__icon { color: var(--etape-offres_chiffrees); }
[data-preset-criterion="__transformation_piste__"] .jsb-preset-chip[data-criterion-value="transformees"]:not(.is-active) .jsb-preset-chip__icon { color: var(--etape-transformees); }

.jsb-preset-chip__icon {
    display: inline-flex; /* aligne verticalement le SVG sur le libellé */
}
.jsb-preset-chip:hover,
.jsb-vue-choix:hover {
    color: #0047AB;
    background: rgba(0, 71, 171, 0.08);
    transform: translateY(-1px);
}
.jsb-preset-chip:active {
    transform: none; /* effet d'enfoncement à la pression */
}
.jsb-preset-chip:focus-visible,
.jsb-vue-choix:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: 2px;
}
.jsb-preset-chip.is-active,
.jsb-preset-chip.is-active:hover,
.jsb-vue-choix.is-active,
.jsb-vue-choix.is-active:hover {
    background: #0047AB;
    color: #ffffff;
    box-shadow: 0 1px 3px rgba(0, 71, 171, 0.35);
    transform: none;
}

/* -- UN GESTE N'EST PAS UN ETAT --------------------------------------------------
   « Tout cocher » et « Tout décocher » partageaient l'apparence d'un chip NON
   sélectionné : gris, plat, sur le même fond. Dans une pilule où les voisins s'allument
   en cobalt quand ils sont actifs, cela se lit comme « aucune valeur n'est
   sélectionnée » — alors que les 81 colonnes l'étaient toutes. Le composant mentait sur
   l'état du système (Nielsen 1), et confondait deux natures que rien ne distinguait
   (Bastien & Scapin > Signifiance).

   Un geste se donne donc l'allure d'un bouton : surface blanche, contour net, jamais le
   cobalt plein qui, ici, veut dire « sélectionné ». */
.jsb-preset-chip--action {
    background: #ffffff;
    border: 1px solid #ced4da;
    color: #495057;
    font-weight: 600;
    padding: 3px 12px;
    /* La pilule colle ses chips (`gap: 0`), ce qui va bien à des pastilles sans contour ;
       deux boutons bordés s'y toucheraient et se liraient comme un seul. */
    margin: 0 2px;
}
.jsb-preset-chip--action:hover {
    background: #ffffff;
    border-color: #0047AB;
    color: #0047AB;
}

.jsb-list-summary {
    display: flex;
    align-items: center;
    flex-wrap: wrap; /* Totaliser / Total / Sélection se replient aussi au besoin */
    gap: 0.5rem;
    font-size: 0.8rem;
}
.jsb-list-summary__label {
    color: #6c757d;
    font-size: 0.78rem;
    white-space: nowrap;
    margin-bottom: 0;
}
.jsb-list-summary__select {
    border: 1px solid #dee2e6;
    border-radius: 0.375rem;
    background: transparent;
    font-size: 0.78rem;
    color: #495057;
    padding: 0.125rem 0.375rem;
    max-width: 160px;
    cursor: pointer;
}
.jsb-list-summary__select:focus { outline: 2px solid #0047AB; outline-offset: 1px; }
.jsb-list-summary__divider {
    width: 1px;
    height: 1rem;
    background: #dee2e6;
    flex-shrink: 0;
}
.jsb-list-summary__item {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    white-space: nowrap;
}
.jsb-list-summary__item-label { color: #6c757d; font-size: 0.78rem; }
.jsb-list-summary__item-value {
    color: #0047AB;
    font-weight: 600;
    font-size: 0.8rem;
}
.jsb-list-summary.is-disabled {
    opacity: 0.5;
    pointer-events: none;
}

/* --- 3.6 Tableaux de données --- */
/* STYLES POUR LES TABLEAUX */
.h-100 {
    height: 100%;
}
/* ═══════════════════════════════════════════════════════════════════════════════
   SEUL LE TABLEAU DÉFILE — tout le reste de la liste reste en place.

   ── CE QUI N'ALLAIT PAS ─────────────────────────────────────────────────────────
   La coquille du workspace défilait EN ENTIER : barre de contrôles, chips et tableau
   remontaient ensemble, et l'on rendait la barre et les chips « collantes » pour
   qu'elles reviennent se poser en haut. L'entête du tableau devait alors se coller
   SOUS elles, d'où un décalage calculé en JavaScript (--jsb-pgbar-h + --jsb-chipsbar-h)
   et réappliqué à chaque redimensionnement.

   Ce montage tenait à une condition tacite : que l'entête s'accroche au même conteneur
   défilant que les deux barres. `.table-scroll-wrapper` a plus tard reçu
   `overflow-x: auto` pour les tableaux larges — et `overflow-x: auto` fait basculer
   `overflow-y` de `visible` à `auto`. Le wrapper est ainsi devenu un CONTENEUR DÉFILANT
   à son tour, sans le vouloir : `position: sticky` s'accroche au plus proche ancêtre
   défilant, donc à lui. Mais lui ne défile jamais (sa hauteur est celle de son contenu).
   L'entête ne se collait donc plus à rien : elle restait plantée à 74 px du haut du
   tableau, RECOUVRANT les deux ou trois premières lignes, et dérivait avec la page —
   d'où l'impression qu'elle « se déplace ».

   ── CE QU'ON FAIT À LA PLACE ────────────────────────────────────────────────────
   On arrête de faire défiler la coquille. La liste devient une colonne de hauteur
   bornée : les barres en haut, à demeure ; le tableau au milieu, SEUL à défiler ; son
   entête collée à `top: 0` de sa propre zone, sans décalage à calculer.

   Plus de mesure JavaScript, plus de variables à tenir en accord, et une seule chose
   qui bouge à l'écran — celle qu'on est en train de lire.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* Le panneau d'onglet donne sa hauteur à la liste qu'il contient : sans cette borne,
   `height: 100%` plus bas se résoudrait contre le contenu lui-même, et la colonne
   s'allongerait indéfiniment au lieu de faire défiler son tableau. */
[role="tabpanel"]:has(> .jsb-list-manager) { height: 100%; }

.jsb-list-manager {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

/* LA ZONE DÉFILANTE, et la seule. `overflow: auto` sur les DEUX axes : quand les
   colonnes numériques (nowrap) rendent le tableau plus large que le panneau, le
   défilement horizontal doit se faire ICI aussi — sinon l'entête s'accrocherait à un
   ancêtre qui, lui, ne défile pas horizontalement, et les titres se décaleraient des
   colonnes qu'ils nomment dès qu'on va chercher les dernières. */
.jsb-list-manager > .table-scroll-wrapper {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

/* L'état vide occupe la même place que le tableau qu'il remplace. */
.jsb-list-manager > [data-list-manager-target="emptyStateContainer"]:not(.d-none) {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

/* ⚠ HORS WORKSPACE — collection embarquée dans un dialogue —, le wrapper n'est PAS
   borné et ne doit surtout pas devenir défilant : c'est le corps de la modale qui
   défile, et l'entête doit s'y accrocher. On ne lui donne donc aucun `overflow` ; la
   règle ci-dessus est réservée aux listes de rubrique. */
.table-scroll-wrapper { min-width: 0; }

thead.sticky-header {
    z-index: 100;
}
.sticky-header th {
    position: sticky;
    top: 0;
    z-index: 10;
}
.table-light.sticky-header th {
    background-color: #f8f9fa;
}
.table-enhanced {
    border-collapse: separate;
    border-spacing: 0;
}
.table-enhanced th,
.table-enhanced td {
    border-bottom: 1px solid #dee2e6;
    border-top: none;
    border-left: none;
    border-right: none;
    text-align: left;
    vertical-align: middle;
}
.table-enhanced thead th {
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #6c757d;
}
.table-enhanced tbody td {
    color: #495057;
}
.list-row-primary {
    font-weight: bold;
    color: #0047AB;
}
/* Badge « déjà dans le contexte du chat IA » : pastille cobalt subtile posée
   à côté du libellé principal de la ligne, basculée (hidden) par list-manager
   selon l'état du chat actif. Aucune contribution au layout quand masquée. */
.jsb-ctx-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    width: 22px;
    height: 22px;
    margin-left: .35rem;
    border-radius: 50%;
    background: #e8f0fb;
    color: #0047AB;
}
.jsb-ctx-badge[hidden] {
    display: none;
}
.list-row-secondary {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    overflow: hidden;
    width: 0;          /* Ne contribue pas à la largeur intrinsèque de la colonne (table auto-layout) */
    min-width: 100%;   /* ... mais occupe toute la largeur réelle du <td> au rendu */
}
/* ─────────────────────────────────────────────────────────────────────────────
   LA LIGNE SÉLECTIONNÉE, PARTOUT PAREIL.

   La classe `row-selected` est posée par `list-row` et `list-manager` : elle vaut
   donc pour les trente-quatre rubriques, et pour toute liste qui adopte le même
   contrat de ligne. La règle est écrite ICI, une fois — une rubrique qui se
   peindrait ses propres lignes cochées finirait par ne plus ressembler aux autres.

   ⚠ LA COULEUR SE POSE SUR LES CELLULES, pas sur la rangée. Bootstrap colore les
   `td` depuis `.table > :not(caption) > * > *`, plus spécifique qu'un fond de `tr` :
   la teinte était donc recouverte dès que la table portait `.table`. On peint donc
   là où Bootstrap peint.

   DEUX SIGNES, jamais la couleur seule (WCAG 1.4.1) : le fond cobalt pâle de la
   charte, et un filet cobalt en tête de ligne. La case cochée reste le premier.
   ─────────────────────────────────────────────────────────────────────────── */
tr.row-selected > td {
    background-color: var(--bg-cobalt-subtle, #e8f0fb);
}
tr.row-selected > td:first-child {
    box-shadow: inset 3px 0 0 var(--cobalt, #0047AB);
}
/* Le survol reste perceptible SUR une ligne déjà cochée : sans cette reprise, la
   règle ci-dessus l'emportait et la ligne cessait de répondre à la souris. */
tr.row-selected:hover > td {
    background-color: #dce9f9;
}
.table-enhanced tbody tr:hover {
    background-color: #f8f9fa;
}
/* TOUTE LA LIGNE COCHE (`list-row#toggleSelection`) : le curseur doit le dire, sans
   quoi le geste ne se découvre qu'au hasard d'un clic. */
.table-enhanced tbody tr { cursor: pointer; }
.table-enhanced tbody tr:last-child td {
    border-bottom: none;
}

/* NOUVEAU : Alignement pour les en-têtes de colonnes numériques */
.table-enhanced .th-numeric {
    text-align: right;
}
.table-enhanced .td-numeric {
    white-space: nowrap;               /* Jamais de montant replié sur deux lignes ("USD" / "1 160,00") */
    font-variant-numeric: tabular-nums; /* Chiffres à chasse fixe : décimales alignées verticalement */
}
.table-enhanced .td-numeric .td-numeric-unit {
    font-weight: 400;                  /* L'unité répétée à chaque cellule est dé-emphasée, la valeur reste en gras */
}

/* ══ LE TABLEAU D'UNE COLLECTION DE DIALOGUE ══
   Trois colonnes — principale | valeur | actions — de largeur FIGEE.

   `table-layout: fixed` est la seule facon de garantir la meme abscisse d'un onglet a
   l'autre : sous `auto`, une largeur n'est qu'une suggestion que le contenu peut
   deborder, et deux onglets voisins placeraient leur colonne de valeur a deux endroits
   differents — l'oeil le verrait sauter en changeant d'onglet.

   Le selecteur se scope tout seul : le <colgroup> n'est pose qu'en dialogue. */
.table-enhanced:has(> colgroup) {
    table-layout: fixed;
    /* En `ch`, et non en pixels : la colonne doit tenir le PLUS GRAND montant reel, qui
       n'est pas un montant en dollars. « 125 000 000,00 CDF » fait 18 caracteres, et
       `.td-numeric` interdit le repli — une largeur calee sur « 10 250,00 USD » ferait
       deborder le franc congolais sur la colonne d'actions. `ch` suit la police, donc le
       debordement ne revient pas au premier changement de fonte. Aucune requete media ne
       la retrecit : c'est la colonne PRINCIPALE qui cede et ellipse, un libelle tronque
       restant lisible la ou un montant tronque est faux. */
    --jsb-col-valeur: 20ch;
    /* TROIS boutons de 34 px (Modifier, un geste declare comme Souscrire, Supprimer),
       leurs gouttieres de 10 px et le rembourrage de cellule. Calibree pour deux, la
       colonne ecrasait le troisieme : les cibles se touchaient, et un clic tombait sur la
       voisine. Commune a toutes les collections : la colonne de valeur garde la meme
       abscisse d'un onglet a l'autre. */
    --jsb-col-actions: 148px;
}
.table-enhanced > colgroup > col.jsb-col-valeur  { width: var(--jsb-col-valeur); }
.table-enhanced > colgroup > col.jsb-col-actions { width: var(--jsb-col-actions); }

/* PIED DE TOTAL — convention comptable : le total se lit SOUS la colonne qu'il somme.
   Un filet epais le detache du corps ; c'est le seul signe dont il a besoin. */
.table-enhanced > tfoot > tr > th,
.table-enhanced > tfoot > tr > td {
    border-top: 2px solid var(--border-medium, #ced4da);
    border-bottom: none;
    background-color: var(--bg-white, #ffffff);
}
.table-enhanced > tfoot > tr > th {
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-muted, #6c757d);
}
.table-enhanced > tfoot .td-numeric {
    font-weight: 700;
    color: var(--cobalt, #0047AB);
}
/* --- 3.7 États de chargement et vides --- */
/*
===================================================================
 SECTION: SQUELETTE DE CHARGEMENT GÉNÉRIQUE POUR L'ESPACE DE TRAVAIL
===================================================================
*/

.workspace-skeleton {
    padding: 1.5rem;
    animation: fadeIn 0.3s ease-in-out;
}

.workspace-skeleton .skeleton-header {
    display: flex;
    gap: 1rem;
    align-items: center;
    margin-bottom: 1.5rem;
}

.workspace-skeleton .skeleton-icon {
    width: 40px;
    height: 40px;
    background-color: #dee2e6; /* Fond gris un peu plus foncé */
    border-radius: 8px;
}

.workspace-skeleton .skeleton-line {
    height: 20px;
    background-color: #dee2e6; /* Fond gris un peu plus foncé */
    border-radius: 4px;
    margin-bottom: 10px;
}

/* --- NOUVEAU : Style pour le conteneur du spinner --- */
.spinner-container {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 4rem 2rem; /* Un peu d'espace pour que ça respire */
    min-height: 200px; /* Hauteur minimale pour être visible même si la liste est vide */
    width: 100%;
}

/* --- NOUVEAU : Le spinner lui-même --- */
.custom-spinner {
    width: 50px;
    height: 50px;
    border: 5px solid rgba(0, 123, 255, 0.2); /* Couleur de fond de la bordure (bleu clair transparent) */
    border-top-color: #0d6efd; /* Couleur de la partie qui tourne (bleu primaire de Bootstrap) */
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

/* --- NOUVEAU : L'animation de rotation --- */
@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

/* --- Styles pour le composant Empty State --- */
.empty-state-container {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 2rem;
  height: 100%;
  min-height: 250px; /* Hauteur minimale pour un bon rendu visuel */
  animation: fadeIn 0.3s ease-in-out; /* Animation d'apparition douce */
}

.empty-state-content {
  max-width: 450px;
}

.empty-state-icon {
  /* font-size: 3rem; Taille de l'icône */
  color: #6c757d; /* --dark-gray-color ou équivalent */
  margin-bottom: 1rem;
  width: 100px;
  height: 100px;
  display: block; /* Assure que l'icône est un bloc pour le centrage */
  margin-left: auto; /* Centre l'icône horizontalement */
  margin-right: auto; /* Centre l'icône horizontalement */
}

.empty-state-title {
  font-size: 1.4em; /* Police légèrement plus grande */
  color: #343a40; /* --dark-gray-color ou équivalent */
  font-weight: 600;
  margin-bottom: 0.5rem;
}

.empty-state-description {
  font-size: 1.1em;
  color: #6c757d; /* --dark-gray-color ou équivalent */
  margin-bottom: 1.5rem;
}

/* Le bouton utilise déjà les styles .btn-primary de Bootstrap, qui correspondent à votre --primary-cobalt-blue */
/* CORRECTION : Style pour les boutons avec icône et texte */
.empty-state-button, .empty-state-button-secondary {
  display: inline-flex; /* Permet d'aligner l'icône et le texte */
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1.25rem; /* Padding pour un aspect rectangulaire */
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); /* Ombre discrète */
  transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; /* Animation douce */
  border-radius: 8px !important; /* Forme rectangulaire avec coins arrondis */
}

.empty-state-button:hover {
  transform: translateY(-2px); /* Léger mouvement vers le haut au survol */
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); /* Ombre plus prononcée */
}

.empty-state-button-secondary:hover {
    transform: translateY(-2px); /* Léger mouvement vers le haut au survol */
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); /* Ombre plus prononcée */
    background-color: #495057;
    border-color: #495057;
}

/* Cible l'icône SVG à l'intérieur du bouton */
/* CORRECTION : Assure que les icônes font exactement 24px */
.empty-state-button .me-2,
.empty-state-button-secondary .me-2 {
    width: 24px !important;
    height: 24px !important;
}

/* --- 3.8 Panneau de description (volet de visualisation) --- */
/* ================================================================== */
/* SECTION : PANNEAU DE DESCRIPTION DANS LE VOLET DE VISUALISATION    */
/* ================================================================== */

.description-panel {
    /* Hauteur de l'image de bannière. Fixée à 300px, elle donnait une TRANCHE très
       zoomée des visuels — ceux-ci sont pour la plupart en portrait (816×1456), or
       `cover` cale l'image sur la LARGEUR du panneau : à 450px de large, la photo
       est agrandie à ~800px de haut et l'on n'en voyait qu'un bandeau du milieu.
       Une hauteur proportionnée rend la bannière lisible comme bandeau, et
       l'agrandit quand la colonne passe en plein écran.
       (Un visuel en PAYSAGE, ~1600×600, resterait le cadrage idéal : à format
       portrait, aucun réglage CSS ne peut montrer la photo entière dans un
       bandeau sans la réduire à une vignette.) */
    --description-panel-top-gap: clamp(130px, 17vh, 210px); /* Espace supérieur sans transparence */
    --description-panel-side-gap: 10px;   /* NOUVEAU : Espace latéral sans transparence */
    --description-panel-bottom-gap: 10px; /* NOUVEAU : Espace inférieur sans transparence */
    position: relative; /* Requis pour positionner l'overlay */
    color: var(--text-white); /* Texte en blanc pour contraster avec l'overlay */
    font-size: 0.95rem;
    line-height: 1.6;
    border-radius: var(--nav-radius);
    box-shadow: var(--shadow-sm);
    padding: var(--description-panel-top-gap) var(--description-panel-side-gap) var(--description-panel-bottom-gap);
    margin-bottom: 1rem;
    overflow: hidden; /* Assure que l'overlay respecte les coins arrondis */
    display: flex; /* Permet de centrer le contenu verticalement */
    min-height: 150px; /* Hauteur minimale pour que l'image soit visible */

    /* --- Définition de l'image de fond --- */
    background-size: cover; /* L'image couvre tout le panneau */
    /* Centrée sur les DEUX axes, et jamais répétée : sur les deux axes explicitement,
       car `center` seul laisse l'axe vertical à l'interprétation du navigateur pour
       la valeur initiale, et c'est justement l'axe qui cadre ces visuels portrait. */
    background-position: center center;
    background-repeat: no-repeat;
}

/* --- NOUVEAU : Overlay sombre pour la lisibilité --- */
.description-panel::before {
    content: '';
    position: absolute;
    top: var(--description-panel-top-gap);
    left: var(--description-panel-side-gap);
    right: var(--description-panel-side-gap);
    bottom: var(--description-panel-bottom-gap);
    background-color: var(--overlay-dark); /* Fond noir avec 65% d'opacité */
    z-index: 1; /* Se place entre le fond et le texte */
    transition: background-color 0.3s ease-in-out; /* NOUVEAU : Transition douce pour l'overlay */
}

.description-panel p {
    margin-bottom: 0;
    position: relative; /* Assure que le texte est au-dessus de l'overlay */
    z-index: 2;
    /* NOUVEAU : Ajoute un espacement interne pour que le texte ne colle pas aux bords de la zone transparente. */
    padding: 10px;
    transition: color 0.3s ease-in-out; /* NOUVEAU : Transition douce pour la couleur du texte */
}

.description-panel strong {
    color: var(--text-white); /* Texte important en blanc pur */
    transition: color 0.3s ease-in-out; /* NOUVEAU : Transition douce */
}

.description-panel em {
    color: var(--bg-muted); /* Un gris très clair pour le texte moins important */
    font-style: normal;
    transition: color 0.3s ease-in-out; /* NOUVEAU : Transition douce */
}

/* --- NOUVEAU : Styles au survol de la souris --- */
.description-panel:hover::before {
    background-color: var(--overlay-light); /* Fond gris clair semi-transparent */
}

.description-panel:hover p,
.description-panel:hover strong,
.description-panel:hover em {
    color: var(--text-dark); /* Texte noir pour une lisibilité maximale sur fond clair */
}

/* ================================================================== */
/* SECTION 4: STYLES POUR LES BOÎTES DE DIALOGUE (MODALES)            */
/* ================================================================== */

/* --- 4.1 Structure générale et layout --- */
.custom-modal-scroll {
    height: 85vh; /* La modale prend 85% de la hauteur de la fenêtre */
    display: flex;
    flex-direction: column;
}

.app-dialog .modal-header {
    border-bottom: 3px solid #0047AB;
    flex-shrink: 0;
}

.app-dialog .modal-footer,
#confirmation-dialog-modal .modal-footer {
    background-color: #f8f9fa;
    border-top: 1px solid #dee2e6;
    flex-shrink: 0;
}

/* 4.2 Barre de progression du dialog */
.dialog-progress-container {
    height: 3px;
    background-color: #e9ecef;
    overflow: hidden;
    flex-shrink: 0;
    display: none;
}
.dialog-progress-container.is-loading {
    display: block;
}
.dialog-progress-bar {
    width: 35%;
    height: 100%;
    background: linear-gradient(90deg, transparent 0%, #0047AB 40%, #0047AB 60%, transparent 100%);
    animation: dialogProgressSlide 1.4s infinite ease-in-out;
}
@keyframes dialogProgressSlide {
    0%   { transform: translateX(-200%); }
    100% { transform: translateX(500%); }
}

/* 4.2.b Mode CHIFFRÉ : le serveur connaît son dénominateur, la barre le montre.
   Une barre qui glisse indéfiniment dit « ça travaille » ; celle-ci dit « on en est là ».
   La différence compte quand la suppression d'un dossier emporte cent lignes. */
.dialog-progress-container.is-determinate .dialog-progress-bar {
    animation: none;
    transform: none;
    width: var(--dialog-progress, 0%);
    background: #0047AB;
    transition: width .2s linear;
}

/* L'étape en cours, sous la barre. Jamais la couleur seule pour porter l'information
   (WCAG 1.4.1) : c'est le TEXTE qui nomme ce qui avance. */
.dialog-progress-label {
    padding: .25rem .75rem;
    font-size: .75rem;
    color: #495057;
    background-color: #f8f9fa;
    border-bottom: 1px solid #dee2e6;
    flex-shrink: 0;
}

/* 4.2.c BARRE DES ACTIONS DE LA FICHE — pleine largeur, au-dessus des deux volets.
   C'est la barre du workspace (.context-toolbar, rendue par barre-actions.js) : mêmes
   boutons, mêmes familles déroulantes, mêmes couleurs de charte. Son ÉCRIN aussi est
   celui du workspace : un BANDEAU blanc collé sous l'en-tête, gouttière de 8 px (le
   `p-2` de _view_manager) et filet bas de 1 px (.sticky-header) — pas une carte posée
   sur un fond gris. Hors du `.modal-body`, elle ne défile pas avec le formulaire. */
.dialog-barre-actions {
    flex-shrink: 0;
    background-color: #ffffff;
    padding: 0.5rem;
    border-bottom: 1px solid var(--border-light);
}

/* ICÔNE + LIBELLÉ. Dans le dialogue, la barre a la place d'ÉCRIRE ce que fait chaque
   bouton : « Relevé de compte », « Portefeuille », « Attacher des pièces » se lisent
   d'un coup d'œil, là où une icône seule obligeait à survoler pour comprendre
   (Nielsen 6 — reconnaître plutôt que se souvenir). Les couleurs restent celles de
   `.context-toolbar` : gris #495057 au repos, cobalt au survol, pour l'icône ET le texte. */
.context-toolbar .btn.a-libelle {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.4rem 0.75rem;
    font-size: 0.875rem;
    font-weight: 500;
    color: #495057;
    white-space: nowrap;
}
.context-toolbar .btn-default.a-libelle:hover,
.context-toolbar .toolbar-groupe-bouton.a-libelle[aria-expanded="true"] {
    color: #0047AB;
}
.context-toolbar .btn.a-libelle .toolbar-icone {
    display: inline-flex;
}
.context-toolbar .btn.a-libelle .toolbar-icon {
    width: 20px;
    height: 20px;
}
/* Le chevron d'une famille se pose APRÈS le libellé, avec de l'air. */
.context-toolbar .toolbar-groupe-bouton.a-libelle {
    padding-right: 1.5rem !important;
}
.context-toolbar .toolbar-groupe-bouton.a-libelle::after {
    right: 0.6rem;
}
.context-toolbar .toolbar-groupe-bouton.a-libelle[aria-expanded="true"]::after {
    border-top-color: #0047AB;
}

/* Écran étroit : les libellés se taisent, la barre redevient celle du workspace
   (icônes + infobulle) et tient sur une ligne au lieu d'en empiler trois. */
@media (max-width: 767.98px) {
    .dialog-barre-actions .toolbar-libelle {
        display: none;
    }
    .context-toolbar .btn.a-libelle {
        padding: 0.4rem;
    }
    .context-toolbar .toolbar-groupe-bouton.a-libelle {
        padding-right: 1.05rem !important;
    }
    .context-toolbar .toolbar-groupe-bouton.a-libelle::after {
        right: 0.35rem;
    }
}

/* 2. On dit au corps de prendre tout l'espace restant et d'organiser les colonnes.
   VOLETS JOINTIFS, comme les colonnes du workspace : ni gouttière extérieure, ni gris
   entre eux. Ils touchent la barre, les bords du dialogue et son pied ; la colonne
   sombre touche le formulaire sans filet, comme col-1 touche col-2 (le contraste
   suffit). Cette règle est la SEULE source de la gouttière : aucune classe `p-0` dans
   la coquille, dont le `!important` la masquait. */
.custom-modal-scroll .modal-body {
    flex: 1;
    overflow-y: hidden; /* On empêche le scroll sur le corps parent */
    background-color: #f0f0f0;
    min-height: 0;
    padding: 0;
    display: flex; /* Le corps devient le conteneur flex */
    gap: 0;
}

/* --- 4.3 Layout à deux colonnes --- */
/*
===================================================================
 SECTION: MISE EN PAGE À DEUX COLONNES POUR LES DIALOGUES
===================================================================
*/

/* --- Colonne de gauche (Attributs Calculés) --- */
.calculated-attributes-column {
    /* Styles de base pour la colonne sombre. Un PANNEAU et non une carte, comme les
       colonnes du workspace : ni coins arrondis, ni accent cobalt en tête — le filet
       cobalt de l'en-tête signe déjà le dialogue. */
    background-color: #212529;
    color: #e9ecef;
    padding: 1.5rem;

    /* NOUVEAU : Assurer que l'icône du bouton Enregistrer est blanche */
    .btn-primary .button-icon svg {
        fill: white;
    }

    /* Par défaut, la colonne est cachée */
    display: none;

    /* REFACTORING : On active le défilement sur toute la colonne pour se conformer au modèle. */
    overflow-y: auto;
}

/* On affiche la colonne SEULEMENT si on est en mode édition ET qu'elle a du contenu */
.app-dialog.is-edit-mode.has-attributes-column .calculated-attributes-column {
    display: block; /* On la rend visible en tant que bloc pour un défilement vertical simple. */
}

/* EN CRÉATION, la même colonne porte la DESCRIPTION du paramètre.

   Règle DISTINCTE et non fusionnée avec celle du dessus, volontairement : la classe
   `has-creation-description` n'est posée que si le FormCanvasProvider a déclaré
   `description_creation`. Un dialogue qui ne l'a pas déclarée reste donc exactement
   dans son état d'avant — colonne masquée en création —, ce qui permet d'enrichir les
   dialogues un par un sans toucher aux autres. */
.app-dialog.has-creation-description .calculated-attributes-column {
    display: block;
}

.creation-description p {
    margin: 0 0 0.85rem;
    font-size: 0.88rem;
    line-height: 1.6;
    color: var(--bg-muted); /* lisible sur le fond sombre #212529 de la colonne */
}

.creation-description p:last-child {
    margin-bottom: 0;
}

/* Le premier paragraphe est la phrase-clé : c'est aussi celle que reprend le guide
   de démarrage sur ses cartes. On la met en avant plutôt que de la répéter. */
.creation-description p:first-child {
    font-weight: 600;
    color: var(--text-white);
}

.calculated-attributes-column .column-title {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-weight: bold;
    color: white;
    font-size: 1.05rem;
    margin-bottom: 1.25rem;
    padding-bottom: 0.85rem;
    border-bottom: 1px solid #495057;
}
/* Pastille cobalt de l'entité : miroir de la pastille claire de l'entête du volet
   droit (.form-intro-icon) — cohérence des deux volets. Blanc sur cobalt (charte). */
.calculated-attributes-column .column-title-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 10px;
    background-color: #0047AB;
    color: #ffffff;
}
.calculated-attributes-column .column-title-icon svg {
    width: 20px;
    height: 20px;
}

.calculated-attributes-list {
    /* Liste de description (dl) : chaque groupe devient un panneau sombre délimité,
       en miroir des cartes claires du volet droit (Cohérence). */
    list-style: none;
    padding: 0.3rem 0.35rem;
    margin: 0;
    background-color: #343a40;
    border: 1px solid #495057;
    border-radius: 10px;
}

/* Ligne d'un couple intitulé / valeur (ancien <li>, désormais un conteneur de <dt>/<dd>) */
.calculated-attributes-item {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.6rem 0.6rem;
    border-radius: 7px;
    transition: background-color 0.2s ease;
}
/* Filet discret entre les rangées d'un même panneau. */
.calculated-attributes-item + .calculated-attributes-item {
    border-top: 1px solid #495057;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}

.calculated-attributes-item:hover {
    background-color: #495057;
}

.calculated-attributes-list .attr-label {
    /* <dt> : gris clair de la charte, lisible sur les fonds sombres */
    font-weight: normal;
    color: #adb5bd;
}
.calculated-attributes-list .attr-value {
    /* <dd> : on retire la marge par défaut du navigateur */
    margin: 0;
    font-weight: bold;
    text-align: right;
}

/* --- Colonne de droite (Formulaire) --- */
.form-column {
    /* NOUVEAU : La colonne de formulaire est un item flex qui grandit et défile. */
    flex: 1;
    min-width: 0; /* Correctif flex pour empêcher le dépassement horizontal. */
    /* AUCUNE GOUTTIÈRE, comme une colonne du workspace : l'entête, les onglets et les
       cartes de champ touchent les bords et se séparent par des filets. Chaque bloc
       porte ses propres marges intérieures. Ce nombre est COUPLÉ au décalage du bandeau
       d'onglets collant (`.form-column .jsb-onglets-barre`, top) : changer l'un oblige
       à changer l'autre. */
    padding: 0;
    overflow-y: auto;
    /* Fond blanc, SANS coins arrondis : un panneau jointif, comme les colonnes du
       workspace, et non plus une carte posée sur un fond gris. */
    background-color: #ffffff;
}

/* Stabilité des colonnes */
.form-column-skeleton, .form-column {
    min-height: 400px; /* Évite le collapse vertical */
}

/* 3. On fait "disparaître" la rangée intermédiaire pour que les colonnes deviennent les enfants flex directs du modal-body */
.custom-modal-scroll .modal-body > .row {
    display: contents;
}

/*
===================================================================
 SECTION: STYLE POUR LES TABLEAUX CLÉ-VALEUR DANS LES ATTRIBUTS
===================================================================
*/

/* Conteneur principal du tableau */
.attr-table {
    width: 100%;
    border-collapse: collapse; /* Fusionne les bordures pour un look plus net */
    margin-top: 5px;
    font-size: 0.9em;
}

/* Style de chaque ligne du tableau */
.attr-table tr {
    border-bottom: 1px solid #495057; /* Ligne de séparation gris foncé */
}

/* On enlève la bordure de la dernière ligne */
.attr-table tr:last-child {
    border-bottom: none;
}

/* Style des cellules (clé et valeur) */
.attr-table td,
.attr-table th {
    padding: 8px 5px;
    vertical-align: top;
}

/* Style de la cellule de gauche (la clé, ex: "Attendus") — <th scope="row"> pour l'accessibilité */
.attr-table .attr-table-key {
    text-align: left;
    font-weight: normal; /* On neutralise le gras par défaut du <th> */
    color: #adb5bd; /* Gris plus clair pour la clé */
}

/* Style de la cellule de droite (la valeur, ex: "5 pc(s)") */
.attr-table .attr-table-value {
    text-align: right;
    font-weight: bold;
    color: #ffffff; /* Blanc pour la valeur */
}

/* NOUVEAU : Style pour les titres de groupe dans la colonne des attributs.
   Micro-libellé au-dessus du panneau de groupe (le panneau délimite désormais
   le contenu : plus besoin de filet sous le titre). */
.calculated-attributes-group-title {
    font-size: 0.72rem;
    font-weight: bold;
    color: #adb5bd; /* Gris clair pour le titre du groupe */
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: 0.4rem;
    padding-left: 0.25rem;
}
.calculated-attributes-content dl + .calculated-attributes-group-title {
    margin-top: 1.25rem; /* Ajoute un espace au-dessus des groupes, sauf le premier */
}

/* --- 4.4 Styles de formulaire spécifiques aux dialogues --- */
/*
===================================================================
 SECTION: STYLE POUR LES BLOCS DE CHAMPS DE FORMULAIRE DANS LES DIALOGUES
===================================================================
*/

/* NOUVEAU : Ajoute une délimitation visuelle pour chaque champ dans un formulaire de dialogue. */
/* CARTES DE CHAMP JOINTIVES, comme les lignes d'une liste du workspace : aucune
   gouttière entre elles, un filet de 1 px pour les séparer, ni ombre ni coins arrondis.

   Le filet est une ombre INTÉRIEURE (droite + bas), et non une bordure : il se dessine
   dans la carte, n'élargit rien, ne déborde pas de la colonne (qui défile, et
   montrerait une barre horizontale pour un pixel), et il marche aussi bien dans les
   rangées Bootstrap que dans la grille des formulaires de rôle — sans avoir à deviner
   quelle carte est la dernière de sa rangée.

   `!important` sur la marge : la classe `mb-3` de Bootstrap l'est elle-même. Elle reste
   dans le gabarit, où elle sert de crochet à ces règles. */
.form-column .mb-3 {
    margin-bottom: 0 !important;
    padding: 0.5rem;
    border-radius: 0;
    background-color: #ffffff; /* Assure un fond blanc propre */
    box-shadow: inset -1px -1px 0 var(--border-light);
    transition: box-shadow 0.2s ease-in-out;
}

/* Le champ actif se signale par un anneau cobalt, intérieur lui aussi : il ne mord
   pas sur les cartes voisines. */
.form-column .mb-3:focus-within {
    box-shadow: inset 0 0 0 2px #0047AB;
}

/* Les rangées et colonnes de Bootstrap perdent leurs gouttières : les cartes se
   touchent bord à bord. */
.form-column form .row {
    --bs-gutter-x: 0;
    --bs-gutter-y: 0;
}

/* Le panneau d'un onglet commence au ras du bandeau d'onglets. */
.form-column .jsb-onglets-panneau {
    padding-top: 0;
}

/*
===================================================================
 SECTION: RENDU DÉDIÉ DES FORMULAIRES DE RÔLES
 Ciblage ROBUSTE : un formulaire de rôle est reconnu à la présence de cases
 « accès… » (input[name^="access"]), indépendamment de toute classe ajoutée par
 le canevas. Le style s'applique donc dès le rechargement de la feuille de style,
 sans dépendre d'un gabarit recompilé côté serveur.
 Objectif : ergonomie (Bastien & Scapin — Guidage, Signifiance, Charge
 de travail) + charte Joseara (cobalt #0047AB). Les champs connus
 (nom du rôle, collaborateur) sont masqués côté canevas ; chaque groupe
 de droits devient une carte lisible, les cases cochées adoptent le
 cobalt de marque, le focus est visible (WCAG 2.4.7). Entièrement scopé :
 n'affecte AUCUN autre formulaire.
===================================================================
*/

/* Gouttière unique et cohérente entre les cartes, dans les deux directions.
   On force explicitement le rembourrage des colonnes (gouttière horizontale) et
   la marge basse des cartes (gouttière verticale) à la MÊME valeur (1rem), sans
   dépendre de la résolution de --bs-gutter-x de Bootstrap (qui restait à 1.5rem
   et rendait l'horizontale plus large que la verticale). La marge négative de la
   ligne (-0.5rem) compense le rembourrage des colonnes pour aligner les bords. */
/* Grille 2 colonnes UNIFORME pour les droits, quel que soit le découpage du canevas.
   On aplatit les rangées/colonnes Bootstrap (display:contents) pour poser TOUTES les
   cartes dans une seule grille : exactement 2 cartes par ligne, avec une gouttière
   unique (`gap`) rigoureusement identique à l'horizontale et à la verticale. Les
   rangées cachées (nom/collaborateur) restent masquées (.d-none l'emporte). */
.form-column:has(input[name^="access"]) form {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    /* Cartes jointives, séparées par leur filet intérieur, comme partout ailleurs dans
       le formulaire (grammaire du workspace) : ni gouttière, ni cadre gris. */
    gap: 0;
    align-items: stretch;
    padding: 0;
    background-color: #ffffff;
}
.form-column:has(input[name^="access"]) form > .row,
.form-column:has(input[name^="access"]) form .row > [class*="col-"] {
    display: contents;
}

/* Chaque groupe de droits (ChoiceType étendu) = une carte claire et autonome.
   La grille gère les hauteurs égales (align-items:stretch) et tout l'espacement ;
   la carte ne porte donc aucune marge. */
.form-column:has(input[name^="access"]) .mb-3 {
    margin: 0 !important;
    padding: 0.9rem 1rem 0.75rem;
    /* Filet intérieur et anneau de focus : ceux de toutes les cartes du formulaire. */
    transition: background-color 0.15s ease, box-shadow 0.15s ease;
}
/* Survol : un fond à peine teinté plutôt qu'une élévation — une carte jointive ne
   « sort » pas de sa grille (visibilité de l'état du système, Nielsen 1). */
.form-column:has(input[name^="access"]) .mb-3:hover {
    background-color: var(--bg-light);
}
/* Carte à entête bandeau (variante avec icône) : le rembourrage global disparaît,
   chaque zone interne (entête, options) porte le sien ; overflow:hidden pour que
   le bandeau épouse les coins arrondis. */
.form-column:has(input[name^="access"]) .mb-3.dlg-field-card {
    padding: 0;
    overflow: hidden;
}

/* En-tête de carte : intitulé de la rubrique, accent cobalt (Guidage).
   Le champ « ChoiceType étendu » rend l'intitulé dans un <legend class="col-form-label">
   (et non un .form-label) : on cible donc les deux. On neutralise le float par défaut
   du <legend> pour un bloc propre. */
.form-column:has(input[name^="access"]) .form-label,
.form-column:has(input[name^="access"]) legend {
    float: none;
    display: block;
    width: 100%;
    margin-bottom: 0.6rem;
    padding: 0 0 0.4rem 0.6rem;
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 1.3;
    color: #212529;
    border-bottom: 1px solid #e0e0e0;
    border-left: 3px solid #0047AB;
}

/* Carte de champ avec icône (field_icons du canvas) : l'entête de carte reprend
   l'accent cobalt (bordure gauche + soulignement) à la place du libellé seul, qui
   est remis à plat ; une mini-pastille illustre l'entité concernée (écho de la
   pastille de l'entête .form-intro). */
.form-column:has(input[name^="access"]) .dlg-field-card-head {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid #e0e0e0;
}
.form-column:has(input[name^="access"]) .dlg-field-card-head .form-label,
.form-column:has(input[name^="access"]) .dlg-field-card-head legend {
    border: 0;
    margin: 0;
    padding: 0;
    width: auto;
    display: inline;
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.3;
    color: #343a40;
}
.dlg-field-card-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 8px;
    background-color: #e8f0fb;
    color: #0047AB;
}
.dlg-field-card-icon svg {
    width: 16px;
    height: 16px;
}

/* Chaque niveau d'accès : rangée cliquable entière, cible élargie (WCAG 2.5.5),
   retour visuel au survol et à la sélection (Nielsen 1 — visibilité de l'état). */
.form-column:has(input[name^="access"]) .form-check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 40px;
    margin: 0;
    padding: 0.15rem 0.5rem; /* alignement géré par flex, pas par le retrait Bootstrap */
    border-radius: 8px;
    transition: background-color 0.15s ease;
}
.form-column:has(input[name^="access"]) .form-check:hover {
    background-color: #f8f9fa;
}
/* Rangée cochée : surlignage cobalt très clair. L'état reste aussi porté par la
   coche elle-même (pas d'information par la couleur seule — WCAG 1.4.1). */
.form-column:has(input[name^="access"]) .form-check:has(.form-check-input:checked) {
    background-color: #e8f0fb;
}
.form-column:has(input[name^="access"]) .form-check:has(.form-check-input:checked) .form-check-label {
    color: #212529;
    font-weight: 500;
}
.form-column:has(input[name^="access"]) .form-check-input {
    float: none;
    margin: 0;
    width: 1.15em;
    height: 1.15em;
    cursor: pointer;
    border-color: #ced4da;
}
.form-column:has(input[name^="access"]) .form-check-label {
    cursor: pointer;
    color: #495057;
    user-select: none;
    font-size: 0.875rem;
    flex: 1; /* toute la largeur de la rangée est cliquable */
}

/* État coché : cobalt de marque (au lieu du bleu Bootstrap par défaut). */
.form-column:has(input[name^="access"]) .form-check-input:checked {
    background-color: #0047AB;
    border-color: #0047AB;
}
/* Focus visible conforme charte : anneau cobalt. */
.form-column:has(input[name^="access"]) .form-check-input:focus {
    border-color: #0047AB;
    box-shadow: 0 0 0 0.2rem rgba(0, 71, 171, 0.35);
}

/*
===================================================================
 GÉNÉRALISATION DES DIALOGUES MODERNISÉS (tous formulaires imbriqués)
 Les mécanismes introduits pour les formulaires de rôle deviennent
 disponibles pour TOUS les dialogues d'entité : cadre du formulaire dès
 qu'un entête contextuel (.form-intro) est présent, cartes de champ à
 entête bandeau (field_icons du canvas), groupes de choix en rangées
 cliquables. Les règles « rôles » ci-dessus, plus spécifiques, restent
 prioritaires (grille 2 cartes/ligne, cartes bordées).
===================================================================
*/
/* Cadre englobant du formulaire : appliqué dès que le canvas fournit un
   entête contextuel. */
.form-column:has(.form-intro) form {
    /* LE CADRE DECLARE SA GOUTTIERE, et le bandeau collant s'en sert pour border a
       l'interieur de lui (cf. `.form-column .jsb-onglets-barre`). Recopier le nombre
       d'un bloc a l'autre, c'est ce qui avait fait deborder le bandeau de 3 px et
       couper la bordure du cadre sur toute sa hauteur.

       NULLE désormais, comme au workspace : plus de cadre gris arrondi autour des
       onglets. Les cartes de champ touchent le bandeau d'onglets et les bords. */
    --gouttiere-cadre: 0rem;
    padding: var(--gouttiere-cadre);
    background-color: #ffffff;
}
/* Carte à entête bandeau : le rembourrage est porté par les zones internes,
   overflow:hidden pour que le bandeau épouse les coins arrondis. */
.form-column .mb-3.dlg-field-card {
    padding: 0;
    overflow: hidden;
}
/* La colonne devient une pile, et sa DERNIÈRE carte occupe ce qui reste : les cartes
   d'une même rangée se terminent alors sur la même ligne. `d-none` reste prioritaire
   (l'utilitaire de Bootstrap est en !important), donc une rangée masquée le demeure. */
.form-column form .row > [class^="col-"],
.form-column form .row > [class*=" col-"] {
    display: flex;
    flex-direction: column;
}
.form-column form .row > [class^="col-"] > .dlg-field-card:last-of-type,
.form-column form .row > [class*=" col-"] > .dlg-field-card:last-of-type {
    flex: 1 1 auto;
}
.form-column .dlg-field-card-head {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid #e0e0e0;
}
.form-column .dlg-field-card-head .form-label,
.form-column .dlg-field-card-head legend {
    float: none;
    border: 0;
    margin: 0;
    padding: 0;
    width: auto;
    display: inline;
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.3;
    color: #343a40;
}
.form-column .dlg-field-card-body {
    padding: 0.55rem 0.65rem 0.65rem;
}
.form-column .dlg-field-card-body .form-text {
    margin-top: 0.35rem;
}
/* Groupes de choix (cases/radios) dans une carte : 2 options par ligne
   (compacité) et rangée entière cliquable avec retour visuel, comme sur
   les formulaires de rôle (Cohérence). */
.form-column .dlg-field-card-body > div:has(> .form-check) {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.35rem 0.6rem;
}
.form-column .dlg-field-card-body .form-check {
    display: flex;
    align-items: flex-start; /* les options riches (titre + aide) tiennent sur 2 lignes */
    gap: 0.5rem;
    min-height: 40px;
    margin: 0;
    padding: 0.4rem 0.55rem;
    border-radius: 8px;
    transition: background-color 0.15s ease;
}
/* La coche/puce reste DANS sa rangée : on neutralise le flottement Bootstrap
   (float + margin négative) qui la faisait déborder et se faire rogner. */
.form-column .dlg-field-card-body .form-check-input {
    float: none;
    flex: 0 0 auto;
    margin: 0.25em 0 0; /* alignée optiquement sur la première ligne du libellé */
    width: 1.15em;
    height: 1.15em;
    cursor: pointer;
}
.form-column .dlg-field-card-body .form-check-label {
    cursor: pointer;
    user-select: none;
    font-size: 0.875rem;
    flex: 1;
    min-width: 0; /* autorise le retour à la ligne du texte d'aide dans la cellule */
}
.form-column .dlg-field-card-body .form-check:hover {
    background-color: #f8f9fa;
}
.form-column .dlg-field-card-body .form-check:has(.form-check-input:checked) {
    background-color: #e8f0fb;
}
.form-column .dlg-field-card-body .form-check:has(.form-check-input:checked) .form-check-label {
    color: #212529;
    font-weight: 500;
}

/*
===================================================================
 ENTÊTE CONTEXTUEL DES FORMULAIRES DE DIALOGUE (.form-intro)
 Bloc opt-in rendu au-dessus du cadre du formulaire quand le canvas
 pose form_intro (cf. _form_content.html.twig) : pastille cobalt de
 l'entité + description de ce que l'utilisateur édite et de son
 importance. Reprend le motif de l'entête des formulaires Console
 (.cs-head-icon) : pastille 46x46 sur fond cobalt très clair.
 Ergonomie (Bastien & Scapin — Guidage / Incitation).
===================================================================
*/
.form-column .form-intro {
    display: flex;
    align-items: flex-start; /* le bloc texte peut porter plusieurs lignes + les puces de contexte */
    gap: 0.9rem;
    /* Un BANDEAU, comme l'entête d'une rubrique du workspace : il porte ses propres
       marges intérieures (la colonne n'en a plus) et se sépare de ce qui suit par un
       filet, sans marge. */
    margin: 0;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--border-light);
}
.form-column .form-intro-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    border-radius: 11px;
    background-color: #e8f0fb;
    color: #0047AB;
}
.form-column .form-intro-icon svg {
    width: 26px;
    height: 26px;
}
.form-column .form-intro-title {
    margin: 0 0 0.15rem;
    font-size: 1.05rem;
    font-weight: 700;
    color: #212529;
}
.form-column .form-intro-desc {
    margin: 0;
    font-size: 0.875rem;
    line-height: 1.45;
    color: #6c757d;
    max-width: 78ch; /* longueur de ligne lisible (Opquast — confort de lecture) */
}
/* Puces de contexte : rappel des informations pré-remplies (champs masqués),
   présentées en badges bicolores « libellé | valeur » : le libellé est une
   pastille grise à texte noir, la valeur repose sur le fond blanc du badge
   en texte gris, le tout encadré en pilule (charte Joseara). */
.form-column .form-intro-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.5rem;
    margin: 0.6rem 0 0;
}
.form-column .form-intro-fact {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.18rem 0.75rem 0.18rem 0.18rem; /* fine gouttière blanche autour de la pastille du libellé */
    background-color: #ffffff;
    border: 1px solid #ced4da;
    border-radius: 999px;
}
.form-column .form-intro-fact dt {
    margin: 0;
    padding: 0.2rem 0.65rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    color: #212529;
    background-color: #e9ecef;
    border-radius: 999px;
}
.form-column .form-intro-fact dd {
    margin: 0;
    font-size: 0.82rem;
    font-weight: 600;
    color: #495057;
}

/*
===================================================================
 BANDEAU D'ALERTE DES FORMULAIRES DE DIALOGUE (.form-alerte)
 Bloc opt-in rendu TOUT EN HAUT du volet de saisie quand le canvas
 pose form_alerte (cf. _form_content.html.twig) : un fait décisif
 qui doit être vu avant toute saisie — par exemple une police
 signalée « à ne pas renouveler ».
 Placé au-dessus de .form-intro, et non dans la colonne d'attributs
 calculés, où ses dizaines de lignes le noieraient.
 Ergonomie (Bastien & Scapin — Guidage / Incitation ; Nielsen 1).
===================================================================
*/
.form-column .form-alerte {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    /* Bandeau pleine largeur, au ras de la colonne (grammaire du workspace). */
    margin: 0;
    padding: 0.7rem 1.25rem;
    border-left: 4px solid;
    border-bottom: 1px solid var(--border-light);
}
.form-column .form-alerte-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    margin-top: 0.1rem;
}
.form-column .form-alerte-icon svg { width: 22px; height: 22px; }
.form-column .form-alerte-title {
    margin: 0 0 0.15rem;
    font-size: 0.92rem;
    font-weight: 700;
}
.form-column .form-alerte-desc {
    margin: 0;
    font-size: 0.85rem;
    line-height: 1.45;
    max-width: 78ch;
}
/* Niveaux. Contraste du texte sur fond teinté vérifié pour AA (WCAG 1.4.3). */
.form-column .form-alerte--warning {
    background-color: #fff8e6;
    border-left-color: #e69500;
    color: #7a4f00;
}
.form-column .form-alerte--warning .form-alerte-icon { color: #e69500; }
.form-column .form-alerte--danger {
    background-color: #fdf2f3;
    border-left-color: #dc3545;
    color: #842029;
}
.form-column .form-alerte--danger .form-alerte-icon { color: #dc3545; }
.form-column .form-alerte--info {
    background-color: #e8f0fb;
    border-left-color: #0047AB;
    color: #05306b;
}
.form-column .form-alerte--info .form-alerte-icon { color: #0047AB; }

/* STYLES POUR LE DÉFILEMENT DE FORMULAIRES (utilisé par le dialogue de recherche avancée) */
.form-scrollable-content {
    max-height: 45vh; /* Hauteur augmentée pour plus de confort */
    overflow-y: auto;
    padding-right: 10px; /* Padding ajusté */
    scrollbar-width: thin;
    scrollbar-color: #adb5bd #f8f9fa; /* Couleurs pour Firefox, assorties au thème */
}
.form-scrollable-content::-webkit-scrollbar {
    width: 8px;
}
.form-scrollable-content::-webkit-scrollbar-track {
    background: transparent; /* Le fond de la piste est transparent */
}
.form-scrollable-content::-webkit-scrollbar-thumb {
    background-color: #adb5bd; /* Couleur du curseur, gris moyen */
    border-radius: 4px;
    border: 2px solid #f8f9fa; /* Crée un effet de "flottement" avec la couleur de fond */
}
.form-scrollable-content::-webkit-scrollbar-thumb:hover {
    background-color: #495057; /* Plus foncé au survol */
}


/* --- 4.5 Squelettes de chargement --- */
/*
===================================================================
 SECTION: SQUELETTE DE CHARGEMENT (Shimmer Effect)
===================================================================
*/

:root {
  --sk-bg: #e9ecef;        /* Gris de base (Bootstrap-like) */
  --sk-soft: #f3f3f3;      /* Couleur de transition douce */
  --sk-light: #ffffff;     /* Le cœur du reflet (très brillant) */

  /* Couleurs pour le mode sombre */
  --sk-dark-bg: #343a40;
  --sk-dark-soft: #495057;
  --sk-dark-light: #6c757d;
}
 
/* Styles de base communs à tous les squelettes */
.skeleton-line,
.skeleton-row {
    background-color: var(--sk-bg);
    border-radius: 4px;
    position: relative;
    overflow: hidden; /* Important pour l'animation */
}

/* Squelette pour une ligne de texte ou un champ (utilisé dans les dialogues et collections) */
.skeleton-line {
    display: block; /* Assure que la hauteur est respectée */
}

/* Squelette pour une ligne complète de tableau (utilisé dans les listes principales) */
.skeleton-row {
    height: 50px; /* Hauteur d'une ligne de tableau */
    width: 100%;
}

/* Animation Shimmer */
.skeleton-line::after,
.skeleton-row::after {
    content: "";
    position: absolute;
    top: 0; right: 0; bottom: 0; left: 0;
    background: linear-gradient(
        90deg,
        var(--sk-bg) 0%,
        var(--sk-soft) 20%,
        var(--sk-light) 50%,
        var(--sk-soft) 80%,
        var(--sk-bg) 100%
    );
    background-size: 200% 100%;
    animation: shimmer-advanced 1.8s infinite linear;
}

@keyframes shimmer-advanced {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* WCAG 2.3.3 : on retire le MOUVEMENT, pas le retour visuel — le bloc gris reste, il dit
   toujours « ceci se charge ». La regle vise le PSEUDO-ELEMENT, seul porteur de
   l'animation ci-dessus : ecrite sur `.skeleton-line`, elle n'aurait rien neutralise.
   Elle couvre d'un coup les cinq squelettes du projet, celui du total compris. */
@media (prefers-reduced-motion: reduce) {
    .skeleton-line::after,
    .skeleton-row::after {
        animation: none;
        background: var(--sk-soft);
    }
}

/* LE SQUELETTE D'UN TOTAL, dans la cellule de valeur du pied d'une collection.
   Rien n'est redessine : la base `.skeleton-line` apporte le gris, le degrade, les jetons
   de theme et l'animation. Ne restent que les dimensions — et `1em` de haut pour que la
   rangee garde EXACTEMENT la hauteur qu'aura le montant : aucun saut de mise en page entre
   l'attente et le nombre. */
.skeleton-total {
    display: inline-block;
    vertical-align: middle;
    width: 7ch;
    height: 1em;
}

/* NOUVEAU : Style pour le squelette sur fond sombre (colonne des attributs) */
.calculated-attributes-column-skeleton .skeleton-line {
    background-color: var(--sk-dark-bg); /* Un gris plus clair que le fond #212529 */
}

.calculated-attributes-column-skeleton .skeleton-line::after {
    background: linear-gradient(
        90deg,
        var(--sk-dark-bg) 0%,
        var(--sk-dark-soft) 20%,
        var(--sk-dark-light) 50%,
        var(--sk-dark-soft) 80%,
        var(--sk-dark-bg) 100%
    );
    background-size: 200% 100%;
}

/* NOUVEAU : Fond sombre pour le squelette de la colonne des attributs */
.calculated-attributes-column-skeleton {
    background-color: #212529; /* Couleur de fond de la colonne des attributs */
    padding: 1.5rem; /* Assurer le même padding que la colonne réelle (sans coins arrondis, comme elle) */
}

/* NOUVEAU : Squelette pour la colonne de formulaire, imitant parfaitement .form-column */
.form-column-skeleton {
    flex: 1;
    min-width: 0;
    overflow: hidden; /* Le squelette ne doit pas défiler */
    padding: 1.5rem; /* Imitation du padding de .form-column */
    background-color: #ffffff; /* Imitation du fond de .form-column (sans coins arrondis, comme elle) */
}

/* --- 4.6 Messages de feedback --- */
.feedback-container {
    /* NOUVEAU : On s'assure que le conteneur peut grandir mais pas au-delà de l'espace disponible. */
    flex: 1 1 auto; /* flex-grow: 1, flex-shrink: 1, flex-basis: auto */
    min-width: 0; /* Correctif crucial pour que le flex-shrink fonctionne correctement avec du contenu long. */
    display: flex;
    flex-direction: column;
    justify-content: center;
}

@keyframes fadeInFeedback {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}

.feedback-container .feedback-message {
    padding: 10px 15px;
    border-radius: 5px;
    margin-bottom: 5px; /* Espace si plusieurs messages s'affichent */
    font-size: 0.9em;
    animation: fadeInFeedback 0.2s ease;
    /* MODIFICATION : On passe en flexbox pour un meilleur contrôle */
    display: flex;
    flex-wrap: wrap; /* Permet au contenu de passer à la ligne */
    align-items: center;
    gap: 0 8px; /* Espace horizontal entre le timestamp et le message */
}

/* Style pour les messages de succès (vert) */
.feedback-message.feedback-success {
    background-color: #d1e7dd;
    border-left: 5px solid #0f5132;
    color: #0f5132;
    font-weight: normal; /* Texte normal, pas gras [cite: 5] */
}

/* Style pour les messages d'erreur (rouge) */
.feedback-message.feedback-error {
    background-color: #f8d7da;
    border-left: 5px solid #842029;
    color: #842029;
    font-weight: bold; /* Texte en gras [cite: 4] */
}

/* Style pour les messages d'avertissement (jaune) */
.feedback-message.feedback-warning {
    background-color: #fff3cd;
    border-left: 5px solid #664d03;
    color: #664d03;
    font-weight: bold; /* Texte en gras [cite: 6] */
}

/* Style pour la partie "timestamp" du message */
.feedback-message .timestamp {
    font-weight: bold;
    white-space: nowrap; /* Empêche le timestamp de se couper */
}

/* NOUVEAU : Style pour le texte du message pour qu'il prenne l'espace restant */
.feedback-message span:last-child {
    flex-grow: 1;
    word-break: break-word; /* Permet de couper les mots longs si nécessaire */
}

/* --- 4.7 Barres de défilement spécifiques --- */
/*
===================================================================
 SECTION: STYLE POUR LES BARRES DE DÉFILEMENT DES COLONNES DE DIALOGUE
===================================================================
*/

/* NOUVEAU : Style de la barre de défilement pour la colonne des attributs (mode sombre) */
.calculated-attributes-content::-webkit-scrollbar {
    width: 8px;
}
.calculated-attributes-content::-webkit-scrollbar-track {
    background: transparent; /* La piste est invisible sur le fond sombre */
}
.calculated-attributes-content::-webkit-scrollbar-thumb {
    background-color: #495057; /* Un gris foncé, visible sur le fond #212529 */
    border-radius: 4px;
    border: 2px solid #212529; /* Crée un effet de "flottement" avec la couleur de fond */
}
.calculated-attributes-content::-webkit-scrollbar-thumb:hover {
    background-color: #6c757d; /* Plus clair au survol */
}

/* --- 4.8 Cas spécifique : Modale de confirmation --- */
#confirmation-dialog-modal .modal-header {
    background-color: #dc3545; /* Rouge pour la confirmation */
}

/* On s'assure que le header de la modale de confirmation n'est pas flex */
#confirmation-dialog-modal .modal-content {
    display: block;
}

#confirmation-dialog-modal .modal-body {
    background-color: #ffffff; /* Fond blanc pour la confirmation */
}

/* Champ mot de passe de la confirmation : champ unifié arrondi avec icône
   cadenas en préfixe et bascule œil en suffixe (même pattern que la page de
   connexion). Couleurs strictement issues de la charte. */
.dialog-password-field {
    position: relative;
}

.dialog-password-icon {
    position: absolute;
    top: 50%;
    left: 0.85rem;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    color: #6c757d; /* --text-muted */
    pointer-events: none;
}
.dialog-password-icon svg { width: 20px; height: 20px; }

.dialog-password-field .form-control {
    height: 48px;            /* zone de saisie confortable */
    padding-left: 2.6rem;    /* place pour l'icône préfixe */
    padding-right: 3rem;     /* place pour la bascule œil */
    color: #000000;          /* --text-black (contenu de champ) */
}

/* Focus visible conforme charte : bordure + anneau cobalt */
.dialog-password-field .form-control:focus {
    border-color: #0047AB; /* --cobalt */
    box-shadow: 0 0 0 0.25rem rgba(0, 71, 171, 0.35); /* --cobalt-focus-ring */
}

/* Bouton afficher/masquer (≥ 44×44, WCAG 2.5.5), sans bordure ni fond */
.dialog-password-toggle {
    position: absolute;
    top: 50%;
    right: 0.35rem;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: transparent;
    color: #6c757d; /* --text-muted */
    border-radius: 8px;
    cursor: pointer;
}
.dialog-password-toggle:hover { color: #0047AB; } /* --cobalt */
.dialog-password-toggle:focus-visible {
    outline: 2px solid #0047AB; /* --cobalt */
    outline-offset: 2px;
}
.dialog-password-toggle svg { width: 20px; height: 20px; }

/* 6. SUPERPOSITION : le z-index de #confirmation-dialog-modal est calculé
   dynamiquement par confirmation-dialog_controller.js pour rester au-dessus
   de toutes les modales ouvertes, quel que soit le niveau d'imbrication. */

/* --- 4.9 Éléments divers (boutons, infobulles) --- */
/* 5. ÉLÉMENTS DANS LES BOUTONS */
.app-dialog .modal-footer .btn,
#confirmation-dialog-modal .modal-footer .btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

/* Cible l'icône SVG à l'intérieur du conteneur .button-icon pour le bouton primaire */
.btn-primary .button-icon svg {
    fill: white; /* On s'assure que l'icône est blanche sur le bouton bleu */
}

/* Fichier : assets/styles/app.css (ou un fichier importé) */

/* Styles pour l'infobulle (tooltip) */
.canvas-tooltip {
    position: absolute;
    background-color: #0d6efd; /* Couleur à adapter */
    color: white;
    padding: 0.8rem 1rem;
    border-radius: 8px;
    font-size: 0.85rem;
    line-height: 1.4;
    max-width: 250px;
    overflow-wrap: anywhere;
    z-index: 1090; /* Au-dessus de tout */
    pointer-events: none;
    opacity: 0;
    transform: translateX(-10px);
    transition: opacity 0.2s ease, transform 0.2s ease;
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}

.canvas-tooltip.is-visible {
    opacity: 1;
    transform: translateX(0);
}

/* Infobulle sombre de la barre d'outils — reprend le style des infobulles du
   tableau de bord du workspace (fond #212529, texte blanc). Élément flottant
   ajouté au <body>, piloté par dark-tooltip_controller.js. */
.toolbar-dark-tip {
    position: fixed;
    z-index: 1090; /* Au-dessus de la barre d'outils et des listes */
    display: none;
    max-width: 260px;
    background-color: #212529;
    color: #ffffff;
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.4;
    padding: 0.45rem 0.7rem;
    border-radius: 8px;
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.35);
    pointer-events: none;
    text-align: center;
    white-space: nowrap;
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 0.15s ease, transform 0.15s ease;
}

.toolbar-dark-tip.is-visible {
    opacity: 1;
    transform: translateY(0);
}

/* ================================================================== */
/* SECTION 5: STYLES POUR LES COMPOSANTS RÉUTILISABLES                */
/* ================================================================== */

/* --- 5.1 Gestionnaire de collection (Accordéon) --- */
/*
===================================================================
 SECTION: COMPOSANT ACCORDÉON (Gestionnaire de Collection)
===================================================================
*/
.collection-manager-accordion {
    /* Aligné sur les cartes de champ à entête bandeau (.dlg-field-card) : même
       bordure, mêmes coins, même élévation au survol (Cohérence). */
    background-color: #ffffff;
    border: 1px solid #dee2e6;
    border-radius: 12px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
    overflow: hidden;
}
.collection-manager-accordion:hover {
    border-color: #ced4da;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
.accordion-title {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 0.75rem; /* meme bandeau que .dlg-field-card-head */
    /* Plus de `cursor: pointer` : l'entete n'ouvre plus rien, c'est l'ONGLET qui
       ouvre la liste. Une main sur un bandeau inerte promet un geste qui n'existe pas. */
}

.title-left-block {
    flex-grow: 1;
    /* NOUVEAU : On transforme le conteneur en flex pour aligner tous les éléments sur une seule ligne. */
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
/* ACTION OFFERTE MAIS INDISPONIBLE ICI — grisée, jamais masquée.
   Une action qui disparaît laisse l'utilisateur soupçonner un droit manquant ou une
   panne ; inactive et porteuse de son infobulle, elle dit où aller. Sert au bouton
   « Ajouter » des rubriques dont l'entité n'existe que rattachée à une autre
   (cf. `creation_interdite`). */
.btn.is-disabled,
.btn.is-disabled:hover,
[data-toolbar-target].is-disabled,
[data-context-menu-target].is-disabled {
    opacity: 0.65;
    pointer-events: none;
    cursor: not-allowed;
}

.field-label {
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.3;
    color: #343a40;
}
.badge {
    font-style: italic;
    font-weight: bold;
    /* transform: translateY(-5px); */ /* N'est plus nécessaire avec l'alignement flex */
    background-color: #0d6efd !important;
}
.title-right-block {
    /* TOUJOURS A DROITE, y compris pendant le chargement de la liste.
       Deux regles .accordion-title se contredisent (space-between ici, flex-start dans
       interactive-menu.css) et c'est la seconde qui l'emporte : le bouton ne tenait sa
       place que par le flex-grow de .title-left-block — or ce bloc cede la place a
       .title-left-block-loading pendant le chargement, lequel ne grandit pas. Le bouton
       retombait alors contre le spinner. margin-left:auto le tient a droite quelle que
       soit la regle gagnante, et quoi qu'il ait a sa gauche. */
    margin-left: auto;
    /* VISIBLE EN PERMANENCE. Il n'apparaissait qu'au survol, du temps ou le titre
       etait la poignee d'un accordeon. Un bouton qu'il faut trouver en promenant la
       souris n'existe pas pour qui navigue au clavier (WCAG 2.1.1). */
    /* La gouttière est maintenant gérée par le padding du .accordion-title parent. */
}
/* LA LISTE EST OUVERTE : il n'y a plus d'accordeon a deplier, chaque collection etant
   un onglet du formulaire -- et l'onglet ouvre deja son contenu.

   SURCHARGE, ET NON SIMPLE SUPPRESSION. `.accordion-content` designe AUSSI le panneau
   des attributs calcules du workspace (interactive-menu.css l. 1163), et cette
   regle-la n'est pas scopee : chargee apres app.css, elle retomberait sur nous et
   replierait la liste, sans plus aucun moyen de l'ouvrir. */
.collection-manager-accordion .accordion-content {
    max-height: none;
    overflow: visible;
    padding: 1px;
    border-top: 1px solid #e0e0e0;
}

/*
===================================================================
 MODERNISATION DES COLLECTIONS DANS LES DIALOGUES (scopée à l'accordéon)
 Finitions alignées sur les cartes de champ : badge et bouton d'ajout au
 cobalt de marque, tableau interne aéré avec entêtes discrètes. Aucune
 incidence hors du composant (sélecteurs préfixés par l'accordéon).
===================================================================
*/
/* Compteur d'éléments : cobalt de marque, droit (pas d'italique). */
.collection-manager-accordion .badge {
    font-style: normal;
    background-color: #0047AB !important;
    border-radius: 999px;
}
/* Bouton « Ajouter » : action primaire de marque (cobalt, hover cobalt foncé). */
.collection-manager-accordion .accordion-title .btn-primary {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    background-color: #0047AB;
    border-color: #0047AB;
}
.collection-manager-accordion .accordion-title .btn-primary:hover,
.collection-manager-accordion .accordion-title .btn-primary:focus-visible {
    background-color: #003380;
    border-color: #003380;
}
/* Tableau interne : entêtes en micro-libellés discrets, rangées aérées. */
.collection-manager-accordion .accordion-content thead th {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #6c757d;
    background-color: #f8f9fa;
    border-bottom: 1px solid #dee2e6;
    vertical-align: middle;
}
.collection-manager-accordion .accordion-content tbody td {
    padding: 0.6rem 0.75rem;
    vertical-align: middle;
}
.collection-manager-accordion .accordion-content .table {
    margin-bottom: 0;
}
/* Le libellé du form_row est redondant : le bandeau de l'accordéon porte déjà
   le même intitulé (+ compteur). On le masque, sans perte d'information. */
.form-column .mb-3:has(.collection-manager-accordion) > label,
.form-column .mb-3:has(.collection-manager-accordion) > .form-label {
    display: none;
}
/* `.collection-item`, `.item-icon`, `.item-text`, `.text-main` ont ete retires le
   2026-10-02 : ils stylaient `templates/components/_collection.html.twig` et
   `collection_items/`, que plus aucun code n'incluait.

   ⚠ `.text-secondary` ci-dessous vivait au milieu de ce bloc mort, mais il est bien
   VIVANT : il redefinit globalement l'utilitaire Bootstrap, et des dizaines de gabarits
   en dependent. Le supprimer avec son voisinage aurait ete la plus large regression du
   lot. */
.text-secondary {
    font-size: 0.85em;
    color: #a0a0a0;
}
.title-right-block .btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: bold;
    border-radius: 8px; /* Forme rectangulaire avec coins arrondis */
    padding: 0.5rem 1rem; /* Padding pour un aspect rectangulaire */
}
/* LA PHRASE QUI PREND LA PLACE DU BOUTON « AJOUTER » quand l'ajout est impossible
   (option `ajoutImpossible` du canevas). Texte d'aide, pas une erreur : gris muet sur
   fond clair (charte : --text-muted, 4,7:1 sur blanc), à droite comme le bouton qu'elle
   remplace, et bornée en largeur pour ne pas écraser l'entête de la liste. */
.jsb-ajout-impossible {
    margin: 0;
    max-width: 32rem;
    font-size: 0.8rem;
    line-height: 1.35;
    color: #6c757d;
    text-align: right;
}

/*
===================================================================
 SECTION: STYLE POUR ACCORDÉON DE COLLECTION DÉSACTIVÉ
===================================================================
*/

.collection-manager-accordion.is-disabled {
    opacity: 0.65;
    background-color: #e9ecef; /* Un fond gris léger */
    pointer-events: none; /* Bloque toutes les interactions (clics, survol) */
    cursor: not-allowed;
}

/* On s'assure que le curseur est correct sur le titre aussi */
.collection-manager-accordion.is-disabled .accordion-title {
    cursor: not-allowed;
}

/* --- 5.2 Critères de recherche avancée --- */
/* ================================================================== */
/* Formulaire de recherche avancée — criterion blocks               */
/* ================================================================== */

/* Le bloc de critère est un <fieldset> : on neutralise les styles natifs. */
.criterion-block {
    min-width: 0;
    padding: 0;
    margin: 0 0 0.75rem 0;
    background: #ffffff;
    border: 1px solid #dee2e6;
    border-left: 4px solid #dee2e6;
    border-radius: 0.5rem;
    transition: border-left-color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
}
.criterion-block.is-active {
    border-left-color: #0047AB;
    background-color: #e8f0fb;
}
.criterion-block.has-error {
    border-left-color: #dc3545;
}
/* La <legend> native se place hors flux : on la réintègre comme un vrai en-tête,
   en ligne avec son icône de signification. */
.criterion-block .criterion-header {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    float: none;
    width: 100%;
    padding: 0.45rem 0.875rem;
    margin: 0;
    background: transparent;
    border-bottom: 1px solid #dee2e6;
    font-size: 0.78rem;
    font-weight: 600;
    color: #495057;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
/* Icône d'en-tête : neutre au repos, cobalt quand le critère est actif (hérite de la
   couleur du texte de l'en-tête via currentColor). */
.criterion-header__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    color: #6c757d;
}
.criterion-header__icon svg { width: 100%; height: 100%; }
.criterion-header__label { min-width: 0; }
.criterion-block.is-active .criterion-header {
    color: #0047AB;
    border-bottom-color: rgba(0, 71, 171, 0.2);
}
.criterion-block.is-active .criterion-header__icon { color: #0047AB; }
.criterion-block .criterion-body { padding: 0.75rem; }

/* Ligne de champ : opérateur/mode + valeur alignés, responsive. */
.jsb-advanced-form .jsb-field-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}
.jsb-advanced-form .jsb-field-mode {
    flex: 0 0 auto;
    width: auto;
    min-width: 9.5rem;
}
.jsb-advanced-form .jsb-field-row > .form-control,
.jsb-advanced-form .jsb-field-row > .form-select:not(.jsb-field-mode) {
    flex: 1 1 10rem;
    min-width: 0;
}
/* Cibles tactiles confortables (≥ 44px de haut) et focus charte cobalt. */
.jsb-advanced-form .form-control,
.jsb-advanced-form .form-select {
    min-height: 44px;
}
.jsb-advanced-form .form-control:focus,
.jsb-advanced-form .form-select:focus {
    border-color: #0047AB;
    box-shadow: 0 0 0 0.2rem rgba(0, 71, 171, 0.25);
}

/* Ligne de dates : libellés « Du … au … » intercalés. */
.jsb-advanced-form .jsb-date-row { flex-wrap: nowrap; }
.jsb-advanced-form .jsb-field-inline-label {
    flex: 0 0 auto;
    margin: 0;
    color: #6c757d;
    font-size: 0.85rem;
}
@media (max-width: 480px) {
    .jsb-advanced-form .jsb-date-row { flex-wrap: wrap; }
}

/* Presets de période : puces cobalt discrètes. */
.jsb-advanced-form .jsb-date-presets {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
    margin-bottom: 0.625rem;
}
.jsb-advanced-form .jsb-preset-btn {
    border: 1px solid rgba(0, 71, 171, 0.3);
    background-color: #e8f0fb;
    color: #0047AB;
    border-radius: 2rem;
    padding: 0.25rem 0.75rem;
    font-size: 0.78rem;
    font-weight: 500;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;
}
.jsb-advanced-form .jsb-preset-btn:hover,
.jsb-advanced-form .jsb-preset-btn:focus {
    background-color: #0047AB;
    color: #ffffff;
    outline: none;
}

/* Message d'erreur inline (validation de plage de dates). */
.jsb-advanced-form .jsb-field-error {
    margin: 0.5rem 0 0 0;
    color: #842029;
    background-color: #f8d7da;
    border-radius: 0.375rem;
    padding: 0.35rem 0.625rem;
    font-size: 0.8rem;
}

/* `.contextual-row-actions` a ete retiree le 2026-10-02 : aucun gabarit ne la posait.
   Les actions de ligne vivent dans `.list-row-actions-container` (section 5.4). */

/*
===================================================================
 SECTION: BOUTONS D'ACTION SUR LES LIGNES DE COLLECTION (SURVOL)
===================================================================
*/

/* Style pour le conteneur des boutons d'action sur une ligne de collection.
   Il est estompé par défaut et se révèle au survol / focus clavier. */
.list-row-actions-container {
    opacity: 0; /* Caché par défaut (souris) */
    transition: opacity 0.2s ease-in-out;
    display: flex;
    /* UNE GOUTTIÈRE FRANCHE entre deux cibles : un clic destiné à l'une ne doit pas
       tomber sur sa voisine — et la voisine peut être « Supprimer ». */
    gap: 0.625rem;
    justify-content: flex-end;
}

/* Classe ajoutée par collection_controller.js pour rendre les boutons visibles. */
.list-row-actions-container.visible,
/* Accessibilité : révélation au survol de la ligne (fallback CSS pur, indépendant du JS)
   ET au focus clavier d'un bouton d'action (WCAG 2.1 — contenu au survol / accès clavier). */
tr:hover .list-row-actions-container,
.list-row-actions-container:focus-within {
    opacity: 1;
}

/* Sur écran tactile (pointeur grossier), il n'y a pas de survol : les actions restent
   toujours visibles pour rester atteignables. */
@media (hover: none), (pointer: coarse) {
    .list-row-actions-container {
        opacity: 1;
    }
}

/* Bouton d'action de ligne : cible confortable (≈ 34px, cf. WCAG 2.5.5 Target Size),
   fond neutre clair de la charte, coins arrondis, retour visuel au survol. */
.list-row-action-btn {
    width: 34px;
    height: 34px;
    flex-shrink: 0; /* la cible garde ses 34 px, quel que soit le nombre de boutons */
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--bs-border-radius);
    /* AUCUN FOND PROPRE, ET PAS D'OMBRE AU REPOS.
       Le bouton portait un gris clair et une ombre : il dessinait un bloc qui tranchait
       sur la ligne, alors qu'il n'est qu'une action DANS cette ligne. Transparent, il
       épouse le fond quel qu'il soit — blanc au repos, et le gris de survol de la ligne
       quand la souris passe, sans jamais créer de rupture dans un sens ni dans l'autre.
       Le survol du bouton lui-même, lui, reste marqué : c'est là qu'un retour est utile. */
    background-color: transparent;
    box-shadow: none;
    transition: background-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.list-row-action-btn:hover {
    background-color: #e9ecef; /* --bg-muted : survol neutre de la charte */
}
.list-row-action-btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 0.2rem rgba(0, 71, 171, 0.35); /* --cobalt-focus-ring */
}
/* L'ACTION DESTRUCTIVE SE VOIT ROUGE, DÈS LE REPOS.
   La couleur est portée par le bouton, donc héritée par l'icône (currentColor) :
   celle rendue par le serveur comme celle injectée par le tampon des collections
   s'accordent sans que l'une ait à répéter la classe de l'autre.
   Le rouge ne décore rien ici — il annonce une perte (charte, Règle 3). */
.list-row-action-btn--danger {
    color: var(--danger, #dc3545);
}
.list-row-action-btn--danger:hover {
    background-color: #f8d7da; /* --danger-bg */
}

/* NOUVEAU : L'icône à l'intérieur du bouton d'action.
   On utilise stroke-width pour un effet "gras" sur les icônes SVG. */
.list-row-actions-container .btn .ux-icon {
    stroke-width: 2; /* Largeur de trait par défaut pour les icônes Lucide */
    transition: stroke-width 0.15s ease-in-out;
}

.list-row-actions-container .btn:hover .ux-icon {
    stroke-width: 2.5; /* Trait plus épais pour un effet "gras" au survol */
}

/* ================================================================== */
/* SECTION 6: STYLES POUR LES ITEMS D'AUTOCOMPLÉTION (TOMSELECT)      */
/* ================================================================== */

.jsb-autocomplete-item {
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 8px; /* Respiration entre titre / contexte / indicateurs */
}

/* LIGNE 1 : TITRE */
.jsb-autocomplete-title {
    color: #0047AB; /* Bleu Cobalt */
    font-weight: 700;
    font-size: 1rem;
    line-height: 1.3;
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px;
}

.jsb-autocomplete-title-suffix {
    font-weight: 500;
    color: #6c757d;
    font-size: 0.8em;
}

/* LIGNE 2 : CONTEXTE (ligne de méta-données) */
.jsb-autocomplete-context {
    color: #495057;
    font-size: 0.8rem;
    line-height: 1.4;
    display: flex;
    flex-wrap: wrap;
    column-gap: 10px;
    row-gap: 4px;
    align-items: center;
}

.jsb-autocomplete-context strong {
    color: #212529;
    font-weight: 600;
}

.jsb-context-separator {
    color: #ced4da;
}

/* ------------------------------------------------------------------ */
/* LIGNE 3 : L'AIDE AU CHOIX                                           */
/* ------------------------------------------------------------------ */
/*
 * TROIS CHIFFRES EN LIGNE, et non cinq tuiles empilees.
 *
 * L'ancienne grille `repeat(auto-fit, minmax(7.5rem, 1fr))` ne faisait qu'UNE colonne
 * dans une deroulante de 350 px : les cinq tuiles s'empilaient, et la carte occupait
 * sept a dix-huit lignes. Une liste d'autocompletion sert a CHOISIR ; au-dela de trois
 * chiffres on ne choisit plus, on lit.
 */
/*
 * TROIS CHIFFRES SUR UNE SEULE LIGNE — et c'est une contrainte, pas une preference.
 *
 * Mesure au navigateur, dans une deroulante de 380 px : avec un espacement de 14 px et
 * des libelles complets, le troisieme chiffre passait a la ligne. On retombait sur
 * l'empilement qu'on venait justement de supprimer, en trois lignes au lieu de cinq.
 *
 * `space-between` repartit l'espace disponible au lieu de le fixer, et le libelle cede
 * avant la valeur : un montant tronque ne veut rien dire, un libelle raccourci se devine.
 */
.jsb-autocomplete-aide {
    display: flex;
    flex-wrap: nowrap;
    justify-content: space-between;
    column-gap: 10px;
    font-size: 0.75rem;
    line-height: 1.35;
    overflow: hidden;
}

/* Libelle et valeur restent solidaires : un retour a la ligne entre eux les separerait. */
.jsb-chiffre {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    white-space: nowrap;
    min-width: 0;
}

/*
 * --text-body, et non --text-muted : mesure sur le fond d'une option survolee
 * (#e8f0fb), #6c757d tombe a 4,08 — sous le seuil AA de 4,5. #495057 tient 7,12.
 */
.jsb-chiffre-libelle {
    color: var(--text-body, #495057);
    font-weight: 400;
    /* Le libelle se laisse rogner ; la valeur, jamais. */
    overflow: hidden;
    text-overflow: ellipsis;
}

/*
 * `tabular-nums` : trois montants qu'on compare doivent aligner leurs chiffres, sans
 * quoi l'oeil compare des largeurs au lieu de comparer des valeurs.
 */
.jsb-chiffre-valeur {
    color: var(--text-dark, #212529);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/*
 * LES TEINTES SOMBRES, et c'est mesure, pas esthetique.
 *
 * Sur le fond d'une option survolee (#e8f0fb), --success (#198754) et --danger
 * (#dc3545) tombent a 3,95 et 3,94 — sous le seuil AA de 4,5. Leurs variantes foncees
 * tiennent 8,15 et 8,24, sur ce fond comme sur le blanc.
 *
 * Et la couleur n'est JAMAIS seule a porter l'etat (WCAG 1.4.1) : « 0,00 » dit le solde,
 * un nombre non nul dit le reste, le signe moins dit le trop-percu.
 */
.jsb-chiffre--solde { color: var(--success-dark, #0f5132); }
.jsb-chiffre--du { color: var(--danger-dark, #842029); }

/* ------------------------------------------------------------------ */
/* RELEVÉ FINANCIER D'UNE TRANCHE (onglets Prime, Commission…)         */
/* ------------------------------------------------------------------ */
/*
 * Rien d'inventé : la synthèse reprend `.jsb-chiffre*` (libellé + valeur solidaires,
 * chiffres tabulaires, teinte foncée du solde/dû), le tableau la grammaire des
 * collections de dialogue (trois colonnes figées). Ne s'ajoutent que l'espacement des
 * blocs et le retour à la ligne des chiffres — une fiche étroite (tablette) en aligne
 * deux par ligne plutôt que de les rogner.
 */
.jsb-releve-bloc + .jsb-releve-bloc {
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border-color, #dee2e6);
}

/* Titre et synthèse prennent la gouttière des cellules du tableau (`.p-2`) : sans elle,
   ils collaient au bord du cadre quand les lignes juste dessous s'en écartaient. */
.jsb-releve-titre,
.jsb-releve-chiffres,
.jsb-releve-vide {
    padding-inline: 0.5rem;
}

.jsb-releve-titre {
    margin: 0.5rem 0 0.5rem;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-dark, #212529);
}

.jsb-releve-chiffres {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
    margin: 0 0 0.75rem;
    font-size: 0.8rem;
}

/* `<dl>` sémantique : on annule les marges par défaut de dt/dd. */
.jsb-releve-chiffres dt,
.jsb-releve-chiffres dd {
    margin: 0;
}

.jsb-releve-vide {
    margin: 0;
    font-size: 0.8rem;
    color: var(--text-body, #495057);
}

/*
 * INFÉRÉ : un montant que le calcul déduit, sans pièce de règlement derrière lui.
 * Contour pointillé + le mot lui-même — jamais la couleur seule (WCAG 1.4.1). Cobalt de
 * la charte sur fond transparent : 8,6:1 sur blanc.
 */
.jsb-state-badge--infere {
    background: transparent;
    color: #0047AB;
    border: 1px dashed #0047AB;
}


/* ------------------------------------------------------------------ */
/* LA VALEUR CHOISIE N'EST PAS UNE OPTION DE LISTE                     */
/* ------------------------------------------------------------------ */
/*
 * LE SERVEUR NE PRODUIT QU'UNE SEULE CHAÎNE POUR DEUX ÉTATS.
 *
 * Dans le contrôleur du bundle, `render.option` et `render.item` sont identiques
 * (controller.js, l. 348-350) : le même HTML sert la liste déroulante ET le champ une
 * fois le choix fait. Les cinq tuiles chiffrées, utiles pour CHOISIR entre deux
 * assureurs, restaient donc affichées en permanence sous une valeur déjà choisie — sept
 * à dix-huit lignes occupées pour une information que l'utilisateur vient d'arrêter.
 *
 * Et un `render` passé depuis PHP serait silencieusement écrasé (`mergeConfigs_fn`,
 * spread plat, l. 371-381) : la distinction ne peut pas se faire côté serveur.
 *
 * ── POURQUOI LE CSS, ET NON UN HOOK JAVASCRIPT ──────────────────────────────────
 * Parce que l'appelant n'a rien à savoir du mécanisme : aucun provider, aucun FormType,
 * aucun contrôleur Stimulus ne change. Et parce que le projet consomme déjà cinq classes
 * de Tom Select, dont la version est épinglée (2.3.1) : on ne crée aucune dépendance
 * nouvelle.
 *
 * ── CHAQUE RÈGLE EST SCOPÉE, ET CE N'EST PAS DÉCORATIF ──────────────────────────
 * `:has(.jsb-autocomplete-item)` restreint ces règles à NOTRE balisage. Sans lui, elles
 * toucheraient tous les Tom Select de l'application — la barre de recherche, les select
 * simples — qui n'ont rien demandé.
 */

/* L'aide au choix quitte le champ, et reste dans la déroulante où elle sert. */
.ts-control .item:has(.jsb-autocomplete-item) .jsb-autocomplete-aide {
    display: none;
}

/*
 * Le padding de 10px/12px est celui de l'OPTION de menu : dans le champ, il ferait
 * grandir le contrôle. On ne touche pas à celui de `.ts-control`, qui vient du thème.
 */
.ts-control .item:has(.jsb-autocomplete-item) .jsb-autocomplete-item {
    padding: 0;
    gap: 2px;
}

/*
 * EN MODE MULTIPLE, SEUL LE NOM SURVIT.
 *
 * Trois champs sont `multiple` et passent par ces providers : les partenaires d'un
 * client (ClientType), les assistants d'un invité (InviteType), les comptes bancaires
 * d'une note (NoteType). Cinq comptes sélectionnés empileraient cinq fois deux lignes —
 * le défaut qu'on vient de fermer, multiplié par cinq.
 */
.ts-wrapper.multi .ts-control .item:has(.jsb-autocomplete-item) .jsb-autocomplete-context {
    display: none;
}

/*
 * Le contrôle grandit vers le haut, pas vers le bas : une valeur sur deux lignes doit
 * s'aligner sur la première, comme le ferait un texte ordinaire.
 */
.ts-wrapper.single .ts-control:has(.jsb-autocomplete-item) {
    align-items: flex-start;
}

/* NOUVEAU : Style pour les options de mappage déjà sélectionnées */
.mapped-option {
    background-color: #e9ecef !important; /* Gris très clair */
    color: #6c757d; /* Texte gris pour indiquer qu'elle est "prise" */
    font-style: italic;
}

/* NOUVEAU : Styles pour les étapes de l'analyse de bordereau */
.step-content {
    padding: 1rem;
    background-color: #ffffff;
    border-radius: var(--bs-border-radius);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 1px 3px rgba(0, 0, 0, 0.08);
    margin-bottom: 1rem;
}

/* NOUVEAU : Styles pour la barre de progression dans la barre d'outils */
.toolbar-progress-bar-container {
    position: fixed; /* Fixé en haut de la fenêtre */
    top: 0;
    left: 0;
    z-index: 1056; /* Au-dessus des modales Bootstrap */
    width: 100%;
    height: 5px;
    background-color: transparent; /* Le conteneur est transparent */
    overflow: hidden; /* Cache l'animation qui dépasse */
    display: none; /* Caché par défaut */
}

.toolbar-progress-bar {
    width: 100%;
    height: 100%;
    background: linear-gradient(90deg, #0047AB 0%, #e9ecef 50%, #0047AB 100%); /* Bleu Cobalt et gris clair */
    background-size: 200% 100%;
    animation: toolbar-progress-animation 1.5s infinite linear;
}

@keyframes toolbar-progress-animation {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* NOUVEAU : Forcer la couleur blanche pour tous les feedbacks dans la barre d'outils */
/* Feedback de la barre d'outils : 3 couleurs autorisées uniquement */
.actions-bar [data-bordereau-analysis-target="mappingStatusFeedback"] {
    font-size: 0.9rem;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0;
}

.toolbar-feedback-info    { color: #ced4da; }   /* gris clair */
.toolbar-feedback-error   { color: #dc3545; }
.toolbar-feedback-warning { color: #ffc107; }   /* jaune Bootstrap */

.toolbar-title {
    min-width: 0;
}

.btn-icon {
    background: transparent;
    border: none;
    padding: 5px;
    color: white;
}
.btn-icon:hover {
    background: rgba(255,255,255,0.1);
}

/* Options de barre d'outils : Sobriété au survol */
.actions-bar .dropdown-item:hover {
    background-color: #f8f9fa !important; /* Gris très clair */
    font-weight: normal !important;        /* Pas de gras au survol */
    color: #212529 !important;             /* Couleur texte standard Bootstrap */
    transition: background-color 0.2s ease;
}

/* Vous pouvez ajouter des styles spécifiques pour les listes de résultats si nécessaire */

/* ================================================================== */
/* SECTION 7: COMPOSANT D'ITEM DE RÉSULTAT D'ANALYSE                */
/* ================================================================== */

.analysis-item {
    border-width: 1px 1px 1px 3px !important; /* Bordure gauche affinée */
    border-style: solid !important;
    margin-bottom: 0.75rem;
    transition: box-shadow 0.2s ease-in-out;
    overflow: hidden; /* Pour respecter les coins arrondis avec la status bar */
}

.analysis-item:hover {
    box-shadow: 0 0.25rem 0.75rem rgba(0,0,0,.1);
}

.analysis-item-status-bar {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.75rem;
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

.analysis-item-status-bar--new         { background-color: #e8f0fb; color: #0047AB; }
.analysis-item-status-bar--discrepancy { background-color: #fff3cd; color: #e69500; }
.analysis-item-status-bar--match       { background-color: #d1e7dd; color: #198754; }
.analysis-item-status-bar--warning     { background-color: #f8d7da; color: #dc3545; }

.analysis-item-gaps-badge {
    margin-left: auto;
    background-color: #e69500;
    color: #ffffff;
    font-size: 0.68rem;
    font-weight: 700;
    padding: 0.1rem 0.5rem;
    border-radius: 20px;
}

.analysis-item-header {
    cursor: pointer;
    position: relative; /* Pour le positionnement des actions */
}

/* NOUVEAU : Conteneur pour le titre pour qu'il ne prenne pas toute la largeur */
.analysis-item-title-wrapper {
    flex-grow: 1; /* Prend l'espace disponible */
    min-width: 0; /* Empêche le dépassement si le titre est très long */
}

.analysis-item-header .chevron-icon {
    transition: transform 0.3s ease;
    color: #6c757d;
}

.analysis-item-header .chevron-icon.is-rotated {
    transform: rotate(180deg);
}

.analysis-item-details {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease-out;
    background-color: #f8f9fa; /* Fond légèrement différent pour les détails */
    border-top: 1px solid #e9ecef;
}

.analysis-item-details.is-open {
    /* Augmentation de la hauteur max pour éviter de tronquer le contenu (clipping) */
    max-height: 1000px; 
}

.analysis-item-actions {
    display: flex;
    gap: 0.4rem;
    opacity: 0.35;
    flex-shrink: 0;
    min-width: 40px;
    visibility: visible;
    transition: opacity 0.2s ease, visibility 0.2s ease;
}

.analysis-item-actions.is-visible {
    opacity: 1;
}

.analysis-item-actions .btn {
    border-radius: 8px !important;
    padding: 0.35rem 0.75rem !important;
    min-width: auto !important;
    height: auto !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    font-weight: 600;
    font-size: 0.82rem;
    background-color: #ffffff;
    border: 1px solid #dee2e6 !important;
    color: #495057;
    transition: all 0.2s ease-in-out;
    box-shadow: 0 1px 3px rgba(0,0,0,0.06);
    white-space: nowrap;
}

.analysis-item-actions .btn:hover {
    transform: translateY(-1px);
    background-color: #0047AB !important;
    border-color: #0047AB !important;
    color: #ffffff !important;
    box-shadow: 0 4px 10px rgba(0, 71, 171, 0.25);
}

.analysis-item-actions .btn:hover .analysis-action-svg-icon {
    color: #ffffff !important;
}

.analysis-details-group-title {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #adb5bd;
    margin-bottom: 0.4rem;
    margin-top: 0.75rem;
    padding-bottom: 0.3rem;
    border-bottom: 1px solid #e9ecef;
    display: flex;
    align-items: center;
    gap: 0.35rem;
}
.analysis-details-group-title:first-of-type {
    margin-top: 0;
}

/* NOUVEAU : Grille pour les détails de la ligne de bordereau */
.analysis-details-grid .row {
    padding: 0.35rem 0.5rem;
    border-radius: 4px;
    margin-left: 0;
    margin-right: 0;
}

/* Style zébré pour une meilleure lisibilité */
.analysis-details-grid .row:nth-child(odd) {
    background-color: #f8f9fa;
}

.analysis-details-grid .row:hover {
    background-color: #e9ecef;
    font-weight: bold;
}

/* NOUVEAU : Récapitulatif de synthèse de l'analyse */
.analysis-summary-card {
    border-radius: 10px;
    overflow: hidden; /* Assure que le header gradient respecte les coins arrondis */
    animation: fadeIn 0.25s ease-in-out;
}

/* Compteurs — animation d'entrée décalée pour chaque colonne */
.analysis-summary-card .analysis-summary-counter {
    transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
    animation: fadeIn 0.3s ease-in-out;
}

.analysis-summary-card .analysis-summary-counter:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08) !important;
}

/* Barre de conformité — override des styles de .progress Bootstrap qui créent des conflits */
.analysis-summary-card .analysis-conformity-bar {
    height: 8px;
    background-color: #e9ecef;
    border-radius: 4px;
    overflow: hidden;
}
.analysis-summary-card .progress {
    background-color: #e9ecef;
    height: 8px;
    border-radius: 4px;
    overflow: hidden;
}

.analysis-summary-card .progress-bar {
    position: relative !important;
    display: flex !important;
    top: auto !important;
    left: auto !important;
    height: 100% !important;
    z-index: 1 !important;
    animation: none !important;
    box-shadow: none !important;
}

.analysis-summary-counter {
    border-radius: 8px;
    transition: transform 0.2s ease;
}

.analysis-summary-counter:hover {
    transform: translateY(-2px);
}

.analysis-conformity-bar {
    height: 8px;
    border-radius: 4px;
    overflow: hidden;
}

/* NOUVEAU : Tableau des écarts financiers dans les résultats discrepancy */
.analysis-financial-gaps-table th,
.analysis-financial-gaps-table td {
    vertical-align: middle;
    padding: 0.4rem 0.6rem;
}

.analysis-financial-gaps-table tr.has-gap {
    background-color: rgba(255, 193, 7, 0.08);
}

.analysis-gap-positive {
    color: #198754;
    font-weight: bold;
}

.analysis-gap-negative {
    color: #dc3545;
    font-weight: bold;
}

.analysis-financial-gaps-table tr.has-gap {
    background-color: rgba(255, 193, 7, 0.08);
}

.analysis-gap-row-zero {
    opacity: 0.45;
    font-size: 0.78rem;
}

.analysis-gap-positive {
    color: #198754;
    font-weight: bold;
}

.analysis-gap-negative {
    color: #dc3545;
    font-weight: bold;
}

/* AMÉLIORATION 5 : Item d'analyse résolu après action */
.analysis-item-resolved:hover {
    opacity: 0.72;
    transition: opacity 0.3s ease;
}

.analysis-item-resolved .analysis-item-status-bar {
    background-color: #d1e7dd;
    color: #198754;
}

.analysis-item-resolved:hover {
    opacity: 1;
}

.analysis-item-resolved .badge.bg-success {
    font-size: 0.7rem;
    vertical-align: middle;
    animation: badge-pop 0.3s ease-out;
}

@keyframes badge-pop {
    0%   { transform: scale(0.6); opacity: 0; }
    70%  { transform: scale(1.1); }
    100% { transform: scale(1);   opacity: 1; }
}

[data-bordereau-analysis-target="validateButton"]:not(:disabled) {
    animation: validate-btn-appear 0.3s ease-out;
}

@keyframes validate-btn-appear {
    from {
        transform: scale(0.95);
        opacity: 0.7;
    }
    to {
        transform: scale(1);
        opacity: 1;
    }
}

/* NOUVEAU : Bloc des actions en lot */
.batch-actions-block {
    border-style: dashed !important;
}

/* ================================================================== */
/* SECTION 8: STRUCTURE COMMUNE DES TOASTS (.jsb-toast)               */
/* ================================================================== */
/* Structure partagée par TOUS les toasts (toasts dynamiques de
   notification-manager + toast permanent « Paramètres de l'espace de
   travail »). Ne porte QUE la structure : dimensions, marges, ombre et
   en-tête transparent. La COULEUR reste contextuelle, appliquée par-dessus
   (classes Bootstrap bg-* pour les toasts dynamiques, .jsb-ws-toast pour les
   paramètres). */
.jsb-toast {
    width: 350px;
    max-width: calc(100vw - 2rem);
    /* Marges latérales = .25rem (alignement vertical des toasts empilés),
       marge basse un peu plus grande comme séparateur. */
    margin: .25rem .25rem .5rem;
    box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .15);
    border: 0;
}
/* En-tête transparent → la couleur de fond du toast transparaît dessous.
   color: inherit pour suivre la couleur de texte contextuelle (text-white /
   text-dark) posée sur le toast. */
.jsb-toast .toast-header {
    background-color: transparent;
    color: inherit;
    font-weight: 700;
    border-bottom-color: rgba(0, 0, 0, .15);
}
/* Sur fond sombre (texte blanc), bordure d'en-tête claire pour rester lisible. */
.jsb-toast.text-white .toast-header {
    border-bottom-color: rgba(255, 255, 255, .35);
}

/* ================================================================== */
/* SECTION 8b: TOAST DE FEEDBACK (analyse bordereau)                  */
/* ================================================================== */

/* Conteneur : déjà géré par Bootstrap .toast-container */

/* Toast JSB — surcharges de style */
#jsb-analysis-toast {
    min-width: 320px;
    max-width: 480px;
    font-size: 0.9rem;
    border-radius: 10px;
    box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.25) !important;
}

#jsb-analysis-toast .toast-body {
    padding: 0.75rem 1rem;
    font-weight: 500;
    line-height: 1.4;
}

/* Couleur du bouton de fermeture sur fond sombre */
#jsb-analysis-toast.text-bg-success .btn-close,
#jsb-analysis-toast.text-bg-danger .btn-close,
#jsb-analysis-toast.bg-dark .btn-close {
    filter: invert(1) grayscale(100%) brightness(200%);
}

/* Animation d'entrée */
#jsb-analysis-toast.showing,
#jsb-analysis-toast.show {
    animation: toast-slide-in 0.25s ease-out;
}

@keyframes toast-slide-in {
    from { transform: translateY(20px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

/* ================================================================== */
/* SECTION : BARRE D'ANALYSE — BLOC TITRE BIBLIOGRAPHIQUE             */
/* ================================================================== */
.analysis-toolbar-title-block {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    min-width: 0; /* Permet les troncatures */
    max-width: 420px; /* Ne pas empiéter sur le bloc actions droite */
    text-align: left;
}

.analysis-toolbar-ref {
    font-size: 1rem;
    font-weight: 700;
    color: #ffffff;
    letter-spacing: 0.3px;
    display: flex;
    align-items: center;
    line-height: 1.2; /* Adjust line height for better vertical alignment with icon */
}

.analysis-toolbar-ref .ux-icon { /* Target the SVG icon inside */
    color: rgba(255,255,255,0.7);
}

.analysis-toolbar-name {
    font-size: 0.8rem;
    color: rgba(255,255,255,0.65);
    font-style: italic;
    max-width: 300px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.2; /* Adjust line height */
}

.analysis-toolbar-meta {
    font-size: 0.72rem;
    color: rgba(255,255,255,0.55);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: flex;
    align-items: center;
    line-height: 1.2; /* Adjust line height */
}

.analysis-toolbar-meta .ux-icon { /* Target the SVG icon inside */
    color: rgba(255,255,255,0.55);
}

.analysis-toolbar-badges {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    line-height: 1; /* Adjust line height */
}

.analysis-toolbar-badge-step {
    background-color: rgba(255,255,255,0.15);
    color: #ffffff;
    border-radius: 20px;
    font-size: 0.65rem;
    padding: 0.2em 0.7em;
    font-weight: 600;
    white-space: nowrap;
}

.analysis-toolbar-badge-status {
    border-radius: 20px;
    font-size: 0.65rem;
    padding: 0.2em 0.7em;
    font-weight: 600;
    white-space: nowrap;
}

/* ================================================================== */
/* SECTION 9: MOTEUR DE RECHERCHE — RÉSULTATS D'ANALYSE (ÉTAPE 3)     */
/* ================================================================== */

/* Conteneur flex du bloc recherche dans la toolbar sombre */
.analysis-search-block {
    display: flex;
    flex-direction: column;
    align-items: stretch;
}

/* Groupe input + bouton */
.analysis-search-input-group {
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid rgba(255, 255, 255, 0.2);
    transition: border-color 0.2s ease-in-out;
}

.analysis-search-input-group:focus-within {
    border-color: #0047AB; /* Cobalt au focus */
    box-shadow: 0 0 0 2px rgba(0, 71, 171, 0.35);
}

/* Champ de saisie sur fond sombre */
.analysis-search-input {
    background-color: rgba(255, 255, 255, 0.08) !important;
    border: none !important;
    color: #ffffff !important;
    font-size: 0.82rem !important;
    font-weight: normal !important; /* Surcharge le bold global de app.css */
    padding: 0.35rem 0.65rem;
    border-radius: 0 !important;
}

.analysis-search-input::placeholder {
    color: rgba(255, 255, 255, 0.4);
    font-style: italic;
    font-weight: normal;
}

.analysis-search-input:focus {
    background-color: rgba(255, 255, 255, 0.14) !important;
    box-shadow: none !important;
    color: #ffffff !important;
}

/* Supprime la croix native du navigateur sur input[type=search] si besoin */
.analysis-search-input::-webkit-search-cancel-button {
    filter: invert(1); /* La rend blanche sur fond sombre */
    cursor: pointer;
}

/* Bouton loupe */
.analysis-search-btn {
    background-color: #0047AB;
    border: none;
    color: #ffffff;
    padding: 0.35rem 0.65rem;
    border-radius: 0 !important;
    transition: background-color 0.2s ease-in-out;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.analysis-search-btn:hover {
    background-color: #003380; /* cobalt-dark */
    color: #ffffff;
}

.analysis-search-btn:focus {
    box-shadow: none;
    outline: none;
}

/* Compteur de résultats sous le champ */
.analysis-search-count {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.3px;
    text-align: right;
    padding-right: 2px;
    display: block;
    min-height: 1rem; /* Évite le saut de layout quand le texte apparaît */
    transition: color 0.2s ease-in-out;
}

/* ================================================================== */
/* SECTION TABLEAU DE BORD — styles extraits des templates Twig        */
/* ================================================================== */

/* ── Squelettes de chargement ── */
@keyframes db-skeleton-shimmer {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(100%); }
}
.db-skeleton-block {
    background: #e9ecef;
    border-radius: 0.5rem;
    position: relative;
    overflow: hidden;
}
.db-skeleton-block::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(
        90deg,
        transparent 0%,
        rgba(255, 255, 255, 0.55) 50%,
        transparent 100%
    );
    animation: db-skeleton-shimmer 1.5s ease-in-out infinite;
}
.db-skel-line {
    height: .7rem;
    border-radius: .35rem;
    background: rgba(0, 0, 0, 0.07);
}
.db-skel-circle {
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.07);
    flex-shrink: 0;
}
.db-skel-bar {
    border-radius: .25rem .25rem 0 0;
    background: rgba(0, 0, 0, 0.07);
    align-self: flex-end;
    flex: 1;
}

/* ── KPI Carousel ── */
.kpi-carousel-wrapper {
    overflow: hidden;
}

.kpi-carousel {
    display: flex;
    gap: 0.75rem;
}

.kpi-card-slot {
    flex: 1 1 0;
    min-width: 0;
}

.kpi-card {
    height: 115px;
    border: 0 !important;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    cursor: pointer;
    transition: transform 0.18s ease, box-shadow 0.18s ease;
    user-select: none;
    border-radius: 0.5rem;
    text-decoration: none;
}
.kpi-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.14);
}
.kpi-card:active {
    transform: translateY(-1px);
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.10);
}
.kpi-card:focus-visible {
    outline: 2px solid #0047AB;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px rgba(0, 71, 171, 0.30);
}

.kpi-tax-rate {
    font-size: 0.58rem;
    font-weight: 600;
    padding: 1px 5px;
    border-radius: 3px;
    background: rgba(0, 0, 0, 0.07);
    margin-left: 4px;
    vertical-align: middle;
}
.kpi-card-slot--taxes {
    flex: 1 1 0;
    min-width: 0;
}
.kpi-card--taxes {
    height: auto !important;
    min-height: 115px;
}

/* ── Hero bloc "Revenus perçus" ── */
details.db-hero { transition: box-shadow .2s ease; cursor: pointer; }
details.db-hero:hover { box-shadow: 0 8px 28px rgba(0,0,0,.24) !important; }
details.db-hero > summary { list-style: none; cursor: pointer; border-radius: .375rem; }
details.db-hero > summary::-webkit-details-marker { display: none; }
details.db-hero > summary:focus-visible { outline: 2px solid rgba(255,255,255,.55); outline-offset: 2px; }
details.db-hero[open] > summary { border-radius: .375rem .375rem 0 0; }
.db-hero-chevron { transition: transform .25s ease; opacity: .70; flex-shrink: 0; }
details.db-hero[open] > summary .db-hero-chevron { transform: rotate(180deg); }
.db-hero-metric { background: rgba(255,255,255,.09); border-radius: .375rem; padding: .5rem .75rem;
    cursor: pointer; transition: background .15s ease, box-shadow .15s ease, transform .1s ease; }
.db-hero-metric:hover { background: rgba(255,255,255,.16); box-shadow: 0 2px 8px rgba(0,0,0,.18); }
.db-hero-metric:active { transform: scale(0.97); background: rgba(255,255,255,.22); }
.db-hero-metric--selected { background: rgba(255,255,255,.22) !important; box-shadow: 0 0 0 2px rgba(255,255,255,.45); }
.db-hero-metric:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(255,255,255,.55); }
.db-hero-metric-icon { color: rgba(255,255,255,.65); display: flex; }
.db-hero-metric-label { display: block; font-size: .63rem; text-transform: uppercase;
    letter-spacing: .045em; opacity: .60; margin-bottom: .15rem; }
.db-hero-metric-value { display: block; font-size: .88rem; font-weight: 600; }
.db-hero-section-title { font-size: .63rem; text-transform: uppercase;
    letter-spacing: .06em; opacity: .50; font-weight: 600; margin: 0; }
.db-hero-solde { border: 1px solid rgba(255,255,255,.28); }

/* ── Renouvellements ── */
.db-renew-badge {
    transition: transform 0.18s ease, font-size 0.18s ease;
    cursor: default;
    font-size: .68rem;
}
.db-renew-badge:hover {
    transform: scale(1.30);
    font-size: .78rem;
}

/* ── Blocs details (top listes, renouvellements, encaissements) ── */
.db-ass-details { border: 1px solid #e0e0e0; border-radius: .375rem; background: #fff; margin-bottom: .375rem; transition: box-shadow .15s ease, border-color .15s ease; }
.db-ass-details:last-child { margin-bottom: 0; }
.db-ass-details:hover { box-shadow: 0 1px 5px rgba(0,71,171,.09); border-color: #ced4da; }
.db-ass-details[open] { border-color: #dee2e6; box-shadow: 0 2px 8px rgba(0,71,171,.10); }
.db-ass-details > summary { list-style: none; cursor: pointer; border-radius: .375rem; transition: background .15s ease; }
.db-ass-details > summary::-webkit-details-marker { display: none; }
.db-ass-details > summary:hover { background: #f8f9fa; }
.db-ass-details[open] > summary { border-radius: .375rem .375rem 0 0; background: #f8f9fa; border-bottom: 1px solid #dee2e6; }
.db-ass-details > summary:focus-visible { outline: 2px solid rgba(0,71,171,.35); outline-offset: 2px; }
.db-ass-chevron { transition: transform .25s ease; flex-shrink: 0; }
.db-ass-details[open] > summary .db-ass-chevron { transform: rotate(180deg); }
.db-ass-details[open] > summary .db-ass-avatar  { background: #003380 !important; }

.db-ass-table { --bs-table-striped-bg: #f8f9fa; --bs-table-hover-bg: #e9ecef; }

.db-badge-market { display:inline-flex; align-items:center; cursor:default;
    transition: transform .15s ease, box-shadow .15s ease; }
.db-badge-market:hover { transform: scale(1.25);
    box-shadow: 0 2px 6px rgba(13,110,253,.25); }

.db-ass-details[open].db-risque > summary .db-ass-avatar { background: #b37200 !important; }
.db-ass-details[open].db-intermediaire > summary .db-ass-avatar { background: #343a40 !important; }

/* ── Scrolls tâches et similaires ── */
.db-tasks-scroll { max-height: 380px; overflow-y: auto;
    scrollbar-width: thin; scrollbar-color: #ced4da #f8f9fa; }
.db-tasks-scroll::-webkit-scrollbar { width: 5px; }
.db-tasks-scroll::-webkit-scrollbar-track { background: #f8f9fa; }
.db-tasks-scroll::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 3px; }

.db-piste-scroll { max-height: 340px; overflow-y: auto;
    scrollbar-width: thin; scrollbar-color: #ced4da #f8f9fa; }
.db-piste-scroll::-webkit-scrollbar { width: 5px; }
.db-piste-scroll::-webkit-scrollbar-track { background: #f8f9fa; }
.db-piste-scroll::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 3px; }

.db-bord-scroll { max-height: 340px; overflow-y: auto;
    scrollbar-width: thin; scrollbar-color: #ced4da #f8f9fa; }
.db-bord-scroll::-webkit-scrollbar { width: 5px; }
.db-bord-scroll::-webkit-scrollbar-track { background: #f8f9fa; }
.db-bord-scroll::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 3px; }

.db-note-scroll { max-height: 340px; overflow-y: auto;
    scrollbar-width: thin; scrollbar-color: #ced4da #f8f9fa; }
.db-note-scroll::-webkit-scrollbar { width: 5px; }
.db-note-scroll::-webkit-scrollbar-track { background: #f8f9fa; }
.db-note-scroll::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 3px; }

#db-note-tip {
    position: fixed; z-index: 9999; pointer-events: none; display: none;
    background: #1e2227; color: #e9ecef; border-radius: 6px;
    font-size: .72rem; padding: 6px 0; min-width: 200px; max-width: 280px;
    box-shadow: 0 4px 16px rgba(0,0,0,.35);
}
#db-note-tip table { width: 100%; border-collapse: collapse; }
#db-note-tip td { padding: 3px 10px; vertical-align: top; overflow-wrap: anywhere; }
#db-note-tip td:first-child:not([colspan]) { color: #adb5bd; white-space: nowrap; padding-right: 6px; }
#db-note-tip .tip-section { color: #0d6efd; font-weight: 600; padding-top: 6px; padding-bottom: 2px; border-top: 1px solid #3d4349; font-size: .68rem; letter-spacing: .03em; text-transform: uppercase; }
#db-note-tip .tip-section:first-child { border-top: none; padding-top: 3px; }
#db-note-tip .tip-statut { font-weight: 600; }

#db-bord-tip {
    position: fixed; z-index: 9999; pointer-events: none; display: none;
    background: #1e2227; color: #e9ecef; border-radius: 6px;
    font-size: .72rem; padding: 6px 0; min-width: 200px; max-width: 280px;
    box-shadow: 0 4px 16px rgba(0,0,0,.35);
}
#db-bord-tip table { width: 100%; border-collapse: collapse; }
#db-bord-tip td { padding: 3px 10px; vertical-align: top; overflow-wrap: anywhere; }
#db-bord-tip td:first-child:not([colspan]) { color: #adb5bd; white-space: nowrap; padding-right: 6px; }
#db-bord-tip .tip-section { color: #0d6efd; font-weight: 600; padding-top: 6px; padding-bottom: 2px; border-top: 1px solid #3d4349; font-size: .68rem; letter-spacing: .03em; text-transform: uppercase; }
#db-bord-tip .tip-section:first-child { border-top: none; padding-top: 3px; }
#db-bord-tip .tip-statut { font-weight: 600; }

/* Infobulle sombre partagée : bloc Pistes du tableau de bord (#db-piste-tip)
   et puces de contexte du chat IA (.jsb-ctx-tip) — MÊME rendu, même pattern
   (élément flottant au <body>, suit le curseur). */
#db-piste-tip,
.jsb-ctx-tip {
    position: fixed; z-index: 9999; pointer-events: none; display: none;
    background: #1e2227; color: #e9ecef; border-radius: 6px;
    font-size: .72rem; padding: 6px 0; min-width: 200px; max-width: 280px;
    box-shadow: 0 4px 16px rgba(0,0,0,.35);
}
#db-piste-tip table, .jsb-ctx-tip table { width: 100%; border-collapse: collapse; }
#db-piste-tip td, .jsb-ctx-tip td { padding: 3px 10px; vertical-align: top; overflow-wrap: anywhere; }
#db-piste-tip td:first-child:not([colspan]), .jsb-ctx-tip td:first-child:not([colspan]) { color: #adb5bd; white-space: nowrap; padding-right: 6px; }
#db-piste-tip .tip-section, .jsb-ctx-tip .tip-section { color: #0d6efd; font-weight: 600; padding-top: 6px; padding-bottom: 2px; border-top: 1px solid #3d4349; font-size: .68rem; letter-spacing: .03em; text-transform: uppercase; }
#db-piste-tip .tip-section:first-child, .jsb-ctx-tip .tip-section:first-child { border-top: none; padding-top: 3px; }
#db-piste-tip .tip-risque, .jsb-ctx-tip .tip-risque { color: #fbbf24; }
#db-piste-tip .tip-libelle, .jsb-ctx-tip .tip-libelle { color: #9ca3af; font-style: italic; font-size: .68rem; padding-top: 1px; padding-bottom: 3px; }
/* Un paragraphe qui EXPLIQUE (infobulle_controller : description d'une étape du pipe, mode
   de calcul) : lisible, sur plusieurs lignes — pas une note en italique. */
.jsb-ctx-tip .tip-para { color: #e9ecef; white-space: normal; font-size: .7rem; line-height: 1.45; padding-top: 2px; padding-bottom: 4px; }
/* Infobulle posée AU-DESSUS d'une surface déjà sombre (chat de l'assistant en
   mode sombre) : son ombre portée noire n'y délimite plus rien. On lui rend un
   contour perceptible, sans toucher au style partagé avec le tableau de bord.
   Marqueur posé par le contrôleur `assistant-chat` (l'infobulle est attachée au
   <body>, hors du sous-arbre du chat : la cascade du thème ne l'atteint pas). */
.jsb-ctx-tip.is-sur-sombre { border: 1px solid #495057; }

/* Infobulle « dernier commit » (survol de la version) : titre blanc + corps explicite
   sur un ou deux paragraphes lisibles (plus large que l'infobulle partagée). */
.jsb-ctx-tip.commit-tip { max-width: 380px; }
.jsb-ctx-tip.commit-tip .tip-section { color: #fff; }
.jsb-ctx-tip.commit-tip .commit-tip-lead { color: #fff; font-weight: 600; white-space: normal; padding-top: 2px; padding-bottom: 3px; }
.jsb-ctx-tip.commit-tip .commit-tip-para { color: #ced4da; font-style: normal; font-size: .7rem; line-height: 1.45; white-space: normal; padding-top: 2px; padding-bottom: 4px; }

/* ── Blocs details collapsibles (top listes) ── */
.db-block-details > summary { list-style:none; cursor:pointer; border-radius:.375rem .375rem 0 0; transition: background .15s ease; }
.db-block-details > summary::-webkit-details-marker { display:none; }
.db-block-details > summary:hover { background:#f8f9fa; }
.db-block-details > summary:focus-visible { outline:2px solid rgba(0,71,171,.35); outline-offset:2px; }
.db-block-chevron { transition: transform .25s ease; flex-shrink:0; }
.db-block-details[open] > summary .db-block-chevron { transform: rotate(180deg); }

/* ── Segmented control Production ── */
.db-prod-tabs {
    display:inline-flex; align-items:center;
    background:#f0f2f5; border-radius:20px; padding:3px; gap:0;
}
.db-task-toggle {
    display:inline-flex; align-items:center; gap:4px;
    font-size:.63rem; font-weight:500; padding:3px 10px; border-radius:16px;
    border:none; background:transparent; color:#6c757d;
    cursor:pointer; white-space:nowrap; text-decoration:none; font-family:inherit;
    transition:background .2s, color .2s, box-shadow .2s;
}
.db-task-toggle:hover  { color:#212529; }
.db-task-toggle.active { background:#fff; color:#212529; box-shadow:0 1px 3px rgba(0,0,0,.14); }
.db-task-toggle:focus-visible { outline:2px solid #0047AB; outline-offset:2px; }

.db-task-new-btn {
    padding:2px 5px; border:1px solid transparent; border-radius:4px;
    background:transparent; line-height:1; flex-shrink:0; cursor:pointer;
    transition:background .15s, border-color .15s;
}
.db-task-new-btn:hover { background:#e8f0fb; border-color:#0047AB; }

.db-block-add-btn {
    padding:2px 5px; border:1px solid transparent; border-radius:4px;
    background:transparent; line-height:1; flex-shrink:0; cursor:pointer;
    transition:background .15s, border-color .15s;
}
.db-block-add-btn:hover { background:#e8f0fb; border-color:#0047AB; }

/* ── Bloc dashboard hover ── */
.db-dashboard-block { transition: box-shadow .2s ease; }
.db-dashboard-block:hover { box-shadow: 0 4px 16px rgba(0,71,171,.12) !important; }

/* ── Meta chip ── */
.db-meta-chip { display: inline-flex; align-items: center; cursor: default; }

/* ── Feedbacks ── */
.db-fb-scroll { max-height: 380px; overflow-y: auto; overflow-x: hidden;
    scrollbar-width: thin; scrollbar-color: #ced4da #f8f9fa; }
.db-fb-scroll::-webkit-scrollbar { width: 5px; }
.db-fb-scroll::-webkit-scrollbar-track { background: #f8f9fa; }
.db-fb-scroll::-webkit-scrollbar-thumb { background: #ced4da; border-radius: 3px; }
.db-fb-item { padding: .45rem .625rem; border: 1px solid #e0e0e0; border-radius: .375rem;
    background: #fff; margin-bottom: .375rem; cursor: pointer;
    transition: box-shadow .15s ease, border-color .15s ease, background-color .15s ease, transform .1s ease; }
.db-fb-item:last-child { margin-bottom: 0; }
.db-fb-item:hover { box-shadow: 0 1px 5px rgba(0,71,171,.09); border-color: #ced4da; }
.db-fb-item:active { transform: scale(0.985); background: #e8f0fb; border-color: #0047AB; }
.db-fb-item--selected { background: #e8f0fb; border-color: #0047AB; box-shadow: 0 0 0 2px rgba(0,71,171,.15); }
.db-fb-item--selected:hover { box-shadow: 0 0 0 2px rgba(0,71,171,.2); border-color: #0047AB; }
.db-fb-item:focus-visible { outline: none; border-color: #0047AB; box-shadow: 0 0 0 3px rgba(0,71,171,.35); }
.db-fb-type-icon { color: #6c757d; }
.db-fb-meta { display:flex; justify-content:space-between; align-items:baseline; }
.db-fb-author { font-size:.78rem; font-weight:600; color:#343a40; }
.db-fb-date { font-size:.7rem; color:#6c757d; white-space:nowrap; }
.db-fb-task-ref { font-size:.7rem; color:#0047AB; margin:.15rem 0; }
.db-fb-desc { font-size:.75rem; color:#495057; line-height:1.3; }

/* ── Infobulle méta (data-meta-tip) ── */
#db-meta-tip {
    position: fixed; z-index: 9999; pointer-events: none;
    background: #212529; color: #fff;
    padding: 4px 10px; border-radius: 6px;
    font-size: .68rem; white-space: nowrap;
    box-shadow: 0 2px 8px rgba(0,0,0,.3);
    transform: translateX(-50%) translateY(-100%);
    margin-top: -6px;
    display: none;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-width: 280px;
}

/* ── Infobulle renouvellements (data-renew-tip) ── */
#db-renew-tip {
    position:fixed; z-index:9999; pointer-events:none;
    background:#212529; color:#fff;
    padding:8px 14px; border-radius:9px;
    font-size:.68rem;
    box-shadow:0 4px 18px rgba(0,0,0,.45);
    display:none;
    max-width:290px;
}
#db-renew-tip table { border-collapse:collapse; width:100%; min-width:230px; }
#db-renew-tip td { padding:3px 0; vertical-align:top; overflow-wrap:anywhere; }
#db-renew-tip tr + tr td:not(.tip-section) { border-top:1px solid rgba(255,255,255,.06); }
#db-renew-tip td.tip-section {
    color:rgba(255,255,255,.38); font-size:.58rem;
    text-transform:uppercase; letter-spacing:.09em;
    padding-top:8px; padding-bottom:2px;
    border-top:1px solid rgba(255,255,255,.13) !important;
}
#db-renew-tip tr:first-child td.tip-section { padding-top:0; border-top:none !important; }
#db-renew-tip td:first-child:not(.tip-section):not(.tip-piste) { color:#9aa5b4; padding-right:16px; white-space:nowrap; }
#db-renew-tip td:last-child:not(.tip-section):not(.tip-piste) { text-align:right; font-weight:600; letter-spacing:.02em; }
#db-renew-tip .tip-risque { max-width:155px; word-break:break-word; text-align:right; font-weight:600; }
#db-renew-tip .tip-piste { padding-top:4px; }
#db-renew-tip .tip-ok  { color:#4ade80; }
#db-renew-tip .tip-nok { color:#f87171; font-style:italic; font-weight:400; }

/* ── Infobulle KPIs comptables (data-compta-tip) — calquée sur renew-tip ── */
#db-compta-tip {
    position:fixed; z-index:9999; pointer-events:none;
    background:#212529; color:#fff;
    padding:10px 14px; border-radius:9px;
    font-size:.68rem; line-height:1.5;
    box-shadow:0 4px 18px rgba(0,0,0,.45);
    display:none;
    max-width:300px;
}
#db-compta-tip .tip-title {
    font-weight:700; font-size:.72rem; color:#fff;
    margin-bottom:5px;
}
#db-compta-tip p { margin:0; color:rgba(255,255,255,.92); overflow-wrap:anywhere; }
#db-compta-tip .tip-section {
    color:rgba(255,255,255,.38); font-size:.58rem;
    text-transform:uppercase; letter-spacing:.09em;
    margin-top:8px; padding-top:6px; margin-bottom:2px;
    border-top:1px solid rgba(255,255,255,.13);
}

/* ── Infobulle encaissements (data-cash-tip) ── */
#db-cash-tip {
    position:fixed; z-index:9999; pointer-events:none;
    background:#212529; color:#fff;
    padding:8px 14px; border-radius:9px;
    font-size:.68rem;
    box-shadow:0 4px 18px rgba(0,0,0,.45);
    display:none;
    max-width:290px;
}
#db-cash-tip table { border-collapse:collapse; width:100%; min-width:230px; }
#db-cash-tip td { padding:3px 0; vertical-align:top; overflow-wrap:anywhere; }
#db-cash-tip tr + tr td:not(.tip-section) { border-top:1px solid rgba(255,255,255,.06); }
#db-cash-tip td.tip-section {
    color:rgba(255,255,255,.38); font-size:.58rem;
    text-transform:uppercase; letter-spacing:.09em;
    padding-top:8px; padding-bottom:2px;
    border-top:1px solid rgba(255,255,255,.13) !important;
}
#db-cash-tip tr:first-child td.tip-section { padding-top:0; border-top:none !important; }
#db-cash-tip td:first-child:not(.tip-section):not(.tip-note):not(.tip-libelle) { color:#9aa5b4; padding-right:16px; white-space:nowrap; }
#db-cash-tip td:last-child:not(.tip-section):not(.tip-note):not(.tip-libelle) { text-align:right; font-weight:600; letter-spacing:.02em; }
#db-cash-tip .tip-libelle { max-width:220px; word-break:break-word; font-style:italic; color:rgba(255,255,255,.65); padding-top:2px; }
#db-cash-tip .tip-note { padding-top:4px; }
#db-cash-tip .tip-solde-ok  { color:#4ade80; }
#db-cash-tip .tip-solde-due { color:#f87171; }
#db-cash-tip .tip-sinistre  { color:#f87171; font-style:italic; font-weight:400; }

/* ── Infobulle dépenses (data-dep-tip) — calquée sur data-cash-tip ── */
#db-dep-tip {
    position:fixed; z-index:9999; pointer-events:none;
    background:#212529; color:#fff;
    padding:8px 14px; border-radius:9px;
    font-size:.68rem;
    box-shadow:0 4px 18px rgba(0,0,0,.45);
    display:none;
    max-width:290px;
}
#db-dep-tip table { border-collapse:collapse; width:100%; min-width:230px; }
#db-dep-tip td { padding:3px 0; vertical-align:top; overflow-wrap:anywhere; }
#db-dep-tip tr + tr td:not(.tip-section) { border-top:1px solid rgba(255,255,255,.06); }
#db-dep-tip td.tip-section {
    color:rgba(255,255,255,.38); font-size:.58rem;
    text-transform:uppercase; letter-spacing:.09em;
    padding-top:8px; padding-bottom:2px;
    border-top:1px solid rgba(255,255,255,.13) !important;
}
#db-dep-tip tr:first-child td.tip-section { padding-top:0; border-top:none !important; }
#db-dep-tip td:first-child:not(.tip-section) { color:#9aa5b4; padding-right:16px; white-space:nowrap; }
#db-dep-tip td:last-child:not(.tip-section) { text-align:right; font-weight:600; letter-spacing:.02em; }
#db-dep-tip .tip-ttc { color:#f87171; }
#db-dep-tip .tip-statut { font-weight:600; }

/* ── Infobulle sinistres (data-sin-tip) ── */
#db-sin-tip {
    position:fixed; z-index:9999; pointer-events:none;
    background:#212529; color:#fff;
    padding:8px 14px; border-radius:9px;
    font-size:.68rem;
    box-shadow:0 4px 18px rgba(0,0,0,.45);
    display:none;
    max-width:300px;
}
#db-sin-tip table { border-collapse:collapse; width:100%; min-width:240px; }
#db-sin-tip td { padding:3px 0; vertical-align:top; overflow-wrap:anywhere; }
#db-sin-tip tr + tr td:not(.tip-section) { border-top:1px solid rgba(255,255,255,.06); }
#db-sin-tip td.tip-section {
    color:rgba(255,255,255,.38); font-size:.58rem;
    text-transform:uppercase; letter-spacing:.09em;
    padding-top:8px; padding-bottom:2px;
    border-top:1px solid rgba(255,255,255,.13) !important;
}
#db-sin-tip tr:first-child td.tip-section { padding-top:0; border-top:none !important; }
#db-sin-tip td:first-child:not(.tip-section):not(.tip-libelle) { color:#9aa5b4; padding-right:16px; white-space:nowrap; }
#db-sin-tip td:last-child:not(.tip-section):not(.tip-libelle) { text-align:right; font-weight:600; letter-spacing:.02em; }
#db-sin-tip .tip-libelle { max-width:240px; word-break:break-word; font-style:italic; color:rgba(255,255,255,.65); padding-top:2px; }
#db-sin-tip .tip-solde-ok  { color:#4ade80; }
#db-sin-tip .tip-solde-due { color:#f87171; }

/* ── Bloc Production (histogramme mensuel) ── */
#db-prod-chart-wrapper {
    padding: .5rem .75rem 1rem;
    position: relative;
}
#db-prod-chart {
    max-height: 230px;
    width: 100% !important;
}
.db-prod-tip {
    position: fixed;
    z-index: 9999;
    pointer-events: none;
    background: #fff;
    border: 1px solid rgba(0,71,171,.18);
    border-radius: 8px;
    box-shadow: 0 4px 14px rgba(0,71,171,.18);
    padding: .5rem .75rem;
    font-size: .78rem;
    color: #212529;
    white-space: nowrap;
    display: none;
    min-width: 160px;
}

/* ── Production : vue tableau ── */
.db-prod-table-wrap { overflow-x: auto; padding: .5rem .75rem 1rem; }
.db-prod-table { width: 100%; border-collapse: collapse; font-size: .75rem; }
.db-prod-table th {
    background: #0047AB; color: #fff; font-weight: 600;
    padding: .4rem .55rem; text-align: right; white-space: nowrap;
    position: sticky; top: 0; z-index: 1;
}
.db-prod-table th:first-child { text-align: left; }
.db-prod-tr-month td {
    background: #e8eef8; color: #0047AB; font-weight: 700;
    padding: .42rem .55rem; cursor: pointer; white-space: nowrap;
    border-top: 2px solid #c5d5ee;
}
.db-prod-tr-month td:first-child { text-align: left; }
.db-prod-tr-month td:not(:first-child) { text-align: right; }
.db-prod-tr-month:hover td { background: #d0def5; }
.db-prod-tr-month .db-prod-chevron { display: inline-block; transition: transform .2s; margin-right: .25rem; }
.db-prod-tr-month.collapsed .db-prod-chevron { transform: rotate(-90deg); }
.db-prod-tr-assureur td {
    padding: .32rem .55rem .32rem 1.5rem;
    border-bottom: 1px solid #f0f0f0; white-space: nowrap;
}
.db-prod-tr-assureur td:first-child { text-align: left; color: #444; }
.db-prod-tr-assureur td:not(:first-child) { text-align: right; color: #333; }
.db-prod-tr-assureur:hover td { background: #f4f7ff; }
.db-prod-tr-assureur:hover td:first-child { color: #0047AB; font-weight: 600; }
.db-prod-tr-grand td {
    background: #0047AB; color: #fff; font-weight: 700;
    padding: .45rem .55rem; white-space: nowrap;
    border-top: 2px solid #003a8c;
}
.db-prod-tr-grand td:first-child { text-align: left; }
.db-prod-tr-grand td:not(:first-child) { text-align: right; }
.db-prod-table-spinner { display: flex; align-items: center; justify-content: center; height: 180px; color: #0047AB; gap: .5rem; font-size: .82rem; }

/*
 * Sélecteur de clients à rattacher à un portefeuille (injecté dynamiquement par le
 * contrôleur « collection »). Modale accessible superposée AU-DESSUS de la fiche
 * (z-index > modale ~1055). Overlay --overlay-dark, surfaces blanches, reste en
 * classes Bootstrap (charte Joseara).
 */
.jsb-picker-overlay {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: rgba(0, 0, 0, 0.65);
}
.jsb-picker-modal {
    /* LA GOUTTIÈRE DU SÉLECTEUR, déclarée une seule fois.
       Entête, entête contextuel, recherche, cellules et pied s'y alignent : c'est ce
       qui donne à la boîte une colonne visuelle unique. La faire évoluer ici les
       déplace tous ensemble — la répéter en six endroits garantissait qu'un jour
       l'un d'eux resterait en arrière. */
    --jsb-picker-gouttiere: 1.5rem;
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 680px;
    /* Hauteur CONSTANTE, indépendante du nombre de lignes : la zone de liste occupe
       toujours le même espace (elle défile), la boîte ne se déforme pas quand la liste
       est vide ou courte. */
    height: 72vh;
    min-height: 380px;
    max-height: 620px;
    background: #ffffff;
    border-radius: 12px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
    overflow: hidden;
    outline: none;
}
/* UNE BOÎTE PLUS GRANDE POUR CELLES QUI FONT SAISIR, PAS SEULEMENT CHOISIR.
   680 px sur 620 conviennent à une liste qu'on parcourt pour y cocher une ligne. Ils ne
   conviennent plus dès que la boîte empile un tableau à quatre colonnes, trois champs et
   une zone de dépôt : les en-têtes de colonnes se coupaient en deux, chaque carte
   n'exposait qu'une bande de sa hauteur, et la fenêtre se lisait au travers d'une
   meurtrière alors que l'écran était vide autour (Bastien & Scapin > Charge de travail).
   Le modificateur reste GÉNÉRIQUE : tout picker de saisie en aura besoin. */
.jsb-picker-modal--large {
    max-width: 1040px;
    height: 88vh;
    max-height: 880px;
}
/* Barre de progression (feedback à chaque ajout/retrait) : rail discret sous l'entête,
   segment cobalt animé quand .is-active. Réserve toujours sa hauteur (pas de saut). */
.jsb-picker-progress {
    position: relative;
    flex: 0 0 auto;
    height: 3px;
    background: #e9ecef;
    overflow: hidden;
    opacity: 0;
    transition: opacity .15s ease-in-out;
}
.jsb-picker-progress.is-active { opacity: 1; }
.jsb-picker-progress-bar {
    position: absolute;
    height: 100%;
    width: 40%;
    background: #0047AB;
    border-radius: 3px;
    transform: translateX(-100%);
}
.jsb-picker-progress.is-active .jsb-picker-progress-bar {
    animation: jsbPickerIndeterminate 1s ease-in-out infinite;
}
@keyframes jsbPickerIndeterminate {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(320%); }
}
/* Barre de titre : reprend le pattern .modal-header du dialogue d'édition, rendu
   AUTOSUFFISANT. Deux styles venaient du contexte .modal et disparaissaient quand le
   picker est inséré dans <body> (ouverture depuis la toolbar/menu contextuel de la
   liste, sans dialogue ouvert) :
   1) le padding Bootstrap de .modal-header vaut var(--bs-modal-header-padding),
      définie UNIQUEMENT sous .modal → hors modale, gouttières effondrées à 0
      (icône/titre/✕ collés) ;
   2) la signature cobalt venait de .app-dialog .modal-header.
   Tout est donc porté explicitement par .jsb-picker-header, quel que soit le point
   d'insertion — même rendu pour les pickers clients ET portefeuilles, partout. */
.jsb-picker-header {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: 1.1rem var(--jsb-picker-gouttiere);
    border-bottom: 3px solid #0047AB;
}
.jsb-picker-header .modal-title {
    font-size: 1.05rem;
    font-weight: 600;
    color: #212529;
}
/* Entête contextuel calqué sur le « form-intro » du formulaire d'édition :
   pastille cobalt-subtle + titre + description + statistiques. */
.jsb-picker-intro {
    flex: 0 0 auto;
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 1.1rem var(--jsb-picker-gouttiere) 1.2rem;
    border-bottom: 1px solid #f0f0f0;
}
.jsb-picker-intro-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    border-radius: 11px;
    background-color: #e8f0fb;
    color: #0047AB;
}
.jsb-picker-intro-text { min-width: 0; }
.jsb-picker-intro-title {
    margin: 0 0 .15rem;
    font-size: 1.02rem;
    font-weight: 700;
    color: #212529;
}
.jsb-picker-intro-desc {
    margin: 0;
    font-size: .84rem;
    line-height: 1.45;
    color: #6c757d;
    max-width: 78ch;
}
/* Statistiques : « N dans ce portefeuille / M au total ». */
.jsb-picker-stats {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-top: .55rem;
    font-size: .8rem;
    color: #6c757d;
}
.jsb-picker-stat-num {
    font-weight: 700;
    color: #0047AB;
}
.jsb-picker-stat-sep { color: #ced4da; }
/* Mise en évidence du mot-clé recherché dans les résultats (fond jaune, texte noir gras). */
.jsb-picker-hl {
    background: #ffe58f;
    color: #000000;
    font-weight: 700;
    padding: 0 1px;
    border-radius: 2px;
}
/* Champ de recherche : icône loupe + input, encadré discret. */
.jsb-picker-search {
    flex: 0 0 auto;
    position: relative;
    /* Padding vertical symétrique : le repère « top:50% » de l'icône coïncide alors
       exactement avec le centre du champ. */
    padding: 1rem var(--jsb-picker-gouttiere);
}
.jsb-picker-search-icon {
    position: absolute;
    /* Centrée dans la zone de padding gauche du champ (1.25rem de gouttière + ~0.7rem). */
    left: 1.95rem;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
    color: #6c757d;
    pointer-events: none;
}
.jsb-picker-search .form-control { padding-left: 2.5rem; }
/* LE CHAMP DE PICKER, DÉCLARÉ UNE FOIS POUR TOUTES SES FAMILLES : la recherche, les
   champs à icône incrustée, et ceux posés dans une cellule de tableau. */
.jsb-picker-search .form-control,
.jsb-picker-field .form-control,
.jsb-picker-field .form-select,
.jsb-picker-table .form-control {
    background-color: var(--bg-light, #f8f9fa) !important;
    border: 1px solid var(--border-light, #dee2e6);
    border-radius: 8px;
    font-weight: normal;
}
/* Le survol reprend le gris de la règle globale : un champ de picker doit réagir
   comme n'importe quel champ de l'application. */
.jsb-picker-search .form-control:hover,
.jsb-picker-field .form-control:hover,
.jsb-picker-field .form-select:hover,
.jsb-picker-table .form-control:hover {
    border-color: var(--text-muted, #6c757d);
}
.jsb-picker-search .form-control:focus,
.jsb-picker-table .form-control:focus {
    border-color: #0047AB;
    box-shadow: 0 0 0 .2rem rgba(0, 71, 171, 0.15) !important;
}
/* Champ de saisie à icône incrustée DANS un picker — même pattern que
   `.jsb-support .jsb-field-control` (lui-même calqué sur le thème console
   `.cs-field`) : l'icône est posée à l'intérieur du champ, à gauche, et l'input
   reçoit un retrait équivalent. Complète `.jsb-picker-search`, qui ne couvre que
   le champ de recherche d'un picker. */
/* ── LES CHAMPS D'UNE CARTE DE PICKER, EN GRILLE ──────────────────────────────
   Ils s'empilaient un par ligne. C'était juste tant que la boîte faisait 680 px :
   côte à côte, chacun sous son propre texte d'aide, ils formaient trois colonnes de
   prose à lire en travers. Depuis que la boîte de saisie s'élargit, l'empilement
   laisse au contraire la moitié droite vide et repousse le reste du formulaire hors
   de l'écran — ce qu'on gagne à ne pas faire lire de travers, on le perd à faire
   défiler (Bastien & Scapin > Charge de travail).

   DEUX COLONNES, PAS TROIS : deux textes d'aide côte à côte se lisent encore ; au
   troisième, on relit. Et un champ qui a besoin de toute la largeur le déclare
   (`--pleine`) plutôt que de se voir comprimé dans une demi-colonne. */
.jsb-picker-champs {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem 1.5rem;
}
@media (min-width: 720px) {
    .jsb-picker-champs { grid-template-columns: 1fr 1fr; }
}
.jsb-picker-champs--pleine { grid-column: 1 / -1; }

.jsb-picker-field {
    position: relative;
}
.jsb-picker-field-icon {
    position: absolute;
    left: .7rem;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    line-height: 0;
    color: #0047AB;
    pointer-events: none;
}
/* Variante des champs multi-lignes : l'icône s'aligne sur la PREMIÈRE ligne,
   un centrage vertical la ferait flotter au milieu du bloc. */
.jsb-picker-field--top .jsb-picker-field-icon {
    top: .72rem;
    transform: none;
}
/* Le retrait vaut aussi pour une LISTE DÉROULANTE : sans lui, l'icône se posait
   par-dessus le premier caractère de l'option retenue. Son chevron, lui, est à
   droite — les deux ne se disputent rien. */
.jsb-picker-field .form-control,
.jsb-picker-field .form-select {
    padding-left: 2.4rem;
}
.jsb-picker-field .form-control:focus,
.jsb-picker-field .form-select:focus {
    border-color: #0047AB;
    box-shadow: 0 0 0 .2rem rgba(0, 71, 171, 0.15) !important;
}
/* Ligne d'option d'un picker (choix d'un destinataire) — layout AUTO-PORTÉ.
   On n'utilise volontairement PAS `.form-check` : sa puce flottante (float +
   marge négative de Bootstrap) déborde de sa rangée et rogne le libellé dès que
   le texte est long — exactement le défaut déjà neutralisé côté dialogues
   (`.form-column .dlg-field-card-body .form-check-input`). Un `<label>` en flex
   contenant l'input rend la ligne entière cliquable et immunise la mise en page
   contre toute règle `.form-check` ambiante du contexte d'insertion (le picker
   est inséré dans la modale visible la plus haute, dont on ne maîtrise pas le CSS). */
.jsb-picker-option {
    display: flex;
    align-items: flex-start;
    gap: .55rem;
    width: 100%;
    min-width: 0;
    min-height: 44px; /* cible tactile confortable (WCAG 2.5.8) */
    margin: 0 0 .15rem;
    padding: .35rem .4rem;
    border-radius: 8px;
    cursor: pointer;
    transition: background-color .15s ease;
}
.jsb-picker-option:hover {
    background: #f8f9fa;
}
.jsb-picker-option:has(input:checked) {
    background: rgba(0, 71, 171, 0.06);
}
.jsb-picker-option:focus-within {
    outline: 2px solid #0047AB;
    outline-offset: 1px;
}
.jsb-picker-option > input {
    flex: 0 0 auto;
    margin: .35rem 0 0; /* alignée optiquement sur la première ligne du libellé */
    cursor: pointer;
}
/* min-width:0 autorise le retour à la ligne dans un conteneur flex ;
   overflow-wrap:anywhere empêche une adresse longue de pousser la largeur du
   bloc — les deux ensemble suppriment tout défilement horizontal de la liste. */
.jsb-picker-option-corps {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}
/* Compteur d'un chip de filtre rapide (nombre d'éléments de la catégorie).
   Deux fonds distincts pour rester lisible sur la piste grise comme sur le chip
   actif cobalt — contraste vérifié dans les deux états. */
.jsb-preset-chip__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    font-size: .72rem;
    font-weight: 600;
    line-height: 1;
    background: rgba(108, 117, 125, 0.16);
    color: #495057;
}
.jsb-preset-chip.is-active .jsb-preset-chip__count {
    background: rgba(0, 0, 0, 0.22);
    color: #ffffff;
}
/* Zone de liste : occupe tout l'espace restant et défile — c'est ce qui garantit une
   hauteur de boîte constante quels que soient le nombre d'éléments. */
.jsb-picker-listwrap {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 0;
}
.jsb-picker-table {
    width: 100%;
    margin: 0;
    border-collapse: collapse;
    font-size: .88rem;
}
.jsb-picker-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: #f1f3f5;
    padding: .7rem .9rem;
    color: #6c757d;
    font-size: .7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .03em;
    text-align: left;
    border-bottom: 1px solid #dee2e6;
}
.jsb-picker-table thead th:last-child { text-align: right; }
/* L'EN-TÊTE S'ALIGNE SUR SA COLONNE, PAS SUR LE HAUT DE LA CELLULE. Une colonne de
   chiffres alignés à droite sous un libellé aligné à gauche se lit comme deux colonnes
   superposées ; et un libellé qui se coupe en deux lignes flottait au-dessus de la case
   à cocher voisine au lieu de lui faire face. */
.jsb-picker-table thead th { vertical-align: middle; }
/* Les colonnes de CHIFFRES : même chasse pour tous les caractères, donc des virgules
   décimales qui tombent les unes sous les autres, et un libellé qui ne se coupe pas. */
.jsb-picker-num {
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.jsb-picker-table tbody td {
    vertical-align: middle;
    /* Des lignes qui respirent : à .55rem, le nom, le code et le bouton se touchaient,
       et la ligne passait sous les 44px de cible confortable (WCAG 2.5.5). */
    padding: .8rem .9rem;
    border-bottom: 1px solid #f0f0f0;
}
/* Gouttières homogènes : les 1res/dernières cellules s'alignent sur le retrait de
   l'entête, de la recherche et du pied (1.25rem) pour une colonne visuelle cohérente. */
.jsb-picker-table th:first-child,
.jsb-picker-table td:first-child { padding-left: var(--jsb-picker-gouttiere); }
.jsb-picker-table th:last-child,
.jsb-picker-table td:last-child { padding-right: var(--jsb-picker-gouttiere); }
.jsb-picker-table tbody tr { transition: background-color .12s ease, box-shadow .12s ease; }
.jsb-picker-table tbody tr:hover { background: #f8f9fa; }
.jsb-picker-table tbody tr:last-child td { border-bottom: none; }
/* ── UNE LIGNE COCHÉE SE VOIT, ET UNE LIGNE CLIQUABLE LE DIT ──────────────────
   La case à cocher seule ne suffit pas : sur cinq lignes remplies de chiffres, il faut
   la chercher pour savoir ce qu'on est en train de régler. La ligne retenue prend donc
   le fond cobalt pâle de la charte et un filet cobalt à gauche — l'information passe par
   DEUX canaux, jamais par la couleur seule (WCAG 1.4.1 : la case cochée reste le
   premier). */
.jsb-picker-table tbody tr:has(input[type="checkbox"]:checked) {
    background: var(--bg-cobalt-subtle, #e8f0fb);
    box-shadow: inset 3px 0 0 var(--cobalt, #0047AB);
}
/* Le survol reste perceptible SUR une ligne déjà cochée : sans cette reprise, la
   règle ci-dessus l'emportait et la ligne cessait de répondre à la souris. */
.jsb-picker-table tbody tr:has(input[type="checkbox"]:checked):hover {
    background: #dce9f9;
}
/* Le clavier voit ce que la souris survole. */
.jsb-picker-table tbody tr:focus-within {
    outline: 2px solid var(--cobalt, #0047AB);
    outline-offset: -2px;
}
/* CURSEUR DE MAIN SEULEMENT LÀ OÙ LA LIGNE EST VRAIMENT CLIQUABLE. Le modificateur est
   posé par le gabarit dont le contrôleur écoute le clic : promettre le geste sans
   l'implémenter serait pire que de ne rien promettre. */
.jsb-picker-table--lignes-cliquables tbody tr { cursor: pointer; }
/* LA CASE EST LE TÉMOIN D'ÉTAT : elle doit se voir avant qu'on la cherche. À la taille
   de Bootstrap (1 rem, bordure très pâle) elle se perdait dans une ligne de chiffres.
   Cochée, elle prend le cobalt de la marque — et non le bleu Bootstrap, qui n'est pas
   la couleur primaire de Joseara. */
.jsb-picker-table .form-check-input {
    width: 1.15rem;
    height: 1.15rem;
    margin-top: 0;
    border-color: var(--text-label, #adb5bd);
    cursor: pointer;
}
.jsb-picker-table .form-check-input:checked {
    background-color: var(--cobalt, #0047AB);
    border-color: var(--cobalt, #0047AB);
}
.jsb-picker-table .form-check-input:focus {
    border-color: var(--cobalt, #0047AB);
    box-shadow: 0 0 0 .2rem var(--cobalt-focus-ring, rgba(0, 71, 171, 0.35));
}
.jsb-picker-client-nom { display: block; font-weight: 600; color: #212529; }
.jsb-picker-client-mail { display: block; font-size: .78rem; color: #6c757d; }
.jsb-picker-btn { display: inline-flex; align-items: center; gap: .3rem; min-height: 32px; white-space: nowrap; }
/* Pastilles de statut (PAS .badge, forcé en bleu !important globalement) — paires
   fond/texte à bon contraste (charte : états sémantiques). */
.jsb-picker-chip {
    display: inline-block;
    padding: .22rem .55rem;
    border-radius: 999px;
    font-size: .72rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
}
.jsb-picker-chip--free    { background: #e9ecef; color: #495057; }
.jsb-picker-chip--current { background: #d1e7dd; color: #0f5132; }
.jsb-picker-chip--other   { background: #fff3cd; color: #664d03; }
/* ── SOUS-ÉCRAN D'UN PICKER ──────────────────────────────────────────────────
   Une boîte qui REMPLACE son contenu (le journal d'un collaborateur, un
   ajustement, un décompte de sortie) doit toujours dire où l'on est et par où
   l'on revient. Sans cette bande, le retour se cherche — et l'on referme la
   fenêtre entière pour retrouver la vue d'où l'on venait.

   Elle est générique parce que rien ici ne concerne les congés : tout picker
   qui navigue en profondeur plutôt qu'en empilant des fenêtres en aura besoin.
   — /ergonomie-web : Bastien & Scapin > Guidage, Nielsen 3 (contrôle et liberté). */
.jsb-picker-sousentete {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .8rem var(--jsb-picker-gouttiere);
    border-bottom: 1px solid #f0f0f0;
}
.jsb-picker-sousentete-titre {
    margin: 0;
    font-size: .95rem;
    font-weight: 700;
    color: #212529;
}
.jsb-picker-sousentete-info {
    font-size: .82rem;
    font-weight: 700;
    color: #0047AB;
    white-space: nowrap;
}

/* ── LA LIGNE DES TOTAUX ─────────────────────────────────────────────────────
   Elle se lit comme une somme, pas comme une ligne de plus : fond neutre, filet
   plus marqué, et le même alignement de chiffres que les colonnes du dessus. */
.jsb-picker-table tfoot th,
.jsb-picker-table tfoot td {
    padding: .8rem .9rem;
    background: #f8f9fa;
    border-top: 2px solid #dee2e6;
    font-weight: 700;
    color: #212529;
}

/* ── LE SENS D'UN CHIFFRE N'EST PAS PORTÉ PAR SA SEULE COULEUR ───────────────
   La couleur appuie une information que le nombre dit déjà — son signe, ou le
   mot qui l'accompagne. Un journal qu'on imprime, ou qu'on lit sans distinguer
   le vert du rouge, reste exact. — WCAG 1.4.1. */
.jsb-picker-num--positif { color: #198754; }
.jsb-picker-num--negatif { color: #dc3545; }
.jsb-picker-num--accent  { color: #0047AB; font-weight: 700; }

/* Les gestes d'une ligne : des boutons discrets, mais des BOUTONS — hauteur de
   cible tenue (WCAG 2.5.8) et espacés, pour ne pas déclencher « Sortie » en
   visant « Ajuster ». */
.jsb-picker-gestes {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    gap: .35rem;
    white-space: nowrap;
}

/* ── LES GESTES NE PARAISSENT QU'AU SURVOL DE LEUR LIGNE ─────────────────────
   Neuf boutons alignés sur trois lignes tiraient l'œil avant les chiffres, alors
   que la grille sert d'abord à COMPARER des soldes. Ils s'effacent donc jusqu'à
   ce qu'on désigne une ligne — et c'est aussi ce qui dit sur QUI l'on va agir.

   ── OPACITÉ, PAS `display: none` ────────────────────────────────────────────
   Les boutons gardent leur place : la colonne ne change pas de largeur au
   passage de la souris, et les lignes ne sautent pas les unes sous les autres.
   `pointer-events` suit l'opacité — un bouton invisible mais cliquable est pire
   qu'un bouton visible.

   ── ET LE CLAVIER LES ATTEINT TOUJOURS ──────────────────────────────────────
   `:focus-within` révèle la ligne dès qu'un de ses boutons reçoit le focus au
   Tab. Sans cela, un utilisateur au clavier tabulerait dans des commandes
   invisibles, ce qui est exactement la panne que le survol seul fabrique
   (WCAG 2.4.7 — focus visible). Les lecteurs d'écran, eux, ne sont jamais
   concernés : l'opacité ne masque rien à l'arbre d'accessibilité. */
.jsb-picker-table tbody .jsb-picker-gestes {
    opacity: 0;
    pointer-events: none;
    transition: opacity .12s ease;
}
.jsb-picker-table tbody tr:hover .jsb-picker-gestes,
.jsb-picker-table tbody tr:focus-within .jsb-picker-gestes {
    opacity: 1;
    pointer-events: auto;
}
.jsb-picker-geste {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    min-height: 30px;
    padding: .2rem .55rem;
    font-size: .8rem;
    line-height: 1.2;
    transition: color .12s ease, background-color .12s ease, border-color .12s ease;
}
/* LE SURVOL DIT QUE C'EST CLIQUABLE, et il le dit en teinte de marque — comme la
   pastille du pagineur, et non par le gris par défaut de Bootstrap (Règle 4 de la
   charte). Sur une ligne qui porte trois gestes voisins, c'est aussi ce qui montre
   lequel on est en train de viser. */
.jsb-picker-geste:not(:disabled):hover,
.jsb-picker-geste:not(:disabled):focus-visible {
    color: #ffffff;
    background-color: #0047AB;
    border-color: #0047AB;
}
.jsb-picker-geste:focus-visible {
    box-shadow: 0 0 0 .2rem rgba(0, 71, 171, 0.35);
}
/* L'icône suit la couleur du bouton : elle blanchit au survol avec le libellé. */
.jsb-picker-geste svg { flex: 0 0 auto; }

/* ── NOTE DE BAS DE TABLEAU ──────────────────────────────────────────────────
   Une phrase d'explication sous une liste doit commencer À LA VERTICALE de sa
   première colonne. Posée avec un retrait Bootstrap quelconque, elle s'en décale
   de quelques pixels — assez pour que l'œil lise deux blocs mal empilés plutôt
   qu'un tableau et son commentaire. */
.jsb-picker-note {
    margin: 0;
    padding: .75rem var(--jsb-picker-gouttiere) 1rem;
    font-size: .78rem;
    color: #6c757d;
}

/* ── LE CALENDRIER D'ÉQUIPE ──────────────────────────────────────────────────
   Le corps : la gouttière du socle, appliquée à un contenu qui n'est pas un
   tableau pleine largeur à en-tête collante. */
.jsb-cal-corps { padding: .75rem var(--jsb-picker-gouttiere) 1.25rem; }

/* La grille défile dans son propre cadre : trente et une colonnes ne tiennent pas
   dans un panneau, et laisser la page défiler emporterait l'entête avec elle. */
.jsb-cal-defilement { overflow-x: auto; }

.jsb-cal-table {
    width: 100%;
    margin: 0 0 .5rem;
    border-collapse: separate;
    border-spacing: 0;
    font-size: .75rem;
}

/* La colonne des noms reste visible pendant le défilement horizontal : sans elle,
   on lit une bande de carrés sans savoir de qui il s'agit. */
.jsb-cal-agent {
    position: sticky;
    left: 0;
    z-index: 2;
    min-width: 160px;
    padding: .35rem .5rem;
    background: var(--bg-white, #ffffff);
    text-align: left;
    font-weight: 600;
    color: #212529;
    white-space: nowrap;
}
.jsb-cal-table tbody .jsb-cal-agent { z-index: 1; }

.jsb-cal-jour {
    width: 24px;
    min-width: 24px;
    padding: .3rem 0;
    text-align: center;
    font-weight: 400;
    color: #212529;
}
.jsb-cal-jour-lettre,
.jsb-cal-jour-numero { display: block; }
.jsb-cal-jour.is-aujourdhui { font-weight: 700; background: #e8f0fb; }
/* Un férié se distingue du week-end : l'un est chômé par le calendrier du cabinet,
   l'autre par le rythme de la semaine. Le `title` porte le nom du férié — la
   couleur ne dit jamais seule (WCAG 1.4.1). */
.jsb-cal-jour.is-ferie { color: #dc3545; }
.jsb-cal-jour.is-weekend { color: #adb5bd; }

.jsb-cal-case { padding: 1px; text-align: center; }
/* Fond intermédiaire de la charte (--bg-subtle) : un jour chômé se creuse
   légèrement, sans devenir une case pleine. */
.jsb-cal-case.is-chome { background: var(--bg-subtle, #f0f0f0); }

.jsb-cal-plage {
    display: block;
    width: 100%;
    height: 16px;
    border-radius: 3px;
}
.jsb-cal-plage--vide { background: transparent; }
/* Un jour que l'intéressé ne travaille pas : la plage s'efface sans disparaître —
   l'absence court bien, mais ce jour n'est pas décompté. */
.jsb-cal-plage.is-non-travaille { opacity: .35; }

.jsb-cal-total {
    min-width: 52px;
    padding: .35rem .5rem;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: #0047AB;
}
.jsb-cal-table thead .jsb-cal-total { color: #212529; font-weight: 400; }

/* ── LA LIGNE SURVOLÉE SE SUIT DU REGARD ─────────────────────────────────────
   Trente et une colonnes de carrés de vingt-quatre pixels : sans repère, l'œil
   perd la ligne entre le nom et le total, et l'on remonte deux fois pour vérifier
   à qui appartient une absence. Le survol n'ajoute aucune information — il aide à
   TENIR celle qui est déjà là (Bastien & Scapin > Guidage ; Nielsen 6).

   Aucune action n'est attachée à ces lignes : c'est une aide à la lecture, pas une
   cible. Rien à rendre accessible au clavier, donc, et rien qui repose sur la
   couleur seule.

   La teinte est celle que la charte réserve au survol des lignes de grille
   (--bg-muted #e9ecef) : sur un fond déjà tramé de gris clairs, le #f8f9fa des
   listes ordinaires se serait confondu avec les cases chômées.

   ⚠ LA CELLULE COLLANTE DOIT SUIVRE, elle aussi. Elle porte son propre fond blanc
   — c'est ce qui l'empêche d'être traversée par les colonnes qui défilent dessous —
   et resterait donc seule non surlignée, au beau milieu du geste. */
.jsb-cal-table tbody tr {
    transition: background-color .12s ease;
}
.jsb-cal-table tbody tr:hover .jsb-cal-agent,
.jsb-cal-table tbody tr:hover .jsb-cal-case,
.jsb-cal-table tbody tr:hover .jsb-cal-total {
    background-color: #e9ecef;
}
/* Le nom passe en teinte de marque : on sait d'un coup d'œil quelle ligne on lit. */
.jsb-cal-table tbody tr:hover .jsb-cal-agent { color: #0047AB; }
/* LE WEEK-END RESTE UN WEEK-END SUR LA LIGNE SURVOLÉE. Repeindre toute la ligne
   d'une seule teinte effacerait la trame des jours chômés là précisément où l'on
   regarde — et l'on compterait un samedi comme un jour ouvré. */
.jsb-cal-table tbody tr:hover .jsb-cal-case.is-chome {
    background-color: var(--border-light, #dee2e6);
}

.jsb-cal-legende {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem;
    font-size: .75rem;
}
.jsb-cal-puce {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 3px;
}
.jsb-cal-puce.is-non-travaille { opacity: .35; }

.jsb-picker-empty {
    padding: 1.5rem .5rem;
    text-align: center;
    color: #6c757d;
    font-size: .88rem;
}
.jsb-picker-foot {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: 1rem var(--jsb-picker-gouttiere);
    border-top: 1px solid #dee2e6;
    background: #f8f9fa;
}
.jsb-picker-hint { margin: 0; }
/* À LARGEUR DE TÉLÉPHONE, LE PIED PASSE À LA LIGNE. Sur une seule ligne, l'aide et deux
   boutons ne tenaient pas dans 400 px : le bouton d'action principal était coupé hors de
   la boîte (constaté sur « Enregistrer le renouvellement »). L'aide prend toute la
   largeur, les boutons se partagent la ligne suivante et gardent leur cible ≥ 44 px. */
@media (max-width: 575.98px) {
    .jsb-picker-foot { flex-wrap: wrap; }
    .jsb-picker-foot > .jsb-picker-hint { flex: 1 1 100%; }
    .jsb-picker-foot > .jsb-picker-btn { flex: 1 1 auto; white-space: normal; }
}

/* ── Boîte « Attacher des pièces » ───────────────────────────────────────────
   Le picker de dépôt réutilise TOUT le socle .jsb-picker-* (overlay, entête,
   intro, progression, pied) ; seule la zone de dépôt et la liste des fichiers
   choisis lui appartiennent, parce qu'aucun autre picker n'en a. Elles vivent
   ici, avec les autres, et non dans un <style> du gabarit : une boîte dont la
   facture serait embarquée cesserait de suivre la charte le jour où celle-ci
   bouge. */
/* La gouttière du corps. .jsb-picker-listwrap n'en a aucune — elle est faite pour
   accueillir un tableau pleine largeur à entête collante. Notre contenu n'est pas un
   tableau : il reprend donc l'alignement horizontal de l'entête (1,25 rem), sinon la
   zone de dépôt et les fichiers touchent les bords de la boîte. */
.jsb-attach-corps { padding: 1rem var(--jsb-picker-gouttiere) 1.25rem; }
.jsb-attach-corps .jsb-picker-empty { padding: 1.1rem .5rem 0; }

/* ── CARTES DE SECTION D'UN PICKER ───────────────────────────────────────────
   Deux structures différentes ne doivent pas se lire comme une seule. Le picker
   de reversement en aligne trois — les affaires à régler, le versement lui-même,
   ce qui sera enregistré — et leurs seules légendes ne suffisaient pas à les
   séparer : le regard descendait d'un titre à l'autre sans frontière, et la
   fenêtre paraissait ne contenir qu'un long formulaire.

   Chaque section devient donc une CARTE blanche sur un fond légèrement grisé,
   sa légende détachée par un filet. La classe est générique — jsb-picker-carte —
   parce que rien ici ne concerne le reversement : tout picker qui alignera
   plusieurs structures en aura besoin. */
.jsb-picker-cartes {
    /* LA GOUTTIÈRE EST CELLE DE LA BOÎTE, et non une valeur à part. Les cartes se
       décalaient d'un quart de rem par rapport à l'entête, à la recherche et au pied :
       assez peu pour ne pas se nommer, assez pour que la fenêtre paraisse bâtie de deux
       morceaux. C'est exactement ce que la variable existe pour éviter. */
    padding: .75rem var(--jsb-picker-gouttiere) 1.25rem;
    background-color: var(--bg-light, #f8f9fa);
}
/* NE JAMAIS JOINDRE `border-0` À CETTE CLASSE. L'utilitaire de Bootstrap pose
   `border: 0 !important` et efface donc la bordure de la carte : les deux
   fieldset le portaient — pour neutraliser l'encadré par défaut, que Reboot
   supprime déjà — et n'affichaient aucune carte, quand la section voisine, elle,
   s'encadrait bien. Un cas où l'utilitaire et le composant disent le contraire. */
.jsb-picker-carte {
    /* LA GOUTTIÈRE DU SÉLECTEUR N'A PLUS COURS ICI. Elle sert à aligner un tableau
       PLEINE LARGEUR sur l'entête et le pied de la boîte ; dans une carte, c'est la
       carte qui donne le retrait, et l'ajouter une seconde fois repousserait la
       première colonne d'un pouce vers l'intérieur. On la redéfinit plutôt que
       d'écrire une exception par sélecteur : la variable est faite pour cela. */
    --jsb-picker-gouttiere: .35rem;
    margin: 0 0 .85rem;
    padding: .85rem 1rem 1rem;
    background-color: var(--bg-white, #ffffff);
    border: 1px solid var(--border-light, #dee2e6);
    border-radius: 10px;
}
.jsb-picker-carte:last-child { margin-bottom: 0; }
/* LA LÉGENDE RESTE FLOTTANTE — c'est ce qui la fait entrer dans la carte.
   Une `legend` n'est pas une boîte ordinaire : le navigateur la pose SUR la
   bordure haute de son fieldset, en dehors du cadre, et la dimensionne au
   contenu. Le `float: left; width: 100%` de Reboot est justement ce qui la
   rend à une mise en page normale ; l'avoir annulé sortait le titre de la
   carte et réduisait son filet à la largeur du mot, quand la section voisine,
   dont le titre est un h6 ordinaire, s'affichait correctement. */
.jsb-picker-carte > legend {
    float: left;
    width: 100%;
}
.jsb-picker-carte > legend,
.jsb-picker-carte > .jsb-picker-carte-titre {
    margin: 0 0 .75rem;
    padding-bottom: .55rem;
    border-bottom: 1px solid var(--border-light, #dee2e6);
    font-size: .95rem;
    font-weight: 600;
}

.jsb-attach-drop {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .2rem;
    padding: 1.6rem 1rem;
    border: 2px dashed #b9c6d4;
    border-radius: 10px;
    color: #5a6c7f;
    cursor: pointer;
    text-align: center;
    transition: border-color .15s, background-color .15s, color .15s;
}
.jsb-attach-drop:hover,
.jsb-attach-drop:focus-visible,
.jsb-attach-drop.is-over { border-color: #0047AB; background: #f4f8ff; color: #0047AB; }
.jsb-attach-drop-icon { color: #0047AB; }
.jsb-attach-drop-titre { font-weight: 600; color: #0d2440; }
.jsb-attach-drop-aide { font-size: .82rem; }

.jsb-attach-liste { list-style: none; margin: .9rem 0 0; padding: 0; }
.jsb-attach-liste:empty { margin: 0; }
.jsb-attach-liste li {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .5rem .65rem;
    margin-bottom: .4rem;
    border: 1px solid #dee2e6;
    border-radius: 8px;
    font-size: .87rem;
}
/* Un fichier écarté reste VISIBLE et se distingue autrement que par la seule
   couleur (son motif est écrit à côté) — /ergonomie-web. */
.jsb-attach-liste li.is-refuse { border-color: #f1c9c4; background: #fdf4f3; }
.jsb-attach-icone { display: inline-flex; color: #5a6c7f; flex: 0 0 auto; }
.jsb-attach-liste li.is-refuse .jsb-attach-icone { color: #c0392b; }
.jsb-attach-nom { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jsb-attach-taille { color: #6c757d; font-size: .8rem; white-space: nowrap; }
.jsb-attach-motif { color: #c0392b; font-size: .78rem; white-space: nowrap; }
.jsb-attach-retirer {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 32px;
    min-height: 32px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: #6c757d;
    cursor: pointer;
}
.jsb-attach-retirer:hover, .jsb-attach-retirer:focus-visible { background: #fdf4f3; color: #c0392b; }

/* ═════════════════════════════════════════════════════════════════════════════
   LES PIÈCES DÉJÀ ATTACHÉES — un bloc à part, et il le faut.

   Deux listes de fichiers cohabitent dans la même carte : ce qui est DÉJÀ attaché
   au virement, et ce qu'on s'apprête à y ajouter. Rendues du même habillage,
   elles se lisaient comme une seule — impossible de savoir, d'un coup d'œil, ce
   qui est enregistré et ce qui ne l'est pas encore (Bastien & Scapin > Guidage,
   Signifiance ; Nielsen 1, visibilité de l'état du système).

   Le bloc porte donc un TITRE qui le nomme, un filet cobalt à gauche — la couleur
   de marque, pas un état sémantique — et un fond très légèrement grisé. La zone de
   dépôt, elle, garde son cadre en pointillés : deux natures, deux dessins.

   Toutes les couleurs viennent de la charte, par leurs jetons.
   ═══════════════════════════════════════════════════════════════════════════ */
.jsb-pieces-deja {
    margin: .25rem 0 1rem;
    padding: .7rem .8rem .8rem;
    background-color: var(--bg-light, #f8f9fa);
    border: 1px solid var(--border-light, #dee2e6);
    border-left: 3px solid var(--cobalt, #0047AB);
    border-radius: 10px;
}
.jsb-pieces-deja__titre {
    display: flex;
    align-items: center;
    gap: .4rem;
    margin: 0 0 .55rem;
    color: var(--text-body, #495057);
    font-size: .78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .03em;
}
.jsb-pieces-deja__titre svg { color: var(--cobalt, #0047AB); }
.jsb-pieces-deja__liste { list-style: none; margin: 0; padding: 0; }
.jsb-pieces-deja__liste li {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .45rem .3rem .45rem .6rem;
    /* Une ligne fait au moins 44 px de haut : c'est la cible confortable du bouton
       qu'elle porte (WCAG 2.5.5). */
    min-height: 44px;
    margin-bottom: .35rem;
    background-color: var(--bg-white, #ffffff);
    border: 1px solid var(--border-light, #dee2e6);
    border-radius: 8px;
    font-size: .87rem;
    transition: background-color .12s ease, border-color .12s ease;
}
.jsb-pieces-deja__liste li:last-child { margin-bottom: 0; }
/* LA LIGNE RÉPOND À LA SOURIS. Sans retour au survol, rien ne dit qu'il se passe
   quelque chose sous le curseur — et le bouton de retrait se cherche. */
.jsb-pieces-deja__liste li:hover {
    background-color: var(--bg-light, #f8f9fa);
    border-color: var(--text-muted, #6c757d);
}
.jsb-pieces-deja__liste li:focus-within {
    border-color: var(--cobalt, #0047AB);
    box-shadow: 0 0 0 .18rem var(--cobalt-focus-ring, rgba(0, 71, 171, 0.35));
}
.jsb-pieces-deja__icone {
    display: inline-flex;
    flex: 0 0 auto;
    color: var(--cobalt, #0047AB);
}
/* LE NOM EST UN LIEN : on l'ouvre pour vérifier qu'un bordereau est le bon avant de
   le remplacer. Il garde l'allure d'un nom de fichier — souligné au survol
   seulement, pour ne pas transformer la liste en champ de liens bleus. */
.jsb-pieces-deja__nom {
    text-decoration: none;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-dark, #212529);
}
.jsb-pieces-deja__nom:hover { text-decoration: underline; color: var(--cobalt, #0047AB); }
/* LE BOUTON PORTE SON LIBELLÉ, donc il s'aligne comme une paire icône + mot — et
   non comme la pastille carrée de `.jsb-attach-retirer`, dont le `justify-content:
   center` et le `min-width: 32px` écrasaient le texte contre le bord. */
.jsb-pieces-deja__action {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    flex: 0 0 auto;
    min-height: 32px;
    padding: 0 .6rem;
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    color: var(--text-muted, #6c757d);
    font-size: .82rem;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.jsb-pieces-deja__action svg { flex: 0 0 auto; }
/* Un geste DESTRUCTEUR se signale au survol — et jamais par la seule couleur : le
   libellé dit « Retirer », puis « Rétablir ». */
.jsb-pieces-deja__action:hover {
    background-color: var(--danger-bg, #f8d7da);
    border-color: var(--danger-bg, #f8d7da);
    color: var(--danger-dark, #842029);
}
.jsb-pieces-deja__action:focus-visible {
    outline: none;
    border-color: var(--cobalt, #0047AB);
    box-shadow: 0 0 0 .18rem var(--cobalt-focus-ring, rgba(0, 71, 171, 0.35));
}
/* MARQUÉE POUR LE RETRAIT — trois signes, jamais la couleur seule : le nom est
   barré, la ligne s'éteint, et le bouton dit « Rétablir » (WCAG 1.4.1). */
.jsb-pieces-deja__liste li.is-refuse {
    background-color: var(--bg-muted, #e9ecef);
    border-color: var(--border-medium, #ced4da);
}
.jsb-pieces-deja__liste li.is-refuse .jsb-pieces-deja__nom {
    text-decoration: line-through;
    color: var(--text-muted, #6c757d);
}
.jsb-pieces-deja__liste li.is-refuse .jsb-pieces-deja__icone { color: var(--text-muted, #6c757d); }
.jsb-pieces-deja__liste li.is-refuse .jsb-pieces-deja__action { color: var(--blue-primary, #0d6efd); }
.jsb-pieces-deja__liste li.is-refuse .jsb-pieces-deja__action:hover {
    background-color: var(--bg-cobalt-subtle, #e8f0fb);
    border-color: var(--bg-cobalt-subtle, #e8f0fb);
    color: var(--cobalt, #0047AB);
}


/* ─────────────────────────────────────────────────────────────────────────────
   COLLECTION EN ATTENTE — les lignes saisies avant l'enregistrement de la fiche.

   Elles reprennent l'habillage des lignes persistées (.table-enhanced, mêmes
   classes de ligne et d'action, mêmes icônes) : un vocabulaire visuel unique pour
   une même chose. Ne restent distincts que les deux signes qui disent l'état —
   sans quoi l'utilisateur croirait sa saisie déjà enregistrée.

   Le cobalt très clair est le ton du statut « nouveau » de la charte
   (--bg-cobalt-subtle, comme .analysis-item-status-bar--new) : une information,
   pas un avertissement. Réserver le jaune ou le rouge à ce qui va mal.
   ───────────────────────────────────────────────────────────────────────────── */
.collection-chip-attente {
    /* PAS de .badge Bootstrap ici : la classe est forcée en bleu plein dans ce projet,
       ce qui écrasait ces couleurs et rendait le mot illisible. Même parti pris que
       .jsb-picker-chip, qui a rencontré le problème avant nous. */
    display: inline-block;
    background-color: var(--bg-cobalt-subtle, #e8f0fb);
    color: var(--text-cobalt, #0047AB);
    font-weight: 600;
    font-size: 0.7rem;
    line-height: 1.4;
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    vertical-align: middle;
    /* Le statut ne passe JAMAIS par la seule couleur : le mot « à enregistrer »
       le porte aussi, pour qui ne distingue pas les teintes (WCAG 1.4.1). */
}

.collection-pied-attente {
    margin: 0;
    padding: 0.5rem 0.75rem 0.65rem;
    color: var(--text-muted, #6c757d);
    font-size: 0.8rem;
    border-top: 1px solid var(--border-subtle, #e0e0e0);
}

/* PAS DE LISERÉ SUR LA LIGNE. Un `box-shadow` posé sur la cellule ÉCRASE celui par
   lequel Bootstrap peint le survol (`--bs-table-accent-bg`, rendu en ombre interne de
   9999px) : la première cellule restait blanche pendant que les autres viraient au gris,
   et la ligne se survolait en deux couleurs. La pastille « à enregistrer » suffit
   largement à distinguer la ligne — inutile de payer ce prix. */

/* ─────────────────────────────────────────────────────────────────────────────
   INTERTITRE DE SECTION dans un dialogue (`group_title` du canevas).

   Il nomme un GROUPE de champs — « Partage des revenus de cette affaire » — et
   non un champ : il ne doit donc ni porter la marque des champs obligatoires, ni
   se confondre avec un libellé. Cobalt de la marque, pour dire l'appartenance à
   une section sans crier plus fort que les champs qu'elle rassemble.
   ───────────────────────────────────────────────────────────────────────────── */
.dlg-group-title {
    display: block;
    /* La colonne n'a plus de gouttière : l'intertitre porte la sienne, et un filet le
       sépare des cartes qu'il annonce. */
    margin: 0;
    padding: 1rem 1.25rem 0.35rem;
    box-shadow: inset 0 -1px 0 var(--border-light);
    color: var(--text-cobalt, #0047AB);
    font-size: 0.82rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* ══════════════════════════════════════════════════════════════════════════
   DIALOGUES SUR ÉCRAN ÉTROIT
   ══════════════════════════════════════════════════════════════════════════
   Les 53 formulaires d'entité passent tous par le même dialogue, et leurs CHAMPS
   étaient déjà adaptatifs : `col-md-*` les empile nativement sous 768 px. Trois
   choses ne l'étaient pas, et elles suffisaient à rendre l'édition impraticable
   au doigt.

   Le seuil reprend celui de Bootstrap (`md`), celui-là même que `col-md-*` utilise
   déjà dans le corps du dialogue : les champs et leur cadre basculent donc au même
   moment. Un seuil propre les aurait désaccordés — des champs empilés dans deux
   colonnes encore côte à côte.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {
    /* 1. LES DEUX COLONNES S'EMPILENT.
       Le corps du dialogue est un conteneur flex en RANGÉE : la colonne sombre des
       attributs calculés et le formulaire restaient donc côte à côte, à se partager
       380 px. Deux colonnes de 190 px ne sont pas deux colonnes, ce sont deux
       gouttières. On les empile, et le corps redevient ce qu'il est ailleurs : une
       pile qui défile. */
    .custom-modal-scroll .modal-body {
        flex-direction: column;
        /* Le défilement passe au corps : en colonne, ce sont les deux blocs
           ensemble qui doivent défiler, et non chacun dans sa propre fenêtre —
           deux ascenseurs imbriqués sur un écran de téléphone sont inutilisables. */
        overflow-y: auto;
    }

    .custom-modal-scroll .modal-body > .calculated-attributes-column,
    .custom-modal-scroll .modal-body > .form-column {
        overflow-y: visible;
    }

    /* 2. LE PLANCHER VERTICAL TOMBE.
       `min-height: 400px` empêchait le tassement quand les colonnes étaient côte à
       côte. Empilées, il additionne deux fois 400 px de vide avant même le premier
       champ : la modale s'ouvre sur une carte blanche. */
    .form-column-skeleton,
    .form-column {
        min-height: 0;
    }

    /* 3. LES GRILLES FIGÉES À DEUX COLONNES SE DÉPLIENT.
       La matrice des droits et les groupes de cases à cocher imposaient
       `repeat(2, …)` quelle que soit la largeur. Sur téléphone, chaque case se
       retrouvait dans une colonne trop étroite pour son libellé, qui passait à la
       ligne au milieu d'un mot. Une seule colonne rend chaque option lisible et sa
       rangée entièrement cliquable. */
    .form-column:has(input[name^="access"]) form,
    .form-column .dlg-field-card-body > div:has(> .form-check) {
        grid-template-columns: 1fr;
    }
}

/*
===================================================================
 GUIDE DE DÉMARRAGE DU CABINET — barre de progression et bandeau
===================================================================
 Trois surfaces partagent la barre (`.jsb-onboarding-barre`) : le bandeau du
 tableau de bord, l'infobulle du voyant et l'entête du guide. Une seule
 définition, donc une seule apparence.
*/

.jsb-onboarding-barre {
    flex: 1 1 auto;
    height: 7px;
    border-radius: 4px;
    background-color: rgba(0, 71, 171, 0.14);
    overflow: hidden;
}

/* SUR FOND SOMBRE (infobulle du voyant), le creux ET le rempli changent.
   Le cobalt du rempli tombe à 1,83:1 sur #212529 — la barre était pleine sans
   qu'on puisse voir jusqu'où. L'ambre y monte à 9,46:1, et raccorde l'infobulle
   à l'anneau du voyant, qui l'utilise pour la même raison. */
.ws-onboarding-detail .jsb-onboarding-barre {
    background-color: rgba(255, 255, 255, 0.18);
}

.ws-onboarding-detail .jsb-onboarding-barre-remplie {
    background-color: var(--warning);
}

.jsb-onboarding-barre-remplie {
    display: block;
    height: 100%;
    border-radius: 4px;
    background-color: var(--cobalt);
    transition: width 0.35s ease;
}

/* --- Le bandeau de dette du tableau de bord ---
   Pleine largeur, en tête de l'écran d'arrivée. Il NOMME ce qui manque plutôt
   que de se contenter d'un pourcentage : un chiffre ne dit pas quoi faire. */
.jsb-onboarding-bandeau {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 1rem;
    padding: 16px 20px;
    border-radius: 12px;
    border-left: 4px solid var(--cobalt);
    background-color: var(--bg-cobalt-subtle);
    box-shadow: var(--shadow-sm);
}

.jsb-onboarding-bandeau-icone {
    flex: 0 0 auto;
    display: inline-flex;
    color: var(--cobalt);
}

.jsb-onboarding-bandeau-corps {
    flex: 1 1 auto;
    min-width: 0;
}

.jsb-onboarding-bandeau-titre {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 10px;
    margin: 0 0 8px;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text-dark);
}

.jsb-onboarding-bandeau-reste {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--cobalt);
}

.jsb-onboarding-bandeau-manques {
    margin: 8px 0 0;
    font-size: 0.86rem;
    color: var(--text-body);
}

.jsb-onboarding-cta-primaire {
    flex: 0 0 auto;
    /* Le bouton porte une icône : sans flex, elle se poserait sur la ligne de base du
       texte au lieu d'être centrée avec lui. */
    display: inline-flex;
    align-items: center;
    /* Centré, parce qu'à l'étroit ce bouton passe pleine largeur (cf. media query) :
       sans cela, l'icône et le libellé se tasseraient à gauche d'une barre vide. */
    justify-content: center;
    gap: 8px;
    padding: 11px 20px;
    border: none;
    border-radius: var(--nav-radius-sm);
    background-color: var(--cobalt);
    color: var(--text-white);
    font-size: 0.88rem;
    font-weight: 700;
    cursor: pointer;
    transition: background-color 0.15s ease;
}

.jsb-onboarding-cta-primaire:hover {
    background-color: var(--cobalt-dark);
}

.jsb-onboarding-cta-primaire:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--cobalt-focus-ring);
}

/* L'icône des boutons d'appel à l'action de l'onboarding — celle de l'écran qu'ils
   ouvrent. Elle vaut sur fond cobalt comme sur fond sombre : c'est le même geste. */
.jsb-onboarding-cta-icone {
    display: inline-flex;
    flex: 0 0 auto;
    line-height: 0;
}

/* Sur écran étroit, le bouton passe sous le texte plutôt que de l'écraser. */
@media (max-width: 767.98px) {
    .jsb-onboarding-bandeau {
        flex-wrap: wrap;
    }
    .jsb-onboarding-cta-primaire {
        width: 100%;
    }
}

/* ===================================================================
   LE GUIDE — une carte-atelier par étape
   ===================================================================
   Chaque carte montre l'existant, laisse ouvrir n'importe quelle ligne pour la
   corriger, et permet d'en ajouter à volonté. Tout se règle dans un dialogue
   posé PAR-DESSUS ce panneau : on ne quitte jamais le guide.
   =================================================================== */
.jsb-onboarding {
    max-width: 1100px;
    margin: 0 auto;
    padding: 20px 24px 40px;
}

.jsb-onboarding-entete { margin-bottom: 26px; }

.jsb-onboarding-titre {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 6px;
    font-size: 1.4rem;
    font-weight: 700;
    color: var(--text-dark);
}

.jsb-onboarding-titre-icone { display: inline-flex; color: var(--cobalt); }

.jsb-onboarding-intro {
    margin: 0 0 14px;
    font-size: 0.92rem;
    color: var(--text-muted);
}

.jsb-onboarding-score { display: flex; align-items: center; gap: 12px; }

.jsb-onboarding-score-valeur {
    flex: 0 0 auto;
    font-size: 1rem;
    font-weight: 700;
    color: var(--cobalt);
}

.jsb-onboarding-section { margin-top: 28px; }

.jsb-onboarding-section-titre {
    margin: 0 0 3px;
    font-size: 1.02rem;
    font-weight: 700;
    color: var(--text-dark-secondary);
}

.jsb-onboarding-section-intro {
    margin: 0 0 14px;
    font-size: 0.84rem;
    color: var(--text-muted);
}

.jsb-onboarding-cartes {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
    gap: 14px;
}

.jsb-onboarding-carte {
    display: flex;
    flex-direction: column;
    padding: 14px 16px;
    border: 1px solid var(--border-light);
    border-radius: 12px;
    background-color: var(--bg-white);
    box-shadow: var(--shadow-sm);
    transition: box-shadow .18s ease, transform .18s ease, border-color .18s ease;
}

/* ── LE SURVOL DIT « JE TE VOIS », PAS « CLIQUE-MOI » ─────────────────────────
   La carte n'est PAS cliquable dans son ensemble : seules ses lignes et son
   bouton le sont. On lui donne donc un accusé de présence — élévation et bord
   cobalt —, et surtout PAS de `cursor: pointer`, qui promettrait un clic sans
   effet au milieu de la carte (Bastien & Scapin > Signifiance).

   L'élévation reprend celle des cartes d'entreprise (`.ent-card:hover`) : même
   geste, même ombre cobalt, d'un écran à l'autre.

   `:focus-within` n'est pas une redite du survol : sans lui, un utilisateur au
   clavier tabulant vers le bouton d'ajout ne verrait jamais quelle carte il a
   atteinte (WCAG 2.4.7).

   Les trois côtés sont nommés un par un pour épargner le BORD GAUCHE, qui porte
   l'état de l'étape — cobalt à faire, vert acquis. Un `border-color` global
   l'aurait effacé au premier survol. */
.jsb-onboarding-carte:hover,
.jsb-onboarding-carte:focus-within {
    border-top-color: var(--cobalt);
    border-right-color: var(--cobalt);
    border-bottom-color: var(--cobalt);
    box-shadow: 0 8px 24px rgba(0, 71, 171, .12);
    transform: translateY(-2px);
}

/* L'icône de l'étape passe du cobalt au cobalt foncé : l'œil trouve d'abord le
   titre de la carte survolée, avant même d'en lire le contenu. */
.jsb-onboarding-carte:hover .jsb-onboarding-carte-icone,
.jsb-onboarding-carte:focus-within .jsb-onboarding-carte-icone {
    color: var(--cobalt-dark);
}

/* WCAG 2.3.3 : on retire le MOUVEMENT, pas le retour visuel. L'ombre et le bord
   restent — ce sont eux qui portent l'information de présence. */
@media (prefers-reduced-motion: reduce) {
    .jsb-onboarding-carte {
        transition: box-shadow .18s ease, border-color .18s ease;
    }
    .jsb-onboarding-carte:hover,
    .jsb-onboarding-carte:focus-within {
        transform: none;
    }
}

/* Une étape à faire s'annonce par sa bordure cobalt : c'est là que le regard
   doit aller. L'état est aussi écrit en toutes lettres dans l'entête, la
   couleur ne portant jamais seule l'information (WCAG 1.4.1). */
.jsb-onboarding-carte.est-a-faire { border-left: 3px solid var(--cobalt); }
.jsb-onboarding-carte.est-fait { border-left: 3px solid var(--success); }

.jsb-onboarding-carte-entete {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 6px;
}

.jsb-onboarding-carte-icone {
    flex: 0 0 auto;
    display: inline-flex;
    color: var(--cobalt);
    transition: color .18s ease;
}

.jsb-onboarding-carte-titre {
    flex: 1 1 auto;
    margin: 0;
    font-size: 0.96rem;
    font-weight: 700;
    color: var(--text-dark);
}

.jsb-onboarding-carte-etat {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 9px;
    border-radius: 20px;
    font-size: 0.72rem;
    font-weight: 600;
    white-space: nowrap;
    background-color: var(--bg-muted);
    color: var(--text-body);
}

.jsb-onboarding-carte.est-fait .jsb-onboarding-carte-etat {
    background-color: #d1e7dd;
    color: #0f5132;
}

.jsb-onboarding-carte-pourquoi {
    margin: 0 0 10px;
    font-size: 0.84rem;
    line-height: 1.5;
    color: var(--text-muted);
}

/* --- Les lignes de l'existant : chacune ouvre sa fiche en édition --- */
.jsb-onboarding-lignes {
    list-style: none;
    margin: 0 0 10px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.jsb-onboarding-ligne {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    /* Cible tactile : 30 px de haut passaient sous le seuil de 24 px CSS exigé par
       WCAG 2.5.8 une fois l'espacement compté. On vise confortablement au-dessus. */
    min-height: 40px;
    padding: 7px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--nav-radius-sm);
    background-color: var(--bg-light);
    font-family: inherit;
    font-size: 0.82rem;
    text-align: left;
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}

.jsb-onboarding-ligne:hover:not(:disabled) {
    background-color: var(--bg-muted);
    border-color: var(--cobalt);
}

.jsb-onboarding-ligne:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--cobalt-focus-ring);
}

.jsb-onboarding-ligne:disabled { opacity: 0.65; pointer-events: none; }

.jsb-onboarding-ligne-libelle {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
    color: var(--text-dark);
}

.jsb-onboarding-ligne-detail {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-muted);
}

.jsb-onboarding-ligne-crayon { flex: 0 0 auto; display: inline-flex; color: var(--text-muted); }
.jsb-onboarding-ligne:hover .jsb-onboarding-ligne-crayon { color: var(--cobalt); }

.jsb-onboarding-reste {
    margin: -4px 0 10px;
    font-size: 0.76rem;
    color: var(--text-muted);
}

/* Le bouton d'ajout est PRIMAIRE tant que l'étape est à faire — c'est l'appel à
   l'action — puis passe en contour une fois l'étape acquise : il reste
   disponible à volonté, mais cesse de crier. */
.jsb-onboarding-ajouter {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    margin-top: auto;
    padding: 9px 14px;
    border: 1px solid var(--cobalt);
    border-radius: var(--nav-radius-sm);
    background-color: var(--cobalt);
    color: var(--text-white);
    font-family: inherit;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;
}

.jsb-onboarding-ajouter:hover:not(:disabled) {
    background-color: var(--cobalt-dark);
    border-color: var(--cobalt-dark);
    color: var(--text-white);
}

.jsb-onboarding-ajouter:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--cobalt-focus-ring);
}

.jsb-onboarding-ajouter:disabled { opacity: 0.65; pointer-events: none; }

.jsb-onboarding-ajouter.est-secondaire {
    background-color: transparent;
    color: var(--cobalt);
}

/* Où retrouver l'élément plus tard, une fois le guide fermé.
   `--text-label` (#adb5bd) est réservé aux labels de formulaire et aux fonds sombres :
   sur blanc il tombe à 1,9:1, très en deçà des 4,5:1 exigés (WCAG 1.4.3). */
.jsb-onboarding-carte-ou {
    margin: 8px 0 0;
    font-size: 0.72rem;
    color: var(--text-muted);
}

/* ═════════════════════════════════════════════════════════════════════════════
   SUPPRIMER UN DOSSIER — l'arbre à gauche, le récit à droite.

   Transposition du système visuel décrit dans la spécification (accent, cadres,
   étapes numérotées) dans la charte de l'application. AUCUNE couleur nouvelle :
   l'accent est le cobalt de marque, le blocage et l'échec sont la famille
   `--danger`, et chaque valeur ci-dessous vient de `:root`.

   ⚠ LE CONTRASTE EST LE PIÈGE DE CE BLOC, et il a été vérifié :
     - une ligne conservée est en `--text-muted` (4,69:1 sur blanc) et JAMAIS en
       opacité réduite — 45 % d'opacité tombe à ~2,1:1, sous le seuil AA ;
     - un échec est `--danger-dark` sur `--danger-bg` (7,0:1). `--danger` sur ce
       même fond ne donne que 3,40:1 : il ne sert donc qu'aux filets et aux icônes ;
     - le pied est sur `--bg-light`, où `--text-muted` retombe à 4,45:1 : il
       utilise `--text-body` (7,9:1).

   ⚠ ET LA CIBLE CLIQUABLE EST LA LIGNE ENTIÈRE. La case fait 16 px : à elle seule
   elle échouerait au critère 2.5.8. La rangée porte donc 32 px de haut, et 44 px
   dès que le pointeur est grossier (doigt).
   ═════════════════════════════════════════════════════════════════════════════ */
.jsb-purge-intro {
    margin: 0;
    padding: 0.75rem var(--jsb-picker-gouttiere) 0.5rem;
    font-size: 0.86rem;
    color: var(--text-body);
}

.jsb-purge-corps {
    display: grid;
    /* `minmax(0, …)` à gauche : sans lui, un long libellé élargit la colonne et fait
       respirer le panneau de droite à chaque frappe. */
    grid-template-columns: minmax(0, 65fr) minmax(320px, 35fr);
    gap: 0;
    flex: 1 1 auto;
    min-height: 0;
}

.jsb-purge-arbre {
    display: flex;
    flex-direction: column;
    min-height: 0;
    border-right: 1px solid var(--border-light);
}

.jsb-purge-compte {
    padding: 0.6rem var(--jsb-picker-gouttiere) 0.5rem;
    border-bottom: 1px solid var(--border-subtle);
}
.jsb-purge-compte-titre {
    margin: 0;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}
.jsb-purge-volume {
    margin: 0.15rem 0 0;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--text-dark);
    font-variant-numeric: tabular-nums;
}

.jsb-purge-listwrap { padding: 0.35rem 0 0.75rem; }

.jsb-purge-tree,
.jsb-purge-groupe {
    list-style: none;
    margin: 0;
    padding: 0;
}

.jsb-purge-rangee {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 32px;
    padding: 0.15rem var(--jsb-picker-gouttiere) 0.15rem 0.5rem;
    cursor: pointer;
    border-left: 3px solid transparent;
}
.jsb-purge-rangee:hover { background: var(--bg-light); }
.jsb-purge-noeud:focus-visible > .jsb-purge-rangee {
    outline: 2px solid var(--cobalt);
    outline-offset: -2px;
}
@media (pointer: coarse) {
    .jsb-purge-rangee { min-height: 44px; }
}

.jsb-purge-fleche {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--text-muted);
    line-height: 1;
    cursor: pointer;
}
.jsb-purge-fleche svg { width: 15px; height: 15px; }
.jsb-purge-fleche:hover { background: var(--bg-muted); color: var(--cobalt); }
.jsb-purge-fleche:focus-visible { outline: 2px solid var(--cobalt); outline-offset: 1px; }
.jsb-purge-fleche--vide { cursor: default; }

/* La case : 16 px de glyphe dans une boîte de 24, pour que le dessin reste fin
   sans que la cible le devienne. */
.jsb-purge-case {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    position: relative;
}
.jsb-purge-case::before {
    content: '';
    width: 16px;
    height: 16px;
    border: 1px solid var(--border-medium);
    border-radius: 3px;
    background: var(--bg-white);
}
.est-coche > .jsb-purge-rangee .jsb-purge-case::before {
    border-color: var(--cobalt);
    background: var(--cobalt);
}
.est-coche > .jsb-purge-rangee .jsb-purge-case::after {
    content: '';
    position: absolute;
    width: 4px;
    height: 8px;
    border: solid var(--text-white);
    border-width: 0 2px 2px 0;
    transform: translateY(-1px) rotate(45deg);
}
.est-partiel > .jsb-purge-rangee .jsb-purge-case::before { border-color: var(--cobalt); }
.est-partiel > .jsb-purge-rangee .jsb-purge-case::after {
    content: '';
    position: absolute;
    width: 8px;
    height: 2px;
    background: var(--cobalt);
}

.est-coche > .jsb-purge-rangee { background: var(--bg-cobalt-subtle); border-left-color: var(--cobalt); }
.est-coche > .jsb-purge-rangee:hover { background: var(--bg-cobalt-subtle); }

.jsb-purge-nom {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.82rem;
    color: var(--text-dark);
}
/* ⚠ « Conservé » se voit au TEXTE autant qu'à la teinte : la case décochée et le
   libellé de type restent lisibles, et rien n'est transmis par la seule couleur. */
.est-conserve > .jsb-purge-rangee .jsb-purge-nom { color: var(--text-muted); }

.jsb-purge-type {
    flex: 0 0 auto;
    font-size: 0.66rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-label);
}
.jsb-purge-volume-noeud {
    flex: 0 0 auto;
    font-size: 0.72rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.jsb-purge-rangee.is-verrouille {
    background: var(--danger-bg);
    border-left-color: var(--danger);
    cursor: not-allowed;
}
.jsb-purge-rangee.is-verrouille .jsb-purge-nom,
.jsb-purge-motif { color: var(--danger-dark); }
.jsb-purge-rangee.is-verrouille .jsb-purge-case::before { border-color: var(--danger); }
.jsb-purge-motif {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 0.72rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Le panneau de droite ────────────────────────────────────────────────── */
.jsb-purge-panneau {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow-y: auto;
    padding: 0.6rem 1rem 1rem;
    background: var(--bg-light);
}

.jsb-purge-etapes {
    display: flex;
    gap: 0.35rem;
    list-style: none;
    margin: 0 0 0.75rem;
    padding: 0;
}
.jsb-purge-etape {
    flex: 1 1 0;
    padding: 0.3rem 0.4rem;
    border-radius: var(--nav-radius-sm);
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    background: var(--bg-muted);
    white-space: nowrap;
}
.jsb-purge-etape.is-active {
    color: var(--cobalt-dark);
    background: var(--bg-cobalt-subtle);
    font-weight: 600;
}
.jsb-purge-etape.is-faite { color: var(--success); background: var(--bg-white); }
.jsb-purge-etape-num { font-variant-numeric: tabular-nums; opacity: 0.75; }

.jsb-purge-panneau-titre {
    margin: 0 0 0.5rem;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--text-dark);
}
.jsb-purge-panneau-section {
    margin: 0.85rem 0 0.3rem;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}
.jsb-purge-panneau-liste {
    list-style: none;
    margin: 0;
    padding: 0;
}
.jsb-purge-panneau-liste li {
    padding: 0.25rem 0 0.25rem 0.6rem;
    border-left: 2px solid var(--border-light);
    font-size: 0.8rem;
    color: var(--text-body);
}
.jsb-purge-panneau-liste.est-refus li {
    border-left-color: var(--danger);
    color: var(--danger-dark);
}

/* ── Dispositions étroites : les deux seuils du projet, pas un de plus ────── */
@media (max-width: 1199.98px) {
    .jsb-purge-corps { grid-template-columns: minmax(0, 1fr); }
    .jsb-purge-arbre { border-right: 0; border-bottom: 1px solid var(--border-light); }
    .jsb-purge-panneau { max-height: 38vh; }
}
@media (max-width: 767.98px) {
    .jsb-purge-type { display: none; }
    .jsb-purge-etape { font-size: 0.62rem; }
}

/* L'entete de la boite de suppression : rouge de charte, texte blanc. Elle remplace la
   confirmation de suppression et en garde le signal — c'est le seul ecran qui detruit. */
.jsb-purge-entete {
    background: var(--danger);
    color: var(--text-white);
    border-bottom: 0;
}
.jsb-purge-entete .modal-title { color: var(--text-white); }

/* Arbre gele pendant l'execution : il temoigne, il ne se manoeuvre plus. */
.jsb-purge-tree.est-gele .jsb-purge-rangee { cursor: default; }
.jsb-purge-tree.est-gele .jsb-purge-rangee:hover { background: transparent; }
.jsb-purge-tree.est-gele .est-coche > .jsb-purge-rangee:hover { background: var(--bg-cobalt-subtle); }

/* Le journal : l'etat se lit au signe et au mot, la teinte ne fait que le souligner. */
.jsb-purge-panneau-liste li.est-echec {
    border-left-color: var(--danger);
    color: var(--danger-dark);
}
.jsb-purge-panneau-liste li.est-fait { border-left-color: var(--success); }
.jsb-purge-panneau-liste li.est-en-cours { border-left-color: var(--cobalt); }

/* L'icone de rubrique dans l'arbre : un repere, jamais l'information. Le type est ecrit
   a cote, et l'icone est masquee aux lecteurs d'ecran (aria-hidden). */
.jsb-purge-icone {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    color: var(--text-muted);
}
.est-coche > .jsb-purge-rangee .jsb-purge-icone { color: var(--cobalt); }
.is-verrouille .jsb-purge-icone { color: var(--danger-dark); }
.jsb-purge-icone svg { width: 16px; height: 16px; }

/* ═════════════════════════════════════════════════════════════════════════════
   SUPPRIMER UN DOSSIER — reprise d'ergonomie (Bastien & Scapin > Guidage,
   Groupement/Distinction ; Nielsen 1, 6 et 8).

   Ce que la première version ratait, et qui se voyait à l'usage :
     - la PROFONDEUR ne se lisait qu'au décalage. Sur cinq niveaux, l'œil perdait
       la branche qu'il suivait. Un filet par niveau traversé la rend continue.
     - « 1 objet » était répété sur chaque feuille : vingt fois le même chiffre
       noyait les deux branches qui en emportaient réellement des dizaines.
     - le TYPE flottait en colonne au milieu de la ligne, comme un tableau qui
       n'existe pas. Il suit désormais le nom, en étiquette.
     - rien ne permettait de tout décocher, ni de survoler puis de se concentrer.
   ═════════════════════════════════════════════════════════════════════════════ */

/* La gouttière de l'arbre : un cran par niveau, une seule valeur à régler. */
.jsb-purge-tree { --purge-cran: 18px; }

.jsb-purge-compte { display: block; }
.jsb-purge-compte-texte { min-width: 0; }
/* La rangee de pilules s'inscrit dans la colonne de gauche : elle ne defile pas avec
   l'arbre, et garde la gouttiere commune de la boite. */
.jsb-purge-arbre > .jsb-preset-filters-bar {
    padding: 0.4rem var(--jsb-picker-gouttiere);
    border-top: 1px solid var(--border-subtle);
}

/* ⚠ AUCUNE REGLE SUR LA PILULE NI SUR SES CHIPS, ET C'EST LE POINT. Les deux pilules
   sont celles du choix des colonnes — memes classes, meme montage, meme rangee
   `jsb-preset-filters-bar`. Les redimensionner « un cran plus compact » les desalignait
   de tous les autres ecrans : c'est exactement ce qu'un composant partage existe pour
   eviter. Ce qui doit s'adapter, c'est la boite autour, pas le composant. */

/* ── La rangée : indentation par cran, guides continus ────────────────────── */
.jsb-purge-rangee {
    position: relative;
    padding-left: calc(0.4rem + var(--purge-niveau, 0) * var(--purge-cran));
}
/* Les guides : un filet vertical par niveau traversé, posé en fond. Ils tiennent
   la lecture de la branche là où le seul décalage la perdait. */
.jsb-purge-groupe { position: relative; }
.jsb-purge-groupe::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(0.4rem + var(--purge-guide, 0) * var(--purge-cran));
    width: 1px;
    background: var(--border-subtle);
    pointer-events: none;
}

/* ── L'étiquette de type : elle qualifie la ligne, elle ne la compare pas ─── */
.jsb-purge-type {
    flex: 0 0 auto;
    font-size: 0.64rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-body);
    background: var(--bg-muted);
    border-radius: 999px;
    padding: 0.08rem 0.45rem;
    white-space: nowrap;
}
.est-coche > .jsb-purge-rangee .jsb-purge-type {
    color: var(--cobalt-dark);
    background: var(--bg-white);
}
.is-verrouille .jsb-purge-type { color: var(--danger-dark); background: var(--bg-white); }

/* Le volume ne s'affiche que lorsqu'il APPREND quelque chose (> 1 objet). */
.jsb-purge-volume-noeud {
    margin-left: auto;
    padding-left: 0.5rem;
    font-weight: 600;
}

/* ── L'étape active : elle doit se voir d'un coup d'œil ───────────────────── */
.jsb-purge-etape {
    border: 1px solid transparent;
    text-align: center;
}
.jsb-purge-etape.is-active {
    border-color: var(--cobalt);
    box-shadow: inset 0 -2px 0 var(--cobalt);
}
.jsb-purge-etape.is-faite { border-color: var(--border-light); }

/* ── Le pied : il porte l'avertissement, il doit donc se lire ─────────────── */
.jsb-picker-foot .jsb-picker-hint { color: var(--text-body); font-size: 0.82rem; }

/* ── Dispositions étroites ────────────────────────────────────────────────── */
@media (max-width: 767.98px) {
    .jsb-purge-tree { --purge-cran: 12px; }
    /* La rangée de pilules se replie d'elle-même (`flex-wrap` de
       `jsb-preset-filters-bar`) : rien à forcer ici. */
}

/* La rangee de pilules de cette boite tient sur UNE ligne : la colonne de gauche n'en
   fait que les deux tiers, et un repli y couperait la lecture entre le titre d'une
   pilule et ses chips. Si la fenetre devient vraiment etroite, elle defile plutot que
   de se replier — aucun geste n'est perdu.

   ⚠ C'EST LA SEULE REGLE QUE CETTE BOITE ECRIT SUR LA PILULE, et elle porte sur la
   RANGEE, pas sur les chips : ceux-ci sont exactement ceux de la liste des pistes, et
   les retoucher les desalignerait de toutes les rubriques. */
.jsb-purge-arbre > .jsb-preset-filters-bar {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
}
.jsb-purge-arbre > .jsb-preset-filters-bar::-webkit-scrollbar { display: none; }
.jsb-purge-arbre > .jsb-preset-filters-bar > .jsb-preset-filters { flex: 0 0 auto; }

/* ⚠ LA BARRE DU PICKER EST INDETERMINEE PAR DEFAUT : `.is-active` anime un `transform`
   qui glisse une echarpe de 40 %. Y poser une largeur en pourcentage ne montrait donc
   RIEN — les deux se battaient sur le meme pixel, et l'animation gagnait.

   `est-chiffree` coupe l'animation et rend la barre a sa largeur, comme le fait deja
   `.is-determinate` sur la barre de la boite de confirmation. Meme raison la-bas : un
   ruban qui glisse ET une largeur qui avance donnent deux informations contradictoires. */
.jsb-picker-progress.est-chiffree .jsb-picker-progress-bar {
    animation: none;
    transform: none;
    width: 0;
    transition: width .25s ease-out;
}

/* Une ligne deja supprimee reste VISIBLE, barree, avec le mot « supprime » a cote : la
   faire disparaitre effacerait la preuve de ce qu'on vient de faire, et c'est
   exactement ce que l'utilisateur vient lire. Gris de charte, jamais une opacite. */
.jsb-purge-noeud.est-parti > .jsb-purge-rangee { background: transparent; }
.jsb-purge-noeud.est-parti > .jsb-purge-rangee .jsb-purge-nom {
    text-decoration: line-through;
    color: var(--text-muted);
}
.jsb-purge-noeud.est-parti > .jsb-purge-rangee .jsb-purge-case::before {
    border-color: var(--border-medium);
    background: var(--bg-muted);
}
.jsb-purge-noeud.est-parti > .jsb-purge-rangee .jsb-purge-case::after { content: none; }
.jsb-purge-etat-ligne {
    flex: 0 0 auto;
    margin-left: auto;
    padding-left: 0.5rem;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--success);
    white-space: nowrap;
}
.jsb-purge-noeud.est-encours > .jsb-purge-rangee .jsb-purge-etat-ligne { color: var(--cobalt); }
.jsb-purge-noeud.est-echoue > .jsb-purge-rangee .jsb-purge-etat-ligne { color: var(--danger-dark); }

/* Le rouet d'une ligne en cours : il tourne pendant que le serveur travaille sur ce
   lot. Masque aux lecteurs d'ecran — c'est la mention « en cours » qui les informe. */
.jsb-purge-rouet {
    flex: 0 0 auto;
    width: 12px;
    height: 12px;
    margin-left: auto;
    border: 2px solid var(--bg-muted);
    border-top-color: var(--cobalt);
    border-radius: 50%;
    animation: jsbPurgeRouet .7s linear infinite;
}
@keyframes jsbPurgeRouet { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .jsb-purge-rouet { animation: none; border-top-color: var(--cobalt); }
}
.jsb-purge-rouet + .jsb-purge-etat-ligne { margin-left: 0.4rem; }

/* ══════════════════════════════════════════════════════════════════════════════
   VUE CARTES — le pipe commercial des Pistes (vue-cartes_controller).
   Une seule question : où en est chaque piste, et quelle est la prochaine action ?
   Jetons de la charte seulement ; la couleur d'une étape (`--etape-*`) est celle du chip
   et du marqueur de la vue Liste.
   ══════════════════════════════════════════════════════════════════════════════ */

/* LA VUE INACTIVE SORT DU RENDU (display:none), pas seulement de l'écran : un lecteur
   d'écran ne doit pas lire deux fois la même page.
   ⚠ Le chip « Statut » se masque par son ENVELOPPE : chips-defilement.js sort le titre du
   groupe dans `.jsb-preset-filters-wrap` — masquer le seul groupe laissait une pilule
   « Statut » vide. */
.is-vue-cartes .table-enhanced,
.is-vue-cartes .jsb-preset-filters-wrap:has([data-preset-criterion="__transformation_piste__"]),
.is-vue-cartes [data-preset-criterion="__transformation_piste__"] { display: none; }
.is-vue-liste .jsb-pipe { display: none; }

/* La bascule « Vue », à droite de la rangée des chips (enveloppée elle aussi). */
.jsb-preset-filters-wrap:has(.jsb-vue-bascule),
.jsb-vue-bascule { margin-left: auto; }

/* LA BARRE DE DÉFILEMENT HORIZONTALE EST TOUJOURS AU BAS DE LA ZONE — jamais sous la
   plus haute colonne. La rangée ne prenait que la hauteur de ses cartes : sa barre flottait
   juste sous elles, au milieu d'un grand vide, et remontait ou descendait avec leur nombre.
   En vue Cartes, la zone de la liste ne défile donc plus elle-même : elle est une colonne
   bornée que le pipe REMPLIT, et c'est la rangée des colonnes qui défile, sur les deux
   axes — la barre horizontale est ainsi posée au pied, et la verticale sur le côté.
   (`.d-none` de l'état vide garde la main : il est en !important.) */
.jsb-list-manager.is-vue-cartes > .table-scroll-wrapper {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.jsb-pipe {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 0.75rem;
    flex: 1 1 auto;
    min-height: 0;
    background: var(--bg-body, #f3f3f3);
}
/* La rangée des colonnes : SEULE elle défile (la page, jamais), et elle occupe toute la
   hauteur restante — sa barre horizontale est au pied de l'écran. */
.jsb-pipe__colonnes {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding-bottom: 0.25rem;
}
.jsb-pipe[aria-busy="true"] { opacity: 0.6; }

.jsb-pipe__colonne {
    flex: 1 0 270px;
    max-width: 380px;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    background: var(--bg-light);
    border: 1px solid var(--border-light);
    border-top: 3px solid var(--etape, var(--border-medium));
    border-radius: 10px;
    padding: 0.6rem;
}
.jsb-pipe__colonne--a_coter { --etape: var(--etape-a_coter); }
.jsb-pipe__colonne--en_cotation { --etape: var(--etape-en_cotation); }
.jsb-pipe__colonne--offres_chiffrees { --etape: var(--etape-offres_chiffrees); }
.jsb-pipe__colonne--transformees { --etape: var(--etape-transformees); }
/* LA COLONNE TERMINALE TIENT SON PIRE CAS sur une ligne : icône, « Transformées », un
   compteur à trois chiffres et « > 99 % ». À 170 px, la part passait à la ligne. */
.jsb-pipe__colonne--terminale { flex: 0 0 236px; }

.jsb-pipe__titre {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
    margin: 0;
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--text-dark);
}
/* UNE SEULE LIGNE, ET UN SEUL ÉLÉMENT QUI CÈDE : le libellé (ellipsis, nom complet dans
   l'infobulle). L'icône, le compteur et la part gardent leur taille — un chiffre coupé ou
   renvoyé à la ligne se lirait mal, ou pas du tout. */
.jsb-pipe__libelle { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.jsb-pipe__icone,
.jsb-pipe__titre > .jsb-onglet-compte,
.jsb-pipe__part { flex-shrink: 0; white-space: nowrap; }
.jsb-pipe__icone { color: var(--etape); display: inline-flex; }
.jsb-pipe__somme { margin: 0.15rem 0 0; font-size: 0.75rem; color: var(--text-muted); }
/* LA PART DE L'ÉTAPE : le chiffre, discret, à côté du compteur — et la même part en longueur,
   un filet à la couleur de l'étape. La couleur souligne ; le chiffre informe. */
.jsb-pipe__part { margin-left: auto; font-size: 0.75rem; font-weight: 600; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.jsb-pipe__jauge { display: block; height: 3px; margin-top: 0.35rem; border-radius: 999px; background: var(--border-light); overflow: hidden; }
.jsb-pipe__jauge > span { display: block; height: 100%; width: 0; background: var(--etape, var(--border-medium)); border-radius: inherit; transition: width 0.35s ease-out; }
@media (prefers-reduced-motion: reduce) { .jsb-pipe__jauge > span { transition: none; } }
.jsb-pipe__cartes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.jsb-pipe__vide,
.jsb-pipe__terminale { margin: 0; padding: 0.75rem; border: 1px dashed var(--border-medium); border-radius: 8px; font-size: 0.8rem; color: var(--text-muted); text-align: center; }
.jsb-pipe__vide[hidden] { display: none; }
.jsb-pipe__pied { margin: 0; font-size: 0.75rem; color: var(--text-muted); text-align: center; }
/* « VOIR LES TRANSFORMÉES », « VOIR DANS LA LISTE » : deux liens qui mènent à la vue
   Liste — d'où l'icône de la bascule « Liste », qui dit la destination avant le texte.
   Au repos, un lien (cobalt, soulignement fin) ; au survol, une pastille cobalt très
   claire et l'icône qui avance d'un pas : on va QUELQUE PART. Cible de 44 px (WCAG 2.5.5). */
.jsb-pipe__lien {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-height: 44px;
    padding: 0.25rem 0.5rem;
    background: transparent;
    border: 0;
    border-radius: 6px;
    color: var(--cobalt);
    font-size: 0.8rem;
    font-weight: 600;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    cursor: pointer;
    transition: background-color 0.15s ease-out, color 0.15s ease-out;
}
.jsb-pipe__lien-icone { display: inline-flex; transition: transform 0.15s ease-out; }
/* Dans la colonne terminale, le lien se lit sur le MÊME AXE que le texte centré au-dessus. */
.jsb-pipe__colonne--terminale > .jsb-pipe__lien { align-self: center; }
.jsb-pipe__lien:hover { background: var(--bg-cobalt-subtle); color: var(--cobalt-dark); }
.jsb-pipe__lien:hover .jsb-pipe__lien-icone { transform: translateX(2px); }
.jsb-pipe__lien:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--cobalt-focus-ring); }
@media (prefers-reduced-motion: reduce) {
    .jsb-pipe__lien, .jsb-pipe__lien-icone { transition: none; }
    .jsb-pipe__lien:hover .jsb-pipe__lien-icone { transform: none; }
}
.jsb-pipe__clotures {
    flex: 0 0 auto; /* la rangée qui défile cède la place, pas le pied */
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    background: var(--bg-white);
    border: 1px solid var(--border-light);
    border-radius: 8px;
    font-size: 0.8rem;
    color: var(--text-body);
}

/* ── La carte : quatre lignes, hauteur constante ─────────────────────────────── */
.jsb-carte {
    display: grid;
    grid-template-rows: repeat(4, auto);
    gap: 0.3rem;
    padding: 0.6rem 0.7rem 0.6rem 0.85rem;
    background: var(--bg-white);
    border: 1px solid var(--border-light);
    border-radius: 8px;
    box-shadow: inset 4px 0 0 var(--etape), var(--shadow-sm);
    cursor: pointer;
    transition: box-shadow 0.15s ease, border-color 0.15s ease;
}
.jsb-carte--a_coter { --etape: var(--etape-a_coter); }
.jsb-carte--en_cotation { --etape: var(--etape-en_cotation); }
.jsb-carte--offres_chiffrees { --etape: var(--etape-offres_chiffrees); }
.jsb-carte--transformees { --etape: var(--etape-transformees); }
.jsb-carte:hover { border-color: var(--cobalt); box-shadow: inset 4px 0 0 var(--etape), var(--shadow-md); }
.jsb-carte:focus-visible { outline: none; border-color: var(--cobalt); box-shadow: inset 4px 0 0 var(--etape), 0 0 0 3px var(--cobalt-focus-ring); }
.jsb-carte.is-selected { background: var(--bg-cobalt-subtle); border-color: var(--cobalt); }
.jsb-carte.is-cloturee { opacity: 0.75; }

.jsb-carte__ligne { display: flex; align-items: center; gap: 0.4rem; min-width: 0; font-size: 0.8rem; color: var(--text-body); }
.jsb-carte__case { flex: 0 0 auto; margin: 0; }
.jsb-carte__titre {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--text-dark);
}
.jsb-carte__ligne--contexte { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--text-muted); }
.jsb-carte__etiquette { color: var(--text-muted); }
.jsb-carte__montant { margin-left: auto; font-weight: 700; color: var(--text-dark); }
.jsb-carte__unite { font-weight: 400; color: var(--text-muted); }

/* Ancienneté de la dernière activité : neutre, à surveiller, en retard — paires fond/texte
   de la charte. Le nombre de jours est écrit : la couleur ne fait que le souligner.
   Les offres reçues prennent la même pastille, neutre : c'est un compte, pas une alerte. */
.jsb-carte__offres,
.jsb-carte__age {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    background: var(--bg-light);
    color: var(--text-muted);
}
.jsb-carte__age--surveiller { background: #fff3cd; color: #664d03; }
.jsb-carte__age--retard { background: #f8d7da; color: #842029; }

.jsb-carte__avatar {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--bg-cobalt-subtle);
    color: var(--cobalt);
    font-size: 0.65rem;
    font-weight: 700;
}
.jsb-carte__responsable { flex: 1 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }

/* L'ACTION PRIMAIRE, TOUJOURS VISIBLE (le tactile n'a pas de survol), et assez grande
   pour un doigt (WCAG 2.5.8). */
.jsb-carte__action {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    min-height: 32px;
    padding: 0.25rem 0.6rem;
    border: 1px solid var(--cobalt);
    border-radius: 6px;
    background: var(--bg-white);
    color: var(--cobalt);
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
}
.jsb-carte__action:hover { background: var(--cobalt); color: var(--text-white); }
.jsb-carte__action:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--cobalt-focus-ring); }
.jsb-carte__cloturee {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.1rem 0.45rem;
    border-radius: 999px;
    background: var(--bg-muted);
    color: var(--text-body);
    font-size: 0.7rem;
    font-weight: 600;
}

/* Marqueur « Clôturée » de la vue Liste (badge d'état). */
.jsb-state-badge--cloturee { background: var(--bg-muted); color: var(--text-body); }

/* ÉCRAN ÉTROIT : une colonne à la fois, qui s'aimante au défilement horizontal. */
@media (max-width: 768px) {
    .jsb-pipe__colonnes { scroll-snap-type: x mandatory; }
    .jsb-pipe__colonne { flex: 0 0 85%; max-width: none; scroll-snap-align: start; }
    .jsb-pipe__colonne--terminale { flex: 0 0 60%; }
}

@media (prefers-reduced-motion: reduce) {
    .jsb-carte { transition: none; }
}

/* ── LE SIGNAL DU REMPLISSAGE AUTOMATIQUE (chantier 16) ──────────────────────
   Le navigateur qui remplit un champ de recherche (:autofill) déclenche cette animation
   invisible ; assets/champs-de-recherche.js l'entend (animationstart) et vide le champ.
   Une valeur posée par l'application n'en déclenche aucune : elle survit. Deux règles
   séparées : un sélecteur inconnu invaliderait toute une liste. Aucune propriété animée,
   donc rien à couper pour prefers-reduced-motion. */
@keyframes jsb-remplissage-auto { from { outline-color: inherit; } to { outline-color: inherit; } }
input[data-champ-recherche]:-webkit-autofill { animation: jsb-remplissage-auto 1ms; }
input[data-champ-recherche]:autofill { animation: jsb-remplissage-auto 1ms; }
