/* ============================================================================
   Kit d'interface DxLean (préfixe `dx-`) — validé par l'utilisateur le 2026-10-06
   ----------------------------------------------------------------------------
   Chargée pour tout le site par templates/dxsite/base_standard.html, juste après
   static/css/filter_bar.css : les règles ne mordent que là où les classes `dx-`
   sont posées. Cahier des charges et plan : Doc/Audit/UI_KIT_MIGRATION.md.

   Squelette d'une page :

     <div class="container-fluid dx-page">
       <a class="dx-back" href="…"><i class="bi bi-arrow-left"></i>Parent</a>   (facultatif)

       <header class="dx-hero">                     carte d'en-tête, UNE ligne
         <span class="dx-hero-accent" aria-hidden="true"></span>
         <span class="dx-hero-avatar" aria-hidden="true"><svg>…</svg></span>   (pictogramme de la barre latérale)
         <div class="dx-hero-titles"><h1 class="dx-hero-title">…</h1>
           <div class="dx-hero-meta">…</div></div>                          (facultatif : référence, version)
         <div class="dx-hero-stats">                                       (facultatif)
           <a class="dx-hero-stat" href="…"><i class="bi …"></i><span>
             <span class="dx-hero-stat-value">12</span><span class="dx-hero-stat-label">En cours</span></span></a>
         </div>
         <div class="dx-hero-actions">secondaires (btn btn-sm btn-outline-secondary),
           puis UNE principale (btn btn-sm btn-dx-primary) à l'extrême droite</div>
       </header>

       <form method="get" class="dx-toolbar" role="search">     recherche puis filtres, toujours ouverts
         <div class="dx-toolbar-search"><i class="bi bi-search"></i><input type="search" name="q" …></div>
         <div class="tl-bar">                                    (champs de static/css/filter_bar.css)
           <div class="tl-field tl-field--md"><label class="tl-label" for="…">…</label><select class="tl-input">…</select></div>
           <div class="tl-actions">
             <a class="tl-btn tl-btn--ghost tl-btn--icon" href="…" title="Réinitialiser" aria-label="Réinitialiser"><svg>↺</svg></a>
             <button class="tl-btn tl-btn--primary">Filtrer</button></div>
         </div>
       </form>

       <section class="dx-section">                              carte de contenu
         <div class="dx-card-tabs" role="navigation" aria-label="…">   onglets intégrés (facultatif)
           <a class="is-active" aria-current="page" href="…"><i class="bi …"></i>Onglet</a>…</div>
         <div class="dx-section-head"><h2 class="dx-section-title"><i class="bi …"></i>Titre</h2>…</div>
         <div class="table-responsive"><table class="table table-sm table-hover align-middle mb-0 dx-table">…</table></div>
           (en-têtes triables au clic d'office, static/js/dx_sort.js ; `data-no-sort` sur une
            table paginée, `data-sort-value` sur une cellule dont le texte trie mal)
         ou <div class="dx-section-body">…</div>
         <div class="dx-empty"><i class="bi bi-inbox"></i><span class="dx-empty-title">…</span></div>
       </section>
     </div>

   Couleur : uniquement var(--module-primary) et dérivées (Module.get_css_variables),
   jamais un hexadécimal de module. Les composants historiques fl-*, wh-*, pj-* ont le
   même rendu et seront remplacés par ceux-ci en fin de migration.
   ========================================================================= */

/* ─── Marges : cinq valeurs, une fois pour tout le site ─────────────────── */
:root {
  --dx-page-top: .6rem;    /* sous l'en-tête du site : léger (2026-10-06, resserré le 2026-10-09) */
  --dx-page-x: 2rem;       /* côtés de la page */
  --dx-page-y: .6rem;      /* bas de la page : une liste descend jusqu'au bas de la fenêtre (2026-10-09) */
  --dx-card-x: 1.5rem;     /* dans une carte, gauche/droite */
  --dx-card-y: 1.25rem;    /* dans une carte, haut/bas */
  --dx-gap: .6rem;         /* entre deux blocs de la page (2026-10-09, était 1,5 rem) */
  --dx-gap-inner: 1rem;    /* entre deux éléments d'une carte */
  --dx-radius: 8px;        /* 2026-10-09, était 16 px */
  --dx-border: rgba(226, 232, 240, .85);
  --dx-shadow: 0 4px 6px -1px rgba(15, 23, 42, .06), 0 12px 28px -6px rgba(15, 23, 42, .12);
  --dx-shadow-soft: 0 1px 2px rgba(15, 23, 42, .04), 0 4px 12px -4px rgba(15, 23, 42, .08);
}
@media (max-width: 1199.98px) { :root { --dx-page-x: 1.25rem; --dx-card-x: 1.25rem; } }
@media (max-width: 767.98px)  { :root { --dx-page-top: .6rem; --dx-page-x: .75rem; --dx-page-y: .6rem;
                                        --dx-card-x: 1rem; --dx-card-y: 1rem; --dx-gap: .6rem; --dx-gap-inner: .75rem; } }

/* ─── Enveloppe : `container-fluid dx-page` (+ content-wide si la page est plafonnée) ──
   Sélecteur appuyé : modern.css et les utilitaires py-* posent leurs marges en !important. */
main.app-main .container-fluid.dx-page,
main .container-fluid.dx-page,
.container-fluid.dx-page { padding: var(--dx-page-top) var(--dx-page-x) var(--dx-page-y) !important; }
.dx-page [hidden] { display: none !important; }

/* ─── Retour ───────────────────────────────────────────────────────────── */
.dx-back { display: inline-flex; align-items: center; gap: .35rem; margin-bottom: .5rem;
           font-size: .8rem; color: var(--color-text-muted, #6b7280); text-decoration: none; }
.dx-back:hover { color: var(--module-primary); }

/* ─── Boutons ──────────────────────────────────────────────────────────── */
.btn-dx-primary { background: var(--module-primary); border: 1px solid var(--module-primary); color: #fff; font-weight: 600; }
.btn-dx-primary:hover, .btn-dx-primary:focus-visible { background: var(--module-primary-hover, var(--module-primary));
           border-color: var(--module-primary-hover, var(--module-primary)); color: #fff; }
.btn-dx-soft { background: #fff; border: 1px solid rgba(var(--module-primary-rgb), .45); color: var(--module-primary); }
.btn-dx-soft:hover, .btn-dx-soft:focus-visible { background: rgba(var(--module-primary-rgb), .08); color: var(--module-primary); }

/* ─── Carte d'en-tête, sur une ligne ───────────────────────────────────────
   Pas d'overflow:hidden : il couperait le menu déroulant d'une action. */
.dx-hero { position: relative; z-index: 3; display: flex; align-items: center; flex-wrap: wrap; gap: .6rem 1rem;
           padding: .45rem var(--dx-card-x) .45rem calc(var(--dx-card-x) + .25rem); margin-bottom: var(--dx-gap);
           background: #fff; border: 1px solid var(--dx-border); border-radius: var(--dx-radius); box-shadow: var(--dx-shadow); }
/* Liseré : calque de toute la carte, au même arrondi, masqué hors des 5 px de gauche, pour
   qu'il épouse les coins (un bandeau de 5 px ne peut pas porter un arrondi de 16 px). */
.dx-hero-accent { position: absolute; inset: 0; border-radius: calc(var(--dx-radius) - 1px); pointer-events: none;
                  background: linear-gradient(180deg, var(--module-primary) 0%, rgba(var(--module-primary-rgb), .55) 100%);
                  -webkit-mask: linear-gradient(90deg, #000 5px, transparent 5px); mask: linear-gradient(90deg, #000 5px, transparent 5px); }
.dx-hero-avatar { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 32px; height: 32px;
                  border-radius: 10px; font-size: 1.1rem; color: var(--module-primary); background: rgba(var(--module-primary-rgb), .1); }
.dx-hero-avatar--photo { overflow: hidden; }
.dx-hero-avatar--photo img { width: 100%; height: 100%; object-fit: cover; }
.dx-hero-titles { flex: 0 1 auto; min-width: 0; }
.dx-hero-title { margin: 0; font-size: 1.15rem; font-weight: 700; line-height: 1.25; color: #1e293b; overflow-wrap: anywhere; }
.dx-hero-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .9rem; margin-top: .15rem;
                font-size: .78rem; color: var(--color-text-muted, #6b7280); }
.dx-hero-meta .bi { margin-right: .25rem; }
.dx-hero-stats { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; flex: 1 1 auto; min-width: 0; }
.dx-hero-stat { display: inline-flex; align-items: center; gap: .4rem; padding: .3rem .65rem; border: 1px solid #e5e7eb;
                border-radius: 999px; background: #fff; color: var(--bs-body-color, #1f2937); text-align: left; text-decoration: none;
                transition: border-color .15s, box-shadow .15s; }
.dx-hero-stat > .bi { font-size: .9rem; color: var(--module-primary); }
.dx-hero-stat > span { display: flex; align-items: baseline; gap: .3rem; }
.dx-hero-stat-value { font-size: .92rem; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.dx-hero-stat-label { font-size: .76rem; color: var(--color-text-muted, #6b7280); white-space: nowrap; }
a.dx-hero-stat:hover, button.dx-hero-stat:hover { border-color: rgba(var(--module-primary-rgb), .45); color: inherit; }
.dx-hero-stat.is-active { border-color: var(--module-primary); box-shadow: 0 0 0 2px rgba(var(--module-primary-rgb), .18); }
.dx-hero-stat.is-warn > .bi, .dx-hero-stat.is-warn .dx-hero-stat-value { color: var(--bs-warning-text-emphasis, #92400e); }
.dx-hero-stat.is-alert > .bi, .dx-hero-stat.is-alert .dx-hero-stat-value { color: var(--bs-danger-text-emphasis, #b91c1c); }
.dx-hero-stat.is-ok > .bi, .dx-hero-stat.is-ok .dx-hero-stat-value { color: var(--bs-success-text-emphasis, #166534); }
.dx-hero-actions { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; margin-left: auto; }
/* Sous 1200 px, les compteurs passent sur une 2e ligne pour ne pas écraser les actions. */
@media (max-width: 1199.98px) { .dx-hero-stats { order: 3; flex-basis: 100%; } }

/* ─── Recherche puis filtres, toujours ouverts ─────────────────────────────
   Ligne de recherche facultative ; la barre tl-* (filter_bar.css) en bande grise dessous. */
.dx-toolbar { margin-bottom: var(--dx-gap); background: #fff; border: 1px solid var(--dx-border);
              border-radius: var(--dx-radius); box-shadow: var(--dx-shadow-soft); }
.dx-toolbar-search { display: flex; align-items: center; gap: .5rem; margin: .5rem var(--dx-card-x); padding: .3rem .7rem;
                     border: 1px solid #dbe2ea; border-radius: 10px; background: #fff; transition: border-color .15s, box-shadow .15s; }
.dx-toolbar-search:focus-within { border-color: var(--module-primary); box-shadow: 0 0 0 3px rgba(var(--module-primary-rgb), .15); }
.dx-toolbar-search > .bi { color: #94a3b8; }
.dx-toolbar-search:focus-within > .bi { color: var(--module-primary); }
/* Double portée + !important : les règles globales input[type=…] de modern.css gagneraient sinon. */
.dx-toolbar .dx-toolbar-search input[type="search"],
.dx-toolbar .dx-toolbar-search input[type="text"] {
  flex: 1 1 auto; min-width: 0; height: auto !important; min-height: 0 !important; margin: 0 !important;
  padding: .1rem 0 !important; border: 0 !important; border-radius: 0 !important; background: transparent !important;
  box-shadow: none !important; outline: none; font-size: .85rem !important; color: var(--color-text, #1f2937); }
.dx-toolbar > .tl-bar { margin: 0; border: 0; border-top: 1px solid #edf0f4; border-radius: 0 0 var(--dx-radius) var(--dx-radius);
                        box-shadow: none; background: #f8fafc; animation: none; }
.dx-toolbar > .tl-bar:first-child { border-top: 0; border-radius: var(--dx-radius); }
.dx-toolbar > .tl-bar .tl-field:first-child { padding-left: var(--dx-card-x); }
.dx-toolbar > .tl-bar .tl-actions { padding-right: var(--dx-card-x); }
.dx-toolbar .tl-field { padding-top: .3rem; padding-bottom: .3rem; }   /* barre de filtres basse (2026-10-09) */
.dx-toolbar .tl-actions .tl-btn { display: inline-flex; align-items: center; gap: .35rem; height: 2rem; padding: 0 .8rem; font-size: .82rem; }
.dx-toolbar .tl-actions .tl-btn--icon { width: 2rem; padding: 0; justify-content: center; }

/* Recherche DANS le formulaire de filtres : quand la recherche doit rester dans le form.tl-bar
   (filtrage sans rechargement data-results, bandeau « non appliqué » de filter_bar.js), elle en
   occupe la première ligne, sur fond blanc, et les filtres passent dessous dans la bande grise.
   <div class="dx-toolbar"><form class="tl-bar" …>
     <div class="dx-toolbar-search-row"><div class="dx-toolbar-search"><i class="bi bi-search"></i>
       <input type="search" name="q" …></div></div>
     <div class="tl-field">…</div>… <div class="tl-actions">…</div></form></div> */
.dx-toolbar > form.tl-bar > .dx-toolbar-search-row { flex: 0 0 100%; min-width: 0; background: #fff;
  border-bottom: 1px solid #edf0f4; border-radius: var(--dx-radius) var(--dx-radius) 0 0; }
.dx-toolbar > form.tl-bar > .dx-toolbar-search-row + .tl-field { padding-left: var(--dx-card-x); }
.dx-toolbar > form.tl-bar:has(> .dx-toolbar-search-row) { border-top: 0; border-radius: var(--dx-radius); }

/* Filtre « Période » (2026-10-07) : UN intitulé, deux dates reliées par une flèche, au lieu de
   deux champs « Du » / « Au ». Champs sans bordure de la barre (filter_bar.css).
   <div class="tl-field tl-field--period" role="group" aria-labelledby="xPeriod">
     <span class="tl-label" id="xPeriod">Période</span>
     <div class="tl-period"><input type="date" class="tl-input" aria-label="Du">
       <span class="tl-period-sep" aria-hidden="true">→</span>
       <input type="date" class="tl-input" aria-label="Au"></div></div> */
.tl-field--period { width: auto; }
.tl-period { display: flex; align-items: center; gap: .35rem; }
/* Largeur au plus juste (« 00/00/0000 » en gras + icône) : l'icône calendrier suit la date au
   lieu de partir au bord droit d'un champ trop large. */
.tl-bar .tl-period .tl-input[type="date"] { width: calc(10ch + 1.25rem) !important; font-variant-numeric: tabular-nums; }
.tl-period .tl-input[type="date"]::-webkit-datetime-edit { padding: 0; }
.tl-period .tl-input[type="date"]::-webkit-calendar-picker-indicator { margin: 0 0 0 .15rem; padding: 0; width: .9rem; height: .9rem; }
.tl-period-sep { color: #94a3b8; font-size: .8rem; line-height: 1; }

/* Filtrage automatique (static/js/filter_auto.js pose `is-auto` sur le formulaire) : le
   bouton « Filtrer » et le bandeau « non appliqué » n'ont plus d'usage. */
form.is-auto .tl-btn--primary[type="submit"], form.is-auto .tl-pending { display: none !important; }

/* ─── Onglets intégrés en tête de la carte de liste ────────────────────────
   Jamais une balise <nav> : accueil/static/accueil/css/styles.css stylise tout <nav>. */
.dx-card-tabs { display: flex; gap: 1.6rem; padding: 0 var(--dx-card-x); background: #f8fafc; border-bottom: 1px solid #e5e7eb;
                overflow-x: auto; scrollbar-width: none; }
.dx-card-tabs::-webkit-scrollbar { display: none; }
.dx-card-tabs > a, .dx-card-tabs > button, .dx-card-tabs .dx-card-tab {
  display: inline-flex; align-items: center; gap: .45rem; margin-bottom: -1px; padding: .85rem 0 .7rem;
  border: 0; border-bottom: 2px solid transparent; border-radius: 0; background: none;
  font-size: .86rem; font-weight: 500; color: #64748b; text-decoration: none; white-space: nowrap; cursor: pointer; }
.dx-card-tabs > a:hover, .dx-card-tabs > button:hover, .dx-card-tabs .dx-card-tab:hover { color: #1f2937; }
.dx-card-tabs .is-active, .dx-card-tabs .active {
  color: var(--module-primary); border-bottom-color: var(--module-primary); font-weight: 600; }
/* Onglets Bootstrap (data-bs-toggle="tab", classe .nav-link) posés dans le bandeau : d'autres
   feuilles donnent à .nav-link / .active un fond, des coins et une bordure qui l'emportaient
   (suivi des modifications Knowledge, 2026-10-06). Le bandeau garde son soulignement. */
.dx-card-tabs .nav-link, .dx-card-tabs .nav-link:hover, .dx-card-tabs .nav-link:focus, .dx-card-tabs .nav-link.active {
  background: none !important; border: 0 !important; border-bottom: 2px solid transparent !important;
  border-radius: 0 !important; box-shadow: none !important; }
.dx-card-tabs .nav-link.active, .dx-card-tabs .nav-link.is-active {
  color: var(--module-primary) !important; border-bottom-color: var(--module-primary) !important; font-weight: 600; }
/* Bandeau autonome entre pages sœurs (menu Heures, 2026-10-12) : posé sous l'en-tête de la page,
   hors de toute carte, il en reprend le cadre. */
.dx-card-tabs.dx-page-tabs { margin-bottom: var(--dx-gap); background: #fff; border: 1px solid var(--dx-border);
                             border-radius: var(--dx-radius); box-shadow: var(--dx-shadow); }
.dx-tab-count { display: inline-block; min-width: 1.3rem; padding: 0 .4rem; border-radius: 999px; font-size: .68rem; font-weight: 700;
                line-height: 1.35rem; text-align: center; background: #e9edf2; color: #64748b; }
.dx-card-tabs .is-active .dx-tab-count, .dx-card-tabs .active .dx-tab-count {
  background: rgba(var(--module-primary-rgb), .12); color: var(--module-primary); }

/* ─── Cartes de contenu ────────────────────────────────────────────────── */
.dx-section { margin-bottom: var(--dx-gap); background: #fff; border: 1px solid var(--dx-border);
              border-radius: var(--dx-radius); box-shadow: var(--dx-shadow); overflow: hidden; }
.dx-section-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .5rem;
                   padding: .9rem var(--dx-card-x); border-bottom: 1px solid #eef0f3; }
.dx-section-title { margin: 0; display: flex; align-items: center; gap: .45rem; font-size: .75rem; font-weight: 700;
                    letter-spacing: .05em; text-transform: uppercase; color: #475569; }
.dx-section-title .bi { color: var(--module-primary); font-size: .95rem; }
.dx-section-link { font-size: .8rem; font-weight: 600; color: var(--module-primary); text-decoration: none; }
.dx-section-link:hover { text-decoration: underline; color: var(--module-primary); }
.dx-section-body { padding: var(--dx-card-y) var(--dx-card-x); }
.dx-section-body > * + * { margin-top: var(--dx-gap-inner); }
.dx-section-body--flush { padding: 0; }
.dx-section-foot { padding: .8rem var(--dx-card-x); border-top: 1px solid #eef0f3; background: #fafbfc;
                   font-size: .8rem; color: var(--color-text-muted, #6b7280); }
.dx-page > .row { --bs-gutter-x: var(--dx-gap); --bs-gutter-y: var(--dx-gap); margin-bottom: var(--dx-gap); }
.dx-page .row > [class*="col"] > .dx-section:last-child { margin-bottom: 0; }
/* Dernière carte de la page : pas de marge sous elle, la liste qui défile va jusqu'au bas de la fenêtre. */
.dx-page > .dx-section:last-child,
.dx-page > :last-child > section.dx-section:last-of-type { margin-bottom: 0; }   /* liste chargée dans un conteneur (projets) */

/* ─── Tableaux ─────────────────────────────────────────────────────────── */
/* Rendu unique de TOUTES les listes du site (demande du 2026-10-07 : « strictement les mêmes
   polices, mêmes tailles et couleurs »). Une seule série de valeurs (--dx-list-*), imposée à la
   table du kit et aux familles historiques qui la recopiaient avec des écarts : fl- (DxFleet),
   wh- (DxWarehouse), pj- (Projets), sl- (Sales), rb- (suivi Knowledge), nr- (rôles équipement),
   res- (Ressources). Sélecteur `:is(table.…)` : spécificité (0,1,4), au-dessus des règles de
   module (0,1,3 au plus) chargées après cette feuille. La grille des projets (div) reprend les
   mêmes variables (project_list.html). Pour changer le rendu des listes : ici seulement. */
:root {
  --dx-list-fs: .85rem;          /* texte des cellules */
  --dx-list-color: #1e293b;      /* texte des cellules */
  --dx-list-th-fs: .7rem;        /* en-tête de colonne */
  --dx-list-th-fw: 600;
  --dx-list-th-ls: .05em;
  --dx-list-th-color: #64748b;
  --dx-list-th-bg: #f9fafb;
  --dx-list-th-border: #e5e7eb;
  --dx-list-row-border: #f1f3f5;
  --dx-list-cell-y: .35rem;      /* haut/bas d'une cellule (2026-10-09, était .65rem) */
  --dx-list-cell-x: .5rem;       /* entre deux colonnes */
}
:is(table.dx-table, table.fl-table, table.wh-table, table.pj-table, table.sl-table, table.rb-table, table.nr-table, table.res-table) {
  font-size: var(--dx-list-fs); }
:is(table.dx-table, table.fl-table, table.wh-table, table.pj-table, table.sl-table, table.rb-table, table.nr-table, table.res-table) > thead > tr > th {
  background: var(--dx-list-th-bg); color: var(--dx-list-th-color); font-size: var(--dx-list-th-fs);
  font-weight: var(--dx-list-th-fw); letter-spacing: var(--dx-list-th-ls); text-transform: uppercase;
  white-space: nowrap; border-bottom: 1px solid var(--dx-list-th-border);
  padding: var(--dx-list-cell-y) var(--dx-list-cell-x); }
:is(table.dx-table, table.fl-table, table.wh-table, table.pj-table, table.sl-table, table.rb-table, table.nr-table, table.res-table) > tbody > tr > td {
  color: var(--dx-list-color); border-bottom: 1px solid var(--dx-list-row-border);
  padding: var(--dx-list-cell-y) var(--dx-list-cell-x); }
:is(table.dx-table, table.fl-table, table.wh-table, table.pj-table, table.sl-table, table.rb-table, table.nr-table, table.res-table) > :not(caption) > tr > :first-child {
  padding-left: var(--dx-card-x); }
:is(table.dx-table, table.fl-table, table.wh-table, table.pj-table, table.sl-table, table.rb-table, table.nr-table, table.res-table) > :not(caption) > tr > :last-child {
  padding-right: var(--dx-card-x); }
:is(table.dx-table, table.fl-table, table.wh-table, table.pj-table, table.sl-table, table.rb-table, table.nr-table, table.res-table) > tbody > tr:last-child > td {
  border-bottom: 0; }
/* Listes : défilement dans la carte plutôt qu'une page longue ou paginée (demande du
   2026-10-07). La table du kit défile dans son propre .table-responsive, hauteur bornée
   à l'écran (--dx-list-max, posé par static/js/dx_scroll.js : du haut de la liste au bas
   de la fenêtre), en-tête de colonnes collant. Ce conteneur
   est l'ancêtre de défilement du sticky : les overflow-x:hidden de modern.css ne le
   cassent pas. Une table courte n'est pas concernée. Automatique seulement sur une page
   de liste, c.-à-d. qui a une barre de recherche / filtres (.dx-toolbar, form.tl-bar) :
   un tableau d'affichage (fiche, récapitulatif, statistiques) garde sa hauteur. Une
   liste hors .dx-table (fl-, wh-, res-…) reçoit .dx-scroll sur son conteneur. Sous 768 px (mobile = consultation),
   la page défile normalement. Opt-out : .dx-no-scroll sur le .table-responsive.
   Liste en grille (div, pas <table>) : .dx-scroll sur le conteneur, .dx-scroll-head sur
   la ligne d'en-tête (fond opaque requis). */
.dx-page:has(.dx-toolbar, form.tl-bar) .dx-section .table-responsive:has(> .dx-table):not(.dx-no-scroll),
.dx-scroll { max-height: var(--dx-list-max, max(22rem, calc(100vh - 16rem))); overflow: auto; overscroll-behavior: contain; }
.dx-page:has(.dx-toolbar, form.tl-bar) .dx-section .table-responsive:has(> .dx-table):not(.dx-no-scroll) > .dx-table > thead > tr > th,
.dx-scroll > table > thead > tr > th { position: sticky; top: 0; z-index: 2; box-shadow: inset 0 -1px 0 #e5e7eb; }
.dx-scroll > .dx-scroll-head { position: sticky; top: 0; z-index: 2; }
/* Pied de liste (2026-10-09) : bande grise vide qui ferme la carte sous une liste qui défile,
   la dernière ligne coupée bute dessus (table du kit, ou liste en grille .dx-scroll comme les
   projets). Pseudo-élément de la carte : aucun gabarit à toucher,
   et dx_scroll.js le compte dans ce qui suit la liste. */
.dx-page:has(.dx-toolbar, form.tl-bar) .dx-section:has(.table-responsive:not(.dx-no-scroll) > .dx-table)::after,
.dx-section:has(.dx-scroll)::after {
  content: ""; display: block; position: relative; height: .75rem; border-top: 1px solid #e2e8f0; background: #f1f5f9;
  box-shadow: 0 -3px 6px -3px rgba(15, 23, 42, .12); }
/* Barre de défilement fine ; sur une table à en-tête collant, sa piste commence sous l'en-tête
   des colonnes. Chrome/Edge : pseudo-éléments -webkit- (ignorés dès que scrollbar-width est
   posé, d'où le @supports réservé à Firefox, qui n'a pas de marge de piste). */
.dx-page:has(.dx-toolbar, form.tl-bar) .dx-section .table-responsive:has(> .dx-table):not(.dx-no-scroll)::-webkit-scrollbar,
.dx-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.dx-page:has(.dx-toolbar, form.tl-bar) .dx-section .table-responsive:has(> .dx-table):not(.dx-no-scroll)::-webkit-scrollbar-track,
.dx-scroll::-webkit-scrollbar-track { background: transparent; }
.dx-page:has(.dx-toolbar, form.tl-bar) .dx-section .table-responsive:has(> .dx-table):not(.dx-no-scroll)::-webkit-scrollbar-track {
  margin-top: calc(var(--dx-list-cell-y) * 2 + var(--dx-list-th-fs) * 1.5 + 1px); }
.dx-page:has(.dx-toolbar, form.tl-bar) .dx-section .table-responsive:has(> .dx-table):not(.dx-no-scroll)::-webkit-scrollbar-thumb,
.dx-scroll::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; border: 2px solid #fff; }
.dx-page:has(.dx-toolbar, form.tl-bar) .dx-section .table-responsive:has(> .dx-table):not(.dx-no-scroll)::-webkit-scrollbar-thumb:hover,
.dx-scroll::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
.dx-page:has(.dx-toolbar, form.tl-bar) .dx-section .table-responsive:has(> .dx-table):not(.dx-no-scroll)::-webkit-scrollbar-corner,
.dx-scroll::-webkit-scrollbar-corner { background: transparent; }
@supports (-moz-appearance: none) {
  .dx-page:has(.dx-toolbar, form.tl-bar) .dx-section .table-responsive:has(> .dx-table):not(.dx-no-scroll),
  .dx-scroll { scrollbar-width: thin; scrollbar-color: #cbd5e1 transparent; }
}
@media (max-width: 767.98px) {
  .dx-section .table-responsive:has(> .dx-table), .dx-scroll { max-height: none; }
}
/* En-têtes triables de toutes les tables du site, posés par static/js/dx_sort.js
   (opt-out : data-no-sort sur la table, un parent ou le th) */
.dx-sort-btn { display: inline-flex; align-items: center; gap: .3rem; width: 100%; padding: 0; border: 0; background: none;
               color: inherit; font: inherit; letter-spacing: inherit; text-transform: inherit; text-align: inherit; cursor: pointer; }
th.num .dx-sort-btn, th.text-end .dx-sort-btn { justify-content: flex-end; }
th.text-center .dx-sort-btn { justify-content: center; }
.dx-sort-btn:hover { opacity: .8; }
.dx-sort-btn:focus-visible { outline: 2px solid var(--module-primary); outline-offset: 2px; border-radius: 4px; }
.dx-sort-ind { font-style: normal; font-family: 'bootstrap-icons'; font-size: .8em; line-height: 1; opacity: 0; transition: opacity .15s; }
.dx-sort-ind::before { content: "\f127"; } /* bi-arrow-down-up */
.dx-sort-btn:hover .dx-sort-ind { opacity: .5; }
th.dx-sortable[aria-sort] .dx-sort-btn { color: var(--module-primary, currentColor); }
.table-dark th.dx-sortable[aria-sort] .dx-sort-btn, thead.table-dark th.dx-sortable[aria-sort] .dx-sort-btn { color: inherit; }
th[aria-sort] .dx-sort-ind { opacity: 1; }
th[aria-sort="ascending"] .dx-sort-ind::before { content: "\f148"; }  /* bi-arrow-up */
th[aria-sort="descending"] .dx-sort-ind::before { content: "\f128"; } /* bi-arrow-down */
.dx-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.dx-table .nowrap { white-space: nowrap; }
.dx-table tr.dx-row-link { position: relative; cursor: pointer; }
.dx-table tr.is-alert > td:first-child { box-shadow: inset 3px 0 0 var(--bs-danger, #dc2626); }
.dx-table tr.is-warn > td:first-child { box-shadow: inset 3px 0 0 var(--bs-warning, #f59e0b); }
.dx-table tr.is-ok > td:first-child { box-shadow: inset 3px 0 0 var(--bs-success, #16a34a); }
.dx-table .dx-group-row > td { background: #f8fafc; font-size: .72rem; font-weight: 700; letter-spacing: .04em;
                               text-transform: uppercase; color: #475569; }

/* ─── État vide ────────────────────────────────────────────────────────── */
.dx-empty { display: flex; flex-direction: column; align-items: center; gap: .4rem; padding: 2.5rem var(--dx-card-x);
            text-align: center; color: var(--color-text-muted, #6b7280); font-size: .88rem; }
.dx-empty > .bi { font-size: 1.8rem; color: #cbd5e1; }
.dx-empty-title { font-weight: 600; color: #475569; }
.dx-empty .btn { margin-top: .35rem; }
td > .dx-empty { padding: 1.75rem var(--dx-card-x); }

/* ─── Pastilles ────────────────────────────────────────────────────────── */
.dx-pill { display: inline-flex; align-items: center; gap: .3rem; padding: .1rem .5rem; border-radius: 999px;
           font-size: .72rem; font-weight: 600; background: rgba(var(--module-primary-rgb), .1); color: var(--module-primary); }

/* ─── Mobile (< 768 px : consultation) ─────────────────────────────────── */
@media (max-width: 767.98px) {
}
