/* ------------------------------------------------------------------ socle */

/* Palette.
 *
 * Deux partis pris, contre l'habitude.
 *
 * 1. Le fond n'est pas gris mais PAPIER — une pointe de chaleur (#F6F4F0 plutôt
 *    que #F5F5F7). C'est une nuance de deux points, invisible isolément, mais
 *    l'écran cesse d'avoir l'air d'un tableur. On facture sur du papier depuis
 *    six siècles ; l'œil le sait.
 *
 * 2. L'accent est le vert de la marque, et il revient de loin : il avait été
 *    retiré parce qu'une concurrente s'en était emparée, puis remplacé par un
 *    pétrole qui lisait certes mieux mais n'appelait plus au geste. Un bleu
 *    d'administration convient à un document opposable ; il ne convient pas au
 *    bouton qu'on veut voir cliquer.
 *
 *    Ce vert coûte du contraste — 5,5 sur le papier, contre 8,9 au pétrole. Il
 *    reste au-dessus du seuil AA, mais la marge est mince, d'où le second ton :
 *    « accent-profond » (8,2) porte le texte long et le survol, l'accent porte
 *    les aplats et les états. Ne pas les intervertir.
 *
 * Le rouge d'erreur reste franc : c'est la seule couleur qui doit interrompre.
 */
:root {
    /* ------------------------------------------------- les deux échelles

       L'ESPACEMENT. Les mêmes huit valeurs que « refonte.css », déclarées ici
       aussi et à dessein : la feuille de la conception est versionnée comme une
       paire et REMPLACÉE à chaque livraison. Cinq cent quarante-six
       déclarations s'appuient sur ces jetons ; une livraison qui en
       renommerait un les casserait toutes, sans rien afficher d'anormal —
       seulement une page qui se déplie. Déclarés ici, ils survivent, et les
       valeurs identiques ne créent aucun conflit.

       LA TYPOGRAPHIE. Cinq crans, et il n'y en avait aucun : trente-trois
       tailles cohabitaient, dont douze entre 9 et 15 px, demi-pixels compris.
       Personne ne distingue 12,5 de 13 — mais l'œil sent qu'aucune décision
       n'a été prise, exactement comme il sent une phrase écrite par défaut.

       · 11  ce qui se lit en second : mentions, pastilles, unités
       · 13  le texte ordinaire, et l'immense majorité de l'interface
       · 15  ce qui se lit en premier dans un bloc : titres de carte, intitulés
       · 20  les titres de section, et les nombres de second rang
       · 28  le chiffre qu'on vient chercher

       Le saut de 15 à 20 est volontaire : entre les deux, rien n'est ni un
       titre ni un texte, et les tailles intermédiaires ne servaient qu'à
       éviter de trancher. */
    --rf-1: 4px;
    --rf-2: 8px;
    --rf-3: 12px;
    --rf-4: 16px;
    --rf-5: 20px;
    --rf-6: 24px;
    --rf-8: 32px;
    --rf-10: 48px;

    --t-1: 11px;
    --t-2: 13px;
    --t-3: 15px;
    --t-4: 20px;
    --t-5: 28px;

    /* LE THÈME EST UN CHOIX, DONC « color-scheme » AUSSI.

       Laissé à « light dark », le navigateur décide seul de la teinte des
       ascenseurs, des menus déroulants natifs et du sélecteur de date. Un
       titulaire qui choisit le CLAIR sur une machine en sombre obtenait donc
       une application claire à ascenseurs sombres. Le jeton suit désormais
       l'attribut, comme tout le reste. */
    color-scheme: light;

    /* ============================================================ ENCRE
       LA COULEUR NE SERT QU'À L'INFORMATION. Décision du produit, 4 septembre
       2026. Palette REFAITE le 7 septembre, et dérivée cette fois par un
       solveur : « tools/palette/palette.py ».

       Quatre teintes portent un SENS ici : vert « réglé » à 149°, ambre
       « partiel » à 37°, rouge « en retard » à 4°, indigo « nous devons » à
       251°. Le fond et la marque sortent de ces angles — sur un écran où le vert
       signifie payé, un bouton vert concurrence l'information, et l'œil ne sait
       plus si la couleur qu'il voit est un état ou un ornement. Le fond est donc
       un gris froid à 2,7 % de chroma, et le geste primaire EST l'encre.

       CE QUE LA VERSION DU 4 SEPTEMBRE AVAIT MANQUÉ, et que le solveur a trouvé
       en une exécution :

         — ses propres rapports étaient FAUX de trois dixièmes. Elle annonçait
           « ok sur pastille 4,8 » et « warn 4,8 » ; mesuré sur la couleur
           réellement rendue — l'encre aplatie sur son voile, et non l'encre pure
           — c'était 4,47 et 4,48, donc SOUS le seuil. Juger une pastille sur son
           encre pure surestime le contraste de plusieurs dixièmes ;
         — la limite d'un CONTRÔLE n'existait pas comme notion. Les champs
           portaient « line-strong », soit 1,47, quand WCAG 2.1 §1.4.11 exige
           3,0 pour la limite visuelle d'un composant. Voir « line-control » ;
         — une carte ne se détachait de sa page qu'à 1,07. Sur un écran de
           mauvaise résolution, la page entière est un aplat blanc qui éblouit et
           où plus rien n'a de bord.

       LE FOND EST DÉSORMAIS L'ENTRÉE DU CALCUL, et non sa conclusion. On le
       descend jusqu'à ce qu'une carte se détache de 1,20 — 0,80 de luminance
       contre 0,93 — puis toutes les encres se résolvent DESSUS. C'est l'ordre
       qui compte : la version précédente avait posé le fond au plus clair et
       calculé les gris dessus, si bien qu'en descendant le fond pour
       l'éblouissement ces gris tombaient sous AA sans que rien ne le dise.
       « text-3 » porte tous les intitulés de colonne en 11,5 px, et il passait
       de 4,72 à 4,21.

       Chaque teinte de sens garde sa TEINTE au degré près — c'est ce qui la
       rend reconnaissable — et seule sa clarté est résolue.

       TOUS LES RAPPORTS SONT CALCULÉS, aucun n'est choisi à l'œil, et
       « python3 tools/palette/palette.py » les revérifie sur la feuille SERVIE :

         encre sur fond            14,5      encre sur carte          17,4
         text-2 sur fond            6,0      text-3 sur fond           4,6
         ok sur pastille            4,6      warn sur pastille         4,6
         err sur pastille           4,6      owed sur pastille         4,6
         limite de contrôle         3,1      carte détachée du fond    1,20

       Le SOMBRE n'a pas été refait : il ne manquait que deux cibles sur douze —
       « text-3 » à 4,50 sur « surface-2 », au ras du seuil, et une carte à 1,10.
       Ses quatre teintes de sens tenaient de 5,96 à 8,13. Les redériver rendait
       un rouge rose pâle pour « en retard » : jeter ce qui tient pour satisfaire
       un solveur écrit une heure plus tôt aurait été une régression déguisée en
       calcul. Deux jetons corrigés, un ajouté, le reste intact.

       La page A4 (« .rf-paper », plus bas) ne suit PAS ce bloc : une feuille de
       papier n'imite pas l'écran, elle imite l'imprimante.

       RELAXED 17 October 2026: a brand accent returns on selection only —
       sidebar selection, links, focus ring, checked controls; buttons stay
       ink (composants.css). Never on state pills, amounts or state charts:
       the four state hues above still carry meaning alone. Ink blue since
       19 October 2026, at 214°, 37° from the nearest of them (indigo
       « owed »); figures in « python3 tools/palette/palette.py ».
       « --accent » stays the ink. */
    --marque: #1d4f91;
    --marque-texte: #1d4f91;
    --sur-marque: #ffffff;
    --marque-soft: rgba(29, 79, 145, .10);
    --bg: #eaeaec;
    --surface: #ffffff;
    /* PLUS CLAIR QUE LE FOND, ET NON PLUS SOMBRE.

       Posé à #f1f2f4, ce jeton passait SOUS le fond en luminance (0,887 contre
       0,931) : la carte d'une rangée sur téléphone devenait plus sombre que la
       page qui la porte, alors que ce jeton nomme une surface SURÉLEVÉE. La
       palette précédente tenait l'ordre — fond 0,906, surface-2 0,940,
       carte 1,000 — et je l'avais inversé sans le voir.

       Trouvé en vérifiant tout autre chose : les vignettes de la liste, dont le
       fond ne concordait pas entre le téléphone et l'écran large. Un défaut ne
       se montre pas toujours là où on le cherche. */
    --surface-2: #f1f2f3;
    --text: #15171e;
    /* DEUX ENCRES, ET IL Y EN AVAIT TROIS.

       « --text-2 » valait #505566 et « --text-3 » #60667a : six pour cent de
       luminance d'écart, que personne ne voit. Deux noms pour un seul gris, et
       le gris servait donc à dire à la fois « secondaire » et « désactivé » —
       c'est-à-dire plus rien.

       Le second est désormais un ALIAS du premier. Les cent quarante et un
       points d'appel n'ont pas à être renommés, et le jour où quelqu'un voudra
       une troisième encre, il devra d'abord dire ce qu'elle signifie. */
    --text-2: #4a5160;
    /* 3,08 auparavant. Il porte les intitulés de colonne en 11,5 px, et un
       intitulé qu'on ne lit pas rend sa colonne muette. Calculé sur le FOND,
       qui est le pire de ses trois supports. */
    --text-3: var(--text-2);
    --line: rgba(21, 23, 30, .10);
    --line-strong: rgba(21, 23, 30, .18);
    /* LA LIMITE D'UN CONTRÔLE, ET NON D'UN SÉPARATEUR.

       Jeton nouveau, et il répare un manquement complet : les champs portaient
       « line-strong », qui tient 1,47 sur la carte. WCAG 2.1 §1.4.11 exige 3,0
       pour la limite visuelle d'un composant d'interface — champ, sélecteur,
       bouton. On était à moins de la moitié, et le survol faisait sauter le
       champ à 4,72 : d'où la sensation, juste, que « ça va quand on interagit
       et c'est délavé au repos ».

       UN SEUL JETON FAISAIT DEUX MÉTIERS. Séparateur de tableau, où la norme
       n'impose rien et où un filet est souhaitable ; limite de contrôle, où elle
       impose 3,0. Corrigé pour l'un, faux pour l'autre — le défaut de ce projet,
       en CSS. « line » et « line-strong » restent donc les séparateurs, et
       celui-ci ne sert qu'aux contrôles.

       Résolu sur le FOND, son pire support : 3,05 sur le fond, 3,36 sur le
       champ, 3,67 sur la carte. */
    --line-control: #7d8398;
    /* La marque quitte les aplats : l'accent EST l'encre. Liens et anneaux de
       focus la portent aussi — le soulignement fait l'affordance d'un lien, la
       couleur ne l'a jamais faite seule. */
    --accent: #15171e;
    /* Le ton lisible de l'accent : celui du texte, des liens et du survol. Le
       vert de marque passe 5,5 sur le papier, ce qui tient pour un aplat mais
       fatigue sur une ligne de douze mots. Celui-ci passe 8,2. */
    --accent-profond: #08090c;
    /* Le texte posé SUR l'accent. Il bascule avec le thème : en sombre
       l'accent est clair, et du blanc dessus devient illisible — d'autant que
       le survol l'éclaircit encore. */
    --sur-accent: #ffffff;
    --accent-soft: rgba(21, 23, 30, .07);
    --ok: #1a6b41;
    --ok-soft: rgba(26, 107, 65, .10);
    /* 4,41 auparavant, sous le seuil sur sa propre pastille : trois centièmes
       de clarté suffisent à le ramener à 4,78. */
    --warn: #805209;
    --warn-soft: rgba(128, 82, 9, .12);
    --err: #a93229;
    --err-soft: rgba(169, 50, 41, .10);

    /* L'argent qui SORT.

       Une couleur de plus dans une palette serrée, et il a fallu s'en
       convaincre. Le produit n'encodait la direction nulle part : « en attente »
       — le client me doit — et « à rembourser » — je dois au client — portaient
       le même ambre, et leurs deux boutons le même vert d'eau. Sur la même
       liste, à trois lignes d'écart, deux fois neuf cent soixante euros qui vont
       en sens contraire se ressemblaient trait pour trait.

       Ni l'ambre (qui dit « à faire ») ni le rouge (qui dit « en faute ») ne
       conviennent : un remboursement n'est pas une anomalie, c'est une dette
       ordinaire. Le rouge sert déjà au refus de dépôt sur la même ligne, et
       porterait alors deux sens à la fois.

       L'indigo ne sert nulle part ailleurs : il s'apprend en une lecture. Le
       signe négatif le double, pour l'œil qui ne distingue pas les teintes. */
    --owed: #5e4dae;
    --owed-soft: rgba(94, 77, 174, .12);

    /* QUATRE RAYONS, ET IL Y EN AVAIT ONZE — dont DEUX pilules, 980 et 999,
       qui produisent exactement le même arc et disent seulement que deux
       personnes ont écrit « très arrondi » deux jours différents.

       · 6   ce qui vit DANS une carte : champs, puces, vignettes
       · 10  la carte, le panneau, le bouton
       · 16  les grands conteneurs
       · pilule

       Six dans dix se lit concentrique ; c'est tout ce qu'on demande à un
       emboîtement. */
    --radius-sm: 6px;
    --radius: 10px;
    --radius-lg: 16px;
    --radius-pill: 980px;
    /* Cards rest on the page, they do not float. Single source: composants.css
       used to override this value. */
    --shadow: 0 1px 2px rgba(21, 23, 30, .05), 0 2px 8px rgba(21, 23, 30, .04);
    --shadow-lg: 0 2px 6px rgba(21, 23, 30, .05), 0 24px 60px rgba(21, 23, 30, .10);
    --radius-xl: 22px;
    --ease: cubic-bezier(.25, .1, .25, 1);

    /* Palette catégorielle des rapports. L'ordre est fixe et ne se recycle
       jamais : la couleur suit le poste, pas son rang du moment, sinon changer
       de période repeindrait tout l'écran.
       Six teintes au plus. Elles ont été vérifiées pour rester distinguables en
       vision daltonienne — l'écart le plus faible est entre le jaune et le vert
       d'eau — et les intitulés sont toujours écrits à côté, jamais la couleur
       seule. « Autres » est gris : ce n'est pas une catégorie, c'est un reste. */
    --cat-1: #2a78d6;
    --cat-2: #eb6834;
    --cat-3: #1baf7a;
    --cat-4: #eda100;
    --cat-5: #e87ba4;
    --cat-6: #008300;
    --cat-other: #9aa4ad;
}

:root[data-theme="dark"] {
        color-scheme: dark;
    /* Le sombre n'est pas noir : le noir pur fait vibrer le texte clair et
       fatigue à la lecture longue. Un bleu-ardoise très sombre tient mieux
       un écran qu'on regarde une heure. */
    --bg: #0c1117;
    --surface: #1e232f;
    --surface-2: #2b3042;
    /* Same ink blue for the fill; a lighter variant of the same hue for links,
       focus and the sidebar selection: 5.48 on its own tint over « surface »,
       4.60 over « surface-2 ». The tint is the palette's dark variant #679ce1,
       which as text gave 4.29 over « surface » (measured 19 October 2026);
       both come out of « python3 tools/palette/palette.py --candidats ». */
    --marque: #1d4f91;
    --marque-texte: #87b1e7;
    --sur-marque: #ffffff;
    --marque-soft: rgba(103, 156, 225, .16);
    --text: #edf1f5;
    --text-2: #9fadba;
    --text-3: var(--text-2);
    --line: rgba(237, 241, 245, .09);
    --line-strong: rgba(237, 241, 245, .17);
    /* Même métier qu'en clair, résolu sur « surface-2 » : en sombre, le pire
       support d'un trait clair est la surface la plus CLAIRE, pas le fond.
       3,06 sur le champ, 3,67 sur la carte, 4,42 sur le fond. */
    --line-control: #727a90;
    /* EN SOMBRE, L'ENCRE DEVIENT CRAIE. Le même parti qu'en clair — l'accent
       est la couleur du texte — mais le texte y est clair : le bouton
       primaire devient donc presque blanc, avec l'encre du fond écrite
       dessus. Un bouton sombre sur un fond sombre disparaîtrait.

       Le conflit existait ici aussi : accent teal à 174°, vert de « réglé »
       à 149° — vingt-cinq degrés. Il tombe avec le reste.

       Mesuré : craie sur fond 16,7 ; encre sur bouton craie 16,7. Les quatre
       teintes de sens sont INCHANGÉES : elles tiennent déjà de 5,0 à 6,9 sur
       leur voile, et il n'y avait rien à y reprendre. */
    --accent: #edf1f5;
    /* La hiérarchie s'inverse toujours : le ton « profond » est le plus
       CLAIR des deux, parce que c'est lui qui doit ressortir du fond. */
    --accent-profond: #ffffff;
    --sur-accent: #0c1117;
    --accent-soft: rgba(237, 241, 245, .13);
    --ok: #4ec98a;
    --ok-soft: rgba(78, 201, 138, .14);
    --warn: #e8b45c;
    --warn-soft: rgba(232, 180, 92, .14);
    --err: #f2736a;
    --err-soft: rgba(242, 115, 106, .14);
    --owed: #a99af0;
    --owed-soft: rgba(169, 154, 240, .16);
    --shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 2px 8px rgba(0, 0, 0, .25);
    --shadow-lg: 0 2px 6px rgba(0, 0, 0, .5), 0 24px 60px rgba(0, 0, 0, .45);

    /* Les mêmes six teintes, redescendues pour le fond sombre. Ce n'est pas
       une inversion automatique : chaque marche a été reprise pour tenir le
       contraste sur cette surface-là, et l'ensemble revérifié. */
    --cat-1: #3987e5;
    --cat-2: #d95926;
    --cat-3: #199e70;
    --cat-4: #c98500;
    --cat-5: #d55181;
    --cat-6: #008300;
    --cat-other: #6b7885;
}

* { box-sizing: border-box; }

/* Un « display » d'auteur l'emporte sur la règle [hidden] de la feuille par
   défaut du navigateur. Sans cette ligne, .gate et .shell — tous deux en flex —
   restent visibles malgré l'attribut hidden. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Segoe UI",
                 Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif,
                 "Apple Color Emoji", "Segoe UI Emoji";
    font-size: var(--t-3);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    letter-spacing: -.01em;
}

h1, h2, h3 { letter-spacing: -.022em; font-weight: 600; margin: 0; }
h1 { font-size: var(--t-5); }
h2 { font-size: var(--t-4); }
h3 { font-size: var(--t-3); }

/* ----------------------------------------------------------- ossature */

.shell { display: flex; min-height: 100vh; }

.sidebar {
    width: 232px;
    flex: 0 0 232px;
    padding: var(--rf-6) var(--rf-3);
    border-right: 1px solid var(--line);
    background: var(--surface);
    position: sticky;
    top: 0;
    height: 100vh;
    display: flex;
    flex-direction: column;
    gap: var(--rf-5);
}

/* ------------------------------------------------------------------ marque */

/* Le sigle : une feuille au coin plié, dont le « m » est formé de deux pans de
   toiture. Le document et le métier dans une seule forme, lisible à 16 px comme
   à 200 — c'est la seule contrainte qui compte pour un sigle, et celle que la
   plupart des logos d'application ratent. */
.mark { flex: 0 0 auto; display: block; }

/* LE LOGO DISPARAISSAIT EN MODE NUIT, et personne ne l'avait vu.

   Ces deux tracés portaient du blanc ÉCRIT EN DUR — « rgba(255,255,255,.30) »
   pour le pli, « #fff » pour la lettre — tandis que la feuille est peinte avec
   « var(--accent) ». En clair, l'accent est l'encre : du blanc dessus ressort.
   En sombre, l'accent devient la CRAIE, et le blanc se posait sur du blanc.

   Mesuré le 4 septembre 2026, rapport de contraste lettre sur feuille :

     clair    17,79
     sombre    1,14      (1,00 = couleurs identiques)

   Le « m » et le pli étaient donc invisibles : le logo se réduisait à une page
   blanche vide. Signalé par le produit, qui l'a vu avant la mesure.

   « --sur-accent » est le jeton fait pour cela — ce qui se pose SUR l'accent —
   et il suit le thème : blanc sur l'encre, encre sur la craie. Aucune valeur
   littérale ne reste ici. */
.mark .fold { fill: var(--sur-accent); fill-opacity: .30; }
.mark .letter {
    stroke: var(--sur-accent);
    stroke-width: 3.4; fill: none; stroke-linecap: round; stroke-linejoin: round;
}

/* Le sigle est un lien vers le tableau de bord. Il doit donc se voir
   cliquable — mais discrètement : c'est un repère permanent, pas une action à
   pousser. D'où un fond qui n'apparaît qu'au survol.

   Le rembourrage vertical est repris en marge négative. Sans cela, la zone
   cliquable pousserait la navigation de 16 px vers le bas, et toute la colonne
   se décalerait pour un effet de survol. */
.brand {
    padding: var(--rf-2) var(--rf-3);
    margin: -8px 0;
    display: flex;
    align-items: center;
    gap: var(--rf-3);
    color: inherit;
    text-decoration: none;
    border-radius: var(--radius);
    transition: background-color .16s var(--ease), transform .16s var(--ease);
}

.brand:hover { background: var(--surface-2); }

/* L'enfoncement, à la manière du système : bref, faible, et sans rebond. Une
   translation plutôt qu'une mise à l'échelle — le sigle porte du texte à
   16,5 px, qu'un scale rendrait flou le temps de l'animation. */
.brand:active { transform: translateY(1px); }

.brand:focus-visible {
    outline: 2px solid var(--anneau, var(--marque-texte));
    outline-offset: 2px;
    background: var(--surface-2);
}
.brand-text { min-width: 0; }
.brand strong {
    display: block; font-size: var(--t-3); letter-spacing: -.03em; line-height: 1.15;
}
/* LE « m » INITIAL N'EST PLUS COLORÉ, et c'est une asymétrie qu'on retire.

   La règle posait « --accent-profond », qui vaut #08090c en thème clair et
   #ffffff en thème sombre. Sur la craie, c'était de l'encre sur de l'encre :
   personne ne voyait rien. Sur l'encre, le « m » ressortait franchement. Un
   effet visible sur un thème et absent de l'autre, que personne n'a choisi.

   Et il était de toute façon redondant : le sigle, juste à gauche, porte déjà
   l'accent. Colorer aussi la première lettre, c'est dire deux fois la même
   chose à deux centimètres d'intervalle.

   L'élément reste un « em » — il est dans le gabarit à quinze endroits — mais
   il ne fait plus que retirer l'italique. */
.brand strong em { font-style: normal; }
.brand span { font-size: var(--t-1); color: var(--text-3); }

/* Ce qu'un comptable ne voit pas.
   ---------------------------------------------------------------------------
   Le serveur REFUSE déjà ces gestes — c'est là qu'est la sécurité, et elle y
   reste. Ici on évite seulement de proposer l'impossible : un bouton qui rend
   un refus se lit comme une panne, pas comme une règle.

   Les actions principales des en-têtes de page d'abord — « Nouvelle facture »,
   « Nouveau devis », « Enregistrer un mouvement » — puis les écrans qui n'ont
   aucun sens pour un cabinet : les réglages de l'entreprise, ses abonnements. */
body[data-role="Accountant"] .page-head .primary,
body[data-role="Accountant"] nav [data-view="settings"],
body[data-role="Accountant"] nav [data-view="modules"],

/* Les commandes qui écrivent, nommées une à une. La liste est longue et c'est
   voulu : la nommer oblige à se demander, pour chacune, si un cabinet a une
   raison de la voir. Deux y échappent — `job-attach` et `job-detach` —
   parce que rattacher une dépense à un projet est précisément son métier, et
   que le serveur l'y autorise. Les réponses aux factures reçues aussi. */
body[data-role="Accountant"] [data-act="pay"],
body[data-role="Accountant"] [data-act="settle"],
body[data-role="Accountant"] [data-act="duplicate"],
body[data-role="Accountant"] [data-act="q-accept"],
body[data-role="Accountant"] [data-act="q-refuse"],
body[data-role="Accountant"] [data-act="q-invoice"],
body[data-role="Accountant"] [data-act="q-send"],
body[data-role="Accountant"] [data-act="q-edit"],
body[data-role="Accountant"] [data-act="contract-issue"],
body[data-role="Accountant"] [data-act="contract-edit"],
body[data-role="Accountant"] [data-act="contract-delete"],
body[data-role="Accountant"] [data-act="contract-active"],
body[data-role="Accountant"] [data-act="stock-move"],
body[data-role="Accountant"] [data-act="stock-reverse"],
body[data-role="Accountant"] [data-act="retry-transmission"],
body[data-role="Accountant"] [data-act="rebuild-transmission"],
body[data-role="Accountant"] [data-act="subscribe"],
body[data-role="Accountant"] [data-act="unsubscribe"],
body[data-role="Accountant"] [data-act="activate"],
body[data-role="Accountant"] [data-act="draft-open"],
body[data-role="Accountant"] [data-act="draft-delete"],
body[data-role="Accountant"] [data-act="job-edit"],
body[data-role="Accountant"] [data-act="job-state"],
body[data-role="Accountant"] [data-act="sale-remove"] { display: none; }

/* Le bloc d'identité mène aux réglages, qui lui sont fermés. */
body[data-role="Accountant"] .identity .who { pointer-events: none; }

/* L'entreprise active. Elle se lit sans qu'on la cherche : un comptable qui a
   trente clients doit savoir chez qui il est AVANT d'agir, pas après. */
.org-switch { padding: var(--rf-2) var(--rf-3) 0; }
.org-switch label {
    display: block; font-size: var(--t-1); letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-3); margin-bottom: var(--rf-1);
}
.org-switch select {
    width: 100%; font-size: var(--t-2); font-weight: 550; padding: var(--rf-2) var(--rf-2);
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface); color: var(--text);
}
.org-switch .hint { margin: var(--rf-1) 0 0; font-size: var(--t-1); }

/* La MENTION du dossier unique. Même place et même graisse que le sélecteur
   qu'elle remplace : c'est ce qui permet de la regarder sans la chercher. Elle
   n'est pas cliquable, et n'en prend donc ni le cadre ni le curseur. */
.org-switch__seul {
    font-size: var(--t-2); font-weight: 500;
    padding: var(--rf-2) 0;
    color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Un titre qui porte un ÉTAT et non un nom. Il se lit comme ce qu'il est :
   quelque chose qui manque, pas une raison sociale bizarre. */
.titre-manquant { color: var(--text-3); font-weight: 500; }

/* La pastille du manque portée par un BOUTON. `refonte.css` la dessine pour un
   élément neutre ; sur un bouton il faut défaire ce que le navigateur ajoute,
   sans quoi elle arrive avec un cadre et une police qui ne sont pas les nôtres.
   Elle est cliquable pour de vrai — c'est ce qui la distingue d'un tiret. */
button.rf-manque {
    border: 0; font: inherit; font-size: var(--t-2); font-weight: 500;
    line-height: 1.4; text-align: left;
}

/* La navigation prend la place qui reste et défile si elle en manque : c'est
   elle qui cède, jamais le bloc d'identité. Sans cela, une fenêtre courte
   poussait « Se déconnecter » sous le bord de l'écran, hors d'atteinte — la
   barre fait 100vh de haut et ne défile pas avec la page. */
nav {
    display: flex; flex-direction: column; gap: 2px;
    flex: 1 1 auto; min-height: 0; overflow-y: auto;
    /* Une barre de défilement système prendrait une gouttière permanente dans
       une colonne de 232 px qui n'a pas de place à donner. */
    scrollbar-width: thin;
}

nav button {
    display: flex; align-items: center; gap: var(--rf-2);
    /* Six pixels et non huit : les intitulés de groupe ont ajouté six lignes à
       une colonne qui en portait dix-neuf, et quatre entrées passaient sous le
       pli d'un écran de neuf cents. La barre du téléphone garde son propre
       dégagement, plus large, dans la requête média. */
    padding: var(--rf-1) var(--rf-3);
    border: 0; border-radius: var(--radius);
    background: transparent; color: var(--text);
    font: inherit; font-size: var(--t-2); text-align: left;
    cursor: pointer;
    transition: background .18s var(--ease);
}
nav button:hover { background: var(--line); }
nav button[aria-current="page"] { background: var(--accent-soft); color: var(--accent-profond); font-weight: 500; }
/* Les pictogrammes héritent de currentColor : ils passent à l'accent avec
   l'onglet actif, sans règle supplémentaire. C'est ce que les émojis ne
   savaient pas faire — ils gardaient leurs couleurs et leur aspect système. */
.ic {
    width: 19px; height: 19px; flex: 0 0 auto;
    fill: none; stroke: currentColor;
    stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
    opacity: .9;
}
nav button[aria-current="page"] .ic { opacity: 1; }
.sprite { display: none; }

.identity {
    margin-top: auto;
    padding: var(--rf-3);
    border-radius: var(--radius);
    background: var(--surface-2);
    border: 1px solid var(--line);
    font-size: var(--t-2);
}
.identity .who {
    font-weight: 600; color: var(--text);
    display: block; width: 100%; text-align: left;
    background: none; border: 0; padding: 0; margin: 0;
    font: inherit; font-weight: 600; font-size: var(--t-2); cursor: pointer;
}
.identity .who:hover { color: var(--accent-profond); }
.identity .siren { color: var(--text-3); font-variant-numeric: tabular-nums; }
.identity button {
    margin-top: var(--rf-2); width: 100%;
    background: transparent; border: 1px solid var(--line-control);
    border-radius: var(--radius); padding: var(--rf-1); font: inherit; font-size: var(--t-2);
    color: var(--text-2); cursor: pointer;
    transition: border-color .18s var(--ease), color .18s var(--ease);
}
.identity button:hover { border-color: var(--accent); color: var(--accent-profond); }

/* LA VERSION N'EST PAS UN BOUTON DE PLUS. Elle hérite pourtant de
   « .identity button », qui la dessinerait encadrée et pleine largeur, à côté de
   « Se déconnecter » — deux cibles jumelles dont l'une déconnecte. La règle
   annule donc l'encadrement : c'est une mention, discrète, qui répond au clic
   sans le réclamer. */
.identity .version-tag {
    margin-top: var(--rf-2); padding: 0; width: auto;
    border: 0; background: none;
    color: var(--text-3); font-size: var(--t-1);
    font-variant-numeric: tabular-nums; letter-spacing: .01em;
    cursor: pointer;
}
.identity .version-tag:hover { border-color: transparent; color: var(--text-2); }
.identity .version-tag:focus-visible { outline: 2px solid var(--anneau, var(--marque-texte)); outline-offset: 2px; }

/* Le contenu est centré dans la place restante, pas collé à la barre latérale.
   La largeur maximale reste bornée — au-delà d'environ 1400 px, un tableau
   s'étire et l'œil perd la ligne entre deux colonnes éloignées. Mais le laisser
   à gauche avec 300 px de vide à droite donnait l'impression d'une page mal
   finie plutôt que d'une mesure de confort de lecture. */
/* La colonne de contenu, à côté de la barre latérale.

   Deux propriétés valent une explication, parce que leur absence faisait
   défiler la page de côté sur toute fenêtre inférieure à 1 130 px environ —
   c'est-à-dire sur un portable de treize pouces, ou sur une fenêtre à demi
   écran. La barre latérale, collée en haut, glissait alors avec le reste : elle
   ne tenait plus rien.

   « width: 100 % » demandait la largeur de la FENÊTRE à un élément qui n'en a
   que la part restante, les 232 px de la barre en moins. Elle est retirée :
   « flex: 1 » suffit à occuper ce qui reste, et le dit sans se tromper.

   « min-width: 0 » lève la valeur par défaut des éléments flexibles, qui est
   « auto » et vaut la largeur MINIMALE DU CONTENU. Sans elle, un tableau large
   interdit à sa colonne de rétrécir, quelle que soit la fenêtre — c'est le piège
   classique des dispositions en flex, et il ne se voit qu'en réduisant. */
main {
    flex: 1;
    min-width: 0;

    /* « min(1400px, 100%) » ET NON « 1400px ».

       Le plafond de lecture est de 1 400 px, et il ne bornait rien du tout en
       dessous : un enfant plus large que l'écran poussait « main » jusqu'à sa
       propre taille, sans jamais toucher le plafond.

       Mesuré le 30 septembre 2026 sur un téléphone de 375 px. L'aperçu A4 de
       l'écran Document est à l'échelle réelle — 794 px, et c'est voulu, son
       conteneur porte « overflow-x: auto » pour qu'il DÉFILE. Il ne défilait
       pas : il étirait « main » à 826 px, la vue, la barre latérale et tout ce
       qu'elles portent. Le bouton « plus » de la barre du bas se retrouvait à
       822 px sur un écran de 375 — hors d'atteinte du doigt, la navigation
       morte sur cet écran.

       « min-width: 0 » ci-dessus ne suffit pas : elle autorise le rétrécissement
       sans jamais l'imposer. C'est le plafond qui le force, et il ne le pouvait
       pas tant qu'il valait 1 400 px.

       Avec la borne, la feuille reste à 794 px et son conteneur défile enfin —
       « scrollWidth 794 / clientWidth 343 ». L'intention écrite est tenue, pas
       contournée. */
    max-width: min(1400px, 100%);
    margin-inline: auto;
    padding: var(--rf-8) var(--rf-10) 72px;
}

.page-head {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: var(--rf-5); margin-bottom: var(--rf-6);
}
.page-head p { margin: var(--rf-1) 0 0; color: var(--text-2); font-size: var(--t-2); max-width: 62ch; }

/* L'action de tête ne se plie pas. « Nouvelle facture » se cassait en deux
   lignes dès que la fenêtre se resserrait, et le bouton devenait une pastille
   haute et bancale à côté d'un titre sur une ligne. Un libellé de bouton est
   court par construction : s'il ne tient pas, c'est la mise en page qui doit
   céder, pas le mot. */
.page-head button { white-space: nowrap; }

/* ⚠️ UN LIBELLÉ DE BOUTON NE SE COUPE PAS DANS UN TABLEAU.

   Un bouton est une cible, pas un paragraphe : coupé en deux lignes, il devient
   plus haut que ses voisins et la colonne d'actions part en escalier.

   Relevé le 8 octobre 2026 sur l'écran des comptes, et c'est une régression que
   j'ai introduite sans la voir : « échue le 30 oct. » est devenu « à régler
   avant le 30 oct. », la colonne d'état s'est élargie, celle des actions s'est
   serrée, et « Marquer réglé » est passé à la ligne.

   La règle est posée ICI et non sur « button », parce qu'un bouton pleine
   largeur sur téléphone a le droit de se replier : c'est dans un tableau, où
   l'espace se partage, que la coupure se voit. */
td button { white-space: nowrap; }

/* Deux actions en tête d'écran plutôt qu'une. Sans cette règle elles se
   chevauchaient : le conteneur était un bloc, et les boutons s'y empilaient à
   la ligne. */
.page-actions { display: flex; flex-wrap: wrap; gap: var(--rf-2); justify-content: flex-end; }
.head-actions { display: flex; gap: var(--rf-2); flex: 0 0 auto; }

/* LA RANGÉE DE BOUTONS HORS DIALOGUE. « .actions » n'avait de règle que dans
   les dialogues : ailleurs, ses boutons retombaient dans le flux du texte, à
   4 px l'un de l'autre (une espace de balisage), et bord à bord une fois
   empilés au téléphone — les journaux de la Comptabilité, l'option Équipe,
   les nouvelles conditions. Les « gap » et « justify-content » écrits en ligne
   sur ces rangées supposaient cette règle et restaient sans effet. Relevé le
   18 octobre 2026. Les dialogues gardent la leur, plus précise. */
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--rf-2); }

/* ------------------------------------------------- tableau de bord réarrangeable */

/* La grille est en quarts : un indicateur en occupe un, une liste deux, un
   graphique les quatre. Les widgets déclarent leur encombrement, la grille ne
   sait rien d'eux. */
/* Les cartes d'une même rangée s'alignent sur la plus haute : quatre
   indicateurs de hauteurs différentes donnent un peigne, et le regard s'arrête
   sur l'irrégularité plutôt que sur les chiffres. */
.board {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--rf-4); align-items: stretch;
}
.board > .w-petit { grid-column: span 1; }
.board > .w-moyen { grid-column: span 2; }
.board > .w-grand { grid-column: span 4; }

/* LA BANDE DE RÉCONCILIATION.

   `refonte.css` porte la mécanique — la colonne, la barre, les segments. Ce qui
   suit est ce que la feuille de la refonte ne connaît pas encore : l'égalité
   posée à côté du nombre, les clés colorées, et les deux notes du bas.

   Les segments prennent leur couleur d'un `data-ton` et non d'un style en
   ligne : la politique de sécurité interdit les gestionnaires en ligne, et une
   couleur écrite dans le balisage échapperait au thème sombre. */
/* La carte porte déjà son rembourrage ; la bande n'en ajoute pas un second.
   Elle resserre seulement sa marge basse : elle précède la grille, et deux
   respirations empilées la détachent de ce qu'elle contrôle. */
.rf-reconcile { margin-bottom: var(--rf-4); }

.rf-reconcile__tete {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--rf-4); flex-wrap: wrap;
}

.rf-reconcile__egalite {
    margin: 0;
    font-size: var(--t-2);
    color: var(--text-2);
    font-variant-numeric: tabular-nums;
}

.rf-reconcile__seg[data-ton="ok"] { background: var(--ok); }
/* UN RÉGLAGE MANQUANT QUI EMPÊCHERA LE DÉPÔT, dit à l'endroit et au moment où
   il se répare — avant la saisie, pas après l'émission. Le ton est celui de
   l'avertissement et non de l'erreur : rien n'est encore cassé. */
.sub--manque { color: var(--warn); font-weight: 500; }
.sub--manque button.link { color: inherit; text-decoration: underline; }

.rf-reconcile__seg[data-ton="warn"] { background: var(--warn); }
.rf-reconcile__seg[data-ton="err"] { background: var(--err); }

.rf-reconcile__key {
    font-size: var(--t-2);
    color: var(--text-2);
    font-variant-numeric: tabular-nums;
}

.rf-reconcile__key b { font-weight: 600; }
.rf-reconcile__key[data-ton="ok"] b { color: var(--ok); }
.rf-reconcile__key[data-ton="warn"] b { color: var(--warn); }
.rf-reconcile__key[data-ton="err"] b { color: var(--err); }

/* L'écart. Il ne se distingue pas par un pictogramme mais par son encre : la
   phrase porte sa force toute seule. */
.rf-reconcile__ecart {
    margin: 0;
    padding: var(--rf-2) var(--rf-3);
    background: var(--err-soft);
    color: var(--err);
    border-radius: var(--radius);
    font-size: var(--t-2);
    line-height: 1.5;
}

.rf-aging__seg[data-tranche="0"] { background: var(--owed); }

/* Le widget est un conteneur transparent : ce qu'il enveloppe porte déjà sa
   propre carte. Seule la personnalisation lui donne un cadre. */
.widget { position: relative; min-width: 0; }
.widget > .card, .widget > .kpi { margin: 0; height: 100%; }

/* --- mode personnalisation --- */

.board.editing .widget {
    user-select: none; border-radius: var(--radius-lg);
    outline: 1px dashed var(--line-strong); outline-offset: 3px;
}
.board.editing .widget:hover { outline-color: var(--accent); }

/* Les cartes deviennent inertes : en personnalisation, un clic ne doit pas
   ouvrir le détail de l'indicateur qu'on est en train de manipuler. Seules la
   poignée et la croix restent vivantes. */
.board.editing .widget * { pointer-events: none; }
.board.editing .widget .w-remove,
.board.editing .widget .w-grip { pointer-events: auto; }

/* La poignée, et non la carte entière. Sur un téléphone, rendre toute la carte
   saisissable impose « touch-action: none » sur toute sa surface — et le doigt
   ne fait alors plus défiler la page : on ne peut plus atteindre les cartes du
   bas, précisément quand on cherche à les réorganiser. */
.w-grip {
    position: absolute; top: -9px; right: -9px; z-index: 3;
    width: 30px; height: 30px; padding: 0; border-radius: 50%;
    display: none; align-items: center; justify-content: center;
    background: var(--surface); color: var(--text-2);
    border: 1px solid var(--line-control);
    cursor: grab; touch-action: none;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .10);
}
.board.editing .w-grip { display: flex; }
.w-grip:hover { border-color: var(--accent); color: var(--accent-profond); }
.w-grip svg { width: 15px; height: 15px; }

.w-remove {
    position: absolute; top: -9px; left: -9px; z-index: 3;
    width: 26px; height: 26px; padding: 0; border-radius: 50%;
    display: none; align-items: center; justify-content: center;
    background: var(--err); color: #fff; border: 2px solid var(--surface);
    font-size: var(--t-3); line-height: 1; cursor: pointer;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
}
.board.editing .w-remove { display: flex; }
.w-remove:hover { transform: scale(1.12); }

/* La carte saisie suit le doigt ; les autres glissent à leur nouvelle place. */
.widget.dragging {
    opacity: .92; z-index: 5; cursor: grabbing;
    transform: scale(1.03); box-shadow: 0 18px 40px rgba(0, 0, 0, .18);
    outline-color: var(--accent); outline-style: solid;
}
.widget.sliding { transition: transform .22s cubic-bezier(.2, .9, .3, 1); }

/* L'emplacement libéré, pour que la grille ne se referme pas sous le doigt. */
.widget.ghost { opacity: 0; }

.board-bar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--rf-4); flex-wrap: wrap;
    background: var(--accent-soft); border: 1px solid var(--accent);
    border-radius: var(--radius-lg); padding: var(--rf-3) var(--rf-4); margin-bottom: var(--rf-4);
}

.board-add { margin-top: var(--rf-4); }
.board-choices { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: var(--rf-3); }

.board-choice {
    text-align: left; padding: var(--rf-3) var(--rf-4); border-radius: var(--radius);
    border: 1px solid var(--line-control); background: var(--surface);
    cursor: pointer; display: block; width: 100%;
}
.board-choice:hover { border-color: var(--accent); background: var(--accent-soft); }
.board-choice strong { display: block; font-size: var(--t-2); margin-bottom: 3px; }
.board-choice span { font-size: var(--t-2); color: var(--text-2); line-height: 1.4; }

/* Le déplacement au doigt n'a pas de sens sans retour visuel ; il en a encore
   moins pour qui a demandé moins d'animations. */
@media (prefers-reduced-motion: reduce) {
    .widget.sliding { transition: none; }
    .widget.dragging { transform: none; }
    .w-remove:hover { transform: none; }
}

/* -------------------------------------------------------------- composants */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: var(--rf-6);
    box-shadow: var(--shadow);
}
.card + .card { margin-top: var(--rf-4); }
.card > h2 { margin-bottom: var(--rf-1); }
.card > .sub { color: var(--text-2); font-size: var(--t-2); margin: 0 0 var(--rf-4); }

button.primary {
    /* Deux points d'écart entre les deux arrêts : assez pour que le bouton ait
       un volume, pas assez pour ressembler aux dégradés des années 2000. */
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--accent) 92%, #fff) 0%, var(--accent) 100%);
    color: var(--sur-accent);
    border: 0; border-radius: var(--radius-pill);
    padding: var(--rf-3) var(--rf-6); font: inherit; font-size: var(--t-2); font-weight: 500;
    cursor: pointer;
    box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 35%, transparent),
                0 4px 14px color-mix(in srgb, var(--accent) 22%, transparent);
    transition: filter .18s var(--ease), transform .12s var(--ease), box-shadow .18s var(--ease);
}
button.primary:hover {
    /* Le fond change, et lui seul. « filter: brightness » agissait sur tout le
       bouton, texte compris : en thème sombre il éclaircissait un fond déjà
       clair sous un texte blanc, et le libellé disparaissait. */
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--accent) 84%, #fff) 0%,
        color-mix(in srgb, var(--accent) 92%, #fff) 100%);
    box-shadow: 0 2px 4px color-mix(in srgb, var(--accent) 40%, transparent),
                0 8px 22px color-mix(in srgb, var(--accent) 28%, transparent);
}
button.primary:active { transform: scale(.97); }
button.primary:disabled { opacity: .45; cursor: default; transform: none; filter: none; }

/* ⚠️ UN BOUTON FANTÔME DÉSACTIVÉ LE MONTRAIT AUSSI PEU QU'UN BOUTON ACTIF.

   Seul l'accentué portait un style d'empêchement ; le fantôme gardait sa bordure
   et sa couleur pleines. Relevé le 8 octobre 2026 sur « Produire le journal »,
   fermé tant qu'aucune écriture n'est passée : il se présentait comme cliquable
   et ne répondait pas. Un bouton qui ne dit pas qu'il est fermé est pire qu'un
   bouton absent — on l'essaie, et on cherche la panne. */
button.ghost:disabled {
    opacity: .45;
    cursor: default;
    transform: none;
}

button.ghost:disabled:hover {
    border-color: var(--line-strong);
    background: transparent;
}

button.ghost {
    background: transparent; color: var(--accent-profond);
    border: 1px solid var(--line-control); border-radius: var(--radius-pill);
    padding: var(--rf-2) var(--rf-4); font: inherit; font-size: var(--t-2); cursor: pointer;
    transition: border-color .18s var(--ease), background .18s var(--ease);
}
button.ghost:hover { border-color: var(--accent); background: var(--accent-soft); }

/* La destruction ne partage jamais la couleur de l'action courante : l'œil doit
   pouvoir distinguer « retirer » d'« enregistrer » sans lire. */
/* Une action principale qui DÉTRUIT ne se peint pas de la couleur d'accent :
   celle-ci dit « continuez », et le geste dit l'inverse. La confirmation en est
   le seul usage, et c'est là qu'un coup d'œil doit suffire. */
button.primary.danger { background: var(--err); }
button.primary.danger:hover { filter: brightness(1.08); }

button.ghost.danger { color: var(--err); }
button.ghost.danger:hover { border-color: var(--err); background: var(--err-soft); }

.grid { display: grid; gap: var(--rf-3); }
.grid.two { grid-template-columns: 1fr 1fr; }
.grid.three { grid-template-columns: repeat(3, 1fr); }

label { display: block; font-size: var(--t-2); color: var(--text-2); margin-bottom: var(--rf-1); font-weight: 500; }

input, select, textarea {
    width: 100%;
    padding: var(--rf-2) var(--rf-3);
    border: 1px solid var(--line-control);
    border-radius: var(--radius);
    background-color: var(--surface-2);
    color: var(--text);
    font: inherit; font-size: var(--t-2);
    transition: border-color .18s var(--ease), box-shadow .18s var(--ease), background .18s var(--ease);
}
input:hover:not(:disabled), select:hover:not(:disabled), textarea:hover { border-color: var(--text-3); }
input:focus, select:focus, textarea:focus {
    outline: 0;
    border-color: var(--accent);
    /* « background-color » ET NON LE RACCOURCI « background ».

       Le raccourci remet à zéro TOUT le fond — image, taille, répétition,
       position — et non seulement la couleur. Sur un « select », dont le chevron
       est une image de fond posée par la règle « select », cela donnait :

         en clair   le chevron DISPARAISSAIT au focus, sans que personne le voie ;
         en sombre  il revenait par la règle de thème, plus spécifique, mais
                    SANS sa taille ni son « no-repeat » — une flèche à la
                    hauteur du champ, répétée en zigzag sur toute la largeur.

       Signalé le 7 septembre 2026 sur le régime de TVA des réglages, en le
       focalisant pour le changer. Le défaut dormait depuis toujours dans sa
       forme claire ; la palette à attribut, plus spécifique que la règle
       « select », l'a rendu bruyant. C'est le raccourci qui est en cause, et
       non le thème.

       Les deux autres raccourcis de ce bloc — le fond au repos et celui d'un
       champ désactivé — sont corrigés de la même façon : ils posent une
       COULEUR, pas un fond entier. */
    background-color: var(--surface);
    box-shadow: 0 0 0 3.5px var(--accent-soft);
}
input:disabled {
    color: var(--text-3); cursor: not-allowed;
    background-color: color-mix(in srgb, var(--line) 40%, transparent);
}
input[type="number"] { font-variant-numeric: tabular-nums; }

/* UNE CASE À COCHER N'EST PAS UN CHAMP DE SAISIE, et la règle ci-dessus la
   traitait comme tel.

   « input, select, textarea { width: 100% } » s'applique aussi aux cases : dans
   une étiquette en drapeau, la case prenait donc la moitié de la largeur et
   repoussait son texte dans une colonne étroite. Signalé le 5 septembre 2026 sur
   la fenêtre d'émission : l'aveu « J'ai lu ces réserves » se lisait en colonne
   d'une lettre, hors du cadre. Mesuré à 1 440 px, le texte commençait 242 px
   après le bord de son étiquette — pour une case qui en fait dix-sept.

   Le produit avait DÉJÀ rencontré ce défaut : « .rappro input[type=checkbox] »
   posait exactement ces trois propriétés, sur un seul écran. Un correctif local
   là où la règle était en cause — et les treize autres cases du produit sont
   restées étirées pendant ce temps. L'exception est donc remontée ici, et la
   locale retirée.

   « flex: 0 0 auto » en plus des dimensions : dans une étiquette en flex, une
   base automatique laisse encore la case s'étirer. */
input[type="checkbox"] {
    flex: 0 0 auto;
    width: 17px; height: 17px;
    padding: 0;
    accent-color: var(--marque);
}

/* Les listes déroulantes natives sont laides et différentes sur chaque système.
   On retire l'habillage du navigateur et on dessine le chevron nous-mêmes — en
   gardant le <select> réel, donc le clavier, le lecteur d'écran et la roue
   native du téléphone, qu'aucun composant maison ne remplace correctement. */
select {
    appearance: none; -webkit-appearance: none;
    padding-right: var(--rf-8);
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%238a94a0' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 13px center;
    background-size: 11px;
    cursor: pointer;
}
:root[data-theme="dark"] select {
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%237d8b98' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
select:focus { background-color: var(--surface); }

/* Le sélecteur de date : l'icône du navigateur est grise, minuscule et
   inamovible. On la remplace par la nôtre, aux couleurs de l'interface, et on
   étend la zone cliquable à tout le champ. */
/* Un enfant de flex ou de grille ne descend PAS sous sa largeur de contenu.

   C'est la règle par défaut — « min-width: auto » — et elle prend à revers les
   champs de date : leur contenu minimal, « jj/mm/aaaa » plus les 38 px réservés
   à l'icône, fait environ 170 px. Sous cette largeur, la piste se laisse
   dépasser au lieu de rétrécir, et la carte déborde vers la droite en emportant
   ce qui suit.

   Chromium ne le montre à aucune largeur de téléphone que j'ai pu atteindre ;
   WebKit est plus strict sur la taille intrinsèque des contrôles natifs, et
   c'est de là que vient le signalement. La garde ne coûte rien là où le défaut
   n'existe pas, et c'est le remède connu là où il existe. Posée le
   2 septembre 2026, SANS avoir pu la constater : à revoir si le débordement
   persiste, car alors la cause est ailleurs. */
.filters > div,
.grid > div { min-width: 0; }

/* ⚠️ UNE DATE A UNE LARGEUR, ET CE N'EST PAS CELLE DE SA COLONNE.

   Les champs de date héritaient du « width: 100% » commun aux saisies, et
   s'étiraient sur la moitié d'une carte pour afficher « 01/01/2026 ». Vu sur les
   captures du bac le 8 octobre 2026, écran Comptabilité : la date occupait moins
   du tiers de sa boîte. Un champ beaucoup plus large que son contenu se lit
   comme un formulaire inachevé — c'est de là que vient l'impression de réglage
   par défaut, et non du calendrier du navigateur, dont le picto est déjà le
   nôtre.

   La largeur est posée en « ch », l'unité du chiffre : dix caractères de date,
   la place du picto, et les marges. Elle suit donc la police au lieu de la
   contredire. */
input[type="date"] {
    position: relative; cursor: pointer;
    width: auto; min-width: 0;
    max-width: calc(10ch + var(--rf-8) + var(--rf-3));
}

/* ⚠️ DEUX DATES SONT UNE PAIRE, ET UNE PAIRE SE LIT DE PRÈS.

   « Du » et « Au » étaient posés dans une grille à trois colonnes dont la
   troisième restait vide : chaque champ prenait trois cents pixels pour en
   remplir cent trente, et les deux bornes d'une même période se retrouvaient à
   un tiers d'écran l'une de l'autre. On lit alors deux réglages, pas un
   intervalle.

   Une rangée qui se replie, et des champs qui gardent leur largeur : la paire
   reste une paire sur un portable comme sur un écran large. */
.champs-serres {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--rf-3) var(--rf-5);
}

/* La colonne du taux, repliée quand aucun rôle du plan n'en prend un. Elle
   n'offrait alors que des champs désactivés sous un en-tête cassé en deux. */
.plan-comptes.sans-taux th:nth-child(2),
.plan-comptes.sans-taux td:nth-child(2) { display: none; }
input[type="date"]::-webkit-calendar-picker-indicator {
    position: absolute; inset: 0; width: auto; height: auto;
    margin: 0; padding: 0; opacity: 0; cursor: pointer;
}
input[type="date"] {
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='2.5' y='4' width='15' height='13.5' rx='2.5' fill='none' stroke='%238a94a0' stroke-width='1.6'/%3E%3Cpath d='M2.5 8.5h15M6.5 2.5v3M13.5 2.5v3' stroke='%238a94a0' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 17px;
    padding-right: var(--rf-8);
}
:root[data-theme="dark"] input[type="date"] {
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='2.5' y='4' width='15' height='13.5' rx='2.5' fill='none' stroke='%237d8b98' stroke-width='1.6'/%3E%3Cpath d='M2.5 8.5h15M6.5 2.5v3M13.5 2.5v3' stroke='%237d8b98' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
}

/* UN SECOND MOMENT DANS LA MÊME CARTE.

   Composer un plan de comptes et l'exploiter sont deux gestes, séparés par un
   enregistrement. Les cinq boutons tenaient sur une ligne, de poids identique :
   la séquence s'effaçait, et l'accentué désignait le terminus — « Produire le
   journal » — c'est-à-dire l'action qu'on ne peut pas faire tant que les autres
   ne sont pas faites.

   Un filet et une respiration suffisent à dire « ce qui suit vient après ». Pas
   une carte dans la carte : ce serait annoncer un sujet nouveau là où il n'y a
   qu'une étape de plus. */
.sous-carte {
    margin-top: var(--rf-5);
    padding-top: var(--rf-5);
    border-top: 1px solid var(--line);
}

.hint { font-size: var(--t-1); color: var(--text-3); margin-top: var(--rf-1); }

/* --------------------------------------------------------- tableau de bord */

.kpis {
    display: grid;
    /* auto-fit + minmax : la grille choisit son nombre de colonnes selon la
       place disponible. Aucun point de rupture à maintenir — quatre cartes sur
       un écran large, deux sur une tablette, une sur un téléphone. */
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--rf-3);
}

/* La carte est un bouton : elle mène au détail. Le curseur, le survol et le
   focus doivent l'annoncer — un chiffre cliquable qui ne se signale pas ne sera
   jamais cliqué. */
/* Un BOUTON centre verticalement son contenu quand il est plus haut que lui.

   Les quatre tuiles font la même hauteur — la grille les étire — mais deux
   seulement portent une courbe. Les deux autres, plus courtes, se retrouvaient
   centrées : leur libellé descendait de vingt-neuf pixels et la rangée perdait
   sa ligne. Mesuré, pas supposé.

   En colonne, avec le pied poussé en bas, les quatre s'alignent par le haut ET
   par le bas, que la courbe soit là ou non. */
.kpi {
    display: flex; flex-direction: column; width: 100%; text-align: left;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: var(--rf-5);
    box-shadow: var(--shadow);
    font: inherit; color: inherit; cursor: pointer;
    transition: transform .18s var(--ease), border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.kpi:hover { transform: translateY(-2px); border-color: var(--line-strong); }
/* UNE CARTE DE LECTURE NE SE DONNE PAS L'AIR D'UN BOUTON.

   Certaines cartes ne répondent pas à « lesquelles ? » — le revenu récurrent
   d'un abonnement, le volume d'un service. Elles portaient pourtant le curseur
   de main, le soulèvement au survol et l'invite « Détail → », et le clic
   ouvrait une fenêtre de factures vide. Relevé par le propriétaire du produit
   le 9 septembre 2026.

   Le soulèvement part avec le reste : c'est lui qui promet le clic. */
.kpi--lecture { cursor: default; }
.kpi--lecture:hover { transform: none; border-color: var(--line); }
.kpi:focus-visible { outline: 2px solid var(--anneau, var(--marque-texte)); outline-offset: 2px; }
.kpi:active { transform: translateY(0); }
.kpi.alert { border-color: color-mix(in srgb, var(--err) 40%, var(--line)); }

.kpi-label {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--rf-2);
    font-size: var(--t-2); color: var(--text-2); font-weight: 500;
}
/* L'invite n'apparaît qu'au survol : présente en permanence, elle ferait du
   bruit sur quatre cartes à la fois. */
.kpi-more { color: var(--accent-profond); font-size: var(--t-1); opacity: 0; transition: opacity .18s var(--ease); }
.kpi:hover .kpi-more, .kpi:focus-visible .kpi-more { opacity: 1; }

.spark-wrap { margin: 2px 0 var(--rf-2); }
.spark { width: 100%; height: 34px; display: block; }

/* Les extrêmes de la série, sous la courbe. L'échelle épouse les données pour
   que la variation se voie ; sans ces deux bornes, le même dessin décrirait
   aussi bien un écart de mille euros que de dix. */
/* Une seule étendue, alignée à droite sous la fin de la courbe. Elle était
   étalée aux deux bords — minimum à gauche, maximum à droite — ce qui laissait
   croire que chacun tombait à ce moment-là de l'année. */
/* AU-DESSUS de la courbe, et non dessous : une valeur posée sous un tracé se
   lit comme la valeur de ce tracé — on croit lire son dernier point. */
.spark-scale {
    display: flex; justify-content: flex-end;
    font-size: var(--t-1); color: var(--text-3);
    font-variant-numeric: tabular-nums; margin-bottom: 1px;
}
.kpi-value {
    font-size: var(--t-5); font-weight: 600; letter-spacing: -.01em;
    margin: var(--rf-2) 0 var(--rf-1); font-variant-numeric: tabular-nums;
    /* Les chiffres ne doivent jamais déborder de leur carte sur un petit écran. */
    overflow-wrap: anywhere;
}
.kpi.alert .kpi-value { color: var(--err); }
.kpi-foot {
    display: flex; align-items: center; gap: var(--rf-2); flex-wrap: wrap;
    font-size: var(--t-2); color: var(--text-3);
    margin-top: auto;
}
.kpi-trend {
    font-weight: 600; padding: 2px var(--rf-2); border-radius: var(--radius-pill);
    font-variant-numeric: tabular-nums;
}
.kpi-trend.ok { background: var(--ok-soft); color: var(--ok); }
.kpi-trend.err { background: var(--err-soft); color: var(--err); }

/* Sur grand écran seulement : le bouton « Plus » n'a aucune raison d'exister
   quand tous les onglets tiennent dans la colonne latérale. */
.only-mobile { display: none; }

/* --------------------------------------------------------- feuille « Plus »

   Elle porte douze vues, le compte et la déconnexion. Deux colonnes plutôt
   qu'une liste : douze lignes identiques obligent à lire chaque libellé, là où
   une grille se balaie du regard — et la feuille tient sans défilement, ce qui
   compte quand on la consulte d'une main. */
#moreList {
    display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--rf-1);
}
/* Icône et libellé sur une ligne. Empilés, douze entrées faisaient une feuille
   plus haute que l'écran, et le pied — donc la déconnexion — passait dessous. */
#moreList button {
    display: flex; align-items: center; gap: var(--rf-2);
    padding: 0 var(--rf-3); border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface-2); color: var(--text);
    font: inherit; font-size: var(--t-2); text-align: left; cursor: pointer;
    /* 52 px : au-dessus des 44 px que réclame un pouce, et six rangées tiennent
       dans la moitié basse de l'écran. */
    min-height: 52px;
    transition: border-color .18s var(--ease), color .18s var(--ease);
}
#moreList button span {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#moreList button:hover { border-color: var(--line-strong); }
#moreList button[aria-current="page"] {
    border-color: var(--accent); color: var(--accent-profond); background: var(--accent-soft);
}
#moreList .ic { width: 21px; height: 21px; }

/* Le compte, en tête : c'est ce qu'on vient vérifier avant de se déconnecter,
   et la colonne latérale qui le porte n'existe pas sur téléphone. */
.sheet-id {
    display: flex; align-items: center; gap: var(--rf-3); width: 100%;
    padding: var(--rf-3); margin-bottom: var(--rf-3);
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface-2); color: var(--text);
    font: inherit; text-align: left; cursor: pointer;
}
.sheet-id:hover { border-color: var(--accent); }
.sheet-avatar {
    flex: 0 0 auto;
    width: 38px; height: 38px; border-radius: 50%;
    display: grid; place-items: center;
    background: var(--accent); color: var(--sur-accent);
    font-size: var(--t-2); font-weight: 600; letter-spacing: .02em;
}
.sheet-id-text { min-width: 0; display: block; }
.sheet-id-text strong { display: block; font-size: var(--t-3); }
/* Une adresse longue doit se couper, jamais élargir la feuille. */
.sheet-id-text span {
    display: block; font-size: var(--t-2); color: var(--text-3);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sheet-id .ic { margin-left: auto; color: var(--text-3); }

/* La poignée n'a d'usage que sur téléphone, où la feuille monte du bas. */
.sheet-grip { display: none; }

.card-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--rf-4); flex-wrap: wrap; margin-bottom: var(--rf-3);
}
.card-head .sub { margin-bottom: 0; }

/* Sélecteur segmenté : trois choix exclusifs, sans le poids d'une liste
   déroulante qu'il faudrait ouvrir pour voir ce qu'elle contient. */
.segmented {
    display: inline-flex; padding: 2px; gap: 2px;
    background: var(--surface-2); border: 1px solid var(--line);
    border-radius: var(--radius-pill); flex: 0 0 auto;
}
.segmented button {
    /* 8 px et non 6 : à 6, la cible mesurait 31 px de haut — un pixel sous le
       plancher de 32 que le produit tient partout ailleurs, et que rien ne
       justifiait d'abaisser ici. Mesuré, pas estimé. */
    border: 0; border-radius: var(--radius-pill); padding: var(--rf-2) var(--rf-4);
    background: transparent; color: var(--text-2);
    font: inherit; font-size: var(--t-2); cursor: pointer;
    transition: background .18s var(--ease), color .18s var(--ease);
}
.segmented button:hover { color: var(--text); background: var(--line); }

/* LE SEGMENT CHOISI ÉTAIT INVISIBLE SUR UN ÉCRAN MÉDIOCRE.

   Rapporté le 22 septembre 2026 — « sur un écran pourri on voit que du blanc ».
   Les contrastes mesurés donnent raison au constat, et pas là où on l'attend :

     · texte du segment inactif sur son fond    6,61:1  — bon
     · texte du segment actif sur son fond     17,43:1  — bon
     · FOND du segment actif sur le conteneur   1,09:1  — il en faut 3
     · conteneur sur le fond de page            1,05:1  — il en faut 3

   Le texte se lisait donc parfaitement ; ce qu'on ne voyait pas, c'est LEQUEL
   des trois est choisi, et où commence le composant. La règle 1.4.11 des WCAG
   demande trois pour un élément d'interface, et « --surface » sur
   « --surface-2 » en donne un.

   Une bordure ne sauve pas la mise : pour atteindre trois sur ce fond clair il
   faudrait la descendre au gris foncé, c'est-à-dire tracer un trait noir autour
   du segment. L'application a déjà son signe pour un choix actif — la pastille
   pleine du bouton « Nouvelle facture ». On l'emploie.

   Mesuré après : 17,90:1 pour le texte et 15,97:1 pour la pastille en thème
   clair, 13,84:1 et 11,53:1 en sombre. Les deux thèmes passent, sans règle
   séparée : « --accent » et « --sur-accent » basculent ensemble. */
.segmented button[aria-pressed="true"] {
    background: var(--accent); color: var(--sur-accent); font-weight: 500;
    box-shadow: none;
}
.segmented button[aria-pressed="true"]:hover { background: var(--accent); }

/* Le conteneur se distingue du fond de page par sa bordure, qui est le seul
   trait que le thème sombre et le thème clair peuvent partager. */
.segmented { border-color: var(--line-strong); }

/* ------------------------------------------------------------------ rapports */

.report-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--rf-4); }

.donut { width: 100%; max-width: 300px; height: auto; display: block; margin: 0 auto; }
/* L'anneau s'éteint sauf là où l'on pointe : c'est ce qui permet de suivre un
   secteur étroit du regard jusqu'à son libellé. */
.donut-slice { transition: opacity .15s var(--ease); cursor: pointer; }
/* Éteint par le JAVASCRIPT et non par `:hover`, parce que le survol peut venir
   de la liste à côté — et un sélecteur CSS ne franchit pas cette distance. */
.donut-slice.donut-eteint { opacity: .28; }

/* Le centre LIT le secteur survolé.

   Une bulle flottante suivait le curseur et recouvrait les secteurs voisins —
   on masquait ce qu'on cherchait à comparer. Le centre est un endroit calme,
   toujours au même point, et il ne servait qu'à répéter un total déjà écrit en
   gros au-dessus.

   C'est ce qui rend un anneau lisible : on ne compare plus des angles, ce que
   l'œil fait le plus mal — on pointe et on lit. */
.donut-libelle {
    font-size: 7.5px;
    fill: var(--text-2);
}

.donut-montant {
    font-size: 13px;
    font-weight: 600;
    fill: var(--text);
    font-variant-numeric: tabular-nums;
}

.donut-part {
    font-size: 7.5px;
    fill: var(--text-3);
    font-variant-numeric: tabular-nums;
}

/* Aucune transition sur les trois : un texte qui se fondrait à chaque
   déplacement du curseur donnerait un scintillement au lieu d'une lecture. */

/* Le classement chiffré à côté de l'anneau. Il n'est pas décoratif : trois des
   six teintes passent sous le seuil de contraste sur fond clair, et c'est cette
   liste — intitulé, montant, part — qui rend le rapport lisible sans la
   couleur. La retirer casserait l'accessibilité du graphe. */
.report-list { display: flex; flex-direction: column; gap: 2px; }
.report-row {
    display: grid; grid-template-columns: 12px minmax(0, 1fr) auto;
    align-items: center; gap: var(--rf-2); padding: var(--rf-2) var(--rf-1);
    border-bottom: 1px solid var(--line); font-size: var(--t-2);
}
.report-row:last-child { border-bottom: 0; }
.report-swatch {
    /* « display » posé d'office : la dimension ne doit pas dépendre du parent.
       Sur un « span » ou un « code », width et height sont ignorés dès que le
       conteneur cesse d'être flex — et l'élément tombe à zéro sans rien dire. */
    display: inline-block; width: 12px; height: 12px; border-radius: var(--radius-sm); }
.report-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.report-amount { font-variant-numeric: tabular-nums; font-weight: 600; }
.report-share { color: var(--text-3); font-size: var(--t-1); margin-left: var(--rf-1); font-weight: 400; }

.legend { display: flex; flex-wrap: wrap; gap: var(--rf-1) var(--rf-4); margin-top: var(--rf-3); font-size: var(--t-2); }
.legend span { display: inline-flex; align-items: center; gap: var(--rf-1); color: var(--text-2); }
.legend i { width: 10px; height: 10px; border-radius: var(--radius-sm); display: inline-block; }

.stack { cursor: pointer; }
.stack .stack-hit { fill: transparent; }
.stack:hover .stack-seg { filter: brightness(1.12); }

@media (max-width: 768px) {
    .report-split { grid-template-columns: 1fr; }
}

/* Affectation d'un achat à un projet, dans la ligne de réception. Compacte :
   elle occupe une colonne d'un tableau déjà large, et ne doit pas repousser le
   montant hors de l'écran. */
.job-picker {
    font-size: var(--t-2); padding: var(--rf-1) var(--rf-6) var(--rf-1) var(--rf-2);
    max-width: 190px; background-position: right 8px center;
}

.chart-wrap { position: relative; }
.chart { width: 100%; height: auto; display: block; overflow: visible; }
/* Les unités du viewBox, pas des pixels : le SVG est mis à l'échelle, ce corps
   de 10,5 tombe à 5 px réels sur un téléphone. La règle mobile le compense. */
/* LE TEXTE D'UN DESSIN NE SUIT PAS L'ÉCHELLE DE L'INTERFACE.

   Ces tailles sont exprimées dans les unités de la zone de dessin, pas en
   pixels d'écran : un anneau tracé dans un « viewBox » de 100 sur 100 et rendu
   sur 300 pixels multiplie chacune par trois. « 7,5 » y vaut donc vingt-deux
   pixels réels, et le passer à 11 en ferait trente-trois — une étiquette qui
   déborde son anneau.

   Elles restent littérales, et c'est une exception NOMMÉE : le vérificateur de
   grille la connaîtra sous ce nom plutôt que de la découvrir comme un oubli. */
.chart-axis { font-size: 10.5px; fill: var(--text-3); font-family: inherit; }
.chart-axis.now { fill: var(--accent-profond); font-weight: 600; }

/* L'infobulle suit le curseur : sur douze barres serrées, une bulle ancrée
   recouvrirait ses voisines. pointer-events none, sinon elle se placerait sous
   le curseur et déclencherait son propre masquage. */
.chart-tip {
    position: absolute; transform: translate(-50%, -100%);
    background: var(--text); color: var(--bg);
    padding: var(--rf-2) var(--rf-3); border-radius: var(--radius);
    font-size: var(--t-2); line-height: 1.45; white-space: nowrap;
    pointer-events: none; z-index: 5; box-shadow: var(--shadow);
    display: flex; flex-direction: column;
}
.chart-tip strong { font-size: var(--t-2); }
.chart-tip span { opacity: .8; }

dialog.wide { max-width: 780px; }
dialog .sub { color: var(--text-2); font-size: var(--t-2); margin: 0 0 var(--rf-4); }
#drillBody { max-height: 58vh; overflow-y: auto; }

.split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--rf-4); margin-top: var(--rf-4); }
.split > .card + .card { margin-top: 0; }

.bar-row + .bar-row { margin-top: var(--rf-3); }
.bar-head { display: flex; justify-content: space-between; gap: var(--rf-3); font-size: var(--t-2); }
.bar-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-value { font-variant-numeric: tabular-nums; color: var(--text-2); white-space: nowrap; }
.bar-track {
    height: 7px; border-radius: var(--radius-pill); background: var(--line);
    margin: var(--rf-1) 0 var(--rf-1); overflow: hidden;
}
.bar-fill {
    height: 100%; border-radius: var(--radius-pill); background: var(--accent);
    animation: grow .5s var(--ease);
}
@keyframes grow { from { width: 0 !important; } }
/* Sous la barre : la PART d'abord, en gras, puis le décompte en retrait. La
   part est ce qu'on vient chercher ; le nombre de factures explique le montant,
   il ne se lit qu'ensuite. */
.bar-sub { font-size: var(--t-1); color: var(--text-3); }
.bar-sub b { color: var(--text-2); font-weight: 600; font-variant-numeric: tabular-nums; }
.bar-sub span { margin-left: var(--rf-1); }
.bar-sub span::before { content: "·"; margin-right: var(--rf-1); opacity: .6; }

/* Le reste. Gris, parce que ce n'est pas une catégorie : c'est ce qui n'est pas
   nommé. Lui donner l'accent le ferait lire comme un client de plus, et le plus
   gros de tous chez qui a un chiffre bien réparti. */
.bar-autres .bar-name,
.bar-autres .bar-value { color: var(--text-3); font-weight: 400; }
.bar-autres .bar-fill { background: var(--cat-other); animation: none; }

/* La lecture, au-dessus des barres. Une phrase qu'on retient, là où cinq barres
   demandent une addition que personne ne fait. */
.bar-lecture {
    margin: 0 0 var(--rf-3);
    padding: var(--rf-2) var(--rf-3);
    border-left: 2px solid var(--warn);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    background: var(--warn-soft);
    font-size: var(--t-2);
    font-weight: 500;
    color: var(--text);
}

/* ---------------------------------------------------------------- modules */

.modules { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--rf-3); }

.module {
    display: flex; flex-direction: column;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface-2); padding: var(--rf-4);
    transition: border-color .18s var(--ease);
}
/* Le bord reste neutre, actif ou non : teinté, il se lisait comme un état sur
   toutes les cartes à la fois. L'état se dit par la pastille. */

.module-head { display: flex; align-items: center; justify-content: space-between; gap: var(--rf-2); }
.module-head h3 { font-size: var(--t-3); }
.module-purpose { color: var(--text-2); font-size: var(--t-2); margin: var(--rf-2) 0 var(--rf-4); flex: 1; }
/* Le pied s'ancre en bas, à hauteur fixe : le prix et l'action tombent à la
   même ligne sur toute la rangée, quelle que soit la longueur du texte. */
.module-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--rf-3); margin-top: auto; min-height: 44px; }
.module-price { font-size: var(--t-3); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ------------------------------------------------------------------ lignes */

/* Les lignes DÉFILENT plutôt que d'être écrasées.

   Le récapitulatif ayant pris sa colonne, la grille de saisie s'est retrouvée
   dans 430 px pour 673 dont elle a besoin. Les colonnes fixes ne cèdent pas :
   c'est la désignation — la seule en « 1fr », et la seule qu'on écrit — qui
   tombait à soixante-sept pixels. Un champ de saisie de soixante-sept pixels
   n'est pas une colonne serrée, c'est un champ inutilisable.

   Même arbitrage que pour le tableau des factures, et pour la même raison : une
   grille écrasée coûte plus cher qu'une grille qui défile. La désignation garde
   un plancher de 140 px, comme la conception le fixe. */
/* UNE CARTE DE LA COLONNE PRINCIPALE DOIT POUVOIR RÉTRÉCIR.

   « .rf-split__main » porte « min-width: 0 » — la refonte y avait pensé — mais
   les cartes qu'elle contient sont elles aussi des items de flex, et leur
   minimum automatique reste leur largeur de CONTENU. Une seule carte large
   figeait donc la colonne entière, et la borne du dessus suffisait à la rendre
   large. Deux règles par défaut qui se combinent : c'est ce qui rend ce défaut
   difficile à voir en lisant. */
.rf-split__main > .card { min-width: 0; }

.lines {
    display: flex; flex-direction: column; gap: var(--rf-2);
    overflow-x: auto;
    /* « overflow-y » DÉCLARÉ, sinon il calcule « auto » et le bloc capture la
       molette — voir « .rf-table » dans refonte.css, où la mesure est écrite.
       Ici c'est l'ÉDITEUR DE LIGNES : le bloc où l'on tape une facture, donc
       celui où la molette qui ne répond pas coûte le plus cher. */
    overflow-y: hidden;
}
/* The VAT report reuses the class on real tables: as a flex box, its header
   and body sized apart and the columns no longer lined up (18 Oct. 2026). */
@media (min-width: 861px) {
    table.lines { display: table; width: 100%; }
}
/* LA LIGNE SE DIMENSIONNE SUR SON CONTENU, et c'est ce qui lui rend son fond.

   J'avais retiré le « min-width » d'ici en croyant que le « minmax » des
   gabarits suffisait. Il ne suffit pas : « minmax » borne les PISTES de la
   grille, pas la BOÎTE qui les contient. La boîte gardait donc la largeur du
   conteneur — 630 px — pendant que ses pistes en réclamaient 942 : le fond et
   la bordure ne peignaient que les 630 premiers, et le bouton de suppression
   flottait trois cents pixels dehors, sur le fond de la carte.

   « max-content » n'est pas un nombre à tenir à jour : la ligne ordinaire, celle
   de pension et celle de remise n'ont pas la même largeur, et chacune prend la
   sienne.

   LA PREMIÈRE COLONNE RESTE EN VUE quand la grille défile. Sans cela, faire
   défiler jusqu'à « Pensionnaire » emporte la désignation hors de l'écran : on
   règle la TVA d'une ligne sans plus voir laquelle. Sur une facture d'une ligne
   c'est un inconfort ; sur une facture de douze, c'est une erreur de saisie qui
   attend. Le fond est celui de la ligne, sans quoi le contenu passerait dessous. */
/* Restreint aux enfants qui sont des GRILLES, et c'est une correction de ma
   propre règle posée une heure plus tôt. « max-content » sur un conteneur
   « flex-wrap » vaut la somme des éléments SANS repli : la règle annulait le
   repli de la liste des brouillons et du rappel de facture corrigée, dont les
   boutons sortaient de l'écran sous 600 px. Une borne posée trop large protège
   ce qu'elle vise et casse ce qu'elle n'avait pas regardé. */
/* LA BORNE NE VAUT QUE LÀ OÙ LA LIGNE EST EN RANGÉE.

   Elle était posée sans condition, et le commentaire ci-dessus disait
   pourtant ce qui allait arriver : « une borne posée trop large protège ce
   qu'elle vise et casse ce qu'elle n'avait pas regardé. »

   Sous 860 px, « .line » devient une grille à DEUX colonnes, désignation en
   pleine largeur — une mise en page mobile écrite, soignée, et entièrement
   annulée par cette borne : la ligne restait à sa largeur de contenu, 658 px,
   et poussait toute la vue à 696 px dans un écran de 375. Mesuré le
   7 septembre 2026 : QUATRE-VINGT-SEPT éléments hors de l'écran sur l'écran de
   création d'une facture, c'est-à-dire l'écran principal du produit.

   Aucune des sept mesures téléphone du banc ne le voyait : elles couvraient
   l'accueil, la liste, la fiche client et les onglets — jamais la création.
   Une mesure a été ajoutée pour cela. */
.lines > .line-block,
.lines > .line { min-width: max-content; }

@media (max-width: 860px) {
    .lines > .line-block,
    .lines > .line { min-width: 0; }
}

.line > div:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--surface-2);
    box-shadow: 1px 0 0 var(--line);
}

/* Le plancher ne dépend PAS de la largeur de l'écran.

   Il y avait ici une media query qui le retirait au-delà de 1 280 px, au motif
   qu'une grille a de la place sur un grand écran. C'est faux : la contrainte
   n'est pas la fenêtre mais la CARTE, qui partage sa largeur avec le
   récapitulatif collant depuis l'étape 5. Sur un écran de 1 400 px, la carte de
   saisie en fait 510 — et la grille s'y écrasait justement parce que la fenêtre,
   elle, était large.

   Sans la media query, rien n'est perdu : « min-width » ne borne que vers le
   bas, et une grille qui a la place s'étale au-delà comme avant. */

.line {
    display: grid;
    /* Les colonnes de listes déroulantes sont plus larges depuis que le chevron
       est dessiné : il occupe 34 px que le texte n'a plus. « Service » y était
       tronqué en « Servic ». */
    /* DEUX CENT QUARANTE PIXELS à la désignation, et non cent quarante.

       Cent quarante était un plancher de survie : « Fourniture et pose de
       menuiseries extérieures » y tient en quatre lettres. Depuis que le
       récapitulatif prend sa colonne, la carte de saisie fait 680 px sur un
       écran de 1 400 — la grille défile déjà, quel que soit ce nombre. Il ne
       coûte donc qu'un peu de défilement, et il achète le seul champ qu'on
       remplit vraiment. */
    grid-template-columns: minmax(240px, 1fr) 86px 104px 116px 98px 112px 30px;
    gap: var(--rf-2); align-items: end;
    padding: var(--rf-3); border-radius: var(--radius);
    background: var(--surface-2); border: 1px solid var(--line);
    animation: rise .28s var(--ease);
}
@keyframes rise { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

.line .rm {
    background: transparent; border: 0; color: var(--text-3);
    font-size: var(--t-4); line-height: 1; cursor: pointer; padding: var(--rf-2) 0;
    border-radius: var(--radius); transition: color .18s var(--ease);
}
.line .rm:hover { color: var(--err); }

/* La remise de document reprend l'habillage d'une ligne, mais pas ses colonnes :
   celles-ci sont taillées pour une désignation, une quantité et un prix. Le
   motif y héritait de 86 px pendant que « Remise / Frais » occupait la colonne
   large — l'inverse de ce qu'on lit. */
/* La TROISIÈME variante de ligne, et le même « 1fr » nu que les deux autres :
   le libellé d'une remise — « Geste commercial sur le second chantier » — se
   serrait au profit de colonnes fixes. Le défilement horizontal est acquis sur
   cet écran ; autant que les champs qu'on remplit y gagnent la place. */
.line.adjustment { grid-template-columns: 116px minmax(240px, 1fr) 104px 116px 116px 30px; }

/* Huit colonnes pour une pension : la désignation cède ce qu'il faut au
   pensionnaire, et les listes déroulantes se resserrent d'une dizaine de pixels.

   Les valeurs sont mesurées sur les libellés les plus longs de chaque menu —
   « Franchise en base » pour la TVA, « Main-d'œuvre » pour la nature. En
   dessous, le chevron mange le texte et « Franchise » se lit « Franchi ». */
.line--pension {
    /* LA DÉSIGNATION GARDE SON PLANCHER, ici aussi.

       Cette variante-là écrase tout le gabarit, et son « 1fr » nu annulait le
       « minmax(140px, 1fr) » posé sur « .line ». Sept colonnes fixes réclament
       634 px ; dans la carte de saisie, qui partage sa largeur avec le
       récapitulatif, il restait CENT PIXELS au seul champ qu'on remplit — « Mesure »
       s'y lisait « Mesur ». Le défaut avait été corrigé sur la ligne ordinaire à
       l'étape 5 et pas sur celle-ci : deux gabarits, une seule correction. */
    grid-template-columns: minmax(240px, 1fr) 76px 96px 108px 104px 108px 112px 30px;
}

/* HUIT COLONNES POUR UN ACHAT, et ce ne sont pas celles d'une vente.

   Une ligne de bon de commande ne porte ni nature ni main-d'œuvre — ces deux
   qualifications servent à ventiler une facture ÉMISE, et personne ne les
   déclare sur ce qu'il achète. Elle porte en revanche deux choses que la vente
   n'a pas : l'article du catalogue, qui est ce que la réception entrera en
   stock, et la RÉFÉRENCE DU FOURNISSEUR — « un bon de commande qui désigne ses
   lignes par leur intitulé n'est pas un bon de commande ».

   L'article prend la première colonne, donc la colonne collante : c'est par lui
   qu'on lit la ligne quand la grille défile. */
.line--achat {
    grid-template-columns:
        minmax(180px, .8fr) minmax(200px, 1fr) 124px 86px 104px 116px 98px 30px;
}

/* CINQ COLONNES POUR UNE RÉCEPTION, et quatre sont en LECTURE.

   On ne saisit ici qu'un seul nombre par ligne : ce qu'on vient de compter en
   déballant. Le reste — désignation, commandé, déjà reçu, reliquat — est ce
   qu'il faut avoir sous les yeux pour le compter juste, et l'offrir en champ
   modifiable inviterait à corriger la commande depuis le quai de livraison. */
.line--reception {
    grid-template-columns: minmax(220px, 1fr) 110px 96px 96px 104px;
}

.line--reception .rec-designation {
    font-size: var(--t-2); color: var(--text); padding: var(--rf-2) 0;
    overflow-wrap: anywhere;
}

.line--reception .rec-chiffre {
    font-size: var(--t-2); color: var(--text-2, var(--text)); padding: var(--rf-2) 0;
    font-variant-numeric: tabular-nums;
}

/* Une ligne soldée le dit en toutes lettres plutôt qu'en « 0 » : un zéro dans
   une colonne de reliquat se lit aussi bien « rien à recevoir » que « rien
   n'est encore parti », et les deux appellent des gestes contraires. */
.line--reception .rec-chiffre--solde { color: var(--ok, #2e7d32); }

/* SIX COLONNES POUR UN RAPPROCHEMENT, toutes en lecture sauf le retrait.

   Les trois chiffres se lisent dans l'ordre où on les pense — commandé, reçu,
   facturé — et l'écart les suit. Rien ne se saisit ici : le seul geste est de
   défaire un lien qui se trompait de commande. */
.line--rapprochement {
    grid-template-columns: minmax(150px, 1fr) 104px 104px 104px 120px 30px;
}

/* Le verdict sous sa ligne, et non dedans : « Facturé au-delà de ce qui a été
   reçu » demande quarante caractères, qu'aucune des six colonnes ne peut tenir
   sans écraser les chiffres. */
.rapprochement__verdict {
    font-size: var(--t-2); color: var(--text-3);
    padding: 2px var(--rf-3) 0; margin-top: -4px;
}

/* LES ÉCARTS SE POSENT SOUS LES SOUCIS DE LECTURE, même place et même
   intention — « cette facture demande un regard avant d'être payée » — mais
   PAS la même classe.

   Le premier jet réutilisait « inbox-carte__soucis », qui pose un « ! » en
   pseudo-élément devant chaque enfant et les peint en orange : les pastilles y
   arrivaient préfixées d'un point d'exclamation, verdict conforme compris. Deux
   intentions voisines ne font pas une classe commune. */
.inbox-carte__ecarts {
    display: flex; flex-wrap: wrap; gap: var(--rf-1); margin-top: var(--rf-1);
}
/* The gap amount ends the label: a clipped pill hid exactly the figure that
   matters, on a phone and in a narrow column (18 October 2026). It wraps. */
.inbox-carte__ecarts .badge { white-space: normal; overflow: visible; border-radius: 10px; }

/* LE RENVOI VERS UN PARCOURS, sous le titre de l'écran.

   Discret mais lisible : il ne doit pas concurrencer l'action principale de la
   page, et il doit se voir quand on est bloqué. Souligné plutôt que coloré —
   un lien se reconnaît à son soulignement, et la couleur d'accent est déjà
   prise par les boutons qui font quelque chose. */
.aide-renvoi {
    background: none; border: 0; margin-top: 2px;
    /* TRENTE-DEUX PIXELS DE HAUT, comme toute cible cliquable de ce produit :
       le banc le mesure, et un lien de quinze pixels se manque au doigt.

       Ce rembourrage valait 9 px, un chiffre hors de toute échelle, posé pour
       atteindre 32 tout juste. Calé sur le pas de 8 il rendait 31 et le banc
       l'a dit ; le pas au-dessus donne 39, et une cible confortable plutôt
       qu'une cible réglementaire au pixel près. */
    padding: var(--rf-3) 0;
    font-size: var(--t-2); color: var(--text-3); cursor: pointer;
    text-decoration: underline; text-underline-offset: 3px;
    text-decoration-color: var(--line-strong, var(--line));
}
.aide-renvoi:hover { color: var(--text); text-decoration-color: currentColor; }
.aide-renvoi::first-letter { text-transform: uppercase; }

/* ------------------------------------------------------------------ aides */

/* UN PARCOURS PAR CARTE, replié. La liste entière dépliée ferait huit pages :
   on cherche un parcours, on l'ouvre, on lit. Le résumé se lit sur la carte
   fermée — sans lui, il faudrait ouvrir les huit pour trouver le bon. */
.aide {
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius); margin-bottom: var(--rf-3);
    overflow: hidden;
}

.aide > summary {
    cursor: pointer; padding: var(--rf-4) var(--rf-4);
    display: flex; flex-direction: column; gap: var(--rf-1);
    list-style: none;
}
.aide > summary::-webkit-details-marker { display: none; }
.aide > summary::after {
    content: "⌄"; position: absolute; right: 18px;
    color: var(--text-3); transition: transform .18s var(--ease);
}
.aide { position: relative; }
.aide[open] > summary::after { transform: rotate(180deg); }
.aide > summary:hover { background: var(--surface-2); }

.aide__titre { font-size: var(--t-3); font-weight: 600; color: var(--text); }
.aide__resume { font-size: var(--t-2); color: var(--text-3); padding-right: var(--rf-6); }

.aide__corps { padding: 0 var(--rf-4) var(--rf-4); }

/* LES ÉTAPES SONT NUMÉROTÉES, et c'est le seul endroit du produit où l'ordre
   des paragraphes porte du sens : un parcours se lit du premier geste au
   dernier. */
.aide__etapes {
    margin: 0; padding: 0 0 0 var(--rf-6);
    display: flex; flex-direction: column; gap: var(--rf-3);
}
.aide__etapes li { padding-left: var(--rf-1); }
.aide__etapes li::marker { color: var(--text-3); font-variant-numeric: tabular-nums; }
.aide__etapes b { font-size: var(--t-2); color: var(--text); }
.aide__etapes p { margin: 3px 0 0; font-size: var(--t-2); line-height: 1.55; color: var(--text-2); }

/* CE QUI COINCE SE DÉTACHE du déroulé : on y vient quand le parcours a échoué,
   pas quand on le suit. */
.aide__pieges {
    margin-top: var(--rf-5); padding-top: var(--rf-4);
    border-top: 1px solid var(--line);
    display: flex; flex-direction: column; gap: var(--rf-3);
}
.aide__pieges h3 {
    margin: 0; font-size: var(--t-1); text-transform: uppercase;
    letter-spacing: .04em; color: var(--text-3); font-weight: 600;
}
.aide__piege b { font-size: var(--t-2); color: var(--text); }
.aide__piege p { margin: 3px 0 0; font-size: var(--t-2); line-height: 1.55; color: var(--text-2); }

@media (max-width: 900px) {
    .aide > summary { padding: var(--rf-3); }
    .aide__corps { padding: 0 var(--rf-3) var(--rf-3); }
    .aide__etapes { padding-left: var(--rf-5); }
}

/* ------------------------------------------------------------- nouveautés */

/* UNE VERSION PAR CARTE, dépliée. À la différence des Aides, rien n'y est à
   chercher : la page se lit de haut en bas, la plus récente en tête, et une
   carte repliée cacherait justement ce qu'on vient lire. */
.nouveaute > h2 { margin-bottom: var(--rf-1); }
.nouveaute__quand {
    margin: 0 0 var(--rf-3); font-size: var(--t-2); color: var(--text-2);
    font-variant-numeric: tabular-nums;
}
.nouveaute__quand .badge { margin-left: var(--rf-1); }
.nouveaute__points {
    margin: 0; padding: 0 0 0 var(--rf-5);
    display: flex; flex-direction: column; gap: var(--rf-2);
}
.nouveaute__points li { font-size: var(--t-2); line-height: 1.55; color: var(--text-2); }

/* LE POINT DU MENU, quand une version n'a pas été consultée.

   À l'encre de la marque, qui désigne déjà la sélection et les liens : un point
   rouge se lirait comme une alerte, et rien ici n'est en retard. Au bout de
   l'entrée dans la colonne et dans la feuille « Plus » ; sur l'onglet « Plus »
   du téléphone, contre son pictogramme, puisque l'entrée y est rangée. */
.nav-point {
    flex: 0 0 auto; width: 8px; height: 8px; margin-left: auto;
    border-radius: 980px; background: var(--marque-texte);
}
#moreTab { position: relative; }
#moreTab .nav-point { position: absolute; top: 8px; left: calc(50% + 8px); margin: 0; }

/* Un brouillon reprend l'habillage d'une ligne, sans ses colonnes : il porte un
   libellé de longueur libre et deux boutons, pas une grille de saisie. */
.draft-row {
    display: flex; align-items: center; gap: var(--rf-2); flex-wrap: wrap;
    padding: var(--rf-3); border-radius: var(--radius);
    background: var(--surface-2); border: 1px solid var(--line);
}
.draft-row b { font-size: var(--t-2); }

/* Celui qu'on modifie. Le liseré suffit : une couleur de fond ferait passer un
   brouillon pour un état d'alerte, alors que c'est l'état normal du travail. */
.draft-row.courant { border-color: var(--accent); }

/* Une ligne et ses remises forment un bloc : le trait vertical et le retrait
   disent à quoi la remise se rattache. Posées à plat dans la même liste, on ne
   saurait plus si « −50 € » s'applique à la ligne du dessus ou à la facture. */
.line-block { display: flex; flex-direction: column; gap: var(--rf-2); }

.subline {
    display: grid;
    /* Le quatrième gabarit de ligne, et le dernier sans plancher : la piste
       « 1fr » porte le champ Motif — « Geste commercial sur le second
       chantier ». Il n'était protégé que par la largeur de sa ligne voisine,
       c'est-à-dire par accident. */
    grid-template-columns: 116px minmax(240px, 1fr) 104px 116px 30px;
    gap: var(--rf-2); align-items: end;
    margin-left: var(--rf-5); padding: var(--rf-2) var(--rf-3);
    border-radius: var(--radius);
    background: var(--surface); border: 1px solid var(--line);
    border-left: 3px solid var(--line-strong, var(--line));
    animation: rise .28s var(--ease);
}

.subline .rm {
    background: transparent; border: 0; color: var(--text-3);
    font-size: var(--t-4); line-height: 1; cursor: pointer; padding: var(--rf-2) 0;
    border-radius: var(--radius); transition: color .18s var(--ease);
}
.subline .rm:hover { color: var(--err); }

/* Discret par nature : la remise de ligne est l'exception, pas la règle. Un
   bouton pleine taille sous chaque ligne encombrerait la saisie courante. */
.add-subline {
    align-self: flex-start; margin-left: var(--rf-5);
    background: transparent; border: 0; padding: 2px 0;
    color: var(--text-3); font-size: var(--t-2); cursor: pointer;
    transition: color .18s var(--ease);
}
.add-subline:hover { color: var(--brand, var(--text)); }

/* Le détail de la prestation (BT-154), sous sa ligne.

   Aligné sur le retrait des remises : ce qui est en retrait appartient à la
   ligne du dessus. Sa zone de texte grandit avec le propos plutôt que de le
   faire défiler dans trois lignes fixes. */
.line-detail { display: flex; flex-direction: column; margin-left: var(--rf-5); }

.line-detail .detail-toggle {
    align-self: flex-start;
    background: transparent; border: 0; padding: 2px 0;
    color: var(--text-3); font-size: var(--t-2); cursor: pointer;
    transition: color .18s var(--ease);
}
.line-detail .detail-toggle:hover { color: var(--brand, var(--text)); }

.line-detail textarea {
    width: 100%; min-height: 66px; resize: vertical;
    padding: var(--rf-2) var(--rf-3); border-radius: var(--radius);
    background: var(--surface); border: 1px solid var(--line);
    border-left: 3px solid var(--line-strong, var(--line));
    color: var(--text); font: inherit; font-size: var(--t-2); line-height: 1.45;
    animation: rise .28s var(--ease);
}
.line-detail textarea::placeholder { color: var(--text-3); }

/* Le montant impayé dans le pied de liste : c'est le chiffre qu'on vient
   chercher, il ne doit pas se fondre dans le décompte. */
.pager-unpaid { color: var(--err); font-weight: 600; }

/* Le total qui SORT, à côté de celui qui entre — jamais fondu dedans. */
.pager-owed { color: var(--owed); font-weight: 600; }

/* Le sélecteur de lignes, dans le pied de liste : discret, aligné sur les
   boutons de navigation. */
.pager-size {
    display: inline-flex; align-items: center; gap: var(--rf-1);
    color: var(--text-3); font-size: var(--t-2); white-space: nowrap;
}
.pager-size select { padding: var(--rf-1) var(--rf-2); font-size: var(--t-2); width: auto; }

/* --------------------------------------------------- volets des réglages */

/* Les onglets reprennent le composant « segmented » déjà employé pour la nature
   d'un client : même geste, même apparence, rien de neuf à apprendre. */
#settingsTabs { margin-bottom: var(--rf-4); }

.settings-pane { animation: rise .22s var(--ease); }

/* L'apparence et son aperçu, côte à côte tant que la place le permet. */
.brand-edit {
    display: grid; gap: var(--rf-6); margin-top: var(--rf-4);
    grid-template-columns: minmax(0, 1fr) minmax(0, 300px);
    align-items: start;
}

.brand-fields { min-width: 0; }

.brand-preview { min-width: 0; position: sticky; top: 12px; }

.preview-label {
    font-size: var(--t-1); letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-3); margin-bottom: var(--rf-2);
}

/* La feuille : assez de la facture pour juger d'une couleur, pas assez pour
   prétendre la remplacer. */
.preview-sheet {
    background: #fff; color: #1d1d1f;
    border: 1px solid var(--line); border-radius: var(--radius);
    padding: var(--rf-4); font-size: var(--t-1); line-height: 1.45;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
}

.preview-head { display: flex; justify-content: space-between; gap: var(--rf-3); }
.preview-issuer { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.preview-issuer img { height: 22px; max-width: 120px; object-fit: contain; margin-bottom: 3px; }
.preview-issuer strong { font-size: var(--t-1); }
.preview-issuer span { color: #6e6e73; font-size: var(--t-1); }

.preview-title { text-align: right; white-space: nowrap; }
.preview-title b { display: block; font-size: var(--t-3); letter-spacing: -.01em; }
.preview-title span { color: #6e6e73; font-size: var(--t-1); }

.preview-rule { border-top: 1px solid #e5e5e7; margin: var(--rf-3) 0 var(--rf-2); }

.preview-lines,
.preview-total { display: flex; justify-content: space-between; gap: var(--rf-3); }
.preview-lines { color: #1d1d1f; padding-bottom: var(--rf-2); border-bottom: 1px solid #f0f0f2; }
.preview-total { margin-top: var(--rf-2); font-size: var(--t-1); }

.preview-foot {
    display: flex; flex-direction: column; gap: 2px;
    margin-top: var(--rf-3); padding-top: var(--rf-2); border-top: 1px solid #e5e5e7;
    color: #86868b; font-size: var(--t-1);
}

/* La feuille reste blanche en thème sombre : c'est du papier, et une facture
   n'a pas de mode nuit. Seul son cadre s'accorde à l'interface. */
@media (max-width: 900px) {
    .brand-edit { grid-template-columns: 1fr; }
    .brand-preview { position: static; }
}

/* ------------------------------------------------------------------ totaux */

.totals { border-top: 1px solid var(--line); margin-top: var(--rf-4); padding-top: var(--rf-4); }
.totals .row {
    display: flex; justify-content: space-between; align-items: baseline;
    padding: var(--rf-1) 0; font-size: var(--t-2);
}
.totals .row span:last-child { font-variant-numeric: tabular-nums; }
.totals .row.grand {
    border-top: 1px solid var(--line); margin-top: var(--rf-2); padding-top: var(--rf-3);
    font-size: var(--t-4); font-weight: 600; letter-spacing: -.02em;
}
.totals .breakdown { font-size: var(--t-2); color: var(--text-2); }

/* ------------------------------------------------------------------ tables */

table { width: 100%; border-collapse: collapse; font-size: var(--t-2); }
th {
    text-align: left; font-size: var(--t-1); text-transform: uppercase;
    letter-spacing: .05em; color: var(--text-3); font-weight: 600;
    padding: 0 var(--rf-2) var(--rf-2); border-bottom: 1px solid var(--line);
}
td { padding: var(--rf-3) var(--rf-2); border-bottom: 1px solid var(--line); }
tbody tr { transition: background .15s var(--ease); }
tbody tr:hover { background: var(--surface-2); }
td.num { font-variant-numeric: tabular-nums; text-align: right; }

/* Un IDENTIFIANT ne se coupe jamais.

   « FAC-0367 » se repliait en « FAC– » puis « 0367 » dès que la colonne se
   serrait, et la date en trois lignes sous lui : les lignes du tableau des
   factures mesuraient 90 px au lieu de 44, sept tenaient à l'écran au lieu de
   quinze. Un numéro coupé se relit d'ailleurs mal — c'est ce qu'on recopie au
   téléphone quand un client conteste. */
td.mono {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: var(--t-2);
    white-space: nowrap;
}

/* Une date non plus. « 29 oct. 2026 » sur trois lignes n'est plus une date,
   c'est une colonne de mots. */
td.date { white-space: nowrap; }

/* L'exception, et elle est nommée : un horodatage n'est pas un identifiant. Le
   jour tient sa ligne, l'heure la sienne, en plus pâle — plus étroit que d'un
   tenant, et plus lisible qu'un repli subi au milieu des chiffres. */
td.mono.horodatage { white-space: normal; }
td.mono.horodatage span { display: block; color: var(--text-3); }

/* Une colonne secondaire qui ne doit pas voler la largeur des autres.

   « max-width: 0 » n'est pas une largeur : c'est ce qui autorise l'algorithme du
   tableau à réduire la colonne à sa part, au lieu de lui réserver la longueur de
   son plus long mot. L'ellipse fait le reste, et la valeur entière reste dans le
   titre au survol. */
td.tronque {
    max-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

td.tronque a { display: block; overflow: hidden; text-overflow: ellipsis; }

/* La précision sous un nom, dans un tableau : plus petite, plus pâle, et sur une
   seule ligne. Ce qui déborde est de la précision, pas de l'information — le nom
   est au-dessus, entier. */
.sous-ligne {
    color: var(--text-3);
    font-size: var(--t-1);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* La cellule qui porte un nom et sa précision. « max-width: 0 » l'autorise à
   rétrécir malgré la ligne insécable qu'elle contient — sans quoi l'ellipse ne
   se déclenche jamais et le tableau réclame la largeur du plus long courriel. */
td.cellule-nom {
    /* « max-width: 0 » autorise le rétrécissement ; « width » dit jusqu'où il est
       raisonnable d'aller. Sans cette part demandée, la colonne se réduisait à
       rien et « A2K TECHNOLOGIES » débordait sur la pastille d'état voisine. */
    max-width: 0;
    width: 34%;
}

/* Et le nom se coupe s'il le faut. Une raison sociale peut être un seul mot de
   trente caractères ; sans cette permission il déborde de sa cellule au lieu de
   se replier, et vient se poser sur la colonne d'à côté. */
td.cellule-nom > div:first-child { overflow-wrap: anywhere; }
/* Phone rows are a grid, not a table: « max-width: 0 » kept the name column
   from claiming its width (subscribers, 18 Oct. 2026). */
@media (max-width: 860px) {
    td.cellule-nom { max-width: none; width: auto; min-width: 0; }
}

/* Le détail se retire AVANT que la page se mette à défiler de côté.

   « on-wide » n'existait que dans la disposition en cartes du téléphone. Entre
   les deux — un portable de 1 100 px, une fenêtre à demi écran — les dix
   colonnes du tableau des factures restaient toutes, le tableau débordait de sa
   carte de 150 px, et c'est la PAGE qui prenait une barre de défilement
   horizontale : mesuré à 1 237 px de large pour 900 px de fenêtre. La colonne de
   navigation glissait alors avec le reste, ce qui est le pire défaut possible
   d'une barre latérale.

   Le seuil est posé sur ce que le tableau demande, non sur un format d'appareil :
   dix colonnes ne tiennent pas sous 1 180 px, six oui. */
@media (max-width: 1180px) {
    th.on-wide, td.on-wide { display: none; }

    /* Retirer des colonnes n'a pas suffi : les six qui restent demandaient
       encore 633 px pour 578 disponibles. Un tableau ne rétrécit pas sous la
       largeur minimale de son contenu — il déborde, et rien ne le retient.

       On récupère la différence sur ce qui n'est pas de la donnée : le
       rembourrage des cellules, et la taille du bouton d'action. Six colonnes à
       six pixels de moins de chaque côté rendent soixante-douze pixels, ce qui
       suffit. Aucun chiffre n'est tronqué, aucune colonne de plus ne disparaît.

       Faire défiler la carte aurait été plus simple, et faux : le menu « ⋯ » est
       posé en absolu dans sa cellule, et un ancêtre qui défile le découperait. */
    th, td { padding-left: var(--rf-1); padding-right: var(--rf-1); }
    th:first-child, td:first-child { padding-left: var(--rf-2); }
    th:last-child, td:last-child { padding-right: var(--rf-2); }

    .row-main { padding: var(--rf-1) var(--rf-3); font-size: var(--t-2); }
}

/* ------------------------------------------------------------------ badges */

/* « max-width: 100% » AVEC « white-space: nowrap ».

   Le « nowrap » est juste : une pastille coupée en deux lignes n'est plus une
   pastille. Mais sans plafond, un libellé long lui donne une largeur INTRINSÈQUE
   que rien ne réduit, et cette largeur remonte toute la page.

   Mesuré le 30 septembre 2026 sur l'écran des factures reçues, à 375 px : un
   « span.badge.err » de 373 px poussait la barre d'onglets du bas de 402 à
   410 px. Une pastille d'erreur rendait la navigation plus difficile que
   l'erreur qu'elle annonçait. */
.badge {
    display: inline-flex; align-items: center; gap: var(--rf-1);
    padding: 3px var(--rf-2); border-radius: var(--radius-pill);
    font-size: var(--t-1); font-weight: 500; white-space: nowrap;
    max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.badge.ok { background: var(--ok-soft); color: var(--ok); }
.badge.warn { background: var(--warn-soft); color: var(--warn); }
.badge.err { background: var(--err-soft); color: var(--err); }
.badge.neutral { background: var(--line); color: var(--text-2); }
/* Dark: on « surface-2 » (dialogs) the tint lifts the background and the
   status ink fell to 3.81 (err) and 4.40 (neutral). Lighter inks, same hue. */
:root[data-theme="dark"] .badge.err { color: #f8988f; }
:root[data-theme="dark"] .badge.neutral { color: #b4c0cb; }

/* Ce qu'on doit en retour, sous l'encours.

   Dans la couleur de l'argent qui sort — la même que la pastille « à rembourser »
   d'une ligne de facture et que le bouton qui la règle. Une seule couleur pour un
   seul sens : c'est ce qui la rend lisible sans légende. */
.kpi-note {
    margin-top: var(--rf-1); padding-top: var(--rf-1);
    border-top: 1px solid var(--line);
    /* ELLE PORTAIT LA COULEUR D'UN ÉTAT, et elle n'en décrit aucun.

       « --owed » est le violet de ce qui est dû et pas encore échu. L'employer
       pour « Plus haut mois : septembre » donne à une note statistique la
       teinte d'une créance : le lecteur qui a appris la couleur ailleurs la
       relit ici, et elle ne veut rien dire. Une couleur signifie un état, ou
       elle ne signifie rien. */
    font-size: var(--t-1); color: var(--text-2); text-align: left;
}

/* La nature de la pièce, sous son numéro : aucun coût horizontal.
   Voir natureDeLaPiece() pour le pourquoi. */
.ligne-nature { display: flex; gap: var(--rf-1); margin-top: var(--rf-1); }
.ligne-nature .badge { padding: 1px var(--rf-2); font-size: var(--t-1); }

/* L'argent qui sort : ce que l'entreprise doit à son client. Voir --owed. */
.badge.owed { background: var(--owed-soft); color: var(--owed); }

/* LE BLOC DES MENTIONS D'UNE FACTURE REÇUE.

   Il ne dispute rien à la carte : deux colonnes, une taille en dessous du corps
   de texte, et il s'ouvre au clic. Ce qu'il montre est de l'ordre de la PREUVE
   — « voici ce que ce document dit » — pas de l'ordre de la lecture courante.
   Le tableau défile seul quand une valeur est longue : la carte, elle, ne doit
   jamais déborder. */
.mentions { padding: var(--rf-1) 0 2px; }

.mentions__table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--t-2);
    display: block;
    overflow-x: auto;
    overflow-y: hidden;
}

.mentions__table th {
    text-align: left;
    font-weight: 600;
    color: var(--text-3);
    font-size: var(--t-1);
    text-transform: uppercase;
    letter-spacing: .04em;
    padding: var(--rf-1) var(--rf-2) var(--rf-1) 0;
}

.mentions__table td {
    padding: var(--rf-1) var(--rf-2) var(--rf-1) 0;
    border-top: 1px solid var(--line);
    vertical-align: top;
}

.mentions__nom { display: block; }

/* Le code du dossier sous le libellé, et non à côté : c'est une référence, on
   la cherche quand on en a besoin. */
.mentions__code {
    display: block;
    font-size: var(--t-1);
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
}

.mentions__valeur { font-variant-numeric: tabular-nums; word-break: break-word; }

.mentions details > summary {
    cursor: pointer;
    font-size: var(--t-2);
    color: var(--text-3);
    padding: var(--rf-1) 0;
}

/* La NATURE « Avoir », sous le numéro. Elle se tient à côté d'« Acompte » et de
   « Reprise », qui restent grises : seul l'avoir retourne le sens des montants
   de sa ligne, et il portait le gris des deux autres. Le trait le détache du
   fond blanc de la carte, que le gris plein ne faisait pas. */
.badge.avoir {
    background: var(--owed-soft);
    color: var(--owed);
    border: 1px solid var(--owed);
    font-weight: 600;
}

/* Une case qui n'a rien à dire, et qui le dit.

   Un avoir n'attend aucun règlement. Laisser la cellule VIDE se lit comme une
   donnée manquante — « pourquoi cette ligne n'a-t-elle pas d'état ? » — là où le
   tiret répond : rien n'est attendu. Le gris efface la case du balayage sans
   l'effacer de la grille. */
.badge-vide { color: var(--text-2); font-size: var(--t-2); }

/* ------------------------------------------------- actions d'une ligne */

/* UNE action visible par ligne — celle qui vient ensuite — et le reste sous un
   menu.

   Vingt-cinq lignes portant chacune trois à cinq boutons faisaient jusqu'à
   soixante-quinze commandes à l'écran, toutes au même poids visuel : rien
   n'était mis en avant, et le regard ne se posait plus sur les montants, qui
   sont pourtant ce qu'on vient lire. Les largeurs variables déchiquetaient en
   prime la colonne. */
.row-actions {
    display: inline-flex; align-items: center; gap: var(--rf-2);
    justify-content: flex-end; white-space: nowrap;
}

.row-actions .primary,
.row-actions .ghost { padding: var(--rf-1) var(--rf-3); font-size: var(--t-2); }

/* L'action principale garde une largeur minimale : « Envoyer », « Facturer » et
   « Encaisser » ne font pas la même longueur, et sans elle la colonne danse
   d'une ligne à l'autre. */
.row-actions .row-main { min-width: 92px; text-align: center; }

/* L'action qui fait SORTIR l'argent porte la couleur de sa pastille.

   Le bouton fantôme suffisait en thème sombre, mais il rend son libellé dans la
   couleur d'accent : en thème clair, « Rembourser » réapparaissait dans le vert
   d'eau d'« Encaisser » — précisément la confusion qu'on venait d'ôter. Vu à
   l'écran, pas dans le code ; les deux thèmes ne se relisent pas l'un l'autre.

   La bordure suit, assez pour que le bouton reste un bouton. */
.row-actions .row-sortante {
    color: var(--owed);
    border-color: var(--owed-soft);
}
.row-actions .row-sortante:hover { background: var(--owed-soft); }

.row-menu { position: relative; display: inline-flex; }

.row-menu > summary {
    list-style: none; cursor: pointer;
    padding: var(--rf-1) var(--rf-2); border-radius: var(--radius);
    border: 1px solid var(--line); color: var(--text-3);
    font-size: var(--t-2); line-height: 1.1;
    transition: background .18s var(--ease), color .18s var(--ease);
}
.row-menu > summary::-webkit-details-marker { display: none; }
.row-menu > summary:hover { background: var(--line); color: var(--text); }

/* LE CHEVRON PIVOTE, ET LA RANGÉE LE DÉSIGNE.

   Deux défauts d'accessibilité du même geste. Le premier : rien ne disait si le
   menu était ouvert autrement qu'en voyant la liste. Le second : la commande
   restait au gris le plus pâle tant qu'on ne la survolait pas ELLE, si bien
   qu'en parcourant une rangée on ne savait pas qu'il y avait quelque chose à
   ouvrir — c'est ce qui la rendait introuvable. */
.row-menu > summary { transition: background .18s var(--ease), color .18s var(--ease), transform .18s var(--ease); }
.row-menu[open] > summary { transform: rotate(180deg); background: var(--line); color: var(--text); }
tr:hover .row-menu > summary,
.rf-table__row:hover .row-menu > summary { color: var(--text-2); border-color: var(--text-3); }

.row-menu-items {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
    min-width: 190px; padding: var(--rf-1);
    display: flex; flex-direction: column; gap: 2px;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius); box-shadow: 0 10px 30px rgba(0, 0, 0, .18);
    animation: rise .16s var(--ease);
}

.row-menu-items a,
.row-menu-items button {
    display: block; width: 100%; text-align: left;
    padding: var(--rf-2) var(--rf-2); border: 0; border-radius: var(--radius);
    background: transparent; color: var(--text);
    font: inherit; font-size: var(--t-2); text-decoration: none; cursor: pointer;
}
.row-menu-items a:hover,
.row-menu-items button:hover { background: var(--line); }

/* Les corrections — avoir, annulation, suppression — se distinguent : elles
   sont rares, lourdes, et ne doivent pas se cliquer par inadvertance. */
.row-menu-items .danger { color: var(--err); }
.row-menu-items hr { border: 0; border-top: 1px solid var(--line); margin: var(--rf-1) 2px; }

/* La ligne entière ouvre le détail : c'est le geste que tout le monde essaie
   d'abord, et il ne faisait rien. */
tbody tr.openable { cursor: pointer; }
tbody tr.openable:hover { background: var(--line); }

@media (max-width: 900px) {
    /* Chaque ligne est déjà une carte : l'action principale prend la largeur,
       le menu reste à côté. */
    .row-actions { display: flex; width: 100%; }
    .row-actions .row-main { flex: 1; min-width: 0; padding: var(--rf-2) var(--rf-3); }
}

/* --------------------------------------------------------------- violations */

.violation {
    display: flex; gap: var(--rf-3); align-items: flex-start;
    padding: var(--rf-3) var(--rf-3); border-radius: var(--radius);
    margin-top: var(--rf-2); font-size: var(--t-2);
}
.violation.err { background: var(--err-soft); }
.violation.warn { background: var(--warn-soft); }
.violation .rule {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: var(--t-1); font-weight: 600; padding-top: 1px; white-space: nowrap;
}
.violation.err .rule { color: var(--err); }
.violation.warn .rule { color: var(--warn); }
.violation .path { color: var(--text-3); font-size: var(--t-1); }

/* ---------------------------------------------------------------- résultat */

.result { text-align: center; padding: var(--rf-8) var(--rf-6); animation: pop .35s var(--ease); }
@keyframes pop { from { opacity: 0; transform: scale(.97); } to { opacity: 1; transform: none; } }
.result .mark { font-size: var(--t-5); line-height: 1; }
.result .number {
    font-size: var(--t-5); font-weight: 600; letter-spacing: -.02em;
    margin: var(--rf-3) 0 var(--rf-1); font-variant-numeric: tabular-nums;
}
.result .meta { color: var(--text-2); font-size: var(--t-2); }
.result .hash {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: var(--t-1); color: var(--text-3); margin-top: var(--rf-3); word-break: break-all;
}
.result .actions {
    margin-top: var(--rf-5); display: flex; flex-wrap: wrap; gap: var(--rf-2); justify-content: center;
}

/* Le bouton porte le nom du fichier, qui est long. À trois de front dans une
   colonne étroite, chacun tombait sur deux lignes et « FAC-2026-000172.pdf »
   se coupait en plein milieu du numéro. Mesuré le 23 septembre 2026 sur un
   écran de 390 points : on empile, pleine largeur, et le nom tient. */
@media (max-width: 560px) {
    .result { padding: var(--rf-6) var(--rf-4); }
    .result .actions { flex-direction: column; align-items: stretch; }
    .result .actions button { width: 100%; }
}

/* ------------------------------------------------------------------ divers */

.empty { text-align: center; padding: var(--rf-10) var(--rf-5); color: var(--text-3); font-size: var(--t-2); }
.empty .mark { font-size: var(--t-5); opacity: .5; display: block; margin-bottom: var(--rf-2); }

/* Le message flotte dans la couche supérieure (popover), et non dans le flux :
   une boîte de dialogue ouverte avec showModal() y monte aussi, et aucun
   z-index ne passe devant elle. Sans cela, « Renseignez le montant » s'affiche
   derrière le voile de la modale qui l'a provoqué — invisible, au moment précis
   où il compte. Les règles ci-dessous écrasent la mise en forme par défaut du
   navigateur pour un popover (bordure, fond, centrage). */
/* Un motif de refus tient en deux lignes dans un tableau : au-delà il pousse
   la colonne et déforme la ligne entière. Le texte complet reste au survol. */
/* Dans la vue en cartes, la cellule de transmission empile son verdict, son
   motif et son bouton : côte à côte, le motif se réduisait à deux mots coupés
   et le bouton à trois lettres. */
@media (max-width: 760px) {
    td[data-label]:has(.clamp) { flex-direction: column; align-items: flex-start; gap: var(--rf-1); }
    td[data-label] .clamp { max-width: 100%; }
}

.clamp {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; max-width: 34ch;
}

/* Les messages après une action : voir « LA PILE DES MESSAGES », composants.css. */

dialog {
    border: 0; border-radius: var(--radius-lg); padding: var(--rf-6);
    background: var(--surface); color: var(--text);
    box-shadow: var(--shadow); max-width: 440px; width: calc(100% - 40px);
}
dialog::backdrop { background: rgba(0, 0, 0, .35); backdrop-filter: blur(3px); }
dialog h2 { margin-bottom: var(--rf-4); }
/* LES DEUX NOMS DE LA MÊME RANGÉE, ET L'UN N'ÉTAIT PAS DESSINÉ.

   Vingt-et-un dialogues, deux classes pour leur rangée de boutons : dix-sept
   portent « actions », quatre portent « dialog-actions » — et cette seconde
   n'avait AUCUNE règle de mise en page, nulle part. Ses boutons retombaient
   donc sur le flux : alignés à GAUCHE, séparés par les 4 px d'un espace de
   balisage au lieu des 10 px voulus.

   Mesuré au bac le 4 septembre 2026 — distance du bord droit du bouton qui
   engage au bord droit de sa rangée :

     .actions        (17 dialogues)   0 px     à droite, écart 10 px
     .dialog-actions ( 4 dialogues)   199 px   « Émettre la facture »
                                      179 px   « Enregistrer » (séjour, pensionnaire)
                                      489 px   « Modifier la fiche »

   Le doigt apprend une place, pas un libellé. « Émettre » — le geste qui
   consomme un numéro, et un numéro consommé ne se récupère pas — se trouvait à
   199 px de là où dix-sept autres fenêtres l'avaient appris, et à 4 px
   d'« Annuler ». Une seule règle, et les vingt-et-un s'alignent. */
dialog .actions,
dialog .dialog-actions { display: flex; gap: var(--rf-2); justify-content: flex-end; margin-top: var(--rf-5); }

.note {
    font-size: var(--t-2); color: var(--text-2);
    background: var(--surface-2); border: 1px solid var(--line);
    border-radius: var(--radius); padding: var(--rf-3) var(--rf-3); margin-top: var(--rf-4);
}
.note strong { color: var(--text); }

/* Une note qui appelle une décision, et non une simple information : elle
   emprunte la teinte de l'état plutôt qu'une couleur à elle, pour que le même
   jaune veuille dire la même chose partout dans le produit. */
.note.warn { background: var(--warn-soft); border-color: var(--warn); color: var(--text); }
.note.err { background: var(--err-soft); border-color: var(--err); color: var(--text); }
.note.decision { display: flex; gap: var(--rf-3); align-items: center; justify-content: space-between; }
.note.decision button { flex-shrink: 0; }

/* Consentement explicite. La case et son libellé forment une seule cible :
   viser un carré de 16 px au doigt fait manquer le geste, et manquer un
   consentement n'est pas une erreur comme une autre. */
.check {
    display: flex; gap: var(--rf-3); align-items: flex-start;
    margin-top: var(--rf-4); padding: var(--rf-3) var(--rf-3);
    border: 1px solid var(--line-control); border-radius: var(--radius);
    font-size: var(--t-2); line-height: 1.45; cursor: pointer;
}
.check:hover { border-color: var(--accent); background: var(--accent-soft); }
.check input {
    width: 18px; height: 18px; margin: 1px 0 0; flex: 0 0 auto;
    accent-color: var(--marque); cursor: pointer;
}
.check strong { color: var(--text); }

.rule-row { display: flex; gap: var(--rf-3); padding: var(--rf-3) 0; border-bottom: 1px solid var(--line); font-size: var(--t-2); }
.rule-row:last-child { border-bottom: 0; }
.rule-row code {
    /* « display » posé d'office : la dimension ne doit pas dépendre du parent.
       Sur un « span » ou un « code », width et height sont ignorés dès que le
       conteneur cesse d'être flex — et l'élément tombe à zéro sans rien dire. */
    display: inline-block;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: var(--t-2); color: var(--accent-profond); min-width: 82px; font-weight: 600;
}

/* ------------------------------------------------------------- adaptation */

@media (max-width: 1040px) {
    .split { grid-template-columns: 1fr; }
}

/* Tablette et téléphone. La barre latérale devient une barre d'onglets ancrée
   en bas : c'est là qu'un pouce atteint sans effort, alors que le haut de
   l'écran exige de changer la prise en main. */
@media (max-width: 860px) {
    .shell { flex-direction: column; }

    .sidebar {
        position: fixed; bottom: 0; left: 0; right: 0; top: auto;
        width: auto; flex: none; height: auto; z-index: 40;
        flex-direction: row; gap: 0;
        padding: var(--rf-1) var(--rf-1);
        /* La zone d'accueil de l'iPhone mange le bas de l'écran : sans cette
           marge, le dernier onglet passe sous la barre d'accueil du système. */
        padding-bottom: max(var(--rf-1), env(safe-area-inset-bottom));
        border-right: 0; border-top: 1px solid var(--line);
        background: color-mix(in srgb, var(--surface) 88%, transparent);
        backdrop-filter: saturate(180%) blur(20px);
        -webkit-backdrop-filter: saturate(180%) blur(20px);
    }

    /* La barre du bas ne porte QUE de la navigation. Le sélecteur de dossier y
       est entré le jour où il a cessé de s'absenter à un seul dossier (lot 8) :
       207 px pris sur 375, et trois onglets sur cinq poussés hors de l'écran,
       « Plus » compris — donc quatorze destinations inatteignables au doigt. Le
       dossier se lit ailleurs sur téléphone : au titre de l'accueil, dans la
       feuille « Plus », et un cabinet a son onglet Dossiers. */
    .brand, .identity, .org-switch { display: none; }

    nav {
        flex-direction: row; width: 100%;
        justify-content: space-around;
    }

    /* La colonne défile sur grand écran ; ici c'est une barre, elle ne défile
       pas et ne doit pas se laisser comprimer. */
    nav { flex: none; overflow: visible; }

    /* Quatre vues visibles et « Plus ». Au-delà, chaque cible passe sous la
       largeur d'un pouce : à neuf onglets, « Clients » et « Prestations » se
       touchaient et « E-reporting » se coupait en deux lignes. */
    /* La barre du téléphone ne range que des cibles : un intitulé de groupe y
       serait une case morte qui vole soixante-quinze pixels au pouce.

       « display: contents » et non « none » : deux entrées non secondaires —
       les factures émises, les clients — vivent dans un groupe, et les masquer
       avec lui les ferait disparaître de la barre. Le groupe s'efface, ses
       boutons prennent sa place dans la rangée.

       Un <details> replié cacherait ses enfants malgré cela : un garde en
       JavaScript rouvre les groupes dès que la barre passe en mode étroit. */
    /* Préfixées par « nav » pour peser plus lourd que les règles de base, qui
       vivent plus bas dans ce fichier : une requête média n'ajoute aucune
       spécificité, et à égalité c'est l'ordre qui tranche. Sans ce poids, les
       six intitulés restaient affichés — plus de cinq cents pixels dans une
       barre qui en fait trois cent soixante-sept, et les quatre onglets réduits
       à quatre pixels chacun. Mesuré au navigateur, pas déduit. */
    /* La barre du téléphone ne range que des cibles : les intitulés s'effacent,
       et le repli ne doit jamais y masquer un onglet. Préfixées par « nav » pour
       peser plus lourd que les règles de base, plus bas dans ce fichier — une
       requête média n'ajoute aucune spécificité. */
    nav .nav-groupe { display: none; }

    /* Le repli n'existe pas ici : la barre du bas n'a pas de groupes, et un
       groupe replié sur grand écran ne doit pas amputer ses quatre cibles.
       Cette règle disait « none » — l'inverse de son intention — et « Émises »
       comme « Clients » disparaissaient dès qu'on avait replié leur groupe
       ailleurs. Vu au navigateur, pas déduit.

       Elle passe AVANT celle des entrées secondaires : à spécificité égale,
       c'est la dernière qui gagne, et les secondaires doivent rester masquées. */
    nav button.replie { display: flex; }

    nav button.secondary { display: none; }
    .only-mobile { display: flex; }

    /* Icône au-dessus, libellé dessous : la cible tactile fait 56 px de haut,
       au-delà des 44 px recommandés, et reste lisible. */
    nav button {
        flex: 1 1 0; min-width: 0;
        flex-direction: column; gap: 3px;
        padding: var(--rf-2) 2px; border-radius: var(--radius);
        font-size: var(--t-1); text-align: center; line-height: 1.15;
        color: var(--text-3);
    }
    nav button[aria-current="page"] { background: transparent; color: var(--accent-profond); }
    nav button .ic { width: 21px; height: 21px; }

    /* Les libellés longs sont raccourcis en CSS plutôt que dans le HTML : la
       version large garde ses intitulés complets. « E-reporting » n'y figure
       plus — il est passé derrière « Plus », où il tient en entier. */
    /* « Émises » ne veut rien dire seule : dans la colonne elle vit sous un
       intitulé « Factures » qui lui donne son sens, et la barre du bas n'a pas
       d'intitulés. Elle y reprend donc le mot entier. */
    nav button[data-view="home"] .lbl::after { content: attr(data-court); }
    nav button[data-view="new"] .lbl::after { content: attr(data-court); }
    nav button[data-view="invoices"] .lbl::after { content: attr(data-court); }
    nav button[data-view="home"] .lbl,
    nav button[data-view="new"] .lbl,
    nav button[data-view="invoices"] .lbl { font-size: 0; }
    nav button[data-view="home"] .lbl::after,
    nav button[data-view="new"] .lbl::after,
    nav button[data-view="invoices"] .lbl::after { font-size: var(--t-1); }

    /* Cinq cibles dans 375 px : chacune dispose de 75 px, largement au-dessus
       des 44 px recommandés. Le libellé peut donc reprendre une taille lisible
       et s'interdire de passer à la ligne. */
    nav button { font-size: var(--t-1); }
    nav button .lbl { white-space: nowrap; }

    /* --------------------------------------------------- feuille « Plus »
       Elle monte du bas, là où le pouce se trouve déjà après avoir touché
       l'onglet. Une boîte centrée aurait obligé à remonter la main. */
    dialog.sheet {
        max-width: none; width: 100%;
        margin: auto auto 0; border-radius: var(--radius-lg) var(--radius-lg) 0 0;
        padding: var(--rf-2) var(--rf-4) calc(var(--rf-4) + env(safe-area-inset-bottom));
        max-height: 88vh; overflow-y: auto;
        animation: monte .24s var(--ease);
    }
    @keyframes monte { from { transform: translateY(100%); } }
    @media (prefers-reduced-motion: reduce) {
        dialog.sheet { animation: none; }
    }

    .sheet-grip {
        display: block; width: 38px; height: 4px; border-radius: var(--radius-pill);
        background: var(--line-strong); margin: 0 auto var(--rf-3);
    }

    /* Les deux boutons de pied côte à côte, à parts égales : « Se déconnecter »
       n'est pas une action secondaire ici, c'est la seule qui existe. */
    dialog.sheet .actions { margin-top: var(--rf-3); }
    dialog.sheet .actions button { flex: 1 1 0; padding: var(--rf-3); }

    /* La barre d'onglets flotte au-dessus du contenu : on lui réserve sa place. */
    /* 84 px : la hauteur de la barre de navigation basse, pas un espacement.
       Hors échelle et nommé comme tel. */
    /* « 100% » ET NON « none ». Le plafond de 1 400 px n'a pas de sens sur un
       téléphone, mais « none » ne l'abaisse pas : il le SUPPRIME, et plus rien
       ne borne « main ». Un enfant plus large que l'écran l'étire alors à sa
       propre taille, et emporte la barre d'onglets avec lui.

       Mesuré le 30 septembre 2026 : l'aperçu A4 de l'écran Document, à 794 px et
       à dessein, poussait « main » à 826 px sur un écran de 375. Le bouton
       « plus » de cette barre se retrouvait à 822 px — hors d'atteinte du doigt.
       Son conteneur porte pourtant « overflow-x: auto » : il ne défilait pas,
       faute d'une largeur à laquelle se borner. */
    main { padding: var(--rf-5) var(--rf-4) calc(84px + env(safe-area-inset-bottom));
           max-width: 100%; }

    h1 { font-size: var(--t-5); }
    .page-head { flex-direction: column; align-items: stretch; gap: var(--rf-3); }
    .page-head button { width: 100%; padding: var(--rf-3) var(--rf-5); }

    .card { padding: var(--rf-4); border-radius: var(--radius); }
    .grid.two, .grid.three { grid-template-columns: 1fr; }

    /* DEUX DATES SE LISENT CÔTE À CÔTE TANT QU'ELLES TIENNENT. Repliée en une
       colonne, la grille posait « Au » sous « Du » : l'intervalle se lisait
       comme deux réglages. Deux colonnes égales dès que chacune garde 140 px
       (une date et son picto, « 18 sept. 2026 » compris), une seule en
       dessous ; jamais trois. Les autres champs de la grille prennent la
       rangée entière. Demandé le 18 octobre 2026. */
    .grid.dates-en-paire,
    .champs-serres {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(max(140px, calc((100% - var(--rf-3)) / 2)), 1fr));
        gap: var(--rf-3);
    }
    .grid.dates-en-paire > :not(:has(input[type="date"])) { grid-column: 1 / -1; }

    /* Deux colonnes au lieu de quatre : un indicateur tient dans une moitié
       d'écran, tout le reste prend la largeur. Un graphique à un quart de
       téléphone ne se lit pas. */
    .board { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--rf-3); }
    .board > .w-moyen, .board > .w-grand { grid-column: span 2; }

    /* Les commandes rentrent dans la carte : débordantes, celles de deux cartes
       voisines se rejoignaient dans l'espace de 12 px qui les sépare, et le
       doigt ne savait plus laquelle il visait. */
    .w-remove { top: 6px; left: 6px; }
    .w-grip { top: 6px; right: 6px; }

    /* Et le contenu leur laisse la place : posées sur la carte, elles couvraient
       le début du libellé — « ncours client » au lieu d'« Encours client ». */
    .board.editing .widget > .card,
    .board.editing .widget > .kpi { padding-top: var(--rf-10); }

    .board-bar { flex-direction: column; align-items: stretch; }
    .board-bar .head-actions button { flex: 1; padding: var(--rf-3); }
    .page-head .head-actions { flex-direction: row; }
    .page-head .head-actions button { width: auto; flex: 1; }

    /* Champs à 16 px : en dessous, iOS zoome automatiquement à la mise au point
       et l'utilisateur se retrouve avec une page décadrée. */
    input, select, textarea { font-size: var(--t-3); padding: var(--rf-3) var(--rf-3); }

    /* UN CHAMP DE DATE NATIF NE RESPECTE PAS SA LARGEUR SOUS iOS.

       Preuve à l'appui, 8 septembre 2026, sur un vrai iPhone en navigation
       privée : le champ rendait « 8 sept. 2026 » centré avec son icône native à
       l'intérieur, et sa largeur venait de CE TEXTE — pas de la feuille. Il
       poussait la carte, qui poussait la page : le titre « Nouvelle facture » se
       retrouvait coupé à gauche.

       « min-width: 0 » avait été posé le 2 septembre pour cela, sans pouvoir le
       constater, et la note prévenait : « à revoir si le débordement persiste,
       car alors la cause est ailleurs ». Elle l'était. La largeur minimale d'un
       contrôle NATIF ne se laisse pas réduire par « min-width » ; il faut
       renoncer à l'habillage natif, comme le produit le fait déjà pour les
       menus déroulants — et pour la même raison.

       SUR APPAREIL TACTILE SEULEMENT, et c'est ce qui rend le remède sans
       risque. Le produit s'appuie sur « ::-webkit-calendar-picker-indicator » —
       invisible, étendu à tout le champ — pour ouvrir le sélecteur au CLIC ;
       renoncer à l'habillage natif pourrait le supprimer, et un champ de date
       qui ne s'ouvre plus serait bien pire qu'un débordement.

       « max-width » seul ne suffisait donc pas : une fenêtre étroite sur
       ordinateur y tombe aussi, et c'est exactement la vue adaptative où le
       sélecteur fonctionnait. « hover: none » et « pointer: coarse » désignent
       le doigt, et sur un doigt la question ne se pose pas — iOS ouvre sa roue
       au toucher de n'importe quel champ de date, sans passer par ce
       pseudo-élément.

       L'icône ne disparaît pas : c'est la nôtre, dessinée en image de fond
       quelques lignes plus haut. */
    @media (hover: none) and (pointer: coarse) {
        input[type="date"] { -webkit-appearance: none; appearance: none; }
    }

    dialog { padding: var(--rf-5); border-radius: var(--radius-lg); }
    dialog .actions { flex-direction: column-reverse; }
    /* UNE PILULE PLEINE LARGEUR N'EST PLUS UNE PILULE, C'EST UN LOSANGE.

       Les boutons du produit portent « var(--radius-pill) » — une pilule, qui
       tient très bien sur un geste compact de cent cinquante pixels. Dans un
       dialogue mobile, la disposition les met en COLONNE et à 100 % : 295 px de
       large sur 48 de haut, et le même rayon dessine deux lozenges empilés.
       « Les boutons sur la page de confirmation sont ronds, affreux », relevé le
       7 septembre 2026, et c'est exact.

       Le rayon standard du produit reprend la main sous 860 px : 11 px, soit un
       quart de la hauteur au lieu de la moitié. La forme suit la taille, ce qui
       est la règle — et non le composant, qui ne sait pas ce qu'on lui fera. */
    dialog .actions button { width: 100%; padding: var(--rf-3); border-radius: var(--radius); }

    /* La feuille garde ses deux boutons sur une ligne. Empilés, ils poussaient
       « Se déconnecter » sous le bord de l'écran — le défaut même qu'on est en
       train de corriger, reproduit deux crans plus bas. */
    dialog.sheet .actions { flex-direction: row; margin-top: var(--rf-3); }
    dialog.sheet .actions button { width: auto; flex: 1 1 0; }

    /* Une ligne de facture devient une fiche : deux colonnes lisibles plutôt que
       sept colonnes écrasées. La désignation prend toute la largeur, le reste se
       range par paires. */
    .line {
        grid-template-columns: 1fr 1fr;
        gap: var(--rf-3); padding: var(--rf-3); position: relative;
    }
    .line > div:first-child { grid-column: 1 / -1; }
    .line .rm {
        position: absolute; top: 8px; right: 8px;
        width: 32px; height: 32px; font-size: var(--t-4);
        display: flex; align-items: center; justify-content: center;
    }

    /* Sur un rapprochement, le numéro de commande prend toute la largeur et les
       quatre chiffres se rangent par paires — « commandé » face à « reçu »,
       « facturé » face à « écart », ce qui est exactement la lecture qu'on
       fait. */
    .line--rapprochement { grid-template-columns: 1fr 1fr; }
    .line--rapprochement > div:first-child { grid-column: 1 / -1; }

    /* Sur une réception, la désignation prend toute la largeur, et les quatre
       chiffres se rangent par paires : c'est la seule disposition où « Commandé »
       et « Reste » restent côte à côte, ce qui est la comparaison qu'on fait. */
    .line--reception { grid-template-columns: 1fr 1fr; }
    .line--reception > div:first-child { grid-column: 1 / -1; }

    /* Sur un achat, l'article ET la désignation prennent toute la largeur :
       l'article parce qu'il est le premier champ, la désignation parce qu'une
       demi-colonne de téléphone n'en montrerait que le début — et c'est elle
       que le fournisseur lira sur le bon. */
    .line--achat { grid-template-columns: 1fr 1fr; }
    .line--achat > div:nth-child(2) { grid-column: 1 / -1; }

    /* Sur une remise, c'est le MOTIF qui prend toute la largeur, pas le premier
       champ : « Remise / Frais » tient dans une demi-colonne. */
    .line.adjustment { grid-template-columns: 1fr 1fr; }
    .line.adjustment > div:first-child { grid-column: auto; }
    .line.adjustment > div:nth-child(2) { grid-column: 1 / -1; }

    .subline {
        grid-template-columns: 1fr 1fr;
        gap: var(--rf-3); padding: var(--rf-3); position: relative;
        margin-left: var(--rf-3);
    }
    .subline > div:nth-child(2) { grid-column: 1 / -1; }
    .subline .rm {
        position: absolute; top: 6px; right: 6px;
        width: 30px; height: 30px; font-size: var(--t-4);
        display: flex; align-items: center; justify-content: center;
    }
    .add-subline { margin-left: var(--rf-3); font-size: var(--t-2); padding: var(--rf-1) 0; }

    /* Le retrait suit celui des remises : sur téléphone, dix pixels de moins
       rendent une ligne entière de texte à la zone de saisie. */
    .line-detail { margin-left: var(--rf-3); }
    .line-detail .detail-toggle { font-size: var(--t-2); padding: var(--rf-1) 0; }

    .kpi { padding: var(--rf-4); }
    .kpi-value { font-size: var(--t-5); }

    /* Le graphe est réduit d'environ moitié : sans compensation, les libellés
       tomberaient à 5 px. Un mois sur deux est masqué pour éviter le chevauchement. */
    .chart-axis { font-size: var(--t-4); }
    .chart-axis.skip { display: none; }

    /* Un tableau à sept colonnes ne se lit pas sur un téléphone. Chaque ligne
       devient une carte, et l'en-tête de colonne s'affiche devant sa valeur —
       c'est data-label, posé côté JavaScript, qui porte le libellé. */
    table, thead, tbody, tr, th, td { display: block; }
    thead { display: none; }

    tbody tr {
        border: 1px solid var(--line); border-radius: var(--radius);
        background: var(--surface-2); padding: var(--rf-3) var(--rf-3); margin-bottom: var(--rf-2);
    }
    tbody tr:hover { background: var(--surface-2); }

    td {
        border: 0; padding: var(--rf-1) 0;
        display: flex; justify-content: space-between; align-items: baseline; gap: var(--rf-3);
        text-align: right;
    }
    td::before {
        content: attr(data-label);
        font-size: var(--t-1); text-transform: uppercase; letter-spacing: .04em;
        color: var(--text-3); font-weight: 600; text-align: left; flex: 0 0 auto;
    }
    /* UNE CELLULE QUI PORTE DEUX BLOCS NE S'ÉLARGIT PAS : ELLE SE PLIE.

       Le libellé de colonne est un élément flex — c'est « td::before » juste
       au-dessus —, si bien qu'une valeur en deux morceaux en fait TROIS. Sans
       repli, le minimum d'une rangée vaut la somme de ses éléments : mesuré le
       24 septembre 2026 sur la liste des bons de commande, « BC-2026-000013 »
       suivi de « reçu 15 sur 40 » portait ce minimum à 347 px sur un écran de
       375, et TOUTE LA PAGE débordait de quatre pixels — « main » à 379, sans
       qu'aucun de ses enfants ne dépasse.

       Avec le repli, le minimum n'est plus que celui du plus large des éléments,
       et la rangée cesse de pousser. Douze cellules du produit portent deux
       blocs — le devis et sa facture, l'état et son motif de refus, le contrat
       et son client — et aucune n'était gardée : il suffisait qu'une donnée
       arrive pour que l'écran déborde. Une règle vaut mieux que douze
       enveloppes à ne pas oublier.

       « margin-left: auto » garde la valeur À DROITE quand elle passe à la
       ligne : un élément seul sur sa ligne, sous « space-between », irait se
       coller à gauche, sous le libellé, et l'on ne saurait plus lequel des deux
       il commente. */
    td[data-label] { flex-wrap: wrap; }
    td[data-label] > * { margin-left: auto; }

    /* Une cellule sans libellé porte les actions : elle occupe toute la largeur. */
    td:not([data-label]) { justify-content: flex-end; padding-top: var(--rf-2); flex-wrap: wrap; }
    td.num { text-align: right; }

    /* Le détail qui ne survit pas à la réduction. Sur un téléphone, ce qu'on
       cherche dans une facture c'est son numéro, son client et son TTC ; la
       ventilation HT/TVA et le cadre réglementaire restent à un doigt, sur la
       fiche complète ou sur un écran plus large. */
    td.on-wide { display: none; }
}

/* Téléphone. Deux colonnes tiennent jusqu'à 375 px pour des quantités et des
   prix — empiler six champs obligerait à faire défiler chaque ligne de facture,
   ce qui est pire qu'un champ un peu étroit. */
@media (max-width: 480px) {
    .kpis { grid-template-columns: 1fr 1fr; gap: var(--rf-2); }
    .kpi-value { font-size: var(--t-4); }
    .kpi-foot { font-size: var(--t-1); }
}

/* En dessous, plus rien ne tient à deux colonnes. */
@media (max-width: 340px) {
    .line { grid-template-columns: 1fr; }
    .kpis { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------- porte d'entrée */

.gate {
    min-height: 100vh;
    /* `align-items: center` sur un enfant PLUS HAUT que la fenêtre pousse son
       sommet hors de la zone défilable : il devient inatteignable, et l'on
       arrive sur un écran dont le titre est au-dessus du vide. C'est ce qui
       arrivait à l'enrôlement au second facteur, le plus haut de nos volets.
       `flex-start` plus `margin: auto` sur la carte centre exactement de la même
       façon quand elle tient, et laisse défiler quand elle déborde. */
    display: flex; align-items: flex-start; justify-content: center;
    padding: var(--rf-6);
    /* Deux halos très diffus : la page respire au lieu d'être un aplat, et rien
       ne bouge — une animation sur l'écran de connexion se remarque au
       deuxième passage, et agace au dixième. */
    background:
        radial-gradient(60rem 40rem at 20% -10%, var(--accent-soft), transparent 60%),
        radial-gradient(50rem 35rem at 85% 110%, var(--accent-soft), transparent 55%),
        var(--bg);
}

.gate-card {
    margin: auto;
    width: 100%; max-width: 410px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: var(--rf-8);
    box-shadow: var(--shadow);
    animation: rise .3s var(--ease);
}

.gate-brand { text-align: center; margin-bottom: var(--rf-6); }
.gate-brand .mark { margin: 0 auto var(--rf-3); }
.gate-brand strong { display: block; font-size: var(--t-4); letter-spacing: -.03em; }
.gate-brand strong em { font-style: normal; color: var(--accent-profond); }
.gate-brand span {
    display: block; font-size: var(--t-2); color: var(--text-2);
    margin: var(--rf-1) auto 0; max-width: 30ch; line-height: 1.45;
}

.gate-card h2 { font-size: var(--t-3); text-align: center; }

.gate-switch { text-align: center; font-size: var(--t-2); color: var(--text-2); margin: var(--rf-4) 0 0; }

/* LES LIENS N'AVAIENT AUCUNE RÈGLE. Vingt ancres sont rendues par le produit,
   et pas une déclaration ne les touchait : elles tombaient donc sur le bleu du
   navigateur, et « :visited » les passait au violet. Deux couleurs que le
   produit n'a pas choisies, dont l'une dépend de l'historique de navigation du
   lecteur — « les voir toutes » n'avait pas la même teinte chez celui qui avait
   déjà cliqué.

   Ils prennent l'encre du texte, et se signalent par un soulignement discret
   qui s'affirme au survol. C'est la grammaire que « .aide-renvoi » avait déjà,
   et qu'il était seul à avoir. */
a {
    color: var(--marque-texte);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: var(--line-strong, var(--line));
}

a:visited { color: var(--marque-texte); }
a:hover { text-decoration-color: currentColor; }

/* Une ancre déguisée en bouton n'est pas un lien : elle porte déjà sa forme,
   et le soulignement la barrerait en travers. */
a.ghost, a.primary, a.brand, a.rf-btn { text-decoration: none; }

button.link {
    background: none; border: 0; padding: 0;
    color: var(--accent-profond); font: inherit; font-size: var(--t-2); cursor: pointer;
}
button.link:hover { text-decoration: underline; }

.gate-error {
    margin-top: var(--rf-3); padding: var(--rf-2) var(--rf-3);
    background: var(--err-soft); color: var(--err);
    border-radius: var(--radius); font-size: var(--t-2);
}

/* Le refus d'un CHAMP, sous ce champ.

   Même encre que le bandeau de la fenêtre, mais sans son fond ni son pavé : à
   cette place il tient la largeur du champ, et il cohabite avec l'indication
   grise au-dessus de lui plutôt que de la remplacer — la consigne reste vraie
   pendant qu'on corrige. */
.champ-erreur {
    margin-top: var(--rf-1);
    font-size: var(--t-2);
    line-height: 1.45;
    font-weight: 500;
    color: var(--err);
}

/* Les sorties d'un refus, en rang. Elles portent des phrases entières — « 
   Encaisser le reste dû (100,00 €) » — et non des verbes seuls : c'est le
   montant qui décide du clic. */
#paymentRemedies {
    display: flex;
    flex-wrap: wrap;
    gap: var(--rf-2);
    margin-top: var(--rf-3);
}

/* Accusé de réception, pas confirmation : le lien a été demandé, ce qui ne dit
   pas qu'un compte existe. Le ton reste neutre pour la même raison. */
.gate-note {
    margin-top: var(--rf-3); padding: var(--rf-2) var(--rf-3);
    background: var(--ok-soft); color: var(--ok);
    border-radius: var(--radius); font-size: var(--t-2);
}

/* -------------------------------------------------- filtres et pagination */

.filters {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--rf-3);
}
.filters > div { flex: 0 0 auto; }
.filters .filter-search { flex: 1 1 260px; min-width: 200px; }
.filters button { align-self: flex-end; }

.pager {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--rf-4); flex-wrap: wrap; margin-top: var(--rf-4);
}
.pager-count { font-size: var(--t-2); color: var(--text-2); font-variant-numeric: tabular-nums; }
.pager-nav { display: flex; align-items: center; gap: var(--rf-3); }
.pager-pos { font-size: var(--t-2); color: var(--text-3); font-variant-numeric: tabular-nums; }
.pager button:disabled { opacity: .4; cursor: default; }
.pager button:disabled:hover { border-color: var(--line-strong); background: transparent; }

/* ------------------------------------------------------ aperçu de facture */

/* L'aperçu occupe l'écran : une facture lue dans une vignette n'est pas lue.
   Le téléchargement reste offert, mais il n'est plus le seul moyen de voir. */
/* Le display n'est posé que sur l'état ouvert. Un dialogue fermé vaut
   « display: none » par la feuille du navigateur, et une règle d'auteur sans
   [open] l'emporte sur elle : l'aperçu restait alors visible en permanence,
   dans le flux de la page, poussant tout ce qui suit. C'est le piège déjà
   rencontré avec .gate et .shell, et documenté en tête de ce fichier. */
dialog.viewer[open] {
    display: flex; flex-direction: column;
}

dialog.viewer {
    max-width: min(960px, 94vw); width: 100%;
    /* Hauteur explicite, pas seulement une borne : dans un conteneur en colonne,
       un enfant en flex-basis 0 ne réclame aucune place, et le dialogue se
       réduisait à son en-tête — le document restait à la hauteur par défaut
       d'un cadre, 150 px. */
    height: 90vh;
    padding: 0; overflow: hidden;
}
.viewer-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--rf-4); padding: var(--rf-4) var(--rf-5); border-bottom: 1px solid var(--line);
}
.viewer-head h2 {
    font-size: var(--t-3); margin: 0;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}
.viewer-actions { display: flex; align-items: center; gap: var(--rf-2); }
.viewer-actions .ghost {
    background: transparent; color: var(--accent-profond);
    border: 1px solid var(--line-control); border-radius: var(--radius-pill);
    padding: var(--rf-2) var(--rf-4); font: inherit; font-size: var(--t-2); cursor: pointer; text-decoration: none;
}
.viewer-actions .ghost:hover { border-color: var(--accent); background: var(--accent-soft); }
#pdfFrame { flex: 1 1 auto; width: 100%; min-height: 0; border: 0; background: var(--surface-2); }

@media (max-width: 860px) {
    dialog.viewer { max-width: 100vw; height: 100vh; border-radius: 0; }
    .filters { gap: var(--rf-2); }
    .filters > div { flex: 1 1 140px; }
    .filters .filter-search { flex: 1 1 100%; }
}

/* Les deux dates prennent chacune leur ligne sur un téléphone.

   Constaté sur iPhone, Safari, sur le staging : « Au » sortait de l'écran.
   Deux causes qui s'additionnent, et il faut les deux remèdes.

   La première est la règle par défaut des enfants de flex — « min-width: auto »,
   qui interdit de descendre sous la largeur de CONTENU. Un champ de date en
   réclame environ 170 px : « jj/mm/aaaa » plus les 38 px que nous réservons à
   notre icône de calendrier. Deux de ces champs plus la gouttière font 352 px
   dans un conteneur qui en offre 343 sur un écran de 375. La garde
   « min-width: 0 » est posée plus haut, avec les champs eux-mêmes.

   Mais rétrécir ne suffit pas : à 166 px, le champ tient dans l'écran en
   rognant sa propre date. Sous 600 px, chacun prend donc toute la largeur —
   c'est déjà ce que fait le champ de recherche, et une date se lit en entier ou
   ne se lit pas.

   Le sélecteur « :has » ne va pas partout ; là où il manque, la garde de
   rétrécissement empêche encore le débordement. Les deux se relaient. */
@media (max-width: 600px) {
    .filters > div:has(input[type="date"]) { flex: 1 1 100%; }
}

/* Compteur de consommation réseau
   ------------------------------------------------------------------
   La jauge n'est pas décorative : c'est le seul endroit du produit où
   le client voit venir une dépense. Elle reste verte tant qu'il n'y a
   rien à signaler — une jauge alarmiste dès le premier envoi apprend
   surtout à ne plus la regarder. */
.conso { margin-top: var(--rf-4); padding-top: var(--rf-4); border-top: 1px solid var(--line); }
.conso-tete {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--rf-3); font-size: var(--t-2); color: var(--text-2);
}
.conso-tete b { font-size: var(--t-3); font-weight: 600; font-variant-numeric: tabular-nums; }
.conso-tete b span { font-size: var(--t-2); font-weight: 400; color: var(--text-3); }
.conso-tete b.ok { color: var(--text); }
.conso-tete b.warn { color: var(--warn); }
.conso-tete b.err { color: var(--err); }

/* La jauge de consommation est celle de la refonte — `.rf-quota__*`, qui sait
   déborder. Celle d'ici ne pouvait pas : une largeur unique bornée à cent pour
   cent. Elle prend seulement sa marge haute, que la feuille de la refonte ne
   connaît pas puisqu'elle ignore ce qui l'entoure. */
.conso .rf-quota__track { margin-top: var(--rf-2); }
.conso .rf-quota__note { margin: var(--rf-2) 0 0; }

.conso-detail { margin: var(--rf-2) 0 0; font-size: var(--t-2); color: var(--text-3); line-height: 1.55; }
.conso-detail b { color: var(--text-2); font-weight: 600; }
.conso-alerte { margin: var(--rf-2) 0 0; font-size: var(--t-2); line-height: 1.55; }
.conso-alerte.warn { color: var(--warn); }
.conso-alerte.err { color: var(--err); }

/* Dépôt d'un relevé bancaire
   ------------------------------------------------------------------
   Un champ de fichier natif est minuscule et illisible : on l'habille
   d'une zone qui dit ce qu'on attend et où cliquer. Le champ reste dans
   le DOM — c'est lui qui ouvre le sélecteur, et le label le déclenche
   au clavier comme à la souris. */
.depot {
    display: flex; flex-direction: column; gap: 3px; align-items: center;
    margin-top: var(--rf-3); padding: var(--rf-5); cursor: pointer;
    border: 1.5px dashed var(--line-strong); border-radius: var(--radius);
    background: var(--surface-2);
    transition: border-color .18s var(--ease), background .18s var(--ease);
}
.depot:hover, .depot:focus-within { border-color: var(--accent); background: var(--accent-soft); }
.depot b { font-size: var(--t-3); }
.depot span { font-size: var(--t-2); color: var(--text-3); }

/* Le résultat du rapprochement.
   Le motif est un mot, pas une couleur seule : « montant seul » se lit,
   un point orange se devine. */
.rappro { margin-top: var(--rf-5); }
.rappro h3 {
    font-size: var(--t-2); letter-spacing: .04em; text-transform: uppercase;
    color: var(--text-3); margin: var(--rf-5) 0 var(--rf-2);
}
.rappro h3:first-child { margin-top: 0; }
.rappro .motif { font-size: var(--t-1); color: var(--text-3); display: block; }
.rappro .ecart { color: var(--warn); font-weight: 600; }
.rappro .lu { font-size: var(--t-2); color: var(--text-3); margin-top: var(--rf-3); }
.rappro td.pick { width: 34px; }

/* L'accès libre à la démonstration.
   ------------------------------------------------------------------
   Il vit sur l'écran de connexion et non derrière un lien qu'il faut
   avoir reçu : un lien partagé perd le texte qui l'accompagnait, et le
   visiteur d'un repartage se retrouvait sans moyen d'essayer.

   Le bloc est discret — un cadre, pas un bouton principal : il ne doit
   pas détourner celui qui vient créer son compte, qui est le geste qu'on
   préfère. */
.essai-libre {
    margin-top: var(--rf-6); padding-top: var(--rf-5); border-top: 1px solid var(--line);
    display: flex; flex-direction: column; gap: var(--rf-2); align-items: flex-start;
}
.essai-libre b { font-size: var(--t-3); }
.essai-libre p { font-size: var(--t-2); color: var(--text-3); line-height: 1.55; margin: 0; }
.essai-libre button { align-self: stretch; }
.essai-libre .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--t-2); color: var(--text-3); align-self: center;
    user-select: all;
}

/* La carte de fermeture de compte.
   ------------------------------------------------------------------
   Elle est en dernier, et signalée : on ne place pas la sortie au
   milieu du couloir. Le filet rouge n'est pas décoratif — il annonce
   que ce qui suit ne se défait pas. */
.danger-card { border-left: 3px solid var(--err); }
.danger-card h2 { color: var(--err); }

.ack { display: grid; grid-template-columns: 20px 1fr; gap: var(--rf-2); align-items: start;
       font-size: var(--t-2); color: var(--text-2); line-height: 1.5; cursor: pointer; }
.ack input { width: 17px; height: 17px; accent-color: var(--err); margin-top: 1px; }

/* Le motif d'un statut qui arrête la facture — refus, litige, rejet, échec de
   dépôt. Il ne vivait qu'en infobulle, donc invisible au doigt comme au lecteur
   d'écran, alors que c'est la seule chose qui dise quoi faire. Le dossier fonde
   tout le cycle de vie sur « une vision partagée du traitement de la facture
   pour l'ensemble des acteurs intéressés » (§3.6.4) : un motif qu'on ne lit pas
   ramène au téléphone, que la réforme cherche justement à supprimer.

   Discret mais lisible : la pastille alerte, le motif explique. */
.motif-statut {
    margin-top: var(--rf-1);
    font-size: var(--t-1);
    line-height: 1.35;
    color: var(--text-3);
    max-width: 26ch;
}

/* ⚠️ LA CONSIGNE SE DÉTACHE DU MOTIF TECHNIQUE.

   Le motif de la plateforme arrive en anglais et décrit une cause ; la consigne
   est en français et nomme un geste. Les rendre du même gris reviendrait à
   noyer la seule des deux phrases sur laquelle le lecteur peut agir.

   Elle prend donc la couleur du texte ordinaire, et un filet à gauche — le
   même que les citations de l'écran d'aide. */
/* L'avis de joignabilité, au-dessus de la liste des clients. Il n'existe que
   lorsqu'il a quelque chose à dire : une ligne rassurante à chaque chargement
   deviendrait un décor qu'on ne lit plus. */
.avis-reseau {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--rf-2);
    margin: 0 0 var(--rf-3);
    padding: var(--rf-2) var(--rf-3);
    border-left: 2px solid var(--err);
    background: var(--err-soft);
    border-radius: var(--rf-2);
    font-size: var(--t-1);
    color: var(--text-1);
}

.motif-statut__consigne {
    margin: 0 0 var(--rf-2);
    padding-left: var(--rf-2);
    border-left: 2px solid var(--accent);
    color: var(--text-1);
}

/* Le détail d'un statut, sous sa pastille. Le motif n'a aucune borne — ni chez
   la plateforme, ni à l'annexe 2 qui nomme les champs sans leur donner de
   longueur — et une colonne ne peut pas l'accueillir. La pastille alerte, le
   clic explique. */
.detail-statut > summary {
    list-style: none;
    cursor: pointer;
}

.detail-statut > summary::-webkit-details-marker { display: none; }

/* Le chevron dit qu'il y a quelque chose à ouvrir : sans lui, la pastille
   ressemble à toutes les autres et personne ne clique. */
.detail-statut > summary::after {
    content: '▾';
    margin-left: var(--rf-1);
    font-size: var(--t-1);
    color: var(--text-3);
    display: inline-block;
    transition: transform .15s;
}

.detail-statut[open] > summary::after { transform: rotate(180deg); }

.detail-statut[open] .motif-statut {
    margin-top: var(--rf-1);
    padding: var(--rf-1) var(--rf-2);
    background: var(--surface-2, rgba(127, 127, 127, .08));
    border-radius: var(--radius-sm);
    max-width: 42ch;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* L'intitulé d'un groupe d'onglets. Il nomme ce que les entrées ont en commun —
   « Factures » au-dessus d'« Émises » et « Reçues » — pour que le mot ne soit
   pas répété deux fois dans une colonne qui n'en a pas la largeur.

   Ce n'est pas une destination : il ne réagit pas au clic, et il est masqué aux
   lecteurs d'écran, qui suivent déjà la liste des boutons. */
/* L'intitulé d'un groupe d'onglets. Il nomme ce que les entrées ont en commun —
   « Factures » au-dessus d'« Émises » et « Reçues » — et il les replie.

   C'est un frère des boutons, pas leur parent : un <details> qui les enveloppait
   cassait la barre du téléphone, où « display: contents » ne promeut pas ses
   enfants au rang d'éléments flexibles. Le repli se fait donc par une classe
   posée sur les boutons du groupe. */
.nav-groupe {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    border: 0;
    background: none;
    /* Serré : six intitulés dans une colonne qui portait déjà dix-neuf entrées
       ont coûté cent quatre-vingts pixels, et cinq destinations sont passées
       sous le pli d'un écran de neuf cents. Le dégagement au-dessus sépare les
       groupes, celui du dessous n'a rien à séparer. */
    padding: var(--rf-2) var(--rf-3) 1px;
    font-size: var(--t-1);
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--text-3);
    text-align: left;
    cursor: pointer;
}

/* Le premier groupe suit un onglet, pas un bord. */
.nav-groupe:first-of-type { padding-top: var(--rf-1); }

/* Le chevron dit que le groupe se replie. Sans lui, l'intitulé passe pour un
   simple titre et personne n'essaie. */
.nav-groupe::after {
    content: '▾';
    font-size: var(--t-1);
    transition: transform .15s;
}

.nav-groupe[aria-expanded="false"]::after { transform: rotate(-90deg); }

/* Replié, l'intitulé reste seul : il doit garder l'air d'un titre et non d'une
   ligne orpheline. */
.nav-groupe[aria-expanded="false"] { padding-bottom: var(--rf-2); }

/* Un onglet dont le groupe est replié. La règle vit ici, avec les autres, pour
   qu'on la trouve en cherchant « replie » — et elle est neutralisée sur
   téléphone, où les groupes n'existent pas. */
nav button.replie { display: none; }

/* Le motif se recopie : c'est ce qu'on colle dans un courriel au fournisseur.
   La ligne autour interdit la sélection pour que le glissement ouvre la facture
   sans surligner la moitié du tableau — ce texte-ci fait exception. */
.detail-statut .motif-statut {
    user-select: text;
    cursor: text;
}

/* Le geste de réparation, sous la cause qu'il répare. Il n'a pas l'allure d'une
   pastille : celle-ci constate, lui agit — les confondre ferait déclencher la
   relance au clic qui voulait seulement lire. */
.lien-relance {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    font-weight: 600;
    color: var(--accent-profond);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* ----------------------------------------------------------------- agenda */

/* Le mois d'une pension, une ligne par séjour.

   Une ligne par séjour plutôt qu'un empilement calculé : le mois le plus chargé
   du premier dossier repris en porte une trentaine, et une ligne par séjour se
   lit sans avoir à deviner quelle barre appartient à qui.

   Les cases sont ÉTROITES et de largeur fixe : trente et une colonnes doivent
   tenir, et un mois qui déborde se fait défiler plutôt que d'écraser les noms. */
.agenda {
    border-collapse: collapse;
    table-layout: fixed;
    min-width: 720px;
    width: 100%;
}

.agenda th {
    width: 3.2%;
    padding: var(--rf-1) 0;
    font-size: var(--t-1);
    font-weight: 500;
    color: var(--text-2);
    text-align: center;
    border-bottom: 1px solid var(--line);
}

.agenda th.agenda-we { color: var(--text-3, var(--text-2)); opacity: .6; }

.agenda td {
    height: 26px;
    padding: 0;
    border-right: 1px solid var(--line);
    overflow: hidden;
}

/* La barre du séjour. Le jour de DÉPART est plus pâle : l'animal repart ce
   jour-là, il n'y dort pas — et c'est ce qui fait qu'un séjour du 1er au 8
   compte sept nuits et non huit. */
.agenda td.sejour-plein { background: var(--accent); }
.agenda td.sejour-fin { background: var(--accent); opacity: .35; }

/* Le jour même se marque sur son EN-TÊTE et non sur la case.

   Une barre s'étend maintenant sur tous ses jours d'un seul tenant : elle ne
   peut plus en surligner un. L'en-tête, lui, garde une colonne par jour. */
.agenda th.agenda-today {
    color: var(--warn);
    font-weight: 700;
    opacity: 1;
    box-shadow: inset 0 -2px 0 var(--warn);
}

/* Le nom du maître, DANS sa barre et coupé par elle.

   Il débordait sur les cases suivantes, hors flux : « mathilde DUBOIS DA COSTA »
   traversait le mois au-dessus de trois jours de garde, et l'on ne pouvait plus
   lire où le séjour finissait. Depuis que les barres se partagent une voie, il
   serait en plus passé par-dessus le séjour suivant.

   Une barre trop courte pour son nom n'en montre donc rien plutôt qu'un
   fragment trompeur — l'infobulle le porte, et le survol est le geste qu'on
   fait déjà sur un agenda. */
.sejour-nom {
    display: block;
    padding: 0 var(--rf-1);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: var(--t-1);
    font-weight: 500;
    line-height: 26px;
    /* Le jeton, et non « blanc ». En thème sombre la barre est un vert CLAIR :
       du blanc dessus tombe à 2 de contraste et le nom disparaît. C'est
       exactement ce que « sur-accent » sert à éviter — il bascule avec le
       thème, la barre aussi.

       L'ombre portée est partie avec : elle rattrapait le blanc sur un fond
       trop clair. Le contraste tenu (6,1 en clair, 8,0 en sombre), elle ne
       faisait plus que salir la lettre. */
    color: var(--sur-accent);
    pointer-events: none;
}

/* L'initiale du jour, au-dessus de son quantième. « 14 » seul ne dit pas si
   c'est un samedi, et un agenda de pension se lit d'abord par les fins de
   semaine. */
.agenda-jsem {
    display: block;
    font-size: var(--t-1);
    line-height: 1;
    opacity: .55;
    text-transform: uppercase;
}

/* ------------------------------------------------------- ce qui attend */

/* La bande d'actions, au-dessus des chiffres.

   Des tuiles LARGES et peu nombreuses plutôt qu'une grille : il y en a zéro à
   quatre, jamais douze, et une grille à colonnes fixes laisserait des trous là
   où il n'y a rien à faire. */
.todo-bande {
    display: flex;
    flex-wrap: wrap;
    gap: var(--rf-3);
    margin-bottom: var(--rf-5);
}

.todo-tuile {
    flex: 1 1 190px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding: var(--rf-3) var(--rf-4);
    border: 1px solid var(--line);
    border-left-width: 4px;
    border-radius: var(--radius);
    background: var(--surface, #fff);
    cursor: pointer;
    text-align: left;
    transition: border-color .15s, transform .15s;
}

.todo-tuile:hover { transform: translateY(-1px); }

/* La couleur porte l'URGENCE, et le bord gauche la porte seul : teinter tout le
   fond ferait crier les quatre tuiles ensemble, et l'œil ne saurait plus par
   laquelle commencer. */
.todo-tuile.ton-err { border-left-color: var(--err); }
.todo-tuile.ton-warn { border-left-color: var(--warn); }
.todo-tuile.ton-accent { border-left-color: var(--accent); }
.todo-tuile.ton-neutral { border-left-color: var(--line); }

.todo-compte {
    font-size: var(--t-5);
    font-weight: 600;
    line-height: 1.1;
}

.todo-libelle { font-size: var(--t-2); color: var(--text-2); }

.todo-montant {
    margin-top: var(--rf-1);
    font-size: var(--t-2);
    font-weight: 500;
}

/* Rien à faire se dit en une phrase, pas en quatre zéros. */
/* Le constat à gauche, le geste à droite — et l'un sous l'autre quand la
   largeur ne les tient plus, plutôt qu'un bouton écrasé contre le bord. */
.todo-vide {
    margin-bottom: var(--rf-5);
    padding: var(--rf-3) var(--rf-4);
    color: var(--text-2);
    font-size: var(--t-2);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--rf-2);
}

/* --------------------------------------------------- listes cherchables */

/* L'en-tête d'une liste : la recherche, et ce qu'elle a retenu.

   Le compte à droite dit toujours DEUX choses quand un filtre est posé — « 12
   sur 782 » — parce qu'un nombre seul laisse croire qu'on voit tout. */
.liste-tete {
    display: flex;
    align-items: center;
    gap: var(--rf-3);
    margin-bottom: var(--rf-3);
}

.liste-tete input[type="search"] {
    flex: 1 1 260px;
    max-width: 420px;
    margin: 0;
}

.liste-tete .sub { white-space: nowrap; margin: 0; }

/* La pagination : deux flèches et la position. Pas de numéros de page — sur une
   liste qu'on CHERCHE, personne ne vise la page sept. */
.pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--rf-3);
    margin-top: var(--rf-4);
    font-size: var(--t-2);
    color: var(--text-2);
}

.pagination button {
    min-width: 36px;
    padding: var(--rf-1) var(--rf-2);
    font-size: var(--t-3);
    line-height: 1.2;
}

.pagination button[disabled] { opacity: .35; cursor: default; }

/* ------------------------------------------------- confirmation d'émission */

/* Ce qui va être consommé, montré AVANT le clic.

   Le numéro en grand, parce que c'est lui qui ne recule pas : la séquence ne
   se rembobine pas, et un trou dans la suite se justifie devant
   l'administration. Le reste sert à reconnaître la pièce qu'on s'apprête à
   émettre — un « êtes-vous sûr » sans contenu s'apprend par cœur et se clique
   sans lire. */
.issue-resume {
    padding: var(--rf-4) var(--rf-4);
    margin-bottom: var(--rf-4);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--accent-soft, var(--line));
    text-align: center;
}

.issue-numero {
    font-size: var(--t-5);
    font-weight: 600;
    letter-spacing: .02em;
    font-variant-numeric: tabular-nums;
}

.issue-resume .sub { margin: var(--rf-1) 0 0; }

/* Les réserves franchies, et l'aveu qui les franchit.

   Le bloc emprunte le ton de l'avertissement plutôt que celui de l'erreur : ce
   qu'il porte n'empêche pas l'émission, il la nomme. La case garde sa cible de
   quarante-quatre pixels — c'est la dernière chose qu'on touche avant de
   consommer un numéro. */
.issue-reserves {
    margin-top: var(--rf-3);
    padding: var(--rf-3) var(--rf-3);
    border: 1px solid var(--warn, #b26b00);
    border-radius: var(--radius);
    background: var(--warn-soft, transparent);
}

.issue-reserves__tete {
    margin: 0 0 var(--rf-2);
    font-weight: 600;
    color: var(--warn, #b26b00);
}

.issue-reserves__liste {
    margin: 0;
    padding-left: var(--rf-4);
    font-size: var(--t-2);
    line-height: 1.5;
}

.issue-reserves__liste li + li { margin-top: var(--rf-1); }

.issue-reserves__aveu {
    display: flex;
    align-items: flex-start;
    gap: var(--rf-2);
    margin-top: var(--rf-3);
    min-height: 44px;
    font-weight: 500;
    cursor: pointer;
}

.issue-reserves__aveu input { margin-top: 2px; }

.issue-recap { width: 100%; }

.issue-recap th {
    width: 40%;
    padding: var(--rf-2) 0;
    text-align: left;
    font-weight: 400;
    color: var(--text-2);
    font-size: var(--t-2);
}

.issue-recap td {
    padding: var(--rf-2) 0;
    text-align: right;
    font-weight: 500;
}

/* ------------------------------------------- chiffre d'affaires par mois */

/* Des barres qui portent leur valeur.

   C'était une courbe d'aire. Sur une entreprise à dix mille euros l'an — la
   cible de ce produit — elle ne disait rien que le total au-dessus ne disait
   mieux : on y lisait une pente, jamais un montant. Une barre par mois, avec
   son chiffre écrit dessus, répond à la question qu'on se pose : « combien en
   juillet ? »
   Pas de graduation verticale : les montants sont écrits. Une échelle en plus
   des valeurs fait lire deux fois la même chose. */
.chart-barres {
    width: 100%;
    height: auto;
    display: block;
}

.bar-mois {
    fill: var(--accent);
    transition: opacity .15s;
}

/* Un mois négatif — un avoir sans facture en face — se distingue par la
   couleur, et non par la seule position sous la ligne de zéro : sur un écran
   étroit, cette ligne se confond avec le bas du cadre. */
.bar-negatif { fill: var(--err); }

.bar-zero {
    stroke: var(--line);
    stroke-width: 1;
}

/* La ligne de lecture sous le graphique : au repos, la part encaissée de la
   période ; au survol ou au clavier, les deux montants du mois. À
   l'horizontale — tournés dans les barres, ils se lisaient mal (propriétaire,
   18 octobre 2026). */
.rf-chart__lecture {
    margin: var(--rf-2) 0 0;
    min-height: 1.5em;
    font-size: var(--t-2);
    color: var(--text-2);
    font-variant-numeric: tabular-nums;
}

.rf-chart__lecture--mois { color: var(--text); font-weight: 500; }

.bar-mois-nom {
    font-size: 11px;
    fill: var(--text-2);
}

/* La zone cliquable couvre toute la colonne, pas la seule barre : viser trois
   pixels de haut sur un mois creux est un geste qu'on rate. */
.bar-zone {
    fill: transparent;
    cursor: pointer;
}

.bar-groupe:hover .bar-zone { fill: var(--line); opacity: .35; }
.bar-groupe:hover .bar-mois { opacity: .85; }
.bar-groupe:focus-visible .bar-zone { fill: var(--line); opacity: .5; }

/* -------------------------------------------------------------- résultat */

/* Facturé − Dépensé = Résultat, posé comme une opération.

   La forme n'est pas décorative : elle dit d'où sort le chiffre. Un « résultat :
   20 € » seul se prend pour un solde de compte, et c'est précisément la
   confusion qui coule une entreprise rentable — une facture émise compte dans le
   résultat avant d'être payée. */
.card.resultat { padding: var(--rf-4) var(--rf-5); margin-bottom: var(--rf-5); }

.resultat-libelle {
    font-size: var(--t-2);
    color: var(--text-2);
}

.resultat-valeur {
    font-size: var(--t-4);
    font-weight: 500;
    font-variant-numeric: tabular-nums;
}

/* Le résultat plus gros que ses deux termes : c'est lui qu'on vient lire. */
.resultat-valeur.fort { font-size: var(--t-5); font-weight: 600; }
.resultat-valeur.negatif { color: var(--err); }

.resultat-note { margin: var(--rf-3) 0 0; }

/* La trésorerie, séparée du résultat par un trait — parce que ce sont deux
   nombres différents, et que les coller les ferait lire comme un total. */
.resultat-tresorerie {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--rf-3);
    margin-top: var(--rf-3);
    padding-top: var(--rf-3);
    border-top: 1px solid var(--line);
}

/* La ligne survolée se signale, comme son secteur d'anneau. */
.report-row { transition: background .15s var(--ease); }
.report-row:hover { background: var(--line); border-radius: var(--radius); }

/* --------------------------------------------------------- fiche client */

/* Ce qu'un client a acheté, ce qu'il doit, et depuis quand.

   AUCUN CHAMP VIDE. D'autres produits alignent vingt lignes — Height, Weigth,
   Profession — dont dix-huit sont blanches, et on apprend à ne plus les lire.
   Ici, ce qui n'est pas renseigné ne s'affiche pas ; ce qui manque VRAIMENT — le
   SIREN d'un professionnel — se dit comme un manque. */
.fiche-tete {
    display: flex;
    align-items: center;
    gap: var(--rf-2);
    margin-bottom: var(--rf-1);
}

.fiche-tete h2 { margin: 0; }

/* De quoi le JOINDRE, en tête et cliquable : c'est ce qu'on vient chercher neuf
   fois sur dix en ouvrant une fiche. */
.fiche-contact {
    display: flex;
    flex-wrap: wrap;
    gap: var(--rf-1) var(--rf-4);
    margin: 0 0 var(--rf-4);
    font-size: var(--t-2);
    color: var(--text-2);
}

.fiche-manque {
    margin: 0 0 var(--rf-3);
    padding: var(--rf-2) var(--rf-3);
    border-radius: var(--radius);
    background: var(--warn-soft);
    color: var(--warn);
    font-size: var(--t-2);
}

.fiche-chiffres {
    display: flex;
    flex-wrap: wrap;
    gap: var(--rf-3) var(--rf-6);
    padding: var(--rf-3) 0;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}

.fiche-chiffres > div { display: flex; flex-direction: column; gap: 2px; }

.fiche-detail { width: 100%; margin-top: var(--rf-3); }
.fiche-detail th {
    width: 32%;
    padding: var(--rf-1) 0;
    text-align: left;
    font-weight: 400;
    color: var(--text-2);
    font-size: var(--t-2);
}
.fiche-detail td { padding: var(--rf-1) 0; font-size: var(--t-2); }

.fiche-titre { margin: var(--rf-5) 0 var(--rf-2); font-size: var(--t-3); }

.fiche-pieces { width: 100%; }
.fiche-pieces td { padding: var(--rf-2) 0; font-size: var(--t-2); }

/* ================================================================ mobile */

/* Tout ce qui a été ajouté ce soir, repris en étroit.

   Le défaut commun de ces blocs est le même : ils rangent des chiffres CÔTE À
   CÔTE. Sur un écran de 375 px, « Facturé − Dépensé = Résultat » sur une ligne
   pousse le résultat hors de l'écran — ou, pire, le laisse déborder sans que
   rien ne le signale. */
@media (max-width: 720px) {

    /* CE QUI ATTEND. Les tuiles s'empilent plutôt que de se serrer : à trois par
       ligne sur un téléphone, chacune fait cent pixels et le libellé se coupe. */
    .todo-tuile { flex: 1 1 100%; }

    /* CE QUI RESTE. Le montant fort perd un cran : il se lit sous son
       libellé, dans une carte de toute la largeur. */
    .resultat-valeur.fort { font-size: var(--t-4); }

    /* La trésorerie garde sa séparation mais passe en colonne comme le reste. */
    .resultat-tresorerie { flex-direction: column; align-items: flex-start; gap: 2px; }

    /* FICHE CLIENT. Les quatre chiffres passent à deux par ligne — à quatre ils
       font 80 px chacun, et « 10 089 € » n'y tient pas. */
    .fiche-chiffres { gap: var(--rf-3) var(--rf-5); }
    .fiche-chiffres > div { flex: 1 1 42%; }

    /* Le contact s'empile : trois éléments côte à côte poussent l'adresse
       hors du cadre, et c'est justement ce qu'on vient lire. */
    .fiche-contact { flex-direction: column; gap: var(--rf-1); }

    /* Un libellé de colonne à 32 % laisse 68 % à une adresse : trop peu. */
    .fiche-detail th { width: 44%; }


    /* LISTES CHERCHABLES. La recherche prend la largeur, le compte passe
       dessous : côte à côte, le champ tombe à 120 px. */
    .liste-tete { flex-wrap: wrap; }
    .liste-tete input[type="search"] { flex: 1 1 100%; max-width: none; }

    /* AGENDA. Trente et une colonnes ne tiennent pas : il défile, et le
       défilement horizontal est ici la bonne réponse — comprimer les jours
       rendrait les barres illisibles. */
    .agenda { min-width: 620px; }

    /* Le nom du maître sur une barre d'un jour déborde forcément. On le laisse
       déborder plutôt que de le couper : l'infobulle porte le détail, et un nom
       tronqué à trois lettres ne sert personne. */
    .sejour-nom { font-size: var(--t-1); }

    /* GRAPHE EN BARRES, SUR TÉLÉPHONE. Les montants ne sont plus écrits dans
       les barres depuis le 18 octobre 2026 : la ligne de lecture sous le
       graphique les porte, et elle se replie comme un texte. */
    /* Unités de dessin, comme sa déclaration d'origine : voir l'exception
       nommée au-dessus de « .chart-axis ». */
    .bar-mois-nom { font-size: 13px; }

    /* CONFIRMATION D'ÉMISSION. Le numéro reste lisible, le récapitulatif
       s'aère. */
    .issue-numero { font-size: var(--t-4); }
    .issue-recap th { width: 46%; }
}

/* Sur les écrans les plus étroits, le graphe mensuel devient illisible à douze
   barres : il défile plutôt que de se comprimer. */
@media (max-width: 480px) {
    .chart-barres { min-width: 480px; }
    .chart-wrap { overflow-x: auto; overflow-y: hidden; }
}

/* ------------------------------------------------ champs en deux colonnes */

/* Employée par les boîtes de saisie du module de pension. Elle était écrite
   dans le balisage et n'existait nulle part : les champs s'empilaient en un
   seul rang, ce qui n'était pas cassé mais faisait une boîte deux fois trop
   haute — un formulaire de séjour tenait mal dans une fenêtre de portable.

   `minmax(0, 1fr)` et non `1fr` : sans le minimum à zéro, un champ au contenu
   long refuse de se réduire et déborde de la grille. C'est le débordement le
   plus fréquent d'une grille CSS, et le plus discret. */
.grid2 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--rf-3) var(--rf-4);
}

.grid2 > div { min-width: 0; }

/* Une colonne en étroit : deux champs de 150 px côte à côte sur un téléphone
   coupent leurs libellés. */
@media (max-width: 560px) {
    .grid2 { grid-template-columns: 1fr; }

    /* Sauf deux dates qui se suivent, comme dans les grilles ordinaires
       (voir « DEUX DATES SE LISENT CÔTE À CÔTE »). */
    .grid2.dates-en-paire {
        grid-template-columns: repeat(auto-fit, minmax(max(140px, calc((100% - var(--rf-3)) / 2)), 1fr));
    }
    .grid2.dates-en-paire > :not(:has(input[type="date"])) { grid-column: 1 / -1; }
}

/* ============================ tableaux qui ne sont pas des listes ============ */

/* La règle mobile transforme TOUS les tableaux en cartes — une ligne par carte,
   le libellé de colonne devant chaque valeur. Elle est juste pour une liste de
   factures ou de clients : sept colonnes ne se lisent pas sur un téléphone.

   Elle est FAUSSE pour trois tableaux ajoutés depuis, qui ne sont pas des
   listes :

     — l'AGENDA est une grille de trente et un jours. Éclaté en cartes, il perd
       ce qui le fait : la continuité horizontale d'un séjour ;
     — le RÉCAPITULATIF d'émission et le DÉTAIL d'une fiche sont des paires
       clé-valeur à deux colonnes. Ils sont déjà lisibles en étroit, et la règle
       leur ajoutait le libellé de colonne DEVANT leur propre libellé ;
     — les PIÈCES d'une fiche tiennent en quatre colonnes courtes.

   Chacun reprend donc son comportement de tableau, et déborde en défilant
   plutôt qu'en s'éclatant. La règle est posée APRÈS pour la surcharger, et bornée
   au même point de bascule. */
@media (max-width: 860px) {
    .agenda, .agenda thead, .agenda tbody, .agenda tr { display: revert; }
    .agenda th { display: table-cell; }
    .agenda td { display: table-cell; }

    /* Le THEAD est dans la liste, et il y manquait. Ce bloc affirme deux lignes
       plus haut que « ces tableaux portent déjà le leur » — c'était faux sur un
       téléphone : le « thead { display: none } » de la convention des cartes
       s'appliquait toujours, si bien que le tableau gardait ses quatre colonnes
       et perdait leurs intitulés. Quatre colonnes de nombres sans en-tête,
       mesuré à 390 px. */
    .issue-recap, .issue-recap thead, .issue-recap tbody, .issue-recap tr,
    .fiche-detail, .fiche-detail tbody, .fiche-detail tr,
    .fiche-pieces, .fiche-pieces thead, .fiche-pieces tbody, .fiche-pieces tr { display: revert; }

    .issue-recap th, .issue-recap td,
    .fiche-detail th, .fiche-detail td,
    .fiche-pieces th, .fiche-pieces td { display: table-cell; }

    /* ET L'ALIGNEMENT REVIENT AVEC ELLES. L'exclusion n'annulait que le
       « display » : les cellules gardaient le « text-align: right » de la
       convention des cartes — où la valeur est poussée à droite, face à son
       libellé — pendant que les en-têtes restaient à gauche. Résultat mesuré à
       390 px : l'intitulé « PIÈCE » à 45 px et le numéro de pièce à 165, sous
       une colonne qui ne le contenait plus. Le « :not(.num) » laisse les
       montants à droite, où ils se comparent. */
    .agenda td:not(.num),
    .issue-recap td:not(.num),
    .fiche-detail td:not(.num),
    .fiche-pieces td:not(.num) { text-align: left; }

    /* Ces lignes ne sont pas des cartes : ni cadre, ni fond, ni marge. */
    .agenda tbody tr,
    .issue-recap tbody tr,
    .fiche-detail tbody tr,
    .fiche-pieces tbody tr {
        border: 0; border-radius: 0; background: none; padding: 0; margin: 0;
    }

    /* Et surtout pas de libellé de colonne ajouté devant la valeur : ces
       tableaux portent déjà le leur. */
    .agenda td::before,
    .issue-recap td::before,
    .fiche-detail td::before,
    .fiche-pieces td::before { content: none; }

    /* Le débordement se fait par le DÉFILEMENT, dans un conteneur propre. */
    .fiche-pieces, .agenda { width: 100%; }
}

/* Le conteneur qui fait défiler les pièces d'une fiche plutôt que de les
   écraser : un numéro tronqué ne désigne plus rien. */
.fiche-defile { overflow-x: auto; overflow-y: hidden; }

/* ------------------------------------------------- enrôlement au second facteur */

.totp-etapes {
  margin: var(--rf-4) 0 0;
  padding-left: var(--rf-5);
  font-size: var(--t-2);
  line-height: 1.65;
  color: var(--text-2);
}

.totp-etapes li + li { margin-top: var(--rf-1); }

/* Le QR est rendu par le serveur, en SVG. Fond blanc imposé : la page suit le
   thème du lecteur, et un code QR sombre sur fond sombre inverse le contraste
   que tous les lecteurs supposent. Certains y arrivent, la plupart non. */
.totp-qr {
  margin: var(--rf-4) auto 0;
  width: 208px;
  padding: var(--rf-2);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.totp-qr svg { display: block; width: 100%; height: auto; }

.totp-manuel { margin-top: var(--rf-3); font-size: var(--t-2); color: var(--text-3); }
.totp-manuel summary { cursor: pointer; color: var(--accent-profond); }
.totp-manuel p { margin: var(--rf-2) 0; }

/* La clé en groupes de quatre, dans une police à chasse fixe : elle se recopie
   à la main sur un clavier de téléphone, et une suite de trente-deux caractères
   sans respiration s'y perd à la douzième. */
.totp-secret {
  display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--t-3);
  letter-spacing: .12em;
  word-break: break-all;
  padding: var(--rf-2) var(--rf-3);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  user-select: all;
}

.totp-manuel button { margin-top: var(--rf-2); }

.secours-liste {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--rf-2);
  margin-top: var(--rf-4);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--t-3);
  letter-spacing: .08em;
}

.secours-liste span {
  padding: var(--rf-2) var(--rf-2);
  text-align: center;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  user-select: all;
}

.secours-actions { display: flex; gap: var(--rf-2); margin-top: var(--rf-3); }
.secours-actions button { flex: 1; }

.secours-vu {
  display: flex;
  align-items: center;
  gap: var(--rf-2);
  margin-top: var(--rf-4);
  font-size: var(--t-2);
  color: var(--text-2);
  cursor: pointer;
}

.secours-vu input { width: auto; margin: 0; }

/* À l'impression, seuls les codes. Une page de secours imprimée avec le
   bandeau de l'application et les boutons est une page qu'on ne range pas. */
/* L'impression des CODES DE SECOURS — et elle seule.

   Cette règle masquait tout ce qui n'est pas le portail, « #shell » compris. Elle
   a été écrite quand le seul document imprimable du produit était la liste des
   codes de secours, qui vit dans le portail.

   Depuis, l'écran « Modèle de document » imprime une page A4 qui vit DANS le
   shell — et « display: none » l'emporte sur le « visibility: visible » de la
   feuille de refonte : la page serait sortie blanche, sans le moindre message.
   Constaté avant d'imprimer, en lisant les deux règles côte à côte.

   Le « :has » borne la règle au moment où le portail est ouvert, c'est-à-dire au
   seul moment où l'on peut imprimer des codes de secours. Une fois connecté, le
   portail porte « hidden » et cette règle ne s'applique plus. */
@media print {
  body:has(#gate:not([hidden])) > *:not(#gate) { display: none !important; }
  #gate > *:not(#secoursPane) { display: none !important; }
  .secours-actions, .secours-vu, #secoursBtn { display: none !important; }
  .secours-liste { grid-template-columns: repeat(2, 1fr); }
}

.totp-etat {
  margin-top: var(--rf-3);
  font-size: var(--t-2);
  color: var(--text-2);
}

.totp-etat b { color: var(--text); }

/* L'heure du dernier enregistrement automatique. Discrète par construction :
   elle rassure d'un coup d'œil et ne doit jamais attirer l'attention — sauf
   quand elle annonce un échec, qui est le seul moment où l'automatisme doit se
   faire entendre. */
.draft-saved {
  font-size: var(--t-2);
  color: var(--text-3);
  transition: color .2s;
}

.draft-saved[data-etat="err"] { color: var(--err); font-weight: 600; }

/* La proposition d'harmonisation d'une adresse.

   Discrète : elle n'annonce pas une erreur, elle offre une écriture. Un bandeau
   d'alerte à côté d'une adresse parfaitement valable ferait douter d'une saisie
   qui n'a rien de faux — « BESANCON » est juste, « Besançon » est mieux. */
.harmonie {
  display: flex;
  align-items: baseline;
  gap: var(--rf-2);
  flex-wrap: wrap;
  margin-top: var(--rf-1);
  font-size: var(--t-2);
  color: var(--text-3);
}

.harmonie b { color: var(--text-2); font-weight: 600; }

.harmonie button {
  width: auto;
  padding: 2px var(--rf-2);
  font-size: var(--t-2);
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: transparent;
  color: var(--accent-profond);
  cursor: pointer;
}

.harmonie button:hover { border-color: var(--accent); }

/* Une case à cocher et son libellé, sur la même ligne.

   La règle générale du produit étire tout champ de saisie sur la largeur de son
   conteneur — juste pour un texte, désastreux pour une case : elle passait
   au-dessus de son libellé, et un carnet de santé de cinq lignes devenait dix
   lignes illisibles. */
label.case {
  display: flex;
  align-items: center;
  gap: var(--rf-2);
  font-size: var(--t-2);
  color: var(--text-2);
  cursor: pointer;
}

label.case + label.case { margin-top: var(--rf-1); }
label.case input { width: auto; margin: 0; flex: none; }

/* La journée d'une pension. Deux blocs courts plutôt qu'un tableau : à quatre
   lignes, un tableau coûte ses en-têtes pour ranger ce qu'on lit d'un coup. */
.journee-bloc { margin-top: var(--rf-3); }

.journee-titre {
    display: flex;
    align-items: baseline;
    gap: var(--rf-2);
    font-size: var(--t-2);
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-3);
}

.journee-titre span {
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
    color: var(--text);
}

.journee-liste {
    margin: var(--rf-1) 0 0;
    padding: 0;
    list-style: none;
}

.journee-liste li {
    padding: 3px 0;
    border-bottom: 1px solid var(--line);
    font-size: var(--t-2);
}

.journee-liste li:last-child { border-bottom: 0; }

/* Une carte que le rôle ne peut pas enregistrer. Elle reste LISIBLE — on ne
   cache pas à quelqu'un les réglages sous lesquels il travaille — mais elle ne
   se laisse pas remplir pour rien. */
.carte-close { background: var(--surface-2, transparent); }

.carte-close .carte-close-note {
    margin: var(--rf-2) 0 0;
    padding: var(--rf-2) var(--rf-2);
    border-left: 2px solid var(--line);
    background: rgba(23, 32, 42, .03);
}

.carte-close input:disabled,
.carte-close select:disabled,
.carte-close textarea:disabled { opacity: .7; }

/* ------------------------------------------------- gestes commerciaux (régie) */

/* Ce qui est accordé, sous le montant, dans la liste des abonnés. Discret à
   dessein : la colonne porte d'abord un montant, et cette ligne explique
   pourquoi il n'est pas celui de la grille. */
.faveur-bref {
    margin-top: 3px;
    font-size: var(--t-1);
    font-weight: 500;
    color: var(--accent-profond);
    white-space: normal;
}

/* DES ONGLETS, ET UN SEUL MOTIF POUR TOUT LE PRODUIT.

   Ils portent aria-selected plutôt qu'une classe — c'est ce qu'annonce un
   lecteur d'écran, et le style suit l'attribut.

   « .faveur-onglets » était le premier usage, dans la fenêtre du geste
   commercial. Quand l'écran de comptabilité en a eu besoin à son tour, le
   réflexe aurait été d'en écrire un second ; deux motifs qui font la même chose
   divergent toujours, et c'est celui qu'on relit le moins qui prend du retard.
   Le sélecteur porte donc les deux noms. */
.onglets,
.faveur-onglets {
    display: flex;
    gap: var(--rf-1);
    margin: var(--rf-4) 0 var(--rf-4);
    padding: 3px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

/* ⚠️ UNE BARRE D'ONGLETS DE PLEINE PAGE ÉPOUSE SES LIBELLÉS.

   Les deux composants partageaient cette règle parce qu'ils se ressemblent. Ils
   ne vivent pas au même endroit : « faveur-onglets » tient dans un dialogue
   étroit, où deux onglets qui remplissent la largeur sont justes ; « onglets »
   s'ouvre sur une page entière.

   Mesuré le 13 octobre 2026 sur la Comptabilité : la barre prenait les 952 px
   disponibles, et l'onglet « TVA » faisait 312 px de cible pour 25 px de texte.
   Un bouton d'onglet est une cible, pas un paragraphe.

   La feuille le disait déjà pour les Réglages — « les onglets reprennent le
   composant segmented : même geste, même apparence, rien de neuf à apprendre ».
   Cet écran était le seul à ne pas suivre. */
.onglets {
    display: inline-flex;
    flex-wrap: wrap;
}

.faveur-onglets button {
    flex: 1;
    padding: var(--rf-2) var(--rf-3);
    border: 0;
    /* Le rayon intérieur valait « --radius moins 4 », soit exactement le pas
       du dessous. L'arithmétique disait la bonne intention — suivre son
       contenant — mais elle se défait dès que le contenant bouge. */
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-2);
    font: inherit;
    font-size: var(--t-2);
    font-weight: 500;
    cursor: pointer;
    transition: background-color .16s var(--ease), color .16s var(--ease);
}

/* Les onglets de pleine page héritent de tout sauf de l'étirement : leur
   largeur suit le libellé, comme partout ailleurs dans le produit. */
.onglets button {
    flex: 0 0 auto;
    padding: var(--rf-2) var(--rf-4);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-2);
    font: inherit;
    font-size: var(--t-2);
    font-weight: 500;
    cursor: pointer;
    transition: background-color .16s var(--ease), color .16s var(--ease);
}

.onglets button:hover,
.faveur-onglets button:hover { color: var(--text); }

.onglets button[aria-selected="true"],
.faveur-onglets button[aria-selected="true"] {
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 1px 2px rgba(23, 32, 42, .06);
}

/* Un champ chiffré et son unité. Le suffixe est DANS le cadre : « 20 » suivi
   d'un « % » posé à côté se lit comme deux champs, et l'on cherche le second. */
.champ-suffixe {
    display: flex;
    align-items: center;
    gap: var(--rf-1);
}

.champ-suffixe input { flex: 1; min-width: 0; }

.champ-suffixe span {
    flex: 0 0 auto;
    font-size: var(--t-2);
    font-weight: 500;
    color: var(--text-2);
}

/* Ce qui est déjà accordé, rappelé dans la fenêtre. Sans cette liste, offrir
   une seconde fois le même module était le geste le plus naturel du monde. */
.faveur-liste { margin-top: var(--rf-4); }

.faveur-liste h3 {
    margin-bottom: var(--rf-2);
    font-size: var(--t-2);
    font-weight: 600;
    letter-spacing: .02em;
    text-transform: uppercase;
    color: var(--text-3);
}

.faveur-ligne {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--rf-3);
    padding: var(--rf-2) var(--rf-3);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.faveur-ligne + .faveur-ligne { margin-top: var(--rf-1); }

/* Un cadeau dont le terme est passé reste AFFICHÉ, en retrait. Il explique une
   ligne qui vient de réapparaître sur la facture du client, et l'effacer
   rendrait ce retour inexplicable. */
.faveur-ligne.echu { opacity: .62; }

.faveur-ligne b { display: block; font-size: var(--t-2); }

.faveur-ligne span {
    display: block;
    font-size: var(--t-2);
    color: var(--text-2);
}

.faveur-ligne em {
    display: block;
    margin-top: 3px;
    font-size: var(--t-2);
    font-style: italic;
    color: var(--text-3);
}

.faveur-ligne button { flex: 0 0 auto; padding: var(--rf-1) var(--rf-3); font-size: var(--t-2); }

@media (max-width: 620px) {
    .faveur-ligne { flex-direction: column; align-items: stretch; }
}

/* ------------------------------------------------- le dossier d'un abonné ---
   Neuf sections dans un dialogue : identité, représentant légal, raccordement
   au réseau, abonnement, échéance ligne à ligne, nos factures, prélèvements,
   équipe, piste d'audit. La tête ne défile pas, le corps oui — même gabarit
   que la fiche client, dont il reprend .rf-fiche__* et .rf-identite__*.

   Ces règles vivent ici et non dans refonte.css, comme celles du rapprochement
   et des gestes commerciaux : la feuille de refonte forme une paire versionnée
   avec le banc de vues, et un écran de régie n'y figure pas. */
.rg-dossier__sections {
    display: flex;
    flex-direction: column;
    gap: var(--rf-5);
    padding-right: 2px;
}

.rg-bloc h3 {
    margin-bottom: var(--rf-2);
    font-size: var(--t-2);
    font-weight: 600;
    letter-spacing: .02em;
    text-transform: uppercase;
    color: var(--text-3);
}

/* Trois colonnes, et un plancher de 190 px : sous cette largeur, « Identifiant
   du parcours d'enrôlement » se casse sur trois lignes au-dessus d'une valeur
   d'un mot. Le nombre de colonnes suit donc la place, il n'est pas décrété. */
.rg-paires {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: var(--rf-3) var(--rf-4);
}

/* Un lien du dossier mène HORS du produit — chez la plateforme agréée, sur ses
   pages. Il se coupe n'importe où : ce sont des URL signées, sans espace, et
   sans cette permission c'est le dialogue entier qui s'élargit. */
.rg-paires a { overflow-wrap: anywhere; }

/* CE QUE LE DOSSIER NE PORTE PAS. Ce bloc est la seule chose de cet écran qui
   ne vient pas de la base, et il compte autant que le reste : un dossier qui
   affiche neuf sections sur treize se lit comme complet. C'est ainsi qu'on jure
   devant un contrôleur détenir une pièce qu'on n'a jamais eue. */
.rg-lacunes {
    margin: 0;
    padding: var(--rf-3) var(--rf-3) var(--rf-3) var(--rf-8);
    border-left: 2px solid var(--warn);
    border-radius: 0 var(--radius) var(--radius) 0;
    background: var(--warn-soft);
    font-size: var(--t-2);
    color: var(--text-2);
}

.rg-lacunes li + li { margin-top: var(--rf-1); }

/* Le cadeau sur la carte d'un module, et la remise sur celle de l'abonnement.
   Même forme pour les deux : ce sont les deux faces du même geste, et le client
   les lira au même endroit — juste sous le prix qu'elles expliquent. */
.module-cadeau,
.note.remise {
    margin: var(--rf-2) 0 0;
    padding: var(--rf-2) var(--rf-2);
    border-left: 2px solid var(--accent);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    background: var(--accent-soft);
    font-size: var(--t-2);
    color: var(--text-2);
}

.module-cadeau b,
.note.remise b {
    display: block;
    color: var(--accent-profond);
    font-weight: 600;
}

/* Terminé, le geste passe au gris : il n'annonce plus rien, il explique un
   montant qui vient de remonter. */
.module-cadeau.echu,
.note.remise.echu {
    border-left-color: var(--line-strong);
    background: var(--surface-2);
}

.module-cadeau.echu b,
.note.remise.echu b { color: var(--text-2); }

/* ------------------------------------------------- campagne d'inventaire */

/* Le comptage en cours, en tête de l'écran des stocks. Il porte une bordure
   d'accent parce qu'il est un ÉTAT du dossier et non une carte de plus : tant
   qu'il est ouvert, l'état théorique affiché plus bas est précisément ce qu'on
   est en train de vérifier. */
.inventaire { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }

.inventaire-actes { display: flex; gap: var(--rf-2); flex-wrap: wrap; }

/* Les trois chiffres du comptage : l'avancement, le nombre d'écarts, et ce
   qu'ils coûtent. Le troisième est le seul qui décide — d'où sa couleur, qui
   suit son SENS : un manquant coûte, un surplus rapporte. */
.inventaire-etat {
    display: flex;
    flex-wrap: wrap;
    gap: var(--rf-3) var(--rf-8);
    margin: var(--rf-4) 0 var(--rf-1);
    padding: var(--rf-3) var(--rf-4);
    border-radius: var(--radius);
    background: var(--surface-2);
}

.inventaire-etat b {
    display: block;
    font-size: var(--t-4);
    font-weight: 600;
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
}

.inventaire-etat span { font-size: var(--t-1); color: var(--text-2); }
.inventaire-etat .ton-err b { color: var(--err); }
.inventaire-etat .ton-ok b { color: var(--ok); }

/* Ce qui reste à compter, dit AVANT la clôture et non après. C'est la seule
   phrase de l'écran qui prévienne d'une conséquence, et elle rassure autant
   qu'elle avertit : ce qu'on ne compte pas n'est pas touché. */
.inventaire-reste {
    margin: var(--rf-3) 0 0;
    padding: var(--rf-2) var(--rf-3);
    border-left: 2px solid var(--warn);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    background: var(--warn-soft);
    font-size: var(--t-2);
    color: var(--text);
}

.inventaire-table { margin-top: var(--rf-4); }

/* L'unité à côté de l'article : on compte des sacs, pas des nombres. */
.inventaire-table .unite {
    margin-left: var(--rf-1);
    font-size: var(--t-1);
    color: var(--text-3);
}

/* Le champ de saisie, étroit et aligné à droite comme la colonne. Compter, c'est
   taper un nombre puis passer au suivant : rien d'autre ne doit s'y trouver. */
.inventaire-table input.compte {
    width: 96px;
    margin-left: auto;
    padding: var(--rf-1) var(--rf-2);
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Une ligne comptée est ACQUISE. Elle s'efface un peu, pour que le regard
   retombe sur ce qui reste — sur quarante articles, savoir où l'on en est vaut
   mieux que relire ce qu'on a déjà fait. */
.inventaire-table tr.comptee td:first-child { color: var(--text-2); }

.inventaire-table .a-compter { color: var(--text-3); }

@media (max-width: 780px) {
    .inventaire-table input.compte { width: 120px; }
    .inventaire-etat { gap: var(--rf-3) var(--rf-5); }
}

/* Ce qu'il faut recommander, en tête de l'écran des stocks.

   Un seuil qui ne produirait qu'une pastille au milieu de quarante lignes ne
   servirait à rien : on ne parcourt pas son stock tous les matins. La bande le
   dit à l'arrivée, avec les articles nommés — c'est ce qu'on emporte chez le
   fournisseur. */
.reappro { border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }

.reappro ul {
    margin: var(--rf-3) 0 0;
    padding: 0;
    list-style: none;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: var(--rf-2) var(--rf-4);
}

.reappro li {
    padding: var(--rf-2) var(--rf-2);
    border-radius: var(--radius);
    background: var(--warn-soft);
}

.reappro li b { display: block; font-size: var(--t-2); font-weight: 600; }
.reappro li span { font-size: var(--t-2); color: var(--text-2); }

/* Le seuil, saisi sur la ligne de l'article. Étroit : c'est un nombre à deux
   chiffres, pas une quantité de stock. */
input.seuil {
    width: 74px;
    margin-left: auto;
    padding: var(--rf-1) var(--rf-2);
    text-align: right;
    font-variant-numeric: tabular-nums;
    /* Sans flèches : à cette largeur elles mangeaient la moitié du champ, et
       un seuil se tape, il ne se règle pas d'un cran. */
    appearance: textfield;
    -moz-appearance: textfield;
}
input.seuil::-webkit-inner-spin-button,
input.seuil::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
/* Un seuil vide dit « Aucun » en gris : le tiret seul se lisait comme une
   valeur manquante par erreur, pas comme un réglage absent. */
input.seuil::placeholder { color: var(--text-3); opacity: 1; font-style: normal; }

/* Un lien qui se comporte comme un bouton. Le téléchargement passe par un vrai
   <a download> et non par du script : c'est le seul moyen que le navigateur
   nomme le fichier comme le serveur le demande. */
.bouton-lien {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

/* ------------------------------------------------- trente prochains jours */

/* Une échéance par ligne : le jour, qui doit, combien. La conception réserve
   66 px au jour et 108 px au montant ; le nom prend ce qui reste et se coupe
   plutôt que de pousser le montant hors de la carte.

   Une facture BLOQUÉE reste dans la liste — la cacher ferait attendre un argent
   qui ne vient pas — mais s'affiche en retrait et sort du solde : elle n'est pas
   en retard, elle est empêchée. */
.rf-sched { display: flex; flex-direction: column; margin-top: var(--rf-3); }
.rf-sched__row {
  display: grid;
  grid-template-columns: 66px minmax(0, 1fr) 108px;
  gap: var(--rf-2);
  align-items: baseline;
  padding: var(--rf-2) 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--t-2);
}
.rf-sched__day { color: var(--text-3); font-size: var(--t-2); }
.rf-sched__who { display: flex; align-items: baseline; gap: var(--rf-2); min-width: 0; }
.rf-sched__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Le motif ne se coupe JAMAIS : c'est lui qui explique pourquoi la ligne ne
   compte pas dans le solde. C'est le nom qui cède la place. */
.rf-sched__why { flex: none; color: var(--err); font-size: var(--t-2); }
.rf-sched__sum { text-align: right; font-variant-numeric: tabular-nums; color: var(--ok); }
.rf-sched__row[data-bloque] .rf-sched__who,
.rf-sched__row[data-bloque] .rf-sched__sum { color: var(--text-3); }
.rf-sched__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--rf-3);
  padding-top: var(--rf-3);
  font-size: var(--t-2);
  color: var(--text-2);
}
.rf-sched__foot b { font-size: var(--t-3); color: var(--ok); }

/* Sous 600 px, le montant passe sous le nom plutôt que d'écraser les deux. */
@media (max-width: 600px) {
  .rf-sched__row { grid-template-columns: 58px minmax(0, 1fr); }
  .rf-sched__sum { grid-column: 2; text-align: left; }
}

/* ---------------------------------------------------------- « Ce qui attend » */

/* Le conteneur que la feuille de la conception ne pose pas : elle définit
   « .rf-queue__item » sans son parent, parce que la maquette le portait en ligne.
   Le dernier filet s'efface — une liste qui se termine par un trait paraît
   coupée. */
.rf-queue { display: flex; flex-direction: column; margin-top: var(--rf-1); }
.rf-queue .rf-queue__item:last-child { border-bottom: 0; padding-bottom: 0; }

/* Sous 560 px, le bouton passe SOUS le texte plutôt que de lui prendre sa
   largeur : « Statuer » et un titre de deux lignes ne tiennent pas côte à côte
   sur un téléphone, et c'est le titre qui se serait écrasé. */
@media (max-width: 560px) {
  .rf-queue__item { grid-template-columns: 4px minmax(0, 1fr); }
  .rf-queue__action { grid-column: 2; margin-right: 0; justify-self: start; margin-top: var(--rf-2); }
}

/* La seconde barre du graphe : ce qui est RENTRÉ, à côté de ce qui est parti.
   Même teinte que la pilule de trop-perçu — c'est le même argent, vu de
   l'autre côté — et aucune couleur inventée. */
.bar-encaisse { fill: var(--owed); }

/* --------------------------------------------- récapitulatif de la saisie */

/* Le panneau qui suit la saisie : totaux, contrôle de conformité, bouton.

   Les trois vivaient en bas, sous les lignes. Sur une facture de douze postes,
   le net à payer n'était visible qu'après avoir dépassé toute la grille, et le
   contrôle n'apparaissait qu'APRÈS un clic, plus bas encore : deux informations
   qui décident d'émettre, hors de vue au moment d'émettre.

   « position: sticky » suffit — aucune media query. Sous 800 px la scission
   « flex-wrap » repose le panneau sous le formulaire, et « sticky » y devient
   sans effet puisque la carte tient dans l'écran. */
.rf-recap { max-width: 380px; }
.rf-recap > .card { position: sticky; top: 16px; }

.rf-recap .totals { border-top: 0; margin-top: 0; padding-top: 0; }

/* Le NET À PAYER est la seule chose qu'on vient lire ici. La feuille de la
   refonte le pose à 25 px ; « .row.grand » du produit le rendait au corps
   courant, comme les six lignes de ventilation au-dessus. */
.rf-recap .totals .row.grand span:last-child {
  font-size: var(--t-5);
  font-weight: 640;
  letter-spacing: -.02em;
}

/* L'ACOMPTE DÉDUIT prend la teinte du trop-perçu : c'est le même argent, vu de
   l'autre côté — déjà chez nous, retranché de ce qui reste à devoir. Aucune
   couleur inventée, et une classe à lui plutôt qu'un sélecteur qui l'attraperait
   par sa position : il vit au milieu des lignes de ventilation de TVA, qui
   portent la même classe et ne disent pas du tout la même chose. */
.totals .row.acompte,
.totals .row.acompte span { color: var(--owed); }

.recap-actions {
  display: flex;
  flex-direction: column;
  gap: var(--rf-2);
  align-items: stretch;
  margin-top: var(--rf-4);
  padding-top: var(--rf-4);
  border-top: 1px solid var(--line);
}
.recap-actions .draft-saved,
.recap-actions #busy { text-align: center; }

/* Le contrôle : un point de couleur, un titre qui compte, puis la liste. */
.recap-controle {
  display: flex;
  flex-direction: column;
  gap: var(--rf-2);
  margin-top: var(--rf-4);
  padding-top: var(--rf-4);
  border-top: 1px solid var(--line);
  font-size: var(--t-2);
}
.recap-controle__tete { display: flex; align-items: center; gap: var(--rf-2); }
.recap-controle__point {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;                     /* une boîte en ligne ignore width et height */
  display: inline-block;
  background: var(--text-3);
}
.recap-controle__point[data-ton="ok"] { background: var(--ok); }
.recap-controle__point[data-ton="warn"] { background: var(--warn); }
.recap-controle__point[data-ton="err"] { background: var(--err); }

/* Le formulaire garde sa largeur utile : sous 900 px la scission le repose en
   pleine largeur, et le panneau passe dessous — sans media query, c'est
   « flex-wrap » qui décide. */
.rf-split__main { display: flex; flex-direction: column; gap: var(--rf-5); }

/* ------------------------------------------------------- balance âgée */

.balance-legende {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: var(--rf-3) var(--rf-5);
  margin-top: var(--rf-3);
}
.balance-legende > div { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--rf-1) var(--rf-2); }
.balance-legende__cle {
  width: 9px; height: 9px; border-radius: var(--radius-sm);
  display: inline-block;          /* une boîte en ligne ignore width et height */
  flex: none;
}
.balance-legende__cle[data-tranche="0"] { background: var(--owed); }
.balance-legende__cle[data-tranche="1"] { background: var(--cat-4); }
.balance-legende__cle[data-tranche="2"] { background: var(--rf-late); }
.balance-legende__cle[data-tranche="3"] { background: var(--err); }
.balance-legende__cle[data-tranche="4"] { background: var(--rf-late-deep); }
.balance-legende__libelle { font-size: var(--t-2); color: var(--text-2); }
.balance-legende b { font-size: var(--t-2); font-variant-numeric: tabular-nums; }
.balance-legende__compte { font-size: var(--t-1); color: var(--text-3); width: 100%; }

/* ------------------------------------------------------ carte Encours */

/* L'encours, la balance âgée des Relances et ce qu'un dépôt bloque, sur une
   carte (18 octobre 2026). La légende est celle de l'écran Relances, classe
   comprise : mêmes tranches, mêmes couleurs. */
.encours { display: flex; flex-direction: column; }
.encours__tete { display: flex; align-items: center; justify-content: space-between; gap: var(--rf-2); }
.encours > .kpi-value { margin-bottom: var(--rf-2); }
.encours .rf-aging { margin-top: var(--rf-2); }
/* Cinq entrées — l'à échoir et les quatre tranches — sur une rangée quand la
   carte a toute la largeur ; la colonne de l'écran Relances en tient quatre. */
.encours .balance-legende { grid-template-columns: repeat(auto-fit, minmax(144px, 1fr)); }
.encours__lecture {
    margin: var(--rf-3) 0 0;
    font-size: var(--t-2);
    line-height: 1.5;
    color: var(--text-2);
}
.encours__lecture:first-of-type { margin-top: 0; }
.encours__bloque { color: var(--warn); }
.encours__lecture .link { font-size: inherit; }

/* ------------------------------------------------ cadence des relances */

/* En INVERSE, et c'est le seul bloc du produit qui l'est. Ce réglage-ci fait
   écrire à des tiers, sous le nom de notre client, à SON client : il ne doit pas
   se lire comme une préférence d'affichage qu'on coche en passant. */
.cadence {
  background: var(--text);
  color: var(--surface);
  border-color: var(--text);
}
.cadence h2 { color: var(--surface); }
.cadence p { color: rgba(255, 255, 255, .72); font-size: var(--t-2); margin: var(--rf-1) 0 0; }
.cadence__tete { display: flex; flex-wrap: wrap; gap: var(--rf-3); justify-content: space-between; align-items: flex-start; }
.cadence__bascule { display: flex; align-items: center; gap: var(--rf-2); cursor: pointer; font-size: var(--t-2); }
.cadence__bascule input { width: auto; }

.cadence__echelons { display: flex; flex-wrap: wrap; gap: var(--rf-2); margin-top: var(--rf-4); }
.cadence__echelon {
  flex: 1 1 110px;
  padding: var(--rf-3) var(--rf-3);
  border-radius: var(--radius);
  border: 1px solid rgba(255, 255, 255, .18);
  display: flex; flex-direction: column; gap: 2px;
}
/* En pause, les échelons restent LISIBLES mais éteints : les masquer ferait
   croire que la cadence n'existe pas, alors qu'elle attend d'être lancée. */
.cadence__echelon b { font-size: var(--t-4); letter-spacing: -.02em; }
.cadence__echelon span { font-size: var(--t-1); color: rgba(255, 255, 255, .6); }
.cadence__echelon[data-active] { border-color: rgba(255, 255, 255, .42); }
.cadence__note { color: rgba(255, 255, 255, .62); font-size: var(--t-2); line-height: 1.5; margin: var(--rf-4) 0 0; }

/* Why the other overdue invoices wait: same counts as the aging report. */
.relances-motifs { margin-top: var(--rf-3); font-size: var(--t-2); color: var(--text-2); }
.relances-motifs p { margin: 0 0 var(--rf-1); }
.relances-motifs ul { margin: 0; padding-left: 1.2em; display: grid; gap: 2px; }
.relances-motifs b { color: var(--text); font-variant-numeric: tabular-nums; }

.relances-groupe {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--rf-3);
  margin-top: var(--rf-4); padding-top: var(--rf-4); border-top: 1px solid var(--line);
}

/* ------------------------------------------------- factures reçues, en cartes */

/* Une facture reçue n'est pas une donnée qu'on parcourt : c'est une pièce à
   laquelle il faut RÉPONDRE. Le tableau donnait le même poids au cadre de
   facturation qu'au geste attendu, sur neuf colonnes. */
.inbox-cartes { display: flex; flex-direction: column; gap: var(--rf-3); }

.inbox-carte {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rf-3) var(--rf-5);
  align-items: flex-start;
  padding: var(--rf-4) var(--rf-5);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}

/* Le liseré porte l'ancienneté de l'attente — non un délai réglementaire, que
   le produit ne connaît pas. Une facture déjà statuée n'en porte aucun : elle
   n'attend plus rien. */
.inbox-carte[data-urgence] { border-left-width: 3px; }
.inbox-carte[data-urgence="accent"] { border-left-color: var(--accent); }
.inbox-carte[data-urgence="warn"]   { border-left-color: var(--warn); }
.inbox-carte[data-urgence="err"]    { border-left-color: var(--err); }

.inbox-carte__corps { flex: 1 1 300px; min-width: 0; display: flex; flex-direction: column; gap: var(--rf-1); }
.inbox-carte__tete { display: flex; align-items: center; flex-wrap: wrap; gap: var(--rf-2); font-size: var(--t-3); }
.inbox-carte__sous {
  display: flex; flex-wrap: wrap; gap: var(--rf-1) var(--rf-3);
  font-size: var(--t-2); color: var(--text-3);
}
.inbox-carte__attente { color: var(--warn); }

/* Ce que le produit n'a pas su lire, dit en clair. C'est la contrepartie de
   « rien n'est refusé à l'entrée », et c'est ce qui décide d'un refus motivé. */
.inbox-carte__soucis { display: flex; flex-direction: column; gap: var(--rf-1); margin-top: 2px; }
.inbox-carte__soucis span {
  font-size: var(--t-2); line-height: 1.45; color: var(--warn);
  padding-left: var(--rf-3); position: relative;
}
.inbox-carte__soucis span::before { content: "!"; position: absolute; left: 0; font-weight: 600; }

.inbox-carte__argent {
  flex: 0 1 auto; display: flex; flex-direction: column; align-items: flex-end; gap: var(--rf-1);
  min-width: 130px;
}
.inbox-carte__argent > b { font-size: var(--t-3); font-variant-numeric: tabular-nums; }

.inbox-carte__actions { flex: 0 0 auto; display: flex; flex-direction: column; gap: var(--rf-2); align-items: stretch; }
.inbox-carte__liens { display: flex; gap: var(--rf-2); justify-content: center; flex-wrap: wrap; }
.inbox-carte__liens a,
.inbox-carte__liens button {
  background: transparent; border: 0; padding: 0; cursor: pointer;
  font-size: var(--t-1); color: var(--text-3); text-decoration: underline;
}
.inbox-carte__liens a:hover, .inbox-carte__liens button:hover { color: var(--text); }

.inbox-carte__pieces { flex: 1 1 100%; border-top: 1px solid var(--line); padding-top: var(--rf-2); }

/* Sur un téléphone, l'argent et les commandes passent sous le corps plutôt que
   de lui prendre sa largeur. */
@media (max-width: 640px) {
  .inbox-carte__argent { align-items: flex-start; flex: 1 1 100%; }
  .inbox-carte__actions { flex: 1 1 100%; }
}

/* --------------------------------------------------- modèle de document */

/* « wrap-reverse » : la page à gauche sur grand écran, les réglages AU-DESSUS
   d'elle quand ça se replie. Sur un téléphone, on règle puis on regarde ; faire
   défiler onze cents pixels de page A4 avant d'atteindre le premier réglage
   serait absurde. */
/* « align-items: FLEX-END », et ce n'est pas une faute de frappe.

   « wrap-reverse » inverse l'axe TRANSVERSE en même temps que l'ordre des
   lignes : le début de cet axe devient le bas. « flex-start » alignait donc les
   réglages sur le bas de la page A4, cent vingt-cinq pixels plus bas que son
   en-tête — mesuré. « flex-end » est ce qui les aligne visuellement en haut. */
.doc-split { display: flex; flex-wrap: wrap-reverse; gap: var(--rf-5); align-items: flex-end; }
.doc-split__page { flex: 1 1 620px; min-width: 0; }
/* « min-width: 0 » sur les DEUX colonnes, et pas seulement sur la page.

   Un « input[type=file] » porte une largeur minimale intrinsèque — le bouton
   « Choisir un fichier » plus « Aucun fichier choisi » — que le navigateur ne
   réduit pas. Elle remontait jusqu'à la colonne, dont la taille minimale
   dépassait alors sa base de 300 px : les deux colonnes ne tenaient plus côte à
   côte, et la page A4 passait sous les réglages sur un écran de 1 280 px, où
   elle a pourtant toute la place. Mesuré, pas supposé. */
.doc-split__reglages { flex: 1 1 300px; max-width: 360px; min-width: 0; }
.doc-split__reglages input[type="file"] { max-width: 100%; }
.doc-split__reglages > .card { position: sticky; top: 16px; }

/* Les quatre couleurs du système. Un sélecteur libre laisserait choisir un jaune
   sur blanc : la facture devient illisible, donc non payée. */
.doc-accents { display: flex; flex-wrap: wrap; gap: var(--rf-2); margin-top: var(--rf-1); }
.doc-accent {
  display: flex; align-items: center; gap: var(--rf-2);
  padding: var(--rf-2) var(--rf-3); font-size: var(--t-2);
  background: var(--surface); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius-pill); cursor: pointer;
}
.doc-accent > span {
  width: 12px; height: 12px; border-radius: 50%;
  display: inline-block;          /* une boîte en ligne ignore width et height */
  flex: none; background: var(--doc-teinte);
}
.doc-accent[aria-pressed="true"] { border-color: var(--text); font-weight: 560; }

.doc-logo { display: flex; align-items: center; gap: var(--rf-3); flex-wrap: wrap; margin-top: var(--rf-1); }
.doc-logo img { height: 38px; width: auto; }

/* La page ne se sert pas de la police de l'écran : elle imite celle du PDF, qui
   est une sans-serif neutre. Un aperçu dans une autre fonte que le document
   produit induirait en erreur sur la place que prend le texte. */
.rf-paper { --rf-paper-font: -apple-system, "Segoe UI", Roboto, sans-serif; }

/* La carte fantôme du glisser-déposer, et la carte tirée.

   Elles vivent ici et non dans la feuille de la refonte : le glisser-déposer du
   produit est écrit avec les événements de POINTEUR — un seul code pour la
   souris, le doigt et le stylet — là où la refonte proposait le glisser-déposer
   natif, qui n'existe pas au doigt. */
.rf-widget.ghost {
  border: 1px dashed var(--accent);
  border-radius: var(--radius-lg);
  background: var(--accent-soft);
}
.rf-widget.dragging { z-index: 60; pointer-events: none; box-shadow: 0 18px 44px rgba(23, 32, 42, .22); }
.rf-board[data-edition] .rf-widget__poignee:first-child { cursor: grab; }

/* « Rétablir » vit sur la barre NOIRE : un bouton fantôme y sortait en encre
   sombre sur fond sombre, c'est-à-dire invisible. Le seul geste destructeur de
   ce mode ne doit pas être celui qu'on ne voit pas. */
.rf-board-bar .ghost {
  background: transparent;
  border-color: rgba(255, 255, 255, .38);
  color: #fff;
}
.rf-board-bar .ghost:hover { border-color: #fff; background: rgba(255, 255, 255, .1); }

/* Les boutons de la poignée doivent rester atteignables au clavier : ils sont
   la seule façon de ranger ce tableau sans pointeur. */
.rf-widget__actions button:disabled { opacity: .35; cursor: default; }
.rf-widget__actions button:focus-visible { outline: 2px solid var(--anneau, var(--marque-texte)); outline-offset: 1px; }

/* ------------------------------------------------------ rythme vertical */

/* UN SEUL ÉCART entre les blocs d'un écran, et c'est celui des cartes.

   Relevé sur les seize vues : le page-head laisse 26 px, deux cartes voisines
   18 px, une note 16, un panneau d'onglets 14 — et l'accueil ZÉRO entre « Ce qui
   attend » et la grille des indicateurs. Zéro sur l'écran le plus regardé du
   produit : les cartes se touchaient, et la file paraissait faire partie de la
   première rangée.

   La cause est mécanique : « .card + .card » ne s'applique qu'entre deux cartes
   voisines, et sur l'accueil les blocs de premier niveau sont des conteneurs,
   pas des cartes. Le rythme se pose donc sur le CONTENEUR de la vue plutôt que
   sur ses enfants — un seul endroit, et rien à retenir en ajoutant un bloc. */
:root { --rythme: 18px; }

/* Des MARGES ciblées, et non une gouttière de conteneur.

   Un « display:flex » avec « gap » sur la vue paraissait plus propre : il
   s'ajoutait à la marge basse du page-head, et l'écart sous le titre passait de
   26 à 44 pixels. Une gouttière ne remplace pas une marge, elle s'y ajoute —
   mesuré, après l'avoir cru le contraire.

   Le page-head garde donc ses 26 px, comme sur les quinze autres vues, et les
   blocs de premier niveau prennent le même écart que deux cartes voisines. */
#seuilFranchise:not([hidden]),
#boardBar:not([hidden]),
#homeTodo:not(:empty) { margin-bottom: var(--rythme); }

#boardAdd:not([hidden]) { margin-top: var(--rythme); }

/* Les trois derniers écarts du relevé, ramenés au même nombre : seize sous une
   barre de filtres, quatorze sous des onglets, zéro entre deux blocs de l'écran
   des abonnés. Trois valeurs qu'on ne remarque pas seule, et qui font qu'aucun
   écran du produit ne respire tout à fait comme son voisin. */
.filters { margin-bottom: var(--rythme); }

/* Les Réglages semblaient tomber à 14 px sous leurs onglets. Ce n'en était pas
   un : « .settings-pane » entre par une animation qui part de
   « translateY(-4px) », et la mesure l'avait attrapée en vol. Dix-huit moins
   quatre. Rien à corriger — noté ici pour que le prochain relevé ne reparte pas
   à la chasse. */

/* Sur l'écran des abonnés, « .card + .card » ne s'applique pas : un conteneur
   vit entre les deux cartes et rompt l'adjacence. L'écart valait donc zéro, et
   la carte rouge des gestes irréversibles se collait à celle du dessus. */
#adminContent:not(:empty) { margin-top: var(--rythme); }
#adminContent + .card { margin-top: var(--rythme); }

/* LES CARTES D'UNE MÊME RANGÉE ONT LA MÊME HAUTEUR.

   La zone de courbe réservée règle la première cause d'inégalité ; il en reste
   une seconde, légitime — « Encours client » porte une note que les autres n'ont
   pas, et prend vingt-six pixels de plus. Une rangée d'indicateurs dont les bas
   ne s'alignent pas se lit comme une grille cassée.

   « stretch » agit RANGÉE PAR RANGÉE : le graphe, seul sur la sienne, n'est pas
   étiré ; deux cartes qui la partagent s'alignent. Le sélecteur passe par
   « #homeContent » parce que la feuille de la conception pose « start » et
   qu'elle est chargée après celle-ci. */
#homeContent .rf-board { align-items: stretch; }

/* LE TIERS RESTE UN TIERS ENTRE 901 ET 1 279 PX — 18 octobre 2026.

   refonte.css y passe le quart ET le tiers à la moitié, pour un montant coupé
   mesuré sur un QUART (183 px à 1 100 px). La disposition d'origine pose
   désormais trois tiers — chiffre d'affaires, encaissement, TVA — et le tiers
   passé à la moitié laissait le troisième seul sur sa rangée, avec une
   demi-rangée de fond nu. Le tiers garde donc sa largeur, et son montant perd
   un cran de corps : il tient alors sur une ligne jusqu'à 901 px. Les deux
   tiers suivent, pour la même raison. Le quart, lui, passe toujours à la
   moitié. L'épreuve de pavage du banc mesure les deux à 1 100 px. */
@media (min-width: 901px) and (max-width: 1279px) {
    #homeContent .rf-widget[data-span="4"] { grid-column: span 4; }
    #homeContent .rf-widget[data-span="8"] { grid-column: span 8; }
    #homeContent .rf-widget[data-span="4"] .kpi-value { font-size: var(--t-4); }
}
#homeContent .rf-widget > :last-child { flex: 1; }

/* UNE CARTE COURTE NE CREUSE PLUS DE TROU EN SON MILIEU.

   L'étirement ci-dessus donne à chaque carte la hauteur de la plus grande de sa
   rangée. Sur une carte à un seul chiffre qui partage sa rangée avec une carte à
   liste, cent pixels de vide s'ouvraient — non pas en bas, où ils passeraient
   pour du rembourrage, mais AU MILIEU : « .kpi-foot » portait un « margin-top:
   auto » qui collait le pied en bas de la carte, et le trou s'ouvrait juste
   au-dessus de lui. Mesuré sur « Délai moyen constaté » : 221 px de carte pour
   120 de contenu, dont cent en plein milieu.

   LE PRODUIT A DEMANDÉ DE CENTRER LE CONTENU, et je l'ai mesuré avant de le
   livrer, parce que centrer déplace aussi ce qui n'avait pas de trou :

     centré           libellés de la première rangée à 372, 393, 356 — trente-
                      sept pixels d'écart ; et le titre de « Délai moyen »
                      trente-huit pixels sous celui de sa voisine de rangée.
     vers le haut     356, 356, 356 ; valeurs à 382, 382, 382 ; et les deux
                      titres de la seconde rangée à quatre pixels l'un de
                      l'autre, c'est-à-dire alignés à l'œil.

   Le trou disparaît dans les DEUX cas — c'est le « margin-top: auto » qui le
   creusait, et il tombe dans les deux. Mais des chiffres d'une même rangée ne se
   comparent que s'ils sont à la même hauteur, et c'est la seule raison d'être
   d'une rangée d'indicateurs. Le vide va donc en bas, où il se lit comme du
   rembourrage, et non au milieu où il se lisait comme une panne.

   Pour centrer littéralement : « justify-content: center » à la place de la
   ligne ci-dessous. Une seule ligne, et l'écart de trente-sept pixels revient. */
#homeContent .kpi { justify-content: flex-start; }
#homeContent .kpi > .kpi-foot { margin-top: 0; }

/* La note reprend l'écart commun. Seize contre dix-huit ne se voit pas seul ;
   sur un écran qui empile sept cartes et une note, le dernier intervalle est le
   seul à ne pas tomber juste. */
.note { margin-top: var(--rythme); }

/* Dans une carte, la note colle à ce qu'elle commente : c'est un aparté, pas un
   bloc de même rang. */
.card > .note:last-child { margin-top: var(--rf-3); }

/* --------------------------------------------------------- sur le réseau */

/* Ce qui s'est passé DEHORS : une ligne par fait, la pastille porte le sort. */
.reseau-journal { display: flex; flex-direction: column; margin-top: var(--rf-2); }
.reseau-fait {
  display: grid;
  grid-template-columns: 9px minmax(0, 1fr) auto;
  gap: var(--rf-2);
  align-items: baseline;
  padding: var(--rf-2) 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--t-2);
}
.reseau-fait:last-child { border-bottom: 0; }
.reseau-fait__point {
  width: 7px; height: 7px; border-radius: 50%;
  display: inline-block;          /* une boîte en ligne ignore width et height */
  background: var(--accent);
  transform: translateY(-2px);
}
.reseau-fait__point[data-ton="ok"]   { background: var(--ok); }
.reseau-fait__point[data-ton="warn"] { background: var(--warn); }
.reseau-fait__point[data-ton="err"]  { background: var(--err); }
.reseau-fait__corps { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* DEUX LIGNES, PAS VINGT ET UNE. Le motif vient de la plateforme : sa longueur
   ne se négocie pas, et rien n'empêche le prochain d'être plus long encore.
   Sans bride, il fixait la hauteur de la carte — donc celle de toute la rangée,
   les trois voisines comprises. Le texte entier reste au survol, et le journal
   complet à un bouton. */
.reseau-fait__piece {
  font-size: var(--t-1); color: var(--text-3);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  /* « mono » ne coupe pas les mots : un numéro de facture non plus, mais un
     motif de la plateforme peut contenir une référence plus large que la
     colonne. Sans cela, elle déborde au lieu de passer à la ligne. */
  overflow-wrap: anywhere;
}
.reseau-fait__quand { font-size: var(--t-1); color: var(--text-3); white-space: nowrap; }

/* ═══════════════════ LE THÈME SOMBRE, PAR-DESSUS LA REFONTE ═══════════════

   La feuille de la refonte n'a AUCUN bloc « prefers-color-scheme » : tous ses
   jetons sont figés sur leur valeur claire. Trois d'entre eux sont de l'encre
   sombre posée à 6 %, ce qui donne un voile gris en clair et RIEN en sombre —
   les séparateurs du tableau des factures, la piste des jauges et le fond des
   pastilles disparaissaient purement et simplement.

   L'attribut « data-theme » porte la spécificité qu'un double « :root »
   apportait auparavant : app.css est chargée AVANT refonte.css, et à
   spécificité égale c'est la dernière qui gagne. « :root[data-theme] » passe
   devant sans toucher au fichier de la conception, qui est versionné comme une
   paire et remplacé à chaque livraison.

   LE THÈME NE SUIT PLUS LE SYSTÈME TOUT SEUL. Les six blocs de ce fichier
   étaient des « @media (prefers-color-scheme: dark) », et l'utilisateur n'avait
   aucun moyen de choisir. Ils sont désormais portés par un attribut, que le JS
   pose au chargement : « système » se résout alors en clair ou en sombre par
   « matchMedia », et un choix explicite l'emporte. Un seul bloc sombre par
   règle, donc, au lieu de deux qui auraient fini par diverger. */
:root[data-theme="dark"] {
    --rf-ink-soft: rgba(255, 255, 255, .07);
    --rf-ink-line: rgba(255, 255, 255, .09);
    /* Le retard garde sa famille : sans variante sombre, une tranche de la
       balance âgée virait pendant que sa voisine restait, et la légende
       n'était plus la même palette. */
    --rf-late: #f08a5f;
    --rf-late-deep: #c2544a;
}

/* L'étape NON FRANCHIE du cycle de vie, en sombre.
   Elle était de l'encre à 13 % : invisible, et le parcours perdait sa longueur —
   on ne voyait plus que les étapes atteintes, sans savoir combien il en reste. */
:root[data-theme="dark"] .rf-life__dot { background: var(--line-strong); }

/* ─────────────── LES BLOCS À FOND INVERSÉ ───────────────

   « background: var(--text) » suit le thème — il devient donc CLAIR en sombre.
   Un premier plan écrit en blanc figé y disparaît. C'est le défaut que la barre
   de personnalisation avait déjà corrigé pour le thème clair, et qui se
   retournait simplement de l'autre côté.

   « var(--bg) » est l'encre qui accompagne « var(--text) » : le produit s'en
   sert déjà ainsi ailleurs. */
/* Sélecteur DOUBLÉ, pour la même raison que le « :root:root » plus haut :
   refonte.css est chargée après celle-ci et pose « color: #fff » à spécificité
   égale. Sans ce doublement, la correction ne s'applique pas — mesuré : fond
   #edf1f5, encre #ffffff, la barre entière illisible en sombre. */
.rf-board-bar.rf-board-bar { color: var(--bg); }
.rf-board-bar .hint { color: color-mix(in srgb, var(--bg) 72%, transparent); }
.rf-board-bar .ghost {
    border-color: color-mix(in srgb, var(--bg) 38%, transparent);
    color: var(--bg);
}
.rf-board-bar .ghost:hover {
    border-color: var(--bg);
    background: color-mix(in srgb, var(--bg) 10%, transparent);
}

/* Doubled class: the shared card base in composants.css, loaded later, painted
   this block in --surface and left its --bg ink unreadable (18 October 2026). */
.card.cadence { background: var(--text); border-color: var(--text); color: var(--bg); color-scheme: dark; }
/* Native controls follow the block, not the page: its checkbox read as filled. */
:root[data-theme="dark"] .card.cadence { color-scheme: light; }
.cadence h2 { color: var(--bg); }
.cadence p { color: color-mix(in srgb, var(--bg) 78%, transparent); }
/* The generic label colour is a dark ink: on the inverse block it vanished. */
.cadence .cadence__bascule { color: var(--bg); margin: 0; font-weight: 600; }
.cadence__echelon { border-color: color-mix(in srgb, var(--bg) 24%, transparent); }
/* Paused: steps stay readable, a dashed border says they are not running. */
.cadence__echelon:not([data-active]) { border-style: dashed; }
.cadence__echelon span { color: color-mix(in srgb, var(--bg) 74%, transparent); }
.cadence__echelon[data-active] { border-color: color-mix(in srgb, var(--bg) 46%, transparent); }
.cadence__note { color: color-mix(in srgb, var(--bg) 74%, transparent); }

/* Blanc sur un accent qui s'éclaircit en sombre : le jeton prévu pour l'encre
   posée SUR l'accent existe, et le produit l'emploie déjà à trois endroits. */
.rf-widget__actions button[aria-pressed="true"] { color: var(--sur-accent); }
.w-remove { color: var(--bg); }

/* ─────────────── LA PAGE A4 RESTE BLANCHE, ET SON ENCRE AVEC ───────────────

   « .rf-paper » posait un fond blanc en dur et une encre au jeton : en sombre,
   du texte quasi blanc sur du papier blanc — la page entière était VIDE à l'œil.

   Une feuille de papier ne suit pas le thème de l'écran : elle imite ce qui
   sortira de l'imprimante. Elle redéclare donc ses propres jetons en valeurs
   claires, pour elle et pour tout ce qu'elle contient. C'est ce que l'autre
   aperçu du produit fait déjà, et il l'assume dans son commentaire. */
.rf-paper {
    --text: #17202a;
    --text-2: #46586b;
    --text-3: var(--text-2);
    --line: rgba(23, 32, 42, .14);
    --line-strong: rgba(23, 32, 42, .24);
    --surface: #fff;
    --surface-2: #f4f6f8;
    --bg: #fff;
    color: var(--text);
}

/* Le rythme, jusqu'au bout : trois blocs de premier niveau restaient à 20 px.
   L'état « rien ne vous attend » ne respirait pas comme l'état peuplé. */
.todo-vide { margin-bottom: var(--rythme); }
.card.resultat { margin-bottom: var(--rythme); }
.rappro { margin-top: var(--rythme); }

/* UN CONTENEUR NAÎT AVEC SON CONTENU.

   Trois cartes vivent dans la page en attendant d'être remplies par le script :
   l'archivage, les règles de conformité, et les séjours à facturer. Quand la
   réponse ne vient pas — ou ne porte rien —, elles restent : cinquante pixels de
   fond, de bordure et d'ombre, sans un mot. On les lit comme une panne.

   « :empty » ne se trompe pas : il ne vise que ce qui n'a AUCUN nœud, pas même
   un espace. Une carte remplie d'un état vide en toutes lettres reste visible,
   et c'est ce qu'on veut — le silence se dit, il ne se montre pas. */
.card:empty,
.rf-board-add:empty { display: none; }

/* Le NUMÉRO et le MONTANT ne se coupent pas.

   Un numéro de facture est un identifiant : « FAC-2026- » sur une ligne et
   « 000046 » sur la suivante ne se lit plus, ne se recopie plus, et ne se
   cherche plus des yeux dans une colonne. Un montant coupé après la virgule est
   pire encore. Les deux préfèrent élargir leur colonne — la grille défile
   déjà. */
.rf-cell .rf-num,
.rf-amount { white-space: nowrap; }

/* La colonne d'actions n'est pas épinglée : sous 1 136 px le tiroir « ⋯ »
   s'atteint en faisant défiler la grille, jamais en masquant la colonne
   voisine. Arbitrage et mesures dans design/refonte/JOURNAL.md, lot 5. */


/* ─────────────────────────── LE CHOIX D'APPARENCE, AU PIED DE LA COLONNE

   Trois segments dans 180 pixels de large : la police descend à 11 px et le
   rembourrage à 5, sinon « Système » passe à la ligne et le groupe fait deux
   étages. Les mots ne s'abrègent pas — « Sys. » économise huit pixels et coûte
   une hésitation à chaque lecture.

   « .segmented » porte déjà l'état pressé, la bordure et le fond : on ne
   redéfinit ici que la densité. */
.identity .theme-choix {
    display: flex;
    margin-top: var(--rf-2);
    width: 100%;
}

/* CE GROUPE DOIT SE DÉFENDRE DE « .identity button ».

   La colonne dessine ses boutons encadrés et pleine largeur — c'est ce qu'il
   faut pour « Se déconnecter », qui est seul de son espèce. Mes trois segments
   en héritaient : chacun prenait sa propre bordure, son rayon de 8 px et
   surtout 8 px de marge haute, si bien que le groupe faisait 40,5 px pour 30,5
   de contenu. Mesuré au navigateur, après une première explication FAUSSE de ma
   part — j'avais accusé le « inline-flex » de « .segmented », et passer le
   groupe en « flex » n'avait rien changé du tout.

   « .segmented » porte déjà le cadre, le fond et l'état pressé, au niveau du
   GROUPE. Les segments ne doivent donc rien porter eux-mêmes : on leur retire
   ce que la colonne leur avait mis, et on ne garde que la densité.

   Le lien de version, douze lignes plus bas, mène le même combat contre la même
   règle. Deux exceptions à une règle trop large sont le signe qu'elle devrait
   viser « #logoutBtn » plutôt que « button » — à reprendre le jour où une
   troisième apparaîtra. */
.identity .theme-choix button {
    flex: 1;
    margin-top: 0;
    width: auto;
    border: 0;
    border-radius: var(--radius-pill);
    padding: var(--rf-1) var(--rf-1);
    font-size: var(--t-1);
    letter-spacing: -.01em;
}

/* ============================================================ LES CODES D'UN
   ARTICLE — 12 septembre 2026

   Un champ et son bouton sur la même ligne, sans que le bouton ne tombe sous
   le champ au premier rétrécissement : c'est « flex: 1 » sur le champ et
   « flex: none » sur le bouton, l'inverse du réflexe. */
.champ-avec-bouton {
    display: flex;
    gap: var(--rf-1);
    align-items: stretch;
}

.champ-avec-bouton input {
    flex: 1;
    min-width: 0;
}

.champ-avec-bouton button {
    flex: none;
    white-space: nowrap;
    margin-top: 0;
}

/* L'ÉTIQUETTE SE MONTRE SUR FOND BLANC, TOUJOURS.

   Le SVG porte son propre fond blanc — un lecteur a besoin du contraste — et
   sur un écran sombre il apparaîtrait comme un rectangle clair posé de travers
   au milieu d'une fenêtre foncée. Le cadre le présente comme ce qu'il est :
   une étiquette, pas une image de l'interface. */
.etiquette {
    margin-top: var(--rf-3);
    padding: var(--rf-3);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: #ffffff;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--rf-3);
    flex-wrap: wrap;
}

.etiquette svg {
    height: 62px;
    width: auto;
    max-width: 100%;
}

.etiquette .sub,
.etiquette .hint {
    color: #4b5563;
}

/* La douchette du stock : un champ large, qui reprend le focus après chaque
   lecture. Il est en tête de l'écran parce qu'on scanne avant de chercher. */
.scan {
    display: flex;
    gap: var(--rf-2);
    align-items: center;
    flex-wrap: wrap;
}

.scan input {
    flex: 1;
    min-width: 180px;
    font-size: var(--t-3);
    letter-spacing: .04em;
}


/* ------------------------------------------------- appareils retenus */

.appareils { display: flex; flex-direction: column; margin-top: var(--rf-3); }

.appareil {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--rf-2) var(--rf-4);
    padding: var(--rf-3) 0;
    border-top: 1px solid var(--rf-ink-line, rgba(0, 0, 0, .08));
}

/* Le premier ne porte pas de trait : il en aurait un au-dessus de lui, sous le
   texte de la carte, et se lirait comme la fin d'un bloc plutôt que le début
   d'une liste. */
.appareil:first-child { border-top: 0; }

.appareil .sub { margin: 3px 0 0; }

@media (max-width: 900px) {
    /* Sur un téléphone, le bouton passe sous la description et prend la
       largeur : à côté d'un libellé qui se replie sur trois lignes, il se
       retrouvait centré dans le vide. */
    .appareil { align-items: flex-start; }
    .appareil > button { width: 100%; }
}

/* The modules offered on the sign-up form: one list, one row per module, the
   price aligned on the right, and the first invoice said underneath. */
.choix-modules { margin-top: var(--rf-4); }
.choix-modules h3 { margin: 0 0 var(--rf-1); font-size: var(--t-2); }
.choix-modules__liste {
  margin-top: var(--rf-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.choix-modules__liste label.case {
  padding: var(--rf-2) var(--rf-3);
  margin: 0;
  align-items: flex-start;
}
.choix-modules__liste label.case + label.case { border-top: 1px solid var(--line); margin-top: 0; }
.choix-modules__liste .choix-modules__nom { flex: 1; min-width: 0; color: var(--text); }
.choix-modules__liste .choix-modules__nom small { display: block; color: var(--text-3); }
.choix-modules__liste .rf-num { white-space: nowrap; color: var(--text); }
.choix-modules__total { margin-top: var(--rf-2); }
