/* ============================================================================
   refonte.css — refonte UI de m'Manager Invoicing
   VERSION 1.23.0 · 2026-09-05
   ----------------------------------------------------------------------------
   À charger APRÈS app.css. N’écrase rien : jetons repris de app.css:root,
   ajouts préfixés --rf-.
   Cette feuille et vues.html forment une PAIRE : elles portent le même numéro
   de version et ne doivent pas être intégrées séparément.

   Historique
   v1.23.0 — 2026-09-05
     · LA LISTE DES FACTURES SUR TÉLÉPHONE. « Horrible », et c'était mesuré
       avant d'être dit : 290 px par facture sur un écran de 375, une liste de
       vingt-cinq à 8 230 px — vingt-deux hauteurs d'écran. Le tableau converti
       en cartes gardait la structure du tableau et réimprimait cinq intitulés
       de colonne sur chaque facture. Trois lignes, deux colonnes, les
       intitulés retirés sauf « TTC ». Après : 150 px de moyenne, 4 622 px,
       aucun débordement. Voir le bloc en fin de feuille pour les trois
       répartitions essayées et pourquoi deux colonnes l'emportent.

     · LE SIGLE DISPARAISSAIT EN MODE NUIT. Son pli et sa lettre portaient du
       blanc écrit en dur, sa feuille est peinte avec « --accent » : en clair
       l'accent est l'encre et le blanc ressort — 17,79 — en sombre l'accent
       devient la craie et le blanc se posait sur du blanc — 1,14, où 1,00 veut
       dire « couleurs identiques ». Les deux tracés prennent
       « --sur-accent » : 16,70 en sombre. Le banc a désormais une vue
       « sombre », et c'est sa première épreuve de thème.

     · CE QUI EST ÉPINGLÉ SE DIT. L'épinglage fonctionnait et rien ne le
       signalait hors du mode Personnaliser. Un intitulé de groupe, une fois,
       plutôt qu'un signe sur chaque carte — et pas un filet coloré, qui dit un
       ÉTAT dans ce produit. Masqué à l'assistance, puisque la grille ne
       réordonne pas le DOM : la mention voyage sur la carte.

   v1.22.0 — 2026-09-04
     · LA GRILLE PASSE DE SIX À DOUZE COLONNES. Signalé par le produit : « le
       tableau de bord est chargé ; il faudrait des div 6 ou des 3 ; le
       "Facturé cette année" qui prend toute la row, ça fait moche ».

       La cause est arithmétique, et mesurée à 1 440 px sur un tableau de
       1 120 : la disposition pose QUATRE indicateurs et quatre ne pave pas
       six. Trois tenaient la rangée, le quatrième restait seul, et la règle
       d'orpheline — écrite en v1.20 pour ne pas laisser de fond nu — l'étirait
       de 363 px à 1 120. Le QUART de rangée, qui règle le cas, n'est pas
       exprimable en sixièmes.

         span  largeur  orpheline  carte
            2      363          ·  Chiffre d'affaires HT
            2      363          ·  TVA collectée
            2      363          ·  Encours client
            2     1120        oui  En retard
            3      552          ·  Principaux clients
            3     1120        oui  Valeur du stock

       Divisions vivantes : quart (3), tiers (4), moitié (6), deux tiers (8),
       pleine (12). Cinquième et sixième restent absents — une carte
       d'indicateur porte un montant, et un montant ne se coupe pas.

     · ET L'ORPHELINE NE S'ÉTIRE PLUS QU'À BON ESCIENT. La règle valait pour
       une liste ou une courbe, qui gagnent à s'étendre ; appliquée à un
       indicateur, elle posait un montant au bord gauche d'une bande de 1 120 et
       rien à droite. La taille déclarée au catalogue tranche : « petit » ne
       s'étire jamais.

     · MIGRATION : AUCUNE LIGNE DE BASE NE BOUGE. Les dispositions enregistrées
       portent des largeurs en sixièmes, et rien dans la chaîne ne le disait —
       un « 4 » valait deux tiers et vaudrait un tiers. Un marqueur d'échelle
       en tête (« v12| ») lève l'ambiguïté ; une chaîne sans marqueur est lue
       comme ancienne et ses largeurs doublées, si bien qu'un tiers reste un
       tiers. Même parti que pour l'ancienne forme sans largeur, en v1.20.

   v1.21.0 — 2026-09-04
     · LA LIGNE DEVIENT UNE VIGNETTE. Demandé par le produit : « les listes
       peuvent-elles avoir un aspect de vignettes bien délimitées, comme des
       rangées empilées ». Mesuré avant de livrer, à rythme vertical ÉGAL —
       sinon on compare deux densités et non deux apparences :

         registre    pas de 100 px · 2 661 px pour 25 lignes · 5 lignes entières
         vignettes   pas de 108 px · 2 854 px pour 25 lignes · 5 lignes entières

       Huit pour cent de hauteur en plus, autant de lignes visibles, et les
       colonnes restent alignées au pixel — la grille reste celle du tableau,
       seule l'apparence de la rangée change.

       Ce que les huit pour cent achètent, et ce n'est pas du goût : sous 860 px,
       app.css empile DÉJÀ chaque rangée en carte. Le même objet avait donc deux
       apparences selon la largeur de la fenêtre. Il n'en a plus qu'une.
       Et le liseré d'état d'argent devient la bordure gauche de la vignette au
       lieu d'un pseudo-élément posé dans une carte : à « left: 0 » dans une
       boîte à coins ronds, il dépassait du coin.
   v1.20.0 — 2026-09-04
     · LA RESPIRATION, demandée par le produit et mesurée avant d'être corrigée.
       · LE FILET D'UNE LIGNE ÉTAIT DOUBLÉ. La ligne porte son trait depuis la
         v1.3.0 ; app.css en ajoute un par CELLULE, et la grille les décale de
         ses gouttières — d'où six moignons au lieu d'un trait, dont un de
         26 px sous le menu « ⋯ ». C'est ce que le produit voyait en disant
         « on voit les bords ». Le trait de cellule s'efface là où la ligne est
         une grille.
       · LE MENU DE LIGNE mesurait 35 × 26 et affleurait le rembourrage de la
         carte. Il passe à 32 × 32 — plancher d'une cible au doigt — et sa
         colonne à 40 px, pour qu'il cesse de toucher le bord.
       · LE PANNEAU LATÉRAL touchait les trois bords de la fenêtre, sans rayon :
         il se lisait comme un mur, pas comme une feuille posée dessus. Il
         flotte à 12 px, avec le rayon des cartes. Sous 900 px il reprend toute
         la place — sur un téléphone, une feuille qui flotte perd de la surface
         utile sans rien gagner.
       · Le rythme vertical d'une ligne passe de 13 à 15 px : vingt-cinq lignes
         à 13 px tiennent plus d'informations qu'on n'en lit.
   v1.19.0 — 2026-09-04
     · LA FICHE CLIENT, ET LE SOLDE QUI A DEUX SENS (README §19).
       Décision du produit, 4 septembre : deux postes côte à côte, JAMAIS un
       solde net. 400 € d'un côté et 960 € de l'autre ne font pas −560 € : on ne
       relance pas −560, on ne rembourse pas −560. Le net est juste
       comptablement et inutilisable opérationnellement.
       · .rf-solde__* — deux pôles NOMMÉS PAR LEUR SENS (« il vous doit » /
         « vous lui devez »), chacun avec sa population, son retard en segment
         de lui-même, et SES SORTIES. L'écran annonçait une dette de 960 € sans
         rien à cliquer : la fiche disait quoi faire, jamais où.
       · .rf-sortie / .rf-sorties — le bouton de sortie devient UN composant du
         système, partagé par la fiche et par le refus d'émission. Il vivait en
         deux exemplaires sous .rf-refus__sortie, dont aucun n'était posé.
       · .rf-fiche__* — la tête reste, la liste défile. Le corps mesurait
         1 017 px dans un dialogue de 684 : les deux pôles sortaient de l'écran
         dès qu'on regardait la troisième pièce, c'est-à-dire toujours.
       · .rf-du / .rf-arembourser — CÂBLÉES, huit jours après avoir été
         dessinées pour cette fiche exactement. Sur un avoir, le « reste » est
         ce que L'ENTREPRISE doit : cinq avoirs s'affichaient du même côté et du
         même signe que quatre factures impayées, et la liste totalisait 1 512 €
         sous un en-tête qui annonçait 600 €.
   v1.18.0 — 2026-09-04
     · LA LISTE DES FACTURES N'AVAIT AUCUNE HIÉRARCHIE, et c'est mesurable :
       352 px sur 900 consommés avant la première ligne, des lignes de 97 px,
       et un seul écart typographique dans toute la ligne — le montant à
       14,5 px/600 contre 14 px/400 pour tout le reste. Un demi-pixel et une
       graisse. D'où quatre gestes, aucun décoratif :
       · .rf-somme — trois nombres NOMMÉS en tête de liste, tirés du filtre
         courant. Le serveur rendait déjà « unpaid » et « refundable » à côté
         de « includingVat » : la liste les jetait depuis le début. Les trois
         sortent de la MÊME population — celle du filtre, pas celle de la page.
         Il n'y a pas de quatrième poste « en retard » : le serveur ne rend pas
         cette somme pour le filtre, et la dériver des 25 lignes affichées
         mettrait un montant de page à côté de deux montants de filtre.
         Le poste attendra son total, il ne mentira pas en attendant.
       · .rf-life__dot devient un SEGMENT (14×4) au lieu d'une pastille de
         7 px. Même donnée, même sémantique, même jetons : quatre pastilles
         rondes de sept pixels au même vert ne se lisent pas comme une
         progression, une piste à quatre crans oui. La liste devient l'écho
         compressé de la chronologie du panneau — un seul organe pour le cycle
         du document, à deux densités.
       · .rf-amount--ligne — le montant est ce qu'on vient lire sur un écran de
         créances ; il monte à 16,5 px et prend le rang de titre de la ligne.
         Le numéro descend au rang d'identifiant (.rf-num--id, déjà défini au
         §17.8 pour le SIREN : un identifiant n'est pas un nom).
       · .rf-table__row[data-argent] — l'état d'argent passe en liseré de 2 px
         au bord gauche. Le regard balaye un bord, il ne balaye pas une colonne.
     · .rf-tete / .rf-chips / .rf-chip / .rf-compte — les sommes et l'instrument
       qui les réduit tiennent sur UNE barre, les trois réglages se replient, et
       ce qui AGIT reste écrit en pastilles retirables.
       Note d'honnêteté : mon premier geste — replier les quatre commandes — n'a
       rien gagné du tout. Elles tenaient déjà sur une seule rangée : 439 px
       avant, 437 après. Ce n'était pas leur nombre qui coûtait, c'était leur
       RANGÉE. La mesure a corrigé le raisonnement, et c'est la deuxième fois
       aujourd'hui qu'une conviction de mise en page ne survit pas au bac.
     · CORRECTIF MOBILE : .rf-board n'avait aucune borne. À 375 px, six colonnes
       laissaient 110 px par carte et cassaient les montants au milieu du
       nombre (« 13 872,00 € » sur deux lignes, « 2 774, / 40 € »). Même faute
       qu'en v1.14.0, un cran plus haut : une grille posée sans seuil.
       Et j'ai posé la correction au mauvais endroit du fichier avant de la
       mesurer : dans le bloc média de tête, donc AVANT la déclaration qu'elle
       corrige, donc écrasée par elle. Une requête média ne l'emporte pas sur
       une règle de même spécificité écrite plus loin. Le bloc vit désormais
       juste sous .rf-board, où il se lit à côté de ce qu'il borne.
   v1.17.0 — 2026-09-02
     · README §25 : la bande de réconciliation, montée sur les vidages réels,
       détecte un encours surévalué de 1 236,00 € (les avoirs comptés en créances).
   v1.16.0 — 2026-09-02
     · .rf-refus__* : un refus porte ses sorties, sous le champ et non dans un
       message fugace (README §24).
   v1.15.0 — 2026-09-02
     · .rf-identite__* : le bloc d'identité nomme le dossier et la personne
       (rôle comptable, README §23).
   v1.14.0 — 2026-09-02
     · CORRECTIF MOBILE : la grille de tableau est bornée à min-width 901px.
       Sans borne, elle écrasait l'empilement en cartes d'app.css et les libellés
       « td[data-label] » se superposaient aux valeurs. Vu en faisant tourner le
       front avec les requêtes média forcées. README §22.
   v1.13.0 — 2026-09-02
     · .rf-partiel__* (trois états côté argent, la pastille porte la proportion)
       et .rf-declare (l'encaissement déclaré). README §21.
   v1.12.0 — 2026-09-02
     · README §20 : le reçu d'émission (avertissement après le point de
       non-retour, « conforme sous réserve », nom du fichier livré).
   v1.11.0 — 2026-09-02
     · .rf-du / .rf-arembourser / .rf-deux-totaux : le sens de l'argent dans une
       colonne de montants (fiche client, README §19).
   v1.10.0 — 2026-09-02
     · .rf-quota__* : une jauge qui déborde (le compris, puis l'au-delà en --err).
     · README §18.8 et §18.9 (jauge plafonnée, « share » à deux unités).
   v1.9.0 — 2026-09-02
     · README §18 : quatre écrans de conformité sur données réelles (marque
       blanche dans le journal d'audit, périodes e-reporting fausses).
   v1.8.0 — 2026-09-02
     · Rejeu de neuf vidages réels du staging (README §17) : .rf-num--id,
       .rf-cadre (préfixe commun retiré du libellé de cadre).
   v1.7.0 — 2026-09-02
     · .rf-vide (un seul tiret pour toute valeur absente) et .rf-manque
       (l'absence qui est une anomalie : courriel manquant, SIREN manquant).
     · README §16 : tenir une réponse partielle.
   v1.6.0 — 2026-09-02
     · Audit sur le front réel (bac à API simulée) : .rf-sched__* (le nom du client
       ne cède plus au motif « bloquée »), .rf-amount--kpi, .rf-kpi__label,
       .rf-reconcile__* (§14), .rf-cell--indisponible.
   v1.5.0 — 2026-09-02
     · Rangée d'indicateurs refondue : bande de réconciliation, courbe qui nomme
       son unité, le retard en segment de l'encours (trois cartes, plus quatre).
     · .spark-vide retiré : plus de zone réservée, les cartes s'égalisent en flex.
     · « non réglées » (et non « non encaissées ») ; état « sans raison sociale ».
   v1.4.0 — 2026-09-02
     · display:inline-block sur .rf-gauge__fill, .rf-chart__key, .rf-aging__seg
       (width/height ignorés sur une boîte en ligne — défaut trouvé 3 fois).
     · Modèle de document : trois libertés seulement (logo, accent, pied de page).
       Habillage et typographie retirés du panneau, gardés comme choix d'éditeur.
     · Cadence de relance 7 / 15 / 30, lue du serveur.
   v1.3.0 — 2026-09-02
     · Tableau des factures : balisage <table> conservé, classes rf-table__* posées
       dessus (display:grid sur <tr>). L'empilement mobile de app.css survit.
     · Clavier : Échap ferme le calque du dessus (une seule écoute au document) ;
       lignes activables à Entrée et Espace.
     · Dialogues d'argent (règlement, remboursement, imputation) : gabarit unique.
     · Fiche client : partage par le SENS DE L'ARGENT, avoirs du côté des dettes.

   v1.2.0 — 2026-09-02
     · Marque blanche appliquée aux fichiers livrés (0 occurrence : css, html, README).
     · Arbitrages tenus : « Cycle du document » / « Encaissement » figés ; overflow-x conservé sur le tableau des factures.
     · Menu de ligne « ⋯ » spécifié hors flux (position fixe calculée à l’ouverture).
     · Trop-perçu : état « À rembourser », deux sorties (remboursement, imputation).
   v1.1.0 — 2026-09-02
     · Tableau de bord à widgets (--rf-span / --rf-order / data-epingle).
     · Écran « Modèle de document » : page A4 794 × 1123, trois habillages, impression isolée.
   v1.0.0 — 2026-09-02
     · Première livraison : 8 écrans, tableaux en grille, panneau de détail.
   ============================================================================ */

/* ==========================================================================
   refonte.css — feuille de la refonte UI de m'Manager Invoicing
   --------------------------------------------------------------------------
   S'ajoute APRÈS app.css et ne remplace rien : tous les jetons de couleur,
   de rayon et d'ombre viennent de app.css (:root). Les jetons propres à la
   refonte sont préfixés --rf- pour qu'on les distingue d'un coup d'œil.

   Convention de nommage : .rf-<bloc>__<élément>--<variante>.
   Aucune règle ne cible un identifiant : les vues gardent leurs #view-*.
   ========================================================================== */

:root {
  /* Rythme vertical. Une seule échelle, multiples de 2. */
  --rf-1: 4px;
  --rf-2: 8px;
  --rf-3: 12px;
  --rf-4: 16px;
  --rf-5: 20px;
  --rf-6: 24px;
  --rf-8: 32px;
  --rf-10: 48px;

  /* Densité des tableaux et des cartes. */
  --rf-row-y: 15px;
  --rf-card-x: 18px;

  /* Chiffres. Toujours tabulaires : une colonne de montants qui danse
     d'une ligne à l'autre se lit deux fois moins vite. */
  --rf-num: "tnum" 1, "lnum" 1;

  /* Teintes d'appui, dérivées des jetons d'app.css. */
  --rf-late: #eb6834;          /* retard installé, entre --warn et --err */
  --rf-late-deep: #7c1f18;     /* + de 60 jours */
  --rf-ink-soft: rgba(23, 32, 42, .06);
  --rf-ink-line: rgba(23, 32, 42, .07);

  --rf-lift: 0 1px 2px rgba(23, 32, 42, .05);
  --rf-lift-2: 0 1px 2px rgba(23, 32, 42, .05), 0 8px 24px rgba(23, 32, 42, .06);
  --rf-drawer-shadow: -24px 0 60px rgba(23, 32, 42, .18);
}

/* --------------------------------------------------------------- primitives */

.rf-num { font-variant-numeric: tabular-nums; font-feature-settings: var(--rf-num); }

.rf-eyebrow {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-3);
}

.rf-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--rf-lift);
}
.rf-card--raised { box-shadow: var(--rf-lift-2); }
.rf-card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--rf-3);
  flex-wrap: wrap;
  padding: 15px var(--rf-card-x);
  border-bottom: 1px solid var(--line);
}
.rf-card__head h2 { margin: 0; font-size: 15px; font-weight: 600; }
.rf-card__body { padding: var(--rf-4) var(--rf-card-x); }
.rf-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rf-3);
  padding: var(--rf-3) var(--rf-card-x);
  font-size: 13px;
  color: var(--text-3);
}

/* Deux colonnes qui se replient plutôt que de s'écraser. Pas de media
   query : le point de rupture est la largeur du contenu, pas celle de
   l'écran — la même règle tient dans un panneau étroit. */
.rf-split { display: flex; flex-wrap: wrap; gap: var(--rf-5); align-items: flex-start; }
.rf-split > .rf-split__main { flex: 1 1 420px; min-width: 0; }
.rf-split > .rf-split__side { flex: 1 1 300px; min-width: 270px; }

/* ------------------------------------------------------------------ pilules */

.rf-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: 20px;
  font-size: 12.5px;
  font-weight: 560;
  background: var(--rf-ink-soft);
  color: var(--text-2);
  white-space: nowrap;
}
.rf-pill--ok   { background: var(--ok-soft);   color: var(--ok); }
.rf-pill--warn { background: var(--warn-soft); color: var(--warn); }
.rf-pill--err  { background: var(--err-soft);  color: var(--err); }
.rf-pill--owed { background: var(--owed-soft); color: var(--owed); }

/* Cycle de vie du document : quatre pastilles, une par étape franchie
   (émise, déposée, mise à disposition, acceptée). Le refus colore la
   dernière pastille atteinte en rouge et s'arrête là. */
.rf-life { display: flex; align-items: center; gap: 3px; }
/* UN SEGMENT, PAS UNE PASTILLE. Quatre ronds de sept pixels au même vert
   portaient la progression sans la dire : à la lecture, « déposée » (trois
   pleins) et « acceptée » (quatre pleins) rendaient la même tache. Une piste
   remplie de gauche à droite se lit sans compter, et c'est la même donnée. */
.rf-life__dot {
  width: 14px;
  height: 4px;
  border-radius: 980px;
  background: var(--rf-ink-line);
}
/* Le dernier cran atteint porte la couleur pleine, les précédents la portent
   atténuée : on voit d'où ça vient ET où c'est arrivé. */
.rf-life__dot[data-on] { background: color-mix(in srgb, var(--accent) 42%, transparent); }
.rf-life__dot[data-on]:last-of-type { background: var(--accent); }
.rf-life__dot[data-on] { background: var(--accent); }
.rf-life[data-etat="acceptee"] .rf-life__dot[data-on] { background: color-mix(in srgb, var(--ok) 42%, transparent); }
.rf-life[data-etat="acceptee"] .rf-life__dot[data-on]:last-of-type { background: var(--ok); }
.rf-life[data-etat="refusee"] .rf-life__dot[data-on]:last-of-type { background: var(--err); }
.rf-life__label { font-size: 12.5px; margin-left: var(--rf-1); color: var(--text-2); }
.rf-life[data-etat="acceptee"] .rf-life__label { color: var(--ok); }
.rf-life[data-etat="refusee"]  .rf-life__label { color: var(--err); }

/* ---------------------------------------------------------------- tableaux */

/* Un tableau en grille CSS, colonnes déclarées dans le HTML via
   --rf-cols, avec un plancher pour la colonne souple et un défilement
   horizontal du bloc plutôt qu'un écrasement des colonnes fixes. */
.rf-table {
  overflow-x: auto;
  /* SANS CETTE LIGNE, LE BLOC MANGE LA MOLETTE.

     « overflow-x: auto » seul ne déclare rien pour l'axe vertical, et la spec
     CSS ne laisse pas les deux axes libres : dès qu'un axe n'est pas
     « visible », l'autre cesse de pouvoir l'être et calcule « auto ». Mesuré au
     navigateur le 7 septembre 2026 :

       overflow-x: auto  seul          →  overflow-y calculé : auto
       overflow-x: auto + y: hidden    →  overflow-y calculé : hidden

     Le tableau devenait donc un conteneur de défilement VERTICAL que personne
     n'avait demandé, et il capturait la molette — y compris sur un écran large,
     où il n'y a aucun débordement horizontal à faire défiler. Il fallait sortir
     le pointeur du tableau pour que la page reprenne la main. Signalé deux fois
     par Eric, les 6 et 7 septembre : « le scroll dans la liste des factures,
     plus le scroll de la page, créent par moment des blocages » puis « il se
     bagarre avec le scroll de la page et c'est pénible ».

     « hidden » ne coupe rien ici : ce bloc n'a aucune hauteur imposée, il prend
     celle de son contenu, et rien ne dépasse donc verticalement. Ce qu'il
     retire est la seule chose de trop — la capture de la molette. */
  overflow-y: hidden;
}
.rf-table__head,
.rf-table__row,
.rf-table__foot {
  display: grid;
  grid-template-columns: var(--rf-cols);
  gap: 14px;
  align-items: center;
  min-width: var(--rf-table-min, 840px);
  padding: var(--rf-row-y) var(--rf-card-x);
}
.rf-table__head {
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  padding-block: 11px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-3);
}
.rf-table__row {
  border-bottom: 1px solid var(--rf-ink-line);
  cursor: pointer;
  transition: background .14s var(--ease);
}
/* UN FILET PAR LIGNE, PAS UN PAR CELLULE. La ligne porte le sien ci-dessus ;
   app.css en pose un sur chaque « td », et la grille les sépare de ses
   gouttières — le trait continu devenait six tronçons décalés, dont un moignon
   de 26 px sous le menu de ligne. Deux traits pour une séparation, c'est un de
   trop, et c'est le plus court qui se voit. */
.rf-table__row > td,
.rf-table__head > th { border-bottom: 0; }
.rf-table__row:hover { background: var(--surface-2); }
.rf-table__row:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: -2px; }
.rf-table__foot { display: flex; justify-content: space-between; color: var(--text-3); font-size: 13px; }

/* ---- tableau HTML ----------------------------------------------------------
   Le balisage reste un <table> pour que l'empilement mobile de app.css
   (td[data-label]) survive. Ces trois règles suffisent à faire cohabiter le
   tableau et la grille ; elles ne s'appliquent qu'au bureau, l'empilement
   mobile vit dans app.css et n'est pas touché. */
.rf-table > table { display: block; width: 100%; border-collapse: collapse; }
.rf-table > table > thead,
.rf-table > table > tbody,
.rf-table > table > tfoot { display: block; }

/* LA GRILLE EST BORNÉE AU BUREAU, et il a fallu faire tourner le front pour le
   voir. Sous 900 px, app.css empile chaque ligne en carte (« td[data-label] »
   avec son libellé en ::before) ; « display: grid » posé sans borne sur le
   <tr> écrase cet empilement, et les libellés se superposent aux valeurs —
   « €RANSMISSION », « RÈGLEMENT » imprimé par-dessus « Paiement déclaré », le
   menu « ⋯ » à moitié sous la pastille. Le tableau devient illisible sur
   téléphone.

   C'est le critère « aucune media query de mise en page » qui était faux : une
   grille de bureau DOIT être bornée, sinon elle fuit dans l'empilement mobile
   de la feuille d'accueil. Le seuil est celui d'app.css (900 px), pas un autre :
   deux bascules à dix pixels d'écart donnent une largeur où ni l'un ni l'autre
   ne s'applique. */
@media (min-width: 901px) {
  .rf-table__head,
  .rf-table__row,
  .rf-table__foot { display: grid; grid-template-columns: var(--rf-cols); gap: var(--rf-3); }
  .rf-table__row { min-width: var(--rf-table-min, auto); }

  /* ---- LA RANGÉE EMPILÉE ------------------------------------------------------
     Chaque ligne est une vignette posée sur le fond de la page, et non une
     rangée dans une carte unique. Bornée à cette largeur : sous 860 px,
     app.css empile déjà les rangées en cartes, avec son propre cadre et sa
     propre marge — les deux jeux de règles se doubleraient. */
  .rf-table.card {
    background: transparent;
    border: 0;
    box-shadow: none;
    padding: 0;
  }
  /* L'en-tête prend le rembourrage des vignettes, sinon les intitulés glissent
     d'un cran par rapport aux valeurs qu'ils nomment.

     ET IL SE SOUDE À LA PREMIÈRE FACTURE. Mesuré le 24 septembre 2026 : la
     bande et la rangée sont collées — écart vertical nul, mêmes bords — mais
     leurs rayons se contredisaient. La bande finissait en angle droit pendant
     que la rangée rentrait de onze pixels, et il restait deux encoches de fond
     de page sous ses coins. Ça ne « posait » sur rien.

     LE CONTOUR PORTE LA SÉPARATION, PAS LE FOND. La bande se détachait à
     1,10 : 1 de la page — neuf points sur deux cent cinquante-cinq, que l'œil
     ne sépare pas. Plutôt que d'inventer une teinte, on lui donne le bord des
     vignettes : l'ensemble bande + première facture devient UNE carte, et c'est
     son contour qui la découpe du fond. Le texte passe de « text-3 » à
     « text-2 » dans le même mouvement — 5,09 : 1 passait la norme de justesse
     pour de l'onze pixels et demi en capitales. */
  .rf-table__head {
    padding: 8px 15px;
    border: 1px solid var(--line);
    border-bottom-color: var(--line-strong);
    border-radius: var(--radius) var(--radius) 0 0;
    color: var(--text-2);
  }

  /* La première rangée rend son haut : la bande y pose ses coins, et le filet
     du bas de la bande fait la couture — deux bords à cet endroit feraient un
     trait double. */
  .rf-table tbody .rf-table__row:first-child {
    border-top: 0;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
  }
  .rf-table__row {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--rf-row-y) 15px;
    margin-bottom: 7px;
    transition: border-color .14s var(--ease), background .14s var(--ease);
  }
  /* Le survol touche le CADRE et non le fond : vingt-cinq vignettes qui
     changent de fond au passage de la souris font clignoter la page. */
  .rf-table__row:hover { background: var(--surface); border-color: var(--line-strong); }
  .rf-table__foot { margin-top: 4px; }

  /* ---- LA FEUILLE DÉCOUPÉE ----------------------------------------------------
     La dernière vignette se termine en dents, comme une feuille détachée d'un
     carnet. Demandé par le produit — et la découpe DIT quelque chose : elle ne
     paraît que si cette page est la dernière (« data-fin », posé au rendu). Au
     bas d'une page 1 sur 2, un bord déchiré affirmerait que la liste s'arrête
     là quand il reste vingt-et-une factures. Une forme qui affirme du faux
     coûte plus cher qu'une forme plate.

     Dessinée en dégradés et non en masque : un masque clipperait la peinture de
     TOUTE la descendance, menu de ligne compris — et ce menu est précisément
     sur la dernière vignette, la seule qui porte la découpe. Ici deux dégradés
     peignent le fond de la page en triangles par-dessus le bas de la carte : la
     carte est coupée à l'œil, rien n'est clippé.

     Les deux pixels négatifs mordent sur la bordure : une déchirure n'a pas de
     trait, et un liseré qui longerait les dents les nierait. */
  /* « .rf-table » est sur la CARTE, « data-fin » sur le tableau : un seul
     élément ne peut pas porter les deux, et mon premier sélecteur les
     confondait — il ne s'appliquait donc à rien. Mesuré, pas relu. */
  .rf-table table[data-fin] .rf-table__row:last-child { position: relative; }
  .rf-table table[data-fin] .rf-table__row:last-child {
    /* La bordure du bas s'en va : la déchirure la remplace, et un trait droit
       sous des dents nierait les dents. */
    border-bottom: 0;
  }
  .rf-table table[data-fin] .rf-table__row:last-child::after {
    content: "";
    position: absolute;
    left: -1px; right: -1px; top: 100%;
    height: 10px;
    pointer-events: none;

    /* La bande prolonge la vignette, puis se fait découper. Le masque porte sur
       CE pseudo-élément et non sur la ligne : un masque clippe la peinture de
       toute la descendance, menu de ligne compris — et ce menu est justement
       sur la dernière vignette. Première version en dégradés peints par-dessus
       la carte : les dents se lisaient à peine, la carte et le fond ne
       différant que de 1,07 de contraste en clair.

       Le « drop-shadow » d'un pixel suit la SILHOUETTE du masque : c'est lui qui
       donne aux dents le même trait que le cadre de la vignette, et c'est ce qui
       les rend lisibles dans les deux thèmes. */
    background: var(--surface);
    border-left: 1px solid var(--line);
    border-right: 1px solid var(--line);
    mask-image: conic-gradient(from -45deg at bottom, transparent 25%, black 0);
    mask-size: 16px 100%;
    mask-repeat: repeat-x;
    filter: drop-shadow(0 1px 0 var(--line));
  }

  /* Le rayon du bas s'en va avec la bordure : un coin arrondi sous des dents se
     lit comme un défaut de dessin. */
  .rf-table table[data-fin] .rf-table__row:last-child {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
  }

  /* L'état d'argent devient la bordure gauche de la vignette. Le
     pseudo-élément de la v1.18.0 servait dans une carte unique ; dans une
     boîte à coins ronds, un trait à « left: 0 » dépasse du coin. */
  .rf-table__row[data-argent]::before { display: none; }
  .rf-table__row[data-argent="retard"]     { border-left: 3px solid var(--err); }
  .rf-table__row[data-argent="partiel"]    { border-left: 3px solid var(--warn); }
  .rf-table__row[data-argent="rembourser"] { border-left: 3px solid var(--owed); }
}

/* Sous le seuil, la ligne rend la main : c'est app.css qui empile. */
@media (max-width: 900px) {
  /* LES DEUX AXES, et c'est le sujet. « overflow-x: visible » seul ne libérait
     rien : la règle de base laisse « overflow-y: hidden », et la spec coerce —
     si un axe vaut « visible » et l'autre non, « visible » calcule « auto ».
     Le bloc restait donc un conteneur de défilement sous 900 px, c'est-à-dire
     exactement le défaut que cette ligne croyait retirer.

     MESURÉ au navigateur le 23 septembre 2026, à 306 px de large : « overflow-x »
     calculé valait « auto ». Trois mois après le signalement, la molette était
     toujours capturée sur mobile. */
  .rf-table { overflow: visible; }
  .rf-table__head { display: none; }
  .rf-table__row { display: revert; min-width: 0; }
}
/* ---- L'EN-TÊTE QUI RESTE ----------------------------------------------------

   Vingt-cinq factures à l'écran, et l'on ne sait plus laquelle des colonnes
   porte le TTC. L'en-tête se colle donc au haut de la PAGE.

   SEULEMENT AU-DESSUS DE CE SEUIL, et ce n'est pas un oubli. Un « position:
   sticky » se résout au plus proche ancêtre qui défile ; « overflow-y: hidden »
   en fait un, si bien que l'en-tête se collerait au haut de son propre bloc,
   c'est-à-dire nulle part. Retirer le conteneur de défilement est donc la
   condition — et on ne peut le retirer que là où il ne sert pas.

   L'AUTRE ISSUE EST ÉCARTÉE, délibérément : donner au bloc une hauteur fixe et
   son propre défilement vertical ferait coller l'en-tête à toutes les largeurs.
   C'est exactement ce que ce fichier a retiré le 7 septembre 2026, après deux
   signalements — « le scroll dans la liste des factures, plus le scroll de la
   page, créent par moment des blocages », puis « il se bagarre avec le scroll
   de la page et c'est pénible ». Un en-tête gagné contre un défilement imbriqué
   serait un mauvais échange.

   LE SEUIL VIENT D'UNE MESURE, pas d'un chiffre rond. Mesuré au navigateur le
   23 septembre 2026 : la grille demande 894 px de min-content, la barre
   latérale et les marges en prennent 320. En dessous de 1214 px le tableau
   défile latéralement, et l'en-tête doit défiler avec lui. 1220 laisse la marge
   du pixel.

   Entre 901 et 1219 px, l'en-tête reste donc en haut du tableau et s'en va avec
   lui. C'est le prix d'un seul défilement dans la page, et c'est le bon prix.

   SUR LE « thead » ET NON SUR LA RANGÉE. Un élément collant glisse dans son
   bloc conteneur ; celui de la rangée est le « thead », haut d'exactement une
   rangée — il n'a nulle part où glisser, et « position: sticky » y reste sans
   effet. Mesuré au navigateur : posé sur la rangée, l'en-tête partait à −345 px
   au premier défilement.

   Et c'est du collage de BLOC, non de « table-header-group » : ce tableau a
   depuis longtemps « display: block » sur son table, son thead et son tbody,
   la grille vivant sur les rangées. Rien ici ne dépend du support inégal de
   « position: sticky » sur les groupes de tableau. */
@media (min-width: 1220px) {
  .rf-table { overflow: visible; }

  .rf-table thead {
    position: sticky;
    top: 0;
    /* Au-dessus des rangées, qui passent dessous — et sous les menus de ligne,
       qui s'ouvrent par-dessus tout. */
    z-index: 3;
    /* OPAQUE, sinon les rangées se lisent par transparence en passant dessous.
       Posé sur le thead et non sur la rangée : c'est le thead qui tient la
       place, et un liseré du fond de page apparaîtrait entre les deux. */
    background: var(--surface-2);

    /* ET IL PREND LE MÊME ARRONDI QUE LA RANGÉE QU'IL PORTE.

       Le fond est peint ici, sur une boîte CARRÉE, tandis que l'arrondi de onze
       pixels vit sur le « tr » à l'intérieur : le coin carré du parent
       dépassait donc derrière la courbe de l'enfant. Un petit angle de gris
       au-dessus à gauche, et le même à droite.

       Mesuré le 24 septembre 2026 en échantillonnant les pixels du coin :
       « elementFromPoint » y rendait THEAD là où la page aurait dû paraître.
       Le défaut n'existait qu'au-dessus de 1220 px, c'est-à-dire là où ce bloc
       s'applique — il est arrivé avec l'en-tête collant, pas avant.

       Signalé par Eric : « le bord haut arrondi qui laisse passer un coin ». */
    border-radius: var(--radius) var(--radius) 0 0;
  }

  /* Le rembourrage et le rayon vivent désormais au seuil des vignettes, avec le
     contour : ils servent à SOUDER la bande à la première facture, ce qui vaut
     à toutes les largeurs de bureau et pas seulement quand elle devient
     collante. */
}

.rf-table th { font: inherit; text-align: left; }
/* Les <td> ne reçoivent aucun display : leur parent <tr> est la grille. */
.rf-table__foot > td { display: flex; justify-content: space-between; gap: var(--rf-3); width: 100%; }

/* Libellé réservé aux lecteurs d'écran (en-tête de la colonne d'actions). */
.rf-sr {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.rf-cell { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.rf-cell__strong { font-size: 14.5px; font-weight: 540; }
.rf-cell__sub {
  font-size: 12.5px;
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rf-cell--right { text-align: right; }
.rf-amount { font-size: 14.5px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------- bande d'indicateurs */

.rf-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(212px, 1fr));
  gap: 1px;                       /* les filets sont le fond qui traverse */
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.rf-kpi {
  background: var(--surface);
  padding: var(--rf-4) var(--rf-card-x) 15px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.rf-kpi__value {
  font-size: 27px;
  font-weight: 640;
  line-height: 1;
  letter-spacing: -.025em;
  font-variant-numeric: tabular-nums;
}
.rf-kpi__foot { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--text-3); }
.rf-kpi__delta { font-weight: 600; }
.rf-kpi__delta[data-ton="ok"]   { color: var(--ok); }
.rf-kpi__delta[data-ton="warn"] { color: var(--warn); }
.rf-kpi__delta[data-ton="err"]  { color: var(--err); }
.rf-kpi__delta[data-ton="neutre"] { color: var(--accent); }

/* ------------------------------------------------------------ file d'attente */

/* « Ce qui attend » : la colonne vertébrale du tableau de bord. Une barre
   de 4 px porte l'urgence — le rouge ne dit pas « erreur » mais « délai
   réglementaire ». */
.rf-queue__item {
  display: grid;
  grid-template-columns: 4px 1fr auto;
  gap: 14px;
  align-items: center;
  padding: 14px 0;
  border-bottom: 1px solid var(--rf-ink-line);
}
.rf-queue__urgence { align-self: stretch; background: var(--text-3); }
.rf-queue__item[data-urgence="reglementaire"] .rf-queue__urgence { background: var(--err); }
.rf-queue__item[data-urgence="argent"]        .rf-queue__urgence { background: var(--warn); }
.rf-queue__item[data-urgence="courant"]       .rf-queue__urgence { background: var(--accent); }
.rf-queue__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; padding-right: var(--rf-3); }
.rf-queue__title { display: flex; align-items: center; gap: var(--rf-2); flex-wrap: wrap; font-size: 14.5px; font-weight: 560; }
.rf-queue__detail { font-size: 13px; color: var(--text-2); line-height: 1.45; }
.rf-queue__action { margin-right: var(--rf-card-x); white-space: nowrap; }

/* ------------------------------------------------------------------ graphique */

.rf-chart__plot {
  display: flex;
  align-items: flex-end;
  gap: 14px;
  height: 168px;
  padding-bottom: 22px;
  position: relative;
  border-bottom: 1px solid var(--line);
}
/* Ligne des charges fixes : un seuil, pas une donnée. Pointillée pour
   qu'on ne la confonde pas avec une série. */
.rf-chart__goal {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(22px + var(--rf-goal, 33%));
  border-top: 1px dashed rgba(23, 32, 42, .35);
}
.rf-chart__month {
  flex: 1;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 7px;
}
.rf-chart__bars { display: flex; align-items: flex-end; justify-content: center; gap: 3px; width: 100%; height: 100%; }
.rf-chart__bar {
  width: 38%;
  max-width: 22px;
  border-radius: 3px 3px 0 0;
  background: var(--accent);
  height: var(--rf-h, 0%);
  transition: height .4s var(--ease);
}
.rf-chart__bar--paid { background: var(--owed); }
.rf-chart__month[data-encours] .rf-chart__bar { opacity: .55; }
.rf-chart__label { font-size: 11.5px; color: var(--text-3); }
.rf-chart__month[data-encours] .rf-chart__label { color: var(--accent); font-weight: 600; }
.rf-chart__legend { display: flex; align-items: center; gap: var(--rf-4); font-size: 12.5px; color: var(--text-2); }
.rf-chart__key { display: inline-block; width: 9px; height: 9px; border-radius: 2px; }

/* --------------------------------------------------- jauges (encours, argent) */

.rf-gauge { display: flex; flex-direction: column; gap: 5px; }
.rf-gauge__line { display: flex; align-items: baseline; justify-content: space-between; gap: var(--rf-3); }
.rf-gauge__track { height: 6px; border-radius: 4px; background: var(--rf-ink-soft); overflow: hidden; }
/* display:inline-block d'office : ces trois classes portent width/height et
   doivent tenir sur n'importe quel élément. Sur un <i> ou un <span> — boîte en
   ligne non remplacée — la largeur est ignorée, et la jauge mesure zéro pixel
   sans que rien ne le signale. Défaut trouvé trois fois à l'intégration. */
.rf-gauge__fill { display: inline-block; height: 100%; width: var(--rf-pct, 0%); border-radius: 4px; background: var(--accent); }
.rf-gauge__note { font-size: 12px; color: var(--text-3); }

/* ------------------------------------ échéancier, réconciliation, montants ---
   Ajouté en v1.6.0, après audit sur le front réel. */

/* Un enrichissement dégrade, il n'emporte jamais la donnée principale : quand
   l'état de transmission manque, la cellule le dit et la ligne reste. */
.rf-cell--indisponible { color: var(--text-3); font-style: normal; }

/* LA LIGNE D'ÉCHÉANCE. Le nom du client ne cède jamais.
   Trouvé en faisant tourner le front : sur une facture bloquée, le nom mesurait
   ZÉRO pixel et la ligne annonçait « 24 180,00 € · bloquée » sans dire de qui.
   Deux causes cumulées — un parent flex sans « min-width: 0 » (les enfants ne
   peuvent alors pas rétrécir, et c'est le dernier arrivé qui gagne) et un motif
   sans « flex: 0 0 auto » (il se laisse comprimer, puis comprime son voisin).
   Le nom est premier servi, le motif dernier. */
.rf-sched { display: flex; flex-direction: column; }
.rf-sched__row {
  display: flex; align-items: baseline; gap: var(--rf-3);
  padding: 9px 0; border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.rf-sched__row:last-of-type { border-bottom: 0; }
.rf-sched__row[data-bloque] .rf-sched__sum { color: var(--text-3); }
.rf-sched__day { flex: 0 0 auto; min-width: 58px; color: var(--text-3); font-size: 12.5px; white-space: nowrap; }
.rf-sched__who { display: flex; align-items: baseline; gap: var(--rf-2); min-width: 0; flex: 1 1 auto; }
.rf-sched__name { flex: 1 1 auto; min-width: 6ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rf-sched__why { flex: 0 0 auto; color: var(--warn); font-size: 12px; white-space: nowrap; }
.rf-sched__sum { flex: 0 0 auto; font-weight: 600; white-space: nowrap; }
.rf-sched__foot {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--rf-3);
  padding-top: var(--rf-3); font-size: 13px; color: var(--text-2);
}

/* LA BANDE DE RÉCONCILIATION (§14). Un seul grand nombre, l'égalité qui le
   produit écrite à côté, et une barre partagée qui somme à ce nombre. */
.rf-reconcile { display: flex; flex-direction: column; gap: var(--rf-4); }
.rf-reconcile__bar {
  display: flex; height: 12px; border-radius: 980px; overflow: hidden; gap: 2px;
  background: var(--rf-ink-soft);
}
.rf-reconcile__seg { display: inline-block; width: var(--rf-pct, 0%); min-width: 2px; }
.rf-reconcile__keys { display: flex; gap: 26px; flex-wrap: wrap; }

/* UN MONTANT NE SE COUPE PAS : c'est le corps qui cède.
   « 13 872,00 € » se cassait en « 13 872,00 » puis « € » seul à la ligne —
   « overflow-wrap: anywhere » en 30 px dans une carte de deux colonnes sur six.
   Et le libellé tient sur une ligne : deux titres de hauteurs différentes
   déchirent la rangée. */
.rf-amount--kpi {
  white-space: nowrap;
  font-size: clamp(21px, 2.1vw, 30px);
  font-weight: 640;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.rf-kpi__label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Un NUMÉRO ne se coupe jamais : c'est la colonne d'identité, et
   « FAC-2026- / 000046 » sur deux lignes casse la lecture de la liste. */
.rf-num--id { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Le cadre de facturation, débarrassé de son préfixe commun. Les treize cadres
   commencent par « Dépôt d'une facture… » : quarante caractères identiques sur
   chaque ligne, et la troncature emportait le seul mot qui distingue.
   L'écran écrit « Bien », « Service », « Double », « Définitive après
   acompte », et garde le code en attribut pour l'œil qui le cherche. */
.rf-cadre { color: var(--text-2); font-size: 12.5px; white-space: nowrap; }
.rf-cadre::after { content: " · " attr(data-code); color: var(--text-3); }

/* ---- jauge de quota, qui DÉBORDE ------------------------------------------
   Une jauge plafonnée à 100 % ne peut pas dire un dépassement : un compte à
   47 sur 30 s'y affiche comme un compte à 30 sur 30, et c'est justement le
   dépassement qui coûte. La piste porte donc deux remplissages — le compris,
   puis l'au-delà en rouge, posé par-dessus depuis la droite. */
.rf-quota { display: flex; flex-direction: column; gap: 6px; }
.rf-quota__track {
  position: relative; height: 8px; border-radius: 980px;
  background: var(--rf-ink-soft); overflow: hidden;
}
.rf-quota__inclus {
  position: absolute; inset: 0 auto 0 0;
  width: var(--rf-inclus, 0%); background: var(--accent);
}
.rf-quota__audela {
  position: absolute; inset: 0 0 0 auto;
  width: var(--rf-audela, 0%); background: var(--err);
}
.rf-quota__note { font-size: 12.5px; color: var(--text-2); }
.rf-quota__note b { color: var(--err); font-variant-numeric: tabular-nums; }

/* ---- le sens de l'argent, dans une colonne de montants -------------------
   Sur un avoir, le « reste » est ce que L'ENTREPRISE DOIT. Le vidage réel de
   la fiche client montre cinq avoirs affichés du même côté et du même signe
   que quatre factures impayées : la liste totalise 1 512 € sous un en-tête qui
   annonce 600 €. La teinte indigo dit le sens, et le signe moins le double
   pour l'œil qui ne distingue pas les teintes. */
.rf-du      { font-variant-numeric: tabular-nums; font-weight: 600; }
.rf-arembourser { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--owed); }

/* Le TTC d'un avoir, dans la colonne qui totalise : signé et indigo, comme
   partout où le produit parle de ce qu'il doit à son client. Le signe seul
   suffirait à 14 px, mais la couleur porte plus loin que le glyphe — c'est elle
   qu'on voit en balayant vingt-cinq lignes. */
.rf-amount--avoir { color: var(--owed); }
.rf-arembourser::before { content: "− "; }

/* Et le pied porte DEUX totaux nommés, jamais un seul. */
.rf-deux-totaux { display: flex; flex-wrap: wrap; gap: var(--rf-3) 26px; }
.rf-deux-totaux > span { display: inline-flex; align-items: baseline; gap: 7px; font-size: 13px; color: var(--text-2); }
.rf-deux-totaux b { color: var(--text); font-variant-numeric: tabular-nums; }
.rf-deux-totaux b[data-sens="rembourser"] { color: var(--owed); }

/* ---- règlement partiel ------------------------------------------------------
   Trois états côté argent, pas deux, et la pastille porte la PROPORTION : c'est
   elle qui dit s'il faut relancer. Le statut réseau 212 s'intitule « paiement
   partiel ou total perçu » — le domaine connaissait déjà le cas. */
.rf-partiel {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; font-weight: 600;
  padding: 3px 9px; border-radius: 980px;
  background: var(--warn-soft); color: var(--warn);
  font-variant-numeric: tabular-nums;
}
.rf-partiel__jauge { display: inline-block; width: 26px; height: 4px; border-radius: 980px; background: color-mix(in srgb, var(--warn) 30%, transparent); overflow: hidden; }
.rf-partiel__part  { display: block; height: 100%; width: var(--rf-pct, 0%); background: var(--warn); }

/* Un encaissement est une opération DÉCLARÉE : la mention vit à côté du
   paiement, et son échec est une anomalie (.rf-manque), pas une note grise. */
.rf-declare { font-size: 12px; color: var(--ok); }
.rf-declare::before { content: "✓ "; }

/* ---- l'identité, quand on tient trente dossiers --------------------------
   Le bloc affichait le nom du dossier, son SIREN et l'adresse du comptable les
   uns sous les autres, sans dire lequel est lequel. Deux mots suffisent, et la
   place ne bouge pas d'un rôle à l'autre : c'est ce qui permet de la regarder
   sans la chercher. */
.rf-identite { display: flex; flex-direction: column; gap: 7px; }
.rf-identite__bloc { display: flex; flex-direction: column; }
.rf-identite__quoi {
  font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-3); font-weight: 600;
}
.rf-identite__qui { font-size: 13px; font-weight: 600; color: var(--text); }
.rf-identite__detail { font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }

/* ---- un refus, avec ses sorties -------------------------------------------
   Un refus sans sortie n'est pas une validation, c'est un mur : l'utilisateur
   retape des montants jusqu'à ce que ça passe. L'erreur vit SOUS le champ, elle
   y reste tant que la valeur est fausse, et elle porte les gestes légitimes. */
.rf-refus {
  display: flex; flex-direction: column; gap: 8px;
  padding: 11px 13px; border-radius: var(--radius);
  background: var(--err-soft);
  border-left: 2px solid var(--err);
  font-size: 13px;
}
.rf-refus__quoi { color: var(--text); line-height: 1.45; }
/* LA SORTIE EST UN COMPOSANT DU SYSTÈME, pas un bouton de ce bloc-ci.

   Un état qu'on annonce sans donner sa sortie fait quitter l'écran : c'est vrai
   d'un refus d'émission (« le montant dépasse le total », et rien à cliquer) et
   c'est vrai d'une dette de 960 € sur une fiche client. Le même geste mérite le
   même objet — il existait en deux exemplaires, dont aucun n'était posé. */
.rf-sorties { display: flex; flex-wrap: wrap; gap: 8px; }
.rf-sortie {
  border: 1px solid var(--line-control); background: var(--surface);
  border-radius: 980px; padding: 6px 13px; font-size: 12.5px; cursor: pointer;
  color: var(--text);
}
.rf-sortie:hover { border-color: var(--accent); color: var(--accent-profond); }
.rf-sortie:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- valeur absente --------------------------------------------------------
   Une seule forme, dans tout le produit : le cadratin. On en trouvait deux à
   trois cellules d'écart sur la liste des clients — « — » pour le courriel,
   « – » pour le téléphone. Et quand l'absence est une ANOMALIE plutôt qu'un
   vide (un client sans courriel ne recevra pas ses relances), c'est la seconde
   classe qui s'emploie : elle se voit, elle se clique. */
.rf-vide { color: var(--text-3); }
.rf-vide::before { content: "—"; }
.rf-manque {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; font-weight: 500;
  padding: 2px 8px; border-radius: 980px;
  background: var(--warn-soft); color: var(--warn);
  cursor: pointer;
}


/* Balance âgée : une seule barre, quatre segments, mêmes couleurs que la
   légende en dessous. */
.rf-aging { display: flex; height: 12px; gap: 2px; border-radius: var(--rf-2); overflow: hidden; }
.rf-aging__seg { display: inline-block; width: var(--rf-pct, 0%); }
.rf-aging__seg[data-tranche="1"] { background: var(--cat-4); }
.rf-aging__seg[data-tranche="2"] { background: var(--rf-late); }
.rf-aging__seg[data-tranche="3"] { background: var(--err); }
.rf-aging__seg[data-tranche="4"] { background: var(--rf-late-deep); }

/* ------------------------------------------------------------ panneau latéral */

.rf-drawer__scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(23, 32, 42, .34);
  animation: rf-fade .18s var(--ease) both;
}
.rf-drawer {
  position: fixed;
  inset: 0 0 0 auto;
  z-index: 41;
  width: min(468px, 94vw);
  background: var(--surface);
  box-shadow: var(--rf-drawer-shadow);
  display: flex;
  flex-direction: column;
  /* LE PANNEAU NE DÉFILE PLUS, SON CORPS DÉFILE.

     Il était en « overflow: auto » tout entier : lire les mentions faisait
     sortir le montant par le haut et les actions par le bas. Sur un quinze
     pouces, on remontait pour vérifier la somme et on redescendait pour agir —
     signalé par Eric le 24 septembre 2026.

     « hidden » et non « auto » : c'est le corps qui prend le défilement, et
     deux conteneurs défilants imbriqués se disputeraient la molette — la faute
     que ce fichier a déjà corrigée sur la liste des factures. */
  overflow: hidden;
  animation: rf-slide .24s var(--ease) both;
}

/* Les deux ancres du panneau ne quittent jamais l'écran : ce que vaut la pièce,
   et ce qu'on peut en faire. */
.rf-drawer__head,
.rf-drawer__actions { flex: none; }

.rf-drawer__corps {
  flex: 1;
  min-height: 0;          /* sans quoi un enfant flex refuse de rétrécir */
  overflow-y: auto;
  /* La molette ne remonte pas à la page une fois le corps au bout : le panneau
     flotte au-dessus d'une liste qui, elle, défilerait derrière. */
  overscroll-behavior: contain;
}

/* LES ENFANTS DU PANNEAU NE DÉPASSENT PAS LE PANNEAU.

   « .rf-drawer » est une colonne flex : ses enfants portent « min-width: auto »,
   qui vaut la largeur MINIMALE DE LEUR CONTENU. Un intitulé un peu long les
   rendait donc plus larges que le panneau, et le panneau plus large que l'écran.

   Invisible sur macOS et rouge en intégration, le 30 septembre 2026 : Linux n'a
   pas la police système d'Apple, son remplaçant rend le texte plus large, et les
   deux à dix-huit pixels de marge disparaissaient. Un défaut qui ne dépend que
   de la fonte installée est un défaut qui attendait le premier utilisateur au
   navigateur différent. */
.rf-drawer > * { min-width: 0; }

.rf-drawer__head { padding: var(--rf-5) var(--rf-6) var(--rf-4); border-bottom: 1px solid var(--line); }
.rf-drawer__amount { font-size: 29px; font-weight: 640; letter-spacing: -.025em; font-variant-numeric: tabular-nums; }
.rf-drawer__section { padding: var(--rf-4) var(--rf-6); border-bottom: 1px solid var(--line); display: flex; flex-direction: column; gap: 14px; }
.rf-drawer__actions {
  /* 26 px en bas ménageaient la fin d'un panneau qui défilait ; le bloc est
     désormais fixe et n'a plus de fin à ménager. */
  padding: var(--rf-4) var(--rf-6);
  display: flex; flex-direction: column; gap: 9px;
  /* Un filet et une ombre courte : sans eux, les boutons désormais fixes
     paraissent flotter sur le texte qui passe dessous. */
  border-top: 1px solid var(--line);
  box-shadow: 0 -6px 18px rgb(0 0 0 / 5%);
  background: var(--surface);
}

/* Chronologie du cycle de vie : la dernière étape est en gras, c'est là
   qu'on regarde. Le trait s'arrête avec elle. */
.rf-timeline__item { display: grid; grid-template-columns: 13px minmax(0, 1fr); gap: var(--rf-3); align-items: start; }
.rf-timeline__gutter { display: flex; flex-direction: column; align-items: center; gap: 2px; padding-top: var(--rf-1); }
.rf-timeline__dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.rf-timeline__dot[data-ton="ok"]   { background: var(--ok);   box-shadow: 0 0 0 3px var(--ok-soft); }
.rf-timeline__dot[data-ton="warn"] { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-soft); }
.rf-timeline__dot[data-ton="err"]  { background: var(--err);  box-shadow: 0 0 0 3px var(--err-soft); }
.rf-timeline__stem { width: 1px; flex: 1; min-height: 26px; background: var(--line-strong); }
.rf-timeline__item:last-child .rf-timeline__stem { display: none; }
.rf-timeline__body { display: flex; flex-direction: column; gap: 1px; padding-bottom: var(--rf-2); }
.rf-timeline__label { font-size: 13.5px; color: var(--text-2); }
.rf-timeline__item:last-child .rf-timeline__label { color: var(--text); font-weight: 600; }
.rf-timeline__at { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.rf-timeline__detail { font-size: 12.5px; color: var(--text-2); line-height: 1.45; }

/* LE DÉPLIANT D'UN GROUPE. Le résumé porte le compte et la somme ; ce qu'il
   replie garde ses références, parce que c'est ce qu'on vient chercher quand un
   client dit « je vous ai payé ». Même idiome que les dossiers d'e-reporting
   regroupés au lot 8. */
.rf-timeline__lot { margin-top: 5px; }
.rf-timeline__lot > summary {
  cursor: pointer;
  font-size: 12px;
  color: var(--accent-profond);
  padding: 3px 0;
  width: fit-content;
}
.rf-timeline__lot > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rf-timeline__lot[open] > summary { margin-bottom: 4px; }
.rf-timeline__ligne {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 12px;
  padding: 4px 0 4px var(--rf-3);
  border-left: 1px solid var(--line);
  font-size: 12.5px;
  color: var(--text-2);
}
.rf-timeline__ligne > .rf-timeline__detail { grid-column: 1 / -1; }

/* ------------------------------------------------- contrôle de conformité */

/* Le panneau collant de la saisie. Il ne dit pas « erreur » : il dit ce
   qui manque et pourquoi ça sera refusé. */
.rf-check { display: grid; grid-template-columns: 16px 1fr; gap: var(--rf-2); align-items: start; }
.rf-check__mark { font-size: 12px; line-height: 1.5; color: var(--ok); }
.rf-check[data-etat="manque"] .rf-check__mark { color: var(--warn); }
.rf-check__label { font-size: 13px; line-height: 1.4; }
.rf-check__hint { font-size: 12px; color: var(--text-3); line-height: 1.4; }

.rf-recap { position: sticky; top: 104px; }

/* --------------------------------------------------------------- téléphone */

/* Barre d'onglets : cinq cibles de 44 px minimum, et la marge basse de
   l'indicateur d'accueil réservée. */
.rf-tabbar {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2px;
  padding: var(--rf-2) 6px calc(var(--rf-2) + env(safe-area-inset-bottom, 22px));
  background: var(--surface);
  border-top: 1px solid var(--line);
}
.rf-tabbar__item {
  min-height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  font-size: 10.5px;
  color: var(--text-3);
}
.rf-tabbar__item[aria-current="page"] { color: var(--accent); font-weight: 600; }
.rf-screen__head { padding: calc(var(--rf-2) + env(safe-area-inset-top, 0px)) var(--rf-card-x) var(--rf-3); }

/* ------------------------------------------------------------- animations */

@keyframes rf-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes rf-slide { from { opacity: 0; transform: translateX(18px); } to { opacity: 1; transform: none; } }
@keyframes rf-rise { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }
.rf-enter { animation: rf-rise .28s var(--ease) both; }

@media (prefers-reduced-motion: reduce) {
  .rf-enter, .rf-drawer, .rf-drawer__scrim { animation: none; }
  .rf-chart__bar { transition: none; }
}


/* ==========================================================================
   Ajouts : tableau de bord à widgets, et document imprimable
   ========================================================================== */

/* ------------------------------------------------ tableau de bord à widgets */

/* Grille de six colonnes. Chaque widget porte sa largeur (span 2 / 4 / 6) et
   son rang (order) : le DOM ne bouge JAMAIS, seul l'ordre visuel change. Deux
   conséquences voulues — le glisser-déposer n'arrache aucun nœud, et une carte
   retirée reste dans le document (display:none), donc réactivable sans
   reconstruire quoi que ce soit. */
/* DOUZE COLONNES, ET NON SIX — 4 septembre 2026.

   Six colonnes ne savent pas exprimer le QUART de rangée, et la disposition
   pose quatre indicateurs : trois tenaient, le quatrième restait seul, et la
   règle d'orpheline plus bas l'étirait sur toute la largeur. Mesuré à 1 440 px
   sur un tableau de 1 120 : un « span 2 » rendu 1 120 px.

   Les divisions vivantes sont donc quart (3), tiers (4), moitié (6), deux tiers
   (8) et pleine (12) — les seules qui pavent douze. Le cinquième et le sixième
   n'y sont pas : une carte d'indicateur porte un montant, et un montant ne se
   coupe pas. */
.rf-board {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--rf-4);
  align-items: start;
}
.rf-widget {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  grid-column: span var(--rf-span, 12);
  order: var(--rf-order, 0);
  border-radius: var(--radius-lg);
  transition: opacity .15s var(--ease);
}
.rf-widget[hidden] { display: none; }
/* LA RÈGLE D'ORPHELINE A ÉTÉ RETIRÉE — 4 septembre 2026.

   Elle prenait la carte seule sur sa rangée et l'élargissait à toute la
   largeur, pour ne pas laisser de fond nu. Le produit l'a jugée laide et sa
   raison vaut mieux : une carte pleine largeur au milieu de colonnes casse le
   rythme de la grille, quel que soit son contenu.

   Le remède est en amont — douze colonnes, et un ordre par défaut qui pave —
   et non dans un rattrapage au rendu. Le sélecteur reste documenté ici plutôt
   que supprimé sans trace : c'est la deuxième fois que ce fond nu est signalé,
   et quelqu'un le réécrira. */

/* Une carte épinglée reçoit un order négatif : elle remonte en tête quel que
   soit son rang dans la liste. */
.rf-widget[data-epingle] { order: calc(var(--rf-order, 0) - 100); }

/* CE QUI EST ÉPINGLÉ SE DIT, UNE FOIS.

   L'épinglage remontait la carte en tête et rien ne le signalait : le lecteur
   voyait une carte première sans savoir si c'est lui qui l'y avait mise. Sa
   question n'est pas « cette carte porte-t-elle une marque ? » mais « pourquoi
   celle-ci est-elle en tête ? » — un intitulé de groupe y répond en un mot,
   sans ajouter un signe à décoder sur chaque carte.

   ÉCARTÉ : un filet coloré sur le bord de la carte. Ici un filet coloré dit un
   ÉTAT — en retard, réglé — et l'emprunter pour un RANGEMENT le ferait lire
   comme une alerte. C'est la faute que la palette a été refaite pour
   éliminer : la couleur ne sert qu'à l'information. Cet intitulé n'en emploie
   aucune.

   Le filet de clôture ferme le groupe : sans lui, on ne saurait pas où
   l'intitulé cesse de s'appliquer. */
.rf-board__groupe {
  grid-column: 1 / -1;
  order: var(--rf-order, 0);
  margin: 0;
  display: flex; align-items: center; gap: var(--rf-3);
  font-size: 11.5px; font-weight: 600;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-3);
}
.rf-board__groupe::after {
  content: ''; flex: 1; height: 1px; background: var(--line);
}
.rf-board__cloture {
  grid-column: 1 / -1;
  order: var(--rf-order, 0);
  height: 1px; background: var(--line);
  /* Le même écart au-dessus qu'en dessous : la gouttière de la grille joue
     déjà d'un côté, ce filet ne doit pas coller à la carte qu'il suit. */
  margin: var(--rf-2) 0;
}
.rf-widget[data-attrape] { opacity: .4; }

/* LA GRILLE TOMBE À UNE COLONNE SOUS 900 px.

   Ce bloc doit vivre ICI, après la déclaration ci-dessus, et non avec les
   autres requêtes média en tête de feuille : à spécificité égale, c'est la
   dernière règle du fichier qui gagne, requête média ou pas. Posée en tête,
   elle était écrasée par le « repeat(6, …) » qui la suit — la règle existait,
   le navigateur la voyait, et la grille restait à six colonnes. Mesuré : six
   colonnes de 43,8 px sur un écran de 375, et « 13 872,00 € » sur deux lignes.
   Une carte d'indicateur porte un montant, et un montant ne se coupe pas. */
/* ENTRE 901 ET 1279 px, LE QUART N'EXISTE PLUS.

   Mesuré le 4 septembre 2026, largeur d'un quart de rangée et coupures de
   montant, disposition par défaut, thème sombre :

     1600 px → 308 px   aucun montant coupé
     1440 px → 268 px   aucun
     1280 px → 228 px   aucun
     1100 px → 183 px   TROIS coupés, « 13 872,00 € » sur deux lignes
     1000 px → 158 px   trois
      940 px → 143 px   trois
      901 px → 133 px   trois

   Un montant coupé après « 13 872, » se lit comme un autre nombre : c'est la
   faute que ce dépôt a déjà payée en production sur le téléphone, et douze
   colonnes viennent de la rouvrir sur les portables.

   Le quart et le tiers passent donc à la moitié dans cette plage — deux
   indicateurs par rangée au lieu de quatre — et les deux tiers à la pleine
   largeur, sans quoi ils laisseraient un tiers de rangée inutilisable. La
   moitié et la pleine ne bougent pas.

   Par attribut et non par variable : une requête média ne sait pas comparer un
   « --rf-span ». */
@media (min-width: 901px) and (max-width: 1279px) {
  .rf-widget[data-span="3"],
  .rf-widget[data-span="4"] { grid-column: span 6; }
  .rf-widget[data-span="8"] { grid-column: span 12; }
}

@media (max-width: 900px) {
  .rf-board { grid-template-columns: minmax(0, 1fr); }
  .rf-widget { grid-column: 1 / -1; }
}

.rf-board[data-edition] .rf-widget { outline: 1px dashed var(--line-strong); outline-offset: 4px; cursor: grab; }
.rf-board[data-edition] .rf-widget:active { cursor: grabbing; }
.rf-widget__bar { display: flex; align-items: center; gap: var(--rf-2); padding: 5px 4px 1px; }
.rf-widget__poignee { font-size: 12px; color: var(--text-3); letter-spacing: .04em; }
.rf-widget__actions { margin-left: auto; display: flex; gap: 5px; }
.rf-widget__actions button {
  font-size: 11.5px;
  padding: 3px 8px;
  border-radius: 7px;
  border: 1px solid var(--line-control);
  background: var(--surface);
  color: var(--text-2);
  cursor: pointer;
}
.rf-widget__actions button[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
.rf-widget__actions button[data-retirer]:hover { border-color: var(--err); color: var(--err); }

/* Barre d'édition, et tiroir des cartes disponibles. */
.rf-board-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  background: var(--text);
  color: #fff;
  border-radius: var(--radius);
  padding: 13px var(--rf-4);
}
.rf-board-add {
  background: var(--surface);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-lg);
  padding: var(--rf-4) var(--rf-card-x);
  display: flex;
  flex-direction: column;
  gap: var(--rf-3);
}
.rf-board-add__choix {
  display: flex;
  align-items: center;
  gap: var(--rf-2);
  border: 1px solid var(--line-control);
  border-radius: var(--radius);
  padding: 9px 13px;
  font-size: 13.5px;
  background: var(--surface-2);
  cursor: pointer;
}
.rf-board-add__choix:hover { border-color: var(--accent); color: var(--accent); }

/* -------------------------------------------------------- document A4 */

/* La page est à l'échelle réelle (794 px = 210 mm à 96 ppp) et ne se
   redimensionne pas : c'est un document, pas un composant élastique. Le
   conteneur défile. */
/* Le bureau ne centre PAS la page : centrer un enfant plus large que son
   conteneur de défilement fait déborder des deux côtés, et le débordement
   gauche est inatteignable au défilement. Le centrage vit dans l'enveloppe
   intérieure, en min-width:max-content — il ne s'applique donc que quand la
   feuille tient dans la largeur. */
.rf-paper-desk { overflow-x: auto; overflow-y: hidden; }
.rf-paper-desk__inner {
  min-width: max-content;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
.rf-paper {
  width: 794px;          /* 210 mm à 96 ppp */
  min-height: 1123px;    /* 297 mm — sans quoi le margin-top:auto du pied ne
                            pousse rien et le pied légal remonte sous le corps */
  flex: none;
  background: #fff;
  color: var(--text);
  border-radius: 2px;
  box-shadow: 0 2px 6px rgba(23, 32, 42, .08), 0 22px 60px rgba(23, 32, 42, .12);
  padding: var(--rf-paper-pad, 46px 52px);
  display: flex;
  flex-direction: column;
  gap: 26px;
  font-family: var(--rf-paper-font, inherit);
}
.rf-paper__head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--rf-8); padding-bottom: var(--rf-5); border-bottom: var(--rf-paper-rule, 1px solid rgba(23,32,42,.14)); }
.rf-paper__mark { width: 46px; height: 46px; flex: none; border-radius: 10px; background: var(--rf-paper-accent, var(--accent)); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 19px; font-weight: 600; }
.rf-paper__kind { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; font-weight: 600; color: var(--rf-paper-accent, var(--accent)); }
.rf-paper__no { font-size: 19px; font-weight: 640; font-variant-numeric: tabular-nums; }
.rf-paper__label { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; font-weight: 600; color: var(--text-3); }
.rf-paper__lines-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 54px 62px 92px 62px 96px;
  gap: 10px;
  padding: 8px 10px;
  font-size: 10.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  font-weight: 600;
  background: var(--rf-paper-head-bg, var(--surface-2));
  color: var(--rf-paper-head-fg, var(--text-2));
  border-radius: var(--rf-paper-radius, 4px);
}
.rf-paper__line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 54px 62px 92px 62px 96px;
  gap: 10px;
  padding: 10px;
  align-items: baseline;
  font-size: 12.5px;
  border-bottom: 1px solid var(--line);
}
.rf-paper__net {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 10px 12px;
  border-radius: var(--rf-paper-radius, 4px);
  background: var(--rf-paper-total-bg, var(--surface-2));
  color: var(--rf-paper-total-fg, var(--text));
}
.rf-paper__qr {
  width: 62px;
  height: 62px;
  flex: none;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  opacity: .82;
  /* gabarit : remplacer par le QR réel (référence de paiement normalisée) */
  background-image:
    repeating-linear-gradient(90deg, var(--text) 0 3px, transparent 3px 7px),
    repeating-linear-gradient(0deg, var(--text) 0 3px, transparent 3px 7px);
}
.rf-paper__mentions { padding: var(--rf-3) 14px; background: var(--surface-2); border-radius: 4px; font-size: 10.5px; color: var(--text-2); line-height: 1.65; display: flex; flex-direction: column; gap: var(--rf-1); }
.rf-paper__foot { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--line); display: flex; justify-content: space-between; gap: var(--rf-5); font-size: 10px; color: var(--text-3); line-height: 1.6; }

/* Trois habillages, une seule page. Poser l'attribut sur .rf-paper. */
/* Habillage et typographie ne sont PAS des réglages utilisateur : le
   générateur de PDF ne porte que logo, couleur d'accent et lignes de pied de
   page. Ces variantes restent dans la feuille pour un choix d'éditeur, jamais
   exposé à l'écran — sinon l'aperçu cesse d'être l'exemplaire déposé. */
.rf-paper[data-habillage="bandeau"] {
  --rf-paper-rule: 3px solid var(--rf-paper-accent, var(--accent));
  --rf-paper-head-bg: var(--rf-paper-accent, var(--accent));
  --rf-paper-head-fg: #fff;
  --rf-paper-total-bg: var(--rf-paper-accent, var(--accent));
  --rf-paper-total-fg: #fff;
}
.rf-paper[data-habillage="minimal"] {
  --rf-paper-pad: 58px 62px;
  --rf-paper-rule: none;
  --rf-paper-head-bg: transparent;
  --rf-paper-head-fg: var(--text-3);
  --rf-paper-radius: 0;
  --rf-paper-total-bg: transparent;
}
.rf-paper[data-habillage="minimal"] .rf-paper__mark { border-radius: 50%; }
.rf-paper[data-serif] { --rf-paper-font: "Iowan Old Style", Palatino, Georgia, serif; }

/* ---- CE QUE LA LIGNE ATTEND DE VOUS -------------------------------------------
   Sous la pastille d'état, dans la même colonne : le geste se pose à côté de ce
   qui le motive. Discrète — c'est l'encre, pas un aplat — parce qu'il y en a une
   par ligne concernée et que quatorze aplats sur vingt-cinq lignes feraient un
   mur. Trente-deux pixels de haut : le plancher d'une cible au doigt, tenu
   partout ailleurs. */
.rf-suite {
  /* En BLOC : l'action passe sous la pastille au lieu de se coller à elle. Vue
     collée à un cadratin sur une ligne d'avoir — « — Rembourser » — ce qui
     lisait comme une seule phrase absurde. */
  display: flex;
  align-items: center;
  min-height: 32px;
  margin-top: 2px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12.5px;
  font-weight: 560;
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: pointer;
}
.rf-suite:hover { color: var(--accent-profond); }

/* LE SECOND GESTE EST EN RETRAIT. Sur une facture en retard, la relance est ce
   que l'écran RECOMMANDE et l'encaissement ce qu'il rend possible : deux liens
   de même poids feraient hésiter là où il n'y a pas d'hésitation à avoir. Il
   garde sa cible de trente-deux pixels — discret ne veut pas dire petit. */
.rf-suite--second {
  color: var(--text-3);
  text-decoration-color: var(--line-strong);
  font-weight: 500;
  margin-top: 0;
}
.rf-suite--second:hover { color: var(--text); text-decoration-color: currentColor; }
.rf-suite:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* ---- LA TÊTE D'UNE LISTE -----------------------------------------------------
   Ce qui rendait cet écran fade n'était pas son dessin, c'était son ordre :
   352 px de titre, de sous-titre, d'onglets et de quatre commandes de filtre
   avant la première donnée, sur une fenêtre de 900 — soit 5,6 lignes visibles
   pour 46 factures. La page ouvrait sur du meuble.

   Trois nombres nommés, et le troisième n'apparaît que s'il vaut quelque chose.
   Ils suivent le FILTRE, jamais l'exercice : une somme qui ignore son filtre
   est un mensonge d'écran, et le serveur les rend déjà tous les trois. */
/* La barre porte les deux : à gauche ce que la liste totalise, à droite
   l'instrument qui la réduit. Elles occupaient deux rangées, dont une sans
   aucune donnée. */
.rf-tete {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--rf-3);
  margin-bottom: var(--rf-4);
}
.rf-tete__outils { display: flex; align-items: flex-end; gap: 10px; margin-left: auto; }
.rf-tete__outils .filter-search { flex: 0 1 260px; min-width: 180px; }
/* Une boîte de pastilles vide ne prend pas la place d'une rangée. */
.rf-tete .rf-chips:empty { display: none; }
.rf-tete .rf-chips { flex-basis: 100%; }

.rf-somme {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--rf-2) var(--rf-8);
  padding: var(--rf-3) var(--rf-4);
  border-radius: var(--radius-lg);
  background: var(--rf-ink-soft);

  /* LA BANDE ÉPOUSE SES CHIFFRES, elle ne s'étire plus pour remplir.

     Mesuré le 23 septembre 2026 à 1 440 px : 841 px de bande pour 344 px de
     contenu, soit 481 px de gris vide — 57 %. Une surface colorée qui occupe
     la largeur annonce qu'elle porte quelque chose sur toute sa longueur ;
     celle-ci n'en portait rien sur plus de la moitié.

     « flex: 0 1 auto » suffit : elle peut encore rétrécir sous la pression,
     elle ne grandit plus sans raison. Le gris mort tombe à 16 px, le
     rembourrage. */
  flex: 0 1 auto; min-width: 0;
}
.rf-somme__poste { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.rf-somme__quoi {
  font-size: 11.5px; font-weight: 560; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-3);
}
.rf-somme__combien {
  font-size: 19px; font-weight: 620; letter-spacing: -.015em;
  color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.rf-somme__combien[data-sens="du"]         { color: var(--warn); }
.rf-somme__combien[data-sens="rembourser"] { color: var(--owed); }
/* La population, à côté du nombre et jamais sous un autre nombre : « le compte
   et le montant viennent de la même population, ou le compte ne s'affiche
   pas » (README §25). */
.rf-somme__combien small { font-size: 12.5px; font-weight: 500; color: var(--text-3); }

@media (max-width: 900px) {
  .rf-somme { gap: var(--rf-2) var(--rf-5); padding: var(--rf-3); }
  .rf-somme__combien { font-size: 17px; }
  /* La barre s'empile : l'instrument prend toute la largeur au lieu de rester
     collé à droite avec un vide à sa gauche. */
  .rf-tete__outils { margin-left: 0; flex-basis: 100%; }
  .rf-tete__outils .filter-search { flex: 1 1 auto; }
}

/* ---- LES COMMANDES DE FILTRE CESSENT D'OUVRIR LA PAGE ------------------------
   Quatre contrôles et un « Réinitialiser » déployés en permanence, c'est 90 px
   de meuble à chaque ouverture de l'écran — pour un réglage qu'on touche une
   fois sur dix visites. Ils se replient derrière un bouton qui PORTE LE NOMBRE
   de filtres actifs.

   La règle qui rend le repli honnête : un filtre actif ne se cache jamais. Ce
   qui agit sur la liste est écrit en pastilles à côté du bouton, chacune
   retirable d'un clic. Replier des commandes est permis ; replier un ÉTAT ne
   l'est pas — une liste filtrée qui ne dit pas qu'elle l'est fait chercher des
   factures qui sont là. */
.rf-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.rf-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 3px 5px 3px 11px;
  border-radius: 980px;
  background: var(--accent-soft); color: var(--accent-profond);
  font-size: 12.5px; font-weight: 560; white-space: nowrap;
}
.rf-chip__retirer {
  all: unset; cursor: pointer;
  display: grid; place-items: center;
  width: 17px; height: 17px; border-radius: 50%;
  font-size: 13px; line-height: 1;
  color: var(--accent-profond);
}
.rf-chip__retirer:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.rf-chip__retirer:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Le compte des filtres actifs sur le bouton, en pastille. */
.rf-compte {
  display: inline-grid; place-items: center;
  min-width: 17px; height: 17px; padding: 0 4px; margin-left: 7px;
  border-radius: 980px;
  background: var(--accent); color: #fff;
  font-size: 11.5px; font-weight: 620; font-variant-numeric: tabular-nums;
}

/* ---- LE RANG DES CHOSES DANS UNE LIGNE ---------------------------------------
   Sur un écran de créances, ce qu'on vient lire est le montant. Il portait
   14,5 px/600 quand le nom du client portait 14/400 : l'écart ne se voyait pas,
   donc l'œil n'avait pas de point d'entrée et balayait tout. */
.rf-amount--ligne {
  font-size: 16.5px; font-weight: 640; letter-spacing: -.015em;
  font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text);
}
/* Un montant qui n'attend plus rien cesse de crier : réglé, il rend l'encre. */
.rf-amount--ligne[data-solde] { font-weight: 560; color: var(--text-2); }

/* Le numéro descend d'un rang : encre secondaire, 13 px, chiffres tabulaires.
   Il gardait le poids du nom du client sur un écran où l'on cherche un client et
   un montant — deux entrées de regard valent zéro entrée de regard. */
.rf-table__row .rf-num--id { font-size: 13px; font-weight: 540; color: var(--text-2); }

/* L'ÉTAT D'ARGENT AU BORD GAUCHE. Une pastille dans la cinquième colonne se
   cherche ligne à ligne ; un liseré se balaye d'un seul mouvement de l'œil.
   Le bord ne remplace pas la pastille — il dit « regarde ici », elle dit quoi. */
.rf-table__row[data-argent] { position: relative; }
.rf-table__row[data-argent]::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 2px; border-radius: 980px; background: transparent;
}
.rf-table__row[data-argent="retard"]::before     { background: var(--err); }
.rf-table__row[data-argent="partiel"]::before    { background: var(--warn); }
.rf-table__row[data-argent="rembourser"]::before { background: var(--owed); }

/* L'ÉCHÉANCE EN CLAIR. « À échoir » ne dit pas quand, et c'est la seule chose
   qui décide si l'on relance aujourd'hui ou dans trois semaines. La donnée
   était là (dueDate) et l'écran ne la lisait pas. */
.rf-echeance { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.rf-echeance[data-proche] { color: var(--warn); }

/* ---- LA FICHE CLIENT : DEUX SENS, JAMAIS UN NET -----------------------------
   L'en-tête alignait cinq nombres du même poids — facturé, reste dû, à
   rembourser, nombre de factures, client depuis — dont deux qui vont dans des
   sens OPPOSÉS et rien pour le dire. Le lecteur ne pouvait pas voir d'un coup
   d'œil que 1 000 € rentrent et que 960 € sortent.

   Deux pôles, nommés par leur sens, et le second n'existe que s'il vaut quelque
   chose. La grille les met côte à côte et laisse le premier prendre toute la
   largeur quand il est seul : un pôle unique dans une demi-largeur laisserait
   croire qu'il manque quelque chose. */
.rf-solde {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(238px, 1fr));
  gap: var(--rf-3);
}
.rf-solde__pole {
  display: flex; flex-direction: column; gap: 5px;
  padding: var(--rf-3) var(--rf-4);
  border-radius: var(--radius-lg);
  background: var(--rf-ink-soft);
  border-left: 3px solid var(--line-strong);
  min-width: 0;
}
/* Le sens se dit trois fois : par le libellé, par le liseré, et par la teinte
   du nombre. Une seule des trois suffirait à un œil ; aucune ne suffit à tous. */
.rf-solde__pole[data-sens="entrant"] { border-left-color: var(--warn); }
.rf-solde__pole[data-sens="entrant"][data-retard] { border-left-color: var(--err); }
.rf-solde__pole[data-sens="sortant"] { border-left-color: var(--owed); }
.rf-solde__sens {
  font-size: 11.5px; font-weight: 560; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-3);
}
.rf-solde__combien {
  font-size: 24px; font-weight: 640; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text);
}
.rf-solde__pole[data-sens="sortant"] .rf-solde__combien { color: var(--owed); }
/* « au moins » — une borne inférieure se lit comme une borne, pas comme un
   ornement : même encre que le nombre, taille du libellé. */
.rf-solde__borne {
  font-size: 12px; font-weight: 560; letter-spacing: 0;
  text-transform: uppercase; opacity: .75;
}
/* Le retard est un SEGMENT du dû, pas un poste de plus : la règle vient du
   §25, où la même faute mettait le même montant sur trois cartes. */
.rf-solde__dont { font-size: 12.5px; color: var(--text-2); line-height: 1.4; }
.rf-solde__dont b { color: var(--err); font-variant-numeric: tabular-nums; }
.rf-solde__pole .rf-sorties { margin-top: 3px; }

/* Le reste des chiffres tombe au rang de contexte : ils ne décident de rien,
   ils situent. Une ligne, en encre secondaire. */
.rf-contexte {
  display: flex; flex-wrap: wrap; gap: var(--rf-2) 22px;
  font-size: 12.5px; color: var(--text-2);
}
.rf-contexte b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }

/* LA TÊTE RESTE, LA LISTE DÉFILE. Mesuré sur le vidage réel : 1 017 px de corps
   dans un dialogue de 684. Les deux pôles — c'est-à-dire la raison d'ouvrir la
   fiche — sortaient de l'écran dès la troisième pièce. Le produit avait déjà ce
   motif (#drillBody), il n'était pas appliqué ici. */
/* Le dialogue est une COLONNE bornée à la fenêtre, et non un bloc qui pousse.
   Une borne en « vh » sur la seule liste ne suffisait pas : la tête, le
   rembourrage et la rangée d'actions s'ajoutaient par-dessus, le dialogue
   dépassait, et c'est par le HAUT qu'il sortait — le nom du client, sur l'écran
   qu'on ouvre pour savoir de qui il s'agit. La liste prend ce qui reste, quoi
   que mesure la tête. */
/* « [open] » N'EST PAS UNE PRÉCAUTION, C'EST LA CONDITION.

   Un <dialog> fermé est masqué par UNE SEULE règle, celle du navigateur :
   « dialog:not([open]) { display: none } ». Un « display: flex » posé sur une
   classe la surclasse — et le dialogue fermé devient visible, son contenu coule
   dans la page. Constaté en production le 4 septembre : la rangée « Modifier la
   fiche / Fermer » flottait sous le tableau de bord, à quatre-vingt-quinze
   pixels de haut, sur l'écran d'accueil.

   Mes quinze mesures ne pouvaient pas le voir : elles n'observaient la fiche
   qu'OUVERTE, et un bloc de plus dans le flux ne déborde de rien. */
.rf-dialogue-colonne[open] {
  display: flex; flex-direction: column;
  max-height: 88vh; min-height: 0;
}
/* « min-width: 0 » SUR TOUTE LA CHAÎNE FLEX, et ce n'est pas une précaution
   d'école : sans lui, un enfant de conteneur flex ne peut pas rétrécir sous la
   largeur de son contenu. À 375 px, la ligne d'identité (SIREN, SIRET, numéro de
   TVA en chiffres tabulaires) poussait le dialogue à 352 px de contenu dans 283
   disponibles, et cinq blocs sortaient de l'écran. C'est la faute que cette
   feuille documente déjà pour la ligne d'échéance — même cause, autre écran. */
.rf-dialogue-colonne > :first-child {
  flex: 1 1 auto; min-height: 0; min-width: 0; display: flex;
}
.rf-dialogue-colonne > .dialog-actions { flex: none; }

.rf-fiche {
  display: flex; flex-direction: column; gap: var(--rf-4);
  min-height: 0; min-width: 0; flex: 1;
}
.rf-fiche__tete {
  display: flex; flex-direction: column; gap: var(--rf-3);
  flex: none; min-width: 0;
}
/* Le tableau des pièces défile DANS sa boîte, sur les deux axes. À 375 px, ses
   quatre colonnes mesurent plus que le dialogue : sans le défilement
   horizontal, c'est le dialogue entier qui s'élargit et sort de l'écran. Même
   arbitrage qu'à la liste des factures — un tableau qui défile coûte moins cher
   qu'un tableau écrasé. */
.rf-fiche__liste { flex: 1 1 auto; overflow: auto; min-height: 120px; min-width: 0; }
/* UN PLANCHER DE LARGEUR, POUR QUE LE DÉFILEMENT EXISTE.

   Le produit a tranché avant moi, et sa décision est bonne : les pièces d'une
   fiche tiennent en quatre colonnes courtes, ce tableau reste donc un tableau
   sous 900 px au lieu de s'éclater en cartes, et il « déborde en défilant
   plutôt qu'en s'éclatant » (app.css, § mobile). Il portait même le conteneur
   prévu pour cela, « .fiche-defile ».

   Sauf que rien ne forçait la largeur du tableau : à « width: 100% » dans une
   boîte de 295 px, il ne débordait pas — il S'ÉCRASAIT, « 14 juil. 2026 » sur
   trois lignes et le numéro de pièce dans le montant. Le conteneur à défilement
   ne défilait jamais. Défaut antérieur à cette refonte, invisible sans mesure.

   540 px est MESURÉ, pas choisi : à 430, 470 et 500, soixante-huit cellules
   dépassent encore deux lignes ; à 540, aucune. */
.rf-fiche__liste table { min-width: 540px; }

@media (max-width: 900px) {
  /* PAS DE TÊTE FIXE SUR UN TÉLÉPHONE. Les deux pôles empilés mesurent déjà
     plus de la moitié de la hauteur utile : garder la tête laisserait quatre-
     vingts pixels à la liste, et la rangée d'actions passait par-dessus. Le
     dialogue entier défile, et l'ordre de lecture fait le reste — les deux
     pôles sont en haut, c'est-à-dire là où le pouce arrive. */
  .rf-dialogue-colonne > :first-child { overflow-y: auto; }
  /* Le défilement HORIZONTAL reste — c'est lui qui porte le plancher ci-dessus.
     Seul le vertical rend la main : sur un téléphone, c'est le dialogue entier
     qui défile. */
  .rf-fiche__liste { overflow-x: auto; overflow-y: visible;
                     max-height: none; min-height: 0; flex: none; }
}
/* L'en-tête de la liste tient bon pendant qu'elle défile : sans lui, une colonne
   de montants sans intitulé au bout de dix lignes. */
.rf-fiche__liste thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface);
  box-shadow: inset 0 -1px 0 var(--line);
}

/* ---- LA CIBLE DU MENU DE LIGNE ------------------------------------------------
   35 × 26 px, et son bord droit exactement sur le rembourrage de la carte :
   trop petit pour un doigt (le plancher tenu partout ailleurs est 32), et
   collé au bord au point qu'on ne sait plus s'il appartient à la ligne ou au
   cadre. La colonne passe à 40 px dans le balisage ; ici, la cible grandit et
   se décale du bord. */
/* Le chemin passe par « .row-actions », que le rendu interpose : un sélecteur
   en enfant direct manquait la cible, et la mesure me l'a dit avant que je
   l'annonce corrigée. */
.rf-table__row > td:last-child .row-menu > summary {
  min-width: 32px;
  min-height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 9px;
  margin-right: -4px;
}

/* ---- LE PANNEAU FLOTTE, IL NE MURE PAS ---------------------------------------
   Il occupait les trois bords de la fenêtre, rayon zéro : un mur qui tombe sur
   la page. Douze pixels et le rayon des cartes suffisent à le poser DESSUS —
   et l'ombre, qui existait déjà, se met enfin à servir.

   Sous 900 px il reprend toute la place : sur un téléphone, une feuille qui
   flotte perd de la surface utile sans rien gagner en lisibilité. */
.rf-drawer {
  inset: var(--rf-3) var(--rf-3) var(--rf-3) auto;
  border-radius: var(--radius-lg);
}
@media (max-width: 900px) {
  .rf-drawer { inset: 0 0 0 auto; border-radius: 0; }
}

/* ---- LE BANDEAU DES MONTANTS ------------------------------------------------
   Trois ou quatre nombres qu'on lit ENSEMBLE — hors taxes, TVA, ce qui reste.
   Empilés en couples, ils occupaient huit lignes ; côte à côte ils en occupent
   deux et se comparent d'un regard. « auto-fit » plutôt qu'un nombre fixe de
   colonnes : le quatrième nombre n'apparaît que sur une facture soldée de
   travers, et la rangée ne doit pas garder sa place vide le reste du temps. */
.rf-sommes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 10px 16px;
}
.rf-sommes > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rf-sommes__cle {
  font-size: 11.5px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-3);
}
.rf-sommes b { font-size: 15px; font-weight: 600; }

/* La note de conservation : lisible, et jamais dans le chemin du regard. */
.rf-archive { margin: 0; font-size: 12px; }

/* ---- LES COMMANDES DU PANNEAU ----------------------------------------------

   LE BOUTON FERMER EST CARRÉ. Il portait la classe « ghost » et donc le
   rembourrage d'un bouton à libellé : 46 × 38 px pour une croix, mesuré le
   24 septembre 2026. Un bouton d'icône se dimensionne par sa CIBLE, pas par son
   texte — 40 px de côté, la croix centrée et lisible. */
.rf-fermer {
  flex: none;
  width: 40px; height: 40px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 22px; line-height: 1;
  color: var(--text-3);
  background: transparent;
  border: 1px solid transparent; border-radius: var(--radius);
  cursor: pointer;
  transition: background .18s var(--ease), color .18s var(--ease);
}
.rf-fermer:hover { background: var(--surface-2); color: var(--text); }
.rf-fermer:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 2px; }

/* LES DEUX SECONDAIRES : côte à côte tant qu'ils tiennent, empilés sinon.
   « Aperçu du PDF » passait à la ligne dans un panneau étroit et les deux
   boutons doublaient de hauteur. Le bloc d'actions étant fixe, ce qu'il gagne
   en hauteur, il le prend au corps. */
.rf-duo { display: flex; gap: 8px; flex-wrap: wrap; }
.rf-duo > * { flex: 1 1 128px; white-space: nowrap; }

/* ---- LE PANNEAU SUR TÉLÉPHONE ----------------------------------------------
   La tête et les actions sont désormais fixes, ce qui vaut encore plus ici :
   le pouce atteint le bas de l'écran sans défiler. Deux réglages s'imposent.

   LA ZONE SÛRE. Sur un iPhone, le bas de l'écran est pris par la barre de
   navigation : un bouton collé à zéro passe dessous et devient intouchable.
   « max() » garde le rembourrage de bureau quand il n'y a pas de zone sûre.

   LA TÊTE RÉTRÉCIT. Montant, cadre, dates et nature occupaient près du tiers
   d'un écran de téléphone avant qu'une seule mention ne paraisse — sur un
   panneau qui existe pour montrer ces mentions. */
@media (max-width: 900px) {
  .rf-drawer__actions {
    padding-bottom: max(var(--rf-4), env(safe-area-inset-bottom, 0px));
  }
  .rf-drawer__head { padding: var(--rf-4) var(--rf-4) var(--rf-3); }
  .rf-drawer__section { padding: var(--rf-4); }
  .rf-drawer__actions { padding-left: var(--rf-4); padding-right: var(--rf-4); }
  .rf-drawer__amount { font-size: 25px; }
}

/* Impression : seule la page sort, à sa taille réelle, sans les écrans. */
@media print {
  html, body { background: #fff; }
  body * { visibility: hidden; }
  .rf-paper, .rf-paper * { visibility: visible; }
  .rf-paper { position: absolute; left: 0; top: 0; box-shadow: none !important; margin: 0 !important; }
  @page { size: A4; margin: 0; }
}

/* ==========================================================================
   LA LISTE DES FACTURES SUR TÉLÉPHONE — 5 septembre 2026
   ==========================================================================

   « Horrible », et c'était mesuré avant d'être dit : UNE FACTURE PAR ÉCRAN.

     hauteur d'une ligne     290 px   sur un écran de 375
     liste de 25 factures  8 230 px   soit vingt-deux hauteurs d'écran

   La cause est un travers connu : le tableau converti en cartes GARDE la
   structure du tableau. La règle générique d'app.css empile les six cellules et
   réimprime devant chacune son intitulé de colonne — NUMÉRO, CLIENT, TTC,
   TRANSMISSION, RÈGLEMENT — vingt-cinq fois, en majuscules. L'œil lit les
   étiquettes et pas les données.

   Et une épreuve d'écran disait « empilé, débordements : aucun ». Elle
   vérifiait que le MÉCANISME fonctionne, jamais que le résultat soit
   utilisable. Elle mesure la hauteur depuis.

   DEUX COLONNES, ET NON TROIS. Premier jet à trois : les contenus n'ont pas des
   largeurs comparables, et l'argent — le plus large — dictait la colonne de
   droite pour TOUTES les rangées. Mesuré 88 / 49 / 110 px : une colonne du
   milieu à quarante-neuf pixels, où devaient tenir une pastille et son bouton.
   Trois répartitions essayées, trois échecs — l'une faisait déborder l'écran
   quarante-sept fois, l'autre reportait ailleurs les quarante pixels de la
   cible du tiroir.

   Une part souple à gauche, une part fixe à droite, sur chacune des trois
   rangées — la forme que le contenu appelait :

     1   le client                              le montant
     2   le numéro et la date                   l'état de dépôt
     3   le règlement et son action             le tiroir

   LES INTITULÉS PARTENT, sauf un. Un nom, une date, un numéro et un montant se
   reconnaissent à leur forme ; « TTC » reste, parce qu'un montant sans base se
   lit de deux façons — et il reste par « data-label », donc traduit, et non
   écrit dans cette feuille.

   Cette règle ne vaut QUE pour cette liste : la règle générique sert tous les
   autres tableaux du produit, et la remplacer partout sans les avoir mesurés
   serait refaire la faute en plus grand. */
@media (max-width: 900px) {
  .rf-table--factures tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: 4px var(--rf-3);
    padding: var(--rf-3) var(--rf-4);
  }

  .rf-table--factures td { display: block; padding: 0; text-align: left; min-width: 0; }
  .rf-table--factures td::before { display: none; }

  /* 1 — le client, puis le montant. */
  .rf-table--factures td:nth-child(2) { grid-column: 1; grid-row: 1; }
  .rf-table--factures td:nth-child(3) {
    grid-column: 2; grid-row: 1;
    display: flex; align-items: baseline; justify-content: flex-end; gap: 4px;
  }
  /* Le seul intitulé qui reste, et il passe APRÈS le montant : « 1 080,00 € TTC »
     se lit d'un trait, « TTC 1 080,00 € » fait buter le regard sur trois lettres. */
  .rf-table--factures td:nth-child(3)::before {
    display: block; order: 2;
    font-size: 10.5px; font-weight: 500; letter-spacing: .03em;
    text-transform: uppercase; color: var(--text-3);
  }

  /* LE CADRE DE FACTURATION QUITTE LA LISTE SUR TÉLÉPHONE. « Double ·
     définitive après acompte » sous le nom du client : du contexte
     réglementaire, qu'on lit quand on le CHERCHE et jamais en parcourant. Il
     coûtait une ligne entière sur chaque facture qui en porte un, et il reste à
     un doigt, sur la fiche. Même arbitrage que « .on-wide » applique déjà à la
     ventilation HT/TVA, écrit ici parce que ce n'est pas une cellule mais une
     sous-ligne. */
  .rf-table--factures td:nth-child(2) .rf-cell__sub { display: none; }

  /* 2 — le numéro et sa date à gauche, l'état de dépôt à droite. */
  .rf-table--factures td:nth-child(1) {
    grid-column: 1; grid-row: 2;
    /* « flex-direction: row » EXPLICITEMENT : la cellule porte « .rf-cell », qui
       l'empile en colonne. Sans ce renversement, le numéro et la date restaient
       l'un sous l'autre — vingt pixels de trop par facture. */
    display: flex; flex-direction: row;
    align-items: baseline; justify-content: flex-start;
    flex-wrap: wrap; gap: 2px var(--rf-2);
  }
  .rf-table--factures td:nth-child(4) {
    grid-column: 2; grid-row: 2;
    display: flex; align-items: center; justify-content: flex-end; gap: 6px;
  }
  /* L'ÉTAT NE SE REPLIE PLUS. « Paiement déclaré », « Mise à disposition » :
     onze libellés sur vingt-cinq tenaient sur deux lignes, chacun coûtant
     dix-huit pixels à sa facture. Il se tronque — le mot entier reste sur la
     fiche. */
  .rf-table--factures td:nth-child(4) .rf-life__label {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    display: block; min-width: 0;
  }

  /* 3 — le règlement et son action à gauche, le tiroir à droite.

     LE TIROIR RESTE, et il coûte ses quarante pixels de cible. Il a été
     envisagé de le masquer, la ligne entière ouvrant déjà le panneau. Écarté
     après lecture de son contenu : il porte le PDF, le XML, l'avoir,
     l'imputation et la correction d'un encaissement. Le retirer n'aurait pas
     allégé l'écran, il aurait retiré des gestes au téléphone. */
  .rf-table--factures td:nth-child(5) {
    grid-column: 1; grid-row: 3;
    display: flex; align-items: center; gap: var(--rf-2); flex-wrap: wrap;
  }
  /* L'ÉCHÉANCE LOINTAINE SE TAIT SUR TÉLÉPHONE, LA PROCHE RESTE.

     Cette cellule montait à 93 px — la plus haute de la ligne : une pastille,
     une phrase « Échéance dans N j », et parfois un bouton d'action. La phrase
     existe pour une raison que le code énonce lui-même : « À échoir » ne dit
     pas QUAND, et c'est la seule chose qui décide si l'on relance aujourd'hui.

     Elle porte donc déjà « data-proche » quand l'échéance est à sept jours ou
     moins. C'est cette moitié-là qui décide, et c'est celle qu'on garde ; une
     échéance à quarante jours ne change rien à la journée de personne, et son
     détail reste sur la fiche. */
  .rf-table--factures td:nth-child(5) .rf-echeance:not([data-proche]) { display: none; }
  .rf-table--factures td:nth-child(6) {
    grid-column: 2; grid-row: 3; text-align: right;
  }
}

/* ═══════════ LA COMPLÉTION DES PRESTATIONS ═══════════

   Elle remplace une <datalist> native dont le filtrage variait selon le
   navigateur — littéral sur les accents partout, borné au DÉBUT du libellé sur
   Safari. Mesuré le 23 septembre 2026 : quinze entrées accentuées sur
   vingt-six, donc plus de la moitié d'un catalogue inatteignable au clavier
   courant, et « mensuelle » qui proposait « Maintenance mensuelle » sur un
   navigateur et rien sur l'autre.

   Posée sur le corps du document et positionnée à la main : dans la grille des
   lignes, un menu en flux pousserait les colonnes voisines à chaque frappe. */
.completion {
  position: absolute; z-index: 60;
  max-height: 264px; overflow-y: auto;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius-md);
  box-shadow: 0 12px 32px rgb(0 0 0 / 14%);
  padding: 4px;
}
.completion__choix {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 7px 9px; border-radius: var(--radius-sm);
  cursor: pointer; font-size: 13.5px;
}
/* Le survol ET la désignation au clavier portent la MÊME marque : deux teintes
   pour un seul état feraient croire à deux sélections concurrentes. */
.completion__choix:hover,
.completion__choix.is-actif { background: var(--rf-ink-soft); }

.completion__quoi { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.completion__combien {
  flex: none; font-size: 12px; color: var(--text-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Le silence était le second défaut : une saisie sans correspondance ne disait
   rien, et rien ne distinguait « aucun article » de « c'est cassé ». */
.completion__vide { padding: 9px; font-size: 13px; color: var(--text-3); }
