/* of-commun.css — règles transversales de l'application, ÉCRITES À LA MAIN.
 *
 * Pourquoi ce fichier : les deux autres feuilles globales sont ENGENDRÉES — of-app.css par
 * tools/purge_css.php, of-icones.css par tools/subset_icones.js — et toute retouche y serait
 * effacée à la régénération suivante. Faute d'endroit où poser une correction transversale,
 * chaque module recopiait la sienne dans son propre CSS ; c'est ainsi que le même remède s'est
 * retrouvé dupliqué dans app/contacts, app/finance et app/temps, avec des valeurs qui ont
 * divergé. Les corrections qui valent pour TOUTE l'application vont désormais ici.
 *
 * Chargé après of-app.css (cf. include/utils/head.inc.php) : les règles ci-dessous priment donc
 * sur le thème sans avoir besoin de !important.
 */

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   Select2 — bouton « × » (allowClear) chevauchant la flèche d'ouverture
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Select2 fait FLOTTER la croix (float:right, margin-right:20px, height:26px), valeurs prévues
   pour son habillage d'origine haut de 28 px. Le thème redimensionne le champ à ~38 px et élargit
   la flèche à 34 px : la croix retombe alors PAR-DESSUS la flèche. Le clic atterrit sur la flèche
   et rouvre la liste au lieu de vider le champ — l'utilisateur ne peut littéralement pas effacer
   sa sélection.

   ARITHMÉTIQUE (à refaire si le thème change ces valeurs) :
     · flèche : right:3px, largeur 34px  → elle occupe la bande [3px, 37px] depuis le bord droit ;
     · croix  : posée juste au-delà, à right:38px, sur une zone cliquable de 22px ;
     · texte  : repoussé au-delà des deux, soit 38 + 22 = 60px.

   ⚠️ Les correctifs locaux qui ont précédé ce fichier utilisaient right:26px — soit 11px SOUS la
   flèche. Ils atténuaient le défaut sans le supprimer : la zone restait partiellement couverte.
   Ne pas revenir à cette valeur en la croyant éprouvée. */

.select2-container--default .select2-selection--single .select2-selection__clear {
    position: absolute;
    right: 38px;
    top: 0;
    height: 100%;
    width: 22px;
    margin: 0;
    padding: 0;
    float: none;                 /* annule le float d'origine, source du chevauchement */
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.15em;
    font-weight: 400;
    line-height: 1;
    color: var(--ct-secondary-color);
    cursor: pointer;
    z-index: 3;                  /* au-dessus de la flèche : le clic vise bien la croix */
}
.select2-container--default .select2-selection--single .select2-selection__clear:hover {
    color: var(--ct-danger, #fa5c7c);
}

/* Le libellé sélectionné doit s'arrêter AVANT la croix et la flèche, sinon un nom long passe
   dessous et les rend illisibles.
   Le réservoir n'est creusé QUE lorsqu'une croix existe : select2 pose lui-même la classe
   « select2-selection--clearable » sur le conteneur quand il en insère une, et la retire à la
   remise à zéro. S'appuyer dessus évite d'indenter inutilement les sélecteurs sans allowClear,
   et de faire sauter le texte au moment de la sélection. */
.select2-container--default .select2-selection--single.select2-selection--clearable .select2-selection__rendered {
    padding-right: 60px;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   Select2 — état DÉSACTIVÉ illisible en thème sombre
   ═══════════════════════════════════════════════════════════════════════════════════════════
   `select2.min.css` code en dur le fond du champ désactivé :

       .select2-container--default.select2-container--disabled .select2-selection--single
       { background-color: #eee }

   Le thème habille bien le select2 ACTIF (of-app.css le repeint en var(--ct-input-bg)), mais il
   n'a aucune règle pour l'état désactivé — et celle du vendeur a une spécificité de (0,3,0),
   supérieure à la sienne (0,2,0). Résultat en sombre : une boîte quasi blanche portant un texte
   clair (--ct-body-color = #aab8c5), soit ~1,6:1. Le libellé disparaît purement et simplement.

   Vu sur le sélecteur « Dossier » d'une pièce VALIDÉE (le module Financier désactive tout l'en-tête
   dès la validation), mais le défaut n'a rien de local : il touche CHAQUE select2 désactivé de
   l'application. La correction est donc ici, pas dans le CSS du Financier.

   Les valeurs reprennent celles de Bootstrap pour un champ désactivé (--ct-tertiary-bg), pour
   qu'un select2 grisé ressemble à l'`<input>` grisé posé juste à côté de lui.
   ⚠️ Reproduire la spécificité du vendeur (les deux classes sur le conteneur) : un sélecteur plus
   court perdrait, et l'ordre de chargement n'y changerait rien. */
.select2-container--default.select2-container--disabled .select2-selection--single,
.select2-container--default.select2-container--disabled .select2-selection--multiple {
    background-color: var(--ct-tertiary-bg);
    border-color: var(--ct-border-color);
}
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__rendered {
    color: var(--ct-body-color);
}
/* Un champ désactivé n'est pas un champ vide : sa flèche reste dessinée, mais en encre atténuée
   pour dire « non modifiable » plutôt que « rien à ouvrir ». */
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__arrow b {
    border-top-color: var(--ct-secondary-color);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   Zone de recherche — bouton d'effacement
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Forme retenue dans l'application : un input-group, l'input, puis un bouton « × » masqué tant
   que le champ est vide (Agenda, Documents, Emails). La classe ci-dessous n'impose rien de plus
   qu'un alignement propre du glyphe, pour que les zones se ressemblent d'un module à l'autre. */
.of-search-clear {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   Badge d'initiales — repli quand l'utilisateur n'a pas de photo
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Le badge réutilise les classes `.avatar-xs|sm|md` du thème, qui ne fixent QUE des dimensions
   (`.avatar-xs` = 1,5rem). Le texte héritait donc de la police du contexte — 1rem — et débordait
   d'un cercle de 24 px. Constaté sur l'agenda de la démonstration le 2026-07-28, où aucun
   utilisateur n'a de photo : toutes les initiales dépassaient de leur pastille.

   La taille est fixée ICI, une fois, et non en ligne dans les cinq endroits qui construisent un
   badge — c'est précisément la duplication qui avait laissé le défaut passer partout à la fois. */
.of-initials {
    font-weight: 700;
    line-height: 1;
    overflow: hidden;
    flex: 0 0 auto;          /* dans un flex, le cercle ne doit ni s'étirer ni se comprimer */
}
.of-initials.avatar-xs { font-size: .5625rem; }   /*  9px dans 24px */
.of-initials.avatar-sm { font-size: .75rem;   }   /* 12px dans 36px */
.of-initials.avatar-md { font-size: 1rem;     }   /* 16px dans 48px */
.of-initials.avatar-lg { font-size: 1.25rem;  }

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   Menu de gauche — il reste en place, y compris en mode « Condensé »
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Le thème pose `position: fixed` sur `.leftside-menu`… puis la repasse à `absolute` pour le seul
   mode Condensé. Un élément absolu se positionne par rapport au document, pas à la fenêtre : la
   barre prenait donc toute la HAUTEUR DE LA PAGE (mesuré : 7167 px sur la liste des contacts, pour
   637 px d'entrées de menu) et s'en allait vers le haut dès qu'on défilait. On se retrouvait à
   regarder le vide sous les entrées disparues — signalé sur les Contacts et les Paramètres
   généraux, les deux écrans assez longs pour que ça se voie.

   Les deux autres modes (Survol, Épinglé) n'ont jamais eu le défaut : ils restent en `fixed`. Le
   correctif rétablit donc simplement la règle commune, sans rien changer d'autre — largeur, survol
   qui déplie, transitions : tout vient d'ailleurs.

   La condition sur `data-layout-position` n'est pas décorative : elle donne la priorité voulue sans
   `!important`, et elle laisse le thème décider s'il existe un jour une page en mise en page
   « scrollable », où une barre qui suit le document est cette fois le comportement attendu. */
html[data-layout-position="fixed"][data-sidenav-size="condensed"]:not([data-layout="topnav"]) .wrapper .leftside-menu {
    position: fixed;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   … et il défile quand il ne tient pas dans la fenêtre, sans ascenseur visible
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Une fois la barre fixée (règle ci-dessus), sa hauteur est celle de la fenêtre : sous ~640 px,
   les dernières entrées passaient sous le bord, hors d'atteinte. Les modes Survol et Épinglé, eux,
   défilaient déjà — le thème coupe le défilement du seul mode Condensé (`overflow: visible` forcé
   sur les conteneurs SimpleBar), afin que le libellé déplié au survol puisse déborder d'une barre
   large de 70 px.

   Le rendre défilable rouvre donc ce conflit : un bloc qui défile rogne son contenu sur les DEUX
   axes — on regagnait les entrées du bas en perdant les libellés, c'est-à-dire la seule façon de
   lire ce menu réduit à des icônes.

   D'où le détour : ce n'est pas le défilement qu'on refuse, c'est le rognage qu'on déplace. Le
   conteneur qui défile prend la largeur du libellé DÉPLIÉ (260 px) au lieu de celle de la barre —
   la coupure tombe alors exactement là où le libellé s'arrête, donc nulle part. La barre, elle,
   reste large de 70 px : seul un conteneur invisible dépasse, et `pointer-events` l'empêche de
   capter le moindre clic destiné à la page. Le contenu reprend les 70 px de la barre pour que la
   molette et les clics fonctionnent au-dessus des icônes.

   Mesuré : 95 px d'entrées regagnés à 560 px de fenêtre, aucune barre de défilement (ni native —
   SimpleBar pose déjà `scrollbar-width: none` — ni la sienne propre), libellé « Appels
   Téléphoniques » affiché en entier, et un clic à 100 px du bord atteint bien le tableau derrière.

   ⚠️ `max-width: 100%` est posé par SimpleBar sur le conteneur : sans le neutraliser, la largeur
   demandée est plafonnée à celle de la barre et tout le reste ne sert à rien — en silence. */
html[data-sidenav-size="condensed"]:not([data-layout="topnav"]) .wrapper .leftside-menu .simplebar-mask,
html[data-sidenav-size="condensed"]:not([data-layout="topnav"]) .wrapper .leftside-menu .simplebar-offset,
html[data-sidenav-size="condensed"]:not([data-layout="topnav"]) .wrapper .leftside-menu .simplebar-content-wrapper {
    width: var(--ct-leftbar-width) !important;
    max-width: none !important;
    pointer-events: none;
}
html[data-sidenav-size="condensed"]:not([data-layout="topnav"]) .wrapper .leftside-menu .simplebar-content-wrapper {
    overflow-y: auto !important;
    overflow-x: hidden !important;
}
html[data-sidenav-size="condensed"]:not([data-layout="topnav"]) .wrapper .leftside-menu .simplebar-content {
    width: var(--ct-leftbar-width-sm);
    pointer-events: auto;
}
/* La barre de SimpleBar est masquée : le menu défile, mais rien ne l'annonce visuellement — c'est
   le parti pris du thème pour les autres modes, on ne le change pas ici. */
html[data-sidenav-size="condensed"]:not([data-layout="topnav"]) .wrapper .leftside-menu .simplebar-scrollbar {
    display: none !important;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   Menu de gauche — la couleur du module, en pastille derrière le glyphe
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Chaque entrée déclare sa teinte (`--of-nav-teinte`, style en ligne dans include/shell/nav.inc.php,
   là où vivait déjà la couleur du module). Ce qui la portait avant : un `border-left: 5px solid`
   posé sur le glyphe lui-même — un trait COMPRIS dans la boîte de l'icône (`box-sizing: border-box`),
   qui lui prenait 5 px de large et poussait le dessin vers la droite. En mode Condensé, où le thème
   passe l'icône en `display: block`, ce même trait se retrouvait plaqué au bord gauche de la rangée,
   détaché du glyphe qu'il était censé qualifier.

   ⚠️ POURQUOI LA COULEUR NE TOUCHE PAS LE GLYPHE — c'était la piste évidente, la mesure l'a écartée.
   Le menu est peint d'un dégradé indigo (`data-menu-color="brand"`, #6379c3 → #546ee5) sur TOUTES
   les pages, thème clair comme sombre. Contraste d'un glyphe teinté sur ce fond, mélange 62 %
   teinte / 38 % blanc, mesuré sur les onze modules : de 1,27:1 (Notes de frais) à 2,76:1 (Temps
   passé). Le blanc pur y tient 4,16, le libellé du thème (#cedce4) 2,96. Teinter le symbole, c'est
   donc le rendre moins lisible qu'aujourd'hui — sur Contacts, Notes de frais et Financier, une
   icône qu'on devine. La teinte passe derrière ; le glyphe garde le blanc.

   ⚠️ POURQUOI LA PASTILLE EST ASSOMBRIE DE 15 % avant d'être posée. Quatre teintes de la palette
   sont trop claires pour porter un glyphe blanc à pleine couleur : Agenda 2,43:1, Dossiers 2,15,
   Tâches 2,54, Temps passé 2,00 — et c'est justement à pleine couleur que s'affiche la pastille de
   l'entrée COURANTE. `color-mix(… 85 %, #000)` remonte les onze modules à 3,08:1 au minimum en
   actif et 3,41:1 au repos, sans rien retirer à la couleur (l'œil lit toujours le rouge d'Emails,
   le cyan de l'Agenda). C'est une correction UNIFORME : pas d'exception module par module, donc
   rien à refaire quand une teinte change ou qu'un module s'ajoute.

   ⚠️ La pastille est un PSEUDO-ÉLÉMENT hors flux, pas une boîte redimensionnée. Le thème donne à
   l'icône trois géométries selon le mode du menu (45 px en Survol/Épinglé, 50 px en Condensé,
   `width: auto` + `display: block` en Compact) ; toucher à sa largeur aurait décalé la pastille
   hors du centre dans deux modes sur trois — le mode Survol REPLIÉ, celui de tout le monde, laisse
   l'icône à 45 px dans une barre de 70. Centrée sur la boîte quelle qu'en soit la taille, la
   pastille suit les trois modes sans qu'aucun ait à être traité à part.

   🔴 05/10/2026 (lot D de l'audit d'uniformité) — pleine couleur, sur le dégradé indigo du thème.
   🔴 07/10/2026 — piste « Ardoise » : le menu et ses pastilles étaient trop vifs pour une journée entière sur
   OrgaFlex. Le fond indigo du thème (`--ct-menu-bg` de `data-menu-color="brand"`) est remplacé par une ardoise
   gris-bleu, voisine de la barre du haut en sombre ; la pastille n'est plus la teinte pure mais un MÉLANGE avec ce
   fond — 52 % au repos, 78 % pour l'entrée courante, 66 % au survol. Les variables `--color-<m>-fond` ne bougent
   pas : rubans, badges et pages gardent leurs couleurs, seul le menu les adoucit. Glyphe blanc mesuré sur les
   pastilles mélangées : 4,1:1 au pire au repos, 4,8 en actif (hors teintes claires).
   Les teintes CLAIRES (Dossiers en jaune « dossier Windows », Temps passé) ne survivent pas au mélange à 52 % : le
   jaune y tourne au kaki. Elles déclarent une dose plus forte (`--of-nav-dose`, `--of-nav-dose-active`) et une
   encre sombre (`--of-nav-encre`), comme un vrai dossier jaune — glyphe sombre à 5,4:1 au repos, 8,1 en actif.
   Le jaune est la couleur des Dossiers PARTOUT depuis le 07/10 (`--color-dossiers-fond`, texte sombre `--color-dossiers-text`). */
html[data-menu-color=brand] {
    --of-menu-fond: #242a33;
    --ct-menu-bg: var(--of-menu-fond);
    --ct-menu-condensed-link-bg: #353d4a;   /* bandeau du libellé au survol, menu replié : #727cf5 du thème jurait */
}
.side-nav .side-nav-link i {
    --of-nav-pastille: color-mix(in oklab, var(--of-nav-teinte, transparent) calc(var(--of-nav-dose, .52) * 100%), var(--of-menu-fond, #242a33));
    position: relative;
    z-index: 0;                  /* crée le contexte d'empilement qui garde la pastille DESSOUS */
    color: var(--of-nav-encre, #e8ecf2);   /* le glyphe ne prend jamais la teinte */
}
.side-nav .side-nav-link i::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 32px;
    height: 32px;
    transform: translate(-50%, -50%);
    border-radius: 9px;
    background: var(--of-nav-pastille);
    z-index: -1;
    transition: background-color .18s ease-in-out, box-shadow .18s ease-in-out;
}
/* Une entrée sans teinte déclarée n'a pas de pastille du tout — sans ce garde-fou, le mélange
   avec `transparent` lui en dessinerait une grise, visible et incompréhensible. */
.side-nav .side-nav-link i:not([style*="--of-nav-teinte"])::after {
    display: none;
}
.side-nav .side-nav-link:hover i {
    --of-nav-pastille: color-mix(in oklab, var(--of-nav-teinte, transparent) calc((var(--of-nav-dose, .52) + .14) * 100%), var(--of-menu-fond, #242a33));
}

/* ── L'entrée courante ────────────────────────────────────────────────────────────────────────
   Le thème stylise `.menuitem-active`, mais c'est `.active` que pose include/shell/scripts.inc.php,
   sur le <li>. La pastille du module ouvert prend une dose plus franche, un liseré clair, et la rangée
   un voile — deux signaux, l'un coloré, l'autre de forme, pour ceux qui ne distinguent pas les teintes. */
.side-nav .side-nav-item.active > .side-nav-link {
    background: rgba(255, 255, 255, .07);
    color: var(--ct-menu-item-active-color);
    font-weight: 600;
}
.side-nav .side-nav-item.active > .side-nav-link i {
    --of-nav-pastille: color-mix(in oklab, var(--of-nav-teinte, transparent) calc(var(--of-nav-dose-active, .78) * 100%), var(--of-menu-fond, #242a33));
}
.side-nav .side-nav-item.active > .side-nav-link i::after {
    box-shadow: 0 0 0 1.5px rgba(255, 255, 255, .6);
}
/* Une sous-page (« Devis », « Vue Kanban »…) marque le <li> de SECOND niveau : sans cette règle,
   le module ouvert n'était signalé nulle part au premier niveau. `:has()` est isolé dans son propre
   bloc — glissé dans la liste ci-dessus, un navigateur qui ne le connaît pas invaliderait aussi les
   sélecteurs valides qui l'accompagnent. */
.side-nav .side-nav-item:has(> .collapse .active) > .side-nav-link {
    color: var(--ct-menu-item-active-color);
}
.side-nav .side-nav-item:has(> .collapse .active) > .side-nav-link i {
    --of-nav-pastille: color-mix(in oklab, var(--of-nav-teinte, transparent) calc(var(--of-nav-dose-active, .78) * 100%), var(--of-menu-fond, #242a33));
}
.side-nav .side-nav-item:has(> .collapse .active) > .side-nav-link i::after {
    box-shadow: 0 0 0 1.5px rgba(255, 255, 255, .6);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   Actions de ligne — les gestes courants sortis du menu « … »
   ═══════════════════════════════════════════════════════════════════════════════════════════
   Une ou deux actions par ligne se répètent toute la journée (consulter, ouvrir) ; les autres se
   CHOISISSENT (créer une tâche, convertir, supprimer). Les premières sont donc rendues en icônes
   devant le menu, qui garde les secondes. Contacts et Documents partagent ces règles : le même
   remède écrit deux fois aurait divergé, c'est déjà arrivé dans ce projet.

   ⚠️ La teinte BRUTE d'un module ne convient pas. Mesuré sur le fond réel des lignes, en thème
   sombre : le violet des Contacts tombe à 2,02:1 et le gris `text-secondary` à 2,45:1 — sous le
   seuil de 3:1 exigé d'un élément graphique, d'où des icônes qu'on devine. C'est la teinte qu'il
   faut corriger, pas l'opacité : assombrir de 15 % en thème clair, éclaircir de 35 % en sombre,
   exactement comme les filets des blocs de la fiche contact.

   Chaque icône déclare sa teinte de base dans `--of-act` ; la correction est faite ici, une fois.
   Le « … » du menu reçoit le même traitement : il faisait partie du même groupe visuel tout en
   restant sur le gris du thème, à 2,45:1. */
.of-row-actions {
    --of-act: #64748B;                       /* ardoise par défaut : jamais d'action sans teinte */
}
.of-row-actions .of-act {
    cursor: pointer;
    line-height: 1;                          /* n'étire pas la ligne du tableau */
}
.of-row-actions .of-act > i,
.of-row-actions .card-drop > i {
    color: color-mix(in srgb, var(--of-act) 85%, black);
    transition: transform .12s ease, filter .12s ease;
}
[data-bs-theme="dark"] .of-row-actions .of-act > i,
[data-bs-theme="dark"] .of-row-actions .card-drop > i {
    color: color-mix(in srgb, var(--of-act) 65%, white);
}
/* Place réservée d'une action absente : même encombrement, invisible, inerte — les colonnes d'icônes restent alignées. */
.of-row-actions .of-act-vide { visibility: hidden; pointer-events: none; cursor: default; }
/* Pièce comptable : un crayon barré à la place de « Ouvrir » (lecture seule pour tous) — il informe, il ne s'actionne pas. */
.of-row-actions .of-act-verrou { cursor: help; color: var(--ct-secondary-color); }
/* Sans libellé ni bordure, le survol est le seul retour dont ces icônes disposent. */
.of-row-actions .of-act:hover > i {
    transform: scale(1.18);
    filter: brightness(1.15);
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
 * BADGE NEUTRE ET REGROUPEMENT — lisibles dans les deux thèmes
 *
 * `badge text-bg-light` de Bootstrap fige sa couleur de TEXTE à la compilation, pour le thème
 * clair. En thème sombre le fond suit le thème, pas le texte : mesuré dans la bibliothèque de
 * modèles, rgb(49,58,70) sur rgb(70,79,91) — un contraste de 1,4:1 là où il en faut 4,5. Les badges
 * de nature et de catégorie y étaient illisibles.
 *
 * ⚠️ NE PAS employer les variables `--bs-*` ici : ce thème ne les définit pas. Un
 * `var(--bs-tertiary-bg)` se résout en valeur invalide, donc en fond TRANSPARENT — la règle paraît
 * écrite et ne s'applique pas, en silence. Les variables du thème portent le préfixe `--ct-`.
 *
 * `--ct-dark` est la bonne encre : elle S'INVERSE d'un thème à l'autre (#313a46 en clair, #f1f1f1
 * en sombre). `--ct-body-color` et `--ct-emphasis-color`, eux, restent gris dans les deux et
 * donnaient moins de 3:1 sur un fond tertiaire.
 * ═════════════════════════════════════════════════════════════════════════════════════════════ */
.of-badge {
    background-color: var(--ct-tertiary-bg);
    color: var(--ct-dark);
    border: 1px solid var(--ct-border-color);
    font-weight: 500;
}

/* ══ Centrage VERTICAL du libellé des badges ══════════════════════════════════════════════════
   🔴 SIGNALÉ LE 2026-09-25 : « les libellés sont rarement centrés verticalement dans les badges ».
   MESURÉ dans Chrome sur 18 écrans (≈ 400 badges) : le texte d'un `.badge` était partout TROP HAUT
   — 1,4 px pour les capitales, 0,4 px pour les minuscules. Deux causes :

   1. `line-height: 1` + padding SYMÉTRIQUE (Bootstrap) avec Nunito, dont l'ascendant (1,01 em)
      l'emporte largement sur le descendant (0,35 em) : la ligne de base tombe bas dans la boîte,
      et le texte, lui, remonte. On déplace donc 0,09 em de padding du bas vers le haut : la
      HAUTEUR du badge ne change pas, le texte descend d'un pixel — capitales et minuscules se
      répartissent alors de part et d'autre du centre (−0,4 / +0,6 px), là où l'œil le cherche.
   2. Dans une ligne FLEX ou une grille, le badge est un élément étiré (`align-self: stretch` par
      défaut) : il prend toute la hauteur de la ligne, son texte reste collé en haut, et deux
      lignes voisines se touchent — les badges paraissent empilés (fiche « Par personne »).
      `align-self: center` lui rend sa hauteur propre, centrée sur la ligne.

   ⚠️ Les utilitaires de padding (`py-1`, `p-0`…) restent maîtres : ils sont en !important. */
.badge {
    padding-top: calc(var(--ct-badge-padding-y) + .09em);
    padding-bottom: calc(var(--ct-badge-padding-y) - .09em);
    align-self: center;
}

/* Variante TEINTÉE : même mécanique, mais la couleur se choisit au point d'usage via
   `--of-badge-teinte`, typiquement une couleur de module (`--color-e-mails`, `--color-dossiers`…).
   Sert à distinguer d'un coup d'œil des étiquettes de natures différentes posées côte à côte —
   compte et dossier sur une ligne de résultat de recherche, par exemple — sans rendre la liste
   bariolée : seul un fond très dilué porte la couleur, l'encre reste celle du thème.

   ⚠️ `--ct-dark` et non une encre figée : c'est la seule encre du thème qui s'inverse entre clair
   et sombre. Une couleur en dur ici donnerait un badge illisible dans l'un des deux — le piège que
   `badge bg-light` avait déjà tendu (1,4:1 en sombre).

   Usage : <span class="badge of-badge-teinte" style="--of-badge-teinte: var(--color-e-mails)"> */
.of-badge-teinte {
    background-color: color-mix(in srgb, var(--of-badge-teinte, var(--ct-info)) 18%, var(--ct-secondary-bg));
    color: var(--ct-dark);
    border: 1px solid color-mix(in srgb, var(--of-badge-teinte, var(--ct-info)) 45%, transparent);
    font-weight: 500;
}

/* Étiquette du DOSSIER d'un email (réception, envoyés…) sur les listes qui en mêlent plusieurs :
   même mécanique, teinte un peu plus soutenue et icône à la couleur pleine — ici la couleur n'est
   pas une nuance d'ambiance, c'est ce qui permet de distinguer « Boîte de réception » d'« Envoyés »
   sans lire. L'encre du texte reste celle du thème. */
.of-badge-teinte.ei-badge-dossier {
    background-color: color-mix(in srgb, var(--of-badge-teinte, var(--ct-info)) 24%, var(--ct-secondary-bg));
}
.of-badge-teinte.ei-badge-dossier > .mdi { color: var(--of-badge-teinte); }

/* Variante « portée » : même mécanique, teinte d'accent, pour que la visibilité se lise d'un coup
   d'œil sans écraser le nom du modèle. */
.of-badge-portee {
    background-color: color-mix(in srgb, var(--ct-info) 18%, var(--ct-secondary-bg));
    color: var(--ct-dark);
    border: 1px solid color-mix(in srgb, var(--ct-info) 45%, transparent);
    font-weight: 500;
}

/* Marqueur de portée dans une LISTE (cadenas devant le nom d'un document restreint). Un badge à
   texte y serait de trop : on ne signale ici que l'exception, et sur des lignes qui tronquent déjà
   leur contenu. `--ct-` et non `--bs-` : le thème ne définit pas les secondes, la règle paraîtrait
   écrite sans jamais s'appliquer.

   ⚠️ DEUX teintes, une par thème, et ce n'est pas une coquetterie : `--ct-info` n'est PAS redéfini
   en sombre, si bien que la même teinte donne 2,55:1 sur fond clair — sous le seuil de 3:1 d'un
   élément graphique — et 4,51:1 sur fond sombre. La foncer suffisamment pour le clair (4,9:1) la
   rendrait illisible en sombre (2,35:1). Mesuré, pas estimé. */
.of-portee-restreint {
    color: color-mix(in srgb, var(--ct-info) 70%, black);
    vertical-align: -0.05em;
}

[data-bs-theme="dark"] .of-portee-restreint {
    color: var(--ct-info);
}

/* ── Encre sur un fond de COULEUR PLEINE (badges, toasts) ──────────────────────────────────────
 *
 * 🔴 SIGNALÉ LE 2026-09-24 : le badge « Administrateur » de la liste des utilisateurs, blanc sur
 * ambre, illisible. Il demandait pourtant la bonne chose — `badge bg-warning text-dark`.
 *
 * Le piège : `text-dark` ne veut pas dire « encre foncée ». Il veut dire « la couleur NOMMÉE dark
 * du thème », et le thème sombre l'inverse : `--ct-dark-rgb` y vaut 241,241,241. Sur un fond
 * `bg-warning` — qui, lui, ne s'inverse pas — cela donne du blanc cassé sur du jaune : 1,41:1.
 * Le même piège frappe l'autre sens, et depuis plus longtemps encore : `.badge` pose `color:#fff`
 * par défaut, si bien qu'un `badge bg-success` NU — il y en a partout — est blanc sur vert clair,
 * soit 2,02:1, et `badge bg-info` 2,55:1. Dans les deux thèmes, depuis toujours.
 *
 * La règle : sur un fond de couleur pleine qui ne suit pas le thème, l'encre ne doit pas le suivre
 * non plus. Elle se choisit sur la clarté du FOND. Les trois teintes ci-dessous sont claires, donc
 * l'encre y est foncée et FIXE : 9,7:1 sur ambre, 7,63:1 sur vert, 6,04:1 sur cyan.
 *
 * ⚠️ Ne vaut QUE pour les fonds pleins clairs. Un fond qui suit le thème (`bg-light`, ou la
 * mécanique teintée de `.of-badge-teinte` plus haut) doit au contraire garder une encre de thème —
 * les deux s'inversent ensemble, et une encre figée y casserait l'un des deux thèmes.
 *
 * ⚠️ Mesuré, pas estimé : tests/verifier_contraste_couleurs.php CALCULE ces rapports (WCAG 2.1) et
 * refait le calcul à chaque passage, y compris si une teinte du thème change. */
.badge.bg-warning, .bg-warning.text-dark, .bg-warning.text-white, .text-bg-warning,
.badge.bg-success, .bg-success.text-dark, .bg-success.text-white, .text-bg-success,
.badge.bg-info,    .bg-info.text-dark,    .bg-info.text-white,    .text-bg-info {
    color: #212529 !important;
}

/* ── En-tête de groupe (regroupement par catégorie, vues cartes et table) ───────────────────────
 *
 * C'est un BOUTON, pas un simple titre : cliquer replie le groupe. Il doit donc se voir comme tel —
 * un liseré coloré, un fond teinté et un chevron. Un simple filet gris sous un texte en capitales se
 * lisait comme une décoration, et personne n'aurait pensé à cliquer dessus.
 *
 * La teinte vient de `--of-groupe-couleur`, que chaque page pose selon son module (défaut : la
 * couleur des Documents). Le fond est calculé par `color-mix` sur le fond du thème, donc il suit le
 * clair comme le sombre sans qu'on ait à écrire deux règles. */
.of-groupe-titre {
    --of-groupe-couleur: var(--color-documents, #1E3A8A);
    display: flex;
    align-items: center;
    gap: .5rem;
    width: 100%;
    font-size: .82rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--ct-dark);
    background-color: color-mix(in srgb, var(--of-groupe-couleur) 12%, var(--ct-secondary-bg));
    border: 0;
    border-left: 3px solid var(--of-groupe-couleur);
    border-radius: .25rem;
    padding: .45rem .7rem;
    text-align: left;
    cursor: pointer;
    transition: background-color .12s ease;
}
.of-groupe-titre:hover {
    background-color: color-mix(in srgb, var(--of-groupe-couleur) 22%, var(--ct-secondary-bg));
}
.of-groupe-titre .of-groupe-chevron {
    transition: transform .15s ease;
    color: var(--of-groupe-couleur);
}
.of-groupe-titre[aria-expanded="false"] .of-groupe-chevron { transform: rotate(-90deg); }
/* ⚠️ Le compte se place JUSTE APRÈS le nom, pas en bout de bande : sur un écran large, un
   `margin-left: auto` le renvoyait à plus de mille pixels du titre auquel il se rapporte. */
.of-groupe-titre .of-groupe-nb {
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ct-body-color);
}

/* ⚠️ Une cellule de tableau ne se colore PAS par `background-color` : le thème le surcharge en
   `!important`. Le seul chemin qui tienne est la variable `--ct-table-bg`, que la feuille du thème
   lit elle-même. Le reste du bloc, lui, s'applique normalement — d'où un faux diagnostic possible
   « la feuille n'est pas chargée » alors que seule la couleur de fond est ignorée. */
tr.of-groupe-ligne > td {
    --of-groupe-couleur: var(--color-documents, #1E3A8A);
    --ct-table-bg: color-mix(in srgb, var(--of-groupe-couleur) 12%, var(--ct-secondary-bg));
    color: var(--ct-dark);
    font-weight: 600;
    letter-spacing: .03em;
    cursor: pointer;
    /* ⚠️ SURTOUT PAS `box-shadow` pour le filet de gauche, si tentant soit-il : la ligne zébrée de
       DataTables (`table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > *`) a une
       spécificité supérieure et écrase la propriété ENTIÈRE par son `inset 0 0 0 9999px`. Résultat
       observé : une ligne de groupe sur deux perdait son filet — un défaut d'apparence ALTERNÉE,
       qu'on prend d'abord pour un hasard d'affichage. `border-left` est libre, et le seul rival est
       la remise à zéro `tbody, td, tfoot, th, thead, tr` que ce sélecteur bat déjà. */
    border-left: 3px solid var(--of-groupe-couleur);
}
tr.of-groupe-ligne > td:hover {
    --ct-table-bg: color-mix(in srgb, var(--of-groupe-couleur) 22%, var(--ct-secondary-bg));
}

/* Repliement. Sur les cartes, on masque les colonnes du groupe ; en table, ses lignes. */
.of-groupe-replie { display: none !important; }

/* ── Densité « compacte » des listes ──────────────────────────────────────────────────────────
   Posée par head.inc.php sur <html> (`data-densite="compacte"`), depuis la préférence de compte.
   « confortable » ne pose AUCUN attribut : c'est l'aspect d'origine, et une règle qui le redirait
   serait une règle de plus à tenir en accord avec le thème.

   ⚠️ ON RESSERRE LES ESPACES, PAS LE TEXTE. Réduire la taille de police gagnerait des lignes en
   les rendant moins lisibles — c'est l'inverse du service rendu à qui passe sa journée dans une
   liste de plusieurs centaines d'entrées. Seuls les rembourrages verticaux diminuent.

   ⚠️ Ciblé sur les TABLEAUX (`.table`), pas sur tout : appliquer un resserrement global toucherait
   les formulaires, les fenêtres et les cartes, où la place n'est pas le problème.

   ⚠️ `!important` — ET C'EST DÉLIBÉRÉ. `css/custom-styles.css` impose déjà
   `.table td { padding: .75rem !important }` à tous les tableaux de l'application. Sans marquer
   cette règle-ci à son tour, la densité compacte n'a AUCUN effet visible : l'attribut est bien
   posé sur <html>, la feuille bien chargée, la règle bien écrite — et rien ne bouge. C'est
   exactement le défaut qui nous a été rapporté, et il est invisible sur un banc d'essai qui
   oublie de charger `custom-styles.css` : les deux premiers l'ont oublié et ont donné un
   « ça marche » qui ne valait rien. Un banc doit charger les feuilles de la PAGE SERVIE, dans
   son ordre. */
html[data-densite="compacte"] .table > :not(caption) > * > * {
  padding-top: .35rem !important;
  padding-bottom: .35rem !important;
}
html[data-densite="compacte"] .table-sm > :not(caption) > * > * {
  padding-top: .2rem !important;
  padding-bottom: .2rem !important;
}
/* Les cellules d'action gardent leur hauteur de cible : un bouton de 24 px reste un bouton de
   24 px, sinon on gagne des lignes en perdant des clics. */
html[data-densite="compacte"] .table .of-row-actions { line-height: 1.1; }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   Onglet « Modules » des réglages — chaque section porte la couleur de son module.

   Le panneau aligne une quinzaine de réglages appartenant à six modules différents : sans repère,
   on cherche « Agenda » en lisant les titres un à un. La couleur de module est déjà le repère
   qu'utilise le reste de l'application (menu, rubans d'en-tête) — c'est elle qu'on reprend ici,
   pas une palette de plus.

   ⚠️ LA COULEUR NE PORTE QUE LE FILET ET L'ICÔNE, JAMAIS LE TITRE. Ces teintes sont choisies pour
   des aplats à texte blanc ; posées sur du texte, plusieurs tombent sous 3:1 sur le fond de la
   fenêtre (le piège déjà rencontré sur les blocs de la fiche contact). Le titre garde donc l'encre
   du thème, et la couleur reste un repère à côté de lui — ce qui la dispense aussi du seuil 4,5:1,
   puisqu'elle ne porte aucune information que le mot ne dise déjà.

   « Listes » couvre Contacts ET Documents : elle n'emprunte la couleur d'aucun des deux et retombe
   sur la bordure neutre du thème. Un repère faux serait pire que pas de repère.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
/* ⚠️ LA TEINTE BRUTE N'EST PAS LISIBLE, ET L'ÉCHEC EST SYMÉTRIQUE. Mesuré sur le fond réel de la
   fenêtre : en thème CLAIR, Dossiers tombe à 2,15:1, Agenda à 2,43:1, Tâches à 2,54:1 — les teintes
   vives. En thème SOMBRE, ce sont les foncées qui disparaissent : Espaces de travail à 1,85:1,
   À la connexion à 2,36:1. Le seuil applicable est 3:1 (repère graphique, pas du texte).
   On dérive donc la teinte PAR THÈME — assombrie sur fond clair, éclaircie sur fond sombre —
   plutôt que de retoucher quatorze couleurs à la main. Le fond à 8 %, lui, garde la teinte brute :
   il ne porte rien, il situe. */
.up-section-titre {
  --up-lisible: color-mix(in srgb, var(--up-couleur, var(--ct-border-color)) 72%, #000);
  border-left: 3px solid var(--up-lisible);
  border-radius: 2px;
  padding: .25rem .5rem;
  background: color-mix(in srgb, var(--up-couleur, transparent) 8%, transparent);
}
html[data-bs-theme="dark"] .up-section-titre {
  --up-lisible: color-mix(in srgb, var(--up-couleur, var(--ct-border-color)) 62%, #fff);
}
.up-section-titre > i { color: var(--up-lisible, var(--of-encre-attenuee)); }
/* Sans couleur de module (« Listes » couvre Contacts ET Documents), on ne dérive rien : l'encre
   atténuée du thème est déjà tenue pour lisible dans les deux modes. */
.up-section-titre:not([style*="--up-couleur"]) {
  --up-lisible: var(--of-encre-attenuee);
  border-left-color: var(--ct-border-color);
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   Onglets de référentiels — Paramètres généraux (Facturation, Notes de frais).

   ⚠️ MESURÉ AVANT DE TOUCHER, ET LE DÉFAUT EST À L'ENVERS : l'onglet ACTIF ressortait à 4,69:1 sur
   blanc, en encre GRISE ET TRANSLUCIDE (rgba(108,117,125,.75)), tandis que les inactifs tenaient
   3,03:1 — sous le seuil de 4,5:1 exigé pour du texte. Autrement dit, l'onglet sélectionné était
   à peine plus lisible que les autres, et aucun des deux n'était vraiment lisible. La seule marque
   de sélection était un filet gris très clair (#E5E8EB) sur fond blanc : invisible.

   On rend donc l'ACTIF franc — encre pleine du thème et accent de 2 px dans la COULEUR DU MODULE,
   le repère que l'application emploie déjà partout (ruban d'en-tête, barre du menu) — et l'on
   relève les inactifs jusqu'au seuil.

   ⚠️ Portée limitée à `.of-onglets` : `.nav-tabs` sert dans tout le produit (emails, contacts,
   dossiers, fiches). Une règle globale aurait changé une dizaine d'écrans qu'on n'a pas regardés.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
.of-onglets.nav-tabs {
    --of-onglet-couleur: var(--ct-primary);
    border-bottom: 1px solid var(--ct-border-color);
    gap: .15rem;
}
.of-onglets.nav-tabs .nav-link {
    /* ⚠️ `--of-encre-attenuee` nue mesure 4,07:1 sur blanc — sous le seuil de 4,5:1. Le jeton est
       partagé et validé pour d'AUTRES fonds ; on ne le déplace donc pas, on l'assombrit ici, où le
       fond est blanc. Un onglet inactif reste un texte qu'on doit pouvoir lire pour choisir. */
    color: color-mix(in srgb, var(--of-encre-attenuee) 84%, #000);
    border: 0;
    /* Le filet vit sur l'onglet lui-même : posé sur un pseudo-élément, il passerait sous la bordure
       du conteneur et l'on ne verrait plus qu'un liseré coupé. */
    border-bottom: 2px solid transparent;
    padding: .5rem .9rem;
    font-weight: 500;
}
.of-onglets.nav-tabs .nav-link:hover,
.of-onglets.nav-tabs .nav-link:focus {
    color: var(--ct-dark);
    border-bottom-color: color-mix(in srgb, var(--of-onglet-couleur) 40%, transparent);
    background-color: transparent;
}
html[data-bs-theme="dark"] .of-onglets.nav-tabs .nav-link {
    /* En sombre, assombrir irait dans le mur : c'est l'inverse qu'il faut. */
    color: color-mix(in srgb, var(--of-encre-attenuee) 88%, #fff);
}
.of-onglets.nav-tabs .nav-link.active {
    color: var(--ct-dark);
    font-weight: 600;
    background-color: transparent;
    border-bottom-color: var(--of-onglet-couleur);
}
/* ⚠️ PIÈGE DE SPÉCIFICITÉ, MESURÉ : la règle sombre des onglets INACTIFS
   (`html[data-bs-theme="dark"] … .nav-link`) porte un sélecteur d'élément de plus que celle de
   l'actif (`… .nav-link.active`). À nombre de classes égal, c'est elle qui l'emporte — et en
   thème sombre l'onglet sélectionné reprenait l'encre des autres : 6,09:1 des deux côtés, plus
   aucune distinction. Il faut donc redire l'actif AU MÊME NIVEAU. */
html[data-bs-theme="dark"] .of-onglets.nav-tabs .nav-link.active {
    color: var(--ct-dark);
}

/* ══ Repère « factures échues à relancer » du bandeau du haut ═════════════════════════════════
   La pastille du thème (`.noti-icon-badge`) est un POINT MUET de 7 px : y écrire un nombre donne
   un point difforme dont le chiffre déborde. La cloche (#notif-unread-badge) et les Notes
   (#of-notes-badge) se sont déjà chacune fait la leur ; c'est exactement la même forme qui est
   reprise ici, pour que les trois compteurs du bandeau soient le même objet.

   La règle vit dans of-commun.css et non dans finance.css : l'icône est sur TOUTES les pages,
   alors que la feuille du module Financier n'est servie que sur les siennes — la pastille se
   serait affichée difforme partout ailleurs, c'est-à-dire là où elle sert.

   ⚠️ Le GLYPHE est à la couleur du Financier (il dit où le clic mène), la PASTILLE au rouge de
   l'alerte — comme la cloche, dont l'icône est neutre et le compteur rouge, et comme le bouton
   « À relancer » de la page des factures, qui est le même travail vu de plus près. */
/* ⚠️ 4e fois (26/09) : le parapheur avait la pastille MUETTE du thème — un point rouge sans chiffre, à côté
   de trois compteurs chiffrés. Tout compteur du bandeau se déclare ICI. */
#of-relances-badge,
#of-parapheur-badge {
    position: absolute;
    top: 2px;
    right: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 16px;
    padding: 0 4px;
    border-radius: 8px;
    background: var(--of-aplat-danger, #fa5c7c);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    box-sizing: border-box;
    box-shadow: 0 0 0 2px var(--ct-topbar-bg, #fff);
}

/* Position des compteurs du bandeau du POSTE — réglée à l'œil par le propriétaire dans la console (26/09) :
   la pastille se pose en bas à droite de l'icône, au lieu de la chevaucher par le haut. Une SEULE règle pour les
   quatre compteurs (relances, parapheur, notes, notifications), qui gardent chacun leur forme dans leur feuille.
   ⚠️ Limitée à `.topbar-menu` (bandeau du poste) : la cloche du front MOBILE porte le même #notif-unread-badge,
   sur une icône d'une autre taille — la déplacer de -12 px l'aurait fait sortir de son bouton. La spécificité
   (classe + id) l'emporte sur les règles d'origine, quel que soit l'ordre de chargement des feuilles. */
.topbar-menu #of-relances-badge,
.topbar-menu #of-parapheur-badge,
.topbar-menu #of-notes-badge,
.topbar-menu #notif-unread-badge {
    top: 15px;
    right: -12px;
}

/* ── « N résultats » à côté d'un champ de recherche (assets/js/of_resultats.js) ───────────────────
   Une pastille discrète, aux couleurs du thème (clair comme sombre, par les variables --ct-) ;
   « Aucun résultat » prend la teinte d'avertissement, sans alarmer. Elle ne se replie jamais sur
   deux lignes : c'est une étiquette, pas une phrase. */
.of-nb-resultats {
    display: inline-flex; align-items: center; align-self: center; flex: 0 0 auto;
    margin-left: .5rem; padding: .15rem .6rem; border-radius: 1rem;
    font-size: .78rem; font-weight: 600; line-height: 1.4; white-space: nowrap;
    color: var(--ct-primary); background: rgba(var(--ct-primary-rgb), .12);
}
.of-nb-resultats.est-vide { color: var(--ct-warning-text-emphasis); background: var(--ct-warning-bg-subtle); }
/* Hors du flux (of_resultats.js) : elle ne déplace jamais le champ ni ses voisins. « dedans », elle
   se pose sur la droite du champ ; `pointer-events: none` : un clic à cet endroit reste un clic
   dans le champ, et la zone de texte s'arrête avant elle (marge intérieure posée par le script). */
.of-nb-resultats.a-gauche, .of-nb-resultats.dedans { z-index: 5; }
.of-nb-resultats.dedans { font-size: .72rem; padding: .05rem .5rem; pointer-events: none; }
.of-nb-resultats.dans-libelle { margin-left: .4rem; font-size: .72rem; padding: .05rem .5rem; vertical-align: 1px; }

/* ── Sens d'un appel téléphonique : entrant en VERT, sortant en ROUGE (2026-09-21) ──────────────────
   Classes globales : la liste et la fiche du module Appels (poste, espace contact), la liste mobile.
   ⚠️ PAS `--ct-success` / `--ct-danger` : #0acf97 et #fa5c7c sont trop pâles sur fond clair (le vert
   ~2:1, sous les 3:1 d'une icône et les 4,5:1 du texte de la puce mobile). Deux encres MESURÉES,
   une par thème — et le sens reste dit par la flèche de l'icône et son libellé, pas par la seule
   couleur. */
:root { --of-appel-entrant: #157347; --of-appel-sortant: #c62828; }
[data-bs-theme="dark"] { --of-appel-entrant: #4ade80; --of-appel-sortant: #ff8a80; }
.of-appel-entrant, .of-appel-entrant .mdi { color: var(--of-appel-entrant) !important; }
.of-appel-sortant, .of-appel-sortant .mdi { color: var(--of-appel-sortant) !important; }

/* ── Avertissement des testeurs de la DÉMONSTRATION (nav.inc.php, m_nav.inc.php, index.php) ─────────
   Teinte d'information du thème, encre du corps de texte (lisible en clair comme en sombre). */
/* (03/10) Le bandeau en tête de page est remplacé : pastille « Démo » de la barre du haut + fenêtre, et le texte en clair
   sur la page de connexion — il poussait le contenu après le calage des hauteurs. Source du texte :
   include/utils/demo_avertissement.php. */
.of-demo-pastille {
    display: inline-flex; align-items: center; height: 26px; padding: 0 .6rem;
    border: 1px solid var(--ct-info); border-radius: 13px;
    background: color-mix(in srgb, var(--ct-info) 16%, transparent);
    color: var(--ct-body-color); font-size: .72rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
    cursor: pointer; line-height: 1; vertical-align: middle;
}
.of-demo-pastille:hover, .of-demo-pastille:focus-visible { background: color-mix(in srgb, var(--ct-info) 30%, transparent); }
/* La liste : une icône teintée, une phrase. Partagée par la fenêtre et la page de connexion. */
.of-demo-limites { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; font-size: .88rem; line-height: 1.4; }
.of-demo-limites li { display: flex; gap: .6rem; align-items: flex-start; }
.of-demo-limites li > .mdi { color: var(--ct-info); font-size: 1.15rem; line-height: 1.2; flex: 0 0 auto; }
/* Page de connexion : un encadré sous les profils, lu avant d'entrer. */
.of-demo-a-savoir {
    margin-top: 1rem; padding: .85rem 1rem; border-radius: .6rem;
    border: 1px solid var(--ct-border-color); border-left: 4px solid var(--ct-info);
    background: var(--ct-tertiary-bg, rgba(0, 0, 0, .03));
}
.of-demo-a-savoir .of-demo-limites { font-size: .82rem; gap: .45rem; color: #2f353b; }   /* page de connexion (claire) : encre pleine, le gris du thème y manquait de contraste */
.of-demo-a-savoir-titre { display: flex; align-items: center; gap: .4rem; margin: 0 0 .55rem; font-weight: 700; font-size: .9rem; }
.of-demo-a-savoir-titre > .mdi { color: var(--ct-info); font-size: 1.1rem; }

/* ══ Popup « dossiers rattachés » (assets/js/of_dossier_flags.js) ═══════════════════════════════
   Il héritait de `.card` : même fond que les lignes de la liste des emails, ombre légère, titre
   gris clair — dans les deux thèmes on ne le distinguait pas et il se lisait mal (signalé le
   2026-09-21). Il est désormais cerné et coiffé de la couleur des Dossiers, sur un fond plus
   contrasté que la page, avec une ombre franche. En-tête ambre : encre FONCÉE (≈ 9:1 ; le blanc
   n'y tiendrait que 2:1). La croix de détachement n'apparaît que si le serveur l'autorise. */
#of-doss-pop {
    min-width: 230px; max-width: 360px;
    background: var(--ct-secondary-bg, #fff);
    /* Encre FIXÉE, pas `--ct-emphasis-color` : en thème clair elle vaut un gris à 75 % d'opacité
       (mesuré), précisément le texte délavé qu'il fallait supprimer. */
    color: #212529;
    border: 2px solid var(--color-dossiers, #8A6500);
    border-radius: 8px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, .28), 0 2px 6px rgba(0, 0, 0, .18);
    overflow: hidden;
    font-size: .875rem;
}
[data-bs-theme="dark"] #of-doss-pop {
    background: var(--ct-tertiary-bg, #2c3035);
    color: #eef1f4;
    box-shadow: 0 12px 32px rgba(0, 0, 0, .65), 0 0 0 1px rgba(255, 255, 255, .06);
}
#of-doss-pop .of-doss-pop-tete {
    background: var(--color-dossiers-fond, #F2C84B);
    color: var(--color-dossiers-text, #1D2330);   /* texte sombre sur le jaune « dossier Windows » (07/10/2026) */
    font-weight: 700; font-size: .8rem;
    padding: .4rem .7rem;
}
#of-doss-pop .of-doss-pop-liste { padding: .25rem 0; }
#of-doss-pop .of-doss-ligne {
    display: flex; align-items: center; gap: .35rem;
    padding: 0 .35rem 0 0;
}
#of-doss-pop .of-doss-ligne + .of-doss-ligne { border-top: 1px solid var(--ct-border-color, #dee2e6); }
#of-doss-pop .of-doss-link {
    flex: 1 1 auto; min-width: 0;
    display: flex; align-items: center; gap: .45rem;
    padding: .45rem .7rem;
    color: inherit; font-weight: 600; text-decoration: none;
}
#of-doss-pop .of-doss-link span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#of-doss-pop .of-doss-link .mdi { color: var(--color-dossiers, #8A6500); font-size: 1.05rem; }
#of-doss-pop .of-doss-link:hover,
#of-doss-pop .of-doss-link:focus-visible { background: var(--ct-tertiary-bg, #f1f3f5); text-decoration: underline; }
[data-bs-theme="dark"] #of-doss-pop .of-doss-link:hover,
[data-bs-theme="dark"] #of-doss-pop .of-doss-link:focus-visible { background: rgba(255, 255, 255, .07); }
#of-doss-pop .of-doss-retirer {
    flex: 0 0 auto;
    width: 1.75rem; height: 1.75rem;
    display: inline-flex; align-items: center; justify-content: center;
    border: 0; border-radius: 50%; background: transparent;
    color: var(--ct-danger, #fa5c7c); font-size: 1.1rem;
}
#of-doss-pop .of-doss-retirer:hover,
#of-doss-pop .of-doss-retirer:focus-visible { background: var(--of-aplat-danger, #fa5c7c); color: #fff; }
#of-doss-pop .of-doss-ligne.confirmer { flex-wrap: wrap; padding: .45rem .6rem; gap: .4rem; }
#of-doss-pop .of-doss-question { flex: 1 1 100%; font-weight: 600; }
#of-doss-pop .of-doss-ligne.en-cours { opacity: .55; }

/* ══ LA fenêtre « Classer dans un dossier » (app/shared/js/classer_dossiers.js) ══════════════════════
   Commune aux emails, documents, tâches, agenda et temps passé (uniformisation du 2026-10-08). Des centaines de
   dossiers : une ligne lisible par dossier (nom, client, éléments, portée), la liste bornée en hauteur — elle
   défile, le champ de recherche reste —, les propositions en cartes au-dessus. Couleurs par variables --ct-. */
#ocd-modal .ocd-liste { max-height: 46vh; overflow-y: auto; border: 1px solid var(--ct-border-color); border-radius: .3rem; }
#ocd-modal .ocd-ligne {
    display: grid; grid-template-columns: auto minmax(0, 2.2fr) minmax(0, 1.4fr) 5.5rem 4.6rem;
    align-items: center; gap: .6rem; margin: 0; padding: .4rem .6rem; cursor: pointer;
    border-bottom: 1px solid var(--ct-border-color);
}
#ocd-modal .ocd-ligne:last-child { border-bottom: 0; }
#ocd-modal .ocd-ligne:hover { background: var(--ct-tertiary-bg); }
#ocd-modal .ocd-ligne.ocd-coche { background: var(--ct-light-bg-subtle, var(--ct-tertiary-bg)); }
#ocd-modal .ocd-ligne .form-check-input { margin: 0; }
#ocd-modal .ocd-nom { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#ocd-modal .ocd-client { color: var(--ct-secondary-color); font-size: .85em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#ocd-modal .ocd-meta { color: var(--ct-secondary-color); font-size: .78em; white-space: nowrap; text-align: right; }
#ocd-modal .ocd-portee { text-align: right; }
#ocd-modal .ocd-prop {
    display: flex; gap: .75rem; align-items: flex-start; padding: .55rem .7rem; margin-bottom: .4rem;
    border: 1px solid var(--ct-border-color); border-left: 3px solid var(--ct-primary); border-radius: .3rem;
}
#ocd-modal .ocd-prop-fait { border-left-color: var(--ct-success); opacity: .85; }
#ocd-modal .ocd-raisons { margin: .2rem 0 0; padding-left: 1.1rem; font-size: .78em; color: var(--ct-secondary-color); }
#ocd-modal .ocd-puce {
    display: inline-flex; align-items: center; margin: 0 .3rem .3rem 0; padding: .1rem .5rem; font-size: .82em;
    border: 1px solid var(--ct-border-color); border-radius: 1rem; background: var(--ct-tertiary-bg);
}
#ocd-modal .ocd-puce-x { border: 0; background: none; color: var(--ct-danger); margin-left: .25rem; line-height: 1; padding: 0 .1rem; }
/* Étroit : le client passe sous le nom, la portée disparaît (elle reste dans la page Dossiers). */
@media (max-width: 575.98px) {
    #ocd-modal .ocd-ligne { grid-template-columns: auto minmax(0, 1fr) 5rem; }
    #ocd-modal .ocd-client { grid-column: 2 / 3; grid-row: 2; }
    #ocd-modal .ocd-portee { display: none; }
}

/* ══ Sous-menu collant des pages à sections (Paramètres généraux) ════════════════════════════════
   Sur une section longue (le catalogue d'articles), le menu des sections partait avec la page : pour
   passer d'« Articles » à « Utilisateurs », il fallait remonter tout en haut (signalé le 2026-09-25).
   Il reste désormais sous la barre supérieure. À partir de 992 px seulement : en dessous, il est
   EMPILÉ au-dessus du contenu — collant, il en masquerait le haut. S'il est plus haut que la
   fenêtre (seize sections sur un petit portable), il défile lui-même au lieu d'être coupé. */
@media (min-width: 992px) {
    .of-sousnav-collante {
        position: sticky;
        top: calc(var(--ct-topbar-height, 70px) + 1rem);
        max-height: calc(100vh - var(--ct-topbar-height, 70px) - 2rem);
        overflow-y: auto;
        z-index: 2;
    }
}

/* ══ Bouton « revenir en haut de la page » (of-shell.js, brancherRetourHaut) ═════════════════════
   N'apparaît qu'après un défilement d'au moins un écran : sur une page courte, il ne serait qu'un
   bouton de plus. Au-dessus du contenu, SOUS les fenêtres modales (1055) et les notifications. */
.of-retour-haut {
    position: fixed;
    /* Collé au bord (12 px) : à 24 px, il recouvrait la colonne des boutons « Supprimer » des
       tableaux qui vont jusqu'au bord de la carte (le catalogue d'articles, mesuré à 1366 px). */
    right: .75rem;
    bottom: 1.5rem;
    z-index: 1030;
    width: 44px; height: 44px;                 /* cible tactile minimale */
    display: inline-flex; align-items: center; justify-content: center;
    border: 0; border-radius: 50%;
    background: var(--of-aplat-primary, #727cf5);
    color: #fff;                               /* 3,55:1 sur le primaire : au-dessus des 3:1 exigés d'une icône */
    font-size: 1.35rem;
    box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .25);
    opacity: .9;
    transition: opacity .15s ease-in-out, transform .15s ease-in-out;
}
.of-retour-haut:hover, .of-retour-haut:focus-visible { opacity: 1; transform: translateY(-2px); }
.of-retour-haut:focus-visible { outline: 3px solid var(--ct-primary-bg-subtle, #c8cbfb); outline-offset: 2px; }
.of-retour-haut[hidden] { display: none; }
/* Sous 768 px, la barre d'onglets occupe le bas de l'écran : le bouton se pose au-dessus. */
@media (max-width: 767.98px) {
    .of-retour-haut { bottom: calc(var(--m-tabbar-h, 64px) + env(safe-area-inset-bottom) + 1rem); right: 1rem; }
}
@media (prefers-reduced-motion: reduce) {
    .of-retour-haut { transition: none; }
    .of-retour-haut:hover, .of-retour-haut:focus-visible { transform: none; }
}

/* ── Droit DEMANDÉ, mis en évidence dans les exceptions d'une personne ──────────────────────────
   On arrive sur Droits d'accès par un lien « Accorder les droits » (un message « X n'a pas les
   droits … » d'un autre écran) : les lignes des droits manquants sont soulignées, pour qu'on n'ait
   pas à les chercher parmi une soixantaine (DroitsUI.ouvrirPour). */
.of-droit-cible {
    border-radius: 6px;
    background: color-mix(in srgb, var(--ct-warning, #ffbc00) 14%, transparent);
    box-shadow: 0 0 0 2px var(--ct-warning, #ffbc00);
}
/* Le « Comment faire » d'un message de droits manquants : une icône, l'aide au survol. */
.of-comment-faire { cursor: help; color: var(--of-encre-attenuee, #6c757d); }

/* ── Bandeau du logo des écrans d'accueil (connexion, verrouillage, erreur) ──────────────────────
   Blanc, liseré à la couleur primaire. Le logo est dessiné pour un fond blanc : sur l'ancien
   bandeau `bg-primary` (indigo, luminance moyenne), son chevron rouge plafonnait à un contraste de
   1,8 — et l'éclaircir le faisait TOMBER (1,3 pour #E0283F), l'indigo étant aussi clair que lui.
   Blanc dans les deux thèmes : c'est le fond du logo, pas celui de la page. */
.card-header.of-bandeau-logo {
    background: #fff;
    border-bottom: 3px solid var(--ct-primary, #727cf5);
    padding: 1.25rem 1rem;
}

/* ── Œil « afficher / masquer le mot de passe » (page de connexion) ───────────────────────────────
   La règle venait du thème Hyper et a disparu avec lui : la case restait vide (signalé le 01/10).
   Bascule de classe : of-shell.js › brancherOeilMotDePasse (.show-password sur le conteneur). */
[data-password] { cursor: pointer; }
.password-eye::before { font-family: "Material Design Icons"; content: "\F06D0"; font-style: normal; font-size: 1.1rem; line-height: 1;
                        vertical-align: middle; color: var(--ct-body-color); }
.show-password .password-eye::before { content: "\F06D1"; }

/* ── Page de connexion « Partagé » (piste A, 2026-10-01) ─────────────────────────────────────────
   La marque à gauche (panneau noir, logo blanc et rouge, grand X en filigrane), la connexion à droite.
   Sous 992 px : le panneau devient un bandeau portant le logo seul. Couleurs en dur : la page de connexion
   n'a ni thème ni mode sombre (head.inc.php « auth »). */
body.of-auth-page { margin: 0; background: #F6F6FA; }
.of-auth { min-height: 100vh; display: grid; grid-template-columns: minmax(0, 1.12fr) minmax(0, 1fr); font-family: Nunito, system-ui, sans-serif; }
.of-auth-marque { position: relative; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; gap: 2rem;
                  padding: 4rem 4.5rem; background-color: #111214; color: #fff;
                  background-image: radial-gradient(rgba(255, 255, 255, .07) 1px, transparent 1px); background-size: 22px 22px; }
.of-auth-x { position: absolute; right: -150px; bottom: -170px; width: 560px; height: 560px; opacity: .22; pointer-events: none; }
.of-auth-x span { position: absolute; left: 230px; top: -40px; width: 100px; height: 640px; border-radius: 50px; background: #C6132B; transform: rotate(45deg); }
.of-auth-x span + span { background: #727cf5; transform: rotate(-45deg); }
.of-auth-logo { position: relative; align-self: flex-start; }
.of-auth-logo img { height: 40px; width: auto; display: block; }
.of-auth-accroche { position: relative; max-width: 34rem; }
.of-auth-accroche h2 { margin: 0 0 1.3rem; font-size: 2.85rem; line-height: 1.12; font-weight: 800; letter-spacing: -.5px; color: #fff; }
.of-auth-accroche p { margin: 0 0 1.6rem; font-size: 1.12rem; line-height: 1.55; color: #B4B8C7; }
.of-auth-modules { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: .6rem; }
.of-auth-modules li { padding: .4rem .9rem; border-radius: 999px; border: 1px solid rgba(255, 255, 255, .18); color: #E6E8F0; font-size: .875rem; font-weight: 600; }
.of-auth-copyright { position: relative; margin: 0; font-size: .8rem; color: #8E93A6; }
.of-auth-copyright-bas { display: none; text-align: center; margin-top: 1.5rem; color: #6B7080; }

.of-auth-panneau { display: flex; align-items: center; justify-content: center; padding: 3rem; }
.of-auth-contenu { width: 100%; max-width: 29rem; display: flex; flex-direction: column; gap: 1.35rem; }
.of-auth-badge { align-self: flex-start; padding: .3rem .75rem; border-radius: 999px; background: #E7E8FD; color: #343db8; font-size: .8rem; font-weight: 700; }
.of-auth-titre h1 { margin: 0 0 .45rem; font-size: 2.1rem; font-weight: 800; letter-spacing: -.3px; color: #111214; }
.of-auth-titre p { margin: 0; font-size: 1rem; color: #555B6B; }
.of-auth-contenu .form-label { font-weight: 600; color: #111214; }
.of-auth-contenu .form-control { height: 46px; border-radius: 10px; border-color: #D5D8E3; background: #fff; }
.of-auth-contenu .input-group-merge .form-control { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.of-auth-contenu .input-group-text { border-radius: 0 10px 10px 0; border-color: #D5D8E3; background: #fff; }
.of-auth-oubli { color: #4b55d6; }
.of-auth-valider { width: 100%; height: 48px; border-radius: 12px; font-weight: 700; font-size: 1rem; }
.of-auth-ou { display: flex; align-items: center; gap: .75rem; color: #8A8FA0; font-size: .8rem; margin: 1rem 0; }
.of-auth-ou::before, .of-auth-ou::after { content: ""; flex: 1 1 auto; height: 1px; background: #E1E3EC; }
.of-auth-autre { width: 100%; height: 48px; border-radius: 12px; border: 1px solid #C9CCDA; background: transparent; font: inherit; font-size: .95rem;
                 font-weight: 700; color: #111214; cursor: pointer; }
.of-auth-autre:hover, .of-auth-autre:focus-visible { border-color: #727cf5; background: #fff; }
.of-auth-autre[aria-expanded="true"] { display: none; }
.of-auth-note { margin: 0; font-size: .8rem; color: #6B7080; text-align: center; }

/* Profils de la démo : tuiles sur deux colonnes, pastille d'initiales colorée. */
.of-demo-grille { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; }
/* Fond de la tuile TEINTÉ de la couleur du profil (comme la maquette B, essai du 01/10) : --of-profil, posé par index.php. */
.of-demo-profil { --of-profil: #3F51B5; display: flex; align-items: center; gap: .75rem; padding: .85rem; border-radius: 16px; border: 1px solid transparent;
                  background: color-mix(in srgb, var(--of-profil) 9%, #fff);
                  text-align: left; font: inherit; cursor: pointer; transition: border-color .15s, box-shadow .15s, transform .15s; }
.of-demo-profil:hover, .of-demo-profil:focus-visible { border-color: color-mix(in srgb, var(--of-profil) 45%, #fff);
                  box-shadow: 0 6px 18px color-mix(in srgb, var(--of-profil) 20%, transparent); transform: translateY(-1px); }
.of-demo-avatar { flex: 0 0 auto; width: 42px; height: 42px; border-radius: 13px; display: flex; align-items: center; justify-content: center;
                  background: var(--of-profil); color: #fff; font-weight: 800; font-size: .95rem; }
.of-demo-texte { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.of-demo-texte b { font-size: .95rem; color: #111214; }
.of-demo-texte small { font-size: .8rem; color: #5B6170; }

@media (max-width: 991.98px) {
    .of-auth { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
    .of-auth-marque { padding: 1.1rem 1.25rem; }
    .of-auth-accroche, .of-auth-marque .of-auth-copyright, .of-auth-x { display: none; }
    .of-auth-logo img { height: 30px; }
    .of-auth-panneau { align-items: flex-start; padding: 2rem 1rem 1.5rem; }
    .of-auth-copyright-bas { display: block; }
}
@media (max-width: 479.98px) { .of-demo-grille { grid-template-columns: 1fr; } }
.of-demo-profil[hidden] { display: none; }

/* Les deux démos se répondent (lot A5 de la démo artisan) : l'offre visitée cerclée (« Vous êtes ici »), l'autre est un
   LIEN vers sa démo. Remplace les deux boutons du lot O6 (choix d'offre par cookie). */
.of-demo-offres { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem; margin-bottom: .9rem; }
.of-demo-offre { display: flex; flex-direction: column; gap: .1rem; padding: .6rem .8rem; border-radius: 12px; text-align: left;
                 font: inherit; border: 2px solid #E2E5EC; background: #fff; color: #111214; text-decoration: none; }
a.of-demo-offre { cursor: pointer; }
a.of-demo-offre:hover { border-color: #3F51B5; color: #111214; }
.of-demo-offre small { font-size: .78rem; color: #5B6170; }
.of-demo-offre[aria-current="page"] { border-color: #3F51B5; background: color-mix(in srgb, #3F51B5 7%, #fff); }
.of-demo-offre .of-demo-offre-ici { color: #3F51B5; font-weight: 600; }
.of-demo-offre .of-demo-offre-aller { color: #3F51B5; font-weight: 600; margin-top: auto; }
.of-demo-offre:focus-visible { outline: 3px solid color-mix(in srgb, #3F51B5 45%, #fff); outline-offset: 2px; }

/* « À compléter » (contacts, migration 162, 03/10) : la fiche saisie sur un téléphone, à reprendre au bureau. Ambre de
   l'ATTENTE — mêmes encres que le front mobile (of-m.css, --m-attente), mesurées ≥ 5,3:1 sur leur fond teinté. */
.of-badge-acompleter {
    font-weight: 600; font-size: .7rem; vertical-align: middle;
    color: #904800; background: color-mix(in srgb, #904800 12%, transparent);
    border: 1px solid color-mix(in srgb, #904800 35%, transparent);
}
[data-bs-theme="dark"] .of-badge-acompleter {
    color: #ffcb87; background: color-mix(in srgb, #ffcb87 12%, transparent);
    border-color: color-mix(in srgb, #ffcb87 35%, transparent);
}
/* ── Pastille de TYPE d'un contact (03/10) : la MÊME au poste et sur le téléphone ─────────────────────────────────
   Rond teinté à 16 %, icône dans l'encre (≥ 4,8:1) : bleu pour une personne, violet pour une société. Née sur le
   mobile (of-m-contacts.css) ; elle vit ICI pour qu'il n'y en ait qu'une. Taille par défaut 36 px, `-sm` 26 px en ligne.
   Glyphes : personne = compte (contour), société = immeuble — voir ofIconeContact() côté JS. */
:root { --of-contact-personne: #1a55ab; --of-contact-societe: #6b3fa0; }
[data-bs-theme="dark"] { --of-contact-personne: #a6d2ff; --of-contact-societe: #d3b8ff; }
.of-avatar-contact {
    flex: 0 0 auto; width: 36px; height: 36px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center; vertical-align: middle;
    --of-contact-teinte: var(--of-contact-personne);
    color: var(--of-contact-teinte); background: color-mix(in srgb, var(--of-contact-teinte) 16%, transparent);
}
.of-avatar-contact > i { font-size: 1.2rem; line-height: 1; }
.of-avatar-contact.of-avatar-contact-m { --of-contact-teinte: var(--of-contact-societe); }
.of-avatar-contact.of-avatar-contact-sm { width: 26px; height: 26px; }
.of-avatar-contact.of-avatar-contact-sm > i { font-size: .95rem; }
/* Ligne sélectionnée d'un tableau (fond primaire) : la pastille passe en blanc, comme les autres icônes de la ligne. */
.table tr.selected .of-avatar-contact { color: #fff; background: rgba(255, 255, 255, .22); }

/* Le même ambre en bandeau, en tête de la fenêtre contact : la case qui retire le marqueur. */
.of-acompleter-bandeau {
    padding: .5rem .75rem; border-radius: .375rem; color: #904800;
    background: color-mix(in srgb, #904800 10%, transparent);
    border: 1px solid color-mix(in srgb, #904800 35%, transparent);
}
[data-bs-theme="dark"] .of-acompleter-bandeau {
    color: #ffcb87; background: color-mix(in srgb, #ffcb87 10%, transparent);
    border-color: color-mix(in srgb, #ffcb87 35%, transparent);
}

/* ── Titres de colonne qui trient, dans un tableau rendu sans DataTables (04/10/2026) ───────────────
   Bibliothèque de modèles, Assistant modèles : le titre est un BOUTON (clavier, lecteur d'écran), le <th> porte
   aria-sort, la flèche dit le sens. Une seule définition pour tous les tableaux triés à la main. */
th .of-th-tri { all: unset; cursor: pointer; font: inherit; color: inherit; border-radius: .2rem; }
th .of-th-tri:hover { color: var(--ct-dark); }
th .of-th-tri:focus-visible { outline: 2px solid var(--ct-primary); outline-offset: 2px; }
th[aria-sort] .of-th-tri { color: var(--ct-dark); }
th[aria-sort] .of-th-tri::after { font-family: "Material Design Icons"; margin-left: .25rem; font-size: .85rem; }
th[aria-sort="ascending"] .of-th-tri::after { content: "\F005D"; }   /* mdi-arrow-up */
th[aria-sort="descending"] .of-th-tri::after { content: "\F0045"; }  /* mdi-arrow-down */

/* ── Rubriques repliables d'un panneau de détail (ofRubrique, js/fonctions_communes.js — 05/10/2026) ──────────────
   Un en-tête qui se VOIT : bandeau teinté de la couleur du module (`--of-rubrique`, posée par le panneau), filet
   à gauche, encre principale — l'ancien titre gris de 0,7 rem se confondait avec le texte. Le chevron dit l'état. */
.of-rubrique { margin-top: .75rem; }
.of-rubrique-tete {
    display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin: 0 0 .45rem;
    padding: .3rem .5rem .3rem .4rem; border-radius: .35rem;
    background: color-mix(in srgb, var(--of-rubrique, var(--ct-primary)) 10%, var(--ct-secondary-bg));
    box-shadow: inset 3px 0 0 var(--of-rubrique, var(--ct-primary));
}
.of-rubrique-bascule {
    display: inline-flex; align-items: center; gap: .3rem; padding: 0; border: 0; background: none; text-align: left;
    font-size: .76rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--ct-dark);
    cursor: pointer;
}
.of-rubrique-bascule:focus-visible { outline: 2px solid var(--of-rubrique, var(--ct-primary)); outline-offset: 2px; border-radius: .2rem; }
.of-rubrique-chevron { font-size: 1rem; line-height: 1; transition: transform .15s; color: var(--of-rubrique, var(--ct-primary)); }
.of-rubrique--repliee .of-rubrique-chevron { transform: rotate(-90deg); }
.of-rubrique--repliee > .of-rubrique-corps { display: none; }
.of-rubrique--repliee > .of-rubrique-tete { margin-bottom: 0; }
.of-rubrique-tete .btn-link { font-size: .78rem; letter-spacing: 0; text-transform: none; font-weight: 600; text-decoration: none; }

/* ── Cases à cocher RONDES dans toute l'application (demande du 05/10/2026, sur le modèle de OneDrive) ─────────────
   Une case reste une case : coche ✓ quand elle est cochée, trait quand la sélection est partielle — c'est ce qui la
   distingue d'un bouton radio (un point). Les interrupteurs (`.form-switch`) gardent leur forme de pilule ; les
   boutons-bascules (`.btn-check`) ne sont pas des cases.
   ⚠️ Spécificité : Bootstrap arrondit `.form-check-input[type=checkbox]` à .25em (0,2,0) et la pilule de
   `.form-switch .form-check-input` (0,2,0). Cette feuille passe après : la règle ronde gagne sur les deux — d'où la
   pilule RÉTABLIE à (0,3,0) juste dessous. */
.form-check-input[type="checkbox"] { border-radius: 50%; }
.form-switch .form-check-input[type="checkbox"], .form-check-input[type="checkbox"][role="switch"] { border-radius: 2em; }

/* Cases « nues » (sans form-check-input : Temps passé, rédaction d'un email, assistant de modèles…) : dessinées
   comme les autres, sinon une liste montrerait des cases rondes et, à côté, des carrés du navigateur. */
input[type="checkbox"]:not(.form-check-input):not(.btn-check) {
    appearance: none; -webkit-appearance: none; flex-shrink: 0;
    width: 1em; height: 1em; margin: 0 .25em 0 0; vertical-align: -.125em; cursor: pointer;
    border: 1px solid var(--ct-border-color, #c9ced6); border-radius: 50%; background-color: var(--ct-secondary-bg, #fff);
    background-repeat: no-repeat; background-position: center; background-size: contain;
}
input[type="checkbox"]:not(.form-check-input):not(.btn-check):checked {
    background-color: var(--of-aplat-primary, #727cf5); border-color: var(--of-aplat-primary, #727cf5);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}
input[type="checkbox"]:not(.form-check-input):not(.btn-check):indeterminate {
    background-color: var(--of-aplat-primary, #727cf5); border-color: var(--of-aplat-primary, #727cf5);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
}
input[type="checkbox"]:not(.form-check-input):not(.btn-check):focus-visible { outline: 2px solid var(--ct-primary, #727cf5); outline-offset: 2px; }
input[type="checkbox"]:not(.form-check-input):not(.btn-check):disabled { opacity: .5; cursor: default; }

/* ── Badge gris clair `bg-light text-dark` (ou `text-bg-light`) : lisible en thème SOMBRE (audit d'uniformité, lot E) ──
   24 fichiers l'emploient ; en sombre son encre restait figée pour le clair (1,4:1). Une règle ici plutôt que 24
   retouches : il devient le badge neutre du thème, comme `.of-badge`. */
[data-bs-theme="dark"] .badge.bg-light.text-dark,
[data-bs-theme="dark"] .badge.text-bg-light {
    background-color: var(--ct-tertiary-bg) !important;
    color: var(--ct-dark) !important;
    border: 1px solid var(--ct-border-color);
}

/* (07/10/2026) Dossiers en JAUNE : la croix des en-têtes de fenêtre passe en encre SOMBRE. `btn-close-white` (et l'inversion
   du thème sombre) la rendaient blanche sur le jaune — peu visible. Une règle ici plutôt que quatre balisages à reprendre. */
.modal-header[style*="--color-dossiers-fond"] .btn-close,
.modal-header.bg-dossiers .btn-close {
    filter: none;
}

/* ══ Pied de page toujours visible, sur toutes les pages (2026-10-10) ════════════════════════════
   Le pied « 2026 © OrgaFlex » était posé en `absolute` au bas de .content-page : sur une page qui
   défile, il n'apparaissait qu'au bout du contenu. `sticky` le colle au bas de l'écran pendant le
   défilement, sans fixer de position gauche — qui dépendrait des trois modes du menu latéral.
   · marges négatives : la pleine largeur que lui donnait `left/right: 0` (.content-page a .75rem de
     marge intérieure) ; la réserve du bas, faite pour le pied absolu, n'a plus d'objet ;
   · `min-height` du contenu : sur une page COURTE, le pied, désormais dans le flux, remonterait sous
     le contenu au lieu de rester en bas de l'écran ;
   · le fond masque ce qui défile dessous.
   Sous 768 px, le pied est masqué (of-mobile.css). Pages encapsulées (?embed=1) : pas de pied. */
@media (min-width: 768px) {
    .content-page { padding-bottom: 0; }
    .content-page > .content {
        min-height: calc(100vh - var(--ct-topbar-height, 70px) - var(--ct-footer-height, 60px) - 2px);
    }
    .content-page > .footer {
        position: sticky;
        bottom: 0;
        margin: 0 -.75rem;
        z-index: 5;
        background-color: var(--ct-body-bg);
    }
}

/* ══ Défilement sur le tableau, pas sur la page (2026-10-10) ════════════════════════════════════
   Cadre marqué `data-of-defile` : sa hauteur est calculée par of-shell.js (brancherDefilementTables)
   pour que la page tienne dans l'écran — l'en-tête du module, la recherche et les filtres restent
   visibles, seul le tableau défile. Les en-têtes de colonnes restent collés en haut du cadre, sur un
   fond opaque (celui de la carte) pour que les lignes qui passent dessous ne se lisent pas au travers.
   `of-defile-actif` n'est posé qu'au-dessus de 768 px et quand la place suffit : sinon la page défile
   comme avant. */
/* `!important` : une liste qui fixait elle-même son débordement (`.ofnotif-list--page` : visible) gardait la
   hauteur imposée SANS ascenseur — ses lignes débordaient sur la page, qui redéfilait. */
.of-defile-actif { overflow-y: auto !important; overscroll-behavior: contain; }
.of-defile-actif thead th {
    position: sticky;
    top: 0;
    z-index: 3;
    /* ⚠️ Les tableaux Bootstrap peignent chaque cellule avec `var(--ct-table-bg)` (sélecteur plus fort que
       celui-ci) : sans redéfinir la variable, l'en-tête restait transparent, les lignes se lisaient au travers. */
    --ct-table-bg: var(--ct-card-bg, var(--ct-secondary-bg));
    background-color: var(--ct-card-bg, var(--ct-secondary-bg));
}

/* ══ Lignes-fiches — refonte de lisibilité, lot L4 (13/10/2026) ═══════════════════════════════════════════════════
   assets/js/of-lignes-fiches.js : le TABLEAU reste (colonnes alignées), chaque valeur porte au-dessus d'elle le nom de
   sa colonne (`data-libelle`, posé par le patron cartes d'of-tableaux.js), l'en-tête est masqué et « Trier par » le
   remplace. Au choix du compte (`uprefs`), lignes-fiches par défaut (décision de Pascal).
   ⚠️ Rien ici ne vaut sous 768 px : le petit écran a son propre patron cartes (of-mobile.css). */
.of-lf-barre { display: flex; align-items: center; justify-content: flex-end; gap: .75rem; flex-wrap: wrap; margin-bottom: .5rem; }
/* « Filtré par » : à gauche de la barre, une étiquette par filtre actif (croix = retirer). */
.of-lf-filtres { display: flex; align-items: center; flex-wrap: wrap; gap: .4rem; margin-right: auto; font-size: .85rem; }
.of-lf-filtres[hidden] { display: none; }
.of-lf-filtres-titre { color: var(--ct-secondary-color); font-weight: 600; }
.of-lf-chip {
    display: inline-flex; align-items: center; gap: .3rem; padding: .1rem .2rem .1rem .6rem; border-radius: 999px;
    border: 1px solid var(--ct-border-color); background: var(--ct-secondary-bg); color: var(--ct-body-color); line-height: 1.5;
}
.of-lf-chip strong { font-weight: 700; }
.of-lf-chip-x {
    border: 0; background: transparent; color: var(--ct-secondary-color); width: 1.4rem; height: 1.4rem;
    display: inline-grid; place-items: center; border-radius: 50%; padding: 0;
}
.of-lf-chip-x:hover, .of-lf-chip-x:focus-visible { background: var(--ct-tertiary-bg); color: var(--ct-danger); }
.of-lf-tri { display: flex; align-items: center; gap: .4rem; margin: 0; font-size: .85rem; color: var(--ct-secondary-color); }
.of-lf-tri[hidden] { display: none; }
.of-lf-tri select { width: auto; }
/* « Trier par » n'a de sens qu'en lignes-fiches : en tableau, les en-têtes trient. */
.of-lf-barre:not(.of-lf-en-fiches) .of-lf-tri { display: none; }
@media (min-width: 768px) {
    table.of-lignes-fiches > thead { display: none; }
    table.of-lignes-fiches { --ct-table-striped-bg: transparent; }
    table.of-lignes-fiches > tbody > tr > td { padding-top: .75rem; padding-bottom: .75rem; }
    table.of-lignes-fiches > tbody > tr > td[data-libelle]:not([data-libelle=""]):not([colspan]):not(.of-carte-vide)::before {
        content: attr(data-libelle);
        display: block; margin-bottom: .15rem;
        font-size: .72rem; font-weight: 600; line-height: 1.2; letter-spacing: .01em;
        color: var(--ct-secondary-color); white-space: nowrap;
    }
    /* Une cellule VIDE (`of-carte-vide`, posée par le patron cartes) reste un blanc, sans libellé — exclue par le
       :not() ci-dessus : « Dossier » au-dessus de rien se lit comme une donnée manquante. (Une règle à part,
       `td.of-carte-vide::before { content: none }`, PERDAIT contre celle-ci, plus spécifique : vu à l'écran.)
       Une colonne vide sur TOUTES les lignes disparaît. */
    table.of-lignes-fiches .of-col-vide { display: none; }
    /* L'identifiant d'une pièce ne se coupe pas (« D-2026- / 0232 » à 1280 px). */
    table.of-lignes-fiches > tbody > tr > td > strong:first-child { white-space: nowrap; }
}
/* Statuts en PASTILLES DOUCES, en lignes-fiches et en thème clair : fond teinté, encre foncée de la même famille
   (5,9 à 7,9:1, mesuré). Le badge plein du thème criait plus fort que le numéro de la pièce. Bootstrap pose ses
   `bg-*` avec !important, d'où celui-ci. Le sombre garde ses badges pleins (lot « contraste du sombre »). */
:root:not([data-bs-theme="dark"]) table.of-lignes-fiches .badge {
    font-size: .78rem; font-weight: 700; padding: .3em .65em; border-radius: 6px;
}
:root:not([data-bs-theme="dark"]) table.of-lignes-fiches .badge.bg-primary   { background-color: #e4edf4 !important; color: #134a70 !important; }
:root:not([data-bs-theme="dark"]) table.of-lignes-fiches .badge.bg-success   { background-color: #e2f1e8 !important; color: #115d39 !important; }
:root:not([data-bs-theme="dark"]) table.of-lignes-fiches .badge.bg-danger    { background-color: #fbe4e4 !important; color: #a82020 !important; }
:root:not([data-bs-theme="dark"]) table.of-lignes-fiches .badge.bg-warning   { background-color: #fdf0d3 !important; color: #7a4f00 !important; }
:root:not([data-bs-theme="dark"]) table.of-lignes-fiches .badge.bg-secondary { background-color: #eceff2 !important; color: #4f5962 !important; }
:root:not([data-bs-theme="dark"]) table.of-lignes-fiches .badge.bg-info      { background-color: #ddeff3 !important; color: #0b5d73 !important; }

/* ── L'état d'une INTERVENTION sur son créneau d'agenda (20/10/2026) ───────────────────────────────────────────
   Bureau (carte, liste, mois) et téléphone. Couleurs de sens : bleu « en cours », vert « fait ». Texte blanc sur fond plein
   (contraste ≥ 4,5:1 dans les deux thèmes). */
.of-etat-int { display: inline-block; flex: none; padding: 0 .4em; border-radius: 999px; font-size: .72em; font-weight: 600; line-height: 1.5;
               color: #fff; background: #1D4ED8; white-space: nowrap; vertical-align: middle; }
.of-etat-int[data-etat="terminee"] { background: #15803D; }
.of-etat-int[data-etat="facturee"] { background: #166534; }
.of-c-etat { align-self: flex-start; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
.of-l-etat, .of-m-etat { margin: 0 .35em; }

/* Une plage de travail déclarée — début, fin, pause (assets/js/of-plage.js, 20/10/2026) : trois champs sur une ligne,
   et le travail effectif dessous, recalculé à la saisie. */
.of-plage-heures { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: .4rem; }
.of-plage-heures label { display: flex; flex-direction: column; gap: .15rem; margin: 0; min-width: 0; }
.of-plage-heures .form-label { margin: 0; }
.of-plage-total { margin: .35rem 0 .5rem; font-size: .88rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.of-plage-total.of-plage-erreur { color: var(--ct-danger, #dc3545); font-weight: 500; }

/* ── Barre d'onglets des espaces Contact et Dossier (maquette « barre renforcée » retenue le 09/11/2026) ───────────────
   Les onglets inactifs se voyaient mal : texte foncé en gras, icône à la couleur du module, trait épais sous l'actif.
   --tab-color est posé par le JS de chaque espace ; la palette l'éclaircit en thème sombre. */
.of-onglets-barre.nav-tabs { background: var(--ct-secondary-bg); border: 1px solid var(--ct-border-color); border-radius: 8px 8px 0 0; padding: 0 .5rem; }
.of-onglets-barre .nav-link { cursor: pointer; display: inline-flex; align-items: center; gap: .4rem; min-height: 52px; padding: 0 1rem; font-weight: 700; color: var(--ct-emphasis-color); background: transparent; border: 0; border-bottom: 4px solid transparent; border-radius: 0; opacity: 1; }
.of-onglets-barre .nav-link i { color: var(--tab-color); margin-right: 0 !important; }
.of-onglets-barre .nav-link:hover { color: var(--ct-emphasis-color); border-bottom-color: color-mix(in srgb, var(--tab-color) 35%, transparent); }
.of-onglets-barre .nav-link.active { color: var(--tab-color) !important; font-weight: 800; background: transparent; border-bottom-color: var(--tab-color); }
.of-onglets-barre .nav-link.active i { color: inherit; }
.of-onglets-barre .nav-link.disabled { opacity: .4; cursor: not-allowed; }
.of-onglets-barre .badge { background: color-mix(in srgb, currentColor 14%, transparent) !important; color: inherit !important; font-size: .72rem; font-weight: 800; }
