/* app/v2/styles/revolut.css — LA SEULE FEUILLE DE LA V2.
 *
 * ┌──────────────────────────────────────────────────────────────────────┐
 * │ Houssem, 07/09 : « DIRECTION DÉFINITIVE : REVOLUT. Aucune des trois  │
 * │ directions n'est retenue ; on ne fait plus de variantes. »           │
 * │                                                                      │
 * │ LE CONTRAT EST DANS `ops/references/revolut.md` — dix éléments et    │
 * │ leur traduction. Un écran qui s'en écarte est un défaut, même s'il   │
 * │ « rend bien ». Quand on hésite, on va là, on ne réinvente pas.       │
 * └──────────────────────────────────────────────────────────────────────┘
 *
 * ⚠️ ELLE NE CONSOMME PAS `app/theme/tokens.css`. C'est la consigne du 06/09 :
 * la v2 n'importe aucune feuille de style de `app/`. Voir
 * `app/v2/SYSTEME-A-PART.md`.
 */

/* ═══════════════════════════ LES JETONS — MODE CLAIR ════════════════════ */
:root {
  /* La marque. `#16324E` est la SEULE couleur de marque du produit, et
     `theme-color` la porte à l'identique dans `app/` et `landing/`. */
  --navy:        #16324E;
  --navy-clair:  #22486E;
  --navy-fond:   #E8EDF3;

  /* ⚠️ `--ambre` A ÉTÉ ASSOMBRI LE 12/09, ET C'EST UNE MESURE, PAS UN GOÛT.
     `scripts/v2/voir-app.mjs` a mesuré #A0620C sur #FBEEDC à 4,32:1 — sous le
     seuil AA de 4,5:1 — sur les 12 avatars `.v2-rond--ambre` de Clients,
     Pilotage, Dossiers et Factures. #935B0B mesure 4,91:1 sur `--ambre-fond`
     et 5,61:1 sur blanc (le bouton primaire, qui consomme le même jeton) :
     un seul jeton corrigé répare les quatre écrans à la fois. Reste
     distinguable de `--ambre-fonce` (#8A5309, hover) et des trois autres
     tons (`--navy`, `--du`, `--encaisse`) — vérifié à l'œil sur un rond de
     32 px, aux deux thèmes. Le thème sombre (#E0A048 sur #33270F, 6,47:1)
     n'était pas en cause. */
  --ambre:       #935B0B;
  --ambre-fonce: #8A5309;
  --ambre-fond:  #FBEEDC;

  /* La sémantique de l'argent — jamais un jugement, toujours un SENS DE FLUX.
       encaisse : l'argent ENTRE — vert, et lui seul
       du       : l'argent est ATTENDU — rouge
       neutre   : un total, ni l'un ni l'autre */
  --encaisse:      #12683F;
  --encaisse-fond: #E4F4EC;
  --du:            #A3170E;
  --du-fond:       #FCE9E7;
  --neutre:        var(--encre);

  --fond:      #F4F5F7;
  --surface:   #FFFFFF;
  --creux:     #EEF0F3;
  --bord:      #E4E7EC;

  --encre:     #0E1726;
  --doux:      #58626F;
  /* ⚠️ `--faible` A ÉTÉ ASSOMBRI LE 07/09, ET C'EST UNE MESURE, PAS UN GOÛT.
     À #8B94A1 il rendait 2,81:1 sur le fond — Lighthouse l'a attrapé sur les
     titres de rangée (« À FAIRE », « RESTE À ENCAISSER »), qui sont
     précisément ce qu'on lit en premier. Le seuil AA est 4,5:1 pour du texte
     de cette taille. #5F6874 mesure 5,18:1 sur le fond et 5,65:1 sur une
     surface — le gris reste discret, il cesse d'être illisible.
     *Un gris élégant illisible en plein soleil est un défaut, pas un parti
     pris* (skill `frontend-design`). */
  --faible:    #5F6874;
  --inverse:   #FFFFFF;

  --r-carte:   18px;
  --r-tuile:   14px;
  --r-pilule:  999px;

  --barre:     72px;    /* la latérale FINE du bureau — pas un back-office */
  --entete:    60px;
  --colonne:   760px;   /* la même app au bureau, pas une app plus large */
  --cible:     48px;    /* plancher 44 px (skill) — on se donne 4 px de marge */
  /* Le débord du carrousel sur le bord de l'écran. Il vit ici parce que
     QUATRE règles en dépendent (marge, remplissage, ancrage du défilement,
     dégradé) et qu'un 18 recopié quatre fois se désaccorde à la première
     retouche — on dérive, on ne recopie pas. */
  --carrousel-bord: 18px;
  --saisie:    16px;    /* en dessous, Safari iOS zoome au focus */

  --ombre:     0 1px 2px rgba(14,23,38,.05), 0 8px 24px rgba(14,23,38,.06);
  --ombre-h:   0 2px 6px rgba(14,23,38,.07), 0 16px 40px rgba(14,23,38,.10);

  color-scheme: light;
}

/* ═══════════════════════════ LES JETONS — MODE SOMBRE ═══════════════════ */
/*
 * ⚠️ LE SOMBRE S'AFFICHE QUAND L'ARTISAN L'A DEMANDÉ, JAMAIS TOUT SEUL.
 *
 * Le 07/09, une règle `prefers-color-scheme` héritée d'une bibliothèque a
 * repeint l'application sur TOUS les téléphones en mode sombre, en
 * production, sans qu'aucune mesure la voie — un navigateur de test n'a pas
 * de préférence sombre. Un thème qui bascule tout seul est un thème que
 * personne n'a validé.
 *
 * D'où la forme : `[data-theme="sombre"]` sur la racine, posé par un CHOIX.
 * La préférence système n'agit QUE si l'artisan a choisi « Système » — et ce
 * choix-là, il l'a fait lui-même.
 */
[data-theme="sombre"] {
  --navy:        #7FA8D4;
  --navy-clair:  #9CBEE4;
  --navy-fond:   #1B2637;

  --ambre:       #E0A048;
  --ambre-fonce: #F0B667;
  --ambre-fond:  #33270F;

  --encaisse:      #46C98A;
  --encaisse-fond: #102A1E;
  --du:            #F0705F;
  --du-fond:       #2E1512;

  --fond:      #0B0F16;
  --surface:   #151B25;
  --creux:     #1D2531;
  --bord:      #262F3D;

  --encre:     #F2F5F9;
  --doux:      #A3AEBC;
  /* En sombre, #6E7987 rendait 3,91:1 sur une SURFACE — sous le seuil, et
     c'est là que vivent les libellés des lignes. #8A94A2 : 6,25 sur le fond,
     5,63 sur la surface. */
  --faible:    #8A94A2;
  --inverse:   #0B0F16;

  --ombre:     0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35);
  --ombre-h:   0 2px 6px rgba(0,0,0,.45), 0 16px 40px rgba(0,0,0,.5);

  color-scheme: dark;
}

/* ═══════════════════════════ LA POLICE ══════════════════════════════════ */
@font-face {
  font-family: "DM Sans v2";
  src: url("/theme/fonts/dm-sans-latin.woff2") format("woff2");
  font-weight: 400 700; font-display: swap;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--fond); color: var(--encre);
  font-family: "DM Sans v2", system-ui, -apple-system, sans-serif;
  font-size: 15px; line-height: 1.45; font-weight: 500;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3 { margin: 0; font-weight: 700; letter-spacing: -0.025em; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }
ul, ol { margin: 0; padding: 0; list-style: none; }
/* Les chiffres ne dansent JAMAIS d'un rafraîchissement à l'autre. */
.v2-nombre, .v2-solde, .v2-montant, .v2-tuile-chiffre { font-variant-numeric: tabular-nums; }

/* ═══════════════════════════ LA CHARPENTE ═══════════════════════════════ */
.v2-app { display: flex; min-height: 100vh; min-height: 100dvh; }

/* LA LATÉRALE — « étendue » (232 px, libellés) PAR DÉFAUT, « réduite »
   (72 px, icônes seules) par choix mémorisé (B-H2, `coquille.js:navChoix`).
   Les règles ci-dessous sont celles du mode RÉDUIT — la base historique ;
   le mode étendu les recouvre juste après. */
.v2-barre {
  width: var(--barre); flex: none; background: var(--surface);
  border-right: 1px solid var(--bord);
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 14px 0; position: sticky; top: 0; height: 100vh; height: 100dvh;
  /* F4, brief pilote 10/09 : « pas de transition » — la latérale passait de
     72 à 232 px d'un coup sec au clic de `.v2-nav-bascule`. `prefers-
     reduced-motion` (plus bas dans ce fichier) coupe déjà TOUTE transition
     à .01ms — cette règle n'a donc besoin d'aucune garde en plus. */
  transition: width .18s ease, padding .18s ease;
}
.v2-marque { margin-bottom: 10px; }
.v2-logo { width: 32px; height: 32px; display: block; }
.v2-marque-nom, .v2-marque-sous { display: none; }

.v2-nav { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 1; width: 100%; }
.v2-nav a {
  width: 52px; height: 52px; border-radius: var(--r-tuile);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  color: var(--faible); font-size: 14px; font-weight: 600;
}
.v2-nav a span { display: none; }
.v2-nav a:hover { background: var(--creux); color: var(--encre); }
.v2-nav a[aria-current="page"] { background: var(--navy-fond); color: var(--navy); }
.v2-nav svg { width: 22px; height: 22px; stroke-width: 1.9; }

/* ─── LE LIBELLÉ AU SURVOL, EN MODE RÉDUIT — retouche 4 de Houssem, 07/09 ──
 *
 * ⚠️ POURQUOI PAS LE `title` NATIF. Une infobulle de navigateur met environ
 * une seconde à apparaître, ne se style pas, et ne s'affiche JAMAIS au
 * clavier. Une latérale de douze icônes muettes se devine, et une icône qu'on
 * devine est une fonction qu'on ne trouve pas — c'est exactement le constat
 * de Houssem sur Avis et Téléphone, transposé à la navigation.
 *
 * Le `<span>` du libellé existe déjà dans le HTML (il sert à la barre basse) :
 * ici il est simplement sorti de la latérale et posé à côté, au survol ET au
 * focus clavier. */
.v2-nav a { position: relative; }
.v2-nav a > span {
  display: block; position: absolute; left: calc(100% + 10px); top: 50%;
  transform: translateY(-50%) translateX(-4px);
  padding: 7px 12px; border-radius: var(--r-tuile);
  background: var(--encre); color: var(--fond);
  font-size: 14px; font-weight: 600; white-space: nowrap;
  opacity: 0; pointer-events: none; z-index: 20;
  transition: opacity .12s ease, transform .12s ease;
}
.v2-nav a:hover > span, .v2-nav a:focus-visible > span {
  opacity: 1; transform: translateY(-50%) translateX(0);
}

/* ⚠️ B-H8, Houssem, 08/09 : « traité comme un réglage, pas une action
 * majeure — une petite icône seule, sans cadre ni bordure ni libellé
 * permanent, libellé au survol, même traitement que le bouton de thème de
 * l'en-tête. » `.v2-ico-btn` EST ce traitement (48 px, rond, fond `--creux`
 * discret) — on le reprend, on ne le réinvente pas. Contrairement au mode
 * étendu du 07/09 (retiré ici), CE bouton reste petit et discret dans les
 * DEUX états de la latérale : c'est un réglage, il ne grandit jamais. */
/* ⚠️ FAUX POSITIF CONNU D'UN INSTRUMENT DE MESURE — mission-ecrans, E1,
   13/09. `.v2-nav-bascule.scrollWidth` (126px) dépasse largement son
   `clientWidth` (48px, le rond) : la cause est le `<span>` d'infobulle
   ci-dessous, `position:absolute`, dont `.v2-nav-bascule` (relative) est le
   bloc englobant — un descendant absolument positionné compte dans le
   `scrollWidth` de son ancêtre positionné MÊME à `opacity:0`
   (`opacity` ne retire rien de la mise en page, contrairement à `display:
   none`). **Ce n'est pas un débordement visible** : le libellé n'apparaît
   qu'au survol/focus (`:hover`, `:focus-visible`, plus bas), et
   `document.documentElement.scrollWidth` reste égal à `clientWidth` sur les
   neuf écrans mesurés. Un instrument qui traite `scrollWidth > clientWidth`
   comme une preuve suffisante, sans vérifier si l'excédent vient d'un
   descendant invisible au repos, le confondra à nouveau. */
.v2-nav-bascule {
  width: var(--cible); height: var(--cible); border: 0; border-radius: 50%;
  background: var(--creux); color: var(--doux); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; position: relative;
}
.v2-nav-bascule:hover { background: var(--bord); color: var(--encre); }
.v2-nav-bascule svg { width: 20px; height: 20px; stroke-width: 1.9; flex: none; }
.v2-nav-bascule > span {
  display: block; position: absolute; left: calc(100% + 10px); top: 50%;
  transform: translateY(-50%) translateX(-4px);
  padding: 7px 12px; border-radius: var(--r-tuile);
  background: var(--encre); color: var(--fond);
  font-size: 14px; font-weight: 600; white-space: nowrap;
  opacity: 0; pointer-events: none; z-index: 20;
  transition: opacity .12s ease, transform .12s ease;
}
.v2-nav-bascule:hover > span, .v2-nav-bascule:focus-visible > span {
  opacity: 1; transform: translateY(-50%) translateX(0);
}

/* ─── LE MODE ÉTENDU — B-H2, Houssem, 08/09 : libellés par défaut ─────── */
.v2-barre[data-nav="etendue"] { width: 232px; align-items: stretch; padding: 14px 12px; }
/* F4, brief pilote 10/09 : « latérale asymétrique » — MESURÉ au navigateur,
   pas supposé : le logo tombait à 6 px de padding pendant que les icônes de
   `.v2-nav a` juste en dessous démarrent à 12 px (leur propre `padding: 0
   12px`) — un décalage de 6 px entre le repère du haut et la colonne
   d'icônes qu'il surplombe. `getBoundingClientRect()` : logo à x=20,
   icône de nav à x=26, avant correctif. Même valeur que `.v2-nav a` referme
   l'écart. */
.v2-barre[data-nav="etendue"] .v2-marque { padding-left: 12px; }
.v2-barre[data-nav="etendue"] .v2-nav a {
  width: 100%; height: 44px; flex-direction: row; justify-content: flex-start;
  padding: 0 12px; gap: 12px; font-size: 14px;
}
.v2-barre[data-nav="etendue"] .v2-nav a > span {
  display: inline; position: static; opacity: 1; transform: none;
  background: none; color: inherit; padding: 0; pointer-events: none;
  font-size: 14px; font-weight: 600; transition: none;
}
/* ⚠️ B-H8 : le bouton reste PETIT en mode étendu — `.v2-barre` porte
 * `align-items: stretch` pour les liens de nav, qui doivent remplir la
 * largeur ; le bascule, lui, ne le doit jamais. `align-self` l'excepte
 * seul, sans toucher aux liens. Le chevron continue de pointer vers la
 * réduction (gauche) en mode étendu, vers l'extension (droite) en réduit. */
.v2-barre[data-nav="etendue"] .v2-nav-bascule { align-self: flex-start; }
.v2-barre[data-nav="etendue"] .v2-nav-bascule svg { transform: scaleX(-1); }

.v2-colonne {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  /* ⚠️ `clip`, PAS `hidden` — ET C'EST UNE TRANSITION QUI L'IMPOSE.
     L'entrée d'écran translate la page de 16 px : pendant l'animation, la
     page est plus large que la fenêtre, et sur un vrai téléphone ça produit
     un rebond horizontal à CHAQUE navigation. Mesuré le 07/09 : 16 px de
     débordement sur la fiche.
     `overflow: hidden` corrigerait aussi — et casserait le `position: sticky`
     de l'en-tête, parce qu'un ancêtre à débordement caché devient le
     conteneur de défilement. `clip` coupe sans créer ce conteneur. */
  overflow-x: clip;
}

/* LE HAUT DE COLONNE — bandeau de support (s'il existe) + en-tête, collés
   ENSEMBLE au défilement. `position: sticky` vit ICI, sur le conteneur, pas
   sur `.v2-entete` : un `top` calculé en dur sur l'en-tête (`var(--bandeau-
   support-h)`) se serait trompé dès que le bandeau passe à deux lignes à
   360 px — la hauteur réelle du bandeau est inconnue au moment d'écrire le
   CSS, et un conteneur sticky unique n'a pas besoin de la connaître : il
   colle sa propre hauteur, quelle qu'elle soit. Ajouté le 12/09 avec le
   bandeau rouge collant — voir `.v2-bandeau-support`. */
.v2-haut { position: sticky; top: 0; z-index: 5; display: flex; flex-direction: column; }

/* L'EN-TÊTE MINIMAL : avatar rond à gauche, titre, une icône à droite. */
.v2-entete {
  height: var(--entete); flex: none;
  display: flex; align-items: center; gap: 12px;
  padding: 0 18px; background: var(--fond);
  width: 100%; max-width: calc(var(--colonne) + 36px); margin-inline: auto;
}
.v2-titre { font-size: 17px; font-weight: 700; }
.v2-entete-droite { margin-left: auto; display: flex; align-items: center; gap: 6px; }
/* LA PHRASE SOUS LE TITRE — E1, Houssem, 13/09 : « ce à quoi sert l'écran,
   en langue d'artisan ». Hors de `.v2-entete` (dont la hauteur est fixée à
   `--entete`) : un sibling dans `.v2-haut`, qui reste collant avec elle sans
   forcer sa hauteur à s'adapter à un texte sur une ou deux lignes. */
.v2-sous-titre {
  margin: 0 auto; padding: 0 18px 12px;
  width: 100%; max-width: calc(var(--colonne) + 36px);
  font-size: 14px; line-height: 1.4; color: var(--doux); background: var(--fond);
}
/* ⚠️ `order: -1` RETIRÉ — B-H14, Houssem, 08/09 17h20. L'avatar quitte la
   gauche décorative pour un bouton fonctionnel à droite (`.v2-compte`) ; il
   n'est plus un enfant direct de `.v2-entete`, `order` n'y ferait plus rien
   et laisserait une propriété morte qui trompe le prochain lecteur. */
.v2-avatar {
  width: 38px; height: 38px; border-radius: 50%; flex: none;
  background: var(--navy); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 700;
}
[data-theme="sombre"] .v2-avatar { color: var(--inverse); }

/* ═══ LE MENU DE COMPTE — B-H14, Houssem, 08/09 17h20 ═══════════════════
   `popover` natif : fermeture au clic dehors et à Échap gratuites, couche
   supérieure sans z-index à la main — même famille de choix que `<dialog>`
   ailleurs dans la coquille. `.v2-compte` porte le bouton ET le menu, pour
   que le menu s'ancre dessous SANS l'API d'ancrage CSS (support plus large). */
.v2-compte { position: relative; }
.v2-compte .v2-ico-btn { padding: 0; background: transparent; }
.v2-compte .v2-ico-btn:hover { background: transparent; }
/* ⚠️ `display` VIT SOUS `:popover-open`, JAMAIS SUR LE SÉLECTEUR NU —
   mesuré le 08/09 en production : un `display: flex` posé ici, sans
   condition, bat la règle `[popover]:not(:popover-open) { display: none }`
   du navigateur (l'origine AUTEUR l'emporte sur l'origine NAVIGATEUR quelle
   que soit la spécificité comparée) et rendait le menu visible EN
   PERMANENCE, ouvert ou pas — un `showPopover()`/`hidePopover()` qui
   fonctionne au niveau de l'état, invisible à l'écran.
   ⚠️ `position: fixed`, JAMAIS `absolute` — MESURÉ : un élément `[popover]`
   ouvert est promu dans la « couche supérieure », dont le bloc englobant
   pour `position: absolute` est la racine du DOCUMENT, PAS `.v2-compte`
   (son parent DOM). `top: calc(100% + 8px)` se résolvait donc contre la
   hauteur totale de la page, pas contre le bouton — le menu s'ouvrait à
   800+ px sous l'écran, invisible, alors que son état interne
   (`:popover-open`) était pourtant correct. `top`/`right` sont posés en
   JS (`main.js`, sur l'évènement `beforetoggle`) depuis la position RÉELLE
   du bouton — `position: fixed` les interprète alors relativement à la
   fenêtre, ce qui correspond déjà à la couche supérieure. */
.v2-menu-compte {
  position: fixed; margin: 0; padding: 8px; border: 1px solid var(--bord); border-radius: 14px;
  background: var(--fond); box-shadow: var(--ombre-h);
  min-width: 240px; max-width: min(320px, calc(100vw - 24px));
}
.v2-menu-compte:popover-open { display: flex; flex-direction: column; gap: 2px; }
.v2-menu-compte-identite { padding: 8px 10px 10px; border-bottom: 1px solid var(--bord); margin-bottom: 4px; }
.v2-menu-compte-identite strong { display: block; font-size: 14px; }
.v2-menu-compte-identite span { display: block; font-size: 14px; color: var(--doux); margin-top: 2px; }
.v2-menu-compte-contexte {
  padding: 8px 10px; margin-bottom: 4px; border-radius: 10px;
  background: var(--creux); font-size: 12.5px; color: var(--doux);
}
.v2-menu-compte-contexte button {
  display: block; margin-top: 6px; border: 0; background: none; padding: 0;
  /* ⚠️ ÉTAIT `var(--a-600)` — CE JETON N'EXISTE PAS DANS CETTE FEUILLE (la v2
     a son propre jeu, voir la note sur `--du` plus haut et
     `app/v2/SYSTEME-A-PART.md`) : la déclaration était silencieusement
     ignorée et le bouton héritait `--doux`, le gris du texte environnant —
     même défaut, même famille, que `.v2-bandeau-support button` juste avant
     le correctif ci-dessous. `--navy` est la couleur d'ACTION de la v2
     (`.v2-lien`) : 11,49:1 de contraste mesuré sur `--creux`, le fond réel de
     `.v2-menu-compte-contexte`. Trouvé par `scripts/v2/voir-app.mjs`
     (famille « fantômes »), pas à l'œil. */
  color: var(--navy); font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.v2-menu-compte a, .v2-menu-compte > button {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 10px; border-radius: 9px; font-size: 14px;
  color: var(--encre); text-decoration: none; border: 0; background: none;
  cursor: pointer; text-align: left; width: 100%;
}
.v2-menu-compte a:hover, .v2-menu-compte > button:hover { background: var(--creux); }
.v2-menu-compte > button svg { width: 17px; height: 17px; flex: none; color: var(--doux); }

/* ═══ LA CLOCHE — E1, Houssem, 13/09 ════════════════════════════════════
   Elle prend la place qu'occupait la bascule de thème dans l'en-tête :
   « ce n'est pas un déplacement d'icône, c'est un arbitrage sur ce qui
   mérite la place la plus chère de l'écran ». Même patron d'ancrage que
   `.v2-menu-compte` juste au-dessus (`position: fixed`, top/right posés en
   JS sur `beforetoggle` — voir `main.js`) et pour la MÊME raison : un
   `[popover]` ouvert est promu hors de son parent DOM. */
.v2-cloche { position: relative; }
.v2-cloche-badge {
  position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 999px; background: var(--du); color: #fff;
  font-size: 14px; font-weight: 700; line-height: 16px; text-align: center;
  /* Le fond de la cloche pour que la pastille se détache, quel que soit le
     thème — sans ce liséré, une pastille rouge sur un rond gris clair se lit
     mal en plein soleil, exactement le défaut que le skill interdit. */
  box-shadow: 0 0 0 2px var(--fond);
  pointer-events: none;
}
.v2-menu-cloche {
  position: fixed; margin: 0; padding: 8px; border: 1px solid var(--bord); border-radius: 14px;
  background: var(--fond); box-shadow: var(--ombre-h);
  min-width: 260px; max-width: min(340px, calc(100vw - 24px));
}
.v2-menu-cloche:popover-open { display: flex; flex-direction: column; gap: 2px; }
.v2-menu-cloche-ligne {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 10px; border-radius: 9px; border: 0; background: none;
  font-size: 14px; color: var(--encre); text-align: left; cursor: pointer;
}
.v2-menu-cloche-ligne:hover { background: var(--creux); }
.v2-menu-cloche-texte { flex: 1; min-width: 0; }
.v2-menu-cloche-vide { padding: 14px 10px; font-size: 13.5px; color: var(--doux); text-align: center; }

/* ═══ LE BANDEAU DE CONTEXTE — B-H15, Houssem, 08/09 17h35, RENVERSÉ le
   12/09 ═══════════════════════════════════════════════════════════════
   Le ton neutre voulu par B-H15 a été mesuré EN PRODUCTION, en support sur
   un client réel : fond gris `--creux`, `position: static`, largeur bornée
   à la colonne — le bandeau DÉFILAIT hors de vue et ne traversait pas la
   coquille. Houssem, 12/09 : « la décision de CLAUDE.md tient, l'écran
   avait tort. Rouge et collant, visible en permanence — c'est la garantie
   la plus lourde du produit, elle ne peut pas défiler hors de vue. » Même
   famille de ton que `.v2-pilule--rouge` / `.v2-btn--danger` : `--du` /
   `--du-fond` PORTENT déjà le rouge de la v2, on ne le réinvente pas.

   `position: sticky` vit sur `.v2-haut`, PAS ici — voir ce sélecteur : le
   bandeau et l'en-tête collent ENSEMBLE, sans hauteur recopiée en dur, et
   sans se chevaucher quel que soit le nombre de lignes que le texte prend
   à 360 px (piège n°40 : un flex-item sans taille minimale se comprime, une
   hauteur figée en calc() se trompe dès que le texte passe à deux lignes).

   PLEINE LARGEUR DE LA COQUILLE — le bandeau n'a plus de `max-width` : il
   traverse `.v2-colonne` bord à bord, jamais la latérale (`.v2-barre` est
   un frère, pas un parent). L'en-tête, lui, garde son cadrage centré. */
.v2-bandeau-support {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  /* ⚠️⚠️ LE TON RESTE NEUTRE — B-H15 TIENT, 12/09, SECOND ARBITRAGE.
     Le premier (12/09, « rouge et collant ») a été rendu en croyant corriger
     une dérive de l'écran. **L'écran n'avait pas dérivé** : le gris était
     B-H15, décision de Houssem du 08/09 — *« Ton NEUTRE, jamais rouge, jamais
     alarmant »* — et la doctrine générale de CLAUDE.md était plus ANCIENNE.
     Houssem a annulé son propre arbitrage : *« Une décision datée ne s'efface
     pas par un arbitrage pris sans l'avoir vue. »*
     ⚠️ CE QUI ÉTAIT UN VRAI DÉFAUT, ET QUI EST CORRIGÉ : `position: static`,
     donc le bandeau DÉFILAIT HORS DE VUE, et une largeur bornée à la colonne.
     B-H15 tranchait le TON, jamais la permanence. *Un écart peut être à moitié
     une décision et à moitié un défaut* — règle 1 duodecosexagies. */
  padding: 7px 18px; background: var(--creux); color: var(--faible);
  border-bottom: 1px solid var(--ligne, #E4E7EC);
  font-size: 12.5px; font-weight: 600;
}
.v2-bandeau-support strong { color: var(--encre); font-weight: 700; }
/* ⚠️ `--encre`, PAS `--texte` : ce dernier n'existe pas dans revolut.css.
   Je l'ai écrit en remettant le ton neutre, et la suite 828 — livrée le
   MÊME JOUR par la mission des jetons fantômes — l'a attrapé avant la
   poussée. Le garde a mordu sur son auteur au premier tour : c'est
   exactement le défaut qu'il existe pour fermer (`--a-600`, 12/09). */
.v2-bandeau-support button {
  display: inline-flex; align-items: center; gap: 5px; border: 0; background: none;
  /* `--a-600` n'existe pas dans ce fichier (v2 a son propre jeu de jetons,
     voir la note plus haut sur `--du`) : la déclaration était silencieusement
     ignorée et le bouton héritait la couleur du texte environnant — piège de
     la même famille que le n°31. `--navy` est la couleur d'ACTION de la v2
     (`.v2-lien`), distincte du rouge d'ALERTE du reste du bandeau : 11,2:1
     de contraste sur `--du-fond`, mesuré. */
  color: var(--navy); font-size: 12.5px; font-weight: 700; cursor: pointer; padding: 2px;
}
.v2-bandeau-support button svg { width: 15px; height: 15px; }
.v2-ico-btn {
  width: var(--cible); height: var(--cible); border: 0; border-radius: 50%;
  background: var(--creux); color: var(--doux); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.v2-ico-btn:hover { background: var(--bord); color: var(--encre); }
.v2-ico-btn svg { width: 20px; height: 20px; stroke-width: 1.9; }

.v2-page { padding: 4px 18px 40px; width: 100%; max-width: calc(var(--colonne) + 36px); margin-inline: auto; }
/* ⚠️ FAUX POSITIF CONNU D'UN INSTRUMENT DE MESURE — mission-ecrans, E1,
   13/09. `.v2-cadre.scrollWidth` dépasse son `clientWidth` de 18 px (360 px)
   dès qu'il contient un `.v2-onglets` : ce dernier bleed volontairement sur
   les deux bords (`margin-inline: -18px`, voir sa règle plus bas) pour
   amorcer la pilule suivante. Le bord GAUCHE ne compte jamais dans
   `scrollWidth` (piège n°23), le bord DROIT si — d'où l'écart mesuré.
   **Ce n'est PAS un débordement visible** : `.v2-colonne` (plus haut dans ce
   fichier) porte `overflow-x: clip`, qui coupe le bleed sans créer de
   scrollbar. Vérifié : `document.documentElement.scrollWidth` reste égal à
   `clientWidth` sur les neuf écrans mesurés. Un instrument qui compare
   `.v2-cadre` seul, sans vérifier l'ancêtre qui le clippe, le confondra à
   nouveau — ne pas « corriger » ce nombre. */
.v2-cadre { display: flex; flex-direction: column; gap: 22px; }
/* ⚠️ `.v2-cadre--etroit` N'ÉTAIT DÉFINI NULLE PART — `app/v2/ecrans/a-venir.js`
   le posait sur ses deux panneaux (le pont vers l'ancienne application, et
   « pas encore écrit ») sans qu'aucune règle n'y réponde : la classe ne
   faisait rien, en silence. Trouvé par `scripts/v2/voir-app.mjs` (famille
   « fantômes »). Une carte d'état unique, sans liste ni tableau à côté, se
   lit mieux resserrée qu'étalée sur les 760 px de `--colonne` — le même
   raisonnement que `.v2-etat p` (34ch, centré) un cran plus haut. */
.v2-cadre--etroit { max-width: 480px; margin-inline: auto; }

/* ═══════════════════════════ 1-3. LE SOLDE ══════════════════════════════ */
.v2-salut { font-size: 15px; color: var(--doux); font-weight: 500; }
.v2-solde-bloc { padding: 6px 2px 2px; }
.v2-solde {
  font-size: 46px; font-weight: 700; letter-spacing: -0.035em; line-height: 1.05;
  /* NEUTRE, PAS ROUGE. Un total est neutre : le vert dit « l'argent ENTRE »,
     le rouge « l'argent est ATTENDU ». Peindre le chiffre principal en rouge
     tous les jours ferait d'un carnet de commandes plein une mauvaise
     nouvelle permanente. La couleur vit sur les écarts, dessous. */
  color: var(--neutre);
}
.v2-solde-quoi { font-size: 14px; font-weight: 600; color: var(--faible); text-transform: uppercase; letter-spacing: .07em; }
.v2-solde-ligne { font-size: 14px; color: var(--doux); margin-top: 6px; }
.v2-encaisse { color: var(--encaisse); font-weight: 700; }
.v2-du { color: var(--du); font-weight: 700; }

/* ═══════════════════════════ 4. LE CARROUSEL ════════════════════════════ */
.v2-rangee-titre {
  font-size: 14px; font-weight: 700; color: var(--faible);
  text-transform: uppercase; letter-spacing: .07em; margin-bottom: 10px;
}
.v2-carrousel {
  display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory;
  /* Les marges négatives font mordre le carrousel sur le bord de l'écran :
     c'est ce qui crée l'AMORCE de la carte suivante. Sans elle, personne ne
     sait qu'il y a quelque chose à droite. */
  margin-inline: calc(-1 * var(--carrousel-bord));
  padding: 2px var(--carrousel-bord) 8px;
  /* ⚠️ `scroll-snap` IGNORE LE REMPLISSAGE DU CONTENEUR, ET LE DÉFAUT EST
     SILENCIEUX. Mesuré le 07/09 à 390 px : au repos, `scrollLeft` revenait
     tout seul à 18 — le snap collait la première tuile au bord GAUCHE de
     l'écran, décalée de 18 px par rapport au titre de la rangée et à tout le
     reste de la page. `scroll-padding` est la seule propriété qui dise au
     snap où commence vraiment la zone utile. */
  scroll-padding-inline: var(--carrousel-bord);
  scrollbar-width: none;
}
.v2-carrousel::-webkit-scrollbar { display: none; }

/* ─── CE QUI DIT QU'UNE RANGÉE DÉFILE — B-C7 ────────────────────────────
 * Houssem, 07/09, sur iPhone : *« une rangée qui s'arrête net au bord se lit
 * comme une rangée COMPLÈTE »* — l'artisan ne défile pas, donc les tuiles
 * suivantes n'existent pas pour lui.
 *
 * TROIS ÉLÉMENTS, ET LES TROIS SONT EXIGÉS : l'amorce de la carte suivante,
 * des points, un dégradé. Le débord seul se confond avec une marge ; les
 * points seuls ne se voient pas en périphérie.
 *
 * ⚠️ ÇA VIT ICI, SUR LE COMPOSANT, JAMAIS DANS UN ÉCRAN. Un carrousel neuf
 * hérite des trois sans que personne y pense — c'est toute la différence
 * entre une règle et une consigne qu'on redonne à chaque écran.
 */
.v2-carrousel-cadre { position: relative; }

/* LE DÉGRADÉ DE DROITE. ⚠️ Il ne peut PAS vivre sur le conteneur qui défile :
   un pseudo-élément d'un scroller défile AVEC son contenu et sort de l'écran
   par la gauche au premier geste. Il vit donc sur le cadre, qui ne bouge
   jamais. Le `right` négatif rattrape le débord du rail, qui déborde son
   cadre des deux côtés — sans lui, le dégradé s'arrêterait 18 px avant le
   bord. */
.v2-carrousel-cadre::after {
  content: ""; position: absolute; top: 0; bottom: 0;
  right: calc(-1 * var(--carrousel-bord)); width: 56px;
  pointer-events: none;
  background: linear-gradient(to right, transparent, var(--fond));
  transition: opacity .18s linear;
}
/* Arrivé au bout, il n'y a plus rien à annoncer — et un dégradé qui resterait
   ferait croire à une suite qui n'existe pas. */
.v2-carrousel-cadre[data-fin="oui"]::after { opacity: 0; }

/* ⚠️ LE DÉGRADÉ DE GAUCHE — E1, Houssem, 13/09 : « carte coupée dans son
   carrousel ». MESURÉ en scrollant le rail à 206 px (deuxième tuile
   affichée) : la première tuile ressort à `left:-188, right:6.39` —
   un éclat de 6,39 px de son coin arrondi qui dépasse au bord GAUCHE de
   l'écran, SANS AUCUN traitement, pendant que le bord droit reçoit un
   dégradé propre. L'asymétrie EST le défaut : un bord qui s'annonce
   (dégradé) et un bord qui se déchire (coupe nette) ne racontent pas la
   même chose, alors que les deux sont le MÊME geste — « il y a une tuile
   de plus par ici ». Même mécanique que `::after`, en miroir : ancrée sur
   le cadre (qui ne défile pas), jamais sur le rail. */
.v2-carrousel-cadre::before {
  content: ""; position: absolute; top: 0; bottom: 0;
  left: calc(-1 * var(--carrousel-bord)); width: 56px;
  pointer-events: none; z-index: 1;
  background: linear-gradient(to left, transparent, var(--fond));
  transition: opacity .18s linear;
}
/* Au repos (rien de défilé à gauche), il n'y a rien à annoncer de ce côté —
   même garde que `data-fin`, en miroir, posé par `main.js:equiperCarrousels`. */
.v2-carrousel-cadre[data-debut="oui"]::before { opacity: 0; }

/* LES POINTS. Une seule couleur, jouée en opacité : un jeton de plus par
   thème serait un jeton de plus à garder d'accord. */
.v2-carrousel-points { display: flex; justify-content: center; gap: 6px; margin-top: 12px; }
/* ⚠️ `[hidden]` EST UNE RÈGLE UA DE FAIBLE SPÉCIFICITÉ (piège n°42) : le
   `display: flex` ci-dessus la battrait, l'attribut resterait posé, et les
   points resteraient à l'écran sans qu'une seule erreur ne le dise. */
.v2-carrousel-points[hidden] { display: none; }
.v2-carrousel-point {
  width: 6px; height: 6px; border-radius: var(--r-pilule);
  background: var(--navy); opacity: .25;
  transition: width .18s, opacity .18s;
}
.v2-carrousel-point[data-actif="oui"] { width: 18px; opacity: 1; }
.v2-tuile {
  scroll-snap-align: start; flex: 0 0 auto; width: 60%; max-width: 230px; min-width: 168px;
  background: var(--surface); border-radius: var(--r-carte); box-shadow: var(--ombre);
  padding: 16px; border: 0; cursor: pointer; text-align: left;
  display: flex; flex-direction: column; gap: 8px; min-height: 132px;
}
.v2-tuile:hover { box-shadow: var(--ombre-h); }
.v2-rond {
  width: 40px; height: 40px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
}
.v2-rond svg { width: 20px; height: 20px; stroke-width: 2; }
.v2-rond--navy { background: var(--navy-fond); color: var(--navy); }
.v2-rond--ambre { background: var(--ambre-fond); color: var(--ambre); }
.v2-rond--du { background: var(--du-fond); color: var(--du); }
.v2-rond--encaisse { background: var(--encaisse-fond); color: var(--encaisse); }
/* Mêmes alias que les pastilles, et pour la même raison : un rond de ton
   inconnu est INVISIBLE — pas de fond, pas de couleur — au lieu d'être faux. */
.v2-rond--rouge { background: var(--du-fond); color: var(--du); }
.v2-rond--vert  { background: var(--encaisse-fond); color: var(--encaisse); }
.v2-rond--gris  { background: var(--creux); color: var(--doux); }
.v2-tuile-chiffre { font-size: 30px; font-weight: 700; letter-spacing: -0.03em; line-height: 1; margin-top: auto; }
.v2-tuile-quoi { font-size: 14px; color: var(--doux); font-weight: 500; }

/* ═══════════════════════════ 5. LA LISTE À LIGNES HAUTES ════════════════ */
.v2-bloc { background: var(--surface); border-radius: var(--r-carte); box-shadow: var(--ombre); overflow: hidden; }
.v2-liste { display: flex; flex-direction: column; }

/* ═══ L'APPARENCE — E1, Houssem, 13/09 : trois boutons, pas un cycle
   (`ecrans/plus.js:blocApparence`). Le thème quitte l'en-tête pour ici. ═══ */
.v2-theme-choix { display: flex; gap: 8px; padding: 14px 16px; }
.v2-theme-opt {
  flex: 1; min-height: var(--cible); min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border: 1px solid var(--bord); border-radius: 12px; background: var(--fond);
  color: var(--doux); font-size: 14px; font-weight: 600; cursor: pointer; padding: 8px 4px;
}
.v2-theme-opt svg { width: 20px; height: 20px; }
.v2-theme-opt:hover { background: var(--creux); }
.v2-theme-opt--actif { border-color: var(--navy); background: var(--navy-fond); color: var(--navy); }
.v2-ligne {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 68px; padding: 12px 16px; border: 0; background: none;
  text-align: left; cursor: pointer;
}
.v2-ligne + .v2-ligne { border-top: 1px solid var(--bord); }
.v2-ligne:hover { background: var(--creux); }
/* ⚠️ UNE LIGNE QUI NE MÈNE NULLE PART NE FAIT PAS SEMBLANT. `.v2-ligne` est
   dessinée pour un bouton : main au survol, fond qui s'allume. Posée sur une
   ligne inerte — un envoi d'avis dont la piece a disparu, donc sans
   identifiant de client — elle promet une fiche qu'on ne sait pas ouvrir. */
.v2-ligne--inerte { cursor: default; }
.v2-ligne--inerte:hover { background: none; }
.v2-ligne-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.v2-ligne-titre { font-size: 15px; font-weight: 700; }
/* ⚠️ UNE SEULE LIGNE, TRONQUÉE. Élément 5 de la planche : le sous-titre d'une
   ligne haute ne passe jamais à la ligne — sinon la ligne grandit, le montant
   de droite se décale, et la colonne de montants cesse d'être une colonne.
   `min-width: 0` sur le parent est ce qui rend l'ellipse possible dans un
   flex : sans lui, l'enfant refuse de descendre sous la largeur de son texte
   (piège n°40). */
.v2-ligne-sous {
  font-size: 14px; color: var(--doux); font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.v2-ligne-titre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-montant { font-size: 15px; font-weight: 700; flex: none; white-space: nowrap; }
/* L'ancienneté, à droite, quand aucune pastille n'a rien à dire. */
.v2-ligne-quand { font-size: 14px; color: var(--faible); font-weight: 600; flex: none; white-space: nowrap; }

/* ─── LE CHEVRON — retouche 3 de Houssem, 07/09 ─────────────────────────
 *
 * ⚠️ LA LIGNE ENTIÈRE EST DÉJÀ CLIQUABLE ; CE QUI MANQUAIT, C'ÉTAIT DE LE
 * DIRE. Sans repère à droite, rien ne distingue une ligne qui mène quelque
 * part d'une ligne qui ne fait qu'afficher — et on n'essaie pas de cliquer sur
 * ce qui n'a pas l'air cliquable. Le chevron est le seul signe universel de
 * « ça continue par là ».
 *
 * Il glisse de 3 px au survol : le mouvement dit « ça part à droite », comme
 * la transition d'écran qui suivra. */
.v2-chevron { color: var(--faible); display: inline-flex; flex: none; transition: transform .14s ease, color .14s ease; }
.v2-chevron svg { width: 18px; height: 18px; }
.v2-ligne:hover .v2-chevron { color: var(--navy); transform: translateX(3px); }

/* ─── LE SOUS-TITRE SUR DEUX LIGNES — retouche 3, seconde moitié ────────
 *
 * ⚠️ « JAMAIS TRONQUÉ SUR L'INFORMATION D'ACTION » (Houssem). Une ligne
 * tronquée convient à un libellé (« Devis D-2026-009 · envoyé ») : on devine
 * la fin. Elle ne convient PAS à une phrase qui dit quoi faire — « Relancer :
 * sa carte a expiré » coupé à « Relancer : sa carte a… » perd précisément le
 * mot qui décide du geste.
 *
 * Deux lignes, donc, quand la ligne porte `v2-ligne--action` : c'est l'écran
 * qui déclare l'intention, jamais le CSS qui devine. */
/* ⚠️ UN NOM DE CLIENT NE SE TRONQUE PAS. Mesuré à l'œil le 07/09 :
   « MARCHAND PLOMB… », « TISSERAND TOITU… », « VOLETTI STO… ». Une troncature
   convient à un libellé — on devine la fin — jamais à une IDENTITÉ : deux
   clients dont les vingt premières lettres se ressemblent deviennent le même
   sur l'écran qui sert à choisir chez qui aller. Le nom passe donc à la ligne
   dans les listes d'action, et c'est la hauteur qui cède, pas l'information. */
.v2-ligne--action .v2-ligne-titre {
  white-space: normal; overflow: visible; text-overflow: clip;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.25;
}
.v2-ligne--action .v2-ligne-sous {
  white-space: normal; overflow: visible; text-overflow: clip;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.35;
}
/* ⚠️ E1, Houssem, 13/09 : « noms de clients tronqués dans la liste de
   pilotage » — MESURÉ à 360 px sur `ecrans/pilotage.js:ligne()` :
   `.v2-ligne-texte` (flex:1, min-width:0, hérité de la règle commune) est le
   SEUL élément de la rangée sans plancher — `.v2-rond` (40px), `.v2-montant`
   et `.v2-chevron` sont `flex:none`, et `.v2-pilule` a `white-space:nowrap`
   sans `overflow`, donc son minimum automatique de flex-item est déjà SA
   PROPRE largeur de texte (elle ne cède jamais). Sur les lignes où palier +
   pilule sont longs, tout le déficit retombe sur `.v2-ligne-texte` : mesuré
   à 2,8 px de large pour « Compte neuf » et « VOLETTI STORES » — le nom
   entier tient alors dans une colonne plus étroite qu'un chiffre, et le
   clamp à 2 lignes (juste au-dessus) ne montre plus rien de lisible. C'est
   le piège n°40 : un flex-item sans plancher se fait comprimer SOUS la
   taille de son propre texte, et `getBoundingClientRect()` rend la boîte
   comprimée, pas ce qui devrait se peindre.
   Le plancher est scopé à `--action` (jamais à la règle commune) : les
   listes à une ligne (Demandes, Clients…) ont déjà leurs propres marges de
   manœuvre mesurées ailleurs, et un plancher pesé ici ne doit rien changer
   là-bas. */
.v2-ligne--action .v2-ligne-texte { min-width: 96px; }
.v2-ligne--action { align-items: flex-start; }
.v2-ligne--action .v2-rond, .v2-ligne--action .v2-pilule, .v2-ligne--action .v2-chevron { margin-top: 2px; }

/* ─── LA LIGNE OUVRANTE + LE BOUTON « GÉRER » — B-fiche, 08/09 ───────────
 * `ligne()` porte désormais DEUX cibles cliquables (« Ouvrir » entre chez le
 * client, « Gérer » ouvre sa fiche opérateur) — un `<button>` ne peut pas en
 * contenir un autre, d'où le passage de `.v2-ligne` en `<div>` conteneur.
 * `.v2-ligne-ouvrant` reprend exactement la mise en page que portait
 * l'ancien bouton unique. */
.v2-ligne-ouvrant {
  all: unset; box-sizing: border-box; display: flex; align-items: center; gap: 12px;
  flex: 1; min-width: 0; padding: 12px 16px; min-height: 68px; cursor: pointer;
}
.v2-ligne-ouvrant:focus-visible { outline: 2px solid var(--navy); outline-offset: -2px; }
.v2-ligne--action .v2-ligne-ouvrant { align-items: flex-start; }
.v2-ligne--action .v2-ligne-ouvrant .v2-rond,
.v2-ligne--action .v2-ligne-ouvrant .v2-pilule,
.v2-ligne--action .v2-ligne-ouvrant .v2-chevron { margin-top: 2px; }

/* ═══════════════════════════ 6. LES PILULES ═════════════════════════════ */
/* ⚠️ LA QUATRIÈME PILULE DOIT SE VOIR DÉPASSER — sinon elle n'existe pas.
   Signalé par le sous-agent Devis : à 390 px, l'onglet « Refusés » était hors
   écran et **rien ne disait qu'il y avait une suite**. La rangée défilait,
   mais un défilement qu'on ne soupçonne pas est un défilement qui n'a pas
   lieu.
   Le remède est celui de l'élément 4 de la planche, appliqué aux onglets :
   la rangée MORD sur le bord de l'écran (marges négatives), donc la pilule
   suivante s'amorce. Pas de dégradé, pas de flèche, pas de JavaScript — une
   amorce visible est le seul signal qui ne ment jamais sur ce qu'il reste. */
.v2-onglets {
  display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none;
  margin-inline: -18px; padding: 0 18px 2px;
}
.v2-onglets::-webkit-scrollbar { display: none; }
.v2-onglet {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  /* ⚠️ 46 px, PAS 42. Mesuré le 07/09 : les onglets tombaient à 42 px de haut
     — deux pixels sous le plancher du doigt, invisibles à l'œil et réels au
     pouce. Le plancher est 44 ; on prend 46 pour que l'arrondi ne le rogne
     jamais. */
  min-height: 46px; padding: 0 16px; border-radius: var(--r-pilule);
  background: var(--creux); color: var(--doux); font-size: 14px; font-weight: 600;
}
.v2-onglet:hover { color: var(--encre); }
.v2-onglet[aria-selected="true"] { background: var(--navy); color: var(--inverse); }
[data-theme="sombre"] .v2-onglet[aria-selected="true"] { color: var(--fond); }
.v2-onglet-compte { font-size: 14px; opacity: .75; }

.v2-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: var(--cible); padding: 0 22px; border: 0; border-radius: var(--r-pilule);
  background: var(--creux); color: var(--encre); font-size: 15px; font-weight: 700; cursor: pointer;
}
.v2-btn:hover { background: var(--bord); }
.v2-btn--primaire { background: var(--ambre); color: #fff; }
.v2-btn--primaire:hover { background: var(--ambre-fonce); }
[data-theme="sombre"] .v2-btn--primaire { color: var(--inverse); }
.v2-btn--navy { background: var(--navy); color: var(--inverse); }
[data-theme="sombre"] .v2-btn--navy { color: var(--fond); }
.v2-btn--navy:hover { background: var(--navy-clair); }
/* Le seul geste v2 dont l'annonce dit « irréversible » (réinitialiser le bac
   de démonstration) — voir main.js:confirmerAction. Aucune couleur en dur :
   `--du` porte déjà le rouge de la v2 (sémantique de l'argent dû — v2 a son
   PROPRE jeu de jetons, dans ce fichier, distinct de `app/theme/tokens.css`
   qui ne s'applique qu'à l'ancienne application : `--err-texte` n'existe pas
   ici, `gabarit.html` ne charge que `revolut.css`). `brightness()` évite
   d'inventer un second rouge pour le survol. */
.v2-btn--danger { background: var(--du); color: var(--inverse); }
.v2-btn--danger:hover { filter: brightness(0.88); }
.v2-btn[disabled] { opacity: .4; cursor: not-allowed; }
.v2-btn--pleine { width: 100%; }
.v2-btn svg, .v2-lien svg, .v2-onglet svg { width: 19px; height: 19px; flex: none; }

.v2-lien {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: var(--cible); padding: 0 4px;
  border: 0; background: none; color: var(--navy); font-size: 15px; font-weight: 700; cursor: pointer;
}
.v2-lien:hover { text-decoration: underline; }

/* Les pastilles de statut : texte FONCÉ sur fond CLAIR de la même teinte —
   du blanc sur ambre tombe à 2,4:1, et un fond coloré ne rend pas lisible. */
.v2-pilule {
  display: inline-flex; align-items: center; padding: 5px 12px; border-radius: var(--r-pilule);
  font-size: 14px; font-weight: 700; white-space: nowrap;
  background: var(--creux); color: var(--doux);
}
.v2-pilule--encaisse { background: var(--encaisse-fond); color: var(--encaisse); }
.v2-pilule--du { background: var(--du-fond); color: var(--du); }
.v2-pilule--ambre { background: var(--ambre-fond); color: var(--ambre-fonce); }
.v2-pilule--navy { background: var(--navy-fond); color: var(--navy); }
[data-theme="sombre"] .v2-pilule--ambre { color: var(--ambre); }

/* ⚠️ TROIS TONS RETOMBAIENT EN SILENCE SUR LE NEUTRE — trouvé le 07/09 par le
   sous-agent Clients, en lisant les tons que les écrans PRODUISENT plutôt que
   ceux que la feuille définit.
   `modele.js` et `demandes.js` rendent `vert`, `rouge`, `gris` ; le socle ne
   connaissait que `encaisse`, `du`, `navy`. Une classe absente ne lève pas :
   `v2-pilule--rouge` s'affichait simplement grise. **La pastille « Urgence »
   d'une demande n'était donc PAS rouge**, et personne ne pouvait le voir en
   relisant le code — les deux côtés se lisent parfaitement (piège n°102 :
   c'est le RACCORD qui manque).
   On pose les alias plutôt que de renommer partout : le vocabulaire du
   MÉTIER dit « rouge » quand il parle de gravité et « du » quand il parle
   d'argent — ce sont deux idées, elles méritent deux noms. */
.v2-pilule--rouge { background: var(--du-fond); color: var(--du); }
.v2-pilule--vert  { background: var(--encaisse-fond); color: var(--encaisse); }
.v2-pilule--gris  { background: var(--creux); color: var(--doux); }

/* ═══════════════════════════ LES ÉTATS ══════════════════════════════════ */
.v2-etat { text-align: center; padding: 48px 24px; }
.v2-etat-rond {
  width: 72px; height: 72px; border-radius: 50%; margin: 0 auto 18px;
  background: var(--creux); color: var(--faible);
  display: flex; align-items: center; justify-content: center;
}
.v2-etat-rond svg { width: 32px; height: 32px; stroke-width: 1.6; }
.v2-etat h2 { font-size: 20px; margin-bottom: 8px; }
.v2-etat p { color: var(--doux); max-width: 34ch; margin-inline: auto; font-size: 15px; }
.v2-etat .v2-btn { margin-top: 20px; }

.v2-squelette { background: var(--creux); border-radius: 8px; height: 16px; animation: v2-pulse 1.5s ease-in-out infinite; }
@keyframes v2-pulse { 0%,100% { opacity: .55; } 50% { opacity: 1; } }

/* La confirmation prend TOUTE la hauteur — on ne rate pas un « ✓ » de 14 px. */
.v2-confirme {
  position: fixed; inset: 0; z-index: 40; background: var(--surface);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
  animation: v2-monte .22s ease-out;
}
.v2-confirme-rond {
  width: 88px; height: 88px; border-radius: 50%;
  background: var(--encaisse-fond); color: var(--encaisse);
  display: flex; align-items: center; justify-content: center;
  animation: v2-pop .3s cubic-bezier(.2,1.4,.4,1);
}
.v2-confirme-rond svg { width: 44px; height: 44px; stroke-width: 2.4; }
.v2-confirme h2 { font-size: 24px; text-align: center; }
/* ⚠️ LA CONFIRMATION A UNE MARGE, ET C'EST LE MESSAGE LE PLUS LONG QUI L'A
   RÉVÉLÉE. Les confirmations existantes tiennent en trois mots (« Facture
   émise · D3-2026-001 ») : centrées, elles ne touchaient jamais les bords.
   Le décompte d'une réinitialisation en fait deux lignes pleines — et à
   390 px le texte partait de x=0 et finissait au pixel de droite. Le défaut
   était dans le socle depuis le début ; il attendait juste un message assez
   long pour se voir. */
.v2-confirme p { color: var(--doux); text-align: center; max-width: 30ch; padding: 0 20px; line-height: 1.5; }
@keyframes v2-monte { from { opacity: 0; transform: translateY(10px); } }
@keyframes v2-pop { from { transform: scale(.6); opacity: 0; } }

/* ═══════════════════════════ LA FICHE ═══════════════════════════════════ */
.v2-carte { background: var(--surface); border-radius: var(--r-carte); box-shadow: var(--ombre); padding: 18px; }
.v2-section-titre { font-size: 14px; font-weight: 700; color: var(--faible); text-transform: uppercase; letter-spacing: .07em; margin-bottom: 10px; }
/* ⚠️ LE TITRE D'UNE FICHE EST L'IDENTITÉ DE L'ÉCRAN, PAS UN SOUS-TEXTE.
   Le sous-agent Clients rendait le nom du client en `v2-salut` — 15 px,
   gris — faute d'un composant : l'écran d'une personne ne disait pas de qui
   il parle. Un héros de 46 px serait trop (ce n'est pas un montant) ; 26 px
   et du gras suffisent à en faire le premier mot lu. */
.v2-fiche-titre { font-size: 26px; font-weight: 700; letter-spacing: -0.025em; overflow-wrap: anywhere; }
/* Deux boutons côte à côte au téléphone — « Rappeler » et « Écrire » —
   là où `v2-actions` empile. Cent pixels gagnés sur une fiche. */
.v2-actions--paire { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.v2-actions--paire .v2-btn { width: 100%; }
.v2-brut { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 16px; line-height: 1.55; font-weight: 500; }
.v2-champs { display: grid; grid-template-columns: max-content 1fr; gap: 8px 18px; font-size: 14px; }
.v2-champs dt { color: var(--faible); font-weight: 600; }
.v2-champs dd { margin: 0; color: var(--doux); overflow-wrap: anywhere; }
.v2-actions { display: flex; flex-direction: column; gap: 10px; }
.v2-meta { display: flex; gap: 8px; flex-wrap: wrap; }
.v2-phrase { font-size: 15px; color: var(--doux); }
.v2-tete { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }

/* ═══════════════════════════ LA SAISIE ══════════════════════════════════ */
/*
 * ⚠️ 16 px SUR TOUT CHAMP, SANS EXCEPTION. En dessous, Safari iOS ZOOME au
 * focus et recadre la page : l'artisan perd le formulaire de vue au moment
 * précis où il commence à écrire. Motif mobile, valeur GLOBALE — un champ ne
 * change pas de taille selon l'appareil, et 16 px se lit mieux au bureau
 * aussi.
 *
 * ⚠️ ET RIEN N'ACCUSE AVANT UNE ACTION. `:invalid` n'est jamais stylé au
 * repos : un champ vide qu'on n'a pas encore touché n'est pas une faute.
 * L'erreur s'affiche à la saisie TERMINÉE ou à l'envoi, portée par
 * `aria-invalid` que l'écran pose lui-même.
 */
.v2-champ { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
/* ⚠️ `[hidden]` EST UNE RÈGLE UA DE FAIBLE SPÉCIFICITÉ, ET `.v2-champ` LA BAT.
   Un champ conditionnel — « Raison sociale » quand le client est une
   entreprise — restait donc à l'écran, attribut posé, sans un mot (piège
   n°42). Signalé par le sous-agent « Créer un dossier », qui a préféré
   afficher quatre champs plutôt que de forcer le CSS depuis un écran. */
.v2-champ[hidden] { display: none !important; }
.v2-champ-nom { font-size: 14px; font-weight: 700; color: var(--faible); }
.v2-saisie {
  width: 100%; min-height: var(--cible); padding: 12px 14px;
  font: inherit; font-size: var(--saisie); font-weight: 500;
  color: var(--encre); background: var(--surface);
  border: 1.5px solid var(--bord); border-radius: var(--r-tuile);
  appearance: none;
}
.v2-saisie:hover { border-color: var(--faible); }
.v2-saisie:focus { outline: none; border-color: var(--navy); box-shadow: 0 0 0 3px var(--navy-fond); }
.v2-saisie::placeholder { color: var(--faible); }
.v2-saisie[aria-invalid="true"] { border-color: var(--du); }
.v2-champ-erreur { font-size: 14px; font-weight: 600; color: var(--du); }
.v2-champ-aide { font-size: 14px; color: var(--faible); }
textarea.v2-saisie { min-height: 110px; resize: vertical; line-height: 1.5; }
/* Un montant, une quantité : à droite, tabulaire, comme partout ailleurs. */
.v2-saisie--nombre { text-align: right; font-variant-numeric: tabular-nums; }
/* La flèche d'un `select` — dessinée en CSS, zéro requête. */
select.v2-saisie {
  background-image: linear-gradient(45deg, transparent 50%, var(--doux) 50%),
                    linear-gradient(135deg, var(--doux) 50%, transparent 50%);
  background-position: calc(100% - 19px) 55%, calc(100% - 13px) 55%;
  background-size: 6px 6px, 6px 6px; background-repeat: no-repeat;
  padding-right: 38px;
}
.v2-groupe { display: grid; grid-template-columns: 1fr; gap: 14px; }
@media (min-width: 640px) { .v2-groupe--2 { grid-template-columns: 1fr 1fr; } }

/* ─── L'ÉDITEUR DE LIGNES (devis, facture) ──────────────────────────────
 *
 * ⚠️ AU TÉLÉPHONE, UNE LIGNE DE DOCUMENT EST UNE CARTE, PAS UNE RANGÉE DE
 * TABLEAU. Cinq colonnes dans 390 px donnent des champs de 60 px où l'on ne
 * peut ni lire ni taper. La même donnée, empilée, se saisit d'un pouce.
 * Au bureau, elle redevient une rangée : c'est là qu'on saisit vite.
 */
.v2-lignes { display: flex; flex-direction: column; gap: 12px; }
.v2-ligne-doc {
  background: var(--creux); border-radius: var(--r-tuile); padding: 14px;
  display: grid; gap: 10px; grid-template-columns: 1fr;
}
.v2-ligne-doc-total { font-weight: 700; font-variant-numeric: tabular-nums; text-align: right; }
@media (min-width: 760px) {
  .v2-ligne-doc {
    grid-template-columns: 1fr 84px 96px 88px 108px 44px;
    align-items: end; background: transparent; padding: 0;
  }
  .v2-ligne-doc .v2-champ-nom { display: none; }
  .v2-lignes--entete { display: grid; grid-template-columns: 1fr 84px 96px 88px 108px 44px;
    gap: 10px; font-size: 14px; font-weight: 700; color: var(--faible); text-transform: uppercase;
    letter-spacing: .06em; padding-bottom: 4px; }
}

/* Le récapitulatif d'un document : libellé à gauche, montant à droite. */
.v2-totaux { display: flex; flex-direction: column; gap: 8px; }
.v2-total-ligne { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; font-size: 15px; }
.v2-total-ligne dt { color: var(--doux); }
.v2-total-ligne dd { margin: 0; font-weight: 700; font-variant-numeric: tabular-nums; }
.v2-total-ligne--fort { font-size: 20px; padding-top: 8px; border-top: 1px solid var(--bord); }
.v2-total-ligne--fort dt { color: var(--encre); font-weight: 700; }

/* ─── LA FEUILLE MONTANTE ───────────────────────────────────────────────
 *
 * Elle remplace la boîte de dialogue : sur un téléphone, un panneau qui monte
 * du bas se referme d'un pouce, là où un dialogue centré demande de viser une
 * croix. Au bureau, elle se centre.
 *
 * ⚠️ `dialog` NATIF, ET PAS UN `div` : il apporte le focus piégé, la
 * fermeture par Échap et l'inertie de l'arrière-plan — trois comportements
 * qu'une réimplémentation rate toujours au moins à moitié.
 */
.v2-feuille {
  border: 0; padding: 0; background: var(--surface); color: var(--encre);
  width: 100%; max-width: 100%; max-height: 90dvh;
  margin: auto auto 0; border-radius: var(--r-carte) var(--r-carte) 0 0;
  box-shadow: var(--ombre-h);
}
.v2-feuille::backdrop { background: rgba(6, 10, 16, .45); }
.v2-feuille[open] { animation: v2-monte-feuille .24s cubic-bezier(.22,.61,.36,1); }
@keyframes v2-monte-feuille { from { transform: translateY(24px); opacity: 0; } }
/* LA CROIX DE FERMETURE — injectée par `armerFeuille` (main.js), donc posée
   UNE fois pour toutes les feuilles. 44 px de cible au doigt, jamais moins :
   c'est le plancher du dépôt, et une croix ratée au pouce vaut une croix
   absente. */
.v2-feuille-croix {
  position: absolute; top: 8px; right: 8px; z-index: 1;
  width: var(--cible); height: var(--cible);
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 999px; background: transparent; color: var(--faible);
  cursor: pointer;
}
.v2-feuille-croix:hover { background: var(--fond); color: var(--encre); }
.v2-feuille { position: relative; }
.v2-feuille-corps { padding: 20px 18px calc(20px + env(safe-area-inset-bottom)); display: flex; flex-direction: column; gap: 16px; }
.v2-feuille-poignee { width: 40px; height: 4px; border-radius: 999px; background: var(--bord); margin: 10px auto 0; }
@media (min-width: 640px) {
  .v2-feuille { max-width: 520px; margin: auto; border-radius: var(--r-carte); }
  .v2-feuille-poignee { display: none; }
}

/* ═══════════════════════════ LA BARRE BASSE ═════════════════════════════ */
.v2-basse { display: none; }

@media (max-width: 899px) {
  .v2-barre { display: none; }
  /* F7, 10/09 : la phrase ajoutée (« vous voyez et modifiez comme lui,
     tracé à votre nom ») tenait sur une ligne à 1440 px ; à 360 elle
     compressait le bouton « Revenir au pilotage » en trois mots empilés
     dans une colonne étroite — mesuré, pas supposé (piège n°40 : un
     flex-item sans taille minimale se fait comprimer sous son texte). Le
     bouton passe donc SOUS la phrase plutôt que de partager sa ligne. */
  .v2-bandeau-support { flex-wrap: wrap; row-gap: 2px; }
  .v2-bandeau-support span { flex: 1 1 100%; }
  .v2-page { padding-bottom: calc(88px + env(safe-area-inset-bottom)); }
  .v2-basse {
    display: grid; grid-template-columns: repeat(5, 1fr);
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 10;
    background: var(--surface); border-top: 1px solid var(--bord);
    padding: 6px 0 env(safe-area-inset-bottom);
  }
  .v2-basse a {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    min-height: var(--cible); font-size: 14px; font-weight: 600; color: var(--faible);
  }
  .v2-basse a svg { width: 23px; height: 23px; stroke-width: 1.9; }
  .v2-basse a[aria-current="page"] { color: var(--navy); }
  /* L'icône active prend une pastille ronde — le repère de Revolut. */
  .v2-basse a[aria-current="page"] svg {
    background: var(--navy-fond); border-radius: var(--r-pilule);
    padding: 3px 12px; box-sizing: content-box; margin-bottom: -3px;
  }
}

/* ═══════════════════════════ LE MOT DU BAS ══════════════════════════════ */
#v2-mot {
  position: fixed; left: 50%; bottom: 24px; transform: translate(-50%, 12px);
  max-width: min(92vw, 480px); padding: 14px 20px; border-radius: var(--r-pilule);
  background: var(--encre); color: var(--fond); font-size: 15px; font-weight: 600; text-align: center;
  opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease; z-index: 50;
}
#v2-mot.v2-mot--visible { opacity: 1; transform: translate(-50%, 0); }
@media (max-width: 899px) { #v2-mot { bottom: calc(96px + env(safe-area-inset-bottom)); } }
/* ⚠️ DANS UNE FEUILLE MODALE, LE MOT PASSE EN HAUT — ET CE N'EST PAS UN GOÛT.
 * La feuille occupe le BAS de l'écran (`margin: auto auto 0`) : garder le mot
 * à 96 px du bas le poserait sur le bouton primaire de la feuille, c'est-à-dire
 * sur le geste qu'il commente. Le haut est libre dans les deux formats — la
 * feuille est ancrée en bas à 390 px, centrée et bornée à 520 px au-dessus de
 * 640 px. */
#v2-mot.v2-mot--sur-feuille {
  top: calc(16px + env(safe-area-inset-top)); bottom: auto;
}

/* ═══════════════════════════ 9. VIVANT, MAIS JAMAIS SAUTANT ═════════════ */
.v2-btn, .v2-lien, .v2-nav a, .v2-basse a, .v2-ico-btn, .v2-onglet, .v2-ligne, .v2-tuile {
  transition: background-color .14s ease, color .14s ease, box-shadow .18s ease, transform .12s ease;
}
.v2-btn:active, .v2-ligne:active, .v2-tuile:active { transform: scale(.985); }
/* F4, brief pilote 10/09 : `transition` est un raccourci qui REMPLACE, il
   ne s'additionne pas — une seconde déclaration sur `.v2-nav a` (posée plus
   haut, pour animer le passage carré → rangée de la bascule latérale)
   perdait silencieusement contre CETTE règle-ci, simplement parce qu'elle
   vient après dans le fichier. Trouvé en mesurant `getComputedStyle`, pas
   supposé : `width, padding` étaient absents de la liste calculée. On
   répète donc la liste complète ICI, au dernier endroit qui décide. */
.v2-nav a {
  transition: background-color .14s ease, color .14s ease, box-shadow .18s ease,
    transform .12s ease, width .18s ease, padding .18s ease;
}

/* ─── LES TRANSITIONS ENTRE ÉCRANS — élément 9 de la planche ────────────
 *
 * ⚠️ LE SENS PORTE UNE INFORMATION, IL N'EST PAS DÉCORATIF. Entrer dans une
 * fiche glisse depuis la DROITE ; revenir glisse depuis la GAUCHE. C'est ce
 * qui fait qu'on sait, sans lire, si l'on avance ou si l'on recule — et c'est
 * la seule chose qu'une application native donne gratuitement et qu'une page
 * web doit construire.
 *
 * Le décalage est appliqué à `.v2-page`, pas à chaque bloc : animer les
 * enfants un par un ferait défiler l'écran par morceaux, ce qui se lit comme
 * un chargement, pas comme une navigation. */
.v2-page { animation: v2-entre-avant .24s cubic-bezier(.22,.61,.36,1) both; }
[data-sens="arriere"] .v2-page { animation-name: v2-entre-arriere; }
[data-sens="aucun"] .v2-page { animation-name: v2-fondu; }
@keyframes v2-entre-avant   { from { opacity: 0; transform: translateX(16px); } }
@keyframes v2-entre-arriere { from { opacity: 0; transform: translateX(-16px); } }
@keyframes v2-fondu         { from { opacity: 0; } }

/* Les tuiles du carrousel arrivent en cascade — 40 ms d'écart suffisent pour
   qu'on VOIE qu'il y en a plusieurs, sans qu'on attende la dernière. */
.v2-carrousel > * { animation: v2-monte-tuile .3s cubic-bezier(.22,.61,.36,1) both; }
.v2-carrousel > *:nth-child(2) { animation-delay: .04s; }
.v2-carrousel > *:nth-child(3) { animation-delay: .08s; }
.v2-carrousel > *:nth-child(4) { animation-delay: .12s; }
@keyframes v2-monte-tuile { from { opacity: 0; transform: translateY(10px) scale(.97); } }

/* ─── L'ÉTAT VIDE ILLUSTRÉ ──────────────────────────────────────────────
   Un rond de couleur et un trait ne suffisent pas : un état vide est le seul
   écran qu'un artisan voit quand tout va bien, et il doit avoir l'air d'une
   bonne nouvelle, pas d'une panne. */
.v2-illustration { width: 132px; height: 132px; margin: 0 auto 20px; display: block; }
.v2-illustration .v2-i-fond { fill: var(--encaisse-fond); }
.v2-illustration .v2-i-trait { stroke: var(--encaisse); stroke-width: 2.4; fill: none;
  stroke-linecap: round; stroke-linejoin: round; }
/* ⚠️ ÉTAIT `30` — E1, Houssem, 13/09 : « le coche de l'état vide est creux,
   un trait manque ». Défaut de TRACÉ, pas de couleur : le chemin
   `M38 61 L53 76 L83 44` (icones.js) mesure 15√2 + √(30²+32²) ≈ 65,08
   unités, et `stroke-dasharray: 30` ne réserve qu'UN dash de 30 suivi d'un
   GAP de 30 (valeur unique répétée) — la seconde moitié du trait montant
   (vers le point 83,44) tombait dans ce gap et restait invisible en
   permanence, animation terminée ou pas. `stroke-dasharray` doit couvrir la
   longueur RÉELLE du tracé pour que la technique « dessiner au trait »
   (dasharray = dashoffset = longueur, animé vers 0) dévoile la coche
   ENTIÈRE — 66 est arrondi au-dessus de 65,08 avec une marge, jamais en
   dessous (suite 893 : compare `dasharray` à la longueur calculée du path). */
.v2-illustration .v2-i-coche {
  stroke-dasharray: 66; stroke-dashoffset: 66;
  animation: v2-trace .5s .18s cubic-bezier(.22,.61,.36,1) forwards;
}
@keyframes v2-trace { to { stroke-dashoffset: 0; } }

.v2-btn:focus-visible, .v2-lien:focus-visible, .v2-ligne:focus-visible,
.v2-tuile:focus-visible, .v2-ico-btn:focus-visible, .v2-nav-bascule:focus-visible, a:focus-visible {
  outline: 3px solid var(--navy); outline-offset: 2px;
}

/* ⚠️ COUPE TOUT — ce n'est pas une politesse : qui a coché cette préférence a
   souvent une raison médicale. Le compteur qui monte s'arrête aussi, en JS. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* ═══════════════════════════ LE BUREAU ══════════════════════════════════ */
/*
 * ⚠️ AU BUREAU, C'EST LA MÊME APPLICATION — PAS UNE PLUS LARGE. La colonne
 * reste bornée à 760 px : au-delà, une ligne cesse de se lire d'un seul
 * balayage de l'œil et la densité se dilue. Élargir donnerait exactement le
 * back-office à trois colonnes qu'on ne veut plus.
 */
@media (min-width: 900px) {
  .v2-page { padding-top: 8px; }
  .v2-solde { font-size: 54px; }
  /* Le carrousel devient une RANGÉE : à la souris, un défilement horizontal
     est une punition. */
  /* ⚠️ `auto-fit`, PAS TROIS COLONNES FIXES. Avec quatre tuiles, une grille à
     trois colonnes en laisse UNE SEULE sur la deuxième rangée — l'œil lit
     « il en manque deux » là où il n'en manque aucune. `auto-fit` répartit ce
     qu'il y a, quel qu'en soit le nombre. */
  .v2-carrousel {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(172px, 1fr));
    overflow: visible; margin-inline: 0; padding: 2px 0 4px;
  }
  /* La grille ne défile pas : il n'y a donc RIEN à annoncer. Un dégradé sur
     une rangée complète, et des points sous une grille, désignent une suite
     qui n'existe pas — l'inverse exact de ce que B-C7 demande. */
  .v2-carrousel-cadre::after { display: none; }
  .v2-carrousel-points { display: none; }
  .v2-tuile { width: auto; max-width: none; min-width: 0; }
  .v2-actions { flex-direction: row; flex-wrap: wrap; }
  .v2-btn--pleine { width: auto; }
}

/*
 * L'IMPRESSION D'UN DOCUMENT — B-F1, item 8 (audit devis/factures, 10/09).
 *
 * ⚠️ « PDF » NE VEUT PAS DIRE UNE DÉPENDANCE. Le navigateur sait déjà
 * imprimer une page en PDF (« Enregistrer au format PDF » dans sa propre
 * boîte de dialogue) — zéro build, zéro librairie, exactement la doctrine du
 * dépôt. Ce bloc ne fait qu'une chose : au moment d'imprimer, ne montrer QUE
 * le document (`[data-imprimable]`), rien du reste de l'application.
 *
 * `devis.js#rendreApercu`, `factures.js#rendreApercu` et `rapport.js#rendre`
 * posent tous les trois cet attribut sur leur racine — un fait, un seul
 * endroit qui décide de ce qui s'imprime.
 *
 * ⚠️ TROUVÉ EN RELECTURE ADVERSE (item 9, 10/09) — `rapport.js` est le SEUL
 * des trois à loger un `<form>` (le sélecteur de période) À L'INTÉRIEUR de
 * `[data-imprimable]` ; devis/factures ne mettent que leur ligne d'actions
 * dans ce périmètre. Sans la règle `form` ci-dessous, `.v2-actions` était
 * bien masqué mais le `<select>`/label qui l'entoure (`.v2-groupe`,
 * `.v2-champ`, hors de `.v2-actions`) restait visible — un contrôle
 * interactif mort dans un document imprimé. La règle vise l'ÉLÉMENT `form`,
 * pas ses classes : elle couvre tout ce qu'un futur écran y ajouterait.
 */
@media print {
  .v2-barre, .v2-entete, .v2-basse, .v2-bandeau-support { display: none !important; }
  body * { visibility: hidden; }
  [data-imprimable], [data-imprimable] * { visibility: visible; }
  [data-imprimable] { position: absolute; inset: 0; }
  [data-imprimable] a.v2-lien, [data-imprimable] .v2-actions, [data-imprimable] form { display: none !important; }

  /*
   * ⚠️⚠️ UN DEVIS NON CONFORME NE S'IMPRIME PAS — arbitrage de Houssem,
   * 14/09 au soir (aucune sortie d'un devis sans les mentions obligatoires).
   *
   * `devis.js#rendreApercu` retire déjà `data-geste="imprimer"` du bouton
   * quand la fiche entreprise est incomplète — un clic sur le bouton de
   * CETTE page ne fait donc rien. Cette règle est le FILET : Ctrl/Cmd+P
   * passe par l'impression NATIVE du navigateur, qui ne demande la
   * permission de personne. `data-conforme="non"` marque le conteneur
   * imprimable ; on réduit alors tout son contenu au seul bandeau
   * d'avertissement (`.v2-devis-bloque`), jamais le document.
   */
  [data-imprimable][data-conforme="non"] > *:not(.v2-devis-bloque) { display: none !important; }
}
