/* ===========================================================================
   MODÈLE ERP — le gabarit commun des écrans, préfixe de classes .mx-*
   («modèle»). À charger APRÈS easytech-theme.css, easytime-shell.css et
   fiche.css.

   D'OÙ ÇA VIENT
   Passation « Projet EasyTime — Design 7 », reçue le 2026-08-17 :
   docs/modeles/passation-design-7.md, captures dans docs/modeles/captures/.
   Deux écrans y font office de gabarits — la liste du personnel et la fiche
   salarié — et TOUS les autres écrans du produit en dérivent. Les valeurs de
   ce fichier sont celles de la passation ; elles ne se rediscutent pas écran
   par écran.

   CE QUE CE FICHIER CONTIENT, ET CE QU'IL NE CONTIENT PAS
   Il ne redéclare AUCUNE couleur : tout passe par les jetons --et-* de
   easytech-theme.css, qui reste la source unique (bascule du 2026-08-17). Il
   n'habille pas non plus la liste — le tableau, sa barre d'outils, sa
   sélection, son pied et sa pagination vivent dans le bloc .tbl-* de
   easytime-shell.css et sont servis par Components/Shared/Tableau.razor.

   Il porte les pièces du gabarit qui n'avaient AUCUN équivalent dans le dépôt :

     .mx-page      la trame de page — en-tête blanc, fil d'Ariane, titre,
                   barre d'outils à droite, filet de 3 px, corps plafonné
     .mx-btn       les boutons de barre d'outils (transparents / engagement)
     .mx-obj       l'en-tête de page objet et sa bande de faits saillants
     .mx-rail      le rail d'onglets groupés d'une fiche
     .mx-form      LA GRAMMAIRE DE FORMULAIRE DENSE — le point le plus
                   important de la passation, celui dont toute fiche dérive
     .mx-modale    la modale, sa grille et son bloc d'effets
     .mx-ind       la couche analytique : cartes d'indicateur et barres

   POURQUOI UN FICHIER À PART PLUTÔT QUE DANS fiche.css
   Le modèle .fx-* qu'il côtoie décrit une AUTRE fiche : bandeau marine à texte
   blanc, une carte de saisie à deux colonnes, libellés au-dessus des champs.
   Une quarantaine d'écrans en vivent. Écraser .fx-* sur place les aurait tous
   cassés d'un coup pour un gabarit qu'ils n'ont pas encore adopté ; les deux
   cohabitent, et les écrans passent au .mx-* un par un, en commençant par les
   deux écrans de référence. Le jour où plus rien ne porte .fx-*, ce fichier-là
   disparaît — pas avant.

   RÈGLES DE REPRISE
   - Aucune couleur en dur dans une page : les jetons, ou une classe d'ici.
   - Angles droits partout, sauf --et-r-btn (boutons de barre d'outils) et
     --et-r-pill (pastilles d'état, barres). Les champs de saisie n'ont
     JAMAIS d'arrondi.
   - Un écran ne porte qu'UN SEUL bouton orange (.mx-btn-cta) à la fois.
   - Sous un champ, une seule ligne à la fois : aide, erreur ou compteur.
   =========================================================================== */

/* ===========================================================================
   0. ANIMATIONS — deux, et deux seulement.
   La passation est explicite : « Rien d'autre ne bouge. » Les préfixer mx-
   évite la collision avec les fxUp/cbp-ouvre déjà posés ailleurs.
   =========================================================================== */
@keyframes mxUp { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes mxIn { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
    .mx-page, .mx-obj, .mx-panneau, .mx-modale-cadre, .mx-sur { animation: none !important; }
}

/* ===========================================================================
   1. TRAME DE PAGE — commune aux deux gabarits.

   Le plafond de 1600 px n'est pas décoratif : au-delà, une rangée de tableau
   sépare tellement son matricule de son état qu'il faut suivre la ligne du
   doigt. La gouttière de 24 px et le padding bas de 52 px viennent de la
   passation ; le second laisse la place au dernier bouton d'une fiche longue
   sans qu'il colle au bord de la fenêtre.
   =========================================================================== */
.mx-page {
    min-height: 100%;
    background: var(--et-surface-2);
    font-family: var(--et-font);
    color: var(--et-ink);
}

.mx-page-tete {
    background: var(--et-surface);
    border-bottom: 3px solid var(--et-form-strip);
}
.mx-page-tete-in,
.mx-page-corps {
    max-width: 1600px;
    margin: 0 auto;
}
.mx-page-tete-in { padding: 12px 24px; }
.mx-page-corps { padding: 18px 24px 52px; }

/* Fil d'Ariane. Le dernier segment n'est pas un lien : c'est la page où l'on
   est. Le rendre cliquable ferait recharger l'écran déjà ouvert. */
.mx-fil {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    font-size: 13px; color: var(--et-faint);
}
.mx-fil a { color: var(--et-ink); font-weight: 500; text-decoration: none; }
.mx-fil a:hover { color: var(--et-form-blue-d); text-decoration: underline; }
.mx-fil .sep { color: var(--et-inerte); }
.mx-fil .courant { color: var(--et-ink); }

.mx-page-titre-rang {
    margin-top: 7px;
    display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap;
}
.mx-page-titre-txt { flex: 1; min-width: 0; }
.mx-page-titre-txt h1 {
    margin: 0; font-size: 23px; font-weight: 700; letter-spacing: -0.025em;
    color: var(--et-ink);
}
/* Le résumé porte le nombre de lignes retenues et le total — l'information la
   plus lue de l'écran après la liste. Le gris de la passation (#6A6D70) donne
   5,74:1 sur blanc : au-dessus du seuil, contrairement au #89919A des notes,
   qui reste réservé aux méthodes de calcul. */
.mx-page-resume { margin-top: 4px; font-size: 13px; color: var(--et-muted); }

/* ---------------------------------------------------------------------------
   BARRE D'OUTILS D'EN-TÊTE — les actions GLOBALES de l'écran, à droite du titre.
   Elle n'est pas dans la carte : une action qui vaut pour l'écran entier ne se
   range pas dans l'un de ses blocs. Le pied de carte, symétriquement, ne porte
   AUCUNE action — seulement la mention explicative.
   --------------------------------------------------------------------------- */
.mx-outils {
    flex: none; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}

.mx-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    height: 32px; padding: 0 12px;
    border: 1px solid transparent; border-radius: var(--et-r-btn);
    background: transparent; color: var(--et-form-blue-encre);
    font-family: inherit; font-size: 14px; font-weight: 600;
    /* Open Sans est large : sans nowrap, « Historique paie » se coupe en deux
       dans un bouton à hauteur fixe et déborde par le bas. */
    white-space: nowrap; cursor: pointer;
    transition: background .12s, color .12s;
}
.mx-btn:hover:enabled { background: var(--et-tint); color: var(--et-form-blue-d); }
.mx-btn:focus-visible { outline: 2px solid var(--et-form-blue); outline-offset: 1px; }
.mx-btn:disabled { color: var(--et-inerte); cursor: not-allowed; }

/* Bouton blanc : une action réelle mais secondaire — Annuler, Suivant, Exporter. */
.mx-btn-blanc {
    background: var(--et-surface); border-color: var(--et-form-border); color: var(--et-ink);
}
.mx-btn-blanc:hover:enabled { background: var(--et-surface); border-color: var(--et-inerte); }

/* LE bouton d'engagement. Un par écran. Modifier, Enregistrer, Nouveau X,
   Se connecter, le CTA d'une modale de création — rien d'autre. */
.mx-btn-cta {
    background: var(--et-action); color: var(--et-sur-aplat); border-color: var(--et-action);
}
.mx-btn-cta:hover:enabled { background: var(--et-action-d); border-color: var(--et-action-d); color: var(--et-sur-aplat); }
/* Délavé et non transparent : un CTA à demi effacé se lit comme un défaut
   d'affichage, alors qu'il dit « il manque quelque chose ». Le not-allowed le
   confirme au survol, avant le clic. */
.mx-btn-cta:disabled {
    background: var(--et-action-off); border-color: var(--et-action-off); color: var(--et-sur-aplat);
    cursor: not-allowed;
}

/* Action destructrice : bordure rouge pâle, jamais un aplat. Un bouton rouge
   plein attire l'œil vers ce qu'on veut justement qu'il évite. */
.mx-btn-danger {
    background: var(--et-surface); border-color: var(--et-danger-bord); color: var(--et-danger-ink);
}
.mx-btn-danger:hover:enabled { background: var(--et-danger-badge-bg); border-color: var(--et-danger); }

/* Variante 42 px — la barre d'outils de la LISTE, pas celle de l'en-tête de page.
   Elle est à ANGLES DROITS, et ce n'est pas une inattention : la passation ne tolère
   les 4 px que « sur les boutons de la barre d'outils d'en-tête ». Ses maquettes de
   liste donnent bien un « Exporter » et un « Nouveau salarié » de 42 px sans le
   moindre arrondi. La règle qui en découle tient en une ligne : 32 px arrondis près
   du titre, 42 px carrés au-dessus de la table. */
.mx-btn-42 { height: 42px; padding: 0 17px; border-radius: 0; }
.mx-btn-ico { width: 42px; height: 42px; padding: 0; border-radius: 0; }

/* Variante MINI — les commandes de FIN DE LIGNE d'une table, pas une barre d'outils.

   ELLE ÉTAIT ÉCRITE DANS QUATRE ÉCRANS ET DÉFINIE DANS AUCUNE FEUILLE.
   Relevé le 2026-09-08 en portant la console d'administration au gabarit :
   `mx-btn mx-btn-mini` apparaît dans Abonnes, Sauvegardes, JournalAudit et
   Tenants — quatre écrans qui rendaient donc un bouton de barre d'outils de
   32 px et 14 px de texte dans une cellule de 22 px de haut. La classe n'était
   pas mal réglée : elle n'existait pas. C'est le pire cas du dépôt, parce que
   rien ne le signale — le balisage est juste, la cascade ne trouve rien à
   appliquer, et l'écran a l'air simplement mal aligné.

   Sa place est donc ici, à côté des deux autres variantes de taille, et non
   dans une cinquième feuille d'écran : la règle du dépôt est qu'une classe
   servant deux écrans monte dans le gabarit (CLAUDE.md).

   LES VALEURS SUIVENT `.tbl .btn-sm` juste au-dessus dans easytime-shell.css —
   24 px de haut, 12 px de texte, angles droits. C'est le bouton que les tables
   Bootstrap du dépôt servent déjà en fin de ligne : la mesure existait, elle
   n'avait pas d'équivalent .mx-*, si bien qu'un écran passé au gabarit perdait
   la taille que l'écran d'à côté avait encore.

   AUCUN PALIER TACTILE ICI : le §12 en pose un pour toute la famille .mx-btn
   (min-height 44 px sous 1024 px, height auto) et il est déclaré APRÈS cette
   ligne — il la reprend donc sans qu'elle ait à le redire. Une cible ne
   rétrécit pas, y compris quand elle est « mini ». */
.mx-btn-mini { height: 24px; padding: 0 8px; border-radius: 0; font-size: 12px; gap: 5px; }
.mx-btn-mini.mx-btn-ico { width: 24px; padding: 0; }

/* LE GABARIT DE BOUTON PORTÉ PAR UN LIEN.
   « Nouveau X » mène à une fiche dès que l'objet en a une : c'est alors une
   ADRESSE, donc un <a> — qui s'ouvre dans un onglet, se met en favori et se
   parcourt au clavier comme un lien. L'apparence, elle, ne change pas : c'est
   toujours le bouton d'engagement de la barre d'outils.
   Ces deux lignes sont nécessaires et non décoratives : :enabled ne s'applique
   qu'aux contrôles de formulaire, si bien qu'un <a class="mx-btn"> resterait
   souligné et inerte au survol. */
a.mx-btn { text-decoration: none; }
a.mx-btn:hover { background: var(--et-tint); color: var(--et-form-blue-d); }
a.mx-btn-blanc:hover { background: var(--et-surface); border-color: var(--et-inerte); color: var(--et-ink); }
a.mx-btn-cta:hover { background: var(--et-action-d); border-color: var(--et-action-d); color: var(--et-sur-aplat); }

/* ===========================================================================
   2. CARTE — surface plate. Ni ombre ni arrondi : « la structure tient par les
   filets ». Le bandeau et le pied reculent d'un ton pour que la zone de données
   reste la seule surface blanche.
   =========================================================================== */
.mx-carte {
    background: var(--et-surface);
    border: 1px solid var(--et-hairline);
    animation: mxUp .3s ease both;
}
.mx-carte-tete {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 14px;
    background: var(--et-surface-2);
    border-bottom: 1px solid var(--et-hairline);
}
.mx-carte-titre { font-size: 14px; font-weight: 700; color: var(--et-table-head-encre); }
/* LA SECONDE LIGNE DU BANDEAU, quand la carte en a besoin : « 7 jours »,
   « 148 salariés attendus », « rien à traiter ». Elle EXPLIQUE, elle n'intitule
   pas — d'où la casse basse et le gris atténué, comme .mx-groupe-note. Elle
   arrive avec le portage de l'accueil du pointage (2026-09-08), dont les cinq
   cartes portaient toutes un sous-titre dans leur `.md-head`. */
.mx-carte-note { font-size: 12px; color: var(--et-faint); }
.mx-carte-corps { padding: 14px 0; }
/* LE PIED NE PORTE PAS D'ACTION. Il porte la mention qui explique ce que la
   carte fait — d'où le fond alterné et le gris atténué. Les actions sont dans
   la barre d'outils d'en-tête, une fois pour toutes. */
.mx-carte-pied {
    padding: 10px 14px;
    background: var(--et-surface-3);
    border-top: 1px solid var(--et-hairline);
    font-size: 13px; color: var(--et-faint);
}

/* ===========================================================================
   3. PAGE OBJET — l'en-tête d'une fiche.

   Fond #F5F6F7, filet inférieur de 3 px : le même filet que la page, repris un
   cran plus bas. C'est ce qui fait lire l'objet comme une seconde entête et non
   comme le premier bloc du formulaire.
   =========================================================================== */
.mx-obj {
    background: var(--et-surface-2);
    border-bottom: 3px solid var(--et-form-strip);
    animation: mxIn .16s ease both;
}
.mx-obj-rang {
    display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap;
    padding: 12px 18px;
}
.mx-obj-id { flex: 1; min-width: 0; }
.mx-obj-nom-rang { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.mx-obj-nom { margin: 0; font-size: 20px; font-weight: 700; color: var(--et-ink); }
.mx-obj-sous {
    margin-top: 3px; font-family: var(--et-font-mono); font-size: 13px;
    color: var(--et-muted);
}
.mx-obj-photo {
    flex: none; width: 44px; height: 44px;
    display: grid; place-items: center;
    background: var(--et-surface); border: 1px solid var(--et-hairline);
    font-size: 15px; font-weight: 600; color: var(--et-muted);
    overflow: hidden;
}
.mx-obj-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Pastille d'état — la SEULE pilule du modèle avec la barre de progression.
   Le point de 6 px double la teinte : il reste lisible pour qui ne distingue
   pas le vert du rouge, et c'est lui qui porte le signal quand la pastille est
   imprimée en noir et blanc. */
.mx-pastille {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 10px; border-radius: var(--et-r-pill);
    font-size: 12px; font-weight: 600; white-space: nowrap;
}
.mx-pastille::before {
    content: ""; width: 6px; height: 6px; border-radius: 50%;
    background: currentColor; flex: none;
}
.mx-pastille.est-positif  { color: var(--et-succes-ink);        background: var(--et-succes-bg); }
.mx-pastille.est-critique { color: var(--et-avertissement-ink); background: var(--et-avertissement-bg); }
.mx-pastille.est-negatif  { color: var(--et-danger-ink);        background: var(--et-danger-badge-bg); }
.mx-pastille.est-neutre   { color: var(--et-neutre);        background: var(--et-neutre-bg); }
.mx-pastille.est-info     { color: var(--et-form-blue-d);   background: var(--et-tint); }

/* ---------------------------------------------------------------------------
   BANDE DE FAITS SAILLANTS
   Les filets viennent de la GOUTTIÈRE, pas de bordures : gap: 1px sur un fond
   #E5E5E5 laisse voir le fond entre les tuiles. C'est ce qui évite le filet
   double au repli — avec des border-right, la dernière tuile d'une rangée et la
   première de la suivante en portaient deux, et la grille se déformait dès
   qu'elle passait à la ligne.
   --------------------------------------------------------------------------- */
/* FLEX ET NON GRILLE, depuis 2026-08-26. La grille en auto-fit posait un nombre
   de colonnes fixe par rangée : cinq tuiles sur deux colonnes en laissaient une
   SIXIÈME, vide — et comme les filets viennent du fond du conteneur, cette
   cellule vide ne se lisait pas comme un blanc mais comme une tuile grise sans
   valeur. Sur une bande de faits saillants, une case grise se cherche.
   En flex, la dernière rangée se PARTAGE la largeur restante : plus de trou, et
   les rangées pleines restent régulières puisque toutes les tuiles ont la même
   base. Le prix est que les colonnes ne s'alignent plus d'une rangée à l'autre
   quand le compte est impair — un décalage contre un trou, le trou est pire. */
.mx-faits {
    display: flex; flex-wrap: wrap;
    gap: 1px; background: var(--et-hairline);
    border-top: 1px solid var(--et-hairline);
}
.mx-fait { flex: 1 1 148px; background: var(--et-surface-2); padding: 6px 10px; min-width: 0; }
.mx-fait-lbl {
    font-family: var(--et-font-mono); font-size: 12px; font-weight: 500;
    text-transform: uppercase; letter-spacing: .16em; color: var(--et-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mx-fait-val { margin-top: 2px; font-size: 13px; font-weight: 600; color: var(--et-ink); }
.mx-fait-val.mono { font-family: var(--et-font-mono); font-weight: 500; }
/* La précision suit la valeur, en clair et en gris : « 40,00 h semaine standard
   40 h ». Un chiffre sans sa base est une devinette. */
.mx-fait-val .prec { margin-left: 6px; font-size: 12px; font-weight: 400; color: var(--et-faint); }
.mx-fait-val.est-positif  { color: var(--et-succes-ink); }
.mx-fait-val.est-critique { color: var(--et-avertissement-ink); }
.mx-fait-val.est-negatif  { color: var(--et-danger-ink); }

/* ===========================================================================
   4. RAIL D'ONGLETS GROUPÉS + PANNEAUX

   Le rail est une COLONNE, pas une rangée : une fiche salarié porte une
   vingtaine d'onglets, qui en rangée se replient sur trois lignes ou se cachent
   derrière un « … ». En colonne ils tiennent tous, et surtout ils se GROUPENT —
   Dossier, Contrat & paie, Suivi RH, Temps & présence, Sortie — ce qu'une
   rangée ne permet pas.
   =========================================================================== */
.mx-fiche {
    display: grid; grid-template-columns: 232px minmax(0, 1fr);
    background: var(--et-surface);
    border: 1px solid var(--et-hairline);
    min-height: 620px;
}
/* Le retrait haut est celui du PANNEAU (.mx-panneau, 12 px) : les deux colonnes
   commencent sur la même ligne, et la fiche se lit comme un seul bloc. Le rail
   partait 4 px plus haut et son premier intitulé 2 px plus bas — assez pour que
   rien ne s'aligne, pas assez pour qu'on voie pourquoi. */
.mx-rail {
    background: var(--et-surface-2);
    border-right: 1px solid var(--et-hairline);
    padding: 12px 0 16px;
    overflow-y: auto;
}
.mx-rail-groupe + .mx-rail-groupe { margin-top: 10px; }
.mx-rail-lbl {
    padding: 6px 12px 4px;
    font-family: var(--et-font-mono); font-size: 12px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .16em; color: var(--et-table-head-encre);
}
/* Le premier intitulé ne reprend PAS le retrait haut du groupe : il est déjà posé
   par le rail. Les suivants le gardent, c'est lui qui sépare deux groupes. */
.mx-rail-groupe:first-child .mx-rail-lbl { padding-top: 0; }
.mx-onglet {
    display: flex; align-items: center; gap: 9px; width: 100%;
    padding: 6px 12px;
    /* Le filet gauche existe TOUJOURS, transparent au repos : sans lui, le
       libellé se décalait de 3 px au changement d'onglet. */
    border: 0; border-left: 3px solid transparent;
    background: transparent; color: var(--et-ink);
    font-family: inherit; font-size: 13px; text-align: left; cursor: pointer;
}
.mx-onglet:hover { background: var(--et-rang); }
.mx-onglet.est-actif {
    background: var(--et-surface); border-left-color: var(--et-form-blue);
    color: var(--et-form-blue-encre); font-weight: 700;
}
.mx-onglet-ico { flex: none; width: 15px; text-align: center; color: var(--et-muted); font-size: 12px; }
.mx-onglet.est-actif .mx-onglet-ico { color: var(--et-form-blue-encre); }
.mx-onglet-lbl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Les panneaux SE SUPERPOSENT dans une seule cellule de grille, les inactifs en
   display: none. Empilés à la suite, la hauteur de la carte sautait d'un onglet
   à l'autre et la page se remettait à défiler à chaque changement. */
.mx-panneaux { display: grid; min-width: 0; }
.mx-panneau { grid-area: 1 / 1; min-width: 0; padding: 12px 0 18px; animation: mxIn .16s ease both; }
.mx-panneau[hidden], .mx-panneau.est-masque { display: none; }

/* ===========================================================================
   5. FORMULAIRE DENSE — LA GRAMMAIRE DE RÉFÉRENCE.

   « C'est le point le plus important de la passation. Toute fiche du produit
   suit exactement ce modèle. »

   Libellé à DROITE dans une colonne fixe de 158 px : l'œil descend le long du
   bord droit des libellés, qui touche le bord gauche des valeurs, et les deux
   colonnes se lisent comme une seule. Un libellé aligné à gauche laisse entre
   lui et sa valeur un blanc variable, d'autant plus large que le libellé est
   court — c'est ce blanc qui oblige à suivre la ligne du doigt sur une fiche de
   quarante champs.

   AUCUN FILET ENTRE LES RANGÉES. Seuls les en-têtes de groupe en portent : à
   raison d'un filet par champ, une fiche dense devient une grille de tableur.
   =========================================================================== */
/* AUCUNE GOUTTIÈRE ENTRE LES RANGÉES — la passation n'en pose pas, et la capture
   de référence le confirme : « padding: 2px 18px » sur .mx-champ, rien d'autre.
   Ces 8 px triplaient l'écart (2 + 8 + 2 au lieu de 2 + 2) et se payaient sur
   CHAQUE rangée de CHAQUE fiche du produit.

   MESURÉ SUR docs/modeles/captures/01-fiche.png, qui « fait foi en recette » :
   les rangées en lecture du bloc Contact s'y suivent à 14 px sur une capture
   réduite à 909 px, soit 24,6 px au 1600 px d'origine. Avec la gouttière nous
   étions à 35 px ; sans elle, à 27 — les 2 px qui restent sont ceux que la
   révision de valeurs du 2026-09-02 a ajoutés au corps du libellé et de la
   valeur, et ils sont assumés. */
.mx-form { display: flex; flex-direction: column; }

.mx-champ {
    display: grid; grid-template-columns: minmax(0, 158px) minmax(0, 1fr);
    gap: 12px; align-items: start;
    padding: 2px 18px;
    /* LA HAUTEUR DU CONTRÔLE, NOMMÉE — c'est le libellé qui la lit, plus bas, pour
       se centrer dessus. Elle vaut 34 px ici, 40 dans une fenêtre (§6) et 44 sous
       le palier tactile : trois valeurs, donc un chiffre en dur aurait réparé un
       cadre en cassant les deux autres. Le jeton est redéclaré à CÔTÉ de chacune
       des trois règles de hauteur, jamais ailleurs — les deux se déplacent
       ensemble ou le libellé se décale à nouveau. */
    --mx-champ-h: 34px;
}
.mx-champ-lbl {
    /*  MARGE REMISE À ZÉRO, ET CE N'EST PAS UNE PRÉCAUTION DE STYLE. app.css:60 pose
        « label { margin: 0.85rem 0 0.35rem } » — un sélecteur d'élément NU, sans
        garde, chargé avant toutes les feuilles du gabarit. Il ajoutait 13,6 px
        au-dessus et 5,6 px au-dessous de CHAQUE libellé de rangée : 19,2 px par
        rangée, sur toutes les fiches du produit.
        C'est ce qui faisait qu'une rangée de saisie mesurait 63 px en production
        là où la charte en pose 44, et que le banc du gabarit — qui ne référençait
        pas app.css — n'en voyait rien. Le banc la charge maintenant.
        La règle nue elle-même touche 1 999 <label> du dépôt : la retirer est une
        décision de charte, pas un geste de lot. Ici on la neutralise là où le
        gabarit décide, et nulle part ailleurs. */
    margin: 0;
    padding-top: 6px;              /* aligne le libellé sur la 1re ligne de la valeur */
    text-align: right;
    font-size: 12px; font-weight: 500; line-height: 1.35;
    color: var(--et-muted);
}
.mx-champ-lbl .req { color: var(--et-danger-ink); margin-left: 2px; }
/* min-width:0 est indispensable et pas décoratif : sans lui un enfant de grille
   refuse de se réduire sous la largeur de son contenu, et une valeur SANS espace
   (e-mail, IBAN, URL) élargit la colonne au lieu de se couper — overflow-wrap
   seul ne suffit pas à le compenser. */
.mx-champ-val { min-width: 0; overflow-wrap: anywhere; font-size: 13px; font-weight: 500; color: var(--et-ink); padding-top: 5px; }
/* Valeurs techniques — matricule, numéro de série, IBAN, dates, heures,
   montants. La mono n'est pas un style : elle aligne les chiffres et rend
   comparables deux valeurs posées l'une sous l'autre. */
.mx-champ-val.mono { font-family: var(--et-font-mono); font-size: 14px; }
/* UNE VALEUR QUI PORTE UN BOUTON SE RANGE EN LIGNE, ET SE CENTRE. Un .mx-btn est
   un inline-block de 24 px : posé sur la même ligne qu'un texte de 18, il emporte
   la ligne de base de la ligne entière et pousse le texte de 12,5 px sous le
   libellé — mesuré sur la rangée « Société » d'un terminal non affecté, la seule
   du produit qui mette les deux côte à côte. Le flex rend au texte sa ligne. */
.mx-champ-val:has(> .mx-btn) { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-height: var(--mx-champ-h); }
/* Un tiret, JAMAIS un zéro : « l'un dit inconnu, l'autre dit aucun ». */
.mx-vide { color: var(--et-inerte); }

/* UNE VALEUR QUI SE LIT ET NE SE SAISIT PAS, dans une rangée qui, elle, en attend une.
   À distinguer de .mx-vide : celui-ci dit « rien ici », celle-là dit « quelque chose ici,
   mais pas votre affaire ». Le cas typique est un chiffre décidé ailleurs — la durée chômée
   d'un férié, qui vient du poste du salarié, ou un taux qui vit sur la nature d'heures.
   Le TAIRE serait pire que l'afficher : on le chercherait, et on finirait par le saisir
   quelque part où il n'a pas d'effet.
   Sans cadre ni fond, à la différence d'un champ désactivé : un input grisé se lit comme
   « saisissable plus tard », alors que cette valeur ne le sera jamais ici. La note de la
   rangée (ChampFiche.Aide) dit d'où elle vient — c'est elle qui porte l'explication. */
.mx-valeur-inerte {
    display: inline-block;
    /* AUCUN RETRAIT VERTICAL : .mx-champ-val en pose déjà un (5 px), et le sien
       s'y ajoutait. Cumulés sur une boîte inline-block — dont la ligne de base
       est celle de sa dernière ligne, retrait compris — le décalage mesurait
       12,5 px entre la ligne de base du libellé et celle de la valeur. Sur la
       fiche d'un terminal c'est la rangée « Société », et c'est la seule chose
       qui restait visiblement de travers dans un formulaire dense. */
    font-size: 13px;
    color: var(--et-muted);
}

/* Lien dans une valeur ou une cellule de tableau. Il porte le bleu SAP et la
   graisse 600 de la passation : « a { color: #0A6ED1; font-weight: 600 } ».
   Il existe comme classe et non seulement comme <a> nu parce qu'il est posé dans
   des rangées cliquables, où il doit rester reconnaissable comme la cible précise
   du clic — celle qui accepte le clic milieu et s'annonce aux lecteurs d'écran. */
.mx-lien { color: var(--et-form-blue-encre); font-weight: 600; text-decoration: none; }
.mx-lien:hover { color: var(--et-form-blue-d); text-decoration: underline; }

/* LE LIBELLÉ SE CENTRE SUR LE CONTRÔLE (2026-09-10, issue #1152).

   Les deux règles qui suivent alignent les BORDS HAUTS du libellé et du contrôle.
   C'est juste pour une valeur en lecture, dont la première ligne est la cible ;
   c'est faux pour une boîte de 34, 40 ou 44 px. Mesuré sur le rendu, en comparant
   le centre de la première ligne du libellé au centre du contrôle : le libellé
   était 8,9 px trop haut en page et 11,9 px trop haut en fenêtre. Les rangées en
   lecture, elles, tombaient à 0,0 px — parfaites. Sur une fiche où les deux se
   suivent dans la même colonne, l'œil ne voit que le décalage.

   Le libellé prend donc la hauteur du contrôle et s'y centre. Un intitulé sur
   deux lignes grandit avec, et reste centré.

   NI .mx-zone, NI .mx-check. Une zone de texte fait 68 px et plus : sa première
   ligne est la bonne cible, la centrer sur le bloc éloignerait l'intitulé de ce
   qu'il désigne. Une case à cocher mesurait déjà à 2 px près.

   MAIS .mx-canal, OUI (ajouté le 2026-09-10, après coup). C'est la SECONDE des
   deux enveloppes du gabarit, et elle manquait là où .mx-input-suffixe a été
   ajoutée pour exactement la même raison : le champ courriel/téléphone enveloppe
   son input dans un div, si bien que la rangée ne « voit » aucun contrôle en
   enfant direct. Le défaut est le même, il coûte plus cher — un suffixe est rare,
   un champ de contact est sur toutes les fiches de tiers, de salarié et
   d'utilisateur. Et il s'est AGGRAVÉ en se réparant autour : mesuré sur la
   fenêtre d'identité du salarié, ces quatre rangées-là restaient à 11,5 px quand
   les dix autres du même formulaire tombaient à 0,6. Un décalage isolé au milieu
   d'une colonne alignée se voit mieux qu'un décalage général.
   ET .mx-btn AUSSI (même jour, autre lot) : la rangée qui pose une valeur et un
   bouton côte à côte. Un bouton est un contrôle — il se clique —, et il emporte la
   ligne de base de sa ligne avec lui : 12,5 px sur la rangée « Société » d'un
   terminal non affecté, la seule du produit dans ce cas. Trois enveloppes, une
   seule liste, et c'est la troisième fois qu'on l'oublie. */
.mx-champ:has(> .mx-champ-val > :is(.mx-input, .mx-select, .mx-input-suffixe, .mx-canal, .mx-btn)) > .mx-champ-lbl {
    /* LES 6 px DE RETRAIT HAUT PARTENT AVEC L'ALIGNEMENT QU'ILS SERVAIENT. Ils
       calent le libellé sur la première ligne d'une valeur EN LECTURE ; sur une
       rangée de saisie, c'est le centrage qui s'en charge, et les garder décalait
       encore de 5 à 6 px — mesuré, et constant dans les trois hauteurs, ce qui
       les désignait. */
    padding-top: 0;
    min-height: var(--mx-champ-h);
    display: flex; align-items: center; justify-content: flex-end;
}
/* ET LE CONTRÔLE REND SES 6 px DE MARGE, qui ne compensaient que le retrait
   ci-dessus. Retirer l'un sans l'autre laisse un décalage de 6 px — constant dans
   les trois hauteurs, ce qui l'a désigné du premier coup : les deux boîtes font
   alors la même hauteur, et seul leur bord haut diffère.

   Même portée que le centrage, à dessein : une rangée .mx-zone garde son retrait
   et garde donc sa marge. Les deux règles sont vraies ensemble ou fausses
   ensemble. */
.mx-champ:has(> .mx-champ-val > :is(.mx-input, .mx-select, .mx-input-suffixe, .mx-canal, .mx-btn))
  > .mx-champ-val > :is(.mx-input, .mx-select) { margin-top: 0; }

/* Sur une rangée qui porte un contrôle, le padding-top de la valeur disparaît :
   le champ se cale de lui-même sur le libellé. */
/* .mx-input-suffixe EST DANS LA LISTE, et son absence coûtait 5 px. Le champ à
   suffixe enveloppe son input dans un div : la valeur ne « voyait » donc aucun
   contrôle en enfant direct, gardait son retrait de 5 px, et le seul champ de la
   fiche terminal qui en porte un était le seul à rester décalé. Un défaut qui ne
   se voit que sur la rangée qui l'a. */
/* .mx-canal Y EST POUR LA MÊME RAISON, et elle coûtait les mêmes 5 px : les deux
   enveloppes du gabarit se tiennent, l'une ne va pas dans cette liste sans
   l'autre. */
.mx-champ-val:has(> .mx-input, > .mx-select, > .mx-zone,
                  > .mx-input-suffixe, > .mx-canal, > .mx-btn) { padding-top: 0; }
/* … MAIS LE CONTRÔLE, LUI, N'HÉRITAIT D'AUCUN DÉCALAGE : son bord haut restait
   collé au haut de la cellule, 6 px au-dessus de celui du libellé (qui porte ce
   6 px en padding-top pour se caler sur la 1re ligne d'une VALEUR EN LECTURE).
   Le padding-top:0 ci-dessus enlève le seul décalage qui compensait — d'où le
   correctif : le reporter sur le contrôle lui-même, en marge et non en padding,
   pour ne pas agrandir sa boîte de 34 px. Scopé à .mx-champ-val (comme la règle
   au-dessus) : .mx-input/.mx-select/.mx-zone servent aussi hors fiche dense —
   modales, filtres — où aucun libellé voisin n'a ce 6 px à compenser. */
.mx-champ-val > .mx-input, .mx-champ-val > .mx-select, .mx-champ-val > .mx-zone { margin-top: 6px; }

.mx-input, .mx-select, .mx-zone {
    width: 100%; height: 34px; padding: 0 11px;
    border: 1px solid var(--et-form-border); border-radius: 0;   /* jamais d'arrondi */
    background: var(--et-surface); color: var(--et-ink);
    font-family: inherit; font-size: 13px; outline: none;
}
.mx-zone { height: auto; min-height: 68px; padding: 8px 11px; line-height: 1.45; resize: vertical; }
/* LE CHEVRON EST PORTÉ PAR LE CHAMP, pas posé à la main à côté de lui.
   Le modèle .fx-* demandait un <div class="fx-champ"> enveloppant et un
   <span class="fx-chevron"> frère du <select> : trois éléments et deux classes
   pour une flèche, à recopier sur chaque liste déroulante — une trentaine rien
   que sur la fiche salarié, et il en manquait déjà sur cinq d'entre elles.
   En image de fond, il ne peut plus être oublié ni désaligné.
   La teinte est écrite en dur dans le SVG : une image de fond ne voit pas
   currentColor. C'est #6A6D70, la valeur de --et-muted — le seul endroit du
   gabarit où un jeton est recopié, et il l'est ici par contrainte du format. */
.mx-select {
    appearance: none; -webkit-appearance: none;
    padding-right: 30px; cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none' stroke='%236A6D70' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 11px center;
}
/* Une liste déroulante inerte perd son chevron : il annonce un menu qui ne
   s'ouvrira pas. */
.mx-select:disabled { background-image: none; }
.mx-input.mono { font-family: var(--et-font-mono); }
.mx-input::placeholder { color: var(--et-faint); }
.mx-input:focus, .mx-select:focus, .mx-zone:focus { border-color: var(--et-form-blue); }
.mx-input.est-faux, .mx-select.est-faux, .mx-zone.est-faux { border-color: var(--et-danger); }
/* Lecture seule : GRISÉ MAIS LISIBLE. Un champ à 40 % d'opacité ne se lit plus,
   et une fiche en consultation n'est faite que pour être lue. */
.mx-input:disabled, .mx-select:disabled, .mx-zone:disabled,
.mx-input[readonly], .mx-zone[readonly] {
    background: var(--et-surface-3); border-color: var(--et-hairline);
    color: var(--et-muted); cursor: default;
}

/* SOUS UN CHAMP, UNE SEULE LIGNE À LA FOIS : aide, erreur ou compteur — jamais
   deux. En création, pas d'aide permanente : seulement l'erreur. Empiler les
   trois pousse le champ suivant de 40 px et fait sauter la fiche à la frappe. */
.mx-sous { margin-top: 4px; font-size: 12px; line-height: 1.4; color: var(--et-faint); }
.mx-sous.est-erreur { color: var(--et-danger-ink); }
.mx-sous.est-critique { color: var(--et-avertissement-ink); }
.mx-compteur { font-family: var(--et-font-mono); }

/* ---------------------------------------------------------------------------
   CASE À COCHER DE FICHE — une case, son intitulé, et la conséquence dessous.

   Elle n'entre pas dans la colonne de 158 px : ce que commande une case n'est pas
   une VALEUR mais une RÈGLE, et une règle a besoin d'une phrase. « Contrôle de
   périmètre » seul ne dit pas que décocher ouvre le pointage web au monde entier.
   L'aide vit donc DANS le champ, sous l'intitulé, et non sur la ligne unique de
   .mx-sous — celle-ci reste réservée à l'erreur, qui doit pouvoir s'y poser sans
   chasser l'explication.

   17 px et accent bleu : la même case que celle de la sélection du tableau. Un
   seul dessin de case dans tout le produit — mais deux RENDUS, et c'est forcé :
   celui-ci quand la case est un <input>, .mx-case juste dessous quand la ligne
   est un <button> et qu'un <input> n'y a pas le droit d'être. Les deux partagent
   la taille (17 px) et le bleu ; c'est ce qui fait qu'on ne les distingue pas.
   --------------------------------------------------------------------------- */
/*  .mx-check ET .mx-switch SONT DES <label>, donc app.css:60 les frappe aussi — les
    mêmes 19,2 px que sur .mx-champ-lbl. Sur une rangée de case à cocher, le banc du
    gabarit les mesurait à 49,2 px de haut au lieu de 30, et l'étiquette 13,9 px sous
    son libellé. Un composant que le gabarit pose et dispose lui-même ne peut pas
    hériter d'une marge d'élément nu. */
.mx-check, .mx-switch { margin: 0; }
.mx-check {
    display: flex; align-items: flex-start; gap: 9px;
    cursor: pointer; font-size: 13px; line-height: 1.45; color: var(--et-ink);
}
/* PAS DE LARGEUR ICI (2026-09-10). app.css pose les 17 px sur toute case native,
   et lot02 les 24 px sous 1024 px. Ce sélecteur est à (0,1,2) et ce fichier est lié
   après lot02 : garder la largeur ici faisait perdre la marche tactile au gabarit
   lui-même — la case du formulaire dense restait à 17 px sur tablette. */
.mx-check > input[type="checkbox"] { flex: none; margin-top: 1px; accent-color: var(--et-form-blue); cursor: pointer; }
/* Grisée, la case garde son intitulé LISIBLE — même règle que .mx-input:disabled :
   une fiche en consultation n'est faite que pour être lue. */
.mx-check > input[type="checkbox"]:disabled { cursor: default; }
.mx-check:has(> input:disabled) { cursor: default; color: var(--et-muted); }
/* La conséquence, sous l'intitulé. Elle change avec l'état de la case : c'est ce
   qui la distingue d'une note fixe, qui n'aurait décrit qu'une des deux moitiés. */
.mx-check-aide { display: block; margin-top: 2px; font-size: 12px; color: var(--et-faint); }

/* ---------------------------------------------------------------------------
   CASE DESSINÉE (.mx-case) — la jumelle de .mx-check pour les lignes qui sont
   elles-mêmes des <button>.

   POURQUOI ELLE EXISTE, ALORS QUE .mx-check EST JUSTE AU-DESSUS
     Un <input type="checkbox"> dans un <button> n'est pas du HTML valide, et
     beaucoup d'écrans rendent la LIGNE ENTIÈRE cliquable — une option de filtre,
     une tuile de section, une permission. Il faut donc dessiner la case. Ce n'est
     pas un choix d'apparence : c'est la seule forme disponible dans ce balisage.

   CE QU'ELLE CORRIGE
     La case dessinée a été écrite HUIT FOIS, dans huit feuilles :
       .flt-case (filtre latéral)      .rh-case (rapports d'heures)
       .crp-marque-case (catalogue)    .rpf-tuiles .tuile .case (rapports périodiques)
       .gen-reco-case (génération)     .rol-case + .rol-perm .case (rôles)
       .ntf-case (notifications)
     Huit copies du même bloc — même bordure, même fond, même bleu à l'état coché
     — et, comme toujours, huit copies qui avaient DIVERGÉ : taille 13, 15, 16 ou
     17 px, classe d'état écrite `est-coche`, `est-cochee` ou `est-active`, encre
     de la marque tantôt --et-sur-aplat tantôt --et-surface. Aucun de ces écarts
     n'était une décision.

   LA TAILLE PAR DÉFAUT EST 17 px, ET CE N'EST PAS LA MOYENNE
     C'est la valeur de la passation (« case à cocher 17 × 17 »), celle de
     .mx-check juste au-dessus et celle de .tbl-case dans les tableaux. Les cinq
     copies qui posaient 15 ou 16 px le faisaient à côté d'un intitulé de 13 px —
     exactement la situation de .mx-check. Elles convergent donc.

     Deux écrans gardent leur taille, parce qu'elle était ARGUMENTÉE :
       --mx-case-t: 15px   filtre latéral — ligne d'option de 12,5 px
       --mx-case-t: 13px   catalogue — pilule en ligne de 32 px, intitulé 12 px

   CONTRAT DE BALISAGE
     Sur un <span> dans une ligne cliquable, ou sur le <button> lui-même :
       <span class="mx-case @(coche ? "est-cochee" : "")"><i class="bi bi-check"></i></span>
     L'état s'écrit `est-cochee` SUR LA CASE. Un écran dont l'état est composé
     (trois états de tuile, un tri-état de domaine) le pilote depuis le parent :
     la règle ci-dessous n'est qu'à (0,2,0), un sélecteur d'écran passe devant.

   :focus-visible EST DANS LE GABARIT et non dans la feuille de l'écran : il ne
   sert que lorsque la case EST le bouton, mais alors il est indispensable — sans
   lui, parcourir l'écran au clavier ne montre rien. Sur un <span>, il ne coûte
   rien : un span n'est pas focusable.
   --------------------------------------------------------------------------- */
.mx-case {
    flex: none;
    width: var(--mx-case-t, 17px); height: var(--mx-case-t, 17px);
    padding: 0;
    display: grid; place-items: center;
    border: 1px solid var(--et-inerte); border-radius: 0;
    background: var(--et-surface);
    color: var(--et-sur-aplat);
    font-size: 12px; line-height: 1;
    cursor: pointer;
}
.mx-case.est-cochee { border-color: var(--et-form-blue); background: var(--et-form-blue); }
.mx-case:disabled { cursor: default; }
.mx-case:focus-visible { outline: 2px solid var(--et-form-blue); outline-offset: 1px; }

/* VARIANTE RADIO — même case, un seul choix. Le fond ne se remplit pas : c'est le
   POINT qui dit l'état, et le contour bleu qui dit lequel des deux est le sien. */
.mx-case-radio, .mx-case-radio.est-cochee { border-radius: 50%; background: var(--et-surface); }
.mx-case-radio.est-cochee { border-color: var(--et-form-blue); }
.mx-case-radio.est-cochee::after {
    content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--et-form-blue);
}

/* ---------------------------------------------------------------------------
   EN-TÊTE DE GROUPE — filet vertical bleu 3 × 15 px, libellé mono 11 px gras
   capitales, sur un filet inférieur de 2 px. La note qui peut suivre reste en
   bas de casse et en gris : elle explique, elle n'intitule pas.
   --------------------------------------------------------------------------- */
.mx-groupe {
    display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
    margin: 4px 18px 8px; padding-bottom: 5px;
    border-bottom: 2px solid var(--et-hairline);
}
.mx-groupe:not(:first-child) { margin-top: 18px; }
.mx-groupe::before {
    content: ""; flex: none; width: 3px; height: 15px; background: var(--et-form-blue);
}
/* L'INTITULÉ PREND LE BLEU DE SA PROPRE BARRE (2026-09-10).

   Il portait --et-table-head-encre #354A5F, l'ardoise que la passation Design 7
   donne aux en-têtes de table et aux intitulés de groupe. Un objet, deux rangs :
   la barre verticale en --et-form-blue et l'intitulé juste à côté en ardoise —
   assez proches pour qu'on ne sache pas si c'est voulu, assez loin pour que le
   bleu de la barre passe pour un accident.

   Un seul bleu, donc, celui que la barre porte déjà. Mesuré à 12 px gras, où le
   seuil AA est 4,5:1 : 6,87:1 sur blanc, 6,35:1 sur --et-surface-2. L'ardoise
   rendait 9,15 et 8,45 — on descend, on reste loin au-dessus.

   C'EST UN ÉCART ASSUMÉ AVEC DESIGN 7, qui écrit #354A5F pour ce rôle. Il rejoint
   ceux que la révision de valeurs du 2026-09-02 a déjà pris sur cette passation,
   et il vaut pour les quarante et quelques fiches d'un coup : .mx-groupe est le
   gabarit commun. --et-table-head-encre garde son emploi propre — les en-têtes de
   table — où le fond ardoise, lui, n'a pas changé. */
.mx-groupe-lbl {
    font-family: var(--et-font-mono); font-size: 12px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .16em; color: var(--et-form-blue);
}
.mx-groupe-note { font-size: 12px; color: var(--et-faint); }

/* Bloc libre dans un panneau — une liste, un tableau, un encart que la grammaire
   du formulaire dense ne couvre pas. Il reprend la gouttière de 18 px des rangées
   pour que son bord gauche s'aligne sur celui des libellés : posé sans elle, il
   débordait de 18 px et cassait la seule verticale que la fiche donne à suivre. */
.mx-bloc { padding: 0 18px; }

/* Note explicative de panneau — même registre que .mx-methode (« dire la méthode »),
   mais à l'échelle du panneau et non d'une série de chiffres. */
.mx-note { padding: 6px 18px 0; font-size: 12px; line-height: 1.5; color: var(--et-faint); }
.mx-note .bi { margin-right: 5px; }

/* Données NON MODIFIABLES — numéro de série, firmware, MAC, compteurs. Elles
   sortent du formulaire : un champ grisé au milieu des autres invite à cliquer
   dessus, et laisse croire qu'un droit manque alors que la donnée n'est
   modifiable par personne. En liste mono, la question ne se pose plus. */
.mx-inerte { padding: 0 14px; }
.mx-inerte-rang {
    display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    padding: 4px 0; font-size: 12px;
}
.mx-inerte-lbl { color: var(--et-muted); }
.mx-inerte-val { font-family: var(--et-font-mono); font-size: 12px; color: var(--et-ink); text-align: right; }

/* Actions irréversibles : panneau séparé, en bas de fiche, bordure rouge pâle.
   Séparé, parce qu'une suppression n'est pas une modification de plus. */
.mx-irreversible {
    margin: 18px; padding: 12px 14px;
    border: 1px solid var(--et-danger-bord); background: var(--et-surface);
}
.mx-irreversible-titre { font-size: 13px; font-weight: 700; color: var(--et-danger-ink); }
.mx-irreversible p { margin: 5px 0 10px; font-size: 13px; color: var(--et-muted); }

/* ===========================================================================
   6. MODALE — 620 à 700 px, en-tête en APLAT BLEU, pied à filet supérieur,
   actions EN BAS À DROITE.

   L'EN-TÊTE PASSE DE #F5F6F7 À L'APLAT BLEU (2026-09-09), ET C'EST UNE DÉCISION
   DE CHARTE, PAS UNE RETOUCHE D'ÉCRAN.

   POURQUOI : LE PRODUIT AVAIT DEUX GABARITS DE FENÊTRE. Celui-ci, posé par la
   passation Design 7, ouvrait sur un en-tête gris fermé par un filet bleu de
   3 px — 149 fenêtres dans 87 fichiers. Et `.xp-*` (easycompta-modal.css), venu
   de la passation EasyCompta, ouvre sur un APLAT `--et-form-blue` à encre
   blanche — 46 écrans, dont toute la trésorerie et la comptabilité. Deux
   fenêtres du même produit, à deux pas l'une de l'autre dans le même menu, ne se
   ressemblaient pas. Ce n'était la décision de personne : c'est ce que deux
   passations font quand aucune ne relit l'autre.

   La feuille `.xp-*` l'avait d'ailleurs vu venir et écrit noir sur blanc :
   « chargée dans App.razor pour tout le back-office, non bornée à .appgrid, elle
   est de fait une feuille de gabarit […] un troisième module qui l'adopte ne la
   recopie pas : il la lit. » Le geste symétrique est celui-ci — c'est le GABARIT
   qui rejoint la fenêtre, plutôt que 46 écrans qui rejoignent le gabarit.

   CE QUI CHANGE EST L'ENCRE, PAS LA GÉOMÉTRIE. Hauteurs, gouttières et gabarit
   de bouton ne bougent pas : un lot qui déplacerait aussi les mesures ne se
   relirait plus. Mêmes valeurs que `.xp-tete`, au jeton près, sans quoi les deux
   familles recommenceraient à diverger le jour même.

   MESURES. Blanc sur `--et-form-blue` : 6,87:1 — au-dessus d'AA pour les 18 px
   du titre comme pour les 12 px du sous-titre. Le fond ne bascule pas en sombre
   (`--et-form-blue` n'est défini qu'une fois, § « Interactif » du thème), donc
   le contraste est le même dans les deux thèmes ; c'est la raison pour laquelle
   `--et-sur-aplat` n'a pas de variante sombre.
   =========================================================================== */
/* ===========================================================================
   L'ÉCHELLE DE LA FENÊTRE (2026-09-10) — QUATRE CRANS, ET UN PLAFOND

   CE QUI EXISTAIT. Une largeur par défaut (660 px), un cran étroit (480 px), et
   pour tout le reste : une valeur écrite à la main. Le relevé du 2026-09-10 sur
   les 124 cadres .mx-modale-cadre du back-office donne DIX-HUIT largeurs
   distinctes entre 440 et 1140 px — 700 ici, 720 là, 760, 820, 860, 880, 900,
   920, 1000, 1080, 1140 — dont toutes sauf deux posées dans une feuille d'écran
   ou en style en ligne, chacune recopiée du voisin puis ajustée de vingt pixels.
   Aucune de ces dix-huit valeurs n'était fausse ; c'est leur NOMBRE qui l'était. On n'ouvre
   qu'une fenêtre à la fois, alors personne ne voit les vingt — et rien ne les
   ramène jamais l'une vers l'autre.

   QUATRE CRANS, ET CE QU'ILS PORTENT :

     est-etroite     480 px   une question fermée, une confirmation, un refus
     (défaut)        660 px   UNE colonne de champs — le cas courant
     est-large       880 px   DEUX colonnes (.mx-form.est-deux-colonnes), ou un
                              tableau étroit
     est-tres-large 1120 px   un tableau, une liste, un comparatif — pas un
                              formulaire : à 1120 px un libellé et sa valeur
                              sont séparés par un couloir vide

   LE PLAFOND DE HAUTEUR EST LA MOITIÉ QUI MANQUAIT, et c'est la plus chère.
   Jusqu'ici il n'existait que sous `:has(.mx-modale-fiche)` — 11 fenêtres sur
   124. Les 113 autres n'en avaient AUCUN : elles s'allongeaient sans borne, et
   c'est le VOILE qui défilait. Mesuré au banc (tools/gabarit-modale/) sur une
   fenêtre de trente champs, avant ce lot :

     téléphone 390×844    le pied à +1308 px sous le bord de l'écran
     tablette  768×1024   +515 px
     portable 1280×800    +725 px
     bureau   1600×900    +625 px

   « Le pied sous le bord » se lit : le bouton « Enregistrer » est à chercher en
   défilant. Et comme c'est le voile qui défile, l'EN-TÊTE part par le haut dans
   le même geste — donc plus de titre pour dire ce qui est ouvert, ni de croix
   pour refermer. Les deux extrémités sortent ensemble, ce qui est exactement le
   défaut que le rail d'onglets en modale avait corrigé pour ses onze écrans, et
   que les 113 autres continuaient de payer.

   La fenêtre se partage donc en trois, comme la fiche en modale le fait déjà :
   tête, bande de faits et pied gardent leur hauteur, le CORPS prend ce qui reste
   et défile. Il n'y a pas de fraction d'écran à deviner.

   UNE SEULE MARGE, ET ELLE EST DÉCLARÉE UNE FOIS. Le voile pose son retrait en
   haut ET en bas ; le plafond du cadre doit retirer les deux. Écrits en deux
   endroits, ces deux chiffres divergent au premier réglage — le bloc
   `:has(.mx-modale-fiche)` portait déjà un « calc(100vh - 96px) » en clair, dont
   le 96 était le double du 48 du voile sans qu'aucune ligne ne le dise. La marge
   est donc une variable, posée sur le voile, dont le cadre HÉRITE. La replier
   sous 720 px ne demande alors qu'une ligne, et le plafond suit tout seul.

   La valeur de repli existe pour les cadres montés HORS d'un voile : la famille
   Bootstrap (Components/Shared/ModalCadre.razor) monte son cadre dans un
   .modal-dialog, où rien ne déclare la variable.
   =========================================================================== */
.mx-voile {
    --mx-voile-marge: 48px;
    position: fixed; inset: 0; z-index: 2100;
    display: flex; align-items: flex-start; justify-content: center;
    padding: var(--mx-voile-marge) 16px; overflow-y: auto;
    background: rgba(20, 27, 38, .34);
    animation: mxIn .16s ease both;
}
.mx-modale-cadre {
    width: 100%; max-width: 660px;
    background: var(--et-surface); border: 1px solid var(--et-hairline);
    animation: mxUp .16s ease both;
    /* La colonne flex n'est pas un habillage : sans elle, `max-height` borne la
       BOÎTE et laisse le contenu déborder par-dessous, ce qui est pire que pas
       de plafond du tout — la fenêtre est coupée au lieu d'être défilable. */
    display: flex; flex-direction: column;
    max-height: calc(100vh - var(--mx-voile-marge, 48px) * 2);
}
/* CE QUI NE DÉFILE PAS : TOUT LE RESTE. La règle porte sur `> *` et non sur la
   liste tête / bande de faits / pied, parce que cette liste est fausse : le
   relevé des 124 fenêtres en trouve six autres — un bandeau d'alerte, un état
   vide, un tableau, une vue de liasse, un cadre de table, une note de méthode —
   posés en frères du corps, et une septième arrivera. Nommer les enfants connus
   aurait laissé chacun de ceux-là se comprimer au profit du corps, c'est-à-dire
   rogner un message d'erreur pour faire de la place à ce qu'il commente.
   La ligne suivante, plus spécifique (0,2,0 contre 0,1,0), reprend la main pour
   le seul enfant qui, lui, doit céder. */
.mx-modale-cadre > * { flex: none; }
/* … et ce qui défile : le contenu, et lui seul. « min-height: 0 » parce qu'un
   enfant de flex refuse par défaut de se réduire sous la hauteur de son contenu :
   sans cette ligne le corps garde sa taille naturelle, le cadre reprend la sienne
   et le plafond ne borne plus rien. */
.mx-modale-cadre > .mx-modale-corps { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* SOUS 720 px LE RETRAIT DU VOILE SE REPLIE — le palier de la grille dense (§9).
   48 px en haut et 48 en bas, c'est 96 px pris sur les 844 d'un téléphone :
   11 % de l'écran donnés au voile pendant que le corps du formulaire défile.
   16 px suffisent à dire que la fenêtre flotte au-dessus de la page, et le
   plafond du cadre suit tout seul puisqu'il lit cette variable-ci.

   Le retrait LATÉRAL ne bouge pas : il reste à 16 px, qui est déjà la gouttière
   minimale de la charte. */
@media (max-width: 720px) {
    .mx-voile { --mx-voile-marge: 16px; }
}
.mx-modale-tete {
    display: flex; align-items: center; gap: 10px;
    padding: 12px 18px;
    background: var(--et-form-blue); border-bottom: 3px solid var(--et-form-blue-d);
    /* L'encre est posée sur le CONTENEUR : les 221 glyphes que les en-têtes
       portent n'ont pas de couleur propre, ils héritent. Une règle par glyphe
       aurait été 221 occasions de l'oublier. */
    color: var(--et-sur-aplat);
}
.mx-modale-titre { margin: 0; font-size: 18px; font-weight: 700; color: var(--et-sur-aplat); }

/* Le titre porte le LIBELLÉ de l'objet ouvert et non « Modifier le … » : sur trente
   lignes, savoir laquelle est ouverte vaut mieux que se voir rappeler ce qu'on est
   en train de faire. Le CODE passe dessous, en second rang.

   MONTÉ DE ecran-formules.css ET ecran-supplements.css (2026-09-07), où il était
   écrit deux fois à l'identique sous deux noms — .frm-tete-txt/-sous et
   .sup-tete-txt/-sous. La règle du dépôt est nette : une classe employée par deux
   écrans monte dans le gabarit, jamais l'inverse. Le troisième demandeur — les
   quatre fiches des paramètres d'organisation — aurait fait une troisième copie,
   et les quatre exemplaires de .mx-seg disent assez où cela mène.

   SON ENCRE EST --et-sur-aplat-doux DEPUIS QUE LE FOND EST BLEU. Elle a été
   --et-muted tant que l'en-tête était gris, et l'arbitrage d'alors tenait : ce
   sous-titre porte une VALEUR qu'on vient lire, pas une glose, d'où un rang
   au-dessus de --et-faint. Le raisonnement ne change pas, seule la famille : sur
   un aplat de marque, le rang sous le blanc plein s'appelle --et-sur-aplat-doux,
   comme dans .xp-tete-sous qui dit la même chose depuis la passation EasyCompta. */
.mx-modale-tete-txt { min-width: 0; }
.mx-modale-sous {
    margin-top: 1px;
    font-size: 12px;
    color: var(--et-sur-aplat-doux);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* UNE CIBLE NE RÉTRÉCIT PAS — docs/conventions-responsive.md §3 : « flex: none sur
   les cibles d'une rangée qui peut déborder. La hauteur seule ne suffit pas : dans
   une barre trop pleine, ce sont les éléments au rétrécissement par défaut qui
   paient. »

   L'en-tête d'une modale est exactement cette rangée-là, et ce qui la remplit est
   une DONNÉE : le titre porte le code de l'objet ouvert. Mesuré à 390 px sur la
   fiche article, « Article PLQ-18-CTP » suffit à la remplir — le carré de
   fermeture, seul élément sans contenu incompressible, tombait alors à 41 px
   quand les 44 px du §12 venaient tout juste de le lui donner. Un code plus long
   l'aurait rétréci davantage.

   Sans palier : la règle ne dit pas « 44 px sous 1024 », elle dit qu'une cible ne
   paie pas le débordement des autres. C'est le titre qui doit céder. */
.mx-modale-tete .mx-btn-ico { flex: none; }
.mx-modale-tete .mx-modale-titre { min-width: 0; }

/* ---------------------------------------------------------------------------
   CE QUE L'EN-TÊTE PORTE, UNE FOIS QU'IL EST BLEU

   121 boutons et 19 pastilles vivent dans les 149 en-têtes. Aucun n'avait été
   dessiné pour un aplat de marque : ils sortaient tous d'un fond #F5F6F7. Les
   laisser tels quels aurait posé un bouton BLANC et une pastille à fond PÂLE au
   milieu du bleu — c'est-à-dire trois surfaces claires découpées dans la seule
   qui ne l'est pas.

   Les deux blocs qui suivent sont donc la moitié qui manquait au lot. Ils ne
   valent QUE dans l'en-tête : le même bouton, le même état, gardent ailleurs le
   rendu qu'ils ont toujours eu.
   --------------------------------------------------------------------------- */

/* LES BOUTONS DE L'EN-TÊTE — 72 « blanc + icône », 24 plats, 20 icônes, 4 icônes
   nues, 1 blanc. Relevé sur le balisage réel, en ne lisant que l'intérieur de la
   balise d'en-tête : AUCUN n'est un .mx-btn-danger, si bien qu'une règle unique
   les couvre sans effacer de signal. C'est la fermeture, et parfois une
   corbeille ou un lien d'aide.

   Transparent plutôt qu'un aplat clair, et le survol dit par le fond blanc
   dilué : exactement .xp-fermer, dont c'est le rôle depuis la passation
   EasyCompta. La spécificité (0,2,0) passe devant .mx-btn-blanc (0,1,0) sans un
   seul !important — la cascade suffit quand on ne se bat pas contre elle. */
.mx-modale-tete .mx-btn {
    background: transparent; border-color: transparent; color: var(--et-sur-aplat-doux);
}
.mx-modale-tete .mx-btn:hover:enabled,
.mx-modale-tete a.mx-btn:hover {
    background: var(--et-fond-marine); border-color: transparent; color: var(--et-sur-aplat);
}
/* L'anneau de focus se prend sur le blanc : le bleu du gabarit, posé sur son
   propre aplat, ne se verrait pas. */
.mx-modale-tete .mx-btn:focus-visible { outline-color: var(--et-sur-aplat); }
.mx-modale-tete .mx-btn:disabled { color: var(--et-sur-aplat-doux); opacity: .45; }

/* LES PASTILLES D'ÉTAT DE L'EN-TÊTE — 19, dont « neutre » et « info » sont les
   plus fréquentes. Elles inversent la paire habituelle de la charte, comme la
   règle de la marine le prescrit (docs/couleurs-en-dur.md) : sur un aplat de
   marque, le FOND devient une dilution, au lieu d'une encre foncée sur un fond
   pâle.

   MAIS L'ENCRE RESTE BLANCHE, ET C'EST LA MESURE QUI L'A IMPOSÉ. Le premier jet
   reprenait la paire complète de la marine — dilution + encre claire
   (--et-sur-marine-succes et ses trois sœurs). Sur la MARINE #1B2A5E, pour
   laquelle ces encres ont été choisies, elles rendent 6,21 à 6,73:1. Sur cet
   aplat-ci, qui est le bleu de CONTENU #0057B8 et non la marine, les mêmes
   paires tombent à 3,64-4,05:1 — sous le seuil AA de 4,5:1 qu'exige une pastille
   de 12 px en graisse 600, et la charte dit que le cran « grand texte » à 3:1 ne
   commence qu'à 24 px, ou 18,7 px en gras.

   C'est exactement le piège que la charte nomme : « mesurer contre le pire fond,
   pas contre le plus blanc ». Une encre claire est faite pour un fond sombre ;
   le bleu de contenu ne l'est pas assez.

   --et-sur-aplat, donc, qui est le rang que la charte donne à l'encre d'un aplat
   de marque, et le même que celui du titre juste à côté. Mesuré sur les fonds
   composés : succès 5,47:1, avertissement 5,61:1, danger 6,93:1, neutre et info
   4,90:1. La TEINTE reste portée par le fond, et le LIBELLÉ dit l'état en toutes
   lettres — la couleur ne porte jamais seule. */
.mx-modale-tete .mx-pastille,
.mx-modale-tete .mx-pastille.est-info,
.mx-modale-tete .mx-pastille.est-neutre   { background: var(--et-fond-marine);               color: var(--et-sur-aplat); }
.mx-modale-tete .mx-pastille.est-positif  { background: var(--et-fond-marine-succes);        color: var(--et-sur-aplat); }
.mx-modale-tete .mx-pastille.est-critique { background: var(--et-fond-marine-avertissement); color: var(--et-sur-aplat); }
.mx-modale-tete .mx-pastille.est-negatif  { background: var(--et-fond-marine-danger);        color: var(--et-sur-aplat); }
.mx-modale-corps { padding: 14px 18px 16px; }
.mx-modale-pied {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 12px 18px;
    background: var(--et-surface-2); border-top: 1px solid var(--et-hairline);
}
.mx-modale-mention { flex: 1; min-width: 180px; font-size: 12px; color: var(--et-faint); }
/* Les actions vont en bas à DROITE : Annuler blanc, puis le CTA orange. */
.mx-modale-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }

/* UNE MODALE ÉTROITE — une confirmation, un refus, une question fermée. Elle porte
   une phrase et, le cas échéant, le bandeau qui dit ce que le geste emporte : à 660 px
   cette phrase s'étire sur une seule ligne très longue, que l'œil ne relit pas.

   Le motif a été écrit d'abord dans ecran-organisation.css (.reforg-confirm), et les
   constantes d'export en ont eu besoin à l'identique. La règle du dépôt tranche : une
   classe que DEUX écrans emploient monte dans le gabarit. .reforg-confirm en était un
   DOUBLON exact — même valeur, même motif — et A ÉTÉ RETIRÉE le 2026-09-10, avec le
   lot qui a mis les 124 fenêtres du back-office sur les quatre crans de l'échelle :
   les quatre fiches des paramètres d'organisation portent .est-etroite comme les
   trente autres confirmations. */
.mx-modale-cadre.est-etroite { max-width: 480px; }

/* UNE FENÊTRE LARGE — deux colonnes de champs, ou un tableau étroit. 880 px et
   non 860 ou 900, qui sont les deux valeurs les plus proches du relevé : c'est
   la largeur à laquelle DEUX colonnes du formulaire dense tiennent sans que la
   valeur se serre — 880 moins les 36 px de retrait du corps et les 24 px de
   gouttière font deux colonnes de 410, soit les 158 px du libellé et 252 pour la
   valeur, qui est ce que rend une colonne unique à 660. La largeur est donc
   DÉDUITE de la grille, pas choisie : un troisième cran arbitraire aurait été un
   dix-neuvième chiffre. */
.mx-modale-cadre.est-large { max-width: 880px; }

/* UNE FENÊTRE TRÈS LARGE — un tableau, une liste, un comparatif. PAS un
   formulaire : à cette largeur, un libellé de 158 px et sa valeur sont séparés
   par un couloir vide que l'œil traverse en perdant la ligne. Une fiche qui
   réclame cette largeur réclame en fait un rail d'onglets (.mx-modale-fiche).
   1120 px couvre les six valeurs du haut du relevé (980 à 1140) et tient dans
   la fenêtre d'un portable de 1280 px, retraits du voile compris. */
.mx-modale-cadre.est-tres-large { max-width: 1120px; }

/* UN BANDEAU D'ÉTAT (§11.x) DANS UN CORPS DE MODALE. Le bandeau est dessiné pour une
   PILE en tête d'écran : il porte sa gouttière basse et rien au-dessus. Dans une
   modale il commente le groupe de champs qui le précède, ou termine le corps — d'où
   la gouttière haute, et la basse retirée sur le dernier.

   Écrit ici et non dans trois feuilles d'écran : ecran-organisation.css et
   ecran-periodes.css portaient déjà les deux mêmes lignes, préfixées de leur classe
   de modale, et les constantes d'export étaient le troisième demandeur. */
.mx-modale-corps > .mx-blocage { margin-top: 14px; }
.mx-modale-corps > .mx-blocage:last-child { margin-bottom: 0; }

/* Dans une modale, la grille est la même mais les champs montent à 40 px : on y
   saisit, on n'y consulte pas. */
.mx-modale-corps .mx-champ { padding: 2px 0; }
.mx-modale-corps .mx-input,
.mx-modale-corps .mx-select { height: 40px; }
/* … et la rangée le dit à son libellé (§5). À déplacer avec la ligne ci-dessus. */
.mx-modale-corps .mx-champ { --mx-champ-h: 40px; }
.mx-modale-corps .mx-groupe { margin-left: 0; margin-right: 0; }

/* ---------------------------------------------------------------------------
   DEUX COLONNES DE CHAMPS (.mx-form.est-deux-colonnes)

   L'ÉCHELLE DE HAUTEUR DE LA FENÊTRE. Une fenêtre trop longue a trois réponses,
   et elles se prennent DANS CET ORDRE — la suivante ne se prend que si la
   précédente ne suffit pas :

     1. une colonne, à 660 px          jusqu'à une dizaine de champs
     2. DEUX colonnes, à 880 px        au-delà, tant que les champs forment un
        (est-large + cette classe)     ensemble qu'on lit d'un coup
     3. un rail d'onglets              quand les champs forment des GROUPES
        (.mx-modale-fiche)             qu'on ne remplit pas dans la même minute

   Le critère du passage de 2 à 3 n'est pas un nombre de champs mais le fait que
   les groupes soient séparables : deux colonnes gardent tout sous les yeux, un
   rail admet qu'on ne regarde qu'une partie à la fois. Un rail posé sur six
   champs cache la moitié d'un formulaire qui tenait ; deux colonnes posées sur
   trente rendent une fenêtre de 900 px de haut qui, elle, ne tient plus.

   Le corps DÉFILE de toute façon (le cadre commun le borne) : ces trois crans ne
   sont pas là pour éviter un débordement, ils sont là pour éviter d'avoir à
   défiler dans un formulaire, ce qui n'est pas la même chose. Une barre de
   défilement dans un formulaire de saisie cache des champs obligatoires sans le
   dire.

   POURQUOI UNE GRILLE ET NON DEUX COLONNES CSS. `columns: 2` coupe où il veut,
   y compris au milieu d'une rangée, et l'ordre de lecture y descend puis
   remonte. La grille remplit RANGÉE PAR RANGÉE : le champ 2 est à droite du
   champ 1, ce qui est l'ordre de tabulation, donc l'ordre de saisie.

   L'EN-TÊTE DE GROUPE TRAVERSE LES DEUX COLONNES. C'est le seul filet du
   formulaire dense (§5) : à demi-largeur il ne séparerait plus rien, et le
   groupe de droite semblerait appartenir au groupe de gauche. Un champ que sa
   longueur destine à la pleine largeur — une zone de texte, une adresse — prend
   `.est-pleine-largeur` et fait de même.

   ET ELLE SE REPLIE À 720 px, le palier de la grille dense (§9). Sous cette
   largeur, deux colonnes de 158 px de libellé ne laissent plus rien à la valeur.
   --------------------------------------------------------------------------- */
.mx-form.est-deux-colonnes {
    display: grid; grid-template-columns: 1fr 1fr;
    column-gap: 24px; align-items: start;
}
.mx-form.est-deux-colonnes > .mx-groupe,
.mx-form.est-deux-colonnes > .est-pleine-largeur { grid-column: 1 / -1; }
@media (max-width: 720px) {
    /* Retour à la colonne unique de .mx-form (§5) — pas « grid-template-columns:
       1fr », qui laisserait la gouttière et les règles de travée en place. */
    .mx-form.est-deux-colonnes { display: flex; flex-direction: column; }
}

/* ---------------------------------------------------------------------------
   UNE FICHE À ONGLETS DANS UNE MODALE (.mx-modale-fiche)

   POURQUOI ELLE ARRIVE ICI
     Deux référentiels de la carte — les menus et formules, les suppléments —
     saisissent dans une modale un objet qui porte trop de champs pour une seule
     colonne. On descendait, le titre sortait de l'écran, le bouton
     d'enregistrement aussi, et l'erreur d'un champ resté en haut ne se voyait
     plus. Le rail d'onglets (§4) est la réponse du gabarit à une fiche longue ;
     il lui manquait de savoir tenir dans une modale, où la PAGE ne défile pas.

     La règle du dépôt veut qu'une classe servant à deux écrans monte dans le
     gabarit. Elle en sert deux le jour où elle est écrite — d'où sa place ici
     plutôt que recopiée dans deux feuilles d'écran, qui auraient divergé sur la
     hauteur de défilement au premier réglage.

   CE QU'ELLE CHANGE, ET RIEN DE PLUS
     · la fiche perd sa bordure et son plancher de 620 px : la modale porte déjà
       la première, et le second creuserait un vide sous un onglet de trois
       champs ;
     · les panneaux DÉFILENT dans la modale au lieu de l'allonger — tête, bande
       de faits, pied et bouton d'enregistrement restent en place quel que soit
       l'onglet ouvert, ce qui est tout l'objet de l'opération ;
     · la gouttière de 18 px des rangées est rendue. .mx-modale-corps l'annule,
       et c'est juste pour une modale à une colonne ; dans un panneau, elle
       aligne les libellés sur le bord du groupe comme sur une fiche.

   LA MODALE EST BORNÉE PAR L'ÉCRAN, ET C'EST LE SEUL CHIFFRE QUI COMPTE.
   Un plafond en vh posé sur les panneaux seuls ne suffit pas : il ignore ce
   qu'il y a AUTOUR d'eux. Mesuré à 390 × 844 — le palier téléphone de
   docs/conventions-responsive.md — la tête fait 71 px, la bande de faits 171
   (cinq tuiles sur trois rangées), le rail replié 47 et le pied 131, soit
   420 px de mobilier ; des panneaux à 56 vh en ajoutaient 473, et le pied
   tombait 104 px SOUS la ligne de flottaison. Le bouton « Enregistrer » se
   retrouvait à chercher en défilant — c'est-à-dire précisément le défaut que
   ce rail est venu corriger, revenu par la porte du téléphone.

   La modale se partage donc en trois : tête, bande et pied gardent leur
   hauteur, les panneaux prennent CE QUI RESTE et défilent. Il n'y a plus de
   fraction d'écran à deviner.

   LE PLANCHER DE 620 px se pose sur la modale et non sur les panneaux, pour
   qu'un onglet de trois champs ne fasse pas remonter le pied au milieu de
   l'écran — et il cède au ras de l'écran (min()) au lieu de le déborder, ce
   qu'un plancher posé sur les panneaux ne savait pas faire.
   --------------------------------------------------------------------------- */
.mx-modale-corps > .mx-modale-fiche { margin: -14px -18px -16px; }

/* CE BLOC A MAIGRI LE 2026-09-10. La colonne flex, le plafond de hauteur, le
   « flex: none » de la tête et du pied et le corps qui prend ce qui reste sont
   passés au cadre COMMUN, quelques centaines de lignes plus haut : ce qui était
   vrai pour les onze fenêtres à rail l'était pour les 124. Ne reste ici que ce
   qui est propre à la fiche en modale — son plancher, et le fait que ce n'est
   pas son corps qui défile mais ses PANNEAUX.

   LE PLANCHER DE 620 px se pose sur la modale et non sur les panneaux, pour
   qu'un onglet de trois champs ne fasse pas remonter le pied au milieu de
   l'écran — et il cède au ras de l'écran (min()) au lieu de le déborder, ce
   qu'un plancher posé sur les panneaux ne savait pas faire. */
.mx-modale-cadre:has(.mx-modale-fiche) {
    min-height: min(620px, calc(100vh - var(--mx-voile-marge, 48px) * 2));
}

/* LE CORPS NE DÉFILE PAS ICI, il contient. Le cadre commun lui donne
   « overflow-y: auto » ; sous un rail, ce sont les panneaux qui portent le
   défilement (.mx-modale-fiche .mx-panneaux, dix lignes plus bas), et deux
   barres imbriquées valent moins qu'une. min-height: 0 sur chaque maillon de la
   chaîne : un seul qui l'oublie fait déborder toute la modale. */
.mx-modale-cadre:has(.mx-modale-fiche) > .mx-modale-corps {
    display: flex; overflow: hidden;
}
.mx-modale-fiche { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; }
.mx-modale-fiche > .mx-fiche { flex: 1 1 auto; border: 0; min-height: 0; }
.mx-modale-fiche .mx-panneaux { min-height: 0; overflow-y: auto; }
.mx-modale-fiche .mx-champ { padding: 2px 18px; }
.mx-modale-fiche .mx-groupe { margin-left: 18px; margin-right: 18px; }

/* ET ELLES SUIVENT LE PALIER DE LA GRILLE DENSE (§9, 720 px), ce qu'elles ne
   faisaient pas. Sous 720 px le gabarit resserre .mx-champ à « 4px 14px » — mais
   avec une spécificité de (0,1,0) contre les (0,1,0) ci-dessus, déclarées PLUS
   HAUT dans le même fichier : à égalité, c'est la dernière lue qui l'emporte, et
   c'est la média-requête. Elle gagne donc pour .mx-champ nu, et perd contre
   « .mx-modale-fiche .mx-champ », qui est à (0,2,0).

   Une fiche en modale gardait ainsi 18 px de gouttière là où la page en pose 14,
   sur l'écran de 390 px où ces 8 px sont les plus chers. Mesuré : colonne de
   valeur à 320 px au lieu de 328. Les trois écrans qui logent un rail dans une
   modale sont concernés — menus et formules, suppléments, catalogue. */
@media (max-width: 720px) {
    .mx-modale-fiche .mx-champ { padding: 4px 14px; }
    .mx-modale-fiche .mx-groupe { margin-left: 14px; margin-right: 14px; }
}

/* ---------------------------------------------------------------------------
   LA RÈGLE TACTILE, ÉTENDUE À LA FICHE EN MODALE (docs/cibles-tactiles.md)

   La règle des 44 px est écrite pour la coquille et le gabarit de liste .tbl-*.
   Le rail et le formulaire dense n'en relevaient pas — ils vivaient sur des
   écrans de bureau. Logés dans une modale de paramétrage qu'un gérant ouvre
   sur la tablette du comptoir, ils en relèvent : mesurés à 768 px, les onglets
   font 34 px et les champs 40. C'est le même geste que easypaie.css §17 (lot
   04), qui a étendu la règle à .ep-*, et il est borné de la même façon — au
   seul .mx-modale-fiche, donc aux fiches en modale et à rien d'autre.
   --------------------------------------------------------------------------- */
@media (max-width: 1024px) {
    .mx-modale-fiche .mx-onglet { min-height: 44px; }
    .mx-modale-fiche .mx-input,
    .mx-modale-fiche .mx-select,
    .mx-modale-fiche .mx-zone { min-height: 44px; }
    /* … et la rangée le dit à son libellé (§5). À déplacer avec les trois lignes
       ci-dessus. La zone de texte est dans la règle de hauteur mais pas dans
       celle du libellé, qui l'exclut : elle ne se centre pas. */
    .mx-modale-fiche .mx-champ { --mx-champ-h: 44px; }
    /* La case à cocher est frappée PAR SON ÉTIQUETTE, et c'est l'étiquette qui
       était mince : 19 px de haut, la hauteur d'une ligne de texte. La case
       reste alignée en tête, sans quoi elle se centrerait sur une étiquette de
       deux lignes et cesserait de désigner la première. */
    .mx-modale-fiche .mx-check { min-height: 44px; align-items: center; }
    .mx-modale-fiche .mx-check:has(.mx-check-aide) { align-items: flex-start; padding-top: 6px; }
}

/* LE PIED D'UNE FICHE EN MODALE PORTE UNE MENTION DE PLUS que les autres — le
   compte de points à corriger, posé à côté du bouton d'engagement parce que
   c'est au moment de cliquer qu'il sert. À 390 px, cette mention de trop
   poussait « Enregistrer » 57 px HORS de la modale : le bouton était coupé par
   le bord, sur le palier téléphone de docs/conventions-responsive.md.

   Les actions passent donc à la ligne sous 640 px, et restent calées à droite.
   La règle est bornée aux modales qui portent une fiche : les autres n'ont que
   « Annuler » et le CTA, et n'ont jamais manqué de place. */
@media (max-width: 640px) {
    .mx-modale-cadre:has(.mx-modale-fiche) > .mx-modale-pied .mx-modale-actions {
        flex-wrap: wrap; justify-content: flex-end;
        margin-left: 0; width: 100%;
    }
}

/* CHAQUE MODALE DE CRÉATION ANNONCE SES EFFETS — ce que la validation
   déclenche. C'est la seule chose qu'on ne peut pas deviner d'un formulaire :
   ni les champs ni le libellé du bouton ne disent qu'enregistrer un contrat
   ouvre aussi un dossier de paie. */
.mx-effets {
    margin: 12px 0 0; padding: 10px 12px;
    background: var(--et-tint); border: 1px solid var(--et-form-border);
}
.mx-effets-titre {
    font-family: var(--et-font-mono); font-size: 12px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .16em; color: var(--et-table-head-encre);
}
.mx-effets ul { margin: 6px 0 0; padding-left: 18px; }
.mx-effets li { font-size: 13px; color: var(--et-ink); line-height: 1.5; }

/* LA SURCHARGE SOMBRE DE CE BLOC EST PARTIE, et c'est une bonne nouvelle. La
   recette de la PR #763 l'avait posée ici faute de mieux : --et-tint n'avait pas
   de valeur sombre, ce bloc rendait 2,00:1, et retoucher le jeton touchait
   vingt-trois feuilles — d'où un correctif de composant, en promettant la
   branche technique/… qui ferait le vrai travail. Elle est faite : le jeton
   porte sa valeur sombre, et .mx-effets n'a plus rien de particulier à dire. */

/* ===========================================================================
   7. COUCHE ANALYTIQUE — vue analytique des listes, et tous les tableaux de
   bord.
   =========================================================================== */
.mx-ind-grille {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(214px, 1fr));
    gap: 12px;
}
.mx-ind {
    padding: 10px 12px;
    background: var(--et-surface); border: 1px solid var(--et-hairline);
}
.mx-ind.est-critique { background: var(--et-avertissement-bg); }
.mx-ind.est-negatif  { background: var(--et-danger-badge-bg); }
.mx-ind.est-positif  { background: var(--et-succes-bg); }
.mx-ind-lbl {
    display: flex; align-items: center; gap: 7px;
    font-size: 12px; color: var(--et-muted);
}
.mx-ind-val {
    margin-top: 5px;
    font-family: var(--et-font-mono); font-size: 20px; font-weight: 500;
    color: var(--et-ink); line-height: 1.15;
}
/* La précision se pose À CÔTÉ de la valeur, en clair : « 193 j · sur 4 788
   jours ouvrés ». Un pourcentage sans dénominateur est une erreur, pas une
   approximation. */
.mx-ind-prec { margin-left: 7px; font-family: var(--et-font); font-size: 12px; color: var(--et-faint); }

/* ---------------------------------------------------------------------------
   BARRES DE RÉPARTITION
   La valeur SUIT la fin du remplissage : elle est posée hors de la pilule, en
   frère à sa droite. Une marge automatique ne suffirait pas — elle n'absorbe
   que de l'espace libre POSITIF, si bien qu'à 100 % la valeur repassait sous la
   barre. Elle n'entre dans la pilule, en blanc aligné à droite, que lorsque le
   remplissage couvre les glyphes : au-delà de 70 %, mesuré, pas estimé.
   --------------------------------------------------------------------------- */
.mx-barre-rang { display: flex; align-items: center; gap: 10px; padding: 4px 0; }
.mx-barre-lbl { flex: 0 0 168px; min-width: 0; font-size: 13px; color: var(--et-ink);
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mx-barre {
    position: relative; flex: 1 1 auto; min-width: 44px; height: 17px;
    border-radius: 0; background: var(--et-piste); overflow: hidden;
}
.mx-barre-jauge {
    height: 100%; border-radius: 0; background: var(--et-form-blue);
    transition: width .3s ease;
}
.mx-barre-jauge.est-critique { background: var(--et-avertissement); }
.mx-barre-jauge.est-negatif  { background: var(--et-danger); }
.mx-barre-jauge.est-positif  { background: var(--et-succes); }
/* Valeur POSÉE DEHORS, à droite de la pilule (cas courant). */
.mx-barre-val {
    flex: none; font-family: var(--et-font-mono); font-size: 12px; color: var(--et-ink);
    font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Valeur INSCRITE DEDANS, quand le remplissage couvre les glyphes. */
.mx-barre-val-in {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: flex-end;
    padding-right: 8px;
    font-family: var(--et-font-mono); font-size: 12px; color: var(--et-sur-aplat);
    font-variant-numeric: tabular-nums;
}
/* DIRE LA MÉTHODE. Sous chaque série ou table, la formule en clair. Un décompte
   nomme sa base : « 4 gabarits · 3 moyens », jamais « 4 sur 3 ». */
.mx-methode { margin-top: 8px; font-size: 12px; line-height: 1.5; color: var(--et-faint); }

/* ===========================================================================
   8. PANNEAU LATÉRAL EN SUPERPOSITION (aperçu, historique).
   Il se pose SUR la vue, pas à côté : à côté, la table perdrait 320 px de
   largeur au moment précis où on lit une de ses lignes.
   =========================================================================== */
.mx-sur {
    display: flex; flex-direction: column; min-width: 0;
    background: var(--et-surface); border-left: 1px solid var(--et-hairline);
    animation: mxIn .16s ease both;
}
.mx-sur-tete {
    display: flex; align-items: center; gap: 10px;
    padding: 11px 14px; background: var(--et-surface-2);
    border-bottom: 1px solid var(--et-hairline);
}
.mx-sur-corps { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 0; }
.mx-sur-pied {
    display: flex; align-items: center; gap: 8px;
    padding: 10px 14px; background: var(--et-surface-2);
    border-top: 1px solid var(--et-hairline);
}

/* ===========================================================================
   9. RAIL EN RANGÉE — par repli sous 1100 px, ou par petit nombre d'onglets.

   DEUX RAISONS, UN SEUL DESSIN. Sous 1100 px, garder la colonne laisserait moins
   de 300 px utiles au formulaire, dont la colonne de libellés occupe déjà 158.
   Et sur une fiche à deux ou trois onglets, à TOUTE largeur, la colonne prend ses
   232 px pour n'y rien mettre : l'écran passe alors `Rangee="true"` à
   `RailOnglets`, qui pose `.est-rangee`. Pourquoi l'écran et non le composant —
   la rangée doit tenir dans SA fenêtre, qui va de 660 à 1140 px : la mesure et le
   relevé des sept écrans concernés sont dans le composant.

   LES DEUX BLOCS QUI SUIVENT PORTENT LES MÊMES DÉCLARATIONS et doivent bouger
   ensemble. CSS ne sait pas partager un corps de règle entre un sélecteur et une
   média-requête ; l'alternative — faire de la rangée la forme de BASE et
   n'appliquer la colonne qu'au-dessus de 1100 px — inverserait le gabarit sous
   quarante écrans pour économiser six lignes. Le doublon est le moindre risque,
   et il est nommé plutôt que subi.
   =========================================================================== */
.mx-fiche.est-rangee { grid-template-columns: minmax(0, 1fr); min-height: 0; }
/* LA RANGÉE NE PEUT PAS GARDER LE GRIS DE LA COLONNE. En colonne, le rail est À CÔTÉ
   du panneau : --et-surface-2 contre --et-surface, la séparation se voit. Couché, il
   passe SOUS la bande de faits saillants, dont les tuiles sont exactement du même
   --et-surface-2 — deux bandes grises l'une sur l'autre, séparées par un filet, qui se
   lisent comme un seul bloc. Les onglets s'y perdent : c'est ce que la relecture en
   image a montré.

   La rangée prend donc le fond du CONTENU qu'elle commande, et les chiffres gardent le
   leur. Le filet haut est explicite : la bande de faits n'en porte pas en bas (ses
   tuiles sont posées sur un fond --et-hairline avec 1 px de jour, sans bord inférieur),
   donc sans cette ligne les deux surfaces se toucheraient à nu.

   MESURÉ, dans les deux thèmes, parce qu'un aplat clair contre un aplat clair se juge
   mal à l'œil : le PAS DE FOND seul ne vaut que 1,08:1 en clair (#F5F6F7 contre
   #FFFFFF) et 1,06:1 en sombre (#16202F contre #111A2B). C'est le FILET qui porte la
   séparation — 1,16 et 1,26:1 de part et d'autre en clair, 1,28 et 1,36:1 en sombre.
   Les deux ensemble suffisent parce que la charte est une charte de filets ; retirer le
   filet en comptant sur le pas de fond ne marcherait pas.

   --et-rang a été écarté comme fond de rangée, alors qu'il aurait donné un troisième
   cran franc : c'est le fond de SURVOL d'un onglet (.mx-onglet:hover). Le rail l'aurait
   porté au repos, et le survol aurait disparu. Un fond qui éteint une interaction n'est
   pas un fond disponible. */
.mx-fiche.est-rangee .mx-rail {
    display: flex; gap: 4px; overflow-x: auto; padding: 6px 8px;
    background: var(--et-surface);
    border-right: 0;
    border-top: 1px solid var(--et-hairline);
    border-bottom: 1px solid var(--et-hairline);
}
.mx-fiche.est-rangee .mx-rail-groupe { display: flex; align-items: center; gap: 4px; }
.mx-fiche.est-rangee .mx-rail-groupe + .mx-rail-groupe { margin-top: 0; }
.mx-fiche.est-rangee .mx-rail-lbl { padding: 0 4px 0 8px; white-space: nowrap; }
.mx-fiche.est-rangee .mx-onglet {
    width: auto; white-space: nowrap;
    border-left: 0; border-bottom: 3px solid transparent;
}
.mx-fiche.est-rangee .mx-onglet.est-actif { border-left-color: transparent; border-bottom-color: var(--et-form-blue); }

@media (max-width: 1100px) {
    .mx-fiche { grid-template-columns: minmax(0, 1fr); min-height: 0; }
    .mx-rail {
        display: flex; gap: 4px; overflow-x: auto; padding: 6px 8px;
        border-right: 0; border-bottom: 1px solid var(--et-hairline);
    }
    .mx-rail-groupe { display: flex; align-items: center; gap: 4px; }
    .mx-rail-groupe + .mx-rail-groupe { margin-top: 0; }
    .mx-rail-lbl { padding: 0 4px 0 8px; white-space: nowrap; }
    .mx-onglet {
        width: auto; white-space: nowrap;
        border-left: 0; border-bottom: 3px solid transparent;
    }
    .mx-onglet.est-actif { border-left-color: transparent; border-bottom-color: var(--et-form-blue); }
}

@media (max-width: 720px) {
    .mx-page-tete-in { padding: 10px 14px; }
    .mx-page-corps { padding: 14px 14px 40px; }
    /* En dessous de cette largeur la colonne de 158 px mange la moitié de
       l'écran : le libellé repasse au-dessus de la valeur et retrouve
       l'alignement à gauche, qui est le seul qui vaille sur une colonne. */
    .mx-champ { grid-template-columns: minmax(0, 1fr); gap: 3px; padding: 4px 14px; }
    .mx-champ-lbl { text-align: left; padding-top: 0; }
    /* ET LE CENTRAGE SUR LE CONTRÔLE EST DÉFAIT : il n'a de sens qu'en DEUX
       colonnes. Le libellé étant passé au-dessus de la valeur, lui laisser la
       hauteur du contrôle creuserait 34 à 44 px de blanc entre les deux — sur
       l'écran le plus étroit, celui où la place est la plus chère. */
    .mx-champ:has(> .mx-champ-val > :is(.mx-input, .mx-select, .mx-input-suffixe, .mx-canal, .mx-btn)) > .mx-champ-lbl {
        display: block; min-height: 0;
    }
    .mx-champ-val { padding-top: 0; }
    .mx-groupe { margin-left: 14px; margin-right: 14px; }
    .mx-barre-lbl { flex-basis: 110px; }
}

/* ===========================================================================
   10. IMPRESSION — le mobilier disparaît, les données restent.
   =========================================================================== */
@media print {
    /* .mx-etapes : un fil de progression dit OÙ L'ON EN EST dans un geste en cours —
       sur papier, le geste est fini et il ne reste que son résultat. Il était masqué
       à l'impression par la feuille de l'assistant d'import, sous son ancien nom
       local ; la règle appartient au composant, pas à l'un de ses appelants. */
    .mx-outils, .mx-rail, .mx-sur, .mx-voile, .mx-etapes { display: none !important; }
    .mx-page, .mx-carte, .mx-fiche { border: 0; background: #fff; }
    .mx-panneau.est-masque { display: block !important; }   /* tout le dossier sur papier */

    /* L'ÉTAT VIDE RESTE, SON BOUTON PART. Un état vide sans un mot se lit comme une
       panne et non comme une absence de données (docs/glose.md) : le message et son
       icône s'impriment donc. Le bouton qui l'accompagne, lui, mène ailleurs —
       « File des tickets » sur une fiche sans ticket, relevé à la recette
       d'impression du 2026-09-11. Sur une feuille, il ne mène nulle part. */
    .mx-etat-vide .act { display: none !important; }

    /* MÊME RAISON DANS UN BANDEAU D'ERREUR. Le motif s'imprime — il dit pourquoi la
       feuille est vide, et c'est une information. Le « Réessayer » qui le suit est
       un geste, et un geste ne s'imprime pas : mesuré sur les quatre états
       consolidés, qui portent tous ce couple. */
    .alert .mx-btn { display: none !important; }
}

/* ===========================================================================
   COMPLÉMENT AU GABARIT — à AJOUTER À LA FIN de wwwroot/css/modele-erp.css.

   Trois pastilles de rail que le gabarit n'avait pas, parce qu'aucune de ses
   maquettes ne montrait un onglet porteur d'un compte. La fiche salarié en a
   besoin sur deux natures distinctes, et il faut qu'on les distingue d'un coup
   d'œil : un « 3 » ambre est un travail à faire, un « 3 » gris est un fait.

   Aucune couleur en dur : jetons --et-* de easytech-theme.css, comme le reste.
   =========================================================================== */

/* Compte d'ÉLÉMENTS — consultation. Gris, discret : il informe, il n'appelle pas. */
.mx-onglet-n {
    flex: none;
    min-width: 18px; padding: 0 5px;
    border-radius: var(--et-r-pill);
    background: var(--et-neutre-bg); color: var(--et-neutre);
    font-family: var(--et-font-mono); font-size: 12px; font-weight: 500;
    text-align: center; line-height: 16px;
}

/* Compte d'ERREURS — édition. Il PRIME sur le précédent (voir RailOnglets.razor) et
   porte la teinte négative : c'est ce qu'on cherche quand l'enregistrement a échoué,
   et le seul moyen de savoir DANS QUEL onglet, sur une fiche qui en a vingt. */
.mx-onglet-err {
    flex: none;
    min-width: 18px; padding: 0 5px;
    border-radius: var(--et-r-pill);
    background: var(--et-danger-badge-bg); color: var(--et-danger-ink);
    font-family: var(--et-font-mono); font-size: 12px; font-weight: 700;
    text-align: center; line-height: 16px;
}
.mx-onglet.est-actif .mx-onglet-n { background: var(--et-tint); color: var(--et-form-blue-d); }

/* Le rail sous 1100 px passe en rangée (règle déjà présente) : les pastilles ne
   doivent pas y forcer la hauteur des onglets. */
@media (max-width: 1100px) {
    .mx-onglet-n, .mx-onglet-err { line-height: 15px; font-size: 12px; }
}

/* ===========================================================================
   11. UTILITAIRES DU GABARIT — le peu qui ne rentre dans aucune section.

   Une classe n'arrive ici que si DEUX écrans au moins s'en servent. Une seule
   suffisait à la laisser dans la feuille de son écran ; c'est le second qui
   fait d'elle une règle commune, et la remonter alors évite qu'un écran
   dépende de la feuille d'un autre pour s'afficher correctement.
   =========================================================================== */

/* Valeur nulle en liste : inerte, jamais critique. Un « — » gris se lit comme
   « rien à cet endroit » ; le même tiret en encre de texte se lirait comme une
   valeur. Cycles et Affectations s'en servent tous deux — d'où sa place ici et
   non dans la feuille de l'un des deux. */
.text-inerte { color: var(--et-inerte); }

/* ---------------------------------------------------------------------------
   INTERRUPTEUR (.mx-switch) — un réglage à deux états, avec sa conséquence dite
   sous l'intitulé.

   POURQUOI IL ARRIVE ICI MAINTENANT
     Il a été écrit deux fois, à quinze jours d'intervalle : .par-switch pour les
     paramètres généraux, .nat-switch pour la fiche d'une nature d'heures. Les deux
     feuilles annonçaient chacune la même chose — « le jour où un deuxième écran le
     pose, il remonte en section 11 sous son nom mx- ». Ce jour est celui-ci.

     Les deux copies avaient déjà DIVERGÉ avant d'être fusionnées : gouttière de 10
     ou 11 px, intitulé à 13 ou 13,5 px, aide à 11,5 ou 12 px. Aucun de ces écarts
     n'était une décision ; c'est ce que deux copies font en quelques jours, et
     c'est la raison de la règle plutôt qu'un principe abstrait. Les valeurs
     retenues sont celles des paramètres généraux, la version la plus complète —
     elle seule portait l'état inerte et le respect de prefers-reduced-motion.

   BLEU ET NON ORANGE : l'orange reste au seul bouton d'engagement de l'écran.

   CONTRAT DE BALISAGE — un <label> qui enveloppe tout, pour que le clic sur le
   texte bascule aussi :
     <label class="mx-switch">
       <input type="checkbox" … />
       <span class="piste"><span class="pastille"></span></span>
       <span class="txt"><span class="titre">…</span><span class="aide">…</span></span>
     </label>
   --------------------------------------------------------------------------- */
.mx-switch { display: flex; align-items: flex-start; gap: 11px; cursor: pointer; }

/* Hors flux et non `display:none` : une case masquée sort de l'ordre de tabulation,
   et l'interrupteur devient inatteignable au clavier. */
.mx-switch input { position: absolute; opacity: 0; pointer-events: none; }

.mx-switch .piste {
    flex: none;
    margin-top: 1px;
    width: 34px; height: 18px;
    border-radius: var(--et-r-pill);
    background: var(--et-form-border);
    position: relative;
}
.mx-switch input:checked + .piste { background: var(--et-form-blue); }

/* L'anneau de focus se pose sur la PISTE : la case, elle, est invisible. Sans cette
   règle, parcourir l'écran au clavier ne montre rien. */
.mx-switch input:focus-visible + .piste { outline: 2px solid var(--et-form-blue); outline-offset: 2px; }

.mx-switch .pastille {
    position: absolute;
    top: 2px; left: 2px;
    width: 14px; height: 14px;
    border-radius: 50%;
    background: var(--et-surface);
    box-shadow: var(--et-ombre-pose);
    transition: left .12s ease;
}
.mx-switch input:checked + .piste .pastille { left: 18px; }

.mx-switch .titre { display: block; font-size: 14px; font-weight: 600; color: var(--et-ink); }
.mx-switch .aide { display: block; margin-top: 4px; font-size: 12px; line-height: 1.6; color: var(--et-muted); }

@media (prefers-reduced-motion: reduce) {
    .mx-switch .pastille { transition: none; }
}

/* INERTE : le réglage existe mais ne commande rien — l'option n'est pas souscrite,
   la période est close. La piste s'éteint sans mentir sur la position de la
   pastille, et le libellé se neutralise ; c'est à l'écran de dire POURQUOI, juste
   à côté. Ce n'est pas `disabled` : la case peut l'être ou non selon le cas. */
.mx-switch.est-inerte { cursor: not-allowed; }
.mx-switch.est-inerte input:checked + .piste { background: var(--et-repos-bord); }
.mx-switch.est-inerte .titre { color: var(--et-muted); }

/* ---------------------------------------------------------------------------
   CHAMP À SUFFIXE (.mx-input-suffixe) — l'unité DANS le champ.

   « 20,00 » suivi d'un « h » posé à côté se lit comme deux valeurs ; posé dedans,
   il se lit comme une grandeur. Le padding réserve sa place — sans lui, une saisie
   longue passe sous le suffixe.

   QUATRE COPIES, PAS DEUX : .term-suffixe (terminaux), .cv-suffixe (conventions),
   .par-suffixe (paramètres généraux), .nat-suffixe (natures d'heures). Elles ne
   différaient que par la RÉSERVE de padding — qui dépend de la longueur du
   suffixe, « % » n'occupant pas la place de « min » — et par une largeur maximale
   qui, elle, est une affaire de mise en page et reste donc chez l'écran.

     --mx-suffixe-reserve  la place à réserver à droite (défaut 30 px)

   La couleur et la taille, en revanche, n'avaient aucune raison de varier : trois
   copies sur quatre disaient --et-faint à 11,5 px, la quatrième --et-muted à 12 px.
   C'est la première qui fait règle.
   --------------------------------------------------------------------------- */
.mx-input-suffixe { position: relative; }
.mx-input-suffixe > .mx-input { padding-right: var(--mx-suffixe-reserve, 30px); }
.mx-input-suffixe > .suffixe {
    position: absolute;
    right: 10px; top: 50%; transform: translateY(-50%);
    font-size: 12px;
    color: var(--et-faint);
    pointer-events: none;
}

/* ---------------------------------------------------------------------------
   CHAMP « CANAL » (.mx-canal) — une adresse e-mail, un téléphone, un WhatsApp :
   trois espèces d'un même genre, et un seul habillage.

   Habillage de Components/Shared/ChampCourriel et ChampTelephone. Le contrôle,
   lui, est en C# (Services/Courriel.cs, Services/Telephone.cs) : une couleur ne
   valide rien, elle rapporte.

   POURQUOI UNE SEULE FAMILLE POUR TROIS CHAMPS
     Ils posent exactement le même problème d'interface : une valeur qu'on saisit,
     dont la forme se contrôle, et qui devient un GESTE en lecture (écrire,
     appeler, ouvrir une conversation). Leur donner trois familles de classes
     aurait produit trois blocs identiques au nom près — et la quatrième espèce,
     le jour où elle arrive, en aurait fait un quatrième.

   TROIS SIGNAUX SUR UNE SEULE MARQUE
     la FORME du glyphe    dit de quel canal il s'agit (enveloppe, combiné, WhatsApp)
     le REMPLISSAGE        dit si la valeur a été jugée (contour = rien à dire encore)
     la COULEUR            dit le verdict (normal, douteux, faux)
     Aucun objet ajouté, aucune couleur de plus : la marque est déjà là.

   POURQUOI DANS LE GABARIT ET NON DANS UNE FEUILLE D'ÉCRAN
     Neuf écrans posaient déjà un champ e-mail, chacun avec son markup : trois
     sans aucune icône, deux avec un « bi-envelope » gris posé à côté du champ,
     un avec l'icône dedans mais à droite, là où le suffixe la heurtait. La règle
     de cette section — deux écrans et la classe remonte — était franchie depuis
     longtemps ; c'est le rattrapage.

   L'ICÔNE EST À GAUCHE, ET C'EST LA SEULE PLACE POSSIBLE
     La droite est déjà prise, et par deux choses : le suffixe de .mx-input-suffixe
     et le chevron de .mx-select. Une enveloppe à droite entre en collision avec
     l'un ou l'autre dès qu'un écran compose (« e-mail + domaine imposé »). À
     gauche, elle ne rencontre rien, et elle se lit AVANT la valeur — ce qui est
     l'ordre où on la veut : on apprend que le champ est une adresse, puis on lit
     l'adresse.

   DEUX CANAUX, ET C'EST CE QUI PERMET À L'ENVELOPPE D'ÊTRE TOUJOURS EN COULEUR

     LA COULEUR dit de quoi on parle, puis ce qui cloche :
       normal      --et-form-blue      c'est un champ d'adresse e-mail
       douteuse    --et-avertissement  licite, mais le domaine ressemble à une faute
       fausse      --et-danger         elle ne partira pas
       inerte      --et-inerte         le champ est hors d'usage

     LE REMPLISSAGE dit si l'adresse a été jugée :
       contour (bi-envelope)       rien à dire encore — champ vide, ou saisie en cours
       plein   (bi-envelope-fill)  l'adresse est jugée, et la couleur donne le verdict

   PREMIÈRE VERSION, ET POURQUOI ELLE A ÉTÉ REPRISE
     L'enveloppe était --et-faint au repos, sur l'argument — juste en soi — que
     --et-inerte (2,04:1) est le cran des valeurs ABSENTES et que l'enveloppe,
     elle, se lit. Sauf que le repos n'est pas un cas de bord : c'est l'état dans
     lequel le champ est vu le plus souvent, chaque fois qu'une fiche s'ouvre en
     consultation ou en création. Une enveloppe grise dans ce cas-là, c'est un
     champ e-mail qui n'est en couleur que pendant qu'on y écrit — donc jamais,
     du point de vue de qui parcourt une fiche.

     Le bleu SAP tient ce rôle sans rien coûter : il est déjà « l'interactif dans
     la page » de la charte, et un champ d'adresse EST une cible d'action (il
     devient un lien mailto: dès qu'on sort d'édition). L'icône annonce donc la
     nature du champ en permanence, et les deux couleurs d'alerte se posent
     par-dessus quand il y a lieu.

     Ce qui se perd : « valide » n'a plus de couleur à lui, puisque le repos a
     pris le bleu. Ce qui le remplace est meilleur — le REMPLISSAGE. Une
     enveloppe pleine se distingue d'une enveloppe en contour sans ajouter un
     objet ni une couleur, et elle dit quelque chose de plus juste : non pas
     « c'est bon » mais « j'ai lu, et voici mon verdict ».

   POURQUOI L'ÉTAT PASSE PAR L'ICÔNE ET NON PAR UN SIGNE DE PLUS
     Un champ dense a UNE ligne sous lui (.mx-sous), et elle est déjà arbitrée :
     erreur > avertissement > aide > compteur. Poser en plus une coche verte à
     droite du champ, c'est ajouter un objet par champ à une fiche qui en compte
     quarante — et la charte a précisément réduit le nombre d'objets colorés
     simultanés. L'enveloppe est déjà là ; elle porte l'état sans rien coûter.

   PAS DE VERT POUR LE VALIDE : le bleu SAP suffit, et le vert de --et-succes est
   réservé aux états MÉTIER (payé, transmis, clôturé). Une adresse bien formée
   n'est pas un succès, c'est l'état normal d'un champ rempli.
   --------------------------------------------------------------------------- */
.mx-canal { position: relative; display: block; }

/* La réserve à gauche : 32 px = 10 (marge) + 13 (icône) + 9 (blanc avant le
   texte). Écrite sur .mx-input et non en variable — contrairement à
   --mx-suffixe-reserve, elle ne varie pas : l'enveloppe a toujours la même
   largeur, là où un suffixe « % » n'occupe pas la place d'un « min ». */
.mx-canal > .mx-input { padding-left: 32px; }

.mx-canal-ico {
    position: absolute;
    left: 10px; top: 50%; transform: translateY(-50%);
    font-size: 13px; line-height: 1;
    color: var(--et-form-blue);   /* EN COULEUR DÈS LE REPOS — voir ci-dessus */
    pointer-events: none;   /* le clic traverse et va au champ */
    transition: color .12s ease;
}
/* LE REPORT DE 3 PX A ÉTÉ RETIRÉ (2026-09-10), ET IL ÉTAIT LUI-MÊME LE DÉFAUT.
   Il disait rattraper la marge haute de 6 px de « .mx-champ-val > .mx-input » —
   mais cette règle ne visait QUE le contrôle nu, et l'input d'un canal est
   petit-fils de .mx-champ-val, jamais fils : il n'a jamais reçu ces 6 px.
   L'icône compensait donc un décalage qui n'existait pas, et se posait 3 px SOUS
   le centre de son champ, partout où un champ de contact paraît. Mesuré : +3,00 px
   sur les quatre rangées de canal de la fenêtre d'identité, avant comme après le
   lot du 2026-09-10 sur le centrage des libellés — les deux défauts sont
   indépendants, celui-ci a seulement cessé d'être noyé dans l'autre.
   Depuis que le §5 reconnaît .mx-canal, la valeur ne pose plus son retrait de
   5 px : l'enveloppe épouse exactement son input, et une icône à 50 % de
   l'enveloppe est centrée sans rien à reprendre. Vérifié : 0,00 px. */

/* « est-valide » ne redéclare RIEN : le bleu du repos est déjà le bon. La classe
   existe quand même — le composant l'émet, et un écran qui voudrait un jour
   marquer le valide autrement a l'accroche sous la main. Ce qui distingue le
   valide du repos est le GLYPHE, que pose le composant (bi-envelope-fill). */
.mx-canal.est-doute   > .mx-canal-ico { color: var(--et-avertissement-ink); }

/* WHATSAPP N'A PAS DE GLYPHE PLEIN — c'est une marque, elle n'existe qu'en une
   forme (bi-whatsapp, sans variante -fill). Le second canal, le remplissage, lui
   est donc inaccessible : sur ce champ-là, seule la couleur porte le verdict.
   C'est une exception ASSUMÉE et non un oubli. La contourner en dessinant un
   faux logo plein aurait été pire : on ne redessine pas la marque d'un tiers. */
.mx-canal.est-faux    > .mx-canal-ico { color: var(--et-danger-ink); }

/* Champ inerte : l'enveloppe rejoint le gris du champ grisé. Elle ne peut pas
   rester bleue sur un fond --et-surface-3, où elle annoncerait une saisie
   possible. C'est le seul emploi de --et-inerte ici, et il est justifié : le
   champ est bien, lui, hors d'usage. */
.mx-canal:has(> .mx-input:disabled) > .mx-canal-ico,
.mx-canal:has(> .mx-input[readonly]) > .mx-canal-ico { color: var(--et-inerte); }

/* --- LECTURE : l'adresse est un LIEN, pas un texte -------------------------
   Une fiche en consultation existe pour qu'on agisse depuis elle. Une adresse
   affichée en texte noir oblige à la sélectionner, la copier, ouvrir le client
   de messagerie, coller — quatre gestes pour le seul geste qu'on voulait faire.
   Le « mailto: » les remplace par un clic.

   L'enveloppe reprend ici sa taille de texte et devient CLIQUABLE avec le
   libellé (pas de pointer-events: none) : c'est une seule cible, pas une icône
   décorative posée devant un lien. */
.mx-canal-lien {
    display: inline-flex; align-items: baseline; gap: 7px;
    color: var(--et-form-blue-encre); font-weight: 600; text-decoration: none;
    word-break: break-all;   /* une adresse longue coupe plutôt que de déborder */
}
.mx-canal-lien > i { color: var(--et-form-blue); font-size: 13px; }
.mx-canal-lien:hover { color: var(--et-form-blue-d); text-decoration: underline; }
.mx-canal-lien:hover > i { color: var(--et-form-blue-d); }

/* Adresse présente mais NON expédiable (héritée d'un import, d'une reprise) :
   elle s'affiche — la masquer ferait croire à un champ vide, et personne ne
   corrigerait — mais elle n'est pas un lien, parce que le clic n'aboutirait pas.
   L'enveloppe barrée dit laquelle des deux situations on regarde. */
.mx-canal-mort { display: inline-flex; align-items: baseline; gap: 7px; color: var(--et-ink); word-break: break-all; }
.mx-canal-mort > i { color: var(--et-danger-ink); font-size: 13px; }

/* ---------------------------------------------------------------------------
   ADRESSE EN LECTURE (.mx-adresse) — habillage de Components/Shared/BlocAdresse.

   POURQUOI CE N'EST PAS UN .mx-canal
     La famille voisine tient sur trois propriétés réunies : une valeur d'UNE LIGNE,
     dont la FORME EST VÉRIFIABLE, et qui devient un GESTE en lecture. L'adresse n'a
     que la troisième — elle est quatre colonnes, et il n'existe aucune règle de rue
     à vérifier (le code postal n'est même pas en usage dans une partie de la zone).
     Lui donner .mx-canal aurait posé quatre icônes qui ne valident rien.

   ELLE SE LIT SUR PLUSIEURS LIGNES, COMME SUR UNE ENVELOPPE
     Mise bout à bout, une adresse ne se recopie pas — et c'est pourtant ce qu'on en
     fait, sur un bon de livraison. Les lignes sont donc celles de l'enveloppe : la
     rue, puis « code postal ville », puis le pays en clair.

   L'ACTION EST UNIQUE ET PORTE SUR L'ENSEMBLE : on ouvre la carte d'une adresse, pas
   celle d'un code postal. Elle se pose SOUS le bloc et non en tête — au-dessus, elle
   se serait lue comme un titre, et c'est l'adresse qu'on vient lire, pas le lien.
   --------------------------------------------------------------------------- */
.mx-adresse { line-height: 1.5; }
.mx-adresse > div { white-space: normal; }

.mx-adresse-carte {
    display: inline-flex; align-items: center; gap: 6px;
    margin-top: 5px;
    color: var(--et-form-blue-encre); font-weight: 600; font-size: 13px;
    text-decoration: none;
}
.mx-adresse-carte > i { color: var(--et-form-blue); font-size: 13px; }
.mx-adresse-carte:hover { color: var(--et-form-blue-d); text-decoration: underline; }
.mx-adresse-carte:hover > i { color: var(--et-form-blue-d); }

/* Même règle tactile que le lien des champs canal : sous 1024 px, une cible ne
   descend pas sous 44 px, sans écarter les rangées de la fiche dense.
   Géométrie seule, aucune couleur. */
@media (max-width: 1024px) {
    .mx-adresse-carte { min-height: 44px; padding: 4px 0; margin-top: 1px; }
}

/* --- PALIER TACTILE (sous 1024 px) ----------------------------------------
   Le lien mailto d'une fiche en lecture EST une cible : sur un téléphone, c'est
   même la principale de la fiche contact. Un texte de 13 px fait une cible de
   18 px de haut. La règle du lot 02 lui rend ses 44 px — en padding vertical et
   en marge négative, pour ne pas écarter les rangées de la fiche dense, qui
   valent 34 px et que le lot 02 laisse explicitement en dehors.
   Géométrie seule, aucune couleur — comme les lots 01 à 04. */
@media (max-width: 1024px) {
    .mx-canal-lien {
        min-height: 44px; align-items: center;
        padding: 4px 0; margin: -4px 0;
    }
}

/* ---------------------------------------------------------------------------
   SÉLECTEUR DE BORNES (.per-*) — habillage de Components/Shared/SelecteurBornes.

   POURQUOI DANS LE GABARIT ALORS QU'UN SEUL ÉCRAN L'EMPLOIE AUJOURD'HUI
     La règle de cette section demande deux écrans avant de remonter une classe. Elle vise
     les classes d'ÉCRAN. Celles-ci habillent un COMPOSANT PARTAGÉ : les laisser dans la
     feuille du journal des transactions rendrait le deuxième écran qui pose le sélecteur
     dépendant de la feuille du premier — exactement la dépendance que la section existe
     pour empêcher. Même parti que .tbl-methode et .mx-valeur-inerte avant elles.

   Aucune couleur en dur : la passation donne des hex (#D9D9D9, #0A6ED1, #EBF3FB…), ce sont
   très exactement les jetons ci-dessous, et c'est par eux que le mode sombre passe.
   Spécification : passation Design 21, COMPOSANT-PERIODE.md.
   --------------------------------------------------------------------------- */

.per-cadre { position: relative; }

/* Angles droits : c'est un champ de saisie, et la charte les veut carrés. */
.per-declencheur {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 0 12px;
    background: var(--et-surface);
    border: 1px solid var(--et-filet-fort);
    border-radius: var(--et-r);
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}
.per-declencheur:hover:not(:disabled) { border-color: var(--et-inerte); }
.per-declencheur.est-ouvert           { border-color: var(--et-form-blue); }
.per-declencheur:disabled             { background: var(--et-surface-3); cursor: not-allowed; }
.per-declencheur:focus-visible        { outline: 2px solid var(--et-form-blue); outline-offset: 1px; }

.per-ico { flex: none; display: grid; color: var(--et-form-blue-encre); font-size: 15px; }
.per-declencheur:disabled .per-ico { color: var(--et-inerte); }

.per-txt { flex: 1; min-width: 0; }

.per-nom {
    display: block;
    font-size: 13px;
    font-weight: 600;
    color: var(--et-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Les bornes en chasse fixe : deux dates posées l'une sous l'autre d'une ouverture à
   l'autre doivent se comparer d'un regard, non se relire. */
.per-bornes {
    display: block;
    margin-top: 1px;
    font-family: var(--et-font-mono);
    font-size: 12px;
    color: var(--et-muted);
    white-space: nowrap;
}

.per-chevron {
    flex: none;
    display: grid;
    font-size: 12px;
    color: var(--et-faint);
    transition: transform .16s ease;
}
.per-chevron.est-ouvert { transform: rotate(180deg); }

/* `est-droite` sert quand le composant est en fin de barre d'outils et déborderait. */
.per-panneau {
    position: absolute;
    z-index: 30;
    top: calc(100% + 6px);
    left: 0;
    min-width: 300px;
    background: var(--et-surface);
    border: 1px solid var(--et-hairline);
    box-shadow: var(--et-ombre-menu);
    padding: 6px;
    animation: mxIn .16s ease both;
}
.per-panneau.est-droite { left: auto; right: 0; }

.per-option {
    display: flex;
    align-items: baseline;
    gap: 12px;
    width: 100%;
    padding: 9px 10px;
    border: none;
    background: transparent;
    font-family: inherit;
    font-size: 13px;
    color: var(--et-ink);
    text-align: left;
    cursor: pointer;
}
.per-option:hover     { background: var(--et-rang); }
.per-option.est-actif { background: var(--et-tint); color: var(--et-form-blue-d); font-weight: 600; }

.per-option-bornes {
    margin-left: auto;
    flex: none;
    font-family: var(--et-font-mono);
    font-size: 12px;
    color: var(--et-faint);
}
.per-option.est-actif .per-option-bornes { color: var(--et-form-blue-d); }

/* Le mode personnalisé sous un filet : c'est une autre nature de choix, pas un huitième
   préréglage — il se saisit et se valide au lieu de s'élire. */
.per-perso {
    margin-top: 4px;
    padding: 10px 10px 4px;
    border-top: 1px solid var(--et-rang);
}

.per-perso-titre {
    font-family: var(--et-font-mono);
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .14em;
    color: var(--et-table-head-encre);
    margin-bottom: 8px;
}

.per-perso-champs {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
}
.per-perso-champs label {
    display: block;
    font-size: 12px;
    color: var(--et-muted);
    margin-bottom: 4px;
}
.per-perso-champs .mx-input {
    height: 40px;
    font-family: var(--et-font-mono);
    font-size: 13px;
}

/* L'erreur sous LES DEUX champs, et non contre l'un d'eux : c'est le couple de dates
   qu'elle juge, jamais une date seule. */
.per-erreur {
    margin-top: 6px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--et-form-danger);
}

.per-perso-pied {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 10px 0 6px;
}

/* ---------------------------------------------------------------------------
   SÉLECTEUR SEGMENTÉ (.mx-seg) — les valeurs d'UN réglage de lecture, côte à côte.

   POURQUOI IL ARRIVE ICI MAINTENANT
     Il a été écrit QUATRE fois : .org-axe pour l'axe de l'organigramme, .pdp-vues
     pour les vues des périodes de pointage, .rol-vues pour celles des rôles,
     .usr-vues pour celles des comptes utilisateurs. Trois de ces feuilles portaient
     déjà le constat, et deux posaient la même condition — « le jour où un troisième
     écran en pose un, les deux montent dans le gabarit ». Ce jour est celui-ci.

     AUCUN DES RELEVÉS N'ÉTAIT COMPLET, et c'est la meilleure raison de ne plus
     attendre : la feuille des rôles en comptait deux, celle des utilisateurs trois,
     et la quatrième est arrivée pendant que celle-ci s'écrivait. Une dette qu'on
     diffère se recompte à la hausse à chaque relevé.

     LES QUATRE COPIES AVAIENT DÉJÀ DIVERGÉ, comme toujours, sur ce que personne
     n'avait décidé : hauteur de 31 ou 32 px, bordure sur --et-form-border ou sur
     --et-filet-fort, focus visible absent de deux, survol de l'actif neutralisé
     chez deux, fond au survol sur --et-rang ou sur --et-surface-2, graisse de
     l'actif à 600 partout sauf chez les utilisateurs, thème sombre traité par
     l'organigramme seul.

     LE DESIGN, LUI, N'AVAIT JAMAIS FRAGMENTÉ CET OBJET. Les passations de
     l'organigramme et des rôles l'appellent toutes deux « .tbl-seg » — un seul nom,
     un seul composant. C'est chaque PORTAGE qui l'a renommé pour son écran, faute
     de trouver la classe dans une feuille : quatre fois le même geste, et quatre
     copies. La leçon tient en une ligne — un nom de la passation qui n'existe nulle
     part est le signe qu'il faut regarder si un voisin ne l'a pas déjà écrit sous
     un autre.

     La bordure est la divergence qui COÛTAIT quelque chose : --et-filet-fort n'a pas
     de valeur sombre — il vaut #D9D9D9 dans les deux thèmes — si bien que le
     sélecteur des périodes gardait un filet clair sur fond sombre. Les valeurs
     retenues sont donc celles de l'organigramme, la version la plus complète : elle
     seule portait le focus visible et le pendant sombre de l'état actif.

   CE QU'IL N'ABSORBE PAS, ET POURQUOI
     .tbl-groupe (easytime-shell.css) a la même grammaire mais n'est pas une copie
     d'écran : il appartient au composant Tableau, vit déjà dans une feuille partagée,
     et sert des boutons d'icône de 34 px avec leur variante .est-lignes. Le fondre
     ici déplacerait un objet qui a déjà UN seul domicile.

     .cv-seg (conventions) porte un actif BLEU et non ardoise, parce qu'il choisit un
     mode dans une fiche au lieu de choisir une lecture dans une barre. L'absorber
     changerait son apparence sans que personne l'ait demandé.

   USAGE
     <div class="mx-seg" role="group" aria-label="…">
         <button class="est-actif">Cockpit</button><button>Toutes</button>
     </div>
   --------------------------------------------------------------------------- */
.mx-seg {
    display: inline-flex; flex: none; overflow: hidden;
    border: 1px solid var(--et-form-border);
}
.mx-seg > button {
    height: 31px; padding: 0 13px;
    border: 0; border-right: 1px solid var(--et-form-border);
    background: var(--et-surface); color: var(--et-ink);
    font-family: inherit; font-size: 13px; white-space: nowrap; cursor: pointer;
}
.mx-seg > button:last-child { border-right: 0; }
.mx-seg > button:hover { background: var(--et-rang); }
.mx-seg > button:focus-visible { outline: 2px solid var(--et-form-blue); outline-offset: -2px; }

/* Actif : ardoise pleine, libellé blanc. Jamais d'orange — il est réservé au bouton
   d'engagement, et l'écran en porte déjà un. L'actif NE RÉAGIT PAS au survol : il ne
   se sélectionne pas deux fois, et un fond qui bouge sous le curseur laisserait croire
   qu'il va se passer quelque chose. */
.mx-seg > button.est-actif,
.mx-seg > button.est-actif:hover { background: var(--et-table-head); color: var(--et-sur-ardoise-plein); font-weight: 600; }
.appgrid[data-theme="dark"] .mx-seg > button.est-actif { background: #2c4373; color: var(--et-ink); }

/* ---------------------------------------------------------------------------
   LISTE DE SESSIONS (.ses-*) — USR-06.

   AU GABARIT, ET PAS DANS UNE FEUILLE D'ÉCRAN : ces classes servent à DEUX
   écrans — « Mes appareils » (MonCompte) et le bloc sessions de la fiche
   utilisateur —, portés par le même composant Shared/ListeSessions.razor. La
   règle du dépôt est explicite : une classe utilisée par deux écrans ou plus
   monte ici, sinon l'un des deux finirait par dépendre de la feuille de
   l'autre pour s'afficher.

   Une LISTE, pas un tableau : chaque ligne porte quatre informations de
   longueurs très inégales — un agent court, deux adresses, deux dates
   relatives. En colonnes, l'adresse IP dicterait la largeur de tout.
   --------------------------------------------------------------------------- */
.ses-liste { list-style: none; margin: 0; padding: 0; }

.ses-item {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--et-hairline);
}
.ses-item:last-child { border-bottom: 0; }

/* L'APPAREIL COURANT SE MARQUE PAR UN LISERÉ, pas par un fond : la liste en
   compte souvent une seule autre, et un bloc teinté sur deux lignes se lit
   comme une alerte alors que c'est une information rassurante. */
.ses-item-courante { box-shadow: inset 3px 0 0 0 var(--et-succes); padding-left: 10px; }

.ses-icone {
    flex: 0 0 auto;
    width: 28px; height: 28px;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--et-muted);
    font-size: 15px;
}

.ses-corps { flex: 1 1 auto; min-width: 0; }

.ses-tete { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ses-agent { font-size: 13px; font-weight: 600; color: var(--et-ink); }

.ses-detail {
    font-size: 12px;
    color: var(--et-muted);
    display: flex; flex-wrap: wrap; gap: 4px;
    margin-top: 1px;
}
.ses-sep { opacity: .5; }

/* L'échéance est la seule information de la ligne qui parle du FUTUR : elle se
   distingue, sans crier — une session expire, ce n'est pas un incident. */
.ses-exp { font-style: italic; }

.ses-ip {
    font-size: 12px;
    color: var(--et-muted);
    margin-top: 1px;
    display: flex; flex-wrap: wrap; gap: 8px;
}
.ses-ip-autre { opacity: .8; }

.ses-fermer { flex: 0 0 auto; align-self: center; }

.ses-vide {
    padding: 12px 0;
    font-size: 13px;
    color: var(--et-muted);
}

.ses-actions {
    display: flex; justify-content: flex-end;
    padding-top: 10px;
}

/* Règle tactile (docs/cibles-tactiles.md) : sous 1024 px, aucune cible sous
   44 px. Et sous 560 px le bouton passe SOUS la ligne — à cette largeur, le
   garder à droite écraserait l'agent sur trois lignes. */
@media (max-width: 1024px) {
    .ses-fermer, .ses-actions .mx-btn { min-height: 44px; }
}

@media (max-width: 560px) {
    .ses-item { flex-wrap: wrap; }
    .ses-fermer { width: 100%; justify-content: center; margin-top: 6px; }
}


/* ---------------------------------------------------------------------------
   11.x  BANDEAU D'ÉTAT (.mx-blocage) — ce qui bloque, ce qui a échoué, ce qui a
         réussi.

   MONTÉ DEPUIS ecran-devis.css (2026-09-02), où il s'appelait .dev-blocage. La
   file d'approbation en avait besoin, et la règle du dépôt est nette : une classe
   employée par deux écrans monte dans le gabarit, jamais l'inverse — sinon la file
   ne s'afficherait correctement qu'à condition que la feuille de l'écran des
   documents soit chargée, ce que rien ne garantit ni n'explique.

   Trois tons, trois classes, et pas un style en ligne : « jamais de couleur codée
   en dur dans une page » vaut aussi pour un var(--et-*) recopié dans un attribut
   style.
   --------------------------------------------------------------------------- */
.mx-blocage {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    padding: 11px 16px; margin-bottom: 16px;
    background: var(--et-avertissement-bg);
    border: 1px solid var(--et-avertissement-bord);
    border-left: 4px solid var(--et-avertissement-mark);
}
.mx-blocage > i { font-size: 18px; color: var(--et-avertissement-ink); }

.mx-blocage.est-erreur {
    background: var(--et-danger-badge-bg);
    border-color: var(--et-danger-bord);
    border-left-color: var(--et-danger);
}
.mx-blocage.est-erreur > i,
.mx-blocage.est-erreur .mx-blocage-titre,
.mx-blocage.est-erreur .mx-blocage-detail { color: var(--et-danger-ink); }

.mx-blocage.est-succes {
    background: var(--et-succes-bg);
    border-color: var(--et-succes-bord);
    border-left-color: var(--et-succes);
}
.mx-blocage.est-succes > i,
.mx-blocage.est-succes .mx-blocage-titre,
.mx-blocage.est-succes .mx-blocage-detail { color: var(--et-succes-ink); }

.mx-blocage-txt { flex: 1; min-width: 220px; }
.mx-blocage-titre { font-size: 13px; font-weight: 700; color: var(--et-avertissement-ink); }
/* 13 px et non 12,5 : l'échelle typographique révisée sur main (« échelle 98 % »)
   a touché cette ligne pendant que le bloc montait ici. Reporté à la fusion —
   laisser 12,5 aurait annulé leur correction sans que personne le voie. */
.mx-blocage-detail { font-size: 13px; color: var(--et-avertissement-ink); }

/* ---------------------------------------------------------------------------
   11.x  LÉGENDE ET MARQUE (.mx-legende / .mx-marque)

   CE QUE C'EST : la rangée « ▪ sous le besoin  ▪ au besoin  ▪ en surnombre » qui
   accompagne un graphique, et le petit carré de couleur qui y désigne une série
   ou un état. Rien d'autre — ce n'est pas une pastille d'état (.mx-pastille, qui
   porte un LIBELLÉ et se lit seule) ni une puce cliquable (.mx-puce).

   POURQUOI ELLE MONTE ICI. Trois écrans du pointage la dessinaient : la vue
   Charge du planning (.plg-pastille, un carré sans couleur — la page écrivait la
   couleur), l'accueil du module (une icône « bi-square-fill » coloriée dans un
   attribut style) et le tableau EasyBI (un .badge de Bootstrap avec un fond
   écrit dans la page). Trois formes pour un rôle, et surtout : dans les trois
   cas la COULEUR était écrite dans la page, ce que la charte proscrit.

   L'AMBRE EST CELLE DES MARQUES, PAS CELLE DES TEXTES. Une marque est un objet
   graphique : au seuil de 3:1, --et-avertissement rend 2,15:1 et échoue, d'où
   --et-avertissement-mark (docs/couleurs-en-dur.md et la charte du 2026-09-03).
   Le rouge et le vert, eux, passent tels quels.

   LES SÉRIES D'IDENTITÉ prennent .est-cat-1 à -8, dans l'ordre d'attribution de
   PaletteCategorielle — jamais une couleur d'état comme « série n° 4 ».
   --------------------------------------------------------------------------- */
.mx-legende {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px;
    font-size: 12px; color: var(--et-muted);
}
.mx-legende > span { display: inline-flex; align-items: center; gap: 6px; }

.mx-marque {
    flex: none; display: inline-block; width: 10px; height: 10px;
    background: var(--et-inerte);
}
.mx-marque.est-positif  { background: var(--et-succes); }
.mx-marque.est-critique { background: var(--et-avertissement-mark); }
.mx-marque.est-negatif  { background: var(--et-danger); }
.mx-marque.est-info     { background: var(--et-form-blue); }
.mx-marque.est-neutre   { background: var(--et-neutre); }

.mx-marque.est-cat-1 { background: var(--et-cat-1); }
.mx-marque.est-cat-2 { background: var(--et-cat-2); }
.mx-marque.est-cat-3 { background: var(--et-cat-3); }
.mx-marque.est-cat-4 { background: var(--et-cat-4); }
.mx-marque.est-cat-5 { background: var(--et-cat-5); }
.mx-marque.est-cat-6 { background: var(--et-cat-6); }
.mx-marque.est-cat-7 { background: var(--et-cat-7); }
.mx-marque.est-cat-8 { background: var(--et-cat-8); }

/* ---------------------------------------------------------------------------
   11.x  ÉTAT VIDE (.mx-etat-vide) — « il n'y a rien », dit dans une carte.

   À NE PAS CONFONDRE avec l'état vide du gabarit de LISTE, que `Tableau` rend
   lui-même (VideIcone / VideMessage, styles .tbl-*). Celui-ci sert un bloc qui
   n'est pas une table : un cumul sans salarié sur le périmètre retenu, une
   feuille de présence avant qu'on ait choisi une période.

   POURQUOI IL MONTE ICI. Le composant partagé `EtatVide` existait déjà, mais ses
   règles vivaient sous « .ecran-compta .cpt-empty » — scopées à un module. Hors
   EasyCompta, le composant rendait donc du texte NU : icône sans cadre, titre
   sans graisse. Deux écrans du pointage s'étaient rabattus sur un <div> à style
   en ligne (padding, centrage, couleur de note), et l'écran des périodes
   appelait le composant sans savoir qu'il ne peignait rien. Les valeurs sont
   celles d'EasyCompta, à l'identique : le module garde son rendu, les autres le
   gagnent.
   --------------------------------------------------------------------------- */
.mx-etat-vide {
    text-align: center; padding: 40px 24px; color: var(--et-muted);
    border: 1px dashed var(--et-form-border); background: var(--et-surface-3);
}
.mx-etat-vide .ic {
    display: inline-flex; align-items: center; justify-content: center;
    width: 56px; height: 56px; font-size: 24px;
    color: var(--et-form-blue-encre); background: var(--et-info-bg); margin-bottom: 10px;
}
.mx-etat-vide .tt { font-weight: 700; color: var(--et-muted); font-size: 15px; }
.mx-etat-vide .ms { font-size: 13px; margin-top: 3px; }
.mx-etat-vide .act { margin-top: 14px; }

/* ---------------------------------------------------------------------------
   11.x  PUCE (.mx-puce) — le petit bouton d'un choix court, en rangée.

   CE QU'ELLE EST : une valeur qu'on désigne d'un clic, quand il y en a trois à
   dix et qu'elles s'écrivent en un mot ou un nombre — une longueur de cycle
   (7, 14, 21, 28), un décalage de rotation, une cadence de période, « tout
   inclure / tout exclure ». Ce n'est ni un <select> (on veut les voir toutes),
   ni un segmenté (.mx-seg, qui porte des VUES exclusives et soudées), ni une
   pastille d'état (.mx-pastille, qui ne se clique pas).

   POURQUOI ELLE MONTE ICI. Quatre écrans l'avaient écrite : .fx-puce du modèle
   de fiche hérité (cycles, absences, affectations) et .pdp-chip d'un écran
   (périodes). La règle du dépôt tranche — une classe qu'emploient deux écrans
   monte au gabarit.

   ET ELLE FERME UNE FUITE EN PASSANT. Les états « actif » et « inerte » que trois
   fiches écrivaient n'étaient définis NI par le modèle de fiche (fiche.css ne
   connaît que la puce au repos) NI par elles : ils vivaient dans
   ecran-affectations.css, sous un sélecteur non préfixé — une feuille d'ÉCRAN qui
   habillait la puce de tous les autres. Trois écrans dépendaient donc de la
   feuille d'un quatrième pour peindre un état, et retirer ce quatrième écran les
   aurait dépeints sans qu'un seul de leurs fichiers change.

   La géométrie retenue est celle de .pdp-chip (écran des périodes), la seule des
   trois à suivre la charte : angles droits — la révision du 2026-09-02 met
   --et-r-btn à 0 — et hauteur de 28 px.
   --------------------------------------------------------------------------- */
.mx-puces { display: flex; gap: 6px; flex-wrap: wrap; }

.mx-puce {
    flex: none; height: 28px; padding: 0 11px;
    display: inline-flex; align-items: center;
    background: var(--et-surface); border: 1px solid var(--et-filet-fort);
    font-family: inherit; font-size: 12px; color: var(--et-ink);
    cursor: pointer; white-space: nowrap;
    transition: background .13s ease, border-color .13s ease, color .13s ease;
}
.mx-puce:hover:enabled { border-color: var(--et-form-blue); background: var(--et-info-bg); }

.mx-puce.est-actif {
    border-color: var(--et-form-blue); background: var(--et-info-bg);
    color: var(--et-form-blue-d); font-weight: 600;
}

/* Une puce INERTE n'est pas une puce désactivée : elle porte une valeur qu'on ne
   peut pas choisir ICI (un membre qu'une règle interdit d'exclure), et son motif
   est dans son title. Elle n'est donc pas un <button> chez ses appelants — d'où
   le curseur par défaut plutôt que « not-allowed », qui promet un clic refusé. */
.mx-puce.est-inerte { color: var(--et-inerte); cursor: default; }
.mx-puce:disabled { cursor: not-allowed; color: var(--et-inerte); }

/* LE MÊME BANDEAU, POSÉ DANS UNE CARTE DE LISTE (.md-card). Le bandeau nu est
   dessiné pour une PILE en tête d'écran, sur le fond de page : il n'a donc pas
   de gouttière latérale, et collé aux filets d'une carte il se lit comme une
   bande de la carte elle-même. Les quatre écrans du groupe Paramétrage
   d'EasyTime — périodes, natures d'heures, constantes d'export, conventions —
   posaient jusqu'ici une .alert de Bootstrap avec un « m-3 » pour la même
   chose : arrondi, couleurs hors charte, et trois écrans sur quatre écrivant
   une marge différente. Le modificateur monte donc ici plutôt que d'être
   recopié dans quatre feuilles d'écran, conformément à la règle du dépôt.

   12 px et non 16 : dans une carte, le bandeau suit une barre d'outils dont la
   gouttière basse est déjà posée. */
.mx-blocage.est-encarte { margin: 12px 18px; }


/* ---------------------------------------------------------------------------
   11.x  TABLE DANS UNE FICHE (.mx-tbl) — la collection d'un onglet.

   CE N'EST PAS LE GABARIT DE LISTE. `.tbl-*` (easytime-shell.css) sert un ÉCRAN
   de liste : barre d'outils, tri, export, pagination, états de vue. Une fiche a
   parfois besoin d'une table pour tout autre chose — les trois portées d'un
   modèle de paie, les six contacts d'un tiers, les quatre enfants d'un salarié.
   Trois à dix lignes, aucun tri, aucun export, et souvent des champs DE SAISIE
   dans les cellules. Lui donner `.tbl-*` poserait une barre d'outils au milieu
   d'un formulaire ; la laisser en `table table-sm` de Bootstrap la sort de la
   charte au milieu d'un écran qui la suit.

   POURQUOI ELLE MONTE ICI PLUTÔT QUE DE RESTER CHEZ UN ÉCRAN. Le motif a été
   écrit une première fois pour l'écran Tiers (`.tie-tbl`, ecran-tiers.css §3),
   et le modèle de paie en avait besoin à l'identique. La règle du dépôt tranche :
   une classe que DEUX écrans emploient monte dans le gabarit — jamais l'inverse,
   sous peine qu'un écran dépende de la feuille d'un autre pour s'afficher.
   `.tie-tbl` est donc désormais un DOUBLON de ce bloc, à retirer quand on
   repassera sur ListeTiers.razor.

   AUCUNE COULEUR LITTÉRALE : tout vient des jetons d'easytech-theme.css.
   --------------------------------------------------------------------------- */

/* Le cadre porte le filet et le défilement. La table, elle, ne défile pas :
   sans ce parent, une table de six colonnes à champs de saisie déborde la
   modale et pousse le voile entier vers la droite. */
.mx-tbl-cadre { border: 1px solid var(--et-hairline); overflow-x: auto; background: var(--et-surface); }

.mx-tbl { width: 100%; border-collapse: collapse; font-size: 13px; color: var(--et-ink); }

/* Tête en mono capitales, comme le gabarit de liste : la mono donne au libellé
   de colonne la même chasse qu'aux valeurs qu'il coiffe (matricules, ordres,
   montants), si bien qu'une colonne de chiffres ne se retrouve ni plus large ni
   plus étroite que son titre. Le fond reste CLAIR — le gris foncé de `.tbl`
   ancre une liste de douze colonnes, il ferait un bandeau dans un formulaire. */
.mx-tbl th {
    padding: 7px 10px; text-align: left; white-space: nowrap;
    background: var(--et-surface-2); border-bottom: 1px solid var(--et-hairline);
    font-family: var(--et-font-mono); font-size: 12px; font-weight: 600;
    letter-spacing: .14em; text-transform: uppercase; color: var(--et-table-head-encre);
}

.mx-tbl td { padding: 7px 10px; border-bottom: 1px solid var(--et-hairline); vertical-align: middle; }
.mx-tbl tbody tr:last-child td { border-bottom: 0; }

/* Variante SAISIE : les cellules portent des `.mx-input` / `.mx-select` de
   34 px, qui ont déjà leur propre respiration. Le rembourrage vertical retombe
   donc à 4 px — sinon chaque rangée en compte deux. */
.mx-tbl-saisie td { padding: 4px 8px; }

/* Trois formes de valeur que la table ne devine pas. `.mono` ne conviendrait
   pas : la charte ne la définit que COMPOSÉE — `.mx-input.mono`,
   `.mx-champ-val.mono`, `.mx-fait-val.mono` — et posée nue sur un `<td>` elle ne
   rend rien. D'où celles-ci, nommées dans la famille du gabarit. */
.mx-tbl-mono   { font-family: var(--et-font-mono); font-variant-numeric: tabular-nums; }
.mx-tbl-fort   { font-weight: 600; }
.mx-tbl-faible { color: var(--et-muted); }

/* TROIS ENCRES D'ÉTAT POUR UN CHIFFRE, au RANG -ink et à lui seul. La charte est
   nette : une couleur d'état ne s'écrit jamais en `color:` — c'est un aplat, un
   filet ou une marque —, et l'encre posée sur un fond clair prend le rang -ink,
   mesuré contre le fond le PIRE (une rangée survolée, une rangée retenue), pas
   contre le blanc. Ces trois classes existent pour que les écrans cessent
   d'employer .text-success / .text-danger, qui portent les verts et les rouges de
   Bootstrap — hors charte, et non mesurés. */
.mx-tbl-positif  { color: var(--et-succes-ink); }
.mx-tbl-critique { color: var(--et-avertissement-ink); }
.mx-tbl-negatif  { color: var(--et-danger-ink); }

/* LA RANGÉE DE TOTAUX. Elle ne se lit pas comme une ligne de plus : un ton de
   fond, une graisse, et un filet supérieur plus marqué que ceux des rangées.
   Deux écrans du pointage l'écrivaient dans la page — fond ET graisse dans un
   attribut style. */
.mx-tbl tfoot td {
    background: var(--et-surface-2); border-top: 1px solid var(--et-filet-fort);
    border-bottom: 0; font-weight: 600;
}

/* DEUX ÉTATS DE RANGÉE, et deux seulement.

   `est-terne` : la ligne est là, elle ne compte pas — un salarié visé par ce
   modèle mais qui en suit un plus précis. Elle se lit encore, elle ne s'annonce
   plus.
   `est-alerte` : la ligne est là et elle POSE UN PROBLÈME — une rubrique
   désactivée au référentiel, que le modèle retient pour rien. Le liseré à
   gauche vaut mieux que le seul fond : il survit au thème sombre, où l'ambre
   pâle d'un aplat descend à quelques pour cent du fond. */
.mx-tbl tbody tr.est-terne td { color: var(--et-inerte); }
.mx-tbl tbody tr.est-alerte td { background: var(--et-avertissement-bg); }
.mx-tbl tbody tr.est-alerte td:first-child { box-shadow: inset 3px 0 0 var(--et-avertissement); }

/* UNE TROISIÈME RANGÉE, ET CE N'EST PAS UN ÉTAT : LE BANDEAU DE GROUPE.

   `est-bande` intitule les rangées qui suivent — « Temps de présence », puis les
   natures d'heures de cette famille. Ce n'est ni une donnée ni une alerte, c'est
   un intertitre DANS la table, et c'est pour cela qu'il ne se trie pas et ne se
   compte pas.

   Deux écrans du pointage l'écrivaient : l'interface ERP avec un fond posé dans
   la page (« style="background:var(--et-info-bg)" »), les natures d'heures avec
   sa propre classe .nat-bandeau. La première passe ici ; la seconde suivra quand
   on repassera sur son tableau groupé, qui porte aussi un filet de couleur et un
   décompte. */
.mx-tbl tbody tr.est-bande td {
    background: var(--et-tint);
    font-family: var(--et-font-mono); font-size: 12px; font-weight: 700;
    letter-spacing: .12em; text-transform: uppercase;
    color: var(--et-form-blue-d);
}

/* L'ÉTAT VIDE DIT CE QUI SE PASSE ALORS, et non « aucun élément » : c'est la
   conséquence qu'on a besoin de connaître avant de décider d'ajouter une ligne. */
/* L'EN-TÊTE DE .mx-tbl EN SOMBRE — 1,79:1 sans cette règle, c'est-à-dire illisible.
   Ce n'est pas un défaut de cette table, c'est une DÉRIVE DE RÔLE de --et-table-head,
   exactement celle que le lot des jetons sombres (#767) a corrigée pour
   --et-form-blue-d. Le jeton a été posé comme un APLAT marine sous du texte blanc —
   « il habille des fonds à texte blanc », dit encore le commentaire d'ecran-organigramme.css
   — et il est vrai qu'en aplat il ne bouge pas par thème. Mais .mx-tbl th l'emploie dans
   l'AUTRE rôle : de l'encre, sur --et-surface-2, un fond qui, lui, s'assombrit. #354A5F
   sur #16202F rend 1,79:1 ; --et-sur-ardoise rend 10,50:1.

   PORTÉE VOLONTAIREMENT ÉTROITE. La dérive dépasse largement cette table : une centaine
   de règles, dans une trentaine de feuilles, posent --et-table-head comme encre et
   tombent au même endroit en sombre — ecran-lanceur.css l'a déjà rencontré et se
   redéfinit le jeton pour lui seul (ligne 90). Le remède de fond est celui de #767 :
   SCINDER le jeton, une valeur d'aplat qui ne bouge pas et une valeur d'encre qui
   s'éclaircit. C'est un lot « technique/… » à part entière, pas la queue d'un port
   d'écran — d'où cette règle bornée au gabarit, qui répare les quatre écrans le
   consommant (ModelePaieFiche, Rubriques, Formules, Cotisations) sans rien préjuger
   du reste. */
.appgrid[data-theme="dark"] .mx-tbl th { color: var(--et-sur-ardoise); }

.mx-tbl-vide { padding: 16px 10px; text-align: center; color: var(--et-muted); }

/* Bouton d'action de rangée. Le `.mx-btn` du gabarit fait 32 px de haut et
   0 12px de côtés : posé dans une table de trois lignes, il en double la
   hauteur. Celui-ci est le même bouton ramené au carré d'une rangée. */
.mx-tbl-btn { width: 26px; height: 26px; padding: 0; font-size: 12px; }
.mx-tbl-btn + .mx-tbl-btn { margin-left: 4px; }

/* Un champ de saisie en cellule prend la largeur de sa colonne, pas celle de la
   grille dense du formulaire — la colonne est déjà dimensionnée par son `th`. */
.mx-tbl td > .mx-input, .mx-tbl td > .mx-select { width: 100%; }

/* RÈGLE TACTILE (docs/cibles-tactiles.md) : sous 1024 px, aucune cible
   interactive ne descend sous 44 px, et une cible ne rétrécit jamais. */
@media (max-width: 1024px) {
    .mx-tbl-btn { width: 44px; height: 44px; font-size: 13px; }
    .mx-tbl-saisie td { padding: 6px 8px; }
}

/* ---------------------------------------------------------------------------
   11.x  ÉDITEUR D'ASSOCIATIONS TAXE (.mx-taxes) — composant EditeurTaxes.razor.

   POURQUOI IL EST MONTÉ DANS LE GABARIT
     Ces classes vivaient dans css/ecran-catalogue.css, sous le préfixe .cat-, du
     temps où le seul écran à saisir des associations db/1397 était le catalogue.
     La fiche article (ART-02) les saisit désormais aussi. Une classe employée par
     DEUX écrans monte ici — jamais l'inverse, ou la fiche article ne se rendrait
     correctement qu'à condition que la feuille du catalogue soit chargée.

   UNE RANGÉE PAR ASSOCIATION : la catégorie, le code, le retrait. La catégorie
   vient EN PREMIER parce que c'est elle qui se lit en diagonale — « ai-je bien
   posé quelque chose pour l'achat ? » —, le code ensuite.

   Aucune couleur littérale : tout vient des jetons d'easytech-theme.css.
   --------------------------------------------------------------------------- */

.mx-taxes { display: flex; flex-direction: column; gap: 6px; min-width: 0; }

.mx-taxe-ligne {
    display: grid;
    /* La catégorie est courte et fixe, le code prend ce qui reste, le retrait est
       un carré. Sans largeur fixe sur la première, les trois rangées s'alignaient
       sur le plus long libellé de code — et « Achat » se retrouvait décalé de
       « Tous domaines ». */
    grid-template-columns: 132px minmax(0, 1fr) 32px;
    gap: 6px;
    align-items: center;
}

/* En lecture seule le bouton de retrait n'est pas rendu : la troisième colonne
   n'a plus rien à porter, et la garder creuserait un vide de 32 px au bout de
   chaque rangée. */
.mx-taxes-lecture .mx-taxe-ligne { grid-template-columns: 132px minmax(0, 1fr); }

.mx-taxe-dom,
.mx-taxe-code { min-width: 0; }

/* Le retrait est DISCRET : c'est un geste rare et destructeur, il ne doit pas se
   disputer l'œil avec les deux listes qu'on vient remplir. Le rouge n'arrive
   qu'au survol — un bouton rouge en permanence attirerait vers ce qu'on veut
   justement éviter. */
.mx-taxe-retirer {
    width: 32px; height: 31px; padding: 0;
    border-radius: 0;
    color: var(--et-inerte);
}
.mx-taxe-retirer:hover { color: var(--et-danger-ink); background: var(--et-danger-badge-bg); }

/* RÈGLE TACTILE (docs/cibles-tactiles.md) : sous 1024 px, 44 × 44 px. Mesuré à
   390 et 768 px : 32 × 44. La HAUTEUR vient du §12 ci-dessous (.mx-btn) ; la
   largeur, non. Une cible à moitié conforme est le pire des cas : elle passe le
   contrôle de hauteur et se rate quand même au doigt. Le retrait d'une
   association fiscale est destructeur : le viser de travers coûte une saisie. */
@media (max-width: 1024px) {
    .mx-taxe-retirer { width: 44px; height: 44px; }
    .mx-taxe-ligne { grid-template-columns: 132px minmax(0, 1fr) 44px; }
}

.mx-taxe-ajouter {
    align-self: flex-start;
    height: 28px; padding: 0 8px;
    font-size: 13px;
}

/* La phrase qui dit ce qui se passe QUAND IL N'Y A RIEN. Un éditeur vide sans
   elle se lit comme un défaut d'affichage, alors qu'il décrit l'héritage — le
   cas le plus fréquent, et celui qu'on veut encourager. */
.mx-taxes-vide {
    font-size: 12px;
    line-height: 1.45;
    color: var(--et-faint);
    padding: 3px 0;
}
.mx-taxes-vide a { color: var(--et-form-blue-encre); }

/* ---------------------------------------------------------------------------
   11.x  FIL D'ÉTAPES (.mx-etapes) — composant FilEtapes.razor.

   Il arrive en section 11 dès sa naissance parce qu'il naît à DEUX écrans : les
   connecteurs Odoo et Sage 100 le posent tous les deux, et ils doivent compter
   pareil. Le laisser dans la feuille de l'un aurait fait dépendre l'autre d'un
   écran qui ne le concerne pas.

   IL S'APPELAIT .mx-fil, ET C'ÉTAIT LE NOM DU FIL D'ARIANE (§1). Deux blocs de
   CETTE feuille déclaraient donc le même sélecteur pour deux objets différents,
   et le second gagnait sur ce que le premier ne disait pas : le fil d'Ariane
   héritait d'un fond, d'un filet et d'une marge basse — il se dessinait comme
   une boîte encadrée — et ce fil-ci héritait du `display: flex` du fil d'Ariane,
   qui posait sa jauge de progression À CÔTÉ de sa liste d'étapes au lieu de
   l'avoir au-dessus. Aucun des deux ne rendait ce qu'il décrit.

   C'est la collision que docs/conventions-nommage.md décrit entre modules —
   « imbriquer, ou préfixer » — arrivée ici entre deux sections d'un même
   fichier, où aucun compilateur ne pouvait la voir. Le fil d'Ariane garde son
   nom : c'est celui de la passation, celui d'`.ecran-fil` qui lui répond dans
   easytime-shell.css, et celui que trois pages écrivent. C'est donc CE fil-ci
   qui est renommé, et « étapes » dit d'ailleurs mieux ce qu'il est.

   QUATRE ÉTATS, ET AUCUN N'EST DÉCORATIF
     · fait     — franchie, on peut y revenir            (encre normale, coche)
     · courant  — celle qu'on regarde                    (bleu SAP, pastille pleine)
     · atteint  — franchie plus tôt puis dépassée en avant, rouvrable
     · verrou   — pas encore accessible, avec sa condition écrite dessous

   LA COULEUR NE PORTE JAMAIS SEULE : « fait » a sa coche, « verrou » son texte de
   condition et son bouton désactivé. Un opérateur qui distingue mal le bleu du gris
   lit la même chose que les autres.
   --------------------------------------------------------------------------- */
.mx-etapes {
    background: var(--et-surface);
    border: 1px solid var(--et-hairline);
    margin-bottom: 14px;
}

/* La jauge : une hairline épaissie, pas un ruban. Elle informe de loin, elle ne
   décore pas de près. */
.mx-etapes-jauge { height: 3px; background: var(--et-surface-2); }
.mx-etapes-jauge-part {
    height: 3px;
    background: var(--et-form-blue);
    transition: width .18s ease-out;
}

.mx-etapes-liste {
    display: flex;
    flex-wrap: wrap;
    gap: 0;
    list-style: none;
    margin: 0;
    padding: 0;
}
.mx-etapes-etape { flex: 1 1 150px; min-width: 0; border-right: 1px solid var(--et-hairline); }
.mx-etapes-etape:last-child { border-right: 0; }

.mx-etapes-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 9px 10px;
    background: none;
    border: 0;
    text-align: left;
    font-family: var(--et-font);
    color: var(--et-ink);
    cursor: pointer;
}
.mx-etapes-btn:disabled { cursor: default; }

.mx-etapes-pastille {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px; height: 22px;
    border: 1px solid var(--et-hairline);
    border-radius: var(--et-r-pill);
    font-size: 12px;
    font-weight: 600;
    color: var(--et-muted);
    background: var(--et-surface);
}

.mx-etapes-txt { min-width: 0; display: flex; flex-direction: column; }
.mx-etapes-titre {
    font-size: 13px;
    line-height: 1.25;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mx-etapes-cond {
    font-size: 12px;
    line-height: 1.3;
    color: var(--et-faint);
    white-space: normal;
}

/* — franchie : la coche fait le travail, la couleur ne fait que l'appuyer. */
.mx-etapes-fait .mx-etapes-pastille,
.mx-etapes-atteint .mx-etapes-pastille {
    color: var(--et-ok);
    border-color: var(--et-ok);
}
.mx-etapes-fait .mx-etapes-titre, .mx-etapes-atteint .mx-etapes-titre { color: var(--et-muted); }

/* — courante : le seul bleu plein de la barre. */
.mx-etapes-courant { background: var(--et-surface-2); }
.mx-etapes-courant .mx-etapes-pastille {
    background: var(--et-form-blue);
    border-color: var(--et-form-blue);
    color: var(--et-sur-aplat);
}
.mx-etapes-courant .mx-etapes-titre { font-weight: 600; }

/* — verrouillée : éteinte, et elle dit ce qui l'ouvre. */
.mx-etapes-verrou .mx-etapes-pastille { color: var(--et-faint); }
.mx-etapes-verrou .mx-etapes-titre { color: var(--et-faint); }

/* Sous 1024 px la barre reste lisible : les conditions tombent (elles rallongent
   trop chaque cellule), et la cible garde ses 44 px — docs/cibles-tactiles.md. */
@media (max-width: 1024px) {
    .mx-etapes-btn { min-height: 44px; }
    .mx-etapes-cond { display: none; }
}

/* Sous 640 px, le fil se lit en colonne : quatre titres tronqués côte à côte ne
   disent plus quelle étape est laquelle, ce qui est tout ce qu'on lui demande. */
@media (max-width: 640px) {
    .mx-etapes-etape { flex: 1 1 100%; border-right: 0; border-bottom: 1px solid var(--et-hairline); }
    .mx-etapes-etape:last-child { border-bottom: 0; }
    .mx-etapes-titre { white-space: normal; }
}


/* ===========================================================================
   12. PALIER TACTILE DE LA BARRE D'OUTILS D'EN-TÊTE — sous 1024 px, aucune de
   ses cibles sous 44 px (docs/cibles-tactiles.md).

   POURQUOI ICI ET NON DANS css/lot02-cibles-tactiles.css
     Cette feuille est chargée EN DERNIER (voir App.razor) : une règle « .mx-btn »
     posée dans le lot perdrait la cascade contre le « height: 32px » déclaré au
     §1, à spécificité égale — et il aurait fallu un !important pour la reprendre,
     ce que l'en-tête de ce fichier refuse explicitement. C'est aussi la règle du
     dépôt depuis le lot 04 : une feuille porte ses propres paliers plutôt que de
     les déléguer à une feuille de lot (easypaie.css §17 pour la famille .ep-*).

   CE QUE ÇA FERME
     Le lot 02 atteignait la coquille, le gabarit de liste .tbl-*, les boutons de
     fin de ligne (.table td .btn) et la bande .barre-liste de BarreListe.razor.
     Il ne visait AUCUNE classe .mx-* : les actions GLOBALES de l'écran — celles
     du §1, Modifier, Enregistrer, Exporter, Nouveau X — restaient à 32 px sur
     les cent vingt écrans du gabarit, soit la surface partagée la plus large qui
     échappait encore à la règle. Un écran l'avait déjà corrigé pour lui seul
     (.ses-actions .mx-btn, plus bas) : c'est la trace du manque, pas sa réponse.

   « min-height » ET « height: auto », JAMAIS UNE HAUTEUR FIXE : .mx-btn en
   déclare une, et une hauteur fixe tronque un libellé qui passe à la ligne — ce
   qui arrive dès qu'un « Enregistrer et fermer » se replie sur un écran étroit.
   Le minimum, lui, ne fait que grandir. Même formulation qu'au §17 d'easypaie.

   AUCUNE COULEUR ICI, comme les lots 01 à 04 : de la géométrie, rien d'autre.
   =========================================================================== */
@media (max-width: 1024px) {
    .mx-btn { min-height: 44px; height: auto; padding-top: 6px; padding-bottom: 6px; }

    /* Icône seule : aucun libellé à replier, donc un carré franc plutôt qu'un
       minimum — comme .tbl-pg au lot 02, qui prend lui aussi une hauteur ferme.
       Déclaré après .mx-btn pour reprendre le « height: auto » qu'il pose. */
    .mx-btn-ico { width: 44px; height: 44px; padding: 0; }

    /* Deux commandes indépendantes voisines gardent leur écart : à 44 px, deux
       boutons collés forment une cible de 88 px dont on ne sait pas laquelle on
       touche. Même geste qu'au lot 02 sur .tbl-nav et qu'au §17 sur .ep-outils. */
    .mx-outils { gap: 10px; }

    /* Le champ posé DANS la barre d'outils suit ses boutons : un champ de 34 px à
       côté d'un bouton de 44 se touche moins bien que son voisin, et c'est lui
       qu'on vise en premier — la raison même qui vaut à .barre-liste ses 44 px.
       Les champs de FORMULAIRE restent hors d'atteinte : c'est la limite n° 2 de
       docs/cibles-tactiles.md, le gabarit dense ayant ses propres paliers. */
    .mx-outils .mx-select, .mx-outils .mx-input { height: 44px; }
}
