/* Design system Digistorm — tokens de la maquette trésorerie
   (docs/design-tokens-tresorerie-desktop.md), partagés desktop + mobile.

   ══════════════════════════════════════════════════════════════════════════
   L1a — LA PALETTE, PREMIÈRE TRANCHE : LES TOKENS NEUTRES ET LES CRÉATIONS
   Référence : docs/maquettes/digistorm_bl_redesign_v2.html (source des valeurs)
               docs/DESIGN_redesign_liste_bl.md (la spécification)

   ⚠️⚠️ CE QUE L1a NE FAIT PAS, ET POURQUOI — à lire avant de « finir le travail ».
   La maquette propose une palette entière. Trois familles de tokens en sont
   EXCLUES ici parce qu'elles changent de SENS, pas seulement de valeur :

   · LE ROUGE (--red, 94 usages) → tranche L1b.
     ⚠️ MESURÉ : il porte HUIT sens distincts — impayé · erreur de saisie ·
     marge négative · suppression · rupture de stock · refus · sortie d'argent ·
     reste à payer. La maquette n'a AUCUN rouge et met tout en ambre.
     ⇒ appliquée telle quelle, un IMPAYÉ et un RESTE À PAYER NORMAL porteraient
     la même couleur — ce que la tranche RG-LISTES a explicitement refusé :
     « un impayé est un PROBLÈME, un remboursement un FAIT NORMAL ; les confondre
     reviendrait à alerter sur un geste ordinaire, et l'alerte cesserait d'être lue ».
     ⭐ SEUL le sens « reste à payer » basculera en ambre (L1b) : là, la maquette
     APPLIQUE la doctrine au lieu de la violer.

   · LES ACCENTS DE PARTENAIRE (--blue fournisseur, --green client, --teal
     document) → tranche L1d. ⚠️ MESURÉ : la distinction est écrite dans ce
     fichier même (.doc-card--partenaire / --partenaire-client / --document).
     La maquette fusionne teal et green dans --brand : sur UN MÊME bon de
     livraison, la carte CLIENT et la carte DOCUMENT porteraient la même teinte.

   · LE TEXTE SECONDAIRE (--slate) → tranche L1c. ✅ FAITE — et elle a démenti
     ce paragraphe, qui annonçait « 229 sélecteurs, 92 décoratifs → --muted et
     137 de lecture → --ink-soft ; un arbitrage sélecteur par sélecteur, pas un
     remplacement de valeur ». ⚠️⚠️ LES TROIS AFFIRMATIONS ÉTAIENT FAUSSES :
       — le COMPTE : 222 occurrences dans 16 fichiers, dont 216 `color:` ;
       — le PARTAGE : 194 textes de lecture contre 22 micro-libellés (uppercase
         ou letter-spacing), soit NEUF SUR DIX en lecture — pas 137/92 ;
       — la CONCLUSION : --muted (#7C8880) est PLUS CLAIR que --slate, donc y
         basculer quoi que ce soit DÉGRADE (3,69 contre 4,51 sur --surface).
         C'est le refus que L1b avait déjà opposé à l'ambre (2,94 < 3,94).
     ⇒ L1c a changé la VALEUR, pas les usages — voir le motif à la déclaration.
     ⏳ SUITE, ET ELLE CLÔT LA QUESTION : L1c laissait --muted « en attente d'un
     emploi à TROUVER ». L2a l'a cherché et a RETIRÉ le token — non sur le seuil,
     mais sur le RÔLE : à 3,69 il ne pourrait vivre que sur du texte AA-large, et
     les 42 règles de cette taille sont TOUTES du texte primaire. Un titre en gris
     pâle est une contradiction de rôle. ⚠️ Les deux mentions de --muted ci-dessus
     sont donc HISTORIQUES : elles disent pourquoi L1c ne l'a pas employé, elles ne
     désignent plus un token existant.

   ⭐⭐ LE SENS DE SÛRETÉ QUI GOUVERNE LES TROIS : quand un choix se répète sur
   N sites, le DÉFAUT est le côté qui pardonne un oubli.
   · sur le rouge, le défaut est de RESTER rouge — un oubli laisse une alerte de
     trop, qui se voit et se corrige ; jamais une alerte éteinte, qui ne se voit pas ;
   · sur le texte, le défaut est le PLUS CONTRASTÉ — un oubli rend l'écran un peu
     trop appuyé, jamais un peu trop discret, sur un écran où l'on passe la journée.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
    /* ---- L1a : valeurs alignées sur la maquette (AUCUN changement de sens) ---- */
    --ink: #17211C;
    --paper: #EEF1ED;
    --surface: #FFFFFF;

    /* ---- ACCENTS DE PARTENAIRE ET D'APPLICATION — inchangés, voir L1d ---- */
    --teal: #0E8F84;
    --teal-dark: #0B756C;
    --teal-soft: #E4F3F1;
    --blue: #3B6FE0;
    --blue-soft: #EAF0FE;
    --green: #1E9E6B;
    --green-soft: #E7F7F0;

    /* ---- LE ROUGE — voir L1b (huit sens distincts) ---- */
    --red: #E24C3D;
    --red-soft: #FCEAE8;

    /* ⚠️⚠️ --red-dark — CRÉÉ EN T-ROUGE-LISIBLE, ET IL RÉPARE UN DÉFAUT QUI PRÉEXISTAIT.
       MÊME PATRON QUE --amber-dark (L1b) ET QUE LES SIX TONS FONCÉS DE L1d : l'accent reste
       sur les APLATS (fond de badge, pastille), le TON FONCÉ porte le TEXTE.

       ⭐⭐ CE QUE LA MESURE A DIT, ET C'EST LE MOTIF DE LA TRANCHE : --red rend 3,94 sur
         --surface, 3,46 sur --paper et 3,69 sur --surface-alt — SOUS le seuil AA (4,50) sur
         LES TROIS. C'est la couleur la plus visible des états, employée depuis toujours.
       ⚠️⚠️ ET AUCUNE GARDE NE LA REGARDAIT : `red/surface` n'était ni dans
         CombinaisonsCourantes ni dans SousLeSeuilAssume, et G2-bis ne voit QUE les couples
         écrits dans une MÊME règle — or `.etat-alerte` ne pose QU'UNE couleur, son fond
         venant d'un ANCÊTRE. La combinaison la plus vue des états n'était mesurée par personne.
         ⇒ G2-ter (PaletteTests) ferme cet angle, et le trou résiduel y est CHIFFRÉ.

       ⭐ CRITÈRE DURCI DE L1d, APPLIQUÉ TEL QUEL : ≥ 4,60 sur --surface, --paper,
         --surface-alt ET son fond doux --red-soft. #C12C1D rend 5,76 / 5,06 / 5,40 / 4,96.
       ⚠️⚠️ LA TEINTE ET LA SATURATION DE --red SONT CONSERVÉES AU DEGRÉ PRÈS (H 5,5°,
         S 74 %) : SEULE LA CLARTÉ CHANGE (56,3 % → 43,6 %). Le rouge doit rester RECONNAISSABLE
         comme le rouge d'alerte — c'est G1 qui l'exige, et c'est le SENS qui le commande.
       ⚠️⚠️ ET LA VALEUR LA PLUS CLAIRE QUI PASSE A ÉTÉ REFUSÉE : #CA2E1E rend 4,61 sur
         --red-soft, soit 0,01 au-dessus du seuil. Le dépôt a déjà payé cette marge une fois
         (L1c : #6E7787 tenait à 4,51 « QUE sur le blanc pur »). Un cran plus sombre coûte
         invisible et rend 4,96. */
    --red-dark: #C12C1D;

    /* ---- L'AMBRE — inchangé, voir L1b (il accueillera le « reste à payer ») ---- */
    --amber: #C98A1F;
    --amber-soft: #FBF1E0;

    /* ⚠️⚠️ LE TEXTE SECONDAIRE — L1c A CHANGÉ SA VALEUR, ET RIEN D'AUTRE.
       #6E7787 → #5E6A64. Aucun fichier de markup n'a été touché : les 222
       consommateurs suivent PAR CONSTRUCTION.

       ⭐⭐ POURQUOI LA VALEUR ET NON UNE BASCULE VERS --ink-soft / --muted :
       l'asymétrie des deux risques est totale. Changer une valeur ne peut RIEN
       oublier — le token est unique. Basculer 216 usages peut en oublier un, et
       un texte sous le seuil NE SE VOIT PAS : le mutant de L1a a rendu --ink
       illisible à 3,13 et LES 2608 ASSERTIONS DU HARNAIS SONT RESTÉES VERTES.
       La voie « bascule » contenait celle-ci PLUS le risque d'oubli.

       ⚠️ CE QUE #6E7787 NE PASSAIT PAS (mesuré, combinaisons DÉMONTRABLES —
       la même règle CSS y pose le fond ET la couleur) :
         --surface     4,51  (passait, mais de 0,01 : il ne tenait QUE sur le blanc pur)
         --paper       3,96  ⚠️ sous AA — l'exception que G2 avait inscrite
         --surface-alt 4,24  ⚠️ sous AA — LES EN-TÊTES DE COLONNES de toutes les
                             listes doclist et de toutes les grilles de document
         --amber-soft  4,03  ⚠️ sous AA — la note « solde masqué » du relevé produit
       ⚠️⚠️ Les DEUX DERNIÈRES échappaient à G2 : sa liste de combinaisons était
       tenue à la main et ne les contenait pas. Elle était VERTE parce qu'elle ne
       les REGARDAIT pas — c'est ce qui a fait passer G2 à la dérivation (L1c).

       ⭐ CE QUE #5E6A64 REND : 5,64 / 4,96 / 5,44 / 5,05 sur ces mêmes fonds, et
       4,68 sur le PIRE des treize fonds doux du dépôt. Il passe AA partout.
       ⚠️ Le seuil applicable est bien 4,50 et jamais AA-large (3,0) : mesuré, les
       216 usages vont de 9,5 à 16 px — AUCUN n'atteint 18,66 px gras ni 24 px.

       ⚠️ LA TEINTE EST UN ARBITRAGE, PAS UNE MESURE : #5F6875 (gris-bleu) rendait
       les mêmes ratios à 0,01 près. Le gris-VERT a été retenu parce que L1a a fait
       basculer toute la famille du texte vers le vert (--ink #17211C,
       --ink-soft #46524B, --muted #7C8880) : garder --slate en bleu en aurait fait
       le seul intrus, et l'alignement ne coûtait rien. */
    --slate: #5E6A64;

    --line: #DEE2D9;
    /* Accents secondaires (KPI/tags du tableau de bord) — deviennent des teintes
       d'avatar dans la maquette : changement de SENS, donc hors L1a. */
    --violet: #8b5cf6;
    --cyan: #0891b2;
    --rose: #e11d48;

    /* ══════════ L1a — LES CRÉATIONS ══════════
       ⚠️ Plusieurs de ces tokens N'ONT ENCORE AUCUN CONSOMMATEUR : ils sont posés
       ici parce que les tranches suivantes en dépendent, et parce que la spec le
       demande (« d'abord les fondations, puisque tout le reste en dépend »).
       ⚠️⚠️ MAIS un token que rien ne consomme finit par être consommé AU HASARD
       (c'est la doctrine T15 sur les colonnes que rien n'alimente). Chacun porte
       donc SA TRANCHE DE DESTINATION, et TokensPaletteTests le vérifie. */

    /* Couleur de MARQUE — l'accent d'APPLICATION seulement (jamais un partenaire).
       Consommée en L1d. */
    --brand: #0C6B57;
    --brand-dark: #0A5747;
    --brand-soft: #DCEEE7;
    --brand-softer: #EEF6F2;

    /* Fond très doux d'ambre — consommé en L1b (le « reste à payer »). */
    --amber-softer: #FBF3E7;

    /* ⚠️⚠️ AMBRE FONCÉ — POUR LE TEXTE, ET SEULEMENT POUR LE TEXTE (créé en L1b).
       MESURÉ avant de basculer quoi que ce soit : --amber (#C98A1F) rend 2,94 sur --surface,
       c'est-à-dire MOINS que --red (3,94) et SOUS le seuil AA-large (3,0) lui-même.
       ⇒ basculer un MONTANT du rouge vers l'ambre l'aurait rendu MOINS LISIBLE qu'avant.
       ⭐ C'est le patron d'--ink-soft (L1a) : on ne bascule pas vers un token illisible, on crée
         le ton foncé qui porte le texte. #8F6214 rend 5,35 sur --surface (AA) et 4,86 sur
         --amber-soft.
       ⚠️ NE PAS l'employer pour un APLAT (fond de badge, barre de KPI, point de légende) :
         un aplat décoratif n'a aucun ratio à tenir, et --amber y reste la bonne teinte. */
    --amber-dark: #8F6214;

    /* Texte secondaire — le niveau de LECTURE, à 8,17 (AAA) sur --surface.
       ⚠️ « consommés en L1c » était FAUX et l'est resté deux tranches : L1c a changé
       la VALEUR de --slate, elle n'a employé NI celui-ci NI --muted. Corrigé en L2a.
       ⏳ Son consommateur vient en L3 : le sous-titre de l'en-tête de page et les
       états de la recherche (mesuré dans la maquette, pas déduit).

       ⚠️⚠️ --muted (#7C8880) A ÉTÉ RETIRÉ ICI PAR L2a, ET LE MOTIF EST LE RÔLE, PAS
       LE SEUIL — c'est ce qui rend la décision définitive plutôt que révisable.
       À 3,69 il ne pouvait vivre QUE sur du texte AA-large (≥ 18,66 px gras ou
       ≥ 24 px) ; or les 42 règles de cette taille dans le dépôt sont TOUTES du
       texte PRIMAIRE — titres, valeurs de KPI, montants mis en avant. Un titre en
       gris pâle est une contradiction de rôle : le token dit « atténué » pendant
       que la taille dit « regardez-moi ». ⇒ il n'a aucun emploi possible.
       ⚠️ NE PAS LE RECRÉER pour un libellé discret : c'est --slate qui le porte,
       à 5,64 (AA) depuis L1c. Le retrouver : git log -S "--muted" -- app.css */
    --ink-soft: #46524B;

    /* ⚠️⚠️ CE COMMENTAIRE ÉTAIT VRAI DE L'UN ET FAUX DE L'AUTRE, ET IL A TRAVERSÉ
       L1b, L1c ET L1d SANS QUE RIEN NE LE SIGNALE — c'est ce défaut, mesuré, qui a
       fait écrire la garde symétrique de L2a (tout token déclaré sans consommateur
       DOIT être annoncé). Elle l'a nommé au premier lancement.
       · --surface-alt EST consommé depuis L1a (--thead-bg / --row-hover pointent
         dessus, et AvatarPalette s'en sert comme fond de repli) ;
       · --line-soft ne l'est PAS, et il n'était pas non plus dans la liste
         d'attente : il dormait, sans que personne sache quelle tranche l'emploierait.
       ⏳ Il est désormais ANNONCÉ pour L4 (bordure des cartes KPI), puis L6
       (séparateurs de lignes, fond de la barre de règlement). ⚠️ C'est un filet
       PLUS DOUX que --line, jamais son remplaçant. */
    --surface-alt: #F7F8F5;
    --line-soft: #E9ECE6;

    /* Teintes d'AVATAR — consommées en L1d (sept teintes : teal, amber, green,
       blue, violet, clay, indigo). */
    --clay: #9C4B2E;
    --clay-soft: #F3E4DC;
    --indigo: #3D5A80;
    --indigo-soft: #D4DDF0;
    --violet-soft: #E7E2F5;
    --rose-soft: #F5D9E0;

    /* ⚠️⚠️ LES SIX TONS FONCÉS — POUR LE TEXTE, ET SEULEMENT POUR LE TEXTE (créés en L1d).
       Même patron qu'--amber-dark (L1b) et --ink-soft (L1a) : on ne bascule JAMAIS un texte
       vers un token illisible, on crée le ton foncé qui le porte.

       ⭐ CRITÈRE DURCI, et il a mordu : un ton calculé pour passer AA sur SON SEUL fond doux
         tombait ailleurs — le premier candidat de --green-dark rendait 4,38 sur --paper, SOUS
         le seuil. Le critère retenu est donc « ≥ 4,60 sur le fond doux ET sur --surface,
         --paper, --surface-alt », parce qu'un accent posé sur son fond doux dans une carte se
         retrouve sur --paper dans la carte d'à côté.

       ⚠️ --green-dark (4,62) et --blue-dark (4,66) sont les DEUX PLUS SERRÉS sur --paper :
         si --paper fonçait, ce sont eux qui tomberaient les premiers. Ne pas les éclaircir.

       ⚠️⚠️ --clay-dark et --indigo-dark VALENT LEUR BASE, et ce n'est pas un oubli : ces deux
         teintes sont déjà assez foncées pour porter du texte (4,90 et 5,18 sur leur fond doux).
         Le token existe pour que la convention --X-soft / --X-dark soit tenable sur LES SEPT
         teintes — sans lui il faudrait une exception dans la palette, et la garde d'avatar
         (G4), qui DÉRIVE le couple depuis le nom, ne saurait pas résoudre la teinte.
         Une exception dans une convention est ce qui la fait tomber. */
    --green-dark: #177B53;
    --blue-dark: #3666CF;
    --violet-dark: #724BCA;
    --clay-dark: #9C4B2E;
    --rose-dark: #C0183D;
    --indigo-dark: #3D5A80;

    /* Fondations non colorées.
       ⚠️⚠️ --num-font vaut EXACTEMENT la chaîne de `html, body`, et ce n'est pas
       une commodité : 67 règles portent tabular-nums SEUL et HÉRITENT leur police
       du corps, quand 28 posent les deux et emploient ce token. Une valeur qui
       différerait ferait tomber ces deux familles sur DEUX replis distincts le
       jour où Manrope ne charge pas (dette de police L0, ouverte) — le trou serait
       rétréci, pas fermé. Aligner le token sur le corps rend les 67 couvertes PAR
       CONSTRUCTION, et une garde d'étage 1 le vérifie.
       ⚠️ La maquette propose 'Manrope', system-ui, sans-serif : REFUSÉ pour ce
       motif — elle fait autorité sur le visuel, jamais sur la mécanique.
       ⚠️ JAMAIS Fraunces : mesuré en L0, la fonctionnalité OpenType « tnum » est
       ABSENTE de cette famille — elle ne sait pas aligner les chiffres. */
    --num-font: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    /* ═══ L2b — L'ÉCHELLE DES RAYONS, PROMUE EN :root ════════════════════════════
       ⚠️⚠️ POURQUOI ELLE EST NOMMÉE PAR SA VALEUR, ET NON EN xs/sm/md/lg.
       Sous PROMOTION STRICTE, chaque bascule doit être une IDENTITÉ : le nom doit
       donc DIRE la valeur, sans quoi l'identité n'est pas vérifiable à l'œil.
       ⭐ Mesuré en L2b : les 11 valeurs fréquentes comptent QUATRE consécutives
       (6,7,8,9) et TROIS autres (10,11,12) — une échelle nominale ne peut pas les
       porter. C'est précisément ce qui rendra la FUSION nécessaire (voir plus bas).

       ⚠️⚠️ LA FUSION EST DIFFÉRÉE, ELLE N'EST PAS ABANDONNÉE — et le motif tient
       à L'ORDRE DES OPÉRATIONS, jamais à la prudence :
         · fusionner MAINTENANT se paierait sur ~85 sites, donc PAR SITE, avec le
           risque d'oubli qui NE SE VOIT PAS (85,4 % du périmètre mesuré) ;
         · la MÊME fusion faite APRÈS tokenisation se paie en changeant UNE VALEUR
           DE TOKEN — par VALEUR, aucun oubli possible.
       ⇒ « Une échelle est un choix de ce qu'on cesse de distinguer ; ce choix
       appartient à Walid et se prendra sur un code où chaque rayon passe par un
       nom. » Le jour venu, fondre 6/7/8/9 s'écrit : --r-6: 8px; --r-7: 8px;
       --r-9: 8px; — trois lignes, et 91 sites suivent sans qu'on les touche.
       ⚠️ Ne lisez donc PAS la promotion stricte comme la fin du sujet.

       ⚠️ LES VALEURS RARES RESTENT EN DUR (moins de 6 usages) : leur donner un
       cran gonflerait l'échelle sans rien unifier. Elles sont NOMMÉES avec leur
       motif dans RayonsTests.ValeursRaresEnDur, et G-R1 en tient le plafond. */
    --r-3: 3px;
    --r-6: 6px;
    --r-7: 7px;
    --r-8: 8px;
    --r-9: 9px;
    --r-10: 10px;
    --r-11: 11px;
    --r-12: 12px;
    --r-14: 14px;
    --r-16: 16px;
    --r-20: 20px;

    /* ⭐⭐ LES TROIS CRANS HISTORIQUES DE .docv2, PROMUS ICI — c'est le MOTIF
       PRINCIPAL de L2b, et ce n'est pas de l'uniformisation.
       Mesuré : article-fiche.css consommait --r-sm/--r-md/--r-lg NEUF fois SANS
       AUCUN REPLI, alors qu'ils n'étaient déclarés que sous .docv2{}. Ces neuf
       déclarations n'étaient valides que parce que ArticleFiche.razor:27 porte
       <div class="docv2 artfiche"> — une classe de DOCUMENT sur une FICHE.
       ⚠️⚠️ Retirer cette classe — geste qui semblerait parfaitement légitime —
       rendait les neuf INVALIDES AU CALCUL : coins carrés, AUCUNE erreur, sur un
       écran qu'aucun parcours ne mesure au style calculé.
       ⇒ ON AJOUTE UN REPLI GLOBAL, ON NE DÉPLACE RIEN : doc-v2.css garde ses
       déclarations locales (même valeur, la cascade leur donne la priorité dans
       .docv2), et le rendu est identique PARTOUT. C'est G-R4 qui le garde. */
    --r-sm: var(--r-9);
    --r-md: var(--r-11);
    --r-lg: var(--r-14);

    /* ⚠️ --radius avait ZÉRO consommateur (mesuré) alors qu'il vaut 10px, la
       valeur LA PLUS FRÉQUENTE du dépôt : le token le plus utile était celui que
       personne n'employait. Il pointe désormais vers son cran — une seule source. */
    --radius: var(--r-10);

    /* ══════════════════════════════════════════════════════════════════════════
       L2c — L'ÉCHELLE D'OMBRES, PROMUE EN REPLI GLOBAL (identité stricte)

       ⚠️⚠️ ON AJOUTE UN REPLI, ON NE DÉPLACE RIEN. Ces deux crans restent déclarés
       à l'identique dans doc-v2.css sous .docv2 : la cascade y donne la priorité à
       la déclaration locale, donc AUCUN des 9 usages ne voit son rendu changer.
       ⇒ Ce que le repli ajoute : article-fiche.css les consommait HORS de .docv2,
       SANS repli — ses trois déclarations étaient INVALIDES AU CALCUL dès que le
       wrapper disparaissait, c'est-à-dire une ombre absente, sans la moindre
       erreur, sur un écran qu'aucun parcours ne mesure au style calculé.

       ⚠️⚠️ DEUX ÉCHELLES CONCURRENTES ONT ÉTÉ RÉSOLUES ICI, ET C'EST L'USAGE QUI A
       TRANCHÉ. --shadow-sm / --shadow-md (recopiés de la maquette du redesign par
       L1a, MESURÉ : mêmes valeurs) vivaient à cet endroit avec ZÉRO consommateur,
       pendant que --sh-sm / --sh-lg en portaient neuf. Garder celle qui est
       EMPLOYÉE est le seul choix qui n'ait AUCUN effet sur le rendu ; garder les
       deux aurait laissé une ambiguïté permanente sur « laquelle employer », et un
       token que rien ne consomme finit par être consommé au hasard (T15, --muted).
       ⭐ La maquette reste versionnée : le jour où L4 aura besoin d'un cran moyen,
       elle le créera DANS cette échelle (--sh-md), jamais dans une seconde.
       ⚠️ Les retrouver : `git log -S "--shadow-md" -- DigistormWeb/wwwroot/app.css`.

       ⚠️ L2c NE NORMALISE PAS les 30 élévations écrites en dur : mesuré, elles
       portent 29 valeurs DISTINCTES, donc aucun cran n'atteint le seuil de
       fréquence (contre 11 crans pour les rayons en L2b). La normalisation est
       DIFFÉRÉE, non abandonnée — voir OmbresTests (G-O1) et le journal.
       ══════════════════════════════════════════════════════════════════════════ */
    --sh-sm: 0 1px 2px rgba(16,40,28,.05);
    --sh-lg: 0 10px 30px rgba(16,40,28,.12);

    /* En-tête de colonne / survol de ligne des tableaux.
       ⚠️ L1a les fait POINTER vers --surface-alt plutôt que de les supprimer :
       leurs 12 usages restent valides, et la valeur s'unifie. Un renommage
       aurait touché 12 sites pour un gain nul — le sens de sûreté vaut ici aussi. */
    --thead-bg: var(--surface-alt);
    --row-hover: var(--surface-alt);
    /* Fond très doux teinté teal des lignes de saisie (grille des documents).
       ⚠️ Teinté ACCENT, donc lié à --teal : il suivra en L1d. */
    --row-soft: #F6FAF9;
}

/* L0 — LE CORPS DE L'APPLICATION.
   ⚠️⚠️ Il n'était NI Sora NI Inter mais une police SYSTÈME : le remplacement demandé
   (« Sora → Fraunces, Inter → Manrope ») était donc plus large que sa formulation.
   ⚠️ CORRECTION D'UN MOTIF QUE J'AVAIS ÉCRIT FAUX : j'avais dit que cette ligne atteint les
   21 pages d'impression « PAR HÉRITAGE ». Mesuré ensuite sur la page réelle : ce n'est vrai
   que d'une PARTIE d'entre elles. `.pf-root` (les 11 documents commerciaux, les états, le
   relevé) DÉCLARE sa propre police — elle était en Inter, et c'est la passe de remplacement
   qui l'a basculée, pas l'héritage. Seuls les tickets 80 mm, le bulletin et la page publique
   héritent réellement d'ici (mesuré : aucune autre racine d'impression ne déclare de police).
   Le résultat est le même, le motif ne l'était pas — et un motif faux se lit au pire moment.
   ⭐ NON-DÉBORDEMENT MESURÉ SUR LE CODE ÉCRIT, pas sur un HTML de laboratoire : deux bons de
   livraison réels (1 150,00 et 980,40) rendus par la vraie page d'impression → 24 et 20
   cellules de montants, **ZÉRO débordement**, à l'écran comme sous media=print, et la page
   ne déborde pas. La hauteur d'x de Manrope est en outre SUPÉRIEURE (54 contre 52 à 100 px) :
   le montant imprimé est légèrement PLUS lisible qu'avant.
   ⚠️ La chaîne de repli reste celle d'avant : si Manrope ne charge pas (installation hors
   ligne, filtre réseau), l'application retombe exactement sur ce qu'elle affichait hier.
   C'est la dette de police, ouverte et non payée — elle vaut désormais pour DEUX familles. */
/* ⚠️⚠️ CETTE RÈGLE PORTE LA CHAÎNE LITTÉRALE, ET JAMAIS var(--num-font) : elle en
   est la SOURCE. --num-font doit VALOIR cette chaîne (une garde d'étage 1 le
   vérifie) ; l'y faire pointer créerait une circularité — le token vaudrait le
   corps qui vaudrait le token — et la garde ne pourrait plus rien comparer.
   ⭐ C'est ici, et nulle part ailleurs, que se décide le repli de TOUT le dépôt :
   les 67 règles qui portent tabular-nums SEUL héritent leur police d'ici.
   ⚠️ Le premier script de bascule de L2a a justement remplacé CETTE déclaration,
   parce qu'il cherchait la première occurrence de la chaîne dans le FICHIER au
   lieu de la remplacer PAR SA POSITION dans la règle ciblée. Il rendait le bon
   NOMBRE de remplacements (28) aux MAUVAIS endroits. */
html, body {
    font-family: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    background: var(--paper); /* #EEF1ED */
    color: var(--ink); /* #17211C — pour éviter le noir de navigateur */
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* Boutons icône (crayon/corbeille) des colonnes Action des écrans CRUD "liste + Nouveau + modale" */
.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: var(--r-6);
    border: 1px solid transparent;
    background: transparent;
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}

.icon-btn-edit {
    color: #0d6efd;
}

    .icon-btn-edit:hover {
        background: rgba(13, 110, 253, 0.1);
        border-color: rgba(13, 110, 253, 0.25);
    }

.icon-btn-delete {
    color: #dc3545;
    margin-left: 0.25rem;
}

    .icon-btn-delete:hover {
        background: rgba(220, 53, 69, 0.1);
        border-color: rgba(220, 53, 69, 0.25);
    }

/* Barre d'outils des écrans CRUD (titre + recherche + Nouveau) */
.crud-search {
    max-width: 320px;
}

/* =========================================================================
   L3 — LE CHAMP DE RECHERCHE : LOUPE, CROIX D'EFFACEMENT, HALO AU FOCUS.

   ⚠️⚠️ TOUT CE BLOC EST BORNÉ À `.crud-toolbar-actions`, ET CE N'EST PAS UN
   ORNEMENT. `.crud-search` est porté par QUARANTE champs, dont deux qui ne
   sont PAS dans une barre d'outils de liste : la pop-up « Saisie multiple »
   (dans une <Modal>) et la bannière du hub des États. Un sélecteur global les
   atteindrait — et la maison a payé DEUX FOIS un style posé sur un conteneur
   partagé (T-UI : `.btn-filtres`, puis `.crud-toolbar-actions` lui-même).
   ⭐ Le bornage est STRUCTUREL, pas une liste d'exceptions : la pop-up vit dans
   `.filter-row` et la bannière dans `.etat-banner-search` — ni l'une ni l'autre
   n'est descendante de `.crud-toolbar-actions`, donc l'atteinte est IMPOSSIBLE
   par construction, jamais évitée par vigilance. C'est G-H6 qui le fige.
   ========================================================================= */
.crud-search-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    min-width: 0;
    max-width: 100%;
}

/* La loupe et la croix vivent DANS le champ : elles sont donc sur --surface
   (blanc), jamais sur le fond de page. G-H2 les mesure sur ce fond-là. */
.crud-search-wrap .rl-ico {
    position: absolute;
    left: 10px;
    width: 16px;
    height: 16px;
    color: var(--slate);
    pointer-events: none;
}

.crud-search-wrap .rl-clear {
    position: absolute;
    right: 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: var(--r-6);
    background: transparent;
    color: var(--slate);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
}

.crud-search-wrap .rl-clear:hover {
    background: var(--surface-alt);
    color: var(--ink);
}

.crud-search-wrap .crud-search {
    width: 100%;
    max-width: none;
    padding-left: 32px;
    padding-right: 30px;
}

/* ⚠️ L'ÉLARGISSEMENT AU FOCUS NE DOIT PAS ATTEINDRE LES DEUX CHAMPS HORS BARRE :
   il est donc porté par `.crud-toolbar-actions`, jamais par `.crud-search-wrap` seul.

   ⚠️⚠️ `width` ET NON `flex-basis`, ET C'EST UNE MESURE QUI L'A IMPOSÉ — la première
   version employait `flex: 0 1 320px` / `flex-basis: 400px`, et la recette a montré
   qu'elle produisait DEUX défauts, tous deux causés par le même fait : mesuré,
   `.crud-toolbar-actions` N'EST DÉCLARÉ NULLE PART en desktop — ce n'est PAS un
   conteneur flex.
     ① au repos comme au focus, `flex` était donc IGNORÉ : le champ restait à sa
        largeur intrinsèque (248 px mesurés), et l'élargissement ne se produisait
        JAMAIS — une fonctionnalité livrée morte, qu'aucun parcours n'aurait vue ;
     ② en MOBILE, où `@media (max-width: 768px)` passe ce conteneur en
        `flex-direction: column`, ce même `flex-basis` gouvernait la HAUTEUR : le
        wrapper prenait 320 px DE HAUT, et repoussait la première ligne de +282 px.
   ⭐ `width` corrige les deux d'un seul geste : il vaut en dehors de tout contexte
   flex, et il devient une `flex-basis: auto` HORIZONTALE quand le parent EST flex
   (écran Clients, T-UI) — où `flex-shrink` le comprime alors normalement.
   ⚠️ `max-width: 100%` est la ceinture : le champ ne peut pas déborder de sa barre. */
.crud-toolbar-actions .crud-search-wrap {
    width: 320px;
    transition: width .18s ease;
}

.crud-toolbar-actions .crud-search-wrap:focus-within {
    width: 400px;
}

/* ⚠️⚠️ LE HALO RÉUTILISE L'ANNEAU DE FOCUS DÉJÀ PRÉSENT DANS LE DÉPÔT — la bordure
   par le token d'accent, l'anneau de 3 px par son fond doux. Il est DÉJÀ porté par
   `.doclist-filters .form-select:focus`, c'est-à-dire par les filtres qui vivent
   JUSTE À CÔTÉ de ce champ : en inventer un second donnerait deux anneaux différents
   à deux contrôles voisins.
   ⚠️ Il ne consomme PAS `--brand` : ce token est en attente d'emploi (liste
   TokensEnAttente), et l'employer ici le ferait SORTIR de la liste — donc
   disparaître l'annonce d'un travail qui reste entier. G-H6 l'interdit. */
.crud-toolbar-actions .crud-search:focus {
    border-color: var(--teal);
    box-shadow: 0 0 0 3px var(--teal-soft);
    outline: none;
}

/* Le placeholder : c'est un TEXTE, donc seuil AA (4,50) — pas le 3,00 des icônes. */
.crud-search-wrap .crud-search::placeholder {
    color: var(--slate);
    opacity: 1;
}

/* Alignement des colonnes numériques QUANTITATIVES (montants DH, quantités, %) dans
   les tableaux CRUD — à poser à la fois sur le <th> et le <td> de la colonne visée.
   Les identifiants/codes numériques (numéro de compte, téléphone), dates et autres
   valeurs qualitatives restent alignés à gauche par défaut : ne PAS leur appliquer
   .crud-num. Pas encore utilisée sur ComptesTresorerie (aucune colonne quantitative
   sur cet écran) ; prête pour les futurs écrans à montants (Produits : PMP/prix,
   Factures : totaux, etc.). */
.crud-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* CHIFFRES DE DONNÉES : décision de token — Inter + tabular-nums (remplace IBM Plex
   Mono partout). Colonnes alignées au pixel grâce à tabular-nums. `.num` = valeur
   normale (500) ; `.num--strong` = valeur forte (600). N'impose PAS l'alignement
   (utiliser .crud-num pour aligner à droite). */
.num {
    font-family: var(--num-font);
    font-variant-numeric: tabular-nums;
    font-weight: 500;
}
.num--strong { font-weight: 600; }

/* En-tête de tableau collant au défilement vertical, desktop uniquement — sous 768px
   le <thead> est déjà masqué (tableau -> cartes, voir plus bas) donc le sticky n'a
   aucun effet à neutraliser côté mobile, mais on borne quand même explicitement à
   min-width:769px pour que ce soit auto-documenté. "top" est décalé de la hauteur de
   .app-topbar (3.75rem, lui-même sticky) pour ne pas se chevaucher avec elle pendant
   le défilement ; z-index inférieur à celui de .app-topbar (5) pour la même raison. */
@media (min-width: 769px) {
    .crud-list thead th {
        position: sticky;
        top: 64px; /* hauteur de .app-topbar (maquette desktop) */
        z-index: 1;
        background: #ffffff;
        box-shadow: inset 0 -1px 0 #dee2e6;
    }
}

/* En-tête de colonne triable (tri client-side, voir ColumnSortState.cs). Le libellé
   et l'icône de tri sont dans un conteneur flex commun ; l'icône garde une largeur
   fixe qu'elle soit affichée ou non (colonne active ou pas), pour que l'en-tête ne
   change jamais de largeur/alignement au clic. */
.crud-sortable {
    cursor: pointer;
    user-select: none;
}

    .crud-sortable:hover {
        color: #0f172a;
    }

.crud-th-label {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}

.crud-sort-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 10px;
    height: 10px;
    flex: none;
}

/* Responsive des écrans CRUD "liste + Nouveau + modale" : un seul point de rupture,
   768px, partagé par tous les écrans (toolbar + tableau -> cartes).
   Centralisé ici (plutôt que dans le *.razor.css de chaque page) pour que la
   propagation aux autres écrans CRUD n'exige que d'ajouter les classes
   "crud-toolbar"/"crud-toolbar-actions"/"crud-search"/"crud-list" et les attributs
   data-label sur les <td>, sans dupliquer de CSS. */
@media (max-width: 768px) {
    .crud-toolbar {
        flex-direction: column;
        align-items: stretch;
    }

    .crud-toolbar-actions {
        flex-direction: column;
        align-items: stretch;
        width: 100%;
    }

    .crud-search {
        max-width: none;
        width: 100%;
    }

    /* ⚠️⚠️ L3 — LE CHAMP OCCUPE TOUTE LA LARGEUR, ET IL NE S'ÉLARGIT PLUS.
       La barre passe en colonne juste au-dessus : une largeur de 320 px y laisserait
       un champ étroit au milieu d'une pile de boutons pleine largeur.
       ⚠️ LES DEUX SÉLECTEURS SONT NÉCESSAIRES : `:focus-within` porte une pseudo-classe,
       donc une spécificité (0,3,0) SUPÉRIEURE à celle de cette règle (0,2,0) — sans le
       second, le champ passerait à 400 px AU FOCUS et déborderait d'un écran de 390. */
    .crud-toolbar-actions .crud-search-wrap,
    .crud-toolbar-actions .crud-search-wrap:focus-within {
        width: 100%;
    }

    .crud-toolbar-actions .btn {
        width: 100%;
    }

    .icon-btn {
        width: 2.5rem;
        height: 2.5rem;
    }

    table.crud-list thead {
        display: none;
    }

    table.crud-list, table.crud-list tbody, table.crud-list tr, table.crud-list td {
        display: block;
        width: 100%;
    }

    table.crud-list tr {
        border: 1px solid #e5e7eb;
        border-radius: var(--r-8);
        margin-bottom: 0.75rem;
        padding: 0.35rem 0.75rem;
        background: #ffffff;
    }

    table.crud-list td {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 0.75rem;
        padding: 0.5rem 0;
        border: none;
        border-bottom: 1px solid #f1f5f9;
        text-align: right;
    }

        table.crud-list td:last-child {
            border-bottom: none;
        }

        table.crud-list td::before {
            content: attr(data-label);
            font-weight: 600;
            color: #475569;
            text-align: left;
            flex: none;
        }

        table.crud-list td[data-label="Action"] {
            justify-content: flex-end;
        }

            table.crud-list td[data-label="Action"]::before {
                content: "";
            }
}

/* ============================================================================
   Cartes mobiles "maquette trésorerie" (crud-cards) — remplaçant, écran par
   écran, le rendu générique tableau->cartes ci-dessus. Spec : maquette
   digistorm-tresorerie-cards + design tokens (Sora/Inter tabular-nums,
   rayon 16px, liseré gauche 4px couleur du type, pastille icône 36px,
   séparateur méta dashed, FAB teal). Desktop (>768px) : bloc entièrement
   masqué, le tableau reste seul visible.
   Couleur/icône par type via CSS custom properties --card-accent /
   --card-accent-soft posées en style inline sur chaque .crud-card.
   ========================================================================= */
/* Chips de filtre — partagées desktop + mobile (rangée horizontale, pill). */
.crud-chips {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 0.35rem;
    margin-bottom: 0.85rem;
    scrollbar-width: none;
    font-family: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

    .crud-chips::-webkit-scrollbar {
        display: none;
    }

.crud-chip {
    flex: none;
    padding: 0.42rem 1rem;
    border-radius: var(--r-20);
    border: 1px solid var(--line);
    background: var(--surface);
    color: #334155;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}

    .crud-chip.active {
        background: var(--ink);
        border-color: var(--ink);
        color: #ffffff;
    }

/* ============================================================================
   Desktop "maquette trésorerie" (> 768px) : en-tête de page (eyebrow + titre +
   sous-titre), cartes de synthèse, tableau en carte blanche avec pastilles de
   type et pied de tableau. Spec : docs/design-tokens-tresorerie-desktop.md.
   Tous ces blocs sont masqués (ou sans objet) en mobile : .page-head-text,
   .summary-grid et le pied vivent dans des éléments portant crud-hide-mobile
   ou dans la table déjà masquée sous 768px via crud-desktop-table.
   ========================================================================= */
.page-head-text {
    font-family: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* ⚠️⚠️ L3 — CE FIL D'ARIANE ÉTAIT SOUS AA DEPUIS TOUJOURS, ET AUCUNE GARDE NE
   POUVAIT LE VOIR. Il portait `--teal` à 12,5 px, soit 3,68 sur le fond réel de
   la page (#f4f6fa) pour un seuil de 4,50. Ni G2 (couples de tokens NOMMÉS) ni
   G2-bis (couples posés dans une MÊME règle CSS) ne l'atteignaient : le fond vient
   d'un ANCÊTRE (.app-shell), et c'est l'angle mort que G2-bis déclare elle-même.
   ⭐ C'est G-H2 qui l'a nommé, en DÉRIVANT le token du CSS au lieu de le porter —
   et c'est pour cela qu'elle a pu rougir sur l'état d'avant.
   ⇒ `--slate` rend 5,22 sur ce fond. Le fil d'ariane perd son accent teal, ce que
   la maquette veut aussi : l'accent doit rester à ce qui est cliquable. */
.page-eyebrow {
    display: block;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--slate);
    margin-bottom: 4px;
}

/* ⚠️ L3 — 28 → 20 px. Le titre reste le plus gros texte de la page ; il cesse
   d'être le plus gros de l'écran. ⭐ Et l'ordonnée qui le suit NE BOUGE PAS d'un
   pixel du fait de la taille : `font-size` et `line-height` étant fixés, la boîte
   du titre ne dépend pas des métriques — c'est ce que L0 avait mesuré en changeant
   de FAMILLE sans déplacer t122/D2. Ici c'est la MISE EN LIGNE qui rend l'espace. */
.page-head-text h1 {
    font-family: 'Manrope', sans-serif;
    font-size: 20px;
    font-weight: 600;
    letter-spacing: -.2px;
    color: var(--ink);
    margin: 0;
    white-space: nowrap;
}

/* L3 — LE TITRE ET SON SOUS-TITRE SUR UNE SEULE LIGNE, alignés sur la BASELINE.
   ⚠️ `baseline` et non `center` : deux textes de tailles différentes alignés au
   centre paraissent décalés, parce que l'œil suit la ligne d'écriture et non la
   boîte. ⚠️ `flex-wrap` conservé : sur un titre long, le sous-titre repasse dessous
   plutôt que de déborder — la page ne défile jamais horizontalement. */
.page-head-text .title-line {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}

/* ⚠️⚠️ L0 — LE SEUL ENDROIT DE L'APPLICATION OÙ LA POLICE DE TITRE (Fraunces) HABILLE
   UN CONTENU VARIABLE, et il est OPT-IN : la classe est POSÉE par PageHeader, et
   seulement quand le titre ne porte AUCUN chiffre (TypographieRegles.ClasseTitre).
   ⚠️ Sens de sûreté : le DÉFAUT est Manrope, ci-dessus — une règle qu'on oublierait de
   basculer tombe donc du côté qui sait aligner. Fraunces ne peut gagner du terrain que
   par un geste explicite.
   ⚠️ « opsz » est l'axe de taille optique de Fraunces : sans lui la famille rend ses
   glyphes dessinés pour le corps de texte, trop contrastés à 28 px. */
/* ⚠️⚠️ AUCUN COMMENTAIRE ENTRE CE SÉLECTEUR ET SES DÉCLARATIONS.
    ⚠️ LES QUATRE DÉCLARATIONS NEUTRALISÉES À LA MAIN ONT ÉTÉ RETIRÉES ICI (L9a). Laissées
   en commentaire, elles séparaient le SÉLECTEUR de sa déclaration vivante — et la garde de
   typographie, qui lit une fenêtre de trois lignes, ne voyait plus que du commentaire :
   elle déclarait Fraunces « hors liste autorisée » sur un sélecteur qui Y EST.
   ⭐ Leur contenu (Fraunces complète, `font-variation-settings: 'opsz' 96`, `-.01em`) est
   consigné au journal : c'est L9b qui REMESURERA la variante optique, à la taille que L3 a
   changée depuis — pas un commentaire mort qui aveugle une garde. */
.page-head-text h1.titre-serif {
    font-family: 'Fraunces', serif;
    font-weight: 600;
    letter-spacing: -.2px;
}

/* L3 — 13,5 → 12 px, et `--slate` → `--ink-soft` (5,22 → 7,56 sur le fond de page).
   ⚠️ Le sous-titre RÉTRÉCIT et devient pourtant PLUS lisible : c'est ce que la
   maquette voulait obtenir en le passant sous `--muted`, sauf que ce token rend
   3,41 sur ce fond — SOUS AA — et que L2a l'a RETIRÉ du :root pour ce motif exact.
   ⭐ `--ink-soft` trouve ici son PREMIER consommateur : il quitte donc la liste
   TokensEnAttente, et c'est la garde symétrique de L1a qui l'exige. */
.page-sub {
    font-size: 12px;
    color: var(--ink-soft);
    font-weight: 500;
    margin: 0;
    white-space: nowrap;
}

.btn-filtres {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 10px 14px;
    border-radius: var(--r-10);
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--ink);
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}

    .btn-filtres:hover {
        background: var(--paper);
    }

.btn-nouveau {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 10px 18px;
    border-radius: var(--r-10);
    border: none;
    background: var(--teal);
    color: #ffffff;
    font-size: 13.5px;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
    box-shadow: 0 6px 16px rgba(14, 143, 132, 0.28);
}

    .btn-nouveau:hover {
        background: var(--teal-dark);
    }

.summary-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 14px;
    margin-bottom: 1.1rem;
    font-family: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.summary-card {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    background: var(--surface);
    border: 1px solid var(--line-soft);
    border-radius: var(--r-14);
    padding: 16px 18px;
    box-shadow: var(--sh-sm);
}

.summary-icon {
    flex: none;
    width: 42px;
    height: 42px;
    border-radius: var(--r-11);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--sum-soft, var(--teal-soft));
    color: var(--sum-color, var(--teal));
}

.summary-label {
    display: block;
    font-size: 12px;
    font-weight: 600;
    color: var(--slate);
}

.summary-value {
    font-family: var(--num-font); font-variant-numeric: tabular-nums;
    font-size: 19px;
    font-weight: 600;
    color: var(--ink);
}

/* Tableau en carte blanche. "overflow: clip" (et PAS "hidden") pour arrondir
   les coins sans créer de conteneur de défilement — hidden casserait le
   position:sticky de l'en-tête de colonnes (le sticky deviendrait relatif à
   la carte, qui ne défile pas, donc plus aucun effet). */
.crud-table-card {
    background: var(--surface);
    border: 1px solid var(--line-soft);
    border-radius: var(--r-16);
    box-shadow: var(--sh-sm);
    overflow: clip;
    font-family: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

    .crud-table-card .crud-list {
        margin-bottom: 0;
    }

    .crud-table-card .crud-list thead th {
        background: #FAFBFC;
        font-size: 11.5px;
        font-weight: 700;
        letter-spacing: 0.4px;
        text-transform: uppercase;
        color: var(--slate);
        padding: 8px 20px;
        border-bottom: 1px solid var(--line);
    }

    .crud-table-card .crud-list tbody td {
        padding: 8px 20px;
        border-bottom: 1px solid var(--line);
        font-size: 14px;
        vertical-align: middle;
    }

    .crud-table-card .crud-list tbody tr:hover td {
        background: #FAFBFD;
    }

    .crud-table-card .cell-mono {
        font-family: var(--num-font); font-variant-numeric: tabular-nums;
        font-weight: 600;
    }

    .crud-table-card .cell-empty {
        font-style: italic;
        color: #B7BEC9;
    }

    .crud-table-card .dh {
        font-size: 11px;
        font-weight: 500;
        color: var(--slate);
        margin-left: 2px;
    }

.type-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--r-9);
    margin-right: 0.6rem;
    background: var(--card-accent-soft, var(--blue-soft));
    color: var(--card-accent, var(--blue));
    vertical-align: middle;
}

.cell-name {
    font-weight: 600;
    color: var(--ink);
}

.crud-table-card .icon-btn-edit {
    background: var(--blue-soft);
    color: var(--blue-dark);
}

.crud-table-card .icon-btn-delete {
    background: var(--red-soft);
    color: var(--red);
}

/* L6a - BOUTON D'ACTION DES LISTES : 32 -> 26 px.
   La valeur est MESUREE, pas copiee de la maquette (qui propose 24) :
   - plancher NORMATIF pour un pointeur fin = 24x24 CSS px (WCAG 2.2, SC 2.5.8
     "Target Size (Minimum)", niveau AA). 26 le depasse de 2 px : un minimum
     n'est pas une cible, s'y poser exactement ne laisse aucune marge au
     premier ajustement de bordure ou de rembourrage ;
   - plancher MESURE DANS L'ECRAN, et c'est lui qui commande : sous 26 px, on
     ne gagne PLUS RIEN, on perd seulement de la cible cliquable. Mesure du
     poste le plus haut de la ligne, ecran par ecran :
         Clients      -> le BOUTON (43,5 px de ligne)
         BL/reception -> doclist-badge 24,5 / paybar (44,5)
         Produits     -> statut-badge 26,5 (43,8)
         Tresorerie   -> type-badge 32 (49,0)
     Des que le bouton passe sous le plus haut de ces postes, c'est LUI qui
     prend le relais : la hauteur de ligne cesse de bouger et le geste ne
     rapporte plus que du risque. 26 est le point ou le bouton cesse d'etre
     le poste dominant SANS passer sous le plancher normatif ;
   ⚠️ NE PAS descendre a 24 "comme la maquette" : cela ne ferait PAS baisser
     une seule ligne (les badges gouvernent deja), et poserait la cible
     exactement SUR le minimum WCAG, sans aucune marge ;
   - l'icone rend 16x16 (icon-btn) et 14x14 (doclist-edit-btn) : il reste 5 a
     6 px de rembourrage de chaque cote.

   BORNAGE - la mesure a impose les deux moities :
   - `.crud-table-card.crud-hide-mobile` est pose par CrudTableShell et par
     ComptesTresorerie, et par EUX SEULS : les documents posent
     `crud-table-card doc-prod-card`, SANS `crud-hide-mobile`. Sans ce couple,
     la regle atteindrait les 22 ecrans hors liste qui emploient `.icon-btn` -
     dont la TopBar et les grilles de saisie des documents. Le depot a paye
     DEUX FOIS un style pose sur un conteneur partage (T-UI).
     ⚠️ Mesure faite : les boutons 38x38 de la TopBar sont INCHANGES - c'est
     d'ailleurs eux qu'une premiere sonde a lus en croyant lire le tableau.
   - `@media (min-width: 769px)` : sous 768 px, `.icon-btn` passe a 2.5rem
     (40 px) - c'est LA CIBLE TACTILE, et elle est PRESERVEE. Le tableau y est
     deja masque, donc la regle serait inerte de toute facon ; le media la rend
     inerte PAR CONSTRUCTION plutot que par effet de bord d'un `display:none`.
   ⚠️ NE PAS retirer le media en croyant simplifier : la cible tactile
   descendrait de 40 a 26 px, sous le seuil recommande pour le doigt. */
@media (min-width: 769px) {
    .crud-table-card.crud-hide-mobile .icon-btn {
        width: 26px;
        height: 26px;
    }
}

.table-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 20px;
    font-size: 12.5px;
    color: var(--slate);
}

/* ══════════════════════════════════════════════════════════════════════════
   L6b — LE PIED COLLANT DES LISTES. Le pied « Affichage de X à Y sur N » et
   son pager restent visibles pendant qu'on parcourt les lignes, comme le
   thead l'est déjà par le haut.

   ⚠️⚠️ CE N'EST PAS LA MÉCANIQUE DE LA MAQUETTE, ET L'ÉCART EST ASSUMÉ.
   Elle obtient le même résultat par un CADRE DE DÉFILEMENT
   (`.table-scroll-inner { overflow-y:auto }`, le pied HORS du cadre). Trois
   mesures l'ont écarté :
   - le cadre coûterait 4 à 6 lignes de hauteur, DAVANTAGE que ce que L6a
     vient de rendre ; le pied collant en coûte UNE ;
   - `.crud-table-card` porte `overflow: clip`, qui rogne SANS créer de
     conteneur de défilement : la propriété reste valide, donc le piège du
     recalage du sticky (retour terrain n°1) n'existe pas ici ;
   - la mécanique de la maquette est MESURÉMENT FAUTIVE en mobile et serait à
     réécrire entièrement.
   ⇒ La maquette a autorité sur le VISUEL, jamais sur la mécanique — et ici
     sa mécanique ne fonctionne pas.

   BORNAGE — le couple exclusif, MESURÉ :
   - QUATRE fichiers posent un `.table-foot`, et DEUX SEULEMENT sont des
     listes. `EtatViewer.razor` et `ReleveProduit.razor` le posent dans une
     `.crud-table-card` SANS `.crud-hide-mobile` : ce ne sont pas des listes,
     ils n'ont aucun rendu mobile en cartes, et un pied collant y
     apparaîtrait sans avoir été décidé.
   - `@media (min-width: 769px)` : le rendu mobile des listes est
     `CrudCardsShell` — des cartes, sans pied ni tableau. La règle y serait
     inerte de toute façon ; le média la rend inerte PAR CONSTRUCTION.
     ⚠️ Le seuil de densité mobile de t78 a une marge EXACTEMENT NULLE : on
     ne laisse pas une règle de position l'atteindre par mégarde.

   ⚠️⚠️ LE FOND EST LA MOITIÉ LA MOINS ÉVIDENTE. Sans surface opaque, les
   lignes défileraient DERRIÈRE le pied et leur texte se superposerait au
   sien. Rien ne planterait : l'écran resterait fonctionnel et deviendrait
   ILLISIBLE. `var(--surface)` est le fond de `.crud-table-card`, mesuré.

   ⚠️ LE FILET EST `--line`, PAS `--line-soft`, ET C'EST UNE SYMÉTRIE MESURÉE :
   le pied collant est le symétrique du thead collant, et celui-ci pose
   `border-bottom: 1px solid var(--line)`. Poser un filet plus doux en bas
   créerait une asymétrie entre les DEUX BORDS QUI ENCADRENT LA MÊME ZONE.
   La maquette pose --line-soft aux deux bords, cohérente avec SA palette ;
   nous restons cohérents avec la nôtre. Le jour où une tranche basculera
   `tbody tr` et le thead vers --line-soft, LE PIED DEVRA SUIVRE DANS LE
   MÊME GESTE. */
@media (min-width: 769px) {
    .crud-table-card.crud-hide-mobile .table-foot {
        position: sticky;
        bottom: 0;
        background: var(--surface);
        border-top: 1px solid var(--line);
    }
}

.page-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    height: 28px;
    border-radius: var(--r-8);
    background: var(--ink);
    color: #ffffff;
    font-weight: 600;
}

/* Contrôles de pagination serveur (pied de tableau + pager mobile) */
.pager {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.pager-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    height: 28px;
    padding: 0 6px;
    border-radius: var(--r-8);
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--ink);
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
}

    .pager-btn:hover:not(:disabled) {
        background: var(--paper);
    }

    .pager-btn:disabled {
        opacity: 0.4;
        cursor: default;
    }

    .pager-btn.active {
        background: var(--ink);
        border-color: var(--ink);
        color: #ffffff;
        cursor: default;
    }

/* Badge de statut produit (dérivé du stock — règle métier, jamais stocké) */
.statut-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 11px 4px 8px;
    border-radius: var(--r-6);
    font-size: 11px;
    font-weight: 600;
}

    .statut-badge::before {
        content: '';
        width: 5px;
        height: 5px;
        border-radius: 50%;
        background: currentColor;
    }

    .statut-badge.dispo { background: rgba(16, 185, 129, 0.09); color: #059669; border: 1px solid rgba(16, 185, 129, 0.18); }
    .statut-badge.epuise { background: rgba(225, 29, 72, 0.09); color: var(--rose); border: 1px solid rgba(225, 29, 72, 0.2); }

/* Rangée de filtres serveur (selects + annuler) de la liste Produits */
.filter-row {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 0.85rem;
}

    .filter-row select {
        max-width: 220px;
    }

/* Onglets désactivés « à venir » en tête de fiche produit */
.fiche-tabs {
    display: flex;
    gap: 2px;
    overflow-x: auto;
    border-bottom: 1px solid var(--line);
    margin-bottom: 1rem;
}

.fiche-tab {
    padding: 8px 12px;
    font-size: 12px;
    font-weight: 600;
    color: #94a3b8;
    border: none;
    background: transparent;
    white-space: nowrap;
}

    .fiche-tab.active {
        color: var(--ink);
        border-bottom: 2.5px solid var(--teal);
    }

    .fiche-tab:disabled {
        opacity: 0.5;
        cursor: not-allowed;
    }

.fiche-section {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--teal);
    margin: 1.1rem 0 0.6rem;
    padding-bottom: 0.25rem;
    border-bottom: 1px dashed var(--line);
}

    .fiche-section:first-of-type {
        margin-top: 0;
    }

/* ============================================================================
   Fiche Produit — grille multi-colonnes (spec : capture WinDev « Fiche Article »).
   Blocs encadrés titre+icône ; zones « à venir » désactivées et marquées.
   ========================================================================= */
.fpro-grid {
    display: grid;
    grid-template-columns: 1.15fr 1fr 0.85fr;
    gap: 12px;
    font-family: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.fpro-bloc {
    border: 1px solid var(--line);
    border-radius: var(--r-10);
    padding: 12px 14px;
    background: var(--surface);
    min-width: 0;
}

.fpro-bloc-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--slate);
    margin-bottom: 10px;
}

    .fpro-bloc-title svg {
        flex: none;
        color: var(--teal);
    }

.fpro-bloc label {
    display: block;
    font-size: 11px;
    font-weight: 500;
    color: var(--slate);
    margin-bottom: 2px;
}

.fpro-field {
    margin-bottom: 8px;
}

.fpro-2col {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
}

.avenir-badge {
    display: inline-flex;
    align-items: center;
    padding: 1px 7px;
    border-radius: var(--r-10);
    background: var(--amber-soft);
    color: var(--amber-dark);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin-left: 5px;
    vertical-align: middle;
}

.tva-chip {
    margin-left: auto;
    padding: 3px 10px;
    border-radius: var(--r-8);
    background: var(--teal-soft);
    color: var(--teal-dark);
    font-family: var(--num-font); font-variant-numeric: tabular-nums;
    font-size: 12px;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
}

/* T8b — le bloc .prixmode/.prixmode-btn (sélecteur « Prix BL / Prix Facture » de l'ancienne
   modale de fiche article) a été SUPPRIMÉ : ce vocabulaire est remplacé par « Plan A » / « Plan B »
   et son markup a disparu avec la modale en T8a. Le segment des plans vit désormais dans
   css/article-fiche.css (.artfiche .art-seg), borné à la fiche. */

.marge-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border: 1px solid rgba(30, 158, 107, 0.25);
    border-radius: var(--r-10);
    background: var(--green-soft);
    color: var(--green-dark);
    font-family: var(--num-font); font-variant-numeric: tabular-nums;
    font-size: 13px;
    font-weight: 600;
}

.fpro-image-drop {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 150px;
    border: 2px dashed var(--line);
    border-radius: var(--r-10);
    color: #c3cad4;
    font-size: 12px;
    margin-bottom: 8px;
}

.fpro-barcode {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 70px;
    border: 1px dashed var(--line);
    border-radius: var(--r-10);
    opacity: 0.45;
    margin-bottom: 4px;
}

.fpro-kv {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px 14px;
}

.fpro-kv-label {
    font-size: 10.5px;
    color: #94a3b8;
}

.fpro-kv-value {
    font-family: var(--num-font); font-variant-numeric: tabular-nums;
    font-size: 15px;
    font-weight: 600;
    color: var(--ink);
}

    .fpro-kv-value.muted {
        color: #c3cad4;
    }

.fpro-note {
    margin-top: 8px;
    padding: 6px 8px;
    border-radius: var(--r-8);
    background: var(--paper);
    font-size: 10px;
    color: var(--slate);
    line-height: 1.35;
}

/* Ventilation du stock par entrepôt (fiche produit, lecture seule) */
.fpro-vent-row {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    font-size: 12.5px;
    padding: 2px 0;
    color: var(--ink);
}

    .fpro-vent-row.total {
        border-top: 1px solid var(--line);
        margin-top: 3px;
        padding-top: 5px;
        font-weight: 700;
    }

.fpro-badge-neutre {
    margin-left: auto;
    padding: 2px 9px;
    border-radius: var(--r-10);
    background: var(--paper);
    color: var(--slate);
    font-size: 10px;
    font-weight: 700;
    text-transform: none;
    letter-spacing: 0;
}

.fpro-span2 {
    grid-column: span 2;
}

@media (max-width: 1100px) {
    .fpro-grid {
        grid-template-columns: 1fr 1fr;
    }
}

@media (max-width: 768px) {
    .fpro-grid {
        grid-template-columns: 1fr;
    }

    .fpro-span2 {
        grid-column: auto;
    }
}

/* Pager sous les cartes mobiles (la liste paginée s'applique aussi < 768px) */
.crud-mobile-pager {
    display: none;
}

@media (max-width: 768px) {
    .crud-mobile-pager {
        display: flex;
        justify-content: center;
        margin: 0.5rem 0 1rem;
    }

    .crud-mobile-pager .pager-btn {
        min-width: 40px;
        height: 40px;
    }
}

.crud-cards,
.crud-fab {
    display: none;
}

@media (max-width: 768px) {
    /* La page qui adopte les cartes masque son tableau (et le rendu carte
       générique qui va avec) et le bouton "+ Nouveau" (remplacé par le FAB). */
    table.crud-list.crud-desktop-table {
        display: none;
    }

    .crud-hide-mobile {
        display: none !important;
    }

    .crud-cards {
        display: block;
        font-family: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    }

    .crud-cards-header {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        margin-bottom: 0.65rem;
    }

    .crud-cards-title {
        font-family: 'Manrope', sans-serif;
        font-size: 1.35rem;
        font-weight: 700;
        color: #0f172a;
        margin: 0;
    }

    .crud-cards-count {
        font-size: 0.82rem;
        color: #64748b;
    }

    .crud-card {
        position: relative;
        background: #ffffff;
        border-radius: var(--r-16);
        box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 4px 14px rgba(15, 23, 42, 0.05);
        padding: 1rem 1rem 0.85rem;
        margin-bottom: 0.9rem;
        overflow: hidden;
    }

        .crud-card::before {
            content: "";
            position: absolute;
            left: 0;
            top: 0;
            bottom: 0;
            width: 4px;
            background: var(--card-accent, #2563eb);
        }

        /* Variante neutre pour les écrans sans notion de type/catégorie :
           liseré gris discret au lieu de la couleur de type. */
        .crud-card.crud-card-plain::before {
            background: var(--line);
        }

    .crud-card-head {
        display: flex;
        align-items: center;
        gap: 0.7rem;
    }

    .crud-card-icon {
        flex: none;
        width: 36px;
        height: 36px;
        border-radius: var(--r-10);
        display: flex;
        align-items: center;
        justify-content: center;
        background: var(--card-accent-soft, #eff6ff);
        color: var(--card-accent, #2563eb);
    }

    .crud-card-headtext {
        flex: 1;
        min-width: 0;
    }

    .crud-card-type {
        display: block;
        font-size: 0.68rem;
        font-weight: 700;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--card-accent, #2563eb);
    }

    .crud-card-title {
        font-family: 'Manrope', sans-serif;
        font-size: 1rem;
        font-weight: 700;
        color: #0f172a;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .crud-card-pill {
        flex: none;
        font-family: var(--num-font); font-variant-numeric: tabular-nums;
        font-weight: 500;
        font-size: 0.8rem;
        color: #0f172a;
        background: #f1f5f9;
        border-radius: var(--r-8);
        padding: 0.3rem 0.6rem;
    }

    .crud-card-meta {
        display: flex;
        flex-wrap: wrap;
        gap: 0.75rem 1.5rem;
        margin-top: 0.8rem;
        padding-top: 0.8rem;
        border-top: 1px dashed #e2e8f0;
    }

    .crud-card-meta-item {
        min-width: 0;
    }

    .crud-card-meta-label {
        display: block;
        font-size: 0.65rem;
        font-weight: 700;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: #94a3b8;
        margin-bottom: 0.1rem;
    }

    .crud-card-meta-value {
        font-size: 0.9rem;
        color: #0f172a;
    }

    .crud-card-empty {
        font-style: italic;
        color: #cbd5e1;
    }

    .crud-card-meta-item.crud-num {
        margin-left: auto;
    }

    .crud-card-actions {
        display: flex;
        justify-content: flex-end;
        gap: 0.55rem;
        margin-top: 0.85rem;
    }

    .crud-card-btn {
        display: inline-flex;
        align-items: center;
        gap: 0.35rem;
        min-height: 44px;
        padding: 0.45rem 1rem;
        border: none;
        border-radius: var(--r-10);
        font-size: 0.87rem;
        font-weight: 600;
        cursor: pointer;
    }

    .crud-card-btn-edit {
        background: #eff6ff;
        color: #2563eb;
    }

    .crud-card-btn-delete {
        background: #fef2f2;
        color: #dc2626;
    }

    .crud-fab {
        display: flex;
        align-items: center;
        justify-content: center;
        position: fixed;
        right: 1.1rem;
        bottom: 1.1rem;
        width: 56px;
        height: 56px;
        border-radius: 50%;
        border: none;
        background: #0d9488;
        color: #ffffff;
        box-shadow: 0 6px 18px rgba(13, 148, 136, 0.4);
        cursor: pointer;
        z-index: 1000;
    }
}

/* ============================================================================
   Tableau de Bord (Accueil) — spec : digistorm-dashboard-reference.html.
   Contenu seul (la navbar/latérale de la maquette ne sont pas reprises, l'app
   a les siennes). Graphes et donuts en CSS/SVG purs, sans librairie tierce.
   ========================================================================= */
.dash {
    font-family: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.dash-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    flex-wrap: wrap;
    gap: 10px;
}

.dash-title {
    font-family: 'Manrope', sans-serif;
    font-size: 20px;
    font-weight: 700;
    color: #1e293b;
    margin: 0;
}

.dash-demo {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    background: var(--amber-soft);
    border: 1px solid rgba(201, 138, 31, 0.35);
    border-radius: 100px;
    padding: 4px 12px;
    font-size: 11px;
    font-weight: 700;
    color: var(--amber-dark);
}

.demo-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--amber);
}

.kpis {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 12px;
    margin-bottom: 16px;
}

.kpi {
    background: var(--surface);
    border: 1px solid rgba(0, 0, 0, 0.06);
    border-radius: var(--r-12);
    padding: 16px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
    position: relative;
    overflow: hidden;
}

    .kpi::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        height: 3px;
        background: var(--kpi-color, var(--blue));
    }

.kpi.blue { --kpi-color: var(--blue); --kpi-soft: rgba(59, 111, 224, 0.1); }
.kpi.amber { --kpi-color: var(--amber); --kpi-soft: rgba(201, 138, 31, 0.1); }
.kpi.green { --kpi-color: var(--green); --kpi-soft: rgba(30, 158, 107, 0.1); }
.kpi.violet { --kpi-color: var(--violet); --kpi-soft: rgba(139, 92, 246, 0.1); }

.kpi-top {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: 10px;
}

.kpi-icon {
    width: 36px;
    height: 36px;
    border-radius: var(--r-8);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--kpi-soft);
    color: var(--kpi-color);
}

.kpi-badge {
    font-size: 10px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: var(--r-20);
}

    .kpi-badge.up { background: rgba(30, 158, 107, 0.1); color: var(--green); }
    .kpi-badge.down { background: rgba(225, 29, 72, 0.1); color: var(--rose); }

.kpi-lbl {
    font-size: 11px;
    color: #64748b;
    font-weight: 500;
    margin-bottom: 4px;
}

.kpi-val {
    font-family: var(--num-font);
    font-size: 20px;
    font-weight: 800;
    line-height: 1.1;
    color: var(--kpi-color);
    font-variant-numeric: tabular-nums;
}

    .kpi-val small {
        font-size: 12px;
        font-weight: 500;
        color: var(--slate);
    }

.kpi-progress {
    height: 4px;
    background: #e2e8f0;
    border-radius: 99px;
    margin-top: 10px;
    overflow: hidden;
}

.kpi-progress-bar {
    height: 100%;
    border-radius: 99px;
    background: var(--kpi-color);
}

.kpi-sub {
    display: flex;
    justify-content: space-between;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid #e2e8f0;
}

.kpi-sub-lbl { font-size: 10px; color: #94a3b8; }
.kpi-sub-val { font-size: 11px; font-weight: 600; color: #475569; font-variant-numeric: tabular-nums; }

.dash-content-grid {
    display: grid;
    grid-template-columns: 1.4fr 0.6fr;
    gap: 16px;
    margin-bottom: 16px;
}

.dash-card {
    background: var(--surface);
    border: 1px solid #e8edf3;
    border-radius: var(--r-14);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
    overflow: hidden;
}

.dash-tabs {
    display: flex;
    padding: 0 16px;
    border-bottom: 1px solid #e8edf3;
}

.dash-tab-btn {
    padding: 14px 16px 13px;
    font-size: 12.5px;
    font-weight: 500;
    color: #94a3b8;
    cursor: pointer;
    position: relative;
    border: none;
    background: transparent;
    font-family: inherit;
    white-space: nowrap;
}

    .dash-tab-btn.active {
        color: #0f172a;
        font-weight: 600;
    }

        .dash-tab-btn.active::after {
            content: '';
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            height: 2.5px;
            background: linear-gradient(90deg, var(--teal), var(--blue));
            border-radius: 2px 2px 0 0;
        }

.dash-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

    .dash-table th {
        text-align: left;
        padding: 11px 18px;
        font-size: 10.5px;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.7px;
        color: #94a3b8;
        border-bottom: 1px solid #f0f4f8;
        background: #fafbfd;
        white-space: nowrap;
    }

    .dash-table td {
        padding: 0 18px;
        height: 54px;
        color: #334155;
        border-bottom: 1px solid #f0f4f8;
        vertical-align: middle;
    }

    .dash-table tr:last-child td { border-bottom: none; }
    .dash-table tbody tr:hover td { background: #f6faff; }

.c-num {
    font-family: 'Manrope', sans-serif;
    font-weight: 700;
    color: var(--green);
    font-size: 12px;
    background: rgba(30, 158, 107, 0.06);
}

.c-client {
    font-weight: 600;
    color: #1e293b;
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.c-date { color: #94a3b8; font-size: 12px; font-weight: 500; }

.c-amount {
    text-align: right;
    font-family: var(--num-font); font-variant-numeric: tabular-nums;
    font-weight: 600;
    font-size: 13.5px;
    white-space: nowrap;
}

    .c-amount small { font-size: 11px; color: var(--slate); }

.c-qty { text-align: center; font-weight: 600; color: #0f172a; }

.dash-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 11px 4px 8px;
    border-radius: var(--r-6);
    font-size: 11px;
    font-weight: 600;
}

    .dash-tag::before {
        content: '';
        width: 5px;
        height: 5px;
        border-radius: 50%;
        background: currentColor;
    }

    .dash-tag.paid { background: rgba(16, 185, 129, 0.09); color: #059669; border: 1px solid rgba(16, 185, 129, 0.18); }
    .dash-tag.pending { background: rgba(245, 158, 11, 0.09); color: #b45309; border: 1px solid rgba(245, 158, 11, 0.2); }
    .dash-tag.new { background: rgba(6, 182, 212, 0.09); color: var(--cyan); border: 1px solid rgba(6, 182, 212, 0.2); }
    .dash-tag.draft { background: rgba(148, 163, 184, 0.1); color: #64748b; border: 1px solid rgba(148, 163, 184, 0.2); }

/* Badge « pièces jointes » (📎 N) dans les listes de documents */
.fj-badge {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    background: rgba(20, 184, 166, 0.1);
    color: var(--teal-dark, #0f766e);
    border: 1px solid rgba(20, 184, 166, 0.22);
    vertical-align: middle;
}

.stats-col {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.stat-card {
    background: var(--surface);
    border: 1px solid rgba(0, 0, 0, 0.06);
    border-radius: var(--r-14);
    padding: 16px 18px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}

.stat-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: 6px;
}

.stat-lbl {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: #94a3b8;
}

.stat-trend {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 11px;
    font-weight: 700;
    padding: 3px 8px;
    border-radius: var(--r-12);
}

    .stat-trend.up { background: rgba(16, 185, 129, 0.1); color: #059669; }
    .stat-trend.down { background: rgba(244, 63, 94, 0.1); color: var(--rose); }

.stat-val {
    font-family: var(--num-font);
    font-size: 22px;
    font-weight: 800;
    color: #0f172a;
    line-height: 1.2;
    letter-spacing: -0.5px;
    font-variant-numeric: tabular-nums;
}

    .stat-val small {
        font-size: 12px;
        font-weight: 500;
        color: #64748b;
    }

.stat-sub { font-size: 11px; color: #94a3b8; margin-top: 4px; }

.dash-bottom-row {
    display: grid;
    grid-template-columns: 1.4fr 0.6fr;
    gap: 16px;
}

.dash-chart { padding: 20px; }

.dash-chart-title {
    font-size: 14px;
    font-weight: 700;
    color: #1e293b;
    margin: 0 0 16px;
}

.bar-chart {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    height: 140px;
    border-bottom: 1px solid #e2e8f0;
}

.bar-group {
    display: flex;
    align-items: flex-end;
    gap: 4px;
    flex: 1;
    height: 100%;
}

.bar {
    flex: 1;
    border-radius: 4px 4px 0 0;
}

    .bar.sales { background: linear-gradient(180deg, var(--green), #059669); }
    .bar.purchases { background: linear-gradient(180deg, var(--amber), #d97706); }

.bar-labels {
    display: flex;
    margin-top: 8px;
    gap: 8px;
}

.bar-label {
    flex: 1;
    text-align: center;
    font-size: 11px;
    color: #64748b;
    font-weight: 500;
}

.chart-legend {
    display: flex;
    gap: 18px;
    margin-top: 14px;
    justify-content: center;
}

.legend-item {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: #64748b;
}

.legend-dot { width: 10px; height: 10px; border-radius: var(--r-3); }
.legend-dot.sales { background: var(--green); }
.legend-dot.purchases { background: var(--amber); }

.donuts-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
}

.donut-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
}

.donut-label { font-size: 11px; font-weight: 700; text-align: center; }
.donut-track { fill: none; stroke: #e2e8f0; stroke-width: 8; }
.donut-fill { fill: none; stroke-width: 8; stroke-linecap: round; }
.donut-center { font-size: 12px; font-weight: 800; fill: #1e293b; }

@media (max-width: 1100px) {
    .kpis { grid-template-columns: repeat(2, 1fr); }
    .dash-content-grid, .dash-bottom-row { grid-template-columns: 1fr; }
    .stats-col { display: grid; grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 768px) {
    .kpis { grid-template-columns: 1fr 1fr; }
    .stats-col { grid-template-columns: 1fr; }
    .dash-tabs { overflow-x: auto; }
    .dash-table th, .dash-table td { padding-left: 12px; padding-right: 12px; }
    .c-client { max-width: 120px; }
    .dash-table-scroll { overflow-x: auto; }
}
/* ============================================================================
   Schéma responsive des pages DOCUMENT (bon de réception, futurs BL/factures) :
   en-tête épuré + section repliable « Plus de détails » + lignes en cartes +
   totaux empilés. Desktop (>= 769px) STRICTEMENT inchangé — tout est sous media.
   ============================================================================ */
.doc-detail-toggle { display: none; }
.doc-totaux-mobile { display: none; }
/* ⚠️⚠️ T-CHAMPREQ — LE TON FONCÉ, ET IL EST INSCRIT **AU SITE**, PAS EN EXCEPTION.
   --red rend 3,94 sur --surface : SOUS le seuil AA de 4,50 pour du texte. La note de champ
   manquant est du TEXTE de 12px — le plus petit de l'écran, donc le moins pardonnant.
   ⭐ On corrige donc la VALEUR ici plutôt que d'inscrire la combinaison dans SousLeSeuilAssume :
   une exception AUTORISE sans réparer, et celle-ci aurait porté sur le message qui REFUSE un
   geste — l'endroit précis où l'utilisateur a le plus besoin de lire. --red-dark rend 5,76.
   ⚠️ LE SENS EST CONSERVÉ : « red-dark » commence par « red », donc la garde G1 (« un sélecteur
   qui signale un PROBLÈME emploie le rouge ») reste satisfaite — un refus reste rouge.
   ⚠️ LA BORDURE PREND LE MÊME TON, bien que 3:1 lui suffirait (WCAG 1.4.11, composant non
   textuel) : deux tons de rouge sur le même champ se liraient comme deux niveaux d'alerte. */
/* ⚠️⚠️ LA SPÉCIFICITÉ EST LE POINT, ET ELLE RÉPARE UN DÉFAUT PRÉEXISTANT — MESURÉ.
   `.doc-field-erreur select` vaut (0,1,1) et PERDAIT contre `.doc-page .form-select` (0,2,0),
   posée 1 200 lignes plus haut. ⇒ depuis que ce marqueur existe, la BORDURE ROUGE n'a JAMAIS
   été rendue sur un `<select>` — donc jamais sur le marqueur d'ENTREPÔT, qui en est toujours
   un. Seule la note rouge s'affichait ; on croyait le champ cerné, il ne l'était pas.
   ⭐ Rien ne le signalait : une règle perdue par la cascade ne se plaint pas — elle se mesure
   au style CALCULÉ, jamais à sa présence dans la source (même famille que le `!important` qui
   écrasait un inline). C'est le navigateur qui l'a nommée, pas la relecture.
   ⇒ `select.form-select` vaut (0,2,1) et l'emporte. L'input du picker était déjà correct :
   `.doc-field-erreur input.form-control` (0,2,1) battait déjà `.doc-page .form-control`.
   ⚠️ `cp-selected` couvre LES DEUX pickers : ClientPicker et FournisseurPicker sont jumeaux
   au nom de classe près — mesuré, ils emploient le MÊME `cp-`. Un `.fp-selected` serait une
   règle morte, et une règle morte se lit comme une couverture qui n'existe pas. */
.doc-field-erreur select,
.doc-field-erreur select.form-select,
.doc-field-erreur input.form-control,
.doc-field-erreur .cp-selected { border-color: var(--red-dark); }
.doc-field-erreur-note { font-size: 12px; color: var(--red-dark); margin-top: 4px; }

/* T-PROFIL — note informative sous un sélecteur affecté par le profil de l'utilisateur.
   ⚠️ Volontairement dans le MÊME registre que .doc-field-erreur-note (12px, sous le champ)
   mais en --slate et JAMAIS en rouge : c'est une information de contexte, pas un avertissement.
   Un ton d'alerte ferait lire un paramétrage normal comme un incident.
   ⚠️ AUCUNE règle mobile : la note doit rester lisible sous le champ à toutes les tailles
   (les champs d'en-tête s'empilent déjà sous 768px). Ni troncature, ni tooltip — le tap long
   n'existe pas pour tout le monde. */
.doc-note-profil {
    font-size: 12px;
    color: var(--slate);
    margin-top: 4px;
    line-height: 1.35;
}

@media (max-width: 768px) {
    .doc-detail-toggle {
        display: flex;
        align-items: center;
        gap: 8px;
        width: 100%;
        padding: 10px 14px;
        margin-bottom: 12px;
        background: var(--paper);
        border: 1px solid var(--line);
        border-radius: var(--r-10);
        font-size: 13px;
        font-weight: 600;
        color: var(--ink);
    }

    /* Champs repliés (entrepôt, exercice, N° BL…) — un tap sur le toggle les montre. */
    .doc-field-detail { display: none !important; }
    .doc-field-detail.open { display: block !important; }

    /* Totaux empilés : une ligne par montant, TTC mis en avant. */
    .doc-totaux-mobile {
        display: block;
        background: #fff;
        border: 1px solid var(--line);
        border-radius: var(--r-12);
        padding: 12px 16px;
        margin-bottom: 14px;
    }

    .doc-total-row {
        display: flex;
        justify-content: space-between;
        gap: 12px;
        padding: 7px 0;
        font-size: 13.5px;
        border-bottom: 1px solid #f1f5f9;
    }

    .doc-total-row:last-child { border-bottom: none; }

    .doc-total-ttc {
        font-weight: 700;
        font-size: 15.5px;
        color: var(--teal);
    }

    /* Rangées de saisie (ligne, charge) : champs empilés pleine largeur au doigt.
       !important : les largeurs desktop sont posées en style inline. */
    .doc-saisie { flex-direction: column !important; align-items: stretch !important; }
    .doc-saisie > div { width: 100% !important; flex: 1 1 auto !important; min-width: 0 !important; margin-left: 0 !important; }
    .doc-saisie > button { width: 100%; }
    .doc-saisie-hide-mobile { display: none !important; }
}

/* En-tête des pages DOCUMENT en mobile : blocs empilés pleine largeur
   (Fournisseur → N° pièce → Date — l'ordre du markup suffit, les champs
   « détail » étant repliés). !important : la grille 3 colonnes desktop est
   posée en style inline sur la page. Réutilisable (BL client, factures…). */
@media (max-width: 768px) {
    .doc-entete { grid-template-columns: 1fr !important; }
    .doc-entete .fpro-2col { grid-template-columns: 1fr; }
}

/* Badge texte Caisse/Banque de l'écran Modes de règlement (réutilisable). */
.mode-type-badge {
    display: inline-flex;
    align-items: center;
    padding: 2px 10px;
    border-radius: var(--r-8);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.01em;
}

.mode-type-badge.caisse {
    background: var(--green-soft);
    color: var(--green-dark);
}

.mode-type-badge.banque {
    background: var(--blue-soft);
    color: var(--blue-dark);
}

/* Badge de MODE de règlement — pastels doux (réutilisable : règlement frs & client). */
.mode-pill {
    display: inline-flex;
    align-items: center;
    padding: 2px 10px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.01em;
    line-height: 1.5;
}

.mode-pill.mode-espece   { background: #e6f6ec; color: #17794b; }  /* vert pastel */
.mode-pill.mode-virement { background: #e7f0fd; color: #1e5fbf; }  /* bleu pastel */
.mode-pill.mode-cheque   { background: #efeafc; color: #6b46c1; }  /* indigo/violet pastel */
.mode-pill.mode-traite   { background: #fdf1dd; color: #b7791f; }  /* ambre pastel */
.mode-pill.mode-autre    { background: #eef1f5; color: #55617a; }  /* neutre */

/* Badge d'ÉTAT d'un règlement. */
.etat-badge {
    display: inline-flex;
    align-items: center;
    padding: 2px 9px;
    border-radius: var(--r-8);
    font-size: 11.5px;
    font-weight: 600;
}

.etat-badge.etat-valide     { background: var(--green-soft); color: var(--green-dark); }
.etat-badge.etat-desimpute  { background: var(--amber-soft); color: var(--amber-dark); }
.etat-badge.etat-brouillon  { background: #eef1f5; color: #55617a; }

/* Dark mode : pastels de mode réajustés pour rester lisibles. */
@media (prefers-color-scheme: dark) {
    .mode-pill.mode-espece   { background: rgba(23,121,75,.22);  color: #7ee2ab; }
    .mode-pill.mode-virement { background: rgba(30,95,191,.24);  color: #9cc2f7; }
    .mode-pill.mode-cheque   { background: rgba(107,70,193,.26); color: #c4b1f2; }
    .mode-pill.mode-traite   { background: rgba(183,121,31,.24); color: #ecc590; }
    .mode-pill.mode-autre    { background: rgba(85,97,122,.24);  color: #b8c1d4; }
}

/* Champs montant/date de la barre de filtres (liste des règlements ; réutilisable). */
.filter-row .filter-montant { max-width: 130px; }
.filter-row .filter-date { max-width: 160px; }

@media (max-width: 768px) {
    .filter-row .filter-montant,
    .filter-row .filter-date { max-width: none; flex: 1 1 45%; }
}

/* Coordonnées fournisseur (lecture seule) sous le sélecteur — bon de réception & futurs docs. */
.fpro-coords {
    margin: -2px 0 8px;
    padding: 8px 10px;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-8);
    font-size: 12px;
    line-height: 1.55;
}

.fpro-coords-k {
    color: var(--slate);
    font-weight: 600;
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.03em;
    margin-right: 4px;
}

.fpro-coords-v {
    color: var(--ink);
}

/* Ligne du bon en cours d'édition en place (desktop + carte mobile). */
.doc-ligne-edit { background: var(--teal-soft) !important; }
.doc-ligne-edit input, .doc-ligne-edit select { min-width: 64px; }

.doc-card-edit {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin: 8px 0;
}
.doc-card-edit > div { min-width: 0; }
.doc-card-edit label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    color: var(--slate);
    margin-bottom: 2px;
}
.doc-card-edit-calc {
    grid-column: 1 / -1;
    font-size: 12px;
    color: var(--slate);
}

/* ==========================================================================
   IMPRESSION DE DOCUMENTS (préfixe pf-) — patron réutilisable (bon, BL, factures).
   Layout minimal ; entête/pied PNG répétés sur chaque page (position:fixed en print) ;
   formats A4/A5 via @page nommés ; filigrane BROUILLON. Les chiffres viennent de
   CalculBon (aucun recalcul ici). Sur écran : aperçu « feuille » centrée.
   ========================================================================== */
/* Le format (@page size/margin) est émis INLINE par la page d'impression selon a4/a5 —
   plus robuste que les @page nommés pour l'impression PDF navigateur. */
.pf-root {
    font-family: 'Manrope', Arial, sans-serif;
    color: #14181f;
    font-size: 12px;
    line-height: 1.4;
}
.pf-fmt-a5 { font-size: 10.5px; }

/* Aperçu écran : feuille centrée. Le padding (marges de contenu) est posé INLINE
   par la page selon les paramètres mm. */
.pf-sheet {
    background: #fff;
    max-width: 210mm;
    margin: 16px auto;
    box-shadow: 0 2px 18px rgba(0,0,0,0.12);
    position: relative;
    z-index: 1;
    min-height: 250mm;
}
.pf-fmt-a5 .pf-sheet { max-width: 148mm; min-height: 200mm; }

/* Papier à en-tête : fond de page UNIQUE, JAMAIS un background CSS (les navigateurs
   suppriment les fonds à l'impression). Une <img> position:fixed se répète sur CHAQUE
   page imprimée et s'imprime toujours. Même ratio A4/A5 → étirement sans déformation. */
.papier-fond {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: -1;
}
/* Avec papier, la feuille est transparente pour laisser voir le fond (écran + print). */
.pf-has-papier .pf-sheet { background: transparent; box-shadow: none; }

.pf-header-fallback { margin-bottom: 10px; }
.pf-header-fallback .pf-hf-nom { font-family: 'Manrope', sans-serif; font-weight: 800; font-size: 17px; color: #0c1730; }
.pf-header-fallback .pf-hf-sub { font-size: 11px; color: #55606f; }

.pf-title { text-align: center; margin: 6px 0 12px; }
.pf-title h1 { font-family: 'Manrope', sans-serif; font-size: 18px; font-weight: 700; margin: 0; color: #0c1730; }
.pf-title .pf-title-sub { font-size: 11px; color: #55606f; margin-top: 2px; }
.pf-mention-interne { font-size: 10.5px; font-weight: 700; color: #8a94a4; text-transform: uppercase; letter-spacing: .04em; margin-top: 3px; }
.pf-qte-total td { border-top: 2px solid #0c1730; padding-top: 5px; font-size: 12px; }

.pf-parties { display: flex; gap: 12px; margin-bottom: 10px; }
.pf-partie { flex: 1; border: 1px solid #e3e7ee; border-radius: var(--r-6); padding: 8px 10px; }
.pf-partie-titre { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: #8a94a4; margin-bottom: 4px; font-weight: 700; }
.pf-partie-nom { font-weight: 700; color: #0c1730; }
.pf-partie div { font-size: 11px; }

.pf-bandeau { display: flex; flex-wrap: wrap; gap: 6px 18px; padding: 7px 10px; background: #f5f7fa; border-radius: var(--r-6); margin-bottom: 10px; font-size: 11px; }
.pf-bandeau b { color: #0c1730; }

.pf-lines { width: 100%; border-collapse: collapse; margin-bottom: 10px; }
.pf-lines th { background: #0c1730; color: #fff; font-size: 10px; text-transform: uppercase; letter-spacing: .02em; padding: 5px 6px; text-align: left; }
.pf-lines td { padding: 4px 6px; border-bottom: 1px solid #e8ebf1; font-size: 11px; vertical-align: top; }
.pf-lines .pf-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pf-lines tbody tr { page-break-inside: avoid; }

.pf-bas { display: flex; gap: 12px; align-items: flex-start; page-break-inside: avoid; }
.pf-tva { flex: 1; }
.pf-tva table { border-collapse: collapse; width: 100%; }
.pf-tva th, .pf-tva td { border: 1px solid #e3e7ee; padding: 3px 6px; font-size: 10.5px; text-align: right; }
.pf-tva th { background: #f5f7fa; text-transform: uppercase; font-size: 9.5px; color: #55606f; }
.pf-tva th:first-child, .pf-tva td:first-child { text-align: left; }

.pf-totaux { width: 62mm; border-collapse: collapse; }
.pf-totaux td { padding: 3px 8px; font-size: 11.5px; }
.pf-totaux td:last-child { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pf-totaux tr.pf-net td { font-weight: 800; font-size: 13px; color: #0c1730; border: 2px solid #0c1730; background: #f5f7fa; }

.pf-arrete { margin: 10px 0; font-size: 11.5px; page-break-inside: avoid; }
.pf-arrete b { text-transform: uppercase; }

.pf-observations { border: 1px solid #e3e7ee; border-radius: var(--r-6); min-height: 16mm; padding: 6px 8px; margin-top: 8px; }
.pf-observations .pf-obs-label { font-size: 10px; text-transform: uppercase; color: #8a94a4; }

.pf-cachet-wrap { text-align: right; margin-top: 6px; page-break-inside: avoid; }
.pf-cachet { width: 40mm; max-height: 40mm; object-fit: contain; transform: rotate(-4deg); opacity: .95; }

.pf-watermark {
    position: absolute;
    top: 42%;
    left: 0; right: 0;
    text-align: center;
    font-family: 'Manrope', sans-serif;
    font-size: 90px;
    font-weight: 800;
    color: rgba(220, 38, 38, 0.12);
    transform: rotate(-30deg);
    letter-spacing: .1em;
    pointer-events: none;
    z-index: 0;
}

/* ---- Rendu IMPRESSION ---- */
@media print {
    /* @page { margin: 0 } est émis inline ; les marges de contenu sont le padding du
       .pf-sheet (variables mm). La feuille remplit la page ; le fond papier (fixed) passe
       dessous, le contenu commence au titre (flux normal → jamais coupé/chevauché). */
    .pf-sheet { max-width: none; margin: 0; box-shadow: none; min-height: 0; background: transparent; }
    .pf-watermark { position: fixed; } /* filigrane répété par page, au-dessus du fond */
    .pf-lines thead { display: table-header-group; } /* thead répété à chaque saut de page */
    .pf-lines tfoot { display: table-footer-group; }
    /* couleurs pleines à l'impression (fonds/bordures foncés + IMAGE de fond papier) */
    .papier-fond, .pf-lines th, .pf-totaux tr.pf-net td, .pf-bandeau, .pf-tva th { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}

/* ==========================================================================
   GABARIT DOCUMENTS COMMERCIAUX (doc-*) — RÉFÉRENCE UNIQUE des écrans « document »
   (bon de réception aujourd'hui ; BL client, factures demain).
   Zones & accents : partenaire = --blue, document = --teal, totaux = --amber ;
   rangée de saisie = liseré --teal ; états brouillon = --amber / validé = --green.
   RÉEMPLOI : reprendre le markup, changer les libellés + l'accent de la carte
   partenaire (client → --green via doc-card--partenaire-client).
   RÈGLE : aucune couleur en dur — uniquement les variables :root.
   Tout est borné à .doc-page (n'affecte pas les autres écrans partageant fpro-*).
   ========================================================================== */
.doc-page { max-width: 1280px; margin: 0 auto; }

/* --- En-tête de page (patron trésorerie) --- */
.doc-header { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; margin-bottom: 14px; }
.doc-header-main { flex: 1 1 auto; min-width: 240px; }
.doc-eyebrow { display: block; font-size: 12.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.6px; color: var(--teal); }
.doc-title-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 2px 0 4px; }
.doc-title { font-family: 'Manrope', 'Segoe UI', sans-serif; font-size: 28px; font-weight: 700; color: var(--ink); margin: 0; }
.doc-subtitle { font-size: 13.5px; color: var(--slate); }
.doc-header-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding-top: 2px; }

.doc-badge-etat { display: inline-flex; align-items: center; gap: 6px; padding: 4px 11px; border-radius: var(--r-20); font-size: 12px; font-weight: 700; line-height: 1; white-space: nowrap; }
.doc-badge-etat .doc-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.doc-badge-etat svg { flex: none; }
.doc-badge-etat--draft { background: var(--amber-soft); color: var(--amber-dark); }
.doc-badge-etat--valid { background: var(--green-soft); color: var(--green-dark); }

/* --- Cartes d'en-tête (identité pastel par zone) --- */
.doc-cards-row { grid-template-columns: 1fr 1fr 1fr; margin-bottom: 14px; }
.doc-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-14); padding: 16px 18px; }
.doc-card__head { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; }
.doc-card__icon { width: 32px; height: 32px; border-radius: var(--r-9); display: inline-flex; align-items: center; justify-content: center; flex: none; background: var(--doc-accent-soft, var(--teal-soft)); color: var(--doc-accent, var(--teal)); }
.doc-card__icon svg { width: 16px; height: 16px; }
.doc-card__title { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px; color: var(--slate); }
.doc-card--partenaire { --doc-accent: var(--blue); --doc-accent-soft: var(--blue-soft); }
.doc-card--partenaire-client { --doc-accent: var(--green); --doc-accent-soft: var(--green-soft); } /* réemploi BL client */
.doc-card--document { --doc-accent: var(--teal); --doc-accent-soft: var(--teal-soft); }
.doc-card--totaux { --doc-accent: var(--amber); --doc-accent-soft: var(--amber-soft); }

/* --- Champs (labels + inputs) dans le gabarit --- */
.doc-page .fpro-field > label, .doc-page .fpro-2col label { font-size: 12px; font-weight: 600; color: var(--slate); margin-bottom: 3px; }
.doc-page .form-control, .doc-page .form-select { border-radius: var(--r-10); border-color: var(--line); color: var(--ink); }
.doc-page .form-control:focus, .doc-page .form-select:focus { border-color: var(--teal); box-shadow: 0 0 0 3px var(--teal-soft); }
/* Lecture seule (bon validé) : fond --paper + texte --ink lisible (jamais gris illisible) */
.doc-page .form-control:disabled, .doc-page .form-select:disabled, .doc-page .form-control[readonly] {
    background: var(--paper); color: var(--ink); -webkit-text-fill-color: var(--ink); opacity: 1; cursor: default;
}
.doc-mono-input { font-family: var(--num-font); font-variant-numeric: tabular-nums; }

/* Coordonnées partenaire : encart fond --paper */
.doc-page .fpro-coords { background: var(--paper); border-radius: var(--r-10); padding: 10px 12px; }
.doc-page .fpro-coords-k { font-size: 11px; text-transform: uppercase; letter-spacing: .3px; color: var(--slate); }
.doc-page .fpro-coords-v { color: var(--ink); }

/* --- Carte Totaux : montants mono, hiérarchie --- */
.doc-page .fpro-kv-label { font-size: 12px; color: var(--slate); }
.doc-page .fpro-kv-value { font-family: var(--num-font); font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums; color: var(--ink); }
.doc-kv-ttc { font-size: 19px; font-weight: 600; color: var(--teal); }
.doc-kv-red { color: var(--red); }
.doc-kv-slate { color: var(--slate); }
.doc-red { color: var(--red); }
.doc-green { color: var(--green); }

/* ⚠️⚠️ SENS ⑧ — LE RESTE À PAYER D'UN DOCUMENT. Classe DÉDIÉE, et voici pourquoi elle existe.
   `.doc-red` porte SIX sens, mesurés sur ses 24 usages :
     · le RESTE à payer / à encaisser        (ResteAPayer > 0m, soldePayable > 0m)  → ⑧, ici
     · la REMISE HT des totaux               (8 documents, sans condition)          → reste rouge
     · la MARGE négative                     (MargeDoc >= 0m, margeM >= 0m)         → reste rouge
     · un DELTA / une VALEUR négatifs        (correction de stock)                  → reste rouge
     · le SENS de trésorerie                 (editing.sens == SensEncaissement)     → reste rouge
     · l'ÉCART de caisse                     (EcartCourant > 0m)                    → reste rouge
   ⇒ LA FAIRE BASCULER AURAIT MIS CINQ AUTRES SENS EN AMBRE, dont la marge négative et l'écart de
     caisse — c'est-à-dire deux VRAIS problèmes. D'où une classe à part, posée sur les 7 sites
     nommés au bloc `.doclist-kpi--reste`.
   ⚠️ CONSTAT, non traité ici : `.doc-kv-red` (ligne au-dessus) est DÉCLARÉE et posée NULLE PART
     dans le markup — classe fantôme. Hors périmètre de L1b : c'est un retrait, pas un sens. */
.doc-reste { color: var(--amber-dark); }
/* Marge (bon de livraison) — indicateur écran uniquement, jamais imprimé. */
.doc-kv-marge-pos { color: var(--green); font-weight: 600; }
.doc-kv-marge-neg { color: var(--red); font-weight: 600; }
/* Alerte prix minimum (non bloquante) sur une ligne de vente. */
.doc-alerte-prixmin { padding: 8px 20px; color: var(--amber-dark); background: var(--amber-soft); font-size: 12.5px; font-weight: 600; }
.doc-cell-prixmin { color: var(--amber); font-weight: 600; }
.doc-totaux-sep { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); }

/* --- Bandes utilitaires --- */
.doc-bande { padding: 10px 20px; border-bottom: 1px solid var(--line); display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-end; }
.doc-bande-titre { display: flex; align-items: center; gap: 8px; }
.doc-bande-titre strong { font-size: 13px; color: var(--ink); }
.doc-bande-hint { font-size: 11px; color: var(--slate); }
.doc-bande-pastille { width: 26px; height: 26px; border-radius: var(--r-8); display: inline-flex; align-items: center; justify-content: center; flex: none; background: var(--doc-accent-soft, var(--amber-soft)); color: var(--doc-accent, var(--amber)); }
.doc-bande-pastille svg { width: 14px; height: 14px; }
.doc-mini-lbl { font-size: 11px; font-weight: 600; color: var(--slate); }

/* Rangée de saisie produit : « la zone où on agit » — fond --paper + liseré --teal */
.doc-page .doc-saisie--produit { background: var(--paper); border-left: 3px solid var(--teal); }

/* --- Grille des lignes (standards tableau) --- */
.doc-page .crud-table-card { border-radius: var(--r-16); }
.doc-page .crud-list thead th { font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px; color: var(--slate); background: var(--thead-bg); }
/* Lignes saisies : fond très doux teinté teal (--row-soft) ; hover un cran plus
   soutenu (--teal-soft). Contraste bande blanche (saisie) / lignes pastel / totaux. */
.doc-page .crud-list tbody td { height: 48px; border-bottom: 1px solid var(--line); color: var(--ink); background: var(--row-soft); }
.doc-page .crud-list tbody tr:hover td { background: var(--teal-soft); }
.doc-page .crud-list .cell-mono { font-family: var(--num-font); font-variant-numeric: tabular-nums; }
/* Ligne en édition : fond --teal-soft + liseré gauche --teal (repère visuel) */
.doc-page .crud-list tbody tr.doc-ligne-edit td { background: var(--teal-soft); }
.doc-page .crud-list tbody tr.doc-ligne-edit td:first-child { box-shadow: inset 3px 0 0 var(--teal); }
.doc-page .table-foot { font-size: 12.5px; color: var(--slate); }
.doc-page .table-foot .cell-mono, .doc-page .table-foot strong { font-family: var(--num-font); font-variant-numeric: tabular-nums; }

/* Espacement des cartes du gabarit */
.doc-page .crud-table-card { margin-bottom: 14px; }

@media (max-width: 768px) {
    .doc-title { font-size: 22px; }
    .doc-header-actions { width: 100%; }
    .doc-header-actions .btn-filtres, .doc-header-actions .btn-nouveau,
    .doc-header-actions .doc-btn-danger, .doc-header-actions .doc-btn-imprimer { flex: 1 1 auto; justify-content: center; }
}

/* Compléments gabarit doc-* */
.doc-lbl-note { font-weight: 400; color: var(--slate); }
.doc-bande--center { align-items: center; }
.doc-bande-pastille--slate { background: var(--paper); color: var(--slate); }
.doc-bande-corps { padding: 10px 20px; }
.doc-vide-note { margin: 0; font-size: .9rem; font-style: italic; color: var(--slate); }

/* ==========================================================================
   GABARIT LISTES DE DOCUMENTS (doclist-*) — RÉFÉRENCE des listes « document »
   (bons de réception aujourd'hui ; BL client, factures demain).
   Réf visuelle : docs/maquette_liste_bons_reception.html. Couleurs = tokens :root
   (vert maquette → --teal/--teal-dark/--teal-soft ; bleu → --blue ; ambre → --amber ;
   rouge → --red ; encres/fonds → --ink/--slate/--paper/--surface/--line).
   RÉEMPLOI BL client/factures : même markup, changer libellés + accents.
   ========================================================================== */
/* --- Rangée de KPIs (agrégés serveur sur l'ensemble filtré) — COMPACTÉE en T-LISTES-a ---
   ⭐⭐ LE POSTE LE PLUS LOURD DU HAUT DE PAGE, ET C'ÉTAIT UNE GRILLE FIGÉE À 4 COLONNES.
   Mesuré au navigateur sur /client/bons-livraison (fenêtre 1000 px) : le bloc faisait
   196 px. Non pas parce qu'une carte est haute (91 px), mais parce que la liste des BL
   porte CINQ KPI depuis T2c (« Reste à facturer ») dans `repeat(4, 1fr)` : le cinquième
   passait à la ligne et DOUBLAIT la hauteur du bloc — 91 + 14 de gap + 91 = 196.
   ⇒ `auto-fit` les remet sur UNE rangée tant que la largeur le permet, et les repasse à
     la ligne d'elle-même quand elle ne le permet plus. Un `repeat(5, 1fr)` figé aurait
     déplacé le problème au sixième KPI.
   ⚠️ BORNAGE MESURÉ : `.doclist-kpis` et `.doclist-kpi*` sont portés par 13 listes et
     par AUCUN écran hors liste. Ce dépôt a payé DEUX FOIS une règle posée sur un
     conteneur partagé (T-UI : `.btn-filtres`, puis `.crud-toolbar-actions`) — d'où la
     mesure AVANT d'écrire. `.btn-filtres` (37 écrans hors liste) et `.btn-nouveau` (36)
     restent INTOUCHÉS, comme `.crud-list` (27) et `.crud-table-card` (23).
   ⚠️ LE LIBELLÉ RESTE À 12 px : c'est le plancher de lisibilité arrêté par Walid, et la
     maquette descendait à 8-9 px. Seule la VALEUR est réduite (22 → 18), ce qui préserve
     la hiérarchie que t75 vérifie au style calculé (`valeur > libellé`). */
/* ─────────────────────────────────────────────────────────────────────────────────────────
   ⚠️⚠️ L4 — LE COMPACTAGE PASSE PAR LE REMBOURRAGE ET LA VALEUR, JAMAIS PAR LE LIBELLÉ.
   ─────────────────────────────────────────────────────────────────────────────────────────
   ⭐ LE PLANCHER DE 12 px A ÉTÉ POSÉ CONTRE DES MAQUETTES, et le libellé de t122/C3 le dit mot
     pour mot : « les maquettes proposaient 8-9 ». Celle de L4 redemande 10 px. Le faire tomber
     parce qu'une maquette le redemande, c'est le faire échouer EXACTEMENT là où il servait.
   ⚠️ Et l'arithmétique le rend inutile : descendre le libellé rendrait 2 à 3 px, quand le
     rembourrage (10 → 8, soit 4) et la valeur (18 → 16, soit 3 sur l'interligne) en rendent
     davantage. G-K5 fige le plancher ; le compactage passe par là.
   ⭐ COMPOSITION DE LA HAUTEUR, à l'unité : padding 8+8 = 16 · bordure 1+1 = 2 · libellé 18
     (12 px × 1,5) + marge 2 = 20 · valeur 24 (16 px × 1,5) ⇒ 62 px, contre 70 avant. */
.doclist-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 8px; margin-bottom: 10px; }
.doclist-kpi { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-12); padding: 8px 14px; position: relative; overflow: hidden; box-shadow: var(--sh-sm); }
.doclist-kpi::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--kpi-bar, var(--teal)); }
.doclist-kpi-lbl { font-size: 12px; color: var(--slate); font-weight: 500; margin-bottom: 2px; }
.doclist-kpi-val { font-family: var(--num-font); font-size: 16px; font-weight: 700; letter-spacing: -.01em; color: var(--ink); font-variant-numeric: tabular-nums; }
.doclist-kpi-val .dh { font-size: 12px; font-weight: 600; color: var(--slate); margin-left: 2px; }
.doclist-kpi--count { --kpi-bar: var(--slate); }
.doclist-kpi--total { --kpi-bar: var(--blue); }
.doclist-kpi--regle { --kpi-bar: var(--teal); }

/* ─────────────────────────────────────────────────────────────────────────────────────────
   ⚠️⚠️ L4 — LES DEUX ACCENTS PASSENT AU FOND TEINTÉ, ET LE TEXTE PREND LE TON FONCÉ.
   ─────────────────────────────────────────────────────────────────────────────────────────
   ⭐ LA MAQUETTE FAIT AUTORITÉ SUR LE VISUEL, et elle teinte le fond des deux indicateurs qui
     portent une information d'argent en attente. Le liseré est CONSERVÉ : il porte l'identité
     depuis T-LISTES-a, et le fond le RENFORCE au lieu de le remplacer — additif, donc aucune
     identité perdue sur les 38 indicateurs.

   ⚠️⚠️ LA MAQUETTE ÉCRIT L'ACCENT CLAIR LÀ OÙ IL FAUT LE FONCÉ, ET LA NUANCE COMPTE POUR RELIRE :
     elle pose `.kpi-value` en `--amber` sur `--amber-softer`. Avec NOS valeurs cela rend 2,67 —
     sous le seuil AA-LARGE lui-même. ⭐ Mais avec l'ambre de LA MAQUETTE (#A85D14) le même couple
     rend 4,50 : elle est cohérente avec SA propre palette. C'est NOTRE `--amber` (#C98A1F, plus
     clair) qui diverge de la sienne — elle ne se trompe pas.
   ⇒ On emploie `--amber-dark`, créé en L1b PRÉCISÉMENT pour porter du TEXTE là où l'accent clair
     ne porte que des APLATS. Mesuré : 4,86 sur `--amber-softer`, donc AA et non AA-large.

   ⚠️⚠️ ET `--brand*` N'EST PAS EMPLOYÉ, BIEN QUE LA MAQUETTE LE POSE SUR « Encaissé ».
     Il est dans `TokensEnAttente` avec le motif « L2+ — accent PRIMAIRE de l'application
     (bouton Nouveau, eyebrow, focus, TTC) ». L'employer ici l'en ferait SORTIR et effacerait
     l'annonce d'un travail entier — c'est le défaut trouvé TROIS fois (L1c, L2a, L2c).
   ⇒ On emploie `--teal-*`, qui EST déjà l'accent de cet indicateur (`--kpi-bar: var(--teal)`
     depuis T-LISTES-a). Mesuré : `--teal-dark` sur `--teal-soft` rend 4,87.

   ⚠️ LE LIBELLÉ RESTE EN `--slate` SUR LES TROIS FONDS (5,64 · 4,94 · 5,13) : la maquette le
     pose en `--muted`, RETIRÉ par L2a pour cause de 3,69. G-K1 chiffre les neuf combinaisons. */
.doclist-kpi--regle { background: var(--teal-soft); /* border-color: var(--teal-soft); */ /* ← SUPPRIMER cette ligne */ }
.doclist-kpi--regle .doclist-kpi-val { color: var(--teal-dark); }
.doclist-kpi--reste { background: var(--amber-softer); /* border-color: var(--amber-soft); */ /* ← SUPPRIMER cette ligne */ }
.doclist-kpi--reste .doclist-kpi-val { color: var(--amber-dark); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   ⚠️⚠️  LE SENS ⑧ — LE RESTE À PAYER — EST EN AMBRE (L1b). LE DÉFAUT EST DE RESTER ROUGE.
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⭐ LE MOTIF : un RESTE À PAYER est un FAIT NORMAL — il n'est pas un problème. Le rouge, dans
     ce dépôt, signale un PROBLÈME : un impayé, une erreur de saisie, une marge négative, une
     suppression, une rupture, un refus, un dépassement. Mettre un reste normal dans la même
     teinte qu'un impayé rend un chiffre JUSTE illisible — c'est la doctrine écrite en toutes
     lettres au bloc « Solde des règlements » (RG-LISTES), et L1b l'APPLIQUE au lieu de la violer.
   ⭐⭐ ET CE N'EST PAS UNE NOUVEAUTÉ ESTHÉTIQUE : `.doclist-badge--partiel` est DÉJÀ en ambre,
     sur la MÊME LIGNE de la MÊME liste où le montant du reste était en rouge (voir plus bas).
     La doctrine était donc appliquée à MOITIÉ ; L1b referme une incohérence qui existait déjà
     dans ce fichier.

   ⚠️⚠️⚠️ À LIRE AVANT DE « SIMPLIFIER » — LE SENS DE SÛRETÉ COMMANDE L'ÉCRITURE.
     L'ambre est posé EXPLICITEMENT, sélecteur par sélecteur, sur une liste NOMMÉE. Il ne l'est
     JAMAIS par un remplacement global suivi d'un repêchage des alertes.
     ⇒ LE SENS INVERSE SERAIT CATASTROPHIQUE : basculer tout en ambre puis repêcher les alertes
       ferait qu'un OUBLI ÉTEINT UNE ALERTE — et une alerte éteinte NE SE VOIT PAS. Dans le sens
       retenu, un oubli laisse une alerte de TROP : elle se voit, et elle se corrige.
     ⚠️ Un mutant qui poserait l'ambre par remplacement global fait mordre G1 (PaletteTests).

   LA LISTE NOMMÉE — 4 sélecteurs ici et dans les blocs voisins, plus 7 sites de document qui
   prennent la classe DÉDIÉE `.doc-reste` :
     · .doclist-kpi--reste   (8 usages : « Reste à payer / à facturer / à encaisser »,
                              « Quantités restantes », « Total créances »)
     · .doclist-payline b    (le reste de la paybar ; `b.solde` est DÉJÀ en teal quand c'est soldé)
     · .clist-due            (mobile : le solde d'un client, posé par SoldeAffiche(c) > 0m)
     · .clist-dette          (mobile : le KPI « Créances »)
     · .doc-reste            (7 sites : ResteAPayer > 0m / soldePayable > 0m)
   ⚠️ `.doc-red` NE BASCULE PAS : elle porte SIX sens (reste, remise, marge négative, delta
     négatif, sens de trésorerie, écart de caisse). C'est pour cela que les 7 sites du sens ⑧
     reçoivent une classe à eux.
   ⚠️ La BARRE garde --amber (un aplat de 3 px n'a aucun ratio de contraste à tenir) ; le TEXTE
     prend --amber-dark (voir son motif dans :root). */
.doclist-kpi--reste { --kpi-bar: var(--amber); }

/* --- Barre de filtres (pilules) — COMPACTÉE en T-LISTES-a ---
   ⚠️ BORNAGE MESURÉ : `.doclist-filters` est porté par 12 listes et par AUCUN écran hors
     liste. La taille de police reste à 12,5 px — le plancher.
   ⚠️⚠️ ON NE RÉORDONNE RIEN, ET C'EST UNE CONTRAINTE DE TEST, PAS UN GOÛT : le harnais
     désigne ces sélecteurs PAR POSITION (`.doclist-filters select` avec `.nth(1)`,
     `.nth(3)` — 10 usages mesurés). Déplacer un filtre ferait cliquer le mauvais, et
     l'assertion suivante rougirait sur une cause introuvable. On compacte, on ne bouge
     pas l'ordre. */
/* ⚠️ LA MARGE EST RENDUE (L9a-③). Elle avait été retirée comme DOUBLON du `gap: 12px` de
   `.doclist` — c'était juste ; mais le `gap` est parti à son tour pour rendre 12 px à la
   densité, et sans marge propre cette rangée toucherait le tableau. L'espacement revient donc
   aux ENFANTS, comme avant l'unification.
   ⚠️⚠️ ET IL COÛTAIT UN DÉCALAGE : dans la carte de filtres des états (`align-items: center`),
   une marge basse est CENTRÉE AVEC L'ÉLÉMENT — la rangée de pastilles remontait donc de 12/2 =
   6 px par rapport aux dates, aux radios et aux cases. Mesuré : écart des centres 6 px → 0.
   ⇒ L'espacement appartient au CONTENEUR (`gap`), jamais à l'enfant. Vérifié : les 13 écrans
   qui posent une rangée de filtres vivent tous dans un `.doclist`. */
.doclist-filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* ⚠️⚠️ LA MARGE APPARTIENT À LA RELATION, PAS À L'ENFANT — et c'est la TROISIÈME fois que ce
   défaut revient, donc la troisième formulation, la seule qui tienne des deux côtés.
   · DANS UNE LISTE, la rangée est fille DIRECTE de `.doclist` et doit s'écarter du tableau :
     sans marge, les deux blocs se touchent (mesuré en L9a-③).
   · DANS LA CARTE DE FILTRES DES ÉTATS, elle est fille de `.etat-filtres`, une carte en
     `align-items: center` — et une marge basse y est CENTRÉE AVEC L'ÉLÉMENT : la rangée
     remonte de 12/2 = 6 px et les dates ne sont plus sur sa ligne. Mesuré 6 px, trois fois.
   ⇒ La déclaration descend donc sur le sélecteur qui la JUSTIFIE (`>` enfant direct). Ce n'est
   pas une surcharge : c'est la même déclaration, portée par la relation qui la motive.
   ⚠️ Chiffré par une garde de géométrie (t23_etats) : l'écart entre les centres des enfants de
   la carte doit rester NUL. Deux corrections à la main du même défaut valaient un filet. */
.doclist > .doclist-filters { margin-bottom: 12px; }
/* --- L5 : LA PASTILLE DE FILTRE ---------------------------------------------
   ⚠️⚠️ LE `<select>` NATIF EST CONSERVÉ SOUS L'APPARENCE. Le motif complet vit dans
     `Components/Shared/FiltresListe.razor` ; en deux mots : le menu custom de la
     maquette n'apporte plus aucun contenu propre depuis le retrait du compte
     d'occurrences, et le menu natif ne peut pas être rogné puisque l'OS le rend.
   ⭐ LES 12 COUPLES SONT MESURÉS, tous ≥ 4,50 (AA) — aucune exception à inscrire :
     pastille repos ink-soft/surface 8,17 · survol ink-soft/surface-alt 7,67 ·
     ACTIVE teal-dark/teal-soft 4,87 · icône et chevron slate sur les TROIS fonds
     (5,64 / 5,29 / 4,94) · badge surface/teal-dark 5,56.
   ⚠️ L'ICÔNE ET LE CHEVRON RESTENT `--slate` SUR LES TROIS FONDS, SANS SURCHARGE :
     c'est ce qui tient « les états cohérents entre les quatre pastilles, sans
     exception visuelle » — et ce qui rend la dérivation du contraste nette. */
.doclist-fpill {
    position: relative; display: inline-flex; align-items: center;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--r-9); transition: background .12s, border-color .12s;
}
.doclist-fpill:hover { background: var(--surface-alt); }
.doclist-fpill.active { background: var(--teal-soft); border-color: var(--teal); }
.doclist-fpill-ico,
.doclist-fpill-chev { position: absolute; color: var(--slate); pointer-events: none; }
.doclist-fpill-ico { left: 10px; width: 13px; height: 13px; }
.doclist-fpill-chev { right: 9px; width: 12px; height: 12px; }
/* ⚠️ `appearance:none` retire le chevron de l'OS ; sans lui le nôtre le DOUBLERAIT.
   Le rembourrage ménage la place des deux SVG, qui sont `pointer-events:none` pour
   que le clic atteigne toujours le `<select>` — sur TOUTE la surface de la pastille. */
.doclist-filters .form-select {
    appearance: none; -webkit-appearance: none; -moz-appearance: none;
    width: auto; min-width: 172px; background: transparent; border: 0;
    /* ⚠️ 31 = 10 (bord → icône) + 13 (icône) + 8 (écart). L'ÉCART DE 8 EST MESURÉ, PAS CHOISI :
       la maquette pose `gap: 8px` dans `.fselect`, et `.doclist-filters` porte le MÊME `gap: 8px`
       entre pastilles. À 29 px l'écart valait 6 et le texte touchait presque l'icône.
       ⚠️ Le rembourrage est DANS la largeur (`box-sizing: border-box` global) et la pastille est
       plancher à `min-width: 172px` : +2 px n'élargit rien tant que le contenu tient sous le
       plancher — et `.doclist-filters` est en `flex-wrap: wrap`, donc AUCUN débordement n'est
       possible, au pire un retour à la ligne. */
    border-radius: var(--r-9); padding: 7px 26px 7px 31px;
    font-size: 12.5px; font-weight: 500; color: var(--ink-soft);
}
.doclist-fpill.active .form-select { color: var(--teal-dark); font-weight: 600; }
/* ⭐ LE HALO RÉUTILISE L'ANNEAU EXISTANT (bordure par l'accent + 3 px de son fond doux),
   celui que L3 a mutualisé — aucun motif neuf, aucun token de marque. */
.doclist-filters .form-select:focus { outline: none; }
.doclist-fpill:focus-within { border-color: var(--teal); box-shadow: 0 0 0 3px var(--teal-soft); }
.doclist-fclear { display: inline-flex; align-items: center; gap: 6px; color: var(--slate); font-size: 13px; font-weight: 500; background: none; border: none; cursor: pointer; padding: 9px 4px; }
.doclist-fclear:hover { color: var(--red); }
.doclist-fclear svg { width: 12px; height: 12px; }
/* --- L5 : LA BARRE DE DÉPLIAGE (mobile SEULEMENT) ---------------------------
   ⚠️⚠️ `display:none` PAR DÉFAUT, ET C'EST STRUCTUREL : au bureau les deux blocs
     sont toujours visibles, un bouton pour les déplier y serait sans objet. Elle
     n'apparaît que sous 768 px, où T-UI masque indicateurs ET filtres.
   ⭐ LE BADGE RESTE VISIBLE REPLIÉ : sans lui, un filtre actif sur une liste repliée
     serait INVISIBLE, et l'utilisateur lirait une liste amputée sans savoir pourquoi. */
.doclist-collapse { display: none; }
.doclist-cbtn {
    flex: 1 1 0; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-9);
    padding: 8px 10px; font-size: 12.5px; font-weight: 600; color: var(--ink-soft);
    cursor: pointer; transition: background .12s, border-color .12s;
}
.doclist-cbtn:hover { background: var(--surface-alt); }
.doclist-cbtn.open { background: var(--teal-soft); border-color: var(--teal); color: var(--teal-dark); }
.doclist-cbtn > svg:first-child { width: 14px; height: 14px; color: var(--slate); }
.doclist-cchev { width: 12px; height: 12px; color: var(--slate); transition: transform .15s; }
.doclist-cbtn.open .doclist-cchev { transform: rotate(180deg); }
/* ⚠️ 11 px GRAS : c'est le plancher de lisibilité, pas un choix — le seuil AA (4,50)
   s'applique donc, et surface/teal-dark rend 5,56. */
.doclist-cbadge {
    min-width: 17px; height: 17px; padding: 0 5px; border-radius: 999px;
    background: var(--teal-dark); color: var(--surface);
    font-size: 11px; font-weight: 700; line-height: 17px; text-align: center;
    font-family: var(--num-font); font-variant-numeric: tabular-nums;
}

/* --- Tableau (bornage .doclist : override du crud-list générique pour cet écran) --- */
.doclist .crud-list thead th { font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--slate); background: var(--thead-bg); padding: 8px 18px; }
.doclist .crud-list tbody td { padding: 8px 18px; font-size: 13.5px; vertical-align: middle; border-bottom: 1px solid var(--line); background: var(--surface); }
.doclist .crud-list tbody tr:hover td { background: var(--thead-bg); }

.doclist-piece { font-family: 'Manrope', sans-serif; font-weight: 700; font-size: 13.5px; letter-spacing: -.01em; color: var(--ink); }
.doclist-piece-prov { color: var(--slate); font-style: italic; }
.doclist-note-pill { display: inline-flex; align-items: center; gap: 3px; background: var(--amber-soft); color: var(--amber-dark); font-size: 10.5px; font-weight: 700; padding: 2px 6px; border-radius: var(--r-20); margin-left: 6px; vertical-align: middle; }
.doclist-note-pill svg { width: 9px; height: 9px; }
.doclist-date { color: var(--slate); font-variant-numeric: tabular-nums; }
.doclist-entrepot { color: var(--slate); }

.doclist-fourn { display: flex; align-items: center; gap: 10px; }
/* ⚠️⚠️ LE TEXTE N'EST PLUS ÉCRIT ICI, ET C'EST LE CŒUR DE L1d.
   Cette règle portait `color: #fff` EN DUR, pendant que le FOND était posé en style inline
   depuis C# — donc invisible du CSS. Mesuré : à 11,5px/700 c'est le seuil AA (4,50) qui
   s'applique, et QUATRE teintes sur cinq passaient dessous : amber 2,94 · green 3,41 ·
   red 3,94 · teal 3,98. Seul blue (4,63) tenait.
   ⭐⭐ NI G2 NI G2-bis NE POUVAIENT LE VOIR : la première ne connaît que des couples nommés à la
     main, la seconde DÉRIVE les couples posés dans une MÊME règle CSS — et ici le fond n'était
     dans aucune règle. C'est la garde d'avatar (G4) qui l'a nommé, teinte par teinte.
   ⇒ le fond ET le texte sont désormais posés ENSEMBLE par AvatarPalette.Style(), en
     --X-soft / --X-dark. Les sept teintes passent AA, la plus serrée à 4,64.
   ⚠️ NE PAS y remettre une couleur : elle gagnerait sur l'inline pour les écrans qui ne
     passent pas encore par le point unique, et le défaut renaîtrait EN SILENCE — rien ne
     plante, rien ne se voit, seul le contraste tombe. */
.doclist-avatar { width: 23px; height: 23px; border-radius: var(--r-9); display: flex; align-items: center; justify-content: center; font-family: 'Manrope', sans-serif; font-weight: 700; font-size: 11.5px; flex-shrink: 0; }
.doclist-fourn-name { font-weight: 600; color: var(--ink); }

.doclist .crud-list td.doclist-num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }
.doclist-num .dh { font-size: 11px; font-weight: 500; color: var(--slate); margin-left: 2px; }

/* Colonne Règlement (fusion réglé + reste) : paybar + ligne réglé/reste */
.doclist-pay { min-width: 150px; }
.doclist-paybar { height: 5px; border-radius: var(--r-3); background: var(--line); overflow: hidden; margin-bottom: 6px; }
.doclist-paybar > i { display: block; height: 100%; background: var(--teal); border-radius: var(--r-3); }
.doclist-payline { display: flex; justify-content: space-between; gap: 8px; font-size: 11px; color: var(--slate); font-variant-numeric: tabular-nums; }
/* SENS ⑧ — le reste de la paybar. Voir le motif complet au bloc `.doclist-kpi--reste`.
   ⭐ `b.solde` était DÉJÀ en teal quand il ne reste rien : le reste > 0 était donc le SEUL état
     de cette ligne à porter une couleur d'alerte pour un fait parfaitement normal. */
.doclist-payline b { color: var(--amber-dark); font-weight: 700; }
.doclist-payline b.solde { color: var(--teal-dark); }
.doclist-pay-empty { color: var(--slate); }

/* Badges de statut pastel */
.doclist-badges { display: flex; gap: 6px; flex-wrap: wrap; }
.doclist-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 700; padding: 4px 9px; border-radius: var(--r-20); }
.doclist-badge .dot { width: 5px; height: 5px; border-radius: 50%; }
.doclist-badge--valide { background: var(--teal-soft); color: var(--teal-dark); }
.doclist-badge--valide .dot { background: var(--teal); }
.doclist-badge--partiel { background: var(--amber-soft); color: var(--amber-dark); }
.doclist-badge--partiel .dot { background: var(--amber); }
.doclist-badge--brouillon { background: var(--amber-soft); color: var(--amber-dark); }
.doclist-badge--brouillon .dot { background: var(--amber); }

/* Badge de SITUATION commerciale d'un devis (En cours / Accepté / Refusé) — suivi, indépendant de la couverture */
.devis-situation-badge { display: inline-flex; align-items: center; gap: 5px; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.devis-situation--encours { background: var(--amber-soft); color: var(--amber-dark); }
.devis-situation--accepte { background: var(--teal-soft); color: var(--teal-dark); }
.devis-situation--refuse { background: var(--red-soft); color: var(--red); }

/* Boutons d'action (pastille 32px) */
.doclist-act { display: inline-flex; gap: 6px; }
.doclist-edit-btn { width: 26px; height: 26px; border-radius: var(--r-9); border: 1px solid var(--line); background: var(--paper); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; color: var(--slate); transition: all .15s; }
.doclist-edit-btn:hover { background: var(--teal-soft); color: var(--teal-dark); border-color: var(--teal-soft); }
.doclist-edit-btn.doclist-del-btn:hover { background: var(--red-soft); color: var(--red); border-color: var(--red-soft); }
.doclist-edit-btn svg { width: 14px; height: 14px; }

/* Pied : page active = --ink (maquette) */
.doclist .pager-btn.active { background: var(--ink); color: #fff; border-color: var(--ink); }

/* Mobile : KPIs 2×2 au-dessus des cartes
   ⚠️⚠️ CETTE RÈGLE EST INERTE AUJOURD'HUI, ET LE TAIRE SERAIT PIRE QUE DE NE PAS L'ÉCRIRE :
     T-UI a posé plus bas `.doclist-kpis, .doclist-filters { display: none !important }`
     sous 768 px, sans aucun bornage — donc sur les 13 listes qui les portent. Le bloc n'a
     PAS de hauteur en mobile (mesuré : 0 px sur /client/bons-livraison en 390×844).
   ⭐⭐ ET CE MASQUAGE VALIDE LA MESURE DE CETTE TRANCHE PAR UN AUTRE CHEMIN : T-UI l'avait
     décidé sur le MÊME diagnostic — « 573 px perdus au-dessus de la première rangée, les
     coupables étaient les KPIs et la rangée de filtres ». Le même coupable, mesuré deux fois
     à deux endroits, par deux tranches qui ne se connaissaient pas.
   ⇒ on la GARDE et on l'AJUSTE pour qu'elle reste cohérente le jour où le masquage sera levé
     (T-LISTES-c donnera des cartes aux 3 listes qui n'en ont pas) — mais elle ne protège de
     rien tant qu'il tient, et le test d'étage 1 qui la vérifie le dit aussi.
   ⚠️ SA VALEUR EST RAMENÉE DE 19 À 17 px : le desktop étant passé à 18, la laisser à 19
     rendrait le KPI PLUS GROS SUR TÉLÉPHONE QUE SUR ÉCRAN — une incohérence que personne ne
     chercherait, puisqu'on ne voit jamais les deux côte à côte. */
@media (max-width: 768px) {
    .doclist-kpis { grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; }
    /* ⚠️ L4 — 17 → 15 px, ET C'EST LA GARDE DE T-LISTES-a QUI L'A EXIGÉ : le desktop passant de
       18 à 16, laisser 17 rendrait l'indicateur PLUS GROS SUR TÉLÉPHONE. On ne voit jamais les
       deux côte à côte, donc personne ne chercherait l'incohérence.
       ⚠️ Cette règle reste INERTE (le bandeau est masqué sous 768 px depuis T-UI) — elle
       maintient la cohérence pour le jour où L5 lèvera le masquage, pas un pixel d'aujourd'hui. */
    .doclist-kpi-val { font-size: 15px; }
}

/* ============================================================================
   MODULE ÉTATS (T3a) — hub de cartes + badges de partie + tableau d'état
   ============================================================================ */
.etats-hub { max-width: 1280px; margin: 0 auto; padding: 4px 0 40px; }
.etats-cat { margin-top: 22px; }
.etats-cat-titre { font-family: 'Manrope', sans-serif; font-size: 17px; font-weight: 700; color: var(--ink); margin: 0 0 12px; }
.etats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.etat-carte { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; text-align: left;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-14); padding: 16px; cursor: pointer;
    transition: box-shadow .15s, border-color .15s, transform .1s; min-height: 92px; }
.etat-carte:hover:not(:disabled) { box-shadow: 0 6px 20px rgba(15,30,60,.08); border-color: var(--teal-soft); transform: translateY(-1px); }
.etat-carte-titre { font-weight: 600; font-size: 14.5px; color: var(--ink); line-height: 1.3; }
.etat-carte-note { font-size: 11.5px; color: var(--slate); font-weight: 600; }
.etat-carte--avenir { opacity: .55; cursor: not-allowed; background: var(--paper); }

/* CT-4/P-12 — LANDING : cartes de catégorie (les 5 entrées du volet États) */
.etats-cats-grid { max-width: 1280px; margin: 0 auto; padding: 8px 0 40px; display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
.etat-cat-carte { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; text-decoration: none;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-16); padding: 20px; min-height: 116px;
    transition: box-shadow .15s, border-color .15s, transform .1s; }
.etat-cat-carte:hover { box-shadow: 0 8px 24px rgba(15,30,60,.10); border-color: var(--teal-soft); transform: translateY(-2px); }
.etat-cat-icone { font-size: 26px; line-height: 1; }
.etat-cat-nom { font-family: 'Manrope', sans-serif; font-weight: 700; font-size: 16px; color: var(--ink); margin-top: 4px; }
.etat-cat-compte { font-size: 12.5px; color: var(--slate); font-weight: 600; }

/* A1 — le stub « Digistorm Académie » a QUITTÉ ce bloc : la page vit désormais dans
   Academie.razor, habillée par le GABARIT AIDE en fin de fichier. Les 5 règles `.etat-academie-*`
   sont retirées avec lui — une règle sans porteur est une rustine qui survit à son motif. */

/* CT-4/P-12 — STRUCTURE DESKTOP (calque WinDev) : bannière + GROUPES (colonnes) → CARTES colorées → rapports */
.etat-banner { position: relative; display: flex; align-items: center; gap: 16px; overflow: hidden;
    background: linear-gradient(120deg, var(--teal-dark), var(--teal)); color: #fff; border-radius: var(--r-14);
    padding: 16px 24px; margin: 4px 0 22px; }
.etat-banner-ico { position: relative; width: 46px; height: 46px; display: grid; place-items: center;
    background: rgba(255,255,255,.15); border-radius: var(--r-12); font-size: 22px; flex: none; }
.etat-banner-star { position: absolute; right: 0; bottom: -2px; font-size: 12px; color: var(--amber); }
.etat-banner-txt { z-index: 1; }
.etat-banner-titre { font-family: 'Manrope', sans-serif; font-weight: 800; font-size: 20px; }
.etat-banner-sous { font-size: 12.5px; opacity: .85; }
.etat-banner-deco { position: absolute; right: 90px; top: 0; bottom: 0; width: 180px; pointer-events: none; }
.etat-banner-deco span { position: absolute; border-radius: 50%; background: rgba(255,255,255,.10); }
.etat-banner-deco span:nth-child(1) { width: 62px; height: 62px; top: 6px; right: 40px; }
.etat-banner-deco span:nth-child(2) { width: 42px; height: 42px; top: 42px; right: 96px; }
.etat-banner-search { margin-left: auto; max-width: 240px; background: rgba(255,255,255,.94); z-index: 1; }

.etats-desktop { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 22px 26px;
    max-width: 1320px; margin: 0 auto; padding-bottom: 44px; align-items: start; }
.etat-groupe { display: flex; flex-direction: column; gap: 16px; }
.etat-groupe-titre { display: flex; align-items: center; gap: 12px; font-size: 12.5px; font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase; color: var(--teal-dark); }
.etat-groupe-titre::after { content: ""; flex: 1; height: 1px; background: var(--line); }

.etat-carte-desk { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-12); overflow: hidden;
    box-shadow: 0 2px 8px rgba(15,30,60,.05); }
.etat-carte-desk-head { display: flex; align-items: center; gap: 8px; padding: 10px 14px; color: #fff; font-weight: 600;
    font-size: 14px; background: linear-gradient(120deg, var(--teal-dark), var(--teal)); }
.etat-carte-desk-ico { font-size: 15px; }
.etat-carte-desk-body { padding: 8px 6px; display: flex; flex-direction: column; }
.etat-lien-desk { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: none;
    border: none; padding: 8px 12px; border-radius: var(--r-8); cursor: pointer; color: var(--ink); font-size: 13.5px;
    transition: background .12s; }
.etat-lien-desk:hover:not(:disabled) { background: var(--teal-soft); }
.etat-chevron { color: var(--teal); font-weight: 700; font-size: 15px; }
.etat-lien-titre { flex: 1; }
.etat-lien-note { font-size: 12px; opacity: .8; }
.etat-lien-desk--off { color: var(--slate); cursor: not-allowed; opacity: .65; }
.etat-lien-desk--off .etat-chevron { color: var(--slate); }

@media (max-width: 1100px) { .etats-desktop { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 768px) { .etats-desktop { grid-template-columns: 1fr; } .etat-banner-search { display: none; } }

/* P-16 — filtre 3 choix de la balance des soldes (Solde TTC > 0 / Tous / Solde réel > 0) */
.etat-balance-radios { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.etat-balance-radios label { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--ink); cursor: pointer; font-weight: 500; }

/* Badge de PARTIE (pastel §13) */
.etat-partie { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 999px; font-size: 11.5px; font-weight: 700; letter-spacing: .02em; }
.etat-partie--physique { background: var(--teal-soft); color: var(--teal-dark); }
.etat-partie--facturee { background: var(--blue-soft); color: var(--blue-dark); }
.etat-partie--tresorerie { background: var(--amber-soft); color: var(--amber-dark); }
.etat-partie--ecart { background: var(--paper); color: var(--slate); border: 1px solid var(--line); }

/* ⚠️⚠️ LA BARRE DE FILTRES DE L'ÉTAT A ÉTÉ RETIRÉE D'ICI (2026-08-23), ET C'EST CETTE RÈGLE-LÀ
   QUI CASSAIT LA RANGÉE — mesuré par le moteur, pas déduit : `CSS.getMatchedStylesForNode` la
   désignait comme GAGNANTE sur `.doclist-filters` dans les états.
   `.etat-filtres > div` (0,1,1) bat `.doclist-filters` (0,1,0) : le conteneur des pastilles du
   composant partagé recevait `flex-direction: column`, et les pastilles S'EMPILAIENT.
   ⚠️ Elle datait du markup « label au-dessus du contrôle », supprimé avec le bloc mort
   d'`EtatViewer` — et elle était INVISIBLE à toute recherche sur `.doclist .etat-filtres`,
   parce qu'elle n'a jamais porté ce préfixe. `.etat-filtres` n'est employé QUE par
   `EtatViewer.razor`, qui vit dans `.doclist` : la carte garde donc ses règles du bloc
   d'unification, plus spécifiques, et rien d'autre ne dépendait de ces deux lignes. */
.etat-interne-note { color: var(--amber); font-weight: 600; font-size: 12px; }

/* Tableau d'état */
/* RETOUR TERRAIN n1 - LE TOTAL NE FAISAIT PAS LA SOMME DES LIGNES VISIBLES.
   Le tableau des etats vit dans un `overflow-x:auto` : ce conteneur DEFILE, donc le sticky
   se cale par rapport a LUI, pas au viewport. Un `top: 64px` (pense pour degager la barre de
   navigation) posait donc l'en-tete 64 px SOUS le haut du tableau, EN PERMANENCE par-dessus
   la PREMIERE ligne : elle comptait dans le total et l'utilisateur ne la voyait pas.
   ⚠️ Meme remede que la pop-up « Saisie multiple », pour la meme raison. Le 64 px n'a jamais
   servi ici : dans un conteneur de defilement il ne degage rien du tout.
   ⚠️ Mesure de non-regression : t76 verifie desormais, ligne par ligne, qu'aucune n'est
   RECOUVERTE (elementFromPoint au centre de la ligne) — le DOM ne suffit pas. */
.etat-table thead th { position: sticky; top: 0; background: var(--thead-bg); font-size: 11.5px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .04em; color: var(--slate); }
.etat-table tbody td { font-size: 13.5px; }
.etat-table tfoot .etat-total td { border-top: 2px solid var(--line); background: var(--row-soft); font-size: 13.5px; }

/* Impression d'un état — le tableau pf-* réutilisé */
.etat-print { width: 100%; font-size: 11px; }
.etat-print th, .etat-print td { padding: 4px 6px; }

/* ⚠️⚠️ LES DEUX TONS DE LA COLONNE CONDITIONNELLE — DEUX CLASSES, JAMAIS UNE SEULE.
   `.etat-alerte` DIT UN PROBLÈME et reste de la FAMILLE ROUGE : elle est inscrite dans
   PaletteTests.SensProbleme, et y poser un vert ferait rougir G1 — elle aurait raison.
   `.etat-favorable` est une CLASSE NEUVE : un délai qui n'est pas encore échu n'est pas
   une alerte atténuée, c'est le CONTRAIRE d'une alerte. Les fondre en une classe à
   deux couleurs rendrait `.etat-alerte` verte une ligne sur deux.
   ⚠️ LES DEUX PORTENT LE TON FONCÉ : --red rendait 3,94 et --green rend 3,41 sur
   --surface, tous deux SOUS AA. Ces cellules sont à 13,5 px à l'écran et 11 px à
   l'impression — AA-large (3,0) ne s'y applique PAS. */
.etat-alerte { color: var(--red-dark); font-weight: 700; }
.etat-favorable { color: var(--green-dark); font-weight: 700; }

/* RUPTURE — ligne de SOUS-TOTAL (TVA-PRÉV). ⚠️ RÈGLES NOUVELLES : aucune règle partagée existante
   n'est modifiée, et aucune des 49 définitions du catalogue ne pose la clé `_soustotal` — elles
   rendent donc class="" (le <tr> nu d'avant), inchangées au pixel. */
.etat-table tbody tr.etat-soustotal td { background: var(--row-soft); font-weight: 700; border-top: 1px solid var(--line); }
.etat-report-hint { font-size: 11px; color: var(--slate); margin-top: 2px; }

/* Note de lecture métier en pied d'état (ex. état d'écart — T3d) */
.etat-note-pied { margin: 10px 4px 0; padding: 10px 12px; background: var(--paper); border-left: 3px solid var(--slate); border-radius: var(--r-8); font-size: 12.5px; color: var(--slate); line-height: 1.5; }

/* ============================================================================
   U1 — AUTHENTIFICATION : layout minimal (login / changement mdp) + menu user
   ============================================================================ */
.auth-shell { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px;
    background: linear-gradient(135deg, #0c1730 0%, #101f42 100%); }
.auth-card { width: 100%; max-width: 400px; background: var(--surface); border-radius: 18px; padding: 34px 32px;
    box-shadow: 0 20px 60px rgba(0,0,0,.35); }
.auth-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 22px; }
.auth-brand-badge { width: 38px; height: 38px; border-radius: var(--r-11); background: var(--ink); color: #fff;
    display: inline-flex; align-items: center; justify-content: center; font-family: 'Manrope', sans-serif; font-weight: 800; font-size: 20px; }
/* L0 — le logo de la page de connexion : même motif que .brand-name (TopBar). */
.auth-brand-name { font-family: 'Fraunces', Georgia, 'Times New Roman', serif; font-variation-settings: 'opsz' 96; font-weight: 700; font-size: 19px; color: var(--ink); }
.auth-brand-pro { color: var(--teal); }
.auth-title { font-family: 'Manrope', sans-serif; font-size: 22px; font-weight: 700; color: var(--ink); margin: 0 0 4px; }
.auth-sub { font-size: 13px; color: var(--slate); margin: 0 0 20px; }
.auth-form { display: flex; flex-direction: column; }
.auth-label { font-size: 12px; font-weight: 600; color: var(--slate); margin: 12px 0 5px; }
.auth-input { height: 42px; }
.auth-submit { margin-top: 20px; height: 44px; justify-content: center; font-size: 15px; }
.auth-erreur { background: var(--red-soft); color: var(--red); border-radius: var(--r-9); padding: 10px 12px; font-size: 13px; font-weight: 600; margin-bottom: 16px; }
.auth-regles { margin: 12px 0 0; padding-left: 18px; font-size: 12px; color: var(--slate); line-height: 1.6; }
.auth-lien-form { text-align: center; margin-top: 14px; }
.auth-lien { background: none; border: none; color: var(--slate); font-size: 12.5px; cursor: pointer; text-decoration: underline; }

/* Menu utilisateur (TopBar) */
.user-chip-wrap { position: relative; }
.user-chip { background: none; border: none; cursor: pointer; display: flex; align-items: center; gap: 10px; padding: 4px 6px; border-radius: var(--r-10); }
.user-chip:hover { background: var(--paper); }
.user-chevron { color: var(--slate); }
.user-menu { position: absolute; right: 0; top: calc(100% + 6px); background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--r-12); box-shadow: 0 12px 32px rgba(15,30,60,.14); min-width: 220px; padding: 6px; z-index: 200; }
.user-menu-head { font-size: 11.5px; color: var(--slate); padding: 8px 10px 6px; border-bottom: 1px solid var(--line); margin-bottom: 4px; font-weight: 600; }
.user-menu-form { margin: 0; }
.user-menu-item { display: block; width: 100%; text-align: left; background: none; border: none; cursor: pointer;
    padding: 9px 10px; border-radius: var(--r-8); font-size: 13.5px; color: var(--ink); text-decoration: none; }
.user-menu-item:hover { background: var(--paper); }
.user-menu-logout { color: var(--red); }
.sidebar-logout-form { margin: 0; }
.sidebar-logout-form .sidebar-link.logout { width: 100%; background: none; border: none; cursor: pointer; text-align: left; }

/* ===== U2 — ACCÈS REFUSÉ (garde de volet) ===== */
.acces-refuse { display: flex; align-items: center; justify-content: center; min-height: 60vh; padding: 40px 16px; }
.acces-refuse-card { text-align: center; max-width: 420px; background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--r-16); padding: 40px 32px; box-shadow: 0 4px 24px rgba(15,23,42,.06); }
.acces-refuse-card svg { color: var(--red); }
.acces-refuse-card h1 { font-family: 'Manrope', sans-serif; font-size: 24px; font-weight: 700; margin: 14px 0 8px; color: var(--ink); }
.acces-refuse-card p { color: var(--slate); font-size: 14.5px; margin: 0 0 6px; }
.acces-refuse-detail { font-size: 13px; color: var(--slate); font-style: italic; }
.acces-refuse-card a.btn-nouveau { display: inline-block; margin-top: 20px; text-decoration: none; }

/* ===== U2 — écran Utilisateurs : permissions & mot de passe temporaire ===== */
.perm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px 18px; }
.perm-check { display: flex; align-items: center; gap: 8px; font-size: 13.5px; color: var(--ink); }
.perm-check input { width: 16px; height: 16px; accent-color: var(--teal); }
.perm-bloc-titre { font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
    color: var(--slate); margin: 4px 0 6px; }
.mdp-temp-box { background: var(--amber-soft); border: 1px solid var(--amber); border-radius: var(--r-10); padding: 14px 16px; margin: 8px 0; }
.mdp-temp-box strong { font-family: var(--num-font); font-variant-numeric: tabular-nums; font-size: 17px; color: var(--ink); letter-spacing: .5px; }
.mdp-temp-box .mdp-temp-hint { font-size: 12.5px; color: var(--slate); margin-top: 4px; }
.perm-info { font-size: 12.5px; color: var(--slate); font-style: italic; margin-top: 4px; }

/* ===== U3 — ligne de traçabilité (créé par / validé par) ===== */
.trac-ligne { font-size: 12px; color: var(--slate); margin-top: 6px; font-variant-numeric: tabular-nums; }
.trac-ligne strong { font-weight: 600; color: var(--ink); }

/* ===== S3 — Relevé produit ===== */
.releve-onglets { display: flex; gap: 4px; border-bottom: 2px solid var(--line); margin-bottom: 14px; }
.releve-onglet { background: none; border: none; padding: 9px 18px; font-size: 14.5px; font-weight: 600; color: var(--slate);
    cursor: pointer; border-bottom: 2.5px solid transparent; margin-bottom: -2px; }
.releve-onglet.active { color: var(--ink); border-bottom-color: var(--teal); }
.releve-onglet-sub { font-weight: 400; font-size: 12px; color: var(--slate); }
.releve-produit-choisi { padding: 7px 10px; background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-8); font-size: 13.5px; }
.releve-changer { background: none; border: none; color: var(--teal-dark); cursor: pointer; font-size: 12.5px; margin-left: 8px; text-decoration: underline; }
.releve-stockactuel { text-align: right; } .releve-stockactuel strong { font-size: 17px; color: var(--ink); font-variant-numeric: tabular-nums; }
.releve-solde-masque { font-size: 12.5px; color: var(--slate); background: var(--amber-soft); border-radius: var(--r-8); padding: 6px 12px; margin-bottom: 8px; }
.releve-table tbody tr td { font-size: 13px; }
.releve-initial td { background: var(--green-soft) !important; }
.releve-row-achat td { background: color-mix(in srgb, var(--blue-soft) 55%, transparent); }
.releve-row-vente td { background: color-mix(in srgb, var(--green-soft) 45%, transparent); }
.releve-row-retour td { background: color-mix(in srgb, var(--amber-soft) 45%, transparent); }
/* PRD-5 — la PRODUCTION a sa propre teinte : ni achat (bleu) ni vente (vert), et distincte de
   l'ambre des retours/corrections. Sans elle, la classe serait FANTÔME et la ligne s'afficherait
   sans fond — le defaut que la garde CSS de T-GARDES existe pour attraper. */
.releve-row-production td { background: color-mix(in srgb, var(--teal-soft) 50%, transparent); }
.releve-piece { color: var(--blue); text-decoration: none; font-weight: 500; } .releve-piece:hover { text-decoration: underline; }
.releve-foot { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; }
.releve-controle-ok { color: var(--teal-dark); font-weight: 700; background: var(--teal-soft); padding: 4px 12px; border-radius: var(--r-20); }
.releve-controle-ko { color: var(--red); font-weight: 700; background: var(--red-soft); padding: 4px 12px; border-radius: var(--r-20); }
.releve-controle-neutre { color: var(--slate); font-style: italic; }

/* ===== S4 — balances partenaires enrichies (tuiles, sous-lignes, blocs) ===== */
.etat-tuiles { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; margin-bottom: 14px; }
.etat-tuile { background: linear-gradient(135deg, var(--teal-dark), var(--teal)); color: #fff; border-radius: var(--r-10); padding: 12px 14px;
    display: flex; flex-direction: column; gap: 4px; box-shadow: 0 2px 8px rgba(15,23,42,.10); }
.etat-tuile-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; opacity: .9; font-weight: 600; }
.etat-tuile-val { font-family: var(--num-font); font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums; }
.etat-tuile-note { font-size: 10.5px; opacity: .85; font-style: italic; }
.etat-tuile.grise { background: var(--paper); color: var(--slate); box-shadow: none; border: 1px dashed var(--line); }
.etat-tuile.grise .etat-tuile-val { color: var(--slate); }
@media (max-width: 1100px) { .etat-tuiles { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 640px) { .etat-tuiles { grid-template-columns: repeat(2, 1fr); } }
.etat-lien { color: var(--blue); text-decoration: none; font-weight: 500; } .etat-lien:hover { text-decoration: underline; }
.etat-sous-row td { background: var(--paper) !important; padding-top: 2px; padding-bottom: 4px; }
.etat-sous { font-size: 11px; color: var(--slate); padding-left: 18px; line-height: 1.5; font-variant-numeric: tabular-nums; }
.etat-blocs { margin-top: 12px; display: flex; flex-direction: column; gap: 6px; max-width: 620px; }
.etat-bloc { display: flex; justify-content: space-between; align-items: center; padding: 8px 14px; border-radius: var(--r-8); font-size: 13.5px; }
.etat-bloc-val { font-weight: 700; font-variant-numeric: tabular-nums; }
.etat-bloc-neutre { background: var(--teal-soft); color: var(--ink); }
.etat-bloc-vert { background: var(--green-soft); color: var(--ink); }
.etat-bloc-rouge { background: var(--red-soft); color: var(--red); }
.etat-bloc-grise { background: var(--paper); color: var(--slate); border: 1px dashed var(--line); font-style: italic; }
.etat-bloc-badge { display: inline-block; margin-left: 8px; font-size: 11px; font-weight: 700; background: var(--red); color: #fff; padding: 2px 8px; border-radius: var(--r-12); font-style: normal; }

/* S4 — impression tuiles/blocs */
.etat-print-tuiles { display: flex; flex-wrap: wrap; gap: 4px 18px; font-size: 11px; margin: 6px 0 10px; padding: 6px 8px; background: #f1f5f9; border-radius: 4px; }
.etat-print-blocs { margin-top: 8px; font-size: 11.5px; display: flex; flex-direction: column; gap: 2px; }

/* ===== R4 — TABLEAU DE BORD (données réelles) ===== */
.dash-periode { display: inline-flex; gap: 4px; background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-10); padding: 3px; }
.dash-periode-btn { border: 0; background: transparent; color: var(--slate); font: 600 13px 'Manrope', sans-serif; padding: 6px 16px; border-radius: var(--r-7); cursor: pointer; }
.dash-periode-btn.active { background: var(--surface); color: var(--ink); box-shadow: 0 1px 3px rgba(0,0,0,.08); }

.kpi-link { text-decoration: none; color: inherit; transition: box-shadow .12s, transform .12s; }
.kpi-link:hover { box-shadow: 0 4px 14px rgba(0,0,0,.10); transform: translateY(-1px); }
.kpi-go { color: var(--kpi-color); font-weight: 800; font-size: 15px; }

/* R4-bis — pied de tuile à DEUX liens : une valeur qui somme deux états doit laisser
   chaque moitié atteignable (un lien unique montrerait moins que le chiffre affiché). */
.kpi-liens { display: flex; gap: 14px; margin-top: 8px; flex-wrap: wrap; }
.kpi-liens a { color: var(--kpi-color); font-size: 12px; font-weight: 600; text-decoration: none; }
.kpi-liens a:hover { text-decoration: underline; }

.dash-doc-link { color: var(--teal-dark); text-decoration: none; font-weight: 600; }
.dash-doc-link:hover { text-decoration: underline; }
.dash-empty { text-align: center; color: var(--slate); padding: 18px 0; font-size: 13px; }

.tres-comptes { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.tres-compte { display: flex; justify-content: space-between; align-items: baseline; font-size: 13px; }
.tres-compte-lbl { color: var(--ink); }
.tres-compte-lbl em { color: var(--slate); font-style: normal; font-size: 11px; margin-left: 4px; text-transform: uppercase; letter-spacing: .04em; }
.tres-compte-val { color: var(--ink); font-weight: 600; }
.tres-compte-val small { color: var(--slate); font-weight: 400; font-size: 10px; }
.tres-ed { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line); font-size: 12.5px; }
.tres-ed-item { display: inline-flex; align-items: center; gap: 6px; margin-right: 16px; color: var(--slate); }
.tres-ed-item strong { color: var(--ink); }
.tres-ed-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.tres-ed-dot.enc { background: var(--green); }
.tres-ed-dot.dec { background: var(--red); }
.tres-ed-bars { margin-top: 8px; display: flex; flex-direction: column; gap: 4px; }
.tres-ed-bar { height: 8px; border-radius: 4px; min-width: 2px; }
.tres-ed-bar.enc { background: var(--green); }
.tres-ed-bar.dec { background: var(--red); }

.dash-alertes { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.dash-alerte { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: var(--r-9); background: var(--paper); border: 1px solid var(--line); text-decoration: none; color: var(--ink); }
.dash-alerte:hover { background: var(--teal-soft); }
.dash-alerte.rouge { background: var(--red-soft); border-color: rgba(225,29,72,.25); }
.dash-alerte-count { font: 800 16px 'Manrope', sans-serif; min-width: 30px; text-align: center; color: var(--teal-dark); font-variant-numeric: tabular-nums; }
.dash-alerte.rouge .dash-alerte-count { color: var(--red); }
.dash-alerte-lbl { flex: 1; font-size: 13px; }
.dash-alerte-go { color: var(--slate); font-weight: 700; }

.dash-chart-full { width: 100%; }
.bar.bar-courant { background: var(--teal); }
.bar-label-courant { color: var(--ink); font-weight: 700; }

/* ===== R1 — badges de situation de règlement (chèque/traite) ===== */
.sit-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 700; padding: 2px 9px; border-radius: var(--r-20); white-space: nowrap; }
.sit-badge::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.sit-instance { background: var(--paper); color: var(--slate); }
.sit-encours { background: var(--amber-soft); color: var(--amber-dark); }
.sit-encaisse { background: var(--teal-soft); color: var(--teal-dark); }
.sit-impaye { background: var(--red-soft); color: var(--red); }
.sit-none { background: var(--paper); color: var(--slate); }
.mode-differe-badge { display: inline-block; margin-left: 6px; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding: 1px 7px; border-radius: var(--r-20); background: var(--amber-soft); color: var(--amber-dark); }
/* échéance dépassée dans la grille des chèques non encaissés
   ⚠️⚠️ BASCULÉE AVEC `.etat-alerte`, ET C'EST LA GARDE NEUVE QUI L'A EXIGÉ : même
   défaut, même famille d'écrans, même 3,94. La laisser en --red aurait rendu G2-ter
   ROUGE dès son premier lancement — ou, pire, m'aurait fait l'écrire en l'excluant,
   c'est-à-dire une garde tenue à l'écart du seul autre cas qu'elle sait voir. */
.etat-echeance-depassee { color: var(--red-dark); font-weight: 700; }

/* R1 — grille des chèques/traites non encaissés (relevé enrichi) */
.etat-cheques-titre { margin: 16px 0 6px; font: 700 13px 'Manrope', sans-serif; color: var(--ink); }
.etat-cheques th { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--slate); }
.etat-cheques td { font-size: 12.5px; }

/* R2 — sélecteur d'opération sur la fiche de règlement */
.op-radios { display: flex; gap: 14px; flex-wrap: wrap; font-size: 13px; }
.op-radio { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; }
.op-radio input { margin: 0; }

/* P-04 — carte d'erreur LOCALE d'un bloc protégé (ErrorBoundary / échec de chargement) */
.bloc-erreur { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border: 1px solid var(--amber); background: var(--amber-soft); border-radius: var(--r-10); font-size: 13px; color: var(--ink); }
.bloc-erreur-ico { font-size: 16px; line-height: 1; }
.bloc-erreur-txt { flex: 1 1 auto; }
.bloc-erreur-btn { border: 1px solid var(--amber); background: #fff; color: var(--ink); border-radius: var(--r-7); padding: 5px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer; }
.bloc-erreur-btn:hover { background: var(--amber-soft); }

/* ===== T9-f1 — SENS D'UN MOUVEMENT DE TRÉSORERIE =====
   ⚠️ Jamais « Débit / Crédit » : la correspondance Desktop est CROISÉE (Desktop « Débit » = notre
   'E'). Les libellés web sont « Entrée d'argent (+) » et « Sortie d'argent (−) », qui collent
   directement à 'E' et 'D'. Le vert MONTE le solde, le rouge le BAISSE. */
.sens-badge {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 2px 9px; border-radius: 999px;
    font-size: 12px; font-weight: 600; white-space: nowrap;
}
.sens-badge.sens-entree { background: var(--green-soft); color: var(--green-dark); }
.sens-badge.sens-sortie { background: var(--red-soft); color: var(--red); }
/* Montant coloré dans la liste : le signe se LIT, il n'est pas seulement écrit. */
.mvt-montant.entree { color: var(--green); font-weight: 600; }
.mvt-montant.sortie { color: var(--red); font-weight: 600; }

/* Marqueur de champ OBLIGATOIRE dans les fiches modales (colonne NOT NULL en base). */
.champ-requis { color: var(--red); font-weight: 700; margin-left: 2px; }

/* =============================================================================
   T11-a — AUTOCOMPLÉTION EN FEUILLE MOBILE (ClientPicker · FournisseurPicker · ProduitPicker)
   -----------------------------------------------------------------------------
   ⚠️⚠️ LE DÉFAUT CORRIGÉ ÉTAIT BLOQUANT : sans sélection de client, aucun document ne peut être
   créé. Le clavier virtuel AMPUTE la hauteur visible ; la liste, ancrée sous le champ, tombait
   dessous. Mesuré sur téléphone puis reproduit : champ à y=333..441 pour 400 px réellement
   visibles — le champ lui-même était DÉJÀ sous la ligne du clavier. Borner la liste (elle l'était
   déjà à 300 px) n'y pouvait donc rien, et scrollIntoView n'aurait fait que contourner, avec un
   timing dépendant du navigateur et du clavier réels — donc invérifiable en CI.

   LA FEUILLE SUPPRIME LA CLASSE DE PROBLÈMES, PAS L'INSTANCE : champ ET liste remontent en haut
   de l'écran, le clavier occupe le bas, le recouvrement devient impossible PAR CONSTRUCTION —
   quelle que soit la position d'origine du champ, la hauteur de la page ou le comportement du
   clavier.

   ⚠️ AUCUN VOILE ASSOMBRI : ce serait rouvrir le piège documenté des overlays plein écran, qui
   avalent le premier tap destiné aux autres contrôles. Le détachement se fait par fond opaque
   et ombre portée. La fermeture reste @onfocusout, la sélection @onmousedown, DOM inchangé.

   ⚠️ SPÉCIFICITÉ — NE PAS « SIMPLIFIER » : DigistormWeb.styles.css (CSS scopé) est chargé APRÈS
   app.css. Un `.cp-wrap.cp-sheet` (0,2,0) ferait donc ÉGALITÉ avec le `.cp-wrap[b-xxx]` scopé et
   PERDRAIT au dernier arrivé. D'où le `body` en tête (0,2,1), qui tranche indépendamment de
   l'ordre de chargement. Le sélecteur du panneau (0,3,0) gagne déjà de lui-même.
   ============================================================================= */
@media (max-width: 768px) {

    body .cp-wrap.cp-sheet,
    body .pp-wrap.pp-sheet {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        z-index: 3000;
        padding: 10px 12px 12px;
        background: var(--surface, #fff);
        border-bottom: 1px solid var(--line);
        box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);
    }

    /* La liste redevient un bloc ORDINAIRE sous le champ, à l'intérieur de la feuille :
       plus aucun ancrage absolu, donc plus rien qui puisse tomber sous le clavier.
       45vh laisse la place au clavier même sur les petits écrans. */
    .cp-wrap.cp-sheet .cp-panel,
    .pp-wrap.pp-sheet .pp-panel {
        position: static;
        margin-top: 8px;
        max-height: 45vh;
        width: auto;
        min-width: 0;
        max-width: 100%;
        overflow-y: auto;
    }

    /* ESPACEUR : le wrapper a quitté le flux, on réserve sa hauteur. Sans lui, le contenu
       remonterait d'un coup à l'ouverture de la liste — ce qui fait douter un utilisateur de
       ce qu'il vient de toucher. */
    .cp-spacer,
    .pp-spacer {
        height: 42px;
    }
}

/* Hors mobile, l'espaceur n'existe pas : la feuille est un comportement MOBILE, le desktop garde
   son menu déroulant classique (panneau en position absolue sous le champ). */
@media (min-width: 769px) {
    .cp-spacer,
    .pp-spacer {
        display: none;
    }
}

/* =============================================================================
   T11-b — LES 4 ÉCRANS DE RÈGLEMENT EN COLONNE UNIQUE SOUS 768 px
   -----------------------------------------------------------------------------
   ⚠️ CAUSE RACINE : `.fpro-grid` portait DÉJÀ une règle mobile `1fr`, mais les 4 pages la
   défaisaient par un `style="grid-template-columns: 1fr 1fr 1fr"` INLINE — et l'inline bat toute
   règle de media query. Résultat sur 400 px : 3 colonnes écrasées, texte cassé mot par mot
   (« encaissemen », « — chois »), et le bloc « Chèque / Traite » (colonne 3) démarrant EN HAUT
   alors que le mode de règlement (bas de colonne 1) était 350 px plus bas — l'ordre du geste
   était inversé. Mesuré avant correctif : chèque à y=85, mode à y=452.

   ⚠️ SPÉCIFICITÉ CHOISIE `.fpro-grid.fpro-grid-3` (0,2,0) : elle bat `.fpro-grid` (0,1,0)
   INDÉPENDAMMENT de l'ordre de déclaration. Une classe simple aurait fait égalité et son sort
   aurait dépendu de sa position dans le fichier — fragile au premier réagencement.
   ============================================================================= */
.fpro-grid.fpro-grid-3 {
    grid-template-columns: 1fr 1fr 1fr;
    margin-bottom: 14px;
}

@media (max-width: 900px) {
    .fpro-grid.fpro-grid-3 {
        grid-template-columns: 1fr 1fr;
    }
}

/* Sous 768 px : COLONNE UNIQUE. L'ordre du DOM donne alors le geste réel —
   qui paie (client) → combien (montant) → comment (mode, compte) → chèque/traite s'il y en a un. */
@media (max-width: 768px) {
    .fpro-grid.fpro-grid-3 {
        grid-template-columns: 1fr;
    }
}

/* ---- Pavé explicatif repliable (NoteRepliable) ---- */
.fpro-note-toggle {
    display: none;              /* desktop : le pavé est déployé, le bouton n'a pas lieu d'être */
    width: 100%;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 6px;
    padding: 7px 10px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--slate);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-8);
    cursor: pointer;
}

.fpro-note-chevron {
    font-size: 10px;
    opacity: 0.7;
}

@media (max-width: 768px) {
    .fpro-note-toggle {
        display: flex;
    }

    /* REPLIÉ PAR DÉFAUT : mesuré à 338-431 px de haut pour 400 px d'écran — il enterrait le
       formulaire. Il n'est pas supprimé (il porte une information réelle sur les effets),
       il est rangé derrière un libellé court. */
    .fpro-note-repli .fpro-note:not(.fpro-note-open) {
        display: none;
    }

    .fpro-note-repli .fpro-note {
        margin-top: 6px;
    }
}

/* =============================================================================
   T11-c — GRILLE D'IMPUTATION EN CARTES (MOBILE)
   -----------------------------------------------------------------------------
   ⚠️ Les cartes n'existent QUE sous 768 px : le tableau reste un tableau sur grand écran.
   Symétrique de `crud-desktop-table` (display:none en mobile) — les deux vues ne coexistent
   jamais à l'écran, alors qu'elles partagent les MÊMES objets en mémoire.
   ============================================================================= */
.imp-cartes {
    display: none;
}

@media (max-width: 768px) {
    .imp-cartes {
        display: block;
    }

    /* Le total imputé RESTE VISIBLE pendant la saisie : sur une longue liste, l'utilisateur perd
       de vue combien il a déjà affecté. Collant en tête de la section. */
    .imp-recap {
        position: sticky;
        top: 0;
        z-index: 5;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 8px 12px;
        margin-bottom: 10px;
        font-size: 12.5px;
        color: var(--slate);
        background: var(--paper);
        border: 1px solid var(--line);
        border-radius: var(--r-8);
    }

    .imp-recap strong {
        font-family: var(--num-font);
        font-variant-numeric: tabular-nums;
        color: var(--ink);
    }

    /* Σ > montant : la validation le refusera. On le dit PENDANT la saisie, pas au refus. */
    .imp-recap-alerte {
        color: var(--red) !important;
    }

    .imp-carte {
        padding: 12px;
        margin-bottom: 10px;
        background: var(--surface);
        border: 1px solid var(--line);
        border-radius: var(--r-12);
    }

    .imp-carte-tete {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 8px;
        margin-bottom: 8px;
    }

    .imp-piece {
        font-family: 'Manrope', sans-serif;
        font-weight: 700;
        font-size: 14px;
        color: var(--ink);
    }

    .imp-date {
        font-size: 12px;
        color: var(--slate);
    }

    .imp-carte-kv {
        display: grid;
        grid-template-columns: 1fr auto;
        gap: 3px 10px;
        font-size: 12.5px;
        color: var(--slate);
    }

    .imp-carte-kv .num,
    .imp-reste .num,
    .imp-fige {
        font-family: var(--num-font);
        font-variant-numeric: tabular-nums;
    }

    /* ⚠️ LE RESTE est le chiffre que l'utilisateur regarde AVANT de taper : détaché du reste des
       valeurs, en accent, jamais noyé dans la ligne. */
    .imp-reste {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        margin: 8px 0;
        padding: 7px 10px;
        font-size: 12.5px;
        color: var(--teal-dark);
        background: var(--teal-soft);
        border-radius: var(--r-8);
    }

    .imp-reste strong {
        font-size: 15px;
        color: var(--teal-dark);
    }

    .imp-saisie {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
    }

    .imp-saisie label {
        font-size: 12.5px;
        font-weight: 600;
        color: var(--ink);
        margin: 0;
    }

    .imp-saisie input {
        width: 140px;
        text-align: right;
    }

    .imp-vide {
        padding: 18px 12px;
        font-size: 13px;
        text-align: center;
        color: var(--slate);
    }
}

/* =====================================================================================
   T15 — SECTEUR & POSITION DU CLIENT (préfixe `cli-`, borné à ces classes)
   Aucune classe existante n'est retouchée : tout est neuf et nommé, donc aucun autre écran
   ne peut être affecté. Couleurs = tokens :root UNIQUEMENT (jamais un hex en dur).
   ===================================================================================== */
.cli-position {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--line);
}

.cli-position-etat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 7px 9px;
    border-radius: var(--r-8);
    background: var(--paper);
    border: 1px solid var(--line);
    font-size: 12.5px;
}

.cli-position-vide {
    color: var(--slate);
    font-style: italic;
}

.cli-position-coords {
    font-weight: 600;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

.cli-position-meta {
    color: var(--slate);
    font-size: 11.5px;
}

/* ⚠️ Une position approximative (repli sur adresse IP, ~2 km) s'affiche avec les mêmes
   6 décimales qu'un point GPS. C'est le seul signal qui les distingue à l'écran. */
.cli-position-approx {
    margin-top: 3px;
    color: var(--amber);
    font-weight: 600;
    font-size: 11.5px;
}

.cli-position-actions {
    display: flex;
    gap: 8px;
    margin-top: 8px;
    flex-wrap: wrap;
}

.cli-position-msg {
    margin-top: 7px;
    padding: 6px 9px;
    border-radius: var(--r-7);
    font-size: 12px;
}

/* Un refus de géolocalisation est un cas NOMINAL : ton ambre (avertissement), jamais rouge
   (erreur). L'utilisateur a le droit de dire non. */
.cli-position-msg.ko {
    background: var(--amber-soft);
    color: var(--amber-dark);
}

.cli-position-msg.ok {
    background: var(--teal-soft);
    color: var(--teal-dark);
}

.cli-position-hint {
    margin-top: 6px;
    font-size: 11px;
    color: var(--slate);
}

/* Marque du client SANS position : il apparaît TOUJOURS dans la liste, et son absence de
   position se LIT au lieu de se deviner. */
.cli-sans-position {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--amber-soft);
    color: var(--amber-dark);
    font-size: 10.5px;
    font-weight: 600;
    white-space: nowrap;
}

@media (max-width: 768px) {
    /* En cartes, la marque prend sa propre ligne : collée à droite d'une valeur elle
       débordait de la carte sur les écrans de 390 px. */
    .crud-card-meta-value.cli-sans-position {
        margin-left: 0;
    }

    .cli-position-actions button {
        flex: 1 1 auto;
    }
}

/* =====================================================================================
   T9-d2 — SELECTEUR DE VARIANTE sur les lignes de document (prefixe `doc-variante-`).
   Le composant ne rend RIEN quand le produit n'a pas de variante selectionnable : ces
   regles ne s'appliquent donc jamais aux articles ordinaires.
   ===================================================================================== */
.doc-variante-select {
    margin-top: 3px;
    max-width: 190px;
    font-size: 12px;
}

/* RT4-a — DISPONIBLE DE LA VARIANTE, à côté du sélecteur : c'est le seul endroit de l'écran où
   la variante est enfin CONNUE. L'indicateur de la rangée d'ajout, lui, porte le disponible de
   l'ARTICLE toutes variantes confondues — les deux chiffres sont justes et différents, chacun
   dit lequel il est. Teal quand il reste du stock, rouge à zéro ou moins. */
.doc-variante-dispo {
    display: inline-block;
    margin-top: 2px;
    margin-left: 4px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--teal-soft);
    color: var(--teal-dark);
    font-size: 10.5px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.doc-variante-dispo.epuise {
    background: var(--red-soft);
    color: var(--red);
}

/* Signal DOUX pendant la saisie : la validation refusera, autant le dire avant. */
.doc-variante-manque {
    display: inline-block;
    margin-top: 2px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--amber-soft);
    color: var(--amber-dark);
    font-size: 10.5px;
    font-weight: 600;
}

/* Stock ventile par variante, dans la fiche article. */
.var-stock-table {
    width: 100%;
    font-size: 12.5px;
    border-collapse: collapse;
}

.var-stock-table th {
    text-align: left;
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--slate);
    padding: 4px 6px;
    border-bottom: 1px solid var(--line);
}

.var-stock-table td {
    padding: 4px 6px;
    border-bottom: 1px solid var(--line);
}

.var-stock-total {
    font-weight: 700;
}

/* ⚠️ L'ECART n'est pas une erreur d'affichage a corriger : il DIT que la ventilation ne
   totalise pas le stock du produit (stock pose sans document, ou ligne sans variante). */
.var-stock-ecart {
    color: var(--red);
    font-weight: 600;
}

.var-stock-ok {
    color: var(--teal-dark);
    font-weight: 600;
}

/* T9-c1 — badge du commercial SYSTEME (« Direct »). Il DIT pourquoi cette ligne se comporte
   autrement, au lieu de laisser l'utilisateur se cogner aux refus. */
.com-badge-systeme {
    display: inline-block;
    margin-left: 7px;
    padding: 1px 7px;
    border-radius: 999px;
    /* ⚠️ L1c — CE FOND ÉTAIT `var(--slate-soft, var(--paper))`, ET --slate-soft
       N'EST DÉCLARÉ NULLE PART (0 dans le :root, mesuré). Le repli était donc ce
       qui s'appliquait DÉJÀ : écrire --paper est un no-op PAR CONSTRUCTION, pas
       un changement de rendu.
       ⭐⭐ POURQUOI NETTOYER L'USAGE PLUTÔT QUE DÉCLARER LE TOKEN — deux motifs,
       et le second est décisif :
         ① déclarer --slate-soft inventerait une valeur que personne n'a décidée,
           et changerait EN SILENCE le rendu de ce badge ;
         ② un token NON DÉCLARÉ employé avec un repli est INVISIBLE pour la garde
           G2 dérivée : elle extrait « slate-soft », ne le trouve pas dans le
           :root, et SAUTE le couple. Ce badge échappait donc au contrôle de
           contraste alors qu'il rendait 3,96. Le nettoyer le rend mesurable. */
    background: var(--paper);
    border: 1px solid var(--line);
    color: var(--slate);
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
}

/* Selecteur de commercial dans l'en-tete des documents. */
.doc-commercial-select {
    max-width: 220px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   CAT — entrée du CATALOGUE DE TOURNÉE depuis la liste clients.
   ⚠️⚠️ CLASSE À PART, ET C'EST LE POINT : partager `icon-btn-edit` avec le bouton
   Modifier faisait résoudre DEUX éléments aux parcours qui désignent « le bouton
   Modifier de cette ligne » (strict mode violation, t69/t72 tombés — mesuré).
   Ajout PUREMENT ADDITIF : aucune règle existante n'est touchée.
   ───────────────────────────────────────────────────────────────────────────── */
.icon-btn-cat {
    color: var(--teal);
}

    .icon-btn-cat:hover {
        background: var(--teal-soft);
        border-color: var(--teal);
    }

.crud-card-btn-cat {
    color: var(--teal);
}

    .crud-card-btn-cat:hover {
        background: var(--teal-soft);
    }

/* ⚠️⚠️ DÉFAUT MESURÉ AU SMOKE : sans cette règle, le bandeau résumé et l'entonnoir
   s'affichaient AUSSI en desktop (une classe définie uniquement dans un media query
   garde le rendu PAR DÉFAUT au-dessus du seuil — un div reste un div). Le desktop de
   cette liste ne doit pas bouger d'un pixel : on les masque EXPLICITEMENT. */
.clist-summary,
.clist-tool { display: none; }

/* ============================================================================
   T-UI — LISTE CLIENTS DENSE (mobile). Maquette : docs/maquettes/maquette_clients_mobile.html

   ⚠️⚠️ TOUT EST DANS @media (max-width: 768px) : au-dessus, PAS UN PIXEL ne change.
   Le desktop de cette liste sert l'administration au bureau et n'est pas concerné.
   ⚠️ Préfixe `clist-` DISTINCT de `doclist-` : ce sont deux gabarits différents, et une
   classe partagée coupleraient deux écrans (leçon icon-btn-edit).
   ⚠️ CHIFFRES : Inter + font-variant-numeric tabular-nums — le token du dépôt, et la
   maquette dit la même chose (IBM Plex Mono a été retiré de l'import Google Fonts).
   ============================================================================ */
@media (max-width: 768px) {

    /* ---------- Bandeau résumé (38px) ---------- */
    .clist-summary {
        display: flex;
        align-items: center;
        background: var(--surface);
        border: 1px solid var(--line);
        border-radius: var(--r-12);
        padding: 0 12px;
        height: 38px;
        margin: 0 0 10px;
        font-size: 12.5px;
    }

    .clist-cell {
        display: flex;
        align-items: baseline;
        gap: 6px;
        flex: 1;
        min-width: 0;
    }

        .clist-cell + .clist-cell {
            border-left: 1px solid var(--line);
            padding-left: 12px;
        }

    .clist-lbl {
        color: var(--slate);
        font-size: 11px;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: .4px;
    }

    .clist-val {
        font-size: 14px;
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }

    /* SENS ⑧ — KPI « Créances » : un reste à encaisser. Motif au bloc `.doclist-kpi--reste`. */
    .clist-dette { color: var(--amber-dark); }

    .clist-unit2 {
        font-size: 10.5px;
        font-weight: 500;
        color: var(--slate);
        white-space: nowrap;
    }

    /* ---------- Rangée dense (~70px) ----------
       ⚠️ La densité est le POINT de la maquette : 9 clients visibles d'un coup en 390×844,
       contre 3 auparavant. Un représentant qui cherche son prochain client ne doit pas
       défiler trois écrans. */
    .clist-row {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 9px 62px 9px 10px;   /* place à droite pour ✎ + ⋮ */
        margin-bottom: 8px;
        position: relative;
        cursor: pointer;
        border-radius: var(--r-14);
        box-shadow: 0 1px 2px rgba(20, 23, 31, .04);
        transition: transform .12s ease, box-shadow .12s ease;
    }

        .clist-row:active {
            transform: scale(.988);
            box-shadow: 0 0 0 2px var(--teal-soft), 0 1px 2px rgba(20, 23, 31, .04);
        }

    @media (prefers-reduced-motion: reduce) {
        .clist-row, .clist-row:active { transition: none; transform: none; }
    }

    .clist-ava {
        width: 38px;
        height: 38px;
        flex: none;
        border-radius: var(--r-12);
        display: grid;
        place-items: center;
        font-family: Manrope, sans-serif;
        font-size: 13px;
        font-weight: 700;
        letter-spacing: .3px;
    }

    .clist-main { flex: 1; min-width: 0; }

    .clist-name {
        font-size: 14.5px;
        font-weight: 600;
        line-height: 19px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        color: var(--ink);
    }

    /* ⚠️ Le badge « Actif » a DISPARU : il était sur 9 lignes sur 10 et n'apprenait donc rien.
       Seul l'INACTIF est marqué — un point gris et un nom en slate. L'information utile est
       l'exception, pas la règle. */
    .clist-off .clist-name { color: var(--slate); }

    .clist-dot-off {
        width: 7px;
        height: 7px;
        border-radius: 50%;
        background: #C3C9D2;
        display: inline-block;
        margin-right: 5px;
        vertical-align: middle;
    }

    .clist-meta {
        font-size: 12px;
        color: var(--slate);
        line-height: 17px;
        margin-top: 1px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .clist-sep { opacity: .45; padding: 0 5px; }
    .clist-empty { font-style: italic; color: #B7BEC9; }

    .clist-dist { color: var(--teal-dark); font-weight: 600; }
    /* Au-delà de 50 km, la distance n'aide plus à choisir qui visiter : elle passe au gris
       pour ne plus attirer l'œil, sans disparaître (l'information reste vraie). */
    .clist-far { color: var(--slate); font-weight: 500; }
    .clist-unknown { color: #B7BEC9; font-style: italic; font-weight: 400; }

    .clist-right {
        text-align: right;
        flex: none;
        padding-left: 4px;
    }

    .clist-solde {
        font-size: 14.5px;
        font-weight: 600;
        font-variant-numeric: tabular-nums;
        line-height: 19px;
    }

    /* SENS ⑧ — le solde d'un client (SoldeAffiche(c) > 0m) : un reste à encaisser, pas un
       impayé. Motif au bloc `.doclist-kpi--reste`. */
    .clist-due { color: var(--amber-dark); }
    .clist-zero { color: var(--slate); font-weight: 500; }

    .clist-unit {
        font-size: 10px;
        font-weight: 500;
        color: var(--slate);
        margin-left: 2px;
    }

    .clist-code {
        font-size: 10.5px;
        font-weight: 600;
        color: var(--slate);
        letter-spacing: .5px;
        line-height: 15px;
        font-variant-numeric: tabular-nums;
    }

    /* ---------- Actions de rangée ----------
       ⚠️ Le ✎ est une DÉVIATION ASSUMÉE de la maquette (qui n'a qu'un kebab) : t72 ouvre la
       fiche client par `.crud-card-btn-edit` pour y capturer la position. Le supprimer aurait
       obligé à réécrire une assertion existante — interdit par la consigne. */
    .clist-edit,
    .clist-kebab {
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        display: grid;
        place-items: center;
        border: 0;
        background: transparent;
        cursor: pointer;
        padding: 0;
    }

    .clist-edit {
        right: 30px;
        width: 30px;
        height: 44px;
        color: var(--slate);
    }

    .clist-kebab {
        right: 2px;
        width: 30px;
        height: 46px;
        color: #C3C9D2;
    }

        .clist-edit:active { color: var(--teal-dark); }
        .clist-kebab:active { color: var(--ink); }

    /* ---------- Feuille d'actions (bottom sheet) ---------- */
    .clist-scrim {
        position: fixed;
        inset: 0;
        background: rgba(20, 23, 31, .42);
        z-index: 1200;
    }

    .clist-sheet {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 1201;
        background: var(--surface);
        border-radius: 20px 20px 0 0;
        padding: 8px 0 calc(14px + env(safe-area-inset-bottom));
        box-shadow: 0 -8px 30px rgba(20, 23, 31, .22);
        max-height: 86vh;
        overflow-y: auto;
    }

    .clist-grip {
        width: 38px;
        height: 4px;
        border-radius: 2px;
        background: var(--line);
        margin: 4px auto 10px;
    }

    .clist-sheet h3 {
        font-family: Manrope, sans-serif;
        font-size: 15px;
        font-weight: 700;
        margin: 0 16px 4px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .clist-sheet-sub {
        font-size: 12px;
        color: var(--slate);
        margin: 0 16px 12px;
    }

    .clist-item {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 12px 16px;
        font-size: 14.5px;
        font-weight: 500;
        color: var(--ink);
        cursor: pointer;
        text-decoration: none;
    }

        .clist-item:active { background: var(--paper); }

    .clist-ico {
        width: 32px;
        height: 32px;
        border-radius: var(--r-10);
        display: grid;
        place-items: center;
        background: var(--paper);
        font-size: 15px;
        flex: none;
    }

    .clist-item-note {
        color: var(--teal-dark);
        font-size: 12px;
        font-weight: 600;
        margin-left: 2px;
    }

    .clist-danger { color: var(--red); }
}

@media (max-width: 768px) {
    /* ⚠️⚠️ BUDGET VERTICAL — MESURÉ, PAS ESTIMÉ. Avant : 573 px perdus au-dessus de la
       première rangée sur 844, soit 4 clients visibles. Les coupables étaient les KPIs
       (87 px) et la rangée de filtres (139 px) — la version DESKTOP, que la maquette
       remplace par le bandeau 38 px et l'entonnoir. Masqués ici, ils rendent 226 px,
       et la barre d'actions passe sur une ligne.
       ⚠️⚠️ L5 : CE N'EST PLUS UN MASQUAGE, C'EST UN REPLI — et la nuance est le point de
       la tranche. Masqués, ces deux blocs rendaient LE FILTRAGE IMPOSSIBLE sur téléphone :
       ni client, ni entrepôt, ni statut, ni facturation, sur les 12 listes. Le gain de
       place était juste ; ce qui manquait était le CHEMIN DE RETOUR. Repliés PAR DÉFAUT,
       ils coûtent les 42 px de la barre et rendent le geste possible. */
    .doclist-kpis,
    .doclist-filters { display: none !important; }
    .doclist-kpis.deplie { display: grid !important; }
    .doclist-filters.deplie { display: flex !important; }
    /* ⚠️ L7 — LES 45 ÉTATS ENTRENT DANS LA MÊME MÉCANIQUE, avec LEUR vocabulaire.
       `.etat-tuiles` n'était pas masqué (elle n'est pas `.doclist-kpis`) : elle coûtait
       jusqu'à 579 px en 390 px, mesuré, soit 11 lignes de tableau. Le composant est le
       MÊME — `BandeauIndicateurs` reçoit sa classe en paramètre — donc l'attribut `deplie`
       est posé au même endroit et par le même code. ⚠️ `display: flex` et non `grid` :
       c'est `.doclist .etat-tuiles` qui gagne au bureau, on rend l'état DÉPLIÉ identique à
       ce qu'il est au-dessus de 768 px, jamais un troisième rendu. */
    .etat-tuiles { display: none !important; }
    .etat-tuiles.deplie { display: flex !important; }
    /* ⚠️ La rangée dépliée EMPILE ses pastilles plutôt que de défiler horizontalement :
       la maquette la fait défiler (`overflow-x:auto`), ce qui CACHE les filtres hors
       cadre — or c'est exactement ce que la tranche corrige. Empilée, aucune pastille
       n'est hors d'atteinte, et la pastille prend toute la largeur. */
    .doclist-filters.deplie { flex-direction: column; align-items: stretch; }
    .doclist-fpill { width: 100%; }
    .doclist-filters .form-select { width: 100%; padding-left: 31px; min-width: 0; }
    .doclist-collapse { display: flex; gap: 8px; margin-bottom: 10px; }

    /* ⚠️⚠️ PORTÉE PAR :has(), ET C'EST LA MÊME LEÇON QU'AU-DESSUS, PAYÉE DEUX FOIS.
       `.crud-toolbar-actions` est le conteneur d'actions de TOUS les écrans (PageHeader).
       Un premier essai posait `flex-wrap: nowrap` dessus sans portée : sur l'écran Commissions,
       dont les actions contiennent deux champs date + un bouton, le « nowrap » les a forcés sur
       une ligne et le champ « au » RECOUVRAIT le bouton « Afficher » — t75 est tombé en timeout
       de 30 s, sur un écran que cette tranche ne touche même pas.
       ⭐ Un conteneur partagé ne se style QUE sous une condition qui le rattache à SON écran. */
    .crud-toolbar-actions:has([data-testid="cli-autour"]) {
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: 8px;
        flex-wrap: nowrap;
    }

        .crud-toolbar-actions:has([data-testid="cli-autour"]) .crud-search { flex: 1 1 auto; min-width: 0; height: 38px; }

        /* « Autour de moi » : carré de 38 px, comme la maquette.
           ⚠️⚠️ PORTÉE STRICTE PAR data-testid, ET C'EST DÉLIBÉRÉ : `.crud-toolbar-actions` est
           le conteneur d'actions de TOUS les écrans (PageHeader). Styler `.btn-filtres` ici
           aurait transformé en carré de 38 px le bouton « Filtres » de chaque liste du dépôt —
           exactement le débordement que la leçon `icon-btn-edit` interdit.
           ⚠️ Le libellé est masqué par font-size:0 + une icône en ::before, jamais par
           `display:none` : t72 exige que le bouton reste VISIBLE et cliquable à 390×400.
           ⚠️ Un premier essai en `text-indent` a rendu un bouton VIDE (l'emoji partait avec le
           texte) — mesuré sur capture, corrigé ici. */
        .crud-toolbar-actions [data-testid="cli-autour"] {
            flex: 0 0 auto;
            width: 38px;
            height: 38px;
            padding: 0;
            overflow: hidden;
            white-space: nowrap;
            display: grid;
            place-items: center;
        }

            /* Seul le LIBELLÉ est masqué ; l'icône SVG du markup reste. */
            .crud-toolbar-actions [data-testid="cli-autour"] .cli-autour-lbl { display: none; }

    /* La rangée dense n'a pas le liseré d'accent de `.crud-card` : la maquette pose une
       carte neutre, l'information de couleur vit dans la pastille d'initiales. */
    .clist-row::before { display: none !important; }

    .clist-tool {
        display: grid;
        place-items: center;
        position: relative;
        flex: 0 0 auto;
        width: 38px;
        height: 38px;
        border: 1px solid var(--line);
        border-radius: var(--r-11);
        background: var(--surface);
        color: var(--slate);
        cursor: pointer;
    }

    .clist-tool-count {
        position: absolute;
        top: -5px;
        right: -5px;
        min-width: 17px;
        height: 17px;
        border-radius: var(--r-9);
        background: var(--teal);
        color: #fff;
        font-size: 10px;
        font-weight: 700;
        display: grid;
        place-items: center;
        border: 2px solid var(--surface);
    }

    .clist-summary { display: flex; }

    /* ---------- Feuille de filtres ---------- */
    .clist-fgroup { padding: 2px 16px 12px; }

    .clist-flbl {
        font-size: 11px;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: .5px;
        color: var(--slate);
        margin-bottom: 8px;
    }

    .clist-fopts { display: flex; gap: 6px; flex-wrap: wrap; }

    .clist-opt {
        height: 32px;
        padding: 0 12px;
        border-radius: var(--r-16);
        border: 1px solid var(--line);
        background: var(--surface);
        font-size: 12.5px;
        font-weight: 600;
        color: var(--slate);
        cursor: pointer;
    }

    .clist-opt-on {
        background: var(--teal-soft);
        border-color: var(--teal);
        color: var(--teal-dark);
    }

    .clist-factions { display: flex; gap: 8px; padding: 6px 16px 0; }

    .clist-fbtn {
        flex: 1;
        height: 44px;
        border-radius: var(--r-12);
        border: 1px solid var(--line);
        background: var(--surface);
        font-size: 14px;
        font-weight: 600;
        cursor: pointer;
    }

    .clist-fbtn-primary {
        background: var(--teal);
        border-color: var(--teal);
        color: #fff;
        font-weight: 700;
    }
}

/* ============================================================================
   REMBOURSEMENTS DANS LES LISTES DE REGLEMENTS — signe, couleur, largeur.
   ⚠️⚠️ BORNE A `.reg-liste`, ET C'EST LA LECON DE T-UI, PAYEE DEUX FOIS : styler un
   conteneur PARTAGE (`.btn-filtres`, puis `.crud-toolbar-actions`) a casse des ecrans
   que la tranche ne touchait meme pas. `.crud-num` est utilise par 64 fichiers du
   depot (mesure) : on ne lui ajoute RIEN. Un prefixe protege les CLASSES, pas les
   BALISES -- aucune regle ici ne cible `td`, `span` ni `table`.
   ⚠️ ORANGE ET NON ROUGE : dans ces memes listes le ROUGE signale deja un IMPAYE.
   Un impaye est un PROBLEME, un remboursement est un FAIT NORMAL. Deux signaux
   distincts pour deux natures distinctes -- les confondre reviendrait a alerter sur
   un geste de gestion ordinaire, et l'alerte cesserait d'etre lue.
   ============================================================================ */
.reg-liste .reg-remb { color: var(--amber); font-weight: 600; }

/* ⚠️ LA LARGEUR : le signe « − » ajoute un caractere. La table est en `table-layout`
   AUTO (mesure : aucune declaration dans tout app.css), donc la colonne s'elargit
   d'elle-meme -- MAIS seulement si rien ne la contraint. `nowrap` garantit qu'un
   montant ne se COUPE JAMAIS en deux lignes, et `min-width` reserve la place du
   plus long montant realiste (« −1 234 567,89 DH ») pour que la colonne ne DANSE
   pas d'une page a l'autre selon les montants presents.
   ⭐ Un montant tronque sur un ecran d'argent est pire qu'un montant sans signe. */
.reg-liste .reg-montant { white-space: nowrap; min-width: 9.5rem; }

/* Le SOLDE du pied. ⚠️ Il n'est PAS la somme des lignes visibles, et il le DIT
   (sous-titre) : sans cela quelqu'un chercherait pourquoi l'addition ne tombe pas,
   et il aurait raison de chercher. */
.reg-liste .reg-solde { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.reg-liste .reg-solde-val { font-size: 15px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.reg-liste .reg-solde-lbl { font-size: 11px; color: var(--slate); text-transform: uppercase; letter-spacing: .04em; }
.reg-liste .reg-solde-note { font-size: 11px; color: var(--slate); font-style: italic; }
.reg-liste .reg-solde-val.reg-neg { color: var(--amber); }

/* ============================================================================
   T-RGL — LES 4 CHAMPS D'INTERVALLE DES LISTES DE REGLEMENTS (montants, dates)

   ⚠️⚠️ ILS NE SONT PAS DES PASTILLES DE `FiltresListe`, ET C'EST UN ARBITRAGE.
   Le composant partage ne porte que des `<select>` ; etendre son record `Filtre`
   d'un genre « intervalle » toucherait le point d'ecriture UNIQUE de 13 ecrans
   pour un besoin qui n'existe que sur 4. Le patron retenu est celui des ETATS,
   dont les dates vivent HORS du composant (`.etat-filtre-dates`).

   ⚠️ CE BLOC NE TOUCHE PAS `.filter-row` — la regle d'origine, partagee par 10
   ecrans dont 6 hors chantier (Produits, Fournisseurs, Salaries, Bulletins,
   AvancesCredits, Projets). Les 4 listes en SORTENT ; elle ne bouge pas.

   ⭐ LA FORME EST CELLE DE LA PASTILLE, ET ELLE N'EST PAS RECOPIEE : le markup
   porte `doclist-fpill`, donc le fond, la bordure, le rayon, le survol et le
   halo de focus viennent des regles de L5. Ce bloc ne pose QUE ce que la
   pastille ne sait pas faire : accueillir un `<input>` a la place d'un `<select>`.
   ============================================================================ */
.reg-champs { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.doclist > .reg-champs { margin-bottom: 12px; }

/* ⚠️ `overflow: hidden` pour que l'input ne deborde pas des coins arrondis de la
   pastille — meme geste que `.etat-fpill-date`. */
.reg-fpill-champ { overflow: hidden; }

.reg-fpill-champ input {
    appearance: none; -webkit-appearance: none; -moz-appearance: none;
    background: transparent; border: 0; border-radius: var(--r-9);
    /* 31 = 10 (bord → icone) + 13 (icone) + 8 (ecart), la MEME arithmetique que
       `.doclist-filters .form-select` — sinon le texte du champ et celui de la
       pastille voisine ne seraient pas sur la meme verticale. */
    padding: 7px 12px 7px 31px;
    font-size: 12.5px; font-weight: 500; color: var(--ink-soft);
    min-width: 132px; height: 36px;
}

.reg-fpill-champ input:focus { outline: none; }

/* ⚠️ Le champ nombre garde ses fleches natives au survol sinon : elles chevauchent
   le rembourrage droit et le champ cesse d'etre aligne sur ses voisins. */
.reg-fpill-champ input[type="number"]::-webkit-outer-spin-button,
.reg-fpill-champ input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.reg-fpill-champ input[type="number"] { -moz-appearance: textfield; }

@media (max-width: 768px) {
    /* ⚠️⚠️ LE MEME REPLI QUE LES PASTILLES, ET C'EST LE DEFAUT QUE LA TRANCHE FERME :
       les 8 filtres empiles occupaient tout l'ecran avant la premiere rangee. Masquer
       ne suffit pas — `BarreReplis` donne LE CHEMIN DE RETOUR, et le badge compte les
       8 filtres (pas les 4 pastilles), sans quoi un montant actif serait invisible.
       ⚠️ Les dates des ETATS, elles, restent visibles : c'est leur filtre le plus
       employe. Ici le montant et la date sont des filtres d'appoint — mesure faite,
       la difference est assumee, elle n'est pas une incoherence. */
    .reg-champs { display: none; }
    .reg-champs.deplie { display: flex; flex-direction: column; align-items: stretch; }
    .reg-fpill-champ { width: 100%; }
    .reg-fpill-champ input { width: 100%; min-width: 0; }

    /* ⚠️ T-RGL-2 — LA CASE SUIT LES CHAMPS : sans cette ligne elle resterait a sa largeur
       de contenu au milieu d'une colonne etiree, et la rangee depliee aurait un element
       qui ne s'aligne sur aucun autre. Elle ne pose AUCUNE forme neuve : le fond, la
       bordure et le rayon viennent de `.doclist-fcheck`, partages avec les etats. */
    .reg-champs.deplie .doclist-fcheck { width: 100%; justify-content: flex-start; }

    /* ⚠️⚠️ LE SOLDE DEVIENT ATTEIGNABLE AU DOIGT (il portait `crud-hide-mobile`, donc
       le SEUL chiffre d'ensemble de ces 4 ecrans etait invisible sur telephone). Le
       tableau etant masque, le bloc se place AU-DESSUS des cartes : il s'aligne donc
       a GAUCHE, comme un en-tete, et non a droite comme un pied de tableau. */
    .reg-liste .reg-solde { align-items: flex-start; margin-bottom: 10px; }
    .reg-liste .reg-solde-note { line-height: 1.35; }
}


/* ============================================================================
   ⚠️⚠️ CETTE OUVERTURE DE COMMENTAIRE MANQUAIT (rétablie le 2026-08-23), ET LE PARSEUR
   AVALAIT LA RÈGLE SUIVANTE. Depuis le commit `44c70b6`, ces trois lignes commençaient par du
   TEXTE NU : le parseur les lisait comme un sélecteur malformé, jetait tout jusqu'à la fin du
   bloc suivant, et `.doclist { display: flex; gap: 12px }` n'a JAMAIS été appliquée.
   ⭐ Mesuré au moteur, pas relu : `CSS.getMatchedStylesForNode` sur un `.doclist` ne renvoyait
   que le `div { display: block }` du navigateur. _Une règle jetée ne se signale nulle part._
   ============================================================================
   UNIFICATION — Les états héritent du gabarit visuel doclist / crud
   ============================================================================ */

/* Conteneur principal */
.doclist {
  display: flex;
  flex-direction: column;
  /* ⚠️⚠️ PAS DE `gap` ICI (retiré en L9a-③, arbitrage de Walid : la densité est la priorité du
     chantier, et 12 px entre trois blocs se remarquent moins qu'une rangée perdue).
     ⚠️ ET 12 px D'UN RYTHME NE SE RETIRENT PAS COMME 12 px D'UNE MARGE : un `gap` sépare TOUTES
     les frontières. Le retirer sec collerait la rangée de filtres au tableau. La marge lui est
     donc RENDUE, et l'espacement redevient ce qu'il était AVANT l'unification : porté par les
     ENFANTS (bandeau 10 px, filtres 12 px), jamais par le conteneur.
     ⭐ Réversible en une valeur, et l'alignement INTERNE des filtres ne bouge pas. */
  background: transparent;
}
/* ===== TABLEAU ===== */
.doclist .etat-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 0;
  background: transparent;
}

.doclist .etat-table thead th {
  background: var(--thead-bg);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--slate);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
  border-top: none;
  vertical-align: middle;
}

.doclist .etat-table tbody td {
  vertical-align: middle;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}

.doclist .etat-table tbody tr:hover td {
  background: var(--thead-bg);
}

/* Sous-total (rupture) */
.doclist .etat-table tbody tr.etat-soustotal td {
  background: var(--row-soft) !important;
  font-weight: 700;
}

/* Ligne de total */
.doclist .etat-table tfoot .etat-total td {
  border-top: 2px solid var(--line);
  background: var(--row-soft);
  font-weight: 600;
}

/* Numériques alignés à droite + mono */
.doclist .etat-table .crud-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.doclist .etat-table .cell-mono {
  font-family: "SF Mono", Monaco, monospace;
}

/* Liens cliquables (colonnes Piece) */
.doclist .etat-lien {
  color: var(--teal);
  text-decoration: none;
  font-weight: 600;
}
.doclist .etat-lien:hover {
  text-decoration: underline;
}

/* Alertes du catalogue — les DEUX tons prennent le même poids dans les listes.
   ⚠️ AUCUNE COULEUR ICI : elle vient de `.etat-alerte` / `.etat-favorable`, une seule
   fois. Redonner la couleur ici en ferait un second point de vérité. */
.doclist .etat-alerte {
  font-weight: 600;
}
.doclist .etat-favorable {
  font-weight: 600;
}

/* ===== FILTRES ===== */
.doclist .etat-filtres {
  /* ⚠️ PAS d'`align-items` ICI. La carte aligne ses enfants sur leur CENTRE, et c'est décidé
     UNE SEULE FOIS, au bloc `.doclist .etat-filtres` le plus bas de ce fichier. Cette règle
     portait `flex-end` : une valeur MORTE, écrasée deux fois plus bas, qui faisait lire au
     fichier le contraire de ce que l'écran rend. */
  border-bottom: none;
}

/* ⚠️⚠️ TROIS BLOCS ONT ÉTÉ RETIRÉS ICI (2026-08-23), ET LE MOTIF EST UNE MESURE DE CASCADE.
   Ils habillaient l'ANCIEN markup des filtres d'états (`<div><label>…<select class="form-select-sm">`),
   supprimé avec le bloc mort d'`EtatViewer`. Restés en place, ils ÉCRASAIENT le composant partagé :
     · `.doclist .etat-filtres > div { flex-direction: column }` — spécificité (0,2,0) contre
       (0,1,0) pour `.doclist-filters` : la RANGÉE de pastilles devenait une COLONNE, elles
       s'empilaient. Mesuré au navigateur avant correction.
     · `.doclist .etat-filtres select { padding: 0 12px }` puis `{ min-width: 160px; height: 38px }`
       puis `{ height: 34px; min-width: 130px }` — spécificité (0,2,1) contre (0,2,0) pour
       `.doclist-filters .form-select` : l'écart icône/texte de 31 px ne s'appliquait PAS.
   ⭐ AUCUN 11ᵉ CORRECTIF N'A ÉTÉ AJOUTÉ : on retire ce qui écrase. Les enfants réels de la carte
   (`.etat-fpill-date`, `.etat-fpill-number`, `.doclist-fcheck`, `.etat-balance-radios`) portent
   chacun leurs propres règles, plus spécifiques — ils ne dépendaient pas de ces trois blocs. */


/* Radios solde balance */
.doclist .etat-balance-radios {
  font-size: 12.5px;
  color: var(--ink-soft);
}
.doclist .etat-balance-radios label {
  text-transform: none;
  letter-spacing: normal;
}

/* Checkboxes */
.doclist .etat-filtres input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--teal);
  cursor: pointer;
}

/* ===== TUILES (bandeau KPI) ===== */
.doclist .etat-tuiles {
  display: flex;
  flex-wrap: wrap;
}

.doclist .etat-tuile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
}

.doclist .etat-tuile.grise {
  opacity: 0.55;
}


.doclist .etat-tuile-val {
  font-weight: 700;
  color: var(--ink);
  /* ⚠️ PAS Fraunces : une valeur de tuile est un NOMBRE, et la famille n'a PAS l'axe
     OpenType `tnum` — les chiffres n'y sont pas de chasse fixe, une colonne de montants
     danserait. C'est le token des chiffres, celui que `.doclist-kpi-val` emploie déjà. */
  font-family: var(--num-font);
}

.doclist .etat-tuile-note {
  font-size: 11px;
  color: var(--slate);
}

/* ===== BLOCS (pied enrichi) ===== */
.doclist .etat-blocs {
  display: flex;
  gap: 10px;
  padding: 12px 20px;
  flex-wrap: wrap;
  border-top: 1px solid var(--line);
  background: var(--surface);
}

.doclist .etat-bloc {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: var(--r-9);
  font-size: 12.5px;
  font-weight: 600;
  background: var(--thead-bg);
  color: var(--ink-soft);
}

.doclist .etat-bloc-val {
  font-weight: 700;
  color: var(--ink);
}

/* ===== PIED DE TABLE ===== */
.doclist .table-foot,
.doclist .etat-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: var(--slate);
}

/* ===== NOTES ===== */
.doclist .etat-note-pied {
  padding: 12px 20px;
  font-size: 11px;
  color: var(--slate);
  font-style: italic;
  border-top: 1px solid var(--line);
  background: var(--surface);
  line-height: 1.5;
}

.doclist .etat-interne-note {
  font-size: 11px;
  color: var(--slate);
  font-style: italic;
}

.doclist .etat-report-hint {
  font-size: 10px;
  color: var(--slate);
  margin-top: 2px;
}

/* ===== CHÈQUES / TRAITES ===== */
.doclist .etat-cheques-titre {
  padding: 14px 20px 0;
  font-size: 12.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--slate);
}

.doclist .etat-cheques {
  margin: 8px 20px 14px;
  width: calc(100% - 40px);
  border-collapse: collapse;
}
.doclist .etat-cheques thead th {
  background: var(--thead-bg);
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--slate);
  padding: 8px 18px;
  border-bottom: 1px solid var(--line);
}
.doclist .etat-cheques tbody td {
  padding: 8px 18px;
  font-size: 13.5px;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}

/* ===== BADGES ===== */
.doclist .doc-badge-etat {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: var(--r-6);
  font-size: 11px;
  font-weight: 600;
  background: var(--row-soft);
  color: var(--slate);
  border: 1px solid var(--line);
  vertical-align: middle;
}
/* ⚠️ LA REDÉFINITION DU BADGE « BROUILLON » A ÉTÉ RETIRÉE ICI (L9a). Elle reposait une
   palette ÉTRANGÈRE en dur (#fff3cd / #856404 / #ffeaa7) alors que `.doc-badge-etat--draft`
   est déjà défini plus haut avec `--amber-soft` / `--amber-dark`. ⇒ Retirer, pas tokeniser :
   l'état hérite désormais du MÊME badge que les listes, ce qui est l'objet de l'unification. */

.doclist .etat-partie {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border-radius: var(--r-20);
}

/* ===== SOUS-LIGNES ===== */
.doclist .etat-sous-row td {
  padding: 0;
  border: none;
  background: transparent !important;
}
.doclist .etat-sous {
  padding: 4px 18px;
  font-size: 12px;
  color: var(--slate);
  border-bottom: 1px solid var(--line-soft);
}

/* Échéance dépassée */
.doclist .etat-echeance-depassee {
  color: var(--red-dark);
  font-weight: 600;
}

/* Scroll horizontal du tableau */
.doclist .table-scroll-inner {
  overflow-x: auto;
  width: 100%;
}
/* ============================================================================
   PATCH — Padding + Police des montants + Tuiles KPI (style BL)
   ============================================================================ */

/* ===== CELLULES — plus d'air ===== */
.doclist .etat-table thead th {
  font-size: 10.5px;
}


/* ===== MONTANTS — Fraunces gras comme les BL ===== */
/* ⚠️⚠️ `color: var(--ink)` A ÉTÉ RETIRÉ D'ICI, ET C'ÉTAIT UN DÉFAUT VIVANT DEPUIS `44c70b6`.
   ⭐⭐ CE QU'IL FAISAIT RÉELLEMENT : `.doclist .etat-table td.crud-num` pèse 0-3-1 et
   `.etat-alerte` 0-1-0 — l'utilitaire GAGNAIT. **Toute couleur sémantique d'une cellule
   chiffrée d'état était donc ÉTEINTE** : le rouge du retard, celui du manque de stock, celui
   d'un écart, celui d'une marge négative. Mesuré au style CALCULÉ : `rgb(23, 33, 28)`,
   c'est-à-dire --ink, là où la classe d'alerte était pourtant BIEN POSÉE sur la cellule.
   ⚠️⚠️ ET TROIS GARDES ÉTAIENT VERTES : G1 vérifiait que `.etat-alerte` emploie un token
   ROUGE (vrai), G2-ter que son ratio passe AA (vrai), `TonDesEtatsTests` que la classe est
   bien posée (vrai). **Aucune ne pouvait dire QUI GAGNE** — seul un navigateur le sait, et
   c'est l'assertion de couleur calculée de t124 qui l'a trouvé.
   ⚠️ LE RETRAIT EST UN NO-OP POUR LES CELLULES NORMALES : `html, body` pose déjà
   `color: var(--ink)`, dont les cellules HÉRITENT. La déclaration n'avait donc qu'UN effet
   mesurable — tuer les couleurs sémantiques. ⭐ Un utilitaire d'ALIGNEMENT ne porte pas de
   couleur : G5 (PaletteTests) l'interdit désormais.
   ⚠️ TROISIÈME DÉFAUT ISSU DU MÊME COLLAGE (`44c70b6`) : après la règle `.doclist` jetée
   par un commentaire cassé et la marge morte d'`EtatViewer`. La dette des « blocs PATCH »
   n'est pas cosmétique. */
.doclist .etat-table .crud-num,
.doclist .etat-table td.crud-num {
  font-family: "Manrope";
  font-weight: 700;
  font-size: 14px;
  letter-spacing: -0.01em;
}

/* Total */
.doclist .etat-table tfoot .etat-total td {
  padding: 6px 6px;
  font-family: "Manrope";
  font-size: 12px;
}

/* ===== TUILES KPI — bordure colorée gauche (comme BL) ===== */
.doclist .etat-tuiles {
  gap: 16px;
  margin-bottom: 20px;
}

.doclist .etat-tuile {
  position: relative;
  border-radius: var(--r-12);
  border: 1px solid var(--line);
  border-left: 4px solid var(--teal);
  background: var(--surface);
  box-shadow: var(--sh-sm);
}

/* Variantes de couleur (rotation comme les BL) */
.doclist .etat-tuile:nth-child(5n + 1) {
  border-left-color: var(--teal);
}
.doclist .etat-tuile:nth-child(5n + 2) {
  border-left-color: var(--blue);
} /* bleu */
.doclist .etat-tuile:nth-child(5n + 3) {
  border-left-color: var(--amber);
} /* orange */
.doclist .etat-tuile:nth-child(5n + 4) {
  border-left-color: var(--red);
} /* rouge */
.doclist .etat-tuile:nth-child(5n + 5) {
  border-left-color: var(--green);
} /* vert */

.doclist .etat-tuile-val {
  font-family: "Manrope";
  font-size: 18px;
  font-weight: 700;

  color: var(--ink);
  margin-top: 6px;
  display: block;
}

.doclist .etat-tuile-lbl {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--slate);
}

/* ===== FILTRES — légèrement plus d'air ===== */


/* ===== PIED ===== */
.doclist .table-foot,
.doclist .etat-foot {
  font-size: 12.5px;
}

/* ============================================================================
   PATCH 3 — Ajustements finaux (marge, hauteur, gras)
   ============================================================================ */

/* ⚠️⚠️ LE REMBOURRAGE VERTICAL A ÉTÉ RETIRÉ ICI (L9a-③, arbitrage de Walid). Ce n'était pas un
   choix de rythme : c'était une SECONDE gouttière, posée par le bloc collé PAR-DESSUS celle de
   la page (`.content-area` porte déjà 1,5 rem). Mesuré sur les 13 écrans en 390 px et au
   bureau — la valeur ne portait AUCUNE information, elle empilait deux marges.
   ⚠️ La gouttière HORIZONTALE (24 px) reste : elle, elle tient la largeur du bloc blanc, et la
   retirer déplacerait les 13 écrans latéralement — ce qui n'est pas le sujet. */
.doclist {
  padding: 0 24px;
}

/* 2. Tuiles KPI — padding plus serré */
.doclist .etat-tuile {
  padding: 10px 14px;
  min-width: 140px;
}

.doclist .etat-tuile-val {
  font-size: 18px;
  margin-top: 4px;
}

/* 3. Hauteur de ligne réduite */
.doclist .etat-table tbody td {
  line-height: 1.3;
}

.doclist .etat-table thead th {
  padding: 8px 20px; /* réduit aussi */
}

/* 4. Colonnes identité en gras (Référence, Client, Fournisseur, Code) */
.doclist .etat-table .etat-col-identite {
  font-weight: 700;
  color: var(--ink);
}
/* ============================================================================
   PATCH 4 — Suppression fond blanc global + réduction chiffres
   ============================================================================ */

/* 1. Retirer le fond blanc de la carte englobante */
.doclist .crud-table-card {
  background: transparent;
  box-shadow: none;
  border: none;
  padding: 0;
  /* ⚠️⚠️ L'`!important` A ÉTÉ RETIRÉ (L9a-③), ET SEULEMENT LUI : la déclaration reste à 0, donc
     les 12 listes ne gagnent AUCUNE marge. Ce qu'il faisait, mesuré : il écrasait l'attribut
     `style="margin-bottom:14px"` posé par `EtatViewer.razor` sur sa carte de filtres — un
     `!important` de feuille bat un inline. La marge de l'état était donc MORTE depuis le collage,
     et l'écran ne tenait son air que du `gap` du conteneur ; le jour où le `gap` est parti, les
     deux blocs se sont touchés. ⭐ Sans le mot-clé, l'inline reprend la main : l'état respire,
     les listes sont inchangées. _Une déclaration sans effet ne se signale nulle part._ */
  margin-bottom: 0;
}

/* 2. Le tableau seul garde un fond blanc (comme les BL) */
.doclist .table-scroll-inner {
  background: var(--surface);
  border-radius: var(--r-9);
  box-shadow: var(--sh-sm);
  border: 1px solid var(--line);
  /* ⚠️⚠️ `overflow: hidden` A ÉTÉ RETIRÉ ICI, ET C'ÉTAIT UN DÉFAUT FONCTIONNEL.
     Deux déclarations se contredisaient sur LE MÊME sélecteur, à MÊME
     spécificité : `.doclist .table-scroll-inner { overflow-x: auto }` plus haut
     (« Scroll horizontal du tableau »), et ce `overflow: hidden` collé ensuite —
     l'ordre du fichier tranchait, et il tranchait CONTRE l'intention écrite.
     ⇒ On RETIRE la déclaration qui gagnait à tort, on n'en empile pas une
     troisième : c'est le geste qui a corrigé trois défauts cette semaine.
     ⚠️ CE QUE ÇA COÛTAIT, MESURÉ : 40 des 45 états rendaient de la donnée
     INATTEIGNABLE en 390 px. Sur `soldes-clients-courant`, 862 px de tableau
     dans 292 px de cadre — 570 px hors champ, sur 8 colonnes l'utilisateur en
     voyait 2. Ni la roue ni le BALAYAGE TACTILE ne bougeaient d'un pixel
     (`scrollLeft` = 0), alors qu'un `scrollLeft` posé PAR SCRIPT fonctionnait :
     la donnée était rendue, simplement hors d'atteinte. Même famille que les
     écrans muets sur téléphone — c'est fonctionnel, pas visuel.
     ⚠️ LE COIN ARRONDI N'EST PAS PERDU : `overflow-x: auto` rogne le contenu
     comme `hidden` le faisait. Ce qui change est le GESTE, pas le tracé. */
  margin-bottom: 14px;
}

/* 3. Réduire la taille des chiffres dans le tableau */
.doclist .etat-table .crud-num,
.doclist .etat-table td.crud-num,
.doclist .etat-table tfoot .etat-total td.crud-num {
  font-size: 12.5px; /* avant 13.5px / 14px */
  font-weight: 700;
}

/* 4. Réduire aussi les textes normaux du tableau pour l'harmonie */
.doclist .etat-table tbody td {
  font-size: 12.5px; /* avant 13px */
  padding: 9px 20px; /* légèrement moins haut */
}

/* 5. Les tuiles KPI restent des cartes blanches individuelles (déjà OK) */
/* 6. Le pied reste propre sous le tableau blanc */
.doclist .table-foot,
.doclist .etat-foot {
  padding: 10px 4px; /* aligné à gauche, pas dans une carte */
  background: transparent;
  border-top: none;
}
/* ============================================================================
   PATCH 5 — Uniformiser les libellés d'en-tête de colonnes
   ============================================================================ */

/* Forcer l'uniformité sur TOUS les <th> sans exception */
.doclist .etat-table thead th,
.doclist .etat-table thead th.crud-num,
.doclist .etat-table thead th.etat-col-identite {
  /* ⚠️ 10,5 px, LA VALEUR DES LISTES (`.doclist .crud-list thead th`), pas 10 : l'unification
     consiste à ce que l'en-tête de l'état soit CELUI des listes, une seule valeur pour les deux.
     Sous le plancher de 11 px, et assumé — la règle DÉCLARE `text-transform: uppercase` et
     `letter-spacing`, donc aucun bas de casse ne peut y apparaître. Inscrit dans
     `SousLePlancherAssume` avec ce motif, comme le précédent identique de L6a. */
  font-size: 10.5px !important;
  font-weight: 700 !important;
  letter-spacing: 0.1em !important;
  text-transform: uppercase !important;
  color: var(--slate) !important;
  padding: 10px 20px !important;
  border-bottom: 1px solid var(--line) !important;
  white-space: nowrap !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  vertical-align: middle !important;
  line-height: 1 !important;
  font-family:
    "Manrope", "Helvetica Neue", Helvetica, Arial, sans-serif !important;
  background: var(--thead-bg) !important;
  border-top: none !important;
}

/* S'assurer qu'aucune classe Bootstrap ne pollue */
.doclist .etat-table thead th.table-active,
.doclist .etat-table thead th.sorting,
.doclist .etat-table thead th.sorting_asc,
.doclist .etat-table thead th.sorting_desc {
  font-size: 10px !important;
  font-weight: 700 !important;
}
/* ============================================================================
   PATCH 6 — Titre des états identique aux Bons de Livraison
   ============================================================================ */


/* Le sous-titre (période) sous le titre, style identique BL */
.doclist .doc-subtitle {
  font-size: 13px;
  color: var(--slate);
  margin-top: 4px;
  font-weight: 500;
}

/* L'eyebrow "Éditions & Rapports" au-dessus du titre */
.doclist .doc-eyebrow {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--teal);
  margin-bottom: 6px;
  display: block;
}

/* Le badge "PARTIE PHYSIQUE" à côté du titre — aligné et proportionné */
.doclist .etat-partie {
  margin-left: 12px;
  top: -2px;
}
/* ============================================================================
   PATCH 7 — Alignement titre + badge pour doc-title 20px / 600
   ============================================================================ */

/* Conteneur flex du titre */
.doclist .doc-title-row {
  display: flex;
  align-items: center; /* centre verticalement titre + badges */
  flex-wrap: wrap;
  gap: 8px; /* espace entre titre et badges */
  line-height: 1; /* évite un interligne parasite */
}

/* Titre — déjà défini par vous, conservé ici pour référence */
/* ⚠️⚠️ AUCUN COMMENTAIRE ENTRE CE SÉLECTEUR ET SES DÉCLARATIONS — la garde de typographie
   lit une fenêtre de TROIS lignes ; un commentaire intercalé la rend aveugle. Deuxième
   occurrence, après celle de `titre-serif` en L9a.
   ⚠️⚠️ CORRIGÉ EN L9b — MA PHRASE DE L9a ÉTAIT FAUSSE, ET LA MESURE L'A DIT. J'avais écrit
   que « `.doc-title` est en Manrope pour les 13 listes » : c'est l'INVERSE. Les listes ne
   titrent pas avec `.doc-title` mais avec `.page-head-text h1.titre-serif`, résolu en
   **Fraunces** — mesuré à l'écran, 20 px / 600 / −0,2 px. Seul l'ÉTAT porte `.doc-title`,
   et L9a l'a donc éloigné des listes au lieu de l'en rapprocher.
   ⭐ TOUTE LA LOGIQUE DU CHANTIER EST QU'UN ÉTAT **EST** UNE LISTE — c'est ce qui a rendu
   trois gardes vertes. Laisser son titre dans une autre police contredirait cette décision
   sur le seul élément qu'on voit en arrivant. Les métriques sont déjà identiques : seule la
   FAMILLE manquait.
   ⚠️ AUCUN `font-variation-settings` ICI : `font-optical-sizing` vaut `auto` (mesuré), donc
   le navigateur pilote déjà l'axe `opsz` sur la taille RENDUE. Figer une valeur le
   DÉSACTIVERAIT — et l'ancienne (`opsz 96`) visait 28 px, une taille que L3 a changée. */
.doclist .doc-title {
  font-family: 'Fraunces', serif;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--ink);
  line-height: 1.2;
  margin: 0;
}

/* ══════════════════════════════════════════════════════════════════════════════
   L7 — LES BOUTONS D'EN-TÊTE DES ÉTATS : GÉOMÉTRIE + HIÉRARCHIE À QUATRE NIVEAUX
   ══════════════════════════════════════════════════════════════════════════════
   ⚠️⚠️ LE SÉLECTEUR EST BORNÉ, ET C'EST MESURÉ, PAS ESPÉRÉ :
     · `.doc-header-actions` seul dresse 22 écrans — les 21 écrans de DOCUMENT
       (bons, factures, règlements, transfert), Commissions, RéflexionMarge,
       RelevéProduit — dont UN SEUL est un état.
     · `.btn-filtres` vit sur 57 fichiers (192 occurrences), `.btn-nouveau` sur
       79 (156). Trois tranches ont déjà cassé des écrans en habillant un
       conteneur PARTAGÉ (T-UI, puis `.crud-toolbar-actions`).
     · `.doclist .doc-header-actions` ne dresse QUE `EtatViewer.razor` — vérifié
       fichier par fichier : c'est le seul des 22 à porter aussi `.doclist`.
       Il sert les 45 états du catalogue sur `/etats/{Id}`, et rien d'autre.
   ⇒ Un seul écran habillé, aucun débordement possible sur les 21 autres.

   ⭐ SEULE LA GÉOMÉTRIE VIENT DE LA MAQUETTE — ni palette, ni typographie, ni
   densité, ni structure, ni nom de classe. Les quatre familles emploient NOS
   tokens, et les deux noms de classe (`etat-btn-*`) sont NOTRE vocabulaire.

   ⚠️ LE RAYON DE 7 px NE CRÉE AUCUN CRAN : `--r-7` EXISTE déjà (:root, 3 usages).
   ⚠️ 11 px EST EXACTEMENT LE PLANCHER DE LISIBILITÉ (11.0), jamais en dessous.

   ⚠️⚠️ LA COMPACITÉ EST BORNÉE AU BUREAU, ET C'EST LA DÉCISION DE L6a REPRISE
   TELLE QUELLE : réduire sur le tableau de BUREAU, PRÉSERVER la cible TACTILE
   en mobile. La bascule mesurée descendait les quatre boutons de 40 à 31 px de
   haut sur téléphone — un recul, pas un gain. Sous 769 px, ils gardent donc
   leurs dimensions d'avant (rembourrage et taille de texte). Le rayon et la
   graisse, eux, valent aux deux tailles : ce sont des traits de FORME, ils ne
   touchent pas à la cible.

   ⚠️⚠️ LE `gap: 6px` A ÉTÉ RETIRÉ PUIS RENDU, ET LES DEUX GESTES SONT JUSTES —
   c'est le MARKUP qui a changé entre eux, pas l'avis. Il ne sépare que des
   ENFANTS flex : tant que les pictogrammes étaient des CARACTÈRES dans le
   libellé, le bouton n'avait qu'un nœud texte (mesuré : 0 enfant flex) et la
   déclaration était INERTE — on l'a donc retirée, car une déclaration sans effet
   ne se signale nulle part (la famille inscrite deux fois au journal : la marge
   morte sous un `!important`, la règle jetée par le parseur). Les vrais dessins
   font du bouton un conteneur à DEUX enfants : la déclaration agit, elle revient.
   ⭐ La leçon tient dans l'ordre : on ne garde pas une déclaration en pariant
   qu'un markup futur la rendra vraie — on la repose quand il l'est.

   ⭐⭐ LES QUATRE NIVEAUX, ET LE PROBLÈME QU'ILS CORRIGENT : les trois premiers
   boutons étaient INDISTINGUABLES (tous `.btn-filtres` neutres) alors qu'ils ne
   font pas la même chose — revenir, imprimer, exporter. Les tons retenus sont
   les DEUX SEULS accents de la palette SANS SENS ATTRIBUÉ (0 usage mesuré dans
   app.css, absents de `TokensEnAttente` donc réservés à aucune tranche) :
     ① neutre    Retour     --ink sur --surface ......... 16,53
     ② teinté    Imprimer   --indigo sur --indigo-soft ... 5,18
     ③ teinté    Excel      --clay sur --clay-soft ....... 4,90
     ④ rempli    Afficher   blanc sur --teal ............. 3,98  ⚠️ voir dette
   Tous les autres accents portent déjà une information qu'un bouton brouillerait :
   --blue = partenaire fournisseur, --green = partenaire client, --amber =
   totaux, --teal = accent primaire ET partenaire, --brand* = réservé à L8.

   ⚠️⚠️ DETTE OUVERTE, ANTÉRIEURE À CETTE TRANCHE — LE BLANC SUR L'ACCENT PLEIN.
   `.btn-nouveau` écrit du blanc sur `--teal` : ratio 3,98, SOUS le seuil AA de
   4,5. Le défaut ne vient PAS de la descente à 11 px — le bouton était à
   13,5 px/700, donc jamais « large text » (≥ 18,66 px gras), et déjà sous AA.
   Le remède SANS baisser aucun seuil est `--teal-dark`, qui rend 5,56.
   ⚠️ Il n'est PAS appliqué ici, et le motif est une DÉCISION de Walid : borné
   aux états, il donnerait à CE bouton un vert différent de tous les autres
   boutons d'action du logiciel — divergence visible, gain invisible. Le
   périmètre réel est de 79 fichiers (156 occurrences) : c'est une tranche à
   part, pas une retouche. ⭐ La dette vit ICI, dans le code qu'elle concerne,
   et non dans un journal — un consommateur la lira au moment de le modifier. */
@media (min-width: 769px) {
  .doclist .doc-header-actions .btn-filtres,
  .doclist .doc-header-actions .btn-nouveau {
    height: 31px;
    padding: 0 14px;
    font-size: 11px;
  }
}

/* ⚠️⚠️ LA HAUTEUR EST IMPOSÉE SUR LE BOUTON, ELLE NE RÉSULTE PLUS DE SON CONTENU.
   LE DÉFAUT, MESURÉ : deux boutons portaient un filet, deux non — et un filet
   ajoute son épaisseur DE CHAQUE CÔTÉ. Écart relevé au moteur : EXACTEMENT
   2,0 px aux deux tailles (30,5 contre 28,5 au bureau ; 42,3 contre 40,3 en
   390 px), c'est-à-dire 2 × 1 px de bordure. `box-sizing: border-box` est bien
   actif (vérifié au moteur, pas déduit d'un commentaire) : la bordure est DANS
   la hauteur, donc une hauteur imposée l'absorbe quel que soit le filet.
   ⚠️ LES DEUX VALEURS SONT DÉRIVÉES DE LA MESURE, PAS CHOISIES AU JUGÉ : hauteur
   du contenu = interligne + rembourrage + bordure, arrondie au pixel SUPÉRIEUR.
     bureau   16,5 + 6 + 6 + 2 = 30,5  ⌈⌉ 31 px
     mobile   20,25 + 10 + 10 + 2 = 42,3  ⌈⌉ 43 px
   Arrondir vers le HAUT est ce qui garantit qu'aucun bouton ne rétrécit — la
   cible tactile du mobile ne descend pas d'un dixième.
   ⭐ C'EST LA QUATRIÈME FOIS QU'UN ALIGNEMENT REVIENT sur cet écran (trois pour
   les filtres, une ici). Une hauteur qui RÉSULTE du contenu rebouge dès qu'une
   icône, un filet ou un libellé plus long change — une hauteur IMPOSÉE, non.
   ⚠️ Le rembourrage VERTICAL part avec : hauteur fixe + centrage flex le rendent
   sans effet, et une déclaration sans effet ne se signale nulle part. Seul le
   rembourrage horizontal reste, lui seul agit encore. */
@media (max-width: 768px) {
  .doclist .doc-header-actions .btn-filtres,
  .doclist .doc-header-actions .btn-nouveau {
    height: 43px;
    padding-top: 0;
    padding-bottom: 0;
  }
}

.doclist .doc-header-actions .btn-filtres,
.doclist .doc-header-actions .btn-nouveau {
  border-radius: var(--r-7);
  font-weight: 600;
  /* ⚠️ Le `gap` EST DE RETOUR, ET CETTE FOIS IL AGIT SUR LES QUATRE. Il avait
     été retiré comme INERTE : les pictogrammes étaient des CARACTÈRES dans le
     libellé, donc un seul nœud texte, donc rien à espacer (mesuré : 0 enfant
     flex). Les vrais dessins font de chaque bouton un conteneur à DEUX enfants —
     la déclaration a désormais un effet, et il se mesure. ⚠️ Il ÉLARGIT donc les
     boutons : vérifié qu'aucun ne déborde en 390 px, où la barre se range en
     2 lignes de 2 (Retour + Imprimer, puis Excel + Afficher), 96 px de haut. */
  gap: 6px;
}

/* ⚠️⚠️ LES DESSINS REMPLACENT LES CARACTÈRES, ET C'EST CE QUI REND LA COULEUR
   PILOTABLE. Un « 🖨 » est un glyphe : il porte SA propre couleur, ignore
   `color`, et ne s'aligne pas sur la ligne de base du libellé. Un tracé en
   `stroke="currentColor"` HÉRITE de la couleur du texte — le ton foncé au repos,
   le blanc au survol — et l'apparence entière se règle d'un seul geste.
   ⭐ RIEN N'EST INVENTÉ : LES QUATRE DESSINS EXISTENT DÉJÀ DANS LE DÉPÔT, repris
   tels quels, et chacun est LE PLUS EMPLOYÉ de son sens — c'est un comptage, pas
   un goût :
     Retour     flèche gauche, 4 usages, déjà sur un bouton « Retour »
                (BonReceptionDocument et 3 autres documents)
     Imprimer   imprimante, 6 usages (DocumentPublic et 5 documents)
     Excel      flèche d'export, FichiersJoints.razor
     Afficher   œil, 9 usages, déjà sur des boutons « Consulter » — contre 2 pour
                la loupe (RechercheListe, Catalogue). L'œil l'emporte au COMPTE,
                et le sens suit : afficher un état, c'est le VOIR, pas le chercher.
   ⚠️ La symétrie des quatre est un ARBITRAGE de Walid : elle prime sur la
   distinction geste physique / navigation qui avait d'abord laissé Retour et
   Afficher sans dessin. Même famille que les 288 autres
   tracés du dépôt : `viewBox="0 0 24 24"`, `fill="none"`, `stroke="currentColor"`,
   `stroke-width="2"` (288 sur 407 tracés, la valeur dominante), extrémités rondes.
   ⚠️ LA TAILLE VIENT DE DEUX MESURES CONCORDANTES, pas d'un choix : le voisin
   immédiat sur CE MÊME écran (`.doclist-fpill-ico`, l'icône des pastilles de
   filtre) fait 13 px ; et les boutons de document portent 15 px pour un texte de
   13 px, soit un rapport de 1,15 qui donne 12,7 pour notre texte de 11 px. */
.doclist .doc-header-actions .etat-btn-ico {
  width: 13px;
  height: 13px;
  flex: none;
}

/* ⚠️⚠️ LE FILET EST NEUTRE SUR TOUS LES BOUTONS QUI EN PORTENT UN, ET C'EST UN
   ARBITRAGE DE WALID, PAS UNE MISE EN FORME. Il avait d'abord été posé à
   l'ACCENT de chaque teinte : mesuré lisible (5,18 / 4,90), mais un contour
   coloré ENFERME le bouton — on lit le cadre avant le mot qu'il entoure. Le
   filet clair `--line` (78 usages, celui des cartes et des champs) donne le
   relief attendu sans concurrencer le libellé, et il donne aux trois boutons
   bordés le MÊME cadre, quel que soit leur ton.
   ⚠️ LE BOUTON PLEIN RESTE SANS CONTOUR : sa couleur le distingue déjà, un
   filet le rendrait plus lourd que ceux qu'il doit dominer. C'est pour cela que
   la hauteur est IMPOSÉE plus haut — sans elle, l'absence de filet le rendait
   plus court de 2 px, exactement le défaut que Walid a vu.
   ⚠️ LA TEINTE NE BOUGE PAS : seul le CONTOUR change, le TEXTE garde l'accent —
   contrastes inchangés, 5,18 (indigo) et 4,90 (clay).

   ⚠️⚠️ L'OMBRE PART, ET LE MOTIF EST LE MÊME QUE POUR LE FILET COLORÉ : sur un
   objet de 31 px, le relief vient du CADRE, pas d'un soulèvement. La mesure qui
   avait servi à choisir le cran discret le disait déjà à demi-mot — `--sh-sm`
   (0 1px 2px) étend l'ombre de 3 px, et `--sh-lg` (0 10px 30px) de 40 px, soit
   129 % de la hauteur du bouton. ⭐ On n'a pas remplacé une valeur par une
   valeur : on a RETIRÉ une déclaration devenue sans objet.
   ⇒ Le survol se renforce par le FOND : l'accent devient plein, le texte passe
   en `--surface`, et le filet prend la couleur du fond pour disparaître dans le
   plein. Contrastes mesurés 7,06 (indigo) et 6,07 (clay), au-dessus de AA. Le
   tracé suit sans qu'on le nomme, puisqu'il est en `currentColor`.

   ⚠️⚠️ POURQUOI LE SURVOL N'EMPLOIE PAS `--indigo-dark` NI `--clay-dark` — ET CE
   N'EST PAS UN OUBLI DE LA PALETTE : ces deux tokens VALENT LEUR BASE, par une
   DÉCISION de L1d dont le motif est écrit au `:root` de ce fichier (bloc des tons
   foncés, juste au-dessus des déclarations `--*-dark`) et au journal
   (`docs/journal/REDESIGN-EN-COURS.md`, tableau des tons foncés). Les deux teintes
   sont DÉJÀ assez foncées pour porter du texte (4,90 et 5,18 sur leur fond doux) ;
   le token existe pour que la convention `--X-soft` / `--X-dark` soit tenable sur
   LES SEPT teintes, faute de quoi la garde d'avatar G4 — qui DÉRIVE le couple
   depuis le NOM — ne saurait pas résoudre la teinte.
   ⭐⭐ CETTE NOTE A ÉTÉ RÉÉCRITE PARCE QU'ELLE REDÉCOUVRAIT CE MOTIF AU LIEU DE LE
   CITER, et l'aurait fait lire comme une dette. Le geste juste, quand une valeur
   SURPREND, est de CHERCHER SON MOTIF avant de conclure à l'oubli — ici il vivait
   4 800 lignes plus haut, et personne ne l'avait lu. */
/* ② IMPRIMER — sortie papier. */
.doclist .doc-header-actions .etat-btn-imprimer {
  background: var(--indigo-soft);
  border-color: var(--line);
  color: var(--indigo);
}
.doclist .doc-header-actions .etat-btn-imprimer:hover:not(:disabled) {
  background: var(--indigo);
  border-color: var(--indigo);
  color: var(--surface);
}

/* ③ EXCEL — export tableur. */
.doclist .doc-header-actions .etat-btn-excel {
  background: var(--clay-soft);
  border-color: var(--line);
  color: var(--clay);
}
.doclist .doc-header-actions .etat-btn-excel:hover:not(:disabled) {
  background: var(--clay);
  border-color: var(--clay);
  color: var(--surface);
}

/* ⚠️ Les deux boutons teintés portent `disabled="@(res is null)"`. Sans cette
   règle ils garderaient leur teinte pleine en étant inertes. Contraste
   désactivé : 5,64.

   ⚠️⚠️ ET L'ÉTAT N'EST PAS ATTEIGNABLE PAR LE FLUX ACTUEL — mesuré, pas supposé :
   0 bouton désactivé sur 5 états parcourus (relevé client, relevé fournisseur,
   ventes par période, balance âgée, CA par client), parce que
   `OnInitializedAsync` appelle `Afficher()` : `res` n'est jamais nul au premier
   rendu. ⭐ LA RÈGLE N'EST POURTANT PAS MORTE, et c'est ce qui la distingue du
   `gap` retiré plus haut : celui-là ne POUVAIT pas agir (un seul nœud dans le
   bouton), celle-ci agit dès que l'attribut est vrai — PROUVÉ en le forçant au
   moteur : le fond teinté part et le curseur redevient neutre.
   ⇒ On la garde : l'attribut est déclaré par la page, et le jour où un état
   échoue à charger ou perd son chargement automatique, il devient vrai. Ce qui
   ne se garde pas, c'est une déclaration qui ne peut PAS agir. */
.doclist .doc-header-actions .etat-btn-imprimer:disabled,
.doclist .doc-header-actions .etat-btn-excel:disabled {
  background: var(--surface);
  border-color: var(--line);
  color: var(--slate);
  cursor: default;
}


/* ══════════════════════════════════════════════════════════════════════════════
   T-BTN — LE 5ᵉ NIVEAU : L'ACTION QUI DÉFAIT · ET LE TEINTÉ « IMPRIMER »
   ══════════════════════════════════════════════════════════════════════════════
   ⚠️⚠️ LE DÉFAUT CORRIGÉ N'EST PAS ESTHÉTIQUE, IL EST D'USAGE, ET IL EST CHIFFRÉ :
   dans le MÊME en-tête vivent **19 boutons « Retour »** et **14 « Repasser en
   brouillon »**, portant la MÊME classe, le MÊME fond blanc, le MÊME contour.
   L'un revient en arrière ; l'autre DÉFAIT UNE VALIDATION — et le service refuse
   alors si un règlement est imputé, si une facture en est issue. Au total,
   **30 des 73 boutons « neutres » des en-têtes de document (41 %) sont dangereux.**

   ⭐⭐ LA SORTIE : ON NE TOUCHE PAS À `.docv2`, ON EN SORT. Les deux classes sont
   portées **SEULES** — l'attribut ne contient plus `btn-filtres`. Aucune règle de
   `doc-v2.css` ne les atteint donc, et la question de la spécificité ne se pose
   même pas. Mesuré avant d'écrire : `doc-v2.css` ne contient **aucun sélecteur
   d'élément `button`**, rien ne les rattrape par la bande.
   ⚠️ L'autre voie — surclasser — aurait exigé (0,4,0) au repos et (0,6,0) au
   survol (car `app.css` est chargé AVANT `doc-v2.css`, donc à spécificité égale
   c'est docv2 qui gagne), et surtout elle aurait fait NOMMER `.docv2` dans
   `app.css` : on ne serait pas sorti, on s'y serait accroché.

   ⭐ LES TOKENS SONT CEUX DU `:root`, ET ILS ARRIVENT INTACTS DANS `.docv2` :
   celui-ci ne redéfinit **AUCUN** token de palette (0 mesuré). Il n'en masque que
   cinq — `--r-sm`, `--r-md`, `--r-lg`, `--sh-sm`, `--sh-lg` — et leurs valeurs y
   sont **identiques** à celles du `:root` (9 / 11 / 14 px, ombres au caractère
   près). ⚠️ L'ombrage est donc INERTE aujourd'hui, mais c'est un DOUBLON qui peut
   dériver en silence : modifier `--r-9` au `:root` ne changerait rien dans les
   18 écrans `.docv2`. Constat INSCRIT, non corrigé — ce n'est pas une tranche de
   boutons.

   ⭐ AUCUN TOKEN NEUF, ET `--red` N'EST PAS TOUCHÉ. Le ton du danger est
   `--red-dark` sur `--red-soft` : **4,96**, au-dessus de AA, et dans la bande
   exacte des deux teintés de L7 (indigo 5,18 · clay 4,90). `--red-dark` existait
   déjà (T-ROUGE-LISIBLE) et ne portait que du TEXTE de montant — **aucun bouton** :
   le sens « action qui défait » n'entre en collision avec aucun sens existant, et
   les **8 sens de `--red`** restent où ils sont, aucun n'étant substitué.
   ⚠️ LE COUPLE D'ALERTE DE `.docv2` EST ÉCARTÉ, MESURE À L'APPUI :
   `--t-alerte` sur `--f-alerte` rend **3,57**, SOUS le seuil AA, et il sert déjà
   11 sens dans `doc-v2.css`. Le reprendre aurait donné un bouton illisible.

   ⚠️ LA GÉOMÉTRIE EST CELLE DE `.docv2 .btn-filtres`, AU PIXEL — hauteur 34,
   rembourrage 0 13px, rayon `--r-sm`, 13px/500, gap 6px. C'est ce qui garde la
   rangée alignée alors que les boutons ne partagent plus aucune règle.
   ⭐ `font-family` n'est PAS déclarée, et c'est délibéré : le reboot Bootstrap pose
   `font-family: inherit` sur `button`, donc la police vient de `.docv2` (Manrope)
   comme pour les autres. La déclarer figerait ce que l'héritage fait déjà.
   ⚠️ LA CIBLE TACTILE NE BOUGE PAS : 34 px aux DEUX tailles, exactement comme
   aujourd'hui. La compacité de L6a/L7 ne s'applique pas ici — il n'y a rien à
   réduire, et réduire aurait été un recul en 390 px.

   ⚠️ PÉRIMÈTRE : les 18 écrans `.docv2`. Les 4 autres porteurs de
   `.doc-header-actions` — Commissions, EtatViewer, ReflexionMarge, ReleveProduit —
   restent inchangés, et le motif est MESURÉ : aucun d'eux ne porte le moindre
   bouton dangereux, le défaut corrigé ici n'y existe pas. Ce qui subsiste est une
   divergence de FORME, bornée et chiffrée : ReflexionMarge et ReleveProduit
   gardent un « Imprimer » BLANC là où les 18 documents et EtatViewer (L7) le
   portent teinté — 2 écrans, 4 boutons. À payer avec le socle des états.
   Détail : docs/journal/REDESIGN-EN-COURS.md, section T-BTN. */
.doc-btn-danger,
.doc-btn-imprimer {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 13px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background .12s, color .12s, border-color .12s;
}

/* ⑤ DANGER — l'action qui DÉFAIT. Repos : ton foncé sur fond doux (4,96). */
.doc-btn-danger {
  background: var(--red-soft);
  color: var(--red-dark);
}

/* ⚠️ LE SURVOL SE RENFORCE PAR LE FOND, comme les teintés de L7 : l'accent devient
   plein, le texte passe en `--surface` (5,76), et le filet prend la couleur du fond
   pour disparaître dans le plein. Pas d'ombre : sur un objet de 34 px, le relief
   vient du CADRE — c'est l'arbitrage déjà pris en L7, on ne le rejoue pas. */
.doc-btn-danger:hover:not(:disabled) {
  background: var(--red-dark);
  border-color: var(--red-dark);
  color: var(--surface);
}

/* ② TEINTÉ — sortie papier. Même ton que L7 sur les états : un « Imprimer » se
   reconnaît d'un écran à l'autre. */
.doc-btn-imprimer {
  background: var(--indigo-soft);
  color: var(--indigo);
}

.doc-btn-imprimer:hover:not(:disabled) {
  background: var(--indigo);
  border-color: var(--indigo);
  color: var(--surface);
}

/* ⚠️ SANS CETTE RÈGLE, UN BOUTON INERTE GARDERAIT SA TEINTE PLEINE — et le rouge
   d'un « Supprimer » désactivé crierait un danger qu'on ne peut même pas courir.
   Elle N'EST PAS théorique ici, contrairement à celle de L7 : `🗑 Supprimer` porte
   `disabled="@(!peutSupprimer)"` sur les 4 écrans de règlement, et `Imprimer` porte
   `disabled="@(Id is null)"` sur tout document neuf — l'état est atteint dès qu'on
   ouvre un document sans l'avoir enregistré. Contraste désactivé : 5,64. */
.doc-btn-danger:disabled,
.doc-btn-imprimer:disabled {
  background: var(--surface);
  border-color: var(--line);
  color: var(--slate);
  cursor: default;
}


/* Badge PARTIE PHYSIQUE — recentré sur le titre 20px */
.doclist .etat-partie {
  font-family: "Manrope", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--teal);
  /* Supprimer tout top / vertical-align bricolé */
  position: static;
  vertical-align: baseline;
  /* Petit ajustement fin pour centrer visuellement sur le 20px */
  margin-top: 1px;
}

/* Badge 🔒 Interne — même alignement */
.doclist .doc-badge-etat {
  margin-top: 1px;
}
/* ============================================================================
   PATCH 8 — Radios Solde Balance : style maquette compact
   ============================================================================ */

/* 1. Forcer l'horizontalité du groupe */
.doclist .etat-balance-radios {
  display: flex !important;
  flex-direction: row !important;
  align-items: center !important;
  gap: 18px;
}

/* 2. Label + radio sur la même ligne */
.doclist .etat-balance-radios label {
  margin: 0;
  line-height: 1;
}

/* 3. Radio native → custom 14 px (style maquette) */
.doclist .etat-balance-radios input[type="radio"] {
  transition: all 0.15s ease;
  position: relative;
}

/* État coché : cercle teal avec point blanc intérieur */


.doclist .etat-balance-radios input[type="radio"]:focus {
  outline: none;
  box-shadow:
    0 0 0 3px var(--teal-soft),
    inset 0 0 0 2.5px var(--surface);
}
/* ============================================================================
   PATCH 8-bis — Radios : forcer des cercles parfaits 14×14 px
   ============================================================================ */

/* Forcer strictement le carré 14×14 avant border-radius: 50% */
.doclist .etat-balance-radios input[type="radio"] {
  appearance: none !important;
  -webkit-appearance: none !important;
  display: inline-block !important;
  width: 14px !important;
  height: 14px !important;
  min-width: 14px !important;
  min-height: 14px !important;
  max-width: 14px !important;
  max-height: 14px !important;
  padding: 0 !important;
  margin: 0 !important;
  border: 1.5px solid var(--line);
  border-radius: 50% !important;
  background: var(--surface);
  cursor: pointer;
  flex-shrink: 0;
  box-sizing: border-box !important;
  overflow: hidden;
  vertical-align: middle;
}

/* État coché : disque plein teal */
.doclist .etat-balance-radios input[type="radio"]:checked {
  border-color: var(--teal);
  background: var(--teal);
  box-shadow: inset 0 0 0 2.5px var(--surface);
}

.doclist .etat-balance-radios input[type="radio"]:hover {
  border-color: var(--teal);
}

/* S'assurer que le label ne compresse pas le radio */
.doclist .etat-balance-radios label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
  cursor: pointer;
}
/* ============================================================================
   PATCH 9 — Carte blanche pour les filtres (style maquette balance)
   ============================================================================ */


/* Labels internes : plus petits et serrés */
.doclist .etat-filtres label.doc-mini-lbl {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--slate);
  margin-bottom: 3px;
}


/* Les radios restent alignés au centre de la carte */
.doclist .etat-balance-radios {
  align-self: center;
  padding: 0;
  margin: 0;
}
/* ============================================================================
   PATCH 10 — Filtres états réutilisant les pastilles BL (FiltresListe)
   ============================================================================ */

/* Le conteneur état utilise la même structure que .doclist-filters */
.doclist .etat-filtres {
  display: flex;
  /* ⚠️⚠️ C'EST ICI, ET NULLE PART AILLEURS, QUE L'ALIGNEMENT VERTICAL DE LA CARTE SE DÉCIDE.
     Cette règle est la DERNIÈRE des quatre `.doclist .etat-filtres` du fichier : c'est elle qui
     gagne. Les valeurs contraires des trois autres sont retirées — un fichier qui déclare
     `flex-end` en haut et `center` en bas ne se lit plus, il se devine.
     ⇒ Tout enfant de cette carte est centré sur la même bande que les pastilles ; un enfant qui
     porterait un rembourrage vertical DISSYMÉTRIQUE se décalerait d'autant (c'est ce que faisait
     le `padding-bottom: 2px` des radios de la balance, retiré avec cette tranche). */
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
  flex-wrap: wrap;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-9);
  padding: 10px 16px;
  box-shadow: var(--sh-sm);
}

/* Les dates stylisées comme des pastilles */
.doclist .etat-filtre-dates {
  display: flex;
  gap: 8px;
  align-items: center;
}

.doclist .etat-fpill-date {
  position: relative;
  display: inline-flex;
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-9);
  padding: 0;
  overflow: hidden;
}

.doclist .etat-fpill-date .doclist-fpill-ico {
  left: 10px;
  width: 13px;
  height: 13px;
  color: var(--slate);
  pointer-events: none;
  position: absolute;
}

.doclist .etat-fpill-date input[type="date"] {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  border-radius: var(--r-9);
  padding: 7px 12px 7px 29px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-soft);
  font-family: "Manrope", "Helvetica Neue", Helvetica, Arial, sans-serif;
  min-width: 130px;
  height: 36px;
}

.doclist .etat-fpill-date input[type="date"]:focus {
  outline: none;
}

.doclist .etat-fpill-date:focus-within {
  border-color: var(--teal);
  box-shadow: 0 0 0 3px var(--teal-soft);
}

/* Checkbox stylisée comme pastille */
.doclist-fcheck {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-9);
  padding: 7px 14px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-soft);
  cursor: pointer;
  transition:
    background 0.12s,
    border-color 0.12s;
}

.doclist-fcheck:hover {
  background: var(--surface-alt);
}

.doclist-fcheck input[type="checkbox"] {
  width: 14px;
  height: 14px;
  accent-color: var(--teal);
  cursor: pointer;
  margin: 0;
}

/* Nombre (report TVA) */
.doclist .etat-fpill-number {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.doclist .etat-fpill-number label {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--slate);
}

.doclist .etat-fpill-number input[type="number"] {
  height: 34px;
  padding: 0 12px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-9);
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-soft);
  font-family: "Manrope", "Helvetica Neue", Helvetica, Arial, sans-serif;
  min-width: 140px;
}

.doclist .etat-fpill-number input[type="number"]:focus {
  border-color: var(--teal);
  box-shadow: 0 0 0 3px var(--teal-soft);
  outline: none;
}

.etat-cards { display: none; }

/* ══════════════════════════════════════════════════════════════════════════════
   L7 — LE RENDU EN CARTES DES ÉTATS (sous 768 px). BORNÉ À `.etat-cards`.
   ══════════════════════════════════════════════════════════════════════════════
   ⚠️ LE TABLEAU EST MASQUÉ SOUS 768 px, ET C'EST DIT PLUTÔT QUE DÉDUIT : les deux
   vues ne coexistent jamais — c'est déjà la règle des 13 listes (`crud-desktop-table`),
   et deux rendus simultanés du même jeu de données doubleraient la hauteur ET les
   totaux à l'écran. ⭐ CONSÉQUENCE ASSUMÉE : le défilement horizontal rendu ce matin
   (`overflow-x: auto`) devient sans objet en mobile — la carte montre TOUTES les
   colonnes, il n'y a plus rien hors champ. Il reste vrai et utile entre 768 px et la
   largeur du tableau, sur tablette.

   ⭐ AUCUNE COULEUR LITTÉRALE, AUCUN CRAN NEUF : tout vient des tokens et du
   vocabulaire de carte existant. Le liseré gauche est celui de `.crud-card::before`
   (4 px, déjà là) ; seule sa couleur change, par `--card-accent` — le mécanisme que
   `CardStyle` emploie depuis la trésorerie.

   ⚠️ PLANCHER DE LISIBILITÉ : rien n'est posé sous 11 px. Les libellés de paires
   héritent de `.crud-card-meta-label` (déjà conforme), et aucune règle ci-dessous ne
   touche à une taille de texte. Aucune exception à nommer. */
@media (max-width: 768px) {
    /* Les deux vues ne coexistent jamais. */
    .doclist .table-scroll-inner { display: none; }
    .etat-cards { display: block; }

    /* ⚠️ La carte d'état n'a PAS de hauteur fixe — elle SUIT son nombre de colonnes
       secondaires (2 à 6 selon l'état, mesuré). Le critère est AUCUNE COLONNE PERDUE,
       jamais un nombre de cartes par écran. */
    .etat-cards .crud-card-meta-value { overflow-wrap: anywhere; }

    /* LE SOUS-TOTAL — les trois marques du tableau, transposées.
       Le filet HAUT du tableau devient le liseré GAUCHE de la carte : une carte n'a
       pas de voisin au-dessus dont il faudrait la séparer. */
    .etat-cards .crud-card.etat-soustotal {
        --card-accent: var(--slate);
        background: var(--row-soft);
    }
    .etat-cards .crud-card.etat-soustotal .crud-card-title,
    .etat-cards .crud-card.etat-soustotal .crud-card-meta-value { font-weight: 700; }

    /* LE TOTAL GÉNÉRAL — même transposition, PLUS son filet haut : lui a bien un
       voisin au-dessus (la dernière carte de données), donc la marque du tableau
       garde tout son sens ici. */
    .etat-cards .crud-card.etat-total {
        --card-accent: var(--ink);
        background: var(--row-soft);
        border-top: 2px solid var(--line);
    }
    .etat-cards .crud-card.etat-total .crud-card-title { font-weight: 700; }

    /* Les sous-lignes de détail (`_sous`) suivent la carte, comme elles suivaient
       la rangée : elles ne sont ni un titre ni une paire. */
    .etat-cards-sous { margin-top: 8px; }

    .crud-cards-empty {
        padding: 20px;
        text-align: center;
        color: var(--slate);
    }
}


/* ══════════════════════════════════════════════════════════════════════════════
   L7 — LA RANGÉE DE RÈGLEMENT REPREND SA LARGEUR (cartes mobiles des listes).
   ══════════════════════════════════════════════════════════════════════════════
   ⚠️⚠️ UNE DÉCLARATION MORTE, LA TROISIÈME DE LA SEMAINE : le markup pose
   `style="grid-column:1 / -1"` sur la rangée de règlement — une propriété de
   GRILLE sur un conteneur qui est en `display: flex`. Elle n'a JAMAIS eu d'effet.
   ⭐ MESURÉ AU MOTEUR, pas déduit : `grid-column` calculé vaut bien `1 / -1`,
   `flex-basis` vaut `auto`, et la rangée est RENDUE à 165 px sur 262 disponibles.
   Le style calculé annonçait la propriété ; la géométrie disait la vérité.

   ⚠️ CE QUE ÇA COÛTAIT, ET C'EST LA RANGÉE QUI PORTE L'ARGENT : la barre de
   paiement, les « X DH encaissé » et le reste à payer tenaient dans un peu plus
   de la MOITIÉ de la largeur. Cette barre a déjà disparu de deux listes pendant
   des semaines sans que rien ne le dise — c'est le dernier endroit du dépôt où
   économiser des pixels.

   ⭐ LE COÛT EST ASSUMÉ ET S'ÉCRIT : la carte médiane passe de 241 à 294 px, donc
   de 3 à 2 cartes par écran en 390 px. Mieux vaut DEUX cartes lisibles que trois
   dont la barre est comprimée — c'est l'échange qu'a déjà fait la barre de
   dépliage de L5, et il est arbitré.

   ⚠️ LE BORNAGE EST DOUBLE, ET C'EST DÉLIBÉRÉ : `.doclist` (les 12 listes) ET la
   largeur mobile. Le même attribut inline vit sur 11 sites, dont SEPT fiches de
   document qui n'ont rien demandé — un geste d'apparence locale sur un sélecteur
   partagé a déjà déplacé une famille trois fois (T-UI, `.crud-toolbar-actions`,
   `.btn-filtres`). Elles restent hors d'atteinte, vérifié écran par écran.

   ⚠️ ON NE CORRIGE PAS LE MARKUP : `grid-column` y reste, inerte. Le retirer sur
   11 sites serait un second geste, sur des écrans hors périmètre — et le jour où
   `.crud-card-meta` passerait en grille, il redeviendrait juste. */
@media (max-width: 768px) {
    .doclist .crud-card-meta > .crud-card-meta-item[style*="grid-column"] {
        flex-basis: 100%;
    }
}

/* ══════════════════════════════════════════════════════════════════════════════
   ⛔ L7 — LE BOUTON D'ACTION NE REJOINDRA PAS LA LIGNE DES MONTANTS. REFUS ÉCRIT.
   ══════════════════════════════════════════════════════════════════════════════
   La maquette le proposait, et le gain semblait acquis : −58 px et 3 → 4 cartes
   par écran, mesurés en retirant la rangée d'actions. Trois chiffres l'ont tué,
   et ils sont écrits ICI pour que personne ne rouvre la question :

     ① SANS MARKUP, IL N'EST PAS ATTEIGNABLE. Le bloc d'actions est un FRÈRE de
        la rangée de paires, pas un enfant : aucun sélecteur ne l'y fait entrer.
        Simulation au moteur — il passe SEUL sur une nouvelle ligne, gain −2 px.
     ② AVEC LE CONTOURNEMENT (`display: contents` sur `.crud-card-meta`), le gain
        combiné tombe à −11 px et les cartes par écran ne bougent PAS (3 → 3),
        parce que la réparation ci-dessus reprend 53 des 64 px rendus. Et il
        coûte le FILET DE SÉPARATION des 12 listes : la boîte disparaît
        littéralement — mesurée à 0 × 0 px — donc ni le pointillé ni la marge ne
        sont plus peints.
     ③ AVEC MARKUP, il faudrait sortir `.crud-card-actions` dans la rangée de
        paires sur 12 fichiers : douze copies à tenir en cohérence, pour un gain
        devenu nul par ①-②.

   ⭐⭐ ET LA TROUVAILLE QUI VAUT AU-DELÀ DE CE REFUS — LE STYLE CALCULÉ DIT CE QUI
   EST DÉCLARÉ, LA GÉOMÉTRIE DIT CE QUI EST RENDU. Sous `display: contents`,
   `getComputedStyle` annonçait encore `border-top: dashed 1px` et
   `margin-top: 12.8px` alors que RIEN n'était peint : la boîte n'existait plus.
   ⚠️ Sans la mesure de géométrie (`getBoundingClientRect` → 0 × 0), j'aurais
   écrit « aucun changement d'aspect » DE BONNE FOI. C'est la famille du journal
   — une observation exacte qui porte sur autre chose — prise sur l'instrument
   même qui sert à la débusquer. ⇒ Pour un ASPECT, on mesure la GÉOMÉTRIE ; le
   style calculé ne répond qu'à « qu'a-t-on déclaré ? ». */


/* ══════════════════════════════════════════════════════════════════════════════
   L7 — LE PIED DE CARTE : actions à GAUCHE, montant à DROITE. Une seule rangée.
   ══════════════════════════════════════════════════════════════════════════════
   ⚠️ La forme vit dans `PiedCarte.razor` — les 11 listes la CONSOMMENT, aucune ne
   la décrit. Ce bloc ne fait que l'habiller ; le motif complet (et les deux voies
   écartées) est dans le composant.

   ⚠️⚠️ LE MONTANT DEVIENT LA VALEUR REINE, ET C'EST UNE NÉCESSITÉ, PAS UN GOÛT :
   à côté d'un bouton de 44 px, il valait 14,4 px en graisse 400 — il ne se lisait
   plus comme le chiffre qui compte. Il passe à la graisse du TITRE de carte (700)
   et à 15 px, mesuré au-dessus du plancher de lisibilité (11 px) : aucune
   exception à nommer. ⭐ On ne DÉPLACE pas un élément sans regarder ce que sa
   nouvelle place lui fait — un montant posé à côté d'un bouton devient un détail
   du bouton si rien ne l'en distingue.

   ⚠️ `flex-wrap: wrap` N'EST PAS UN ORNEMENT : sur un BROUILLON, le pied porte
   DEUX boutons (« Modifier » + « Supprimer », 236 px sur 262 mesurés) et le
   montant n'a plus la place. Il retombe alors à la ligne SUIVANTE au lieu de se
   superposer — c'est précisément ce que la voie sans markup (`position:absolute`)
   ne savait pas faire : elle chevauchait, mesuré. Le cas n'existe dans AUCUNE
   donnée ; il a fallu le FABRIQUER au moteur pour le voir. */
@media (max-width: 768px) {
    .doclist .crud-card-pied {
        display: flex;
        align-items: center;
        justify-content: space-between;
        flex-wrap: wrap;
        gap: 8px;
    }

    .doclist .crud-card-pied-actions {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        flex-wrap: wrap;
    }

    .doclist .crud-card-pied-montant {
        margin-left: auto;
        font-size: 15px;
        font-weight: 700;
        color: var(--ink);
        white-space: nowrap;
    }
}

/* ==========================================================================
   GABARIT AIDE — DIGISTORM ACADÉMIE (tranche A1)
   Préfixe `aide-`, TOUT est borné à `.aide-page` : aucun écran métier ne peut
   être atteint par une règle de ce bloc.

   ⚠️⚠️ CE BLOC N'EST PAS UNE PAGE, C'EST UN VOCABULAIRE. Cinq formes, et cinq
   seulement, à l'usage de A2…A6 :
     ① .aide-chaine   — la suite d'étapes (un écran, un geste, un effet)
     ② .aide-compare  — le tableau à deux colonnes
     ③ .aide-fiche    — la fiche d'écran (où · à quoi · ce que ça change)
     ④ .aide-note     — l'encadré, TROIS tons
     ⑤ .aide-ecran / .aide-bouton — les deux citations, en ligne
   ⭐ Si une tranche a besoin d'une sixième forme, elle l'AJOUTE ICI. Une forme
   inventée dans un article ne serait vue par personne, et le guide finirait
   avec six vocabulaires — c'est très exactement ce que A1 existe pour éviter.

   ⚠️ AUCUNE COULEUR EN DUR, AUCUN RAYON EN DUR, AUCUNE OMBRE HORS ÉCHELLE :
   ce bloc n'ajoute rien aux plafonds de G3 (406 couleurs), G-R1 (9 rayons) et
   G-O1 (29 élévations) — il ne consomme que des tokens existants.

   ⭐⭐ LES COUPLES COULEUR / FOND SONT MESURÉS, sur le critère DURCI de L1d
   (≥ 4,60, et non le seuil AA de 4,50). Le harnais est aveugle au contraste :
   ces chiffres sont la seule chose qui en réponde. Calculés sur les valeurs du
   :root de ce fichier :
     · --ink-soft   sur --surface 8,17 · --surface-alt 7,67 · --teal-soft 7,16
     · --slate      sur --surface 5,64 · --surface-alt 5,29 · --paper 4,96
     · --teal-dark  sur --surface 5,56 · --surface-alt 5,22 · --teal-soft 4,87 · --paper 4,88
     · --amber-dark sur --amber-soft 4,78 · --amber-softer 4,86
     · --red-dark   sur --red-soft 4,96
     · --ink        sur --surface-alt 15,50 · --teal-soft 14,47
   ⚠️ La valeur la plus basse du bloc est 4,78 — au-dessus du critère durci.

   ⚠⚠ --teal ET NON --brand, ET CE N'EST PAS UN DÉTAIL. La famille `--brand*` est
   RÉSERVÉE À LA TRANCHE L8 (« employer --brand là où --teal joue l'accent
   d'APPLICATION ») : elle figure dans la liste d'attente de PaletteTests, qui a
   ROUGI quand ce bloc l'a consommée — à juste titre.
   ⭐ S'en servir ici aurait créé UN ÎLOT DÉJÀ BASCULÉ : L8 aurait balayé tout
   l'écran sauf le guide, sans que rien ne le signale. L'aide porte donc l'accent
   COURANT de l'application, et suivra L8 avec le reste.
   ⚠️ --teal NE PORTE JAMAIS DE TEXTE ICI : mesuré à 3,98 sur --surface, il est
   SOUS le seuil. Le texte est en --teal-dark ; --teal ne sert qu'aux bordures et
   au liseré de la fiche, où aucun seuil de lisibilité ne s'applique.

   ⚠️⚠️ LE TON N'EST PAS UNE COULEUR, C'EST UN SENS (doctrine L1a/L1b, garde G1) :
     · REPÈRE    = vert de marque — un raccourci, jamais une alerte ;
     · ATTENTION = ambre — la conséquence d'un geste. ⚠️ L'ambre ne signale
       JAMAIS un problème dans ce dépôt : G1 le refuse explicitement ;
     · DROIT     = rouge — c'est le sens ⑥ de --red, « REFUS ». Un écran qu'on
       ne verra pas sans un droit est un refus, pas une conséquence. ⭐ Et c'est
       pourquoi la ligne « Droit requis » d'une fiche porte le MÊME ton : une
       notion, une couleur — deux teintes pour la même chose se lisent comme
       deux notions.
   ========================================================================== */

.aide-page {
    /* LA LARGEUR DE LA PAGE N'EST PAS LA LARGEUR DU TEXTE, et c'est ce qui rend cet
       elargissement SANS RISQUE. Un tableau comparatif, une fiche a deux colonnes et une
       chaine gagnent a s'etendre ; une PHRASE, non. Les deux sont decouples : la page est
       large, la prose est plafonnee juste en dessous.

       MESURE AVANT D'ELARGIR, sur l'ecran reel (Chromium, fenetre 1650 px) : en retirant le
       plafond de page, la colonne passe a 1370 px et la prose RESTE a 601 px = 80 caracteres.
       Le plafond `68ch` la borne independamment. La crainte « a pleine largeur le texte ferait
       200 caracteres » etait donc INFONDEE : c'est la mesure qui l'a dit, pas une opinion.

       CE QUE L'ELARGISSEMENT REND, MESURE (1650 px) : le tableau passe de 878 a 1368 px, et le
       tableau des 7 volets cesse ENTIEREMENT de casser ses cellules (10 sur 14 depassaient
       60 px de haut, il n'en reste 0). Les articles raccourcissent de 4 a 8 %.
       AVANT : 880 px fixes laissaient 732 px de vide a 1920 (45 % de la zone), 462 a 1650
       (34 %), 252 a 1440 (22 %), 92 a 1280 (9 %), 0 des 1100.

       LE PADDING A 10 px A ETE DEMANDE ET REFUSE, ET LE CHIFFRE EST LA RAISON :
       `.content-area` porte 24 px de chaque cote ; les ramener a 10 ne rend que 28 px, soit
       6 % des 462 px de vide. Le vide ne venait PAS du padding, il venait de ce plafond de
       880. Ecrit ici pour que la demande ne revienne pas sans reponse.

       POURQUOI 1400 ET NON « TOUT » : au-dela, les trois colonnes d'un tableau comparatif
       s'etirent sans rien gagner et la page perd son centre. A 1920 la zone utile fait
       1612 px : 212 px restent en marge, volontairement.
       RIEN NE BOUGE SOUS 1100 px : la colonne y valait deja toute la zone (mesure : 0 px
       perdu a 1100 et a 900), et en 390 px la lecture est inchangee. */
    max-width: 1400px;
    margin: 0 auto;
    padding-bottom: 40px;
}

/* DIFFERE, ET INSCRIT PLUTOT QU'OUBLIE : LE SOMMAIRE EN COLONNE DE GAUCHE.
   Mesure : `texte 640 + sommaire 240 + gouttiere 24 = 904 px` tient a 1280 (il reste 68 px),
   1440, 1650 et 1920, mais PAS a 1100. Il faudrait donc un SECOND seuil (~1180 px) sous
   lequel le sommaire repasse au-dessus, et un sommaire CONDENSE : l'actuel fait 857 px de
   haut pour 5 chapitres et 6 liens.
   Decision de Walid : differe tant qu'il n'y a que 5 articles. Le jour ou le guide en
   portera vingt, la colonne laterale vaudra son second seuil. */

/* LE PLAFOND DE PROSE : il ne s'applique QU'AU TEXTE COURANT, jamais aux formes.
   C'EST LUI QUI REND LA PAGE LARGE ACCEPTABLE, et il est desormais GARDE
   (AideCatalogueTests, LE_PLAFOND_DE_PROSE_*) : la garde exige qu'il reste exprime en `ch`,
   qu'il ne depasse pas 70, et que TOUTE classe posee sur un <p> d'article y soit couverte.
   La liste des classes est DERIVEE du balisage, jamais tenue a la main. Sans ce plafond,
   elargir la page ferait des lignes de 116 puis 180 caracteres.
   MESURÉ après pose, sur l'écran : 68ch = 601 px = 80 CARACTÈRES à 14,5 px, et
   560 px = 80 caractères à 13,5 px — le compte est le même aux deux tailles, ce qui est
   tout l'intérêt de l'unité `ch` sur un `max-width` en pixels. */
.aide-page .aide-chapo,
.aide-page .aide-p,
.aide-page .aide-chap-promesse,
.aide-page .aide-sous { max-width: 68ch; }

/* ---- en-tête et sommaire ------------------------------------------------ */

.aide-page .aide-entete { margin-bottom: 18px; }

.aide-page .aide-eyebrow {
    display: block;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--teal-dark);
}

.aide-page .aide-titre {
    /* ⚠️ PAS de police de titre (Fraunces) : elle est réservée à quatre
       sélecteurs nommés (TypographieTests), et la cinquième place ne se prend
       pas pour un écran de texte. La police de corps porte très bien un titre. */
    font-size: 26px;
    font-weight: 800;
    letter-spacing: -.015em;
    color: var(--ink);
    margin: 4px 0 6px;
}

.aide-page .aide-sous { margin: 0; font-size: 14px; color: var(--slate); }

.aide-page .aide-sommaire { display: grid; gap: 12px; }

.aide-page .aide-chap,
.aide-page .aide-demos {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-14);
    padding: 16px 18px;
}

.aide-page .aide-chap-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
}

.aide-page .aide-chap-titre {
    font-size: 17px;
    font-weight: 700;
    color: var(--ink);
    margin: 0;
}

.aide-page .aide-chap-avenir {
    font-size: 11.5px;
    font-weight: 700;
    padding: 3px 9px;
    border-radius: var(--r-20);
    background: var(--amber-soft);
    color: var(--amber-dark);
}

.aide-page .aide-chap-compte {
    font-size: 11.5px;
    font-weight: 700;
    padding: 3px 9px;
    border-radius: var(--r-20);
    background: var(--teal-soft);
    color: var(--teal-dark);
}

.aide-page .aide-chap-promesse {
    margin: 6px 0 0;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--ink-soft);
}

.aide-page .aide-chap-liste { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; }

.aide-page .aide-chap-lien {
    display: block;
    text-decoration: none;
    background: var(--surface-alt);
    border: 1px solid var(--line-soft);
    border-radius: var(--r-11);
    padding: 10px 12px;
}

.aide-page .aide-chap-lien:hover { border-color: var(--teal); background: var(--teal-soft); }
.aide-page .aide-chap-lien-titre { display: block; font-size: 14px; font-weight: 700; color: var(--ink); }
.aide-page .aide-chap-lien-resume { display: block; font-size: 12.5px; color: var(--slate); margin-top: 2px; }

/* ---- fil, corps d'article, retour --------------------------------------- */

.aide-page .aide-fil { font-size: 12.5px; color: var(--slate); margin-bottom: 10px; }
.aide-page .aide-fil a { color: var(--teal-dark); text-decoration: none; font-weight: 600; }
.aide-page .aide-fil a:hover { text-decoration: underline; }
.aide-page .aide-fil-sep { margin: 0 6px; }
.aide-page .aide-fil-ici { color: var(--ink-soft); font-weight: 600; }

.aide-page .aide-chapo { font-size: 14.5px; line-height: 1.6; color: var(--ink-soft); margin: 0 0 18px; }
.aide-page .aide-p { font-size: 14px; line-height: 1.65; color: var(--ink-soft); margin: 0 0 10px; }

.aide-page .aide-h2 {
    font-size: 18px;
    font-weight: 700;
    color: var(--ink);
    margin: 28px 0 8px;
    padding-top: 18px;
    border-top: 1px solid var(--line-soft);
}

.aide-page .aide-retour { margin-top: 26px; }

.aide-page .aide-retour-lien {
    display: inline-block;
    font-size: 13px;
    font-weight: 700;
    text-decoration: none;
    color: var(--teal-dark);
    background: var(--teal-soft);
    border-radius: var(--r-9);
    padding: 8px 14px;
}

/* ---- le trait commun aux icônes ----------------------------------------- */

.aide-page .aide-ico { flex: none; vertical-align: -.15em; }

/* ---- ⑤ LES CITATIONS ---------------------------------------------------- */

.aide-page .aide-ecran {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    text-decoration: none;
    font-size: 12.5px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: var(--r-8);
    background: var(--teal-soft);
    color: var(--teal-dark);
    border: 1px solid var(--teal-soft);
}

.aide-page .aide-ecran:hover { border-color: var(--teal); }
.aide-page .aide-ecran-volet { font-weight: 600; opacity: .85; }
.aide-page .aide-ecran-sep { opacity: .55; }

.aide-page .aide-ecran--inconnu {
    background: var(--red-soft);
    color: var(--red-dark);
    border-color: var(--red-soft);
}

.aide-page .aide-bouton {
    /* ⚠️ Le libellé d'un bouton se cite AU CARACTÈRE PRÈS, flèche comprise :
       c'est ce que le lecteur cherche des yeux. La forme imite donc un bouton
       plutôt qu'un mot souligné. */
    display: inline-block;
    font-size: 12.5px;
    font-weight: 700;
    padding: 2px 9px;
    border-radius: var(--r-7);
    background: var(--surface-alt);
    color: var(--ink);
    border: 1px solid var(--line);
    white-space: nowrap;
}

/* ---- l'enveloppe commune aux quatre formes de bloc ----------------------- */

.aide-page .aide-forme { margin: 14px 0 20px; }

.aide-page .aide-forme-titre {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--slate);
    margin-bottom: 8px;
}

/* ---- ① LA CHAÎNE D'ÉTAPES ----------------------------------------------- */

/* ⚠️⚠️ LA CHAÎNE EST VERTICALE À TOUTE LARGEUR, ET C'EST UNE DÉCISION MESURÉE,
   PAS UN REPLI MOBILE.
   ⭐⭐ LA MESURE, prise sur l'ÉCRAN RÉEL en forçant la variante horizontale
   (fenêtre 1650 px, colonne de lecture 880 px, les 5 étapes de l'article de
   démonstration) : 168 px par étape, soit 138 px de boîte utile. Dedans,
     · « → Transférer en livraison » mesure 170 px → DÉBORDE de 32 px ;
     · « → Transférer en facture »   mesure 163 px → DÉBORDE de 25 px ;
     · la citation d'écran passe de 1 à 2 lignes (25 px → 44 px).
   ⚠ Un libellé de bouton ne se coupe PAS (`white-space: nowrap`) : c'est ce
   que le lecteur cherche des yeux à l'écran. Il ne se replie donc pas, il
   dépasse — et une chaîne dont deux gestes sur cinq sortent de leur carte ne
   se lit plus comme une chaîne.
   ⚠ J'avais d'abord ÉCRIT « trois lignes » sans l'avoir mesuré : faux. Le
   défaut réel n'est pas le repli, c'est le DÉBORDEMENT — et il ne se serait pas
   vu sur une capture, seulement au style calculé.
   ⭐ Et la verticale a un second avantage, structurel : UN SEUL agencement à
   toutes les largeurs, donc aucun second rendu à masquer — la leçon de
   DoubleRenduTests, qui a coûté trois rouges sur 2 629. */
.aide-page .aide-chaine {
    list-style: none;
    margin: 0;
    padding: 0;
    counter-reset: aide-etape;
    display: grid;
    gap: 10px;
}

.aide-page .aide-etape {
    /* ⚠️ La numérotation est faite par le CSS : une étape insérée au milieu se
       renumérote toute seule. Un index passé en paramètre serait la première
       chose à se périmer, et rien ne le dirait. */
    counter-increment: aide-etape;
    position: relative;
    display: flex;
    gap: 12px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-12);
    padding: 12px 14px;
}

.aide-page .aide-etape:not(:last-child)::after {
    /* le rail qui relie deux étapes — aligné sur le centre de la pastille */
    content: "";
    position: absolute;
    left: 26px;
    top: 100%;
    width: 2px;
    height: 10px;
    background: var(--line);
}

.aide-page .aide-etape-num {
    flex: none;
    width: 26px;
    height: 26px;
    border-radius: var(--r-20);
    background: var(--teal-soft);
    color: var(--teal-dark);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-weight: 700;
}

.aide-page .aide-etape-num::before { content: counter(aide-etape); }
.aide-page .aide-etape-corps { min-width: 0; }
.aide-page .aide-etape-titre { font-size: 14.5px; font-weight: 700; color: var(--ink); }
.aide-page .aide-etape-ou { margin-top: 5px; }
.aide-page .aide-etape-geste { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 6px; }
.aide-page .aide-etape-effet { margin-top: 6px; font-size: 13px; line-height: 1.5; color: var(--ink-soft); }

/* ---- ② LE TABLEAU COMPARATIF -------------------------------------------- */

.aide-page .aide-compare-cadre {
    border: 1px solid var(--line);
    border-radius: var(--r-12);
    /* ⚠️ `clip` et JAMAIS `hidden` : `hidden` crée un conteneur de défilement,
       et casserait silencieusement tout `position: sticky` posé à l'intérieur.
       Le piège est déjà documenté sur `.crud-table-card`. */
    overflow: clip;
    background: var(--surface);
}

.aide-page .aide-compare { width: 100%; border-collapse: collapse; }

.aide-page .aide-compare th,
.aide-page .aide-compare td {
    text-align: left;
    vertical-align: top;
    padding: 10px 12px;
    font-size: 13.5px;
    line-height: 1.5;
    border-bottom: 1px solid var(--line-soft);
}

.aide-page .aide-compare thead th {
    background: var(--thead-bg);
    color: var(--slate);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
}

.aide-page .aide-compare tbody th { color: var(--ink); font-weight: 700; width: 30%; }
.aide-page .aide-compare tbody td { color: var(--ink-soft); }
.aide-page .aide-compare tbody tr:last-child th,
.aide-page .aide-compare tbody tr:last-child td { border-bottom: 0; }

/* ---- ③ LA FICHE D'ÉCRAN -------------------------------------------------- */

.aide-page .aide-fiche {
    background: var(--surface);
    border: 1px solid var(--line);
    border-left: 3px solid var(--teal);
    border-radius: var(--r-12);
    padding: 14px 16px;
}

.aide-page .aide-fiche-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }

.aide-page .aide-fiche-ico {
    width: 28px;
    height: 28px;
    flex: none;
    border-radius: var(--r-8);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--teal-soft);
    color: var(--teal-dark);
}

.aide-page .aide-fiche-nom { font-size: 15px; font-weight: 700; color: var(--ink); }

.aide-page .aide-fiche-corps {
    margin: 0;
    display: grid;
    grid-template-columns: 132px minmax(0, 1fr);
    gap: 7px 14px;
}

.aide-page .aide-fiche-corps dt {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--slate);
    padding-top: 2px;
}

.aide-page .aide-fiche-corps dd { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--ink-soft); }
.aide-page .aide-fiche-chemin { color: var(--teal-dark); font-weight: 700; text-decoration: none; }
.aide-page .aide-fiche-chemin:hover { text-decoration: underline; }
.aide-page .aide-fiche-inconnu { color: var(--red-dark); font-weight: 700; }

.aide-page .aide-fiche-droit,
.aide-page .aide-fiche-avenir {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12.5px;
    font-weight: 700;
    padding: 3px 9px;
    border-radius: var(--r-8);
    background: var(--red-soft);
    color: var(--red-dark);
}

.aide-page .aide-fiche-avenir {
    margin-top: 10px;
    background: var(--amber-soft);
    color: var(--amber-dark);
}

/* ---- ④ L'ENCADRÉ --------------------------------------------------------- */

/* LA BORDURE EST FONCEE ET L'OMBRE EST LE CRAN DISCRET -- demande de Walid, chiffres a l'appui.

   CE QUI NE MARCHAIT PAS, MESURE : la bordure valait le FOND du bloc lui-meme, soit un
   contraste de 1,00 / 1,02 / 1,02 contre le fond de page. Elle n'existait pas.
   Pour comparaison, le filet des cartes (--line) rend 1,15 : discret, mais pas nul.

   LES TONS FONCES, ET POURQUOI EUX PLUTOT QUE LES TONS MOYENS (seuil composant 3,00) :
     --teal-dark  4,88 sur --paper   |  --teal   3,49
     --amber-dark 4,70               |  --amber  2,58  <-- SOUS LE SEUIL
     --red-dark   5,06               |  --red    3,46
   L'ambre moyen ne passe pas. Prendre le ton fonce PARTOUT donne aux trois tons la meme
   force et evite une exception a expliquer.

   L'OMBRE : --sh-sm, un cran de l'echelle existante, AUCUNE valeur creee.
   ⚠️ --sh-lg EST REFUSE, ET LE MOTIF N'EST PAS CELUI DES BOUTONS. Sur un bouton de 31 px il
   avait ete refuse parce qu'il etendait l'ombre de 40 px, soit 129 % de la hauteur (bloc des
   boutons, plus haut dans ce fichier). Un encadre est plus haut -- mesure : 70 px au bureau,
   91 a 135 px en mobile -- donc 40 px n'y font plus que 30 a 57 %. LA PROPORTION NE TRANCHE
   DONC PLUS. Ce qui tranche est autre chose, et c'est mesure : DEUX ENCADRES EMPILES SONT
   SEPARES DE 20 px, et une ombre de 40 px DEBORDE sur le bloc suivant. --sh-sm en etend 3 :
   il tient. Le relief vient donc de la BORDURE ; l'ombre ne fait que decoller le bloc.

   ⚠️ LE TON « DROIT » RESTE ROUGE, et ce n'est plus seulement une intention : le selecteur
   est desormais inscrit dans SensProbleme (PaletteTests G1), avec son motif -- un ecran
   qu'on ne verra pas sans un droit est un REFUS, le sens ⑥ de --red. Lui donner une autre
   famille ferait rougir la garde de sens, a juste titre. */
.aide-page .aide-note {
    display: flex;
    gap: 10px;
    padding: 12px 14px;
    border-radius: var(--r-12);
    border: 1px solid var(--line);
    background: var(--surface);
    box-shadow: var(--sh-sm);
}

.aide-page .aide-note-ico { flex: none; padding-top: 1px; }
.aide-page .aide-note-titre { font-size: 13.5px; font-weight: 700; margin-bottom: 2px; }
.aide-page .aide-note-texte { font-size: 13.5px; line-height: 1.6; color: var(--ink-soft); }

.aide-page .aide-note--repere { background: var(--teal-soft); border-color: var(--teal-dark); }
.aide-page .aide-note--repere .aide-note-ico,
.aide-page .aide-note--repere .aide-note-titre { color: var(--teal-dark); }

.aide-page .aide-note--attention { background: var(--amber-softer); border-color: var(--amber-dark); }
.aide-page .aide-note--attention .aide-note-ico,
.aide-page .aide-note--attention .aide-note-titre { color: var(--amber-dark); }

.aide-page .aide-note--droit { background: var(--red-soft); border-color: var(--red-dark); }
.aide-page .aide-note--droit .aide-note-ico,
.aide-page .aide-note--droit .aide-note-titre { color: var(--red-dark); }

/* ---- LES MARQUES DE RELECTURE (brouillons A5) ---------------------------
   ⚠️⚠️ CE N'EST PAS UNE SIXIÈME FORME. Les cinq formes du guide sont closes ; ceci
   est une ANNOTATION, et elle est FAITE POUR DISPARAÎTRE.

   Walid a demandé à RELIRE plutôt qu'à dicter. Un brouillon porte donc, en clair, la
   PROVENANCE de chaque affirmation : ⭐ cité (avec sa source) · ⚙️ dérivé du code (avec
   où on l'a lu) · ❓ proposé (ce que l'agent avance de lui-même).
   ⭐⭐ ET ELLES NE SURVIVENT PAS À LA VALIDATION : elles ne s'affichent que sous
   `.aide-brouillon`, classe posée par la page quand l'article est un brouillon. Passer
   `Brouillon: false` dans le catalogue les éteint TOUTES d'un coup — le client de Walid
   ne lira jamais un guide constellé de références internes, et personne n'a à penser à
   les retirer une par une. ⚠️ Un nettoyage manuel, lui, se serait oublié au 3ᵉ article.

   ⚠️ CONTRASTE MESURÉ : --slate tient sur les QUATRE fonds où ces marques atterrissent —
   --surface 5,64 · --surface-alt 5,29 · --teal-soft 4,94 · --amber-softer 5,13 ·
   --red-soft 4,86. Et --red-dark, réservé au ❓, rend 5,76 sur --surface et 4,96 sur
   --red-soft. Tout est au-dessus du critère durci de L1d (4,60).
   -------------------------------------------------------------------------- */

.aide-page .aide-src { display: none; }

.aide-page.aide-brouillon .aide-src {
    display: block;
    margin: 4px 0 0;
    padding-left: 8px;
    border-left: 2px solid var(--line);
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--slate);
}

/* ⚠️ Le ❓ est le SEUL à porter une couleur : c'est là que la relecture doit se poser.
   Les deux autres marques sont des références, pas des questions. */
.aide-page.aide-brouillon .aide-src--propose {
    border-left-color: var(--red);
    color: var(--red-dark);
    font-weight: 600;
}

.aide-page .aide-liste { margin: 0; padding-left: 18px; display: grid; gap: 8px; }
.aide-page .aide-liste li { font-size: 13.5px; line-height: 1.6; }

.aide-page .aide-chap-brouillon {
    margin-left: 8px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    padding: 2px 7px;
    border-radius: var(--r-20);
    background: var(--amber-soft);
    color: var(--amber-dark);
}

/* ⚠️ Le renvoi d'un article vers un autre. Il ferme le chemin : un lecteur qui vient de
   comprendre « pourquoi deux flux » veut savoir « et alors ? » — la réponse est l'article
   suivant, pas la fin de la page. */
.aide-page .aide-suite {
    margin-top: 22px;
    padding-top: 14px;
    border-top: 1px solid var(--line-soft);
    font-size: 13.5px;
    color: var(--slate);
}

.aide-page .aide-suite a { color: var(--teal-dark); font-weight: 700; text-decoration: none; }
.aide-page .aide-suite a:hover { text-decoration: underline; }

/* ---- LA RECHERCHE DU GUIDE ----------------------------------------------
   Formulaire GET : aucun circuit, le resultat est une adresse.
   CONTRASTE MESURE : --ink sur --surface 16,53 · --slate sur --surface 5,64 ·
   --teal-dark sur --surface 5,56 · --ink sur --amber-soft 15,01 (la surbrillance).
   -------------------------------------------------------------------------- */

.aide-page .aide-recherche {
    display: flex;
    gap: 8px;
    margin: 0 0 18px;
    max-width: 520px;
}

.aide-page .aide-recherche-champ {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 14px;
    padding: 9px 12px;
    border-radius: var(--r-9);
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--ink);
}

.aide-page .aide-recherche-champ::placeholder { color: var(--slate); }
.aide-page .aide-recherche-champ:focus { outline: 2px solid var(--teal); outline-offset: 1px; border-color: var(--teal); }

.aide-page .aide-recherche-btn {
    flex: none;
    font-size: 13px;
    font-weight: 700;
    padding: 9px 16px;
    border-radius: var(--r-9);
    border: 1px solid var(--teal-soft);
    background: var(--teal-soft);
    color: var(--teal-dark);
    cursor: pointer;
}

.aide-page .aide-recherche-btn:hover { border-color: var(--teal); }

.aide-page .aide-resultats { margin-bottom: 22px; }
.aide-page .aide-res-compte { font-size: 13px; font-weight: 700; color: var(--slate); margin: 0 0 10px; }
.aide-page .aide-res-liste { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }

.aide-page .aide-res-lien {
    display: block;
    text-decoration: none;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-11);
    padding: 12px 14px;
}

.aide-page .aide-res-lien:hover { border-color: var(--teal); background: var(--surface-alt); }
.aide-page .aide-res-titre { display: block; font-size: 14.5px; font-weight: 700; color: var(--ink); }

/* L'EXTRAIT : c'est LUI qui evite la fausse piste. Il est plafonne comme la prose,
   parce que c'en est. */
.aide-page .aide-res-extrait {
    display: block;
    margin-top: 4px;
    max-width: 68ch;
    font-size: 13px;
    line-height: 1.55;
    color: var(--ink-soft);
}

.aide-page .aide-res-extrait mark {
    background: var(--amber-soft);
    color: var(--ink);
    font-weight: 700;
    padding: 0 2px;
    border-radius: var(--r-3);
}

.aide-page .aide-res-retour { margin: 12px 0 0; font-size: 13px; }
.aide-page .aide-res-retour a { color: var(--teal-dark); font-weight: 700; text-decoration: none; }
.aide-page .aide-res-retour a:hover { text-decoration: underline; }

/* ---- LE MOBILE ----------------------------------------------------------
   ⚠️⚠️ C'EST LÀ QUE L'AIDE SE LIT : on l'ouvre sur un téléphone, en cherchant
   quoi faire, l'écran métier dans l'autre main. Le breakpoint est celui du
   dépôt (768 px) — mesuré : 25 blocs `max-width: 768px` dans ce fichier avant
   celui-ci, aucun autre seuil.
   ⚠️ La largeur utile réelle en 390 px n'est PAS 390 : `.content-area` porte
   1,5 rem de chaque côté, soit 342 px. Toutes les formes sont mesurées à 342.
   -------------------------------------------------------------------------- */

@media (max-width: 768px) {
    .aide-page .aide-titre { font-size: 22px; }
    .aide-page .aide-chap,
    .aide-page .aide-demos { padding: 14px; }
    .aide-page .aide-h2 { font-size: 16.5px; margin-top: 22px; }

    /* la fiche passe en une colonne : 132 px d'étiquette + le contenu ne
       tiennent pas dans 342 px sans couper les mots */
    .aide-page .aide-fiche-corps { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .aide-page .aide-fiche-corps dt { padding-top: 8px; }

    /* ⚠️ LE TABLEAU DEVIENT DES CARTES — par `content: attr(data-label)`, la
       technique déjà employée par `.crud-list` dans tout le dépôt. UN SEUL
       balisage : jamais un second rendu masqué. */
    .aide-page .aide-compare-cadre { border: 0; background: none; }
    .aide-page .aide-compare thead { display: none; }

    .aide-page .aide-compare tr {
        display: block;
        background: var(--surface);
        border: 1px solid var(--line);
        border-radius: var(--r-12);
        margin-bottom: 10px;
    }

    .aide-page .aide-compare th,
    .aide-page .aide-compare td { display: block; width: auto; border-bottom: 0; padding: 8px 12px; }

    .aide-page .aide-compare tbody th {
        background: var(--surface-alt);
        border-bottom: 1px solid var(--line-soft);
        border-radius: var(--r-12) var(--r-12) 0 0;
    }

    .aide-page .aide-compare tbody td + td { border-top: 1px solid var(--line-soft); }

    .aide-page .aide-compare tbody td::before {
        content: attr(data-label);
        display: block;
        font-size: 10.5px;
        font-weight: 700;
        letter-spacing: .06em;
        text-transform: uppercase;
        color: var(--slate);
        margin-bottom: 3px;
    }

    /* ⚠️ Le libellé de bouton est le SEUL élément qu'on laisse déborder plutôt
       que couper : « ⇪ Importer des BL » coupé en deux ne se reconnaît plus à
       l'écran. Il se replie donc sur deux lignes plutôt que de forcer un
       défilement horizontal de la page.
       ⭐ MESURÉ en 390 px : le plus large (« → Transférer en livraison », 170 px)
       tient sur UNE ligne dans les 342 px utiles — le repli ne sert donc que de
       filet, il ne se déclenche pas aujourd'hui. */
    .aide-page .aide-bouton { white-space: normal; }

    /* ⚠️ LA CITATION D'ÉCRAN EST UN LIEN, donc une CIBLE AU DOIGT. Mesurée à
       25 px de haut au bureau — acceptable à la souris, court au pouce. Elle est
       remontée ici à 33 px (mesuré après pose), sans changer sa forme en ligne dans une phrase.
       ⚠ Le vrai point de navigation du sommaire, lui, mesure 83 px (`.aide-chap-lien`,
       mesuré) : c'est celui-là qui porte le parcours, la citation reste secondaire. */
    .aide-page .aide-ecran { padding: 6px 9px; }

    /* C'EST AU TELEPHONE QU'ON CHERCHE. Le champ prend toute la largeur ; le bouton reste
       a cote plutot que dessous (mesure : un champ de 40 px coute 5 % d'un ecran, deux en
       couteraient 10 % avant meme le premier resultat). */
    .aide-page .aide-recherche { max-width: none; }
    .aide-page .aide-recherche-btn { padding: 9px 12px; }
}
