/* FLS — feuille de style unique, forkée de hedo/src/crm/static/style.css (FL-T02).
   Palette bleu profond (distribution nautique) à la place du vert hedo.

   FL-T30 — LE SOCLE. Le fichier a été posé à vue, tâche après tâche, de FL-T02 à FL-T28 :
   15 tailles de texte entre 0,65 et 1,05 rem, 35 valeurs d'espacement, 43 couleurs en dur
   dont neuf gris pâles impossibles à distinguer. Cette passe ne redessine RIEN : elle
   nomme ce qui existait et aligne ce qui avait dérivé. Ce qui a bougé est listé, au pixel,
   dans docs/FL-T30-socle-visuel.md.

   Trois couches (ckm:design-system) : primitive → sémantique → composant.
   La règle de travail : dans le corps du fichier, on écrit un token, jamais une valeur.
   Les rares exceptions portent leur raison sur la ligne même.

   ORDRE DES BLOCS : il est porteur. Une douzaine de paires base/modificateur ont la MÊME
   spécificité (.onglet / .onglet-actif, .flash / .flash-ok, [class^="pastille-"] / les
   .pastille-*), donc seul l'ordre décide du vainqueur. Un bloc se déplace entier ; on ne
   réordonne jamais l'intérieur. */

/* ══ 0. TOKENS ═══════════════════════════════════════════════════════════════ */

:root {
  /* ── Couche 1 — PRIMITIVES : les valeurs de la maison ──────────────────────
     Reprises telles quelles, pas remplacées : ce sont elles, l'identité de
     l'Assistant. Trois seulement sont promues depuis le corps du fichier, où
     elles traînaient en dur. */
  --bleu: #14496b;
  --bleu-clair: #1f6d9e;
  --bleu-sombre: #0f3956;      /* promu : le filet de fuseaux (FL-T27) */
  --sable: #f7f5f1;
  --gris: #6c757d;
  --gris-clair: #9aa4ad;
  --rouge: #c62828;
  --orange: #e65100;
  --vert: #2e7d32;
  --or: #f9a825;
  --violet: #5e35b1;           /* promu : .badge-fls */
  --encre: #1a1a1a;
  --blanc: #fff;

  /* Typographie — 8 crans + 2 chiffres, contre 15 valeurs.
     Les crans sont SERRÉS EN BAS et larges en haut : dans un outil dense, toute
     la matière vit entre 11 et 15 px et les titres sont rares. Une progression
     géométrique régulière aurait été plus jolie à écrire et fausse à l'usage. */
  --texte-xs: 0.7rem;          /* étiquettes, pastilles, badges */
  --texte-s: 0.78rem;          /* second plan : mentions, colonnes secondaires */
  --texte-m: 0.85rem;          /* .hint — le plus employé du fichier */
  --texte-base: 0.92rem;       /* champs, boutons, corps d'un panneau */
  --texte-l: 1rem;             /* h3, valeur en creux */
  --texte-xl: 1.12rem;         /* la marque du bandeau */
  --texte-2xl: 1.25rem;        /* un chiffre secondaire */
  --texte-3xl: 1.6rem;         /* h1 */
  --chiffre-m: 1.35rem;        /* la valeur d'une tuile de bandeau */
  --chiffre-l: 1.75rem;        /* le chiffre qu'on vient lire : stat, prix */

  /* Espacement — 11 crans, contre 35 valeurs. Deux régimes, parce que le fichier
     fait deux choses : SERRER dans un composant (1 à 7, écart maximal 0,8 px) et
     AÉRER entre des blocs (8 à 11, où quelques pixels ne se voient pas). */
  --esp-1: 0.1rem;
  --esp-2: 0.2rem;
  --esp-3: 0.3rem;
  --esp-4: 0.45rem;
  --esp-5: 0.6rem;
  --esp-6: 0.75rem;
  --esp-7: 0.9rem;
  --esp-8: 1.1rem;
  --esp-9: 1.25rem;
  --esp-10: 1.5rem;
  --esp-11: 2.5rem;

  /* Rayons — 5 crans, contre 9 valeurs. `-l` (panneau flottant, encadré) et `-xl`
     (carte de page) restent distincts : deux familles réelles, pas une dérive. */
  --rayon-s: 4px;
  --rayon-m: 6px;
  --rayon-l: 8px;
  --rayon-xl: 10px;
  --rayon-pilule: 999px;

  --ombre: 0 1px 3px rgba(20, 73, 107, 0.10);
  --ombre-forte: 0 4px 16px rgba(20, 73, 107, 0.14);

  /* ── Couche 2 — SÉMANTIQUE : le rôle, jamais la valeur ─────────────────────
     C'est la couche qu'on écrit dans le corps du fichier. `--surface-carte` dit
     ce qu'on veut ; `#fff` disait seulement ce qu'on tape. */

  /* Surfaces */
  --surface-page: var(--sable);
  --surface-carte: var(--blanc);      /* une carte, une table, une tuile : 35 emplois */
  --surface-haute: var(--blanc);      /* ce qui FLOTTE : menu, panneau de loupe */
  --surface-creuse: var(--sable);     /* en-tête de .tbl, ligne dépliée, survol de menu */
  --surface-survol: #f4f9fc;          /* le survol d'une ligne de table */
  --surface-image: #eef1f4;           /* le cadre d'un visuel absent */
  --surface-code: #eceff1;
  --champ-fond: var(--blanc);

  /* Texte */
  --texte-fort: var(--encre);
  --texte-doux: #333;                 /* un paragraphe qu'on lit vraiment */
  --texte-libelle: #555;              /* un <label> */
  --texte-discret: var(--gris);
  --texte-efface: var(--gris-clair);
  --texte-sur-bleu: var(--blanc);

  /* Filets, focus */
  --filet: #dfe3e7;
  /* Le bleu fantôme : cet anneau portait un rgba(31, 90, 138, .14) qui n'était NI
     --bleu NI --bleu-clair — un troisième bleu apparu une seule fois. Rendu à
     --bleu-clair (#1f6d9e = 31,109,158). */
  --anneau-focus: 0 0 0 4px rgba(31, 109, 158, 0.14);

  /* Sur fond bleu — le bandeau. Ces voiles ne sont pas des couleurs mais des
     retraits : c'est le MÊME blanc qui recule plus ou moins. */
  --sur-bleu-fort: rgba(255, 255, 255, 0.9);
  --sur-bleu-moyen: rgba(255, 255, 255, 0.55);
  --voile-survol: rgba(255, 255, 255, 0.15);
  --voile-focus: rgba(255, 255, 255, 0.5);

  /* Les cinq états — fond pâle + encre + filet, APPARIÉS. C'est ici que tombent
     les doublons : le vert existait en #e8f5e9 et #e3f3e5, le rouge en #fdecea et
     #ffe6e6, l'orange en #fff8e1 et #fff3e0. Personne n'aurait su dire lequel
     employer, et personne ne le pouvait : ils ne voulaient rien dire de différent. */
  --etat-ok-fond: #e8f5e9;        --etat-ok-encre: var(--vert);          --etat-ok-filet: #c8e6c9;
  --etat-alerte-fond: #fff8e1;    --etat-alerte-encre: var(--orange);    --etat-alerte-filet: #ffe082;
  --etat-erreur-fond: #fdecea;    --etat-erreur-encre: var(--rouge);     --etat-erreur-filet: #f0c8c5;
  --etat-info-fond: #e3f2fd;      --etat-info-encre: var(--bleu-clair);
  --etat-neutre-fond: #eceff1;    --etat-neutre-encre: var(--gris);

  /* L'accent maison — le bleu pâle tiré de --bleu. Il RESTE distinct de
     --etat-info-fond : le premier dit « ceci appartient à l'Assistant »
     (.badge-client, .badge-cat, .btn-ia), le second dit « information »
     (.pastille-proforma, .pastille-recu). Les confondre serait un raccourci,
     pas une simplification. */
  --accent-fond: #e8f1f7;
  --accent-fond-survol: #d6e8f3;
  --accent-encre: var(--bleu);
  --accent-filet: #c5dcea;
  --violet-fond: #ede7f6;

  /* ── ALIAS À NE PAS SUPPRIMER ──────────────────────────────────────────────
     feedback_box.css (chargé sur TOUS les écrans par base.html) et notice.css
     lisent ces deux noms-là et ne déclarent aucun :root. Les renommer casserait
     le bouton 💡 partout. Ils restent, en alias de la couche sémantique. */
  --text: var(--encre);
  --border: var(--filet);

  /* ── Couche 3 — COMPOSANT ──────────────────────────────────────────────────
     Ce qui n'appartient qu'à un composant, y compris les valeurs volontairement
     uniques : les garder ici les rend visibles au lieu de les cacher au milieu
     d'une règle. */
  --table-rembourrage: var(--esp-6) var(--esp-7);
  --table-rayon: var(--rayon-xl);
  --tbl-entete-fond: var(--surface-creuse);
  --tbl-entete-encre: var(--texte-discret);
  --pipeline-entete-fond: var(--bleu);
  --pipeline-entete-encre: var(--texte-sur-bleu);

  --carte-rayon: var(--rayon-xl);

  /* ── LE PORTRAIT (FL-T31) ─────────────────────────────────────────────────
     La rupture est UNE valeur, 560 px, et c'est celle qui existait déjà pour le
     repli de la barre : au-dessous, l'outil est « en portrait » — un seul mot à
     retenir plutôt que quatre seuils. 560 sépare le téléphone tenu droit (360 à
     430 px) de tout le reste, le même téléphone couché compris (734 à 932 px),
     où une table a la place de rester une table.

     Elle n'est PAS écrite ici : une media query ne lit pas les variables CSS
     (elle est évaluée avant la cascade). Le token existe pour que le nombre soit
     nommé quelque part et que `test_portrait.py` puisse exiger qu'il n'y ait
     qu'une seule rupture dans le fichier. */
  --rupture-portrait: 560px;

  /* 44 px : le plus petit carré qu'un pouce atteint sans viser — c'est le chiffre
     d'Apple (HIG) et, à 48 dp près, celui de Material. François travaille debout,
     dans une boutique, souvent d'une main. Tout ce qui se touche s'y aligne au
     § 5 ; rien au-dessus de la rupture ne bouge, la souris vise mieux. */
  --cible-tactile: 44px;

  /* La colonne de libellé d'une carte. Bornée en bas pour que « Échéance » ne se
     coupe pas en deux, et en haut à 38 % pour que la VALEUR garde la majorité de
     la largeur : c'est elle qu'on vient lire. */
  --carte-libelle: minmax(5.5rem, 38%);
  --btn-rayon: var(--rayon-m);
  --champ-rayon: var(--rayon-m);
  --badge-rayon: var(--rayon-pilule);

  /* Le relevé d'heures (FL-T27). Le libellé recule (42 %) DERRIÈRE l'heure (88 %)
     parce qu'on vient y chercher une heure, pas un nom de ville. Ce couple 88/42
     est un choix écrit : il garde ses valeurs propres au lieu d'être aligné sur
     les voiles du bandeau. */
  --fuseau-heure: rgba(255, 255, 255, 0.88);
  --fuseau-lieu: rgba(255, 255, 255, 0.42);

  /* Les voiles de bord du filet de fuseaux. Le second N'EST PAS `transparent` : un
     dégradé vers `transparent` s'interpole en passant par du noir transparent chez
     plusieurs moteurs, et le voile virerait au gris sale. Il faut LE MÊME bleu à
     alpha zéro. */
  --voile-fuseaux-large: 2.5rem;
  --voile-fuseaux-large-mobile: 1.6rem;
  --bleu-sombre-nul: rgba(15, 57, 86, 0);

  --onglet-actif-tag: #cfe0ec;             /* le compteur d'un onglet actif, sur le bleu */
  --fiche-gouttiere: 2rem;                 /* entre les visuels et les infos d'un produit */

  --voile-bleu: rgba(20, 73, 107, 0.85);   /* la pastille vidéo, sur une image */
  --voile-noir: rgba(0, 0, 0, 0.55);       /* le compteur d'images, sur une image */

  --surface-signalee: #fffdf5;             /* une ligne de table qui est un panier */
  --surface-signalee-survol: #fdf7e6;      /* la même, sous la souris (FL-T58) */
  --corps-mail-fond: #f7f7f5;              /* le papier d'un message relu (FL-T18) */
  --corps-mail-filet: #d8d5cd;             /* son filet, chaud comme le papier */

  --porte-lettre: 132px;
  --porte-lettre-taille: 3.6rem;
  --porte-lettre-mobile: 104px;
  --porte-lettre-taille-mobile: 2.8rem;
}

/* ══ 1. SOCLE — le nu : éléments sans classe ═════════════════════════════════ */

* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: var(--surface-page); color: var(--texte-fort); font-size: 14px; line-height: 1.5;
}

h1 { font-size: var(--texte-3xl); letter-spacing: -0.01em; }
h3 { margin: var(--esp-9) 0 var(--esp-5); font-size: var(--texte-l); }
/* `em` et non un cran de l'échelle : un bout de code se met en retrait de SON
   texte, quelle que soit la taille de celui-ci. */
code { background: var(--surface-code); padding: var(--esp-1) var(--esp-3);
       border-radius: var(--rayon-s); font-size: 0.85em; }

label { display: flex; flex-direction: column; gap: var(--esp-3);
        font-size: var(--texte-s); font-weight: 600; color: var(--texte-libelle); }
label.inline { flex-direction: row; align-items: center; gap: var(--esp-3); font-weight: 500; }
input, textarea, select { padding: var(--esp-4) var(--esp-5); border: 1px solid var(--filet);
                          border-radius: var(--champ-rayon); font-size: var(--texte-base);
                          font-family: inherit; }
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--bleu-clair); }

main { max-width: 1200px; margin: 0 auto; padding: var(--esp-10); }
footer { text-align: center; padding: var(--esp-9); color: var(--texte-efface);
         font-size: var(--texte-s); border-top: 1px solid var(--filet); margin-top: var(--esp-11); }

/* La version, en bas à droite. `bottom` la pose AU-DESSUS de la bulle d'idée (3rem de
   haut, 1.25rem d'écart au bord dans feedback_box.css) plutôt que dessous : le coin est
   déjà pris, et deux éléments fixes au même point s'annulent. `pointer-events: none` pour
   qu'elle ne vole jamais un clic destiné à ce qu'il y a derrière — elle se lit, elle ne
   se manipule pas. */
/* La file des notes, repliée à l'arrivée (2026-08-28). Le résumé porte le compte, donc
   il se lit sans ouvrir — c'est ce qui rend le repli acceptable sur un écran dont la
   raison d'être est de dire ce qu'il reste à faire. */
/* FL-T46 — la nature d'une note : posée en trois boutons quand elle manque, dite en
   une ligne quand elle est là. */
.note-natures { display: flex; flex-wrap: wrap; gap: var(--esp-3); margin: var(--esp-4) 0; }
.note-nature { margin: var(--esp-4) 0; font-style: italic; }

.notes-repli > summary { cursor: pointer; }
.notes-repli[open] > summary { margin-bottom: var(--esp-5); }
/* FL-T97 : le bloc « Côté technique » de l'accueil — replié, discret, en bas des faits. */
.technique { margin-top: var(--esp-5); }
.technique > summary { cursor: pointer; color: var(--texte-discret); font-size: var(--texte-m); }
.technique[open] > summary { margin-bottom: var(--esp-4); }

.version-app {
  position: fixed; right: 1.25rem; bottom: calc(1.25rem + 3rem + .4rem);
  margin: 0; z-index: 9998; pointer-events: none;
  font-size: var(--texte-xs, .75rem); color: var(--texte-efface);
  letter-spacing: .02em;
}

/* ══ 2. SQUELETTE — le bandeau, la loupe, le filet de fuseaux ════════════════ */

.entete { position: sticky; top: 0; z-index: 100; }
nav { background: var(--bleu); color: var(--texte-sur-bleu); padding: var(--esp-6) var(--esp-10);
      display: flex; align-items: center; justify-content: space-between; }
.brand { color: var(--texte-sur-bleu); text-decoration: none; font-weight: 700;
         font-size: var(--texte-xl); letter-spacing: 0.06em; }
.nav-links { display: flex; gap: var(--esp-4); align-items: center; flex-wrap: wrap; }
.nav-links a { color: var(--sur-bleu-fort); text-decoration: none;
               padding: var(--esp-3) var(--esp-6); border-radius: var(--rayon-s); }
.nav-links a:hover { background: var(--voile-survol); }
.nav-gauche { display: flex; align-items: center; gap: var(--esp-4); }

/* ── La loupe du bandeau (loupe.js, FL-T28) ──────────────────────────────── */
.loupe-zone { position: relative; }
.btn-loupe {
  background: none; border: none; color: var(--texte-sur-bleu); cursor: pointer;
  font-size: var(--texte-xl); padding: var(--esp-3) var(--esp-4); line-height: 1;
  border-radius: var(--rayon-s);
}
.btn-loupe:hover, .btn-loupe[aria-expanded="true"] { background: var(--voile-survol); }

/* Le panneau est ANCRÉ sous la loupe, pas centré : c'est un menu déroulant. Il déborde
   du bandeau et du filet de fuseaux, d'où le z-index — `.entete` est déjà en sticky
   z-index 100, celui-ci ne joue qu'à l'intérieur. */
.panneau-loupe {
  position: absolute; top: calc(100% + var(--esp-5)); left: 0; z-index: 50;
  width: min(28rem, calc(100vw - 2rem));
  display: flex; flex-direction: column;
  background: var(--surface-haute); color: var(--texte-fort);
  border: 1px solid var(--filet); border-radius: var(--rayon-l);
  box-shadow: var(--ombre-forte);
  overflow: hidden;
}
/* Sans cette règle, `hidden` ne cache RIEN : le `display:flex` ci-dessus l'emporterait
   sur le `display:none` que le navigateur applique à [hidden]. */
.panneau-loupe[hidden] { display: none; }
.panneau-loupe input[type=search] {
  border: none; border-bottom: 1px solid var(--filet); border-radius: 0;
  padding: var(--esp-6) var(--esp-7); font-size: var(--texte-base); font-family: inherit;
}
.panneau-loupe input[type=search]:focus { outline: none; background: var(--surface-creuse); }
.panneau-resultats { list-style: none; overflow-y: auto; max-height: 60vh; margin: 0; padding: 0; }
/* Panneau ouvert, champ vide : pas de bloc blanc vide sous la barre de recherche. */
.panneau-resultats:empty { display: none; }
.panneau-resultats li + li { border-top: 1px solid var(--filet); }
.panneau-resultats a {
  display: flex; flex-direction: column; gap: var(--esp-2);
  padding: var(--esp-5) var(--esp-7); text-decoration: none; color: var(--texte-fort);
}
.panneau-resultats a:hover { background: var(--surface-creuse); }
.panneau-resultats .resultat-titre { display: flex; gap: var(--esp-4); align-items: baseline; flex-wrap: wrap; }
.panneau-resultats .discret { color: var(--texte-discret); font-size: var(--texte-s); }
/* L'étiquette de TYPE : elle ne dit rien tant qu'il n'y a qu'une source, et elle est ce
   qui rendra la loupe lisible le jour où commandes, factures et messages y cohabiteront. */
.loupe-badge {
  background: var(--surface-creuse); border: 1px solid var(--filet); border-radius: var(--badge-rayon);
  padding: var(--esp-1) var(--esp-4); font-size: var(--texte-xs); color: var(--texte-discret);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.panneau-vide { padding: var(--esp-6) var(--esp-7); color: var(--texte-discret); font-size: var(--texte-base); }

.nav-user { color: var(--sur-bleu-moyen); font-size: var(--texte-s); margin-left: var(--esp-3); }
.btn-logout { font-size: var(--texte-s); }

/* NAV — les menus par thème (FL-T26)
   `<details>` plutôt qu'un menu maison : le clavier, le tactile et le lecteur d'écran
   marchent sans une ligne de JavaScript. Le script de base.html ne fait qu'ajouter le
   confort — refermer le voisin, le clic à côté, Échap. */
.nav-menu { position: relative; }
.nav-menu > summary {
  color: var(--sur-bleu-fort); padding: var(--esp-3) var(--esp-6); border-radius: var(--rayon-s);
  cursor: pointer; white-space: nowrap; list-style: none; user-select: none;
}
.nav-menu > summary::-webkit-details-marker { display: none; }
.nav-menu > summary::after { content: ' ▾'; font-size: 0.75em; opacity: 0.6; }
.nav-menu > summary:hover,
.nav-menu[open] > summary { background: var(--voile-survol); }
.nav-menu-liste {
  position: absolute; top: calc(100% + var(--esp-4)); left: 0; min-width: 12rem; z-index: 200;
  display: flex; flex-direction: column; gap: var(--esp-1);
  background: var(--surface-haute); border-radius: var(--rayon-l); box-shadow: var(--ombre-forte);
  padding: var(--esp-3);
}
.nav-links .nav-menu-liste a { color: var(--texte-fort); padding: var(--esp-4) var(--esp-6); white-space: nowrap; }
.nav-links .nav-menu-liste a:hover { background: var(--surface-creuse); }

/* ── LE ☰ DU TÉLÉPHONE — FL-T32 ───────────────────────────────────────────────
   AU-DESSUS DE LA RUPTURE, CE REPLI N'EXISTE PAS. `display: contents` fait
   disparaître la boîte sans toucher à son contenu : les quatre `.nav-menu` restent
   les enfants directs du `flex` de `.nav-links`, au même rang, avec le même
   `gap` — pas un pixel ne bouge, et test_portrait le vérifie plutôt que de le croire.
   `<summary>` masqué à part : `display: contents` sur le parent ne le retire pas. */
/* AU-DESSUS DE LA RUPTURE, CE REPLI N'EXISTE PAS. `display: contents` supprime la
   boîte sans toucher au contenu : les quatre `.nav-menu` restent les enfants directs
   du flex de `.nav-links`, au même rang et au même `gap` — pas un pixel ne bouge, et
   test_portrait le vérifie plutôt que de le croire.

   La case est déplacée hors de l'écran et non `hidden` : `hidden` la rendrait
   infocalisable, et le ☰ ne s'ouvrirait plus au clavier. */
.menu-tel-case { position: absolute; width: 1px; height: 1px; opacity: 0;
                 pointer-events: none; }
.menu-tel-bouton { display: none; }
.menu-tel-liste { display: contents; }

/* L'HEURE AILLEURS (FL-T27) — le filet de fuseaux sous la barre
   Un bleu plus sombre que `--bleu` : le filet doit se lire comme l'ombre de la barre,
   pas comme un second bandeau. Le libellé recule (42 %) derrière l'heure (88 %), parce
   qu'on vient y chercher une heure, pas un nom de ville.

   `tabular-nums` n'est pas un détail : sans lui les chiffres n'ont pas la même chasse
   et TOUTE la ligne frémit à chaque minute — le défaut le plus visible d'une horloge
   en texte.

   La ligne DÉFILE au lieu de passer sur deux étages : l'en-tête est collé, le doubler
   en hauteur mangerait l'écran d'un téléphone en portrait à chaque page. Pas de media
   query pour cela — à largeur normale les huit places tiennent et rien ne se déclenche.

   Le fond et les voiles sont sur `.fuseaux`, le défilement sur `.fuseaux-piste`. Il
   FAUT deux éléments : un dégradé posé sur la piste se peindrait derrière les heures,
   et laisserait voir le sable de la page au bord du bandeau sombre. */
.fuseaux { position: relative; background: var(--bleu-sombre); font-size: var(--texte-xs); }
.fuseaux-piste { display: flex; gap: var(--esp-8); align-items: baseline;
                 padding: var(--esp-2) var(--esp-10);
                 /* `safe center` et surtout PAS `center` seul : un conteneur défilant
                    centré rogne le DÉBUT de son contenu dès qu'il déborde, et
                    `scrollLeft` ne descendant pas sous zéro, Tahiti deviendrait
                    inatteignable — précisément en portrait, le cas où la ligne défile.
                    `safe` retombe sur un alignement au début dans ce cas. Un navigateur
                    qui ne connaît pas le mot-clé ignore la déclaration entière et garde
                    l'alignement au début : la dégradation va dans le bon sens, ce qui ne
                    serait pas vrai avec un `center` de repli. */
                 justify-content: safe center;
                 overflow-x: auto; scrollbar-width: none;
                 /* PAS seulement du confort : en glissant, Chrome démarre une sélection
                    de texte et fait défiler le conteneur tout seul pour l'étendre. Ce
                    défilement s'ajoutait au nôtre — mesuré à 580 px de ligne pour 229 px
                    de curseur, la ligne partait en glissade. Un relevé d'heures ne se
                    sélectionne de toute façon pas, et la barre fait déjà de même sur
                    `.nav-menu > summary`. */
                 user-select: none; -webkit-user-select: none;
                 /* Le geste s'arrête au bandeau : sans cela, arriver au bout de la
                    ligne propage le défilement à la page — ou déclenche le retour
                    arrière par balayage sur mobile. */
                 overscroll-behavior-x: contain; }
.fuseaux-piste::-webkit-scrollbar { display: none; }
/* Le focus clavier n'arrive ici que lorsque la ligne déborde vraiment (fuseaux.js pose
   alors `tabindex`) : une zone défilante doit être atteignable aux flèches. */
.fuseaux-piste:focus-visible { outline: 2px solid var(--voile-focus); outline-offset: -2px; }

/* Les voiles de bord — le seul indice qu'il reste des places hors du cadre. La barre de
   défilement est masquée (elle doublerait la hauteur du filet pour trois pixels utiles),
   donc sans eux rien ne dit que la ligne bouge. Ils s'allument par classe, côté par
   côté : pas de voile à gauche quand on est déjà au début. */
.fuseaux::before, .fuseaux::after {
  content: ''; position: absolute; top: 0; bottom: 0; width: var(--voile-fuseaux-large);
  pointer-events: none; opacity: 0; transition: opacity 0.2s ease; }
.fuseaux::before { left: 0;
  background: linear-gradient(to right, var(--bleu-sombre) 25%, var(--bleu-sombre-nul)); }
.fuseaux::after { right: 0;
  background: linear-gradient(to left, var(--bleu-sombre) 25%, var(--bleu-sombre-nul)); }
.fuseaux.debord-gauche::before,
.fuseaux.debord-droite::after { opacity: 1; }

/* Le glisser à la souris. Le tactile a déjà son défilement natif — avec une inertie que
   le navigateur fait mieux que nous — donc fuseaux.js ne s'occupe QUE de la souris, qui
   elle n'a aucun moyen d'agir : molette verticale, barre masquée. Le curseur est le
   contrat : une main ouverte n'apparaît que si la ligne déborde pour de bon. */
.fuseaux.deplacable .fuseaux-piste { cursor: grab; }
.fuseaux.deplacant .fuseaux-piste { cursor: grabbing; }
/* `flex: none` fait le défilement : sans lui les places se laissent comprimer par le
   conteneur et le texte déborde de son entrée au lieu que la ligne glisse. */
.fuseau { display: flex; flex: none; gap: var(--esp-4); white-space: nowrap;
          transition: opacity 0.6s ease; }
/* `em` : ces deux-là se règlent sur la taille du filet, pas sur l'échelle de la page. */
.fuseau b { font-weight: 600; font-size: 0.92em; letter-spacing: 0.07em;
            text-transform: uppercase; color: var(--fuseau-lieu); }
.fuseau time { color: var(--fuseau-heure); font-variant-numeric: tabular-nums; }
/* Hors heures ouvrables : atténué. La transition ci-dessus évite qu'une place bascule
   sèchement dans la nuit sous ses yeux à 18 h 00 pile. */
.fuseau.nuit { opacity: 0.42; }

/* ══ 3. COMPOSANTS — ce qui revient d'un écran à l'autre ═════════════════════ */

/* EN-TÊTE DE PAGE */
.cat-header { display: flex; justify-content: space-between; align-items: flex-start;
              gap: var(--esp-8); flex-wrap: wrap; margin-bottom: var(--esp-9); }
.cat-header-actions { display: flex; gap: var(--esp-5); align-items: center; }
.inline-form { display: inline; margin: 0; }
.fil { color: var(--texte-discret); font-size: var(--texte-s); margin-bottom: var(--esp-6); }
.fil a { color: var(--bleu-clair); text-decoration: none; }
.fil-boutique { color: var(--texte-efface); }
.result-count { color: var(--texte-discret); font-size: var(--texte-s); margin-bottom: var(--esp-6); }

.hint { color: var(--texte-discret); font-size: var(--texte-m); }
/* DEUX PHRASES D'AIDE NE FONT PAS UN PARAGRAPHE (FL-T58, trouvé en regardant l'écran).
   Le reset `* { margin: 0 }` de la l. 219 ne laisse AUCUN espace entre deux `.hint`
   voisins : la porte de la visite en a deux — ce que l'écran fait, puis ce qu'on fait
   d'un nom inconnu — et elles se lisaient comme un seul bloc, mesuré à 0 px d'écart.
   HUIT gabarits sont dans ce cas (activités, boîtes, commissions, relevés, journal,
   notes, clients manquants, porte de la visite) : la règle est donc sur la classe et
   non sur l'écran, et elle ne s'applique qu'au SECOND — une phrase seule ne bouge pas. */
.hint + .hint { margin-top: var(--esp-5); }
.empty { color: var(--texte-discret); text-align: center; padding: var(--esp-11) var(--esp-8);
         background: var(--surface-carte); border-radius: var(--carte-rayon); box-shadow: var(--ombre); }

/* STATS */
.stats-bar { display: flex; gap: var(--esp-7); margin-bottom: var(--esp-9); flex-wrap: wrap; }
.stat { background: var(--surface-carte); border-radius: var(--carte-rayon);
        padding: var(--esp-7) var(--esp-10); text-align: center;
        box-shadow: var(--ombre); min-width: 120px; }
.stat-num { display: block; font-size: var(--chiffre-l); font-weight: 700; color: var(--bleu); line-height: 1.2; }
.stat-label { font-size: var(--texte-xs); color: var(--texte-discret); text-transform: uppercase; letter-spacing: 0.06em; }
.stat-val .stat-num { font-size: var(--texte-2xl); color: var(--bleu-clair); }
.stat-warn { border: 1px solid var(--etat-alerte-filet); }
.stat-warn .stat-num { color: var(--orange); }

/* FILTRES */
.filters { background: var(--surface-carte); padding: var(--esp-7) var(--esp-8);
           border-radius: var(--carte-rayon); margin-bottom: var(--esp-9); box-shadow: var(--ombre); }
.filters form { display: flex; gap: var(--esp-5); flex-wrap: wrap; align-items: center; }
.search { flex: 1; min-width: 200px; padding: var(--esp-4) var(--esp-6);
          border: 1px solid var(--filet); border-radius: var(--champ-rayon); }
.filters select { padding: var(--esp-4); border: 1px solid var(--filet);
                  border-radius: var(--champ-rayon); background: var(--champ-fond); }
.filters button { background: var(--bleu); color: var(--texte-sur-bleu); border: none;
                  padding: var(--esp-4) var(--esp-8); border-radius: var(--btn-rayon);
                  cursor: pointer; font-weight: 600; }
.filters button:hover { background: var(--bleu-clair); }
.btn-reset { color: var(--texte-discret); text-decoration: none; padding: var(--esp-4); font-size: var(--texte-m); }
/* Le témoin de la recherche immédiate. htmx pose et retire `htmx-request` tout seul ;
   il ne reste qu'à dire à quoi ça ressemble. Il occupe sa place en permanence pour que
   les filtres ne sautent pas d'un demi-centimètre à chaque frappe. */
.cherche { color: var(--texte-efface); font-size: var(--texte-s); font-style: italic; }

/* BADGES — tous sur --badge-rayon. Les 10px d'origine étaient DÉJÀ une pilule : sur une
   étiquette d'une seule ligne, le rayon dépassait la demi-hauteur et le navigateur le
   ramenait à un demi-cercle. `--rayon-pilule` dit ce qui se passait vraiment. */
.badge-stock { font-size: var(--texte-xs); padding: var(--esp-1) var(--esp-4);
               border-radius: var(--badge-rayon); font-weight: 600; white-space: nowrap; }
.badge-instock { background: var(--etat-ok-fond); color: var(--etat-ok-encre); }
.badge-outofstock { background: var(--etat-erreur-fond); color: var(--etat-erreur-encre); }
.badge-zone { background: var(--surface-creuse); padding: var(--esp-2) var(--esp-4);
              border-radius: var(--rayon-s); font-size: var(--texte-s); }
.badge-marque { background: var(--bleu); color: var(--texte-sur-bleu); font-size: var(--texte-xs);
                font-weight: 700; letter-spacing: 0.06em; padding: var(--esp-2) var(--esp-5);
                border-radius: var(--rayon-s); }
.badge-role { display: inline-block; font-size: var(--texte-xs); font-weight: 600;
              padding: var(--esp-1) var(--esp-4); border-radius: var(--badge-rayon); margin-right: var(--esp-3); }
.badge-client { background: var(--accent-fond); color: var(--accent-encre); }
.badge-fournisseur { background: var(--etat-alerte-fond); color: var(--etat-alerte-encre); }
.badge-fls { background: var(--violet-fond); color: var(--violet); }

/* BOUTONS */
.btn-primary { background: var(--bleu); color: var(--texte-sur-bleu); border: none;
               padding: var(--esp-4) var(--esp-9); border-radius: var(--btn-rayon); cursor: pointer;
               font-size: var(--texte-base); font-weight: 600; text-decoration: none; display: inline-block; }
.btn-primary:hover { background: var(--bleu-clair); }
.btn-block { width: 100%; margin-top: var(--esp-4); }
.btn-export { background: var(--surface-carte); color: var(--bleu); border: 1px solid var(--filet);
              padding: var(--esp-4) var(--esp-7); border-radius: var(--btn-rayon);
              text-decoration: none; font-size: var(--texte-s); }
.btn-export:hover { background: var(--surface-creuse); }
.btn-mini { font-size: var(--texte-s); padding: var(--esp-3) var(--esp-5); border-radius: var(--rayon-s);
            border: 1px solid var(--filet); background: var(--surface-carte); color: var(--bleu);
            cursor: pointer; text-decoration: none; display: inline-block; width: auto; }
.btn-mini:hover { background: var(--surface-creuse); }
.btn-danger { color: var(--rouge); border-color: var(--etat-erreur-filet); background: var(--surface-carte);
              cursor: pointer; padding: var(--esp-3) var(--esp-6); border-radius: var(--btn-rayon);
              border: 1px solid var(--etat-erreur-filet); width: auto; }
.btn-danger:hover { background: var(--etat-erreur-fond); }

/* FORMULAIRES
   `.form-grid` était déclarée DEUX FOIS (l. 374 et l. 512 d'avant FL-T30), avec des
   valeurs différentes ; la seconde gagnait en silence. Une seule règle, qui garde
   exactement ce qui s'affichait. */
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
             gap: var(--esp-6) var(--esp-7); margin-bottom: var(--esp-6); }
.form-grid label, .ligne-form label { display: flex; flex-direction: column; gap: var(--esp-2);
             font-size: var(--texte-s); color: var(--texte-discret); }
.form-grid .large { grid-column: 1 / -1; }
.form-grid .case, .ligne-form .case { flex-direction: row; align-items: center; gap: var(--esp-4);
             color: var(--texte-fort); }
/* La largeur pleine est réservée aux formulaires de gestion : la règle globale `input`
   du socle sert aussi la barre de filtres et l'écran de connexion, qui n'en veulent pas. */
.form-grid input, .form-grid select, .form-grid textarea,
.offre-champs input, .offre-champs select,
.tbl input, .tbl select { width: 100%; font: inherit; }
input.mini, .tbl input.mini { max-width: 6rem; }
.ligne-form { display: flex; gap: var(--esp-4); align-items: flex-end; flex-wrap: wrap;
              padding-top: var(--esp-5); border-top: 1px dashed var(--filet); }
.ligne-form input, .ligne-form select { width: auto; min-width: 8rem; }
.roles-choix { display: flex; gap: var(--esp-8); margin-bottom: var(--esp-7); flex-wrap: wrap; }
.form-nouveau { background: var(--surface-carte); border-radius: var(--carte-rayon);
                box-shadow: var(--ombre); margin-top: var(--esp-9); overflow: hidden; }
.form-nouveau summary { padding: var(--esp-6) var(--esp-8); cursor: pointer; font-weight: 600; color: var(--bleu); }
.form-nouveau form { padding: 0 var(--esp-8) var(--esp-8); }

/* FL-T15 — correction de l'identité d'un client sur sa ligne, repliée par défaut :
   la table reste lisible, et la fiche se corrige sans changer de page.
   Replié, « modifier » se range au bout de la ligne du client et n'en ajoute pas une
   seconde ; déplié, il reprend toute la largeur qu'il lui faut. Le `white-space` est
   rendu à sa valeur normale : la ligne du client est `nowrap`, pas ce formulaire. */
.edit-compte { display: inline-block; vertical-align: middle; margin-left: var(--esp-4);
               white-space: normal; }
.edit-compte[open] { display: block; margin-left: 0; margin-top: var(--esp-4); }
.edit-compte summary { cursor: pointer; font-size: var(--texte-xs); color: var(--bleu-clair); }
.edit-compte form { display: flex; flex-direction: column; gap: var(--esp-3); margin-top: var(--esp-4);
                    padding: var(--esp-5); background: var(--surface-creuse); border-radius: var(--champ-rayon); }
.edit-compte label { display: flex; flex-direction: column; gap: var(--esp-2);
                     font-size: var(--texte-xs); color: var(--texte-discret); }
.edit-compte input, .edit-compte select { font-size: var(--texte-s); padding: var(--esp-3); }

/* FL-T69 lot 2 — glisser un document ou un dossier (`rangement.js`, SortableJS embarqué).
   `.dossiers-enfants` n'a AUCUN rôle visuel : `display: contents` efface la boîte que le
   <div> ajouterait, l'arbre se lit exactement comme avant qu'il existe — c'est un repère
   pour le script, pas une mise en page. */
.dossiers-enfants { display: contents; }
.drag-handle { cursor: grab; color: var(--texte-discret); margin-right: var(--esp-3);
               user-select: none; touch-action: none; }
.drag-handle:active { cursor: grabbing; }
/* Les classes que SortableJS pose lui-même pendant le geste — jamais du HTML statique.
   `.sortable-ghost` est l'emplacement d'arrivée, qu'il atterrisse dans un <summary> visé
   ou dans la liste d'origine ; `.sortable-chosen` est l'élément qu'on tient. */
.sortable-ghost { opacity: .35; background: var(--surface-creuse); }
.sortable-chosen .drag-handle { color: var(--bleu); }

/* FL-T70 lot 3 — LES DEUX VOLETS (`classement.js`). `.classement-arbre` porte l'arbre
   d'hier, inchangé ; `.classement-panneau` est le cadre que le script remplit en y
   déplaçant le contenu du dossier ouvert. Sans JavaScript, le panneau reste vide — la
   grille ne montre alors qu'une colonne pleine à gauche et du vide à droite, jamais du
   contenu caché : c'est la même règle que FL-T68/FL-T69 (l'enrichissement s'ajoute, il ne
   cache rien tant qu'il n'est pas actif). */
.classement-volets { display: grid; grid-template-columns: minmax(220px, 300px) 1fr;
                     gap: var(--esp-9); align-items: start; margin-bottom: var(--esp-8); }
.classement-arbre, .classement-panneau { min-width: 0; }

/* Le fil d'Ariane — posé par le script, jamais par le gabarit (il ne connaît pas
   encore le dossier ouvert). Un bouton et non un lien : il ne change pas d'URL, il
   déplace du contenu déjà dans la page. */
.fil-ariane { display: flex; flex-wrap: wrap; align-items: center; gap: var(--esp-3);
             margin-bottom: var(--esp-6); font-size: var(--texte-s); color: var(--texte-discret); }
.fil-lien { background: none; border: none; padding: 0; font: inherit;
           color: var(--bleu-clair); text-decoration: underline; cursor: pointer; }
.fil-lien:hover { color: var(--bleu); }
.fil-courant { color: var(--texte-fort); font-weight: 600; }

/* Le tri (côté navigateur, § 5 de la tâche) et la barre d'actions de la sélection —
   posés au-dessus de la liste À PLAT que le script construit en fusionnant les groupes
   par année (le tri porte sur tous les documents du dossier, pas sur chacun de ses
   replis). */
.classement-outils { margin-bottom: var(--esp-5); }
.classement-tri { display: flex; flex-wrap: wrap; align-items: center; gap: var(--esp-4);
                 color: var(--texte-discret); font-size: var(--texte-s); margin-bottom: var(--esp-4); }
.classement-tri button.tri-actif { background: var(--bleu); color: #fff; border-color: var(--bleu); }
/* N'apparaît que s'il y a une sélection (§5) — posée `hidden` par le script tant
   qu'aucune case n'est cochée, jamais par ce fichier. `:not([hidden])` et non
   `.classement-actions` seul : un sélecteur de classe bat la règle UA `[hidden]` à
   spécificité égale — piège trouvé à l'écran, même famille que `.btn-primary` contre
   `hidden` en FL-T68. */
.classement-actions:not([hidden]) {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--esp-5);
  background: var(--surface-signalee); border-radius: var(--rayon-s);
  padding: var(--esp-5) var(--esp-6); margin-bottom: var(--esp-5);
}
.classement-compteur { font-weight: 600; color: var(--texte-fort); }
.doc-case { margin-right: var(--esp-4); }
/* Une case cochée n'a rien à glisser : le geste de la sélection remplace celui du
   glisser dans le panneau, ils ne se battent pas pour la même poignée. */
.classement-panneau .drag-handle { display: none; }

/* Le retour à l'arbre, sous 560 px seulement (§ 5.2 plus bas) — au-dessus de la
   rupture les deux volets restent côte à côte et ce bouton n'a rien à faire. */
.classement-retour { display: none; margin-bottom: var(--esp-5); }

/* FL-T71 lot 4 — VOIR SANS TÉLÉCHARGER (docs/FL-T71-apercu-grille-recherche.md).
   La recherche, le filtre et la bascule liste/grille rejoignent `.classement-tri` dans
   `.classement-outils` — trois rangées d'un même bandeau d'outils, pas un second bandeau. */
.classement-filtre { display: flex; flex-wrap: wrap; align-items: center; gap: var(--esp-4);
                     margin-bottom: var(--esp-4); }
.classement-recherche { min-width: 12rem; flex: 1; }
.classement-vue { display: flex; gap: var(--esp-3); margin-bottom: var(--esp-4); }
.classement-vue button.vue-actif { background: var(--bleu); color: #fff; border-color: var(--bleu); }
.classement-filtre-vide { margin-top: var(--esp-5); }

/* La vignette (§1 de la tâche) — posée par `classement.js` DANS le lien qu'elle
   illustre, cachée en vue liste (elle double alors l'icône `.doc-emoji` déjà dans le
   texte) et seule visible que la vue grille montre. Aucune vignette fabriquée : une
   image sert sa propre image en petit, tout le reste garde son icône générique. */
.doc-vignette { display: none; }
.doc-vignette img { width: 96px; height: 96px; object-fit: cover; border-radius: var(--rayon-s); }
.doc-vignette-icone { font-size: 2.5rem; line-height: 1; }

/* La vue grille — un `<ul>` en grille plutôt qu'en colonne, chaque `.note-carte`
   devenant une tuile centrée. `position: relative` sur la tuile est ce qui permet à la
   case de sélection de se poser en coin plutôt que de pousser la vignette de côté.
   `:not([hidden])` et non `.note-carte` seul — MÊME PIÈGE que `.classement-actions` et
   `.classement-apercu` : un `display: flex` de trois classes bat le `[hidden]` de l'UA à
   spécificité égale, et la recherche masquerait le nom sans jamais cacher la tuile
   (trouvé à l'écran en vérifiant CE lot, pas en test). */
.doc-liste.mode-grille { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
                         gap: var(--esp-6); }
.doc-liste.mode-grille .note-carte:not([hidden]) { position: relative; display: flex; flex-direction: column;
                                     align-items: center; text-align: center; gap: var(--esp-3); }
.doc-liste.mode-grille .note-carte a { display: flex; flex-direction: column; align-items: center;
                                       gap: var(--esp-3); text-decoration: none; word-break: break-word; }
.doc-liste.mode-grille .doc-vignette { display: block; }
.doc-liste.mode-grille .doc-emoji { display: none; }
.doc-liste.mode-grille .doc-case { position: absolute; top: 0; left: 0; margin: 0; }
.doc-liste.mode-grille .col-fonction,
.doc-liste.mode-grille .edit-compte { font-size: var(--texte-xs); }

/* L'aperçu — sous la liste, dans le même panneau que le dossier (§4.2 du plan), jamais
   un troisième volet. `:not([hidden])` et non `.classement-apercu` seul : même piège de
   spécificité que `.classement-actions` plus haut, évité de la même façon. */
.classement-apercu:not([hidden]) {
  margin-top: var(--esp-6); border: 1px solid var(--filet); border-radius: var(--rayon-l);
  padding: var(--esp-6); background: var(--surface-carte);
}
.apercu-entete { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
                gap: var(--esp-4); margin-bottom: var(--esp-5); }
.apercu-actions { display: flex; gap: var(--esp-4); }
.apercu-corps iframe { width: 100%; height: 70vh; border: 0; border-radius: var(--rayon-s); background: #fff; }
.apercu-corps img { display: block; max-width: 100%; max-height: 70vh; margin: 0 auto; border-radius: var(--rayon-s); }

/* TABLES — deux classes, un seul socle.
   `.tbl` (48 emplois) et `.pipeline-table` (5) ne diffèrent QUE par leur en-tête, leur
   rembourrage et l'alignement vertical de leurs cellules. FL-T30 leur avait donné les
   mêmes tokens ; FL-T31 leur donne la même RÈGLE de base — le fond, le rayon, l'ombre et
   le repli des colonnes ne sont plus écrits deux fois.

   Ce qui reste distinct reste distinct, et le dit : fusionner leur en-tête (sable contre
   bleu) changerait l'aspect de 48 écrans ou de 5, ce qui est une décision de dessin, pas
   un nettoyage. Elle n'est pas prise ici.

   LA VRAIE FUSION EST AILLEURS : le patron portrait du § 5 ne connaît NI l'une NI
   l'autre. Il s'applique à `[data-cartes]`, l'attribut que cartes.js pose sur toute table
   qui porte des lignes de données. Une troisième classe de table demain hériterait du
   patron sans qu'une ligne bouge ici. */
.tbl, .pipeline-table { width: 100%; background: var(--surface-carte); border-collapse: collapse;
                        border-radius: var(--table-rayon); box-shadow: var(--ombre); overflow: hidden; }
.pipeline-table th { background: var(--pipeline-entete-fond); color: var(--pipeline-entete-encre);
                     padding: var(--table-rembourrage); text-align: left;
                     font-weight: 600; font-size: var(--texte-xs); text-transform: uppercase; letter-spacing: 0.05em; }
.pipeline-table td { padding: var(--table-rembourrage); border-bottom: 1px solid var(--filet); vertical-align: top; }
/* LE SURVOL DE LIGNE, POUR LES DEUX (FL-T58 — dette de FL-T31 § 10, point 4).
   `.pipeline-table` l'avait, `.tbl` non, et rien ne justifiait la différence : ce sont
   les mêmes lignes de données, à la même souris. La retenue d'alors — « c'est sans effet
   au doigt, et l'ajouter changerait 48 écrans » — se lit maintenant à l'envers : c'est
   parce qu'elle porte 48 écrans que `.tbl` est celle des deux qui en a le plus besoin,
   et une facture de vingt colonnes se suit à la ligne ou ne se suit pas.

   `tbody tr` et non `tr` : quatre tables de la fiche client portent un `tfoot` de
   totaux, dont trois avec des `<td class="num">` — un total qui s'allume au passage de
   la souris se lit comme une ligne de données de plus, et il est justement le contraire.
   `.pipeline-table` y gagne la même borne, qui ne lui retire rien : aucune n'a de pied.
   Sa règle d'avant (`.pipeline-table tr:hover td`) est remplacée, pas doublée. */
.tbl tbody tr:hover td,
.pipeline-table tbody tr:hover td { background: var(--surface-survol); }
/* SAUF UNE LIGNE QUI SIGNALE QUELQUE CHOSE. `.ligne-panier` marque une facture en
   retard, un écart de commission non acquitté, un panier non validé — et le survol
   d'au-dessus est plus spécifique qu'elle : sans cette règle, poser la souris sur la
   ligne qu'on est venu regarder EFFACE la raison pour laquelle elle est signalée.
   Elle garde donc sa teinte, un cran plus dense pour que le survol se voie quand même. */
.tbl tbody tr.ligne-panier:hover td,
.pipeline-table tbody tr.ligne-panier:hover td { background: var(--surface-signalee-survol); }
.col-fonction { color: var(--texte-discret); font-size: var(--texte-s); }

/* La liste des clients : UNE ligne par client (2026-08-22).
   Chaque client en tenait deux à quatre — raison sociale, ville et courriel passaient
   sous leur libellé. Trente clients remplissaient l'écran : on ne voyait plus la liste,
   on la parcourait. Le gabarit met désormais tout en ligne (`_comptes_liste.html`) ;
   ce `nowrap` est ce qui garantit que ça le reste, y compris pour un nom de contact en
   deux mots ou une île dont le nom est long. */
.table-clients td { white-space: nowrap; vertical-align: middle; }
/* La seule cellule qui puisse déborder : elle porte l'enseigne, la raison sociale et la
   ville. Elle se coupe aux points de suspension plutôt que de pousser toute la table
   hors de l'écran — le texte entier reste dans l'infobulle. */
.table-clients .enseigne { display: inline-block; max-width: 26rem; overflow: hidden;
                           text-overflow: ellipsis; white-space: nowrap;
                           vertical-align: middle; }

/* Tableaux de gestion */
.tbl { margin-bottom: var(--esp-8); }
.tbl th, .tbl td { padding: var(--esp-5) var(--esp-6); text-align: left; border-bottom: 1px solid var(--filet);
                   vertical-align: middle; }
.tbl thead th { background: var(--tbl-entete-fond); font-size: var(--texte-s); text-transform: uppercase;
                letter-spacing: 0.04em; color: var(--tbl-entete-encre); }
.tbl tbody tr:last-child td { border-bottom: none; }
.tbl .num { text-align: right; }
.tbl tfoot th, .tbl tfoot td { background: var(--surface-creuse); border-bottom: none; }
.tbl .total-fort th, .tbl .total-fort td { font-size: var(--texte-l); color: var(--bleu); font-weight: 700; }
.tbl-cle th { width: 8rem; color: var(--texte-discret); font-weight: 600; }
.tbl-voc { max-width: 34rem; }
.col-domaines { font-size: var(--texte-m); color: var(--texte-discret); }
.col-actions { text-align: right; white-space: nowrap; }
.ligne-inactive td { opacity: 0.55; }
.ligne-panier td { background: var(--surface-signalee); }
.alerte-douce { color: var(--orange); font-size: var(--texte-m); }

/* Onglets d'activité (FL-T08). Sept liens, pas un menu déroulant : l'axe « par
   activité » est ce que François choisit en premier chaque matin, il doit être
   visible sans un clic préalable. */
.onglets { display: flex; flex-wrap: wrap; gap: var(--esp-3); margin-bottom: var(--esp-8); }
.onglet { display: flex; align-items: baseline; gap: var(--esp-4); padding: var(--esp-4) var(--esp-6);
          background: var(--surface-carte); border: 1px solid var(--filet); border-radius: var(--rayon-l);
          text-decoration: none; color: var(--texte-fort); font-weight: 600; font-size: var(--texte-base); }
.onglet:hover { border-color: var(--bleu-clair); }
.onglet-actif { background: var(--bleu); border-color: var(--bleu); color: var(--texte-sur-bleu); }
.onglet-tag { font-size: var(--texte-xs); font-weight: 500; color: var(--texte-discret); }
.onglet-actif .onglet-tag { color: var(--onglet-actif-tag); }

/* MESSAGES ET BLOCS */
.flash { padding: var(--esp-5) var(--esp-7); border-radius: var(--rayon-l);
         margin-bottom: var(--esp-8); font-size: var(--texte-base); }
.flash-ok  { background: var(--etat-ok-fond); color: var(--etat-ok-encre); border: 1px solid var(--etat-ok-filet); }
.flash-err { background: var(--etat-erreur-fond); color: var(--etat-erreur-encre); border: 1px solid var(--etat-erreur-filet); }
/* Ni « ok » ni « erreur » : un signal, pas un verdict — le texte qui suit est encore
   à lire, pas à corriger d'urgence. Sert au bandeau « contient de la transcription
   automatique » avant une correction (visites, débriefs). */
.flash-alerte { background: var(--etat-alerte-fond); color: var(--etat-alerte-encre); border: 1px solid var(--etat-alerte-filet); }

.bloc { background: var(--surface-carte); border-radius: var(--carte-rayon);
        padding: var(--esp-8) var(--esp-9); margin-bottom: var(--esp-9); box-shadow: var(--ombre); }
.bloc > h3:first-child { margin-top: 0; }
.bloc-danger { border-left: 3px solid var(--rouge); }
.note-basse { margin-top: var(--esp-10); }

/* DÉBRIEFS — un fond très légèrement teinté par AUTEUR (FL-T26)
   `--teinte` est posée en style inline par le gabarit ; la teinte vient du nom de
   l'auteur, jamais d'une table de couleurs à tenir à jour. Ce n'est PAS un token : c'est
   un canal de données, qui n'a rien à faire en :root. Saturation basse et clarté très
   haute : il s'agit de distinguer d'un coup d'œil qui a écrit, pas de peindre — le texte
   doit rester aussi lisible que sur blanc, et un bloc traité (grisé) ne doit pas
   redevenir voyant. */
.bloc-auteur { background: hsl(var(--teinte, 210) 45% 97%);
               border-left: 3px solid hsl(var(--teinte, 210) 40% 72%); }

/* RÉGLAGES D'UNE BOÎTE, sous la boîte (FL-T26)
   La ligne dépliante ne doit pas se lire comme une donnée de plus : fond neutre, pas de
   bordure haute, pour qu'elle reste rattachée visuellement à la ligne au-dessus. */
.ligne-reglage > td { background: var(--surface-creuse); padding: 0 var(--esp-6) var(--esp-6); border-top: none; }
.reglage-boite > summary {
  cursor: pointer; padding: var(--esp-4) 0; color: var(--bleu-clair); font-weight: 600;
}
.reglage-boite > summary:hover { text-decoration: underline; }
.reglage-boite[open] > summary { margin-bottom: var(--esp-4); }

/* Pastilles d'état — un mot, une couleur, les mêmes partout.
   ATTENTION au sélecteur : `[class^="pastille-"]` a la spécificité d'une classe et exige
   que `pastille-` soit le DÉBUT de l'attribut. Écrire `class="pastille pastille-x"` dans
   un gabarit ferait disparaître cette règle de base en silence — vérifié, aucun gabarit
   du CRM ne le fait. Il doit rester AVANT toutes les `.pastille-*`. */
[class^="pastille-"] { font-size: var(--texte-xs); padding: var(--esp-1) var(--esp-4);
                       border-radius: var(--badge-rayon);
                       background: var(--etat-neutre-fond); color: var(--etat-neutre-encre);
                       white-space: nowrap; }
.pastille-en_vente, .pastille-livree { background: var(--etat-ok-fond); color: var(--etat-ok-encre); }
.pastille-hors_vente, .pastille-annulee { background: var(--etat-erreur-fond); color: var(--etat-erreur-encre); }
.pastille-panier { background: var(--etat-alerte-fond); color: var(--etat-alerte-encre); }
.pastille-proforma { background: var(--etat-info-fond); color: var(--etat-info-encre); }
.pastille-canon { background: var(--bleu); color: var(--texte-sur-bleu); }
.pastille-off { background: var(--etat-neutre-fond); color: var(--texte-efface); }
/* Une visite (ou un débrief) qui contient de la transcription automatique — même
   teinte que .flash-alerte, un signal et non un verdict. */
.pastille-dictee { background: var(--etat-alerte-fond); color: var(--etat-alerte-encre); }
/* Les huit états de l'affaire (FL-T10). Trois couleurs seulement, et pas huit : ce qui
   compte à l'œil est « c'est fini » (réception), « c'est reparti en arrière »
   (modification du devis) et « ça avance ». Le reste garde le gris par défaut du sélecteur
   d'attribut ci-dessus — huit teintes distinctes ne se retiennent pas. */
.pastille-reception { background: var(--etat-ok-fond); color: var(--etat-ok-encre); }
.pastille-modification_devis { background: var(--etat-alerte-fond); color: var(--etat-alerte-encre); }
/* `.pastille-confirmee` était déclarée DEUX FOIS — verte plus haut, bleue ici — et c'est
   le bleu qui gagnait. Elle reste donc bleue, et une seule fois. */
.pastille-confirmee, .pastille-confirmation { background: var(--etat-info-fond); color: var(--etat-info-encre); }
/* L'issue d'un reliquat : perdu se voit, recommandé se rassure, attendu reste neutre. */
.pastille-perdu { background: var(--etat-erreur-fond); color: var(--etat-erreur-encre); }
.pastille-recommande { background: var(--etat-ok-fond); color: var(--etat-ok-encre); }
/* FL-T17 — les verdicts du contrôleur et les états d'un relevé. `sous_bareme` et `devis`
   sont EN ROUGE et pas en orange comme un simple écart : ce sont des diagnostics nommés,
   c'est-à-dire de l'argent qu'on sait réclamer, alors qu'un « écart » se discute encore.
   `ecart_total` est rouge aussi, mais il accuse la LECTURE et non le mandant. */
.pastille-conforme { background: var(--etat-ok-fond); color: var(--etat-ok-encre); }
.pastille-ecart { background: var(--etat-alerte-fond); color: var(--etat-alerte-encre); }
.pastille-sous_bareme, .pastille-devis, .pastille-ecart_total { background: var(--etat-erreur-fond); color: var(--etat-erreur-encre); }
.pastille-depouille { background: var(--etat-ok-fond); color: var(--etat-ok-encre); }
.pastille-recu { background: var(--etat-info-fond); color: var(--etat-info-encre); }
/* FL-T30 — l'échec de relève d'une boîte (admin_boites). Le gabarit écrivait
   `pastille-hors`, un nom qui n'existait dans aucune feuille : l'étiquette « échec »
   tombait sur le gris par défaut, indistinguable de « lecture » ou « n'envoie pas ».
   Un échec d'authentification est une panne, et se lit comme telle. */
.pastille-echec { background: var(--etat-erreur-fond); color: var(--etat-erreur-encre); }
/* Le geste d'acquittement, replié dans une cellule de tableau. Il est OUVERT tant que
   l'alerte n'est pas expliquée : une explication qu'il faut aller chercher derrière un
   triangle ne se donne pas, et c'est tout l'enjeu de FL-T17. */
.acq { margin-top: var(--esp-3); }
.acq > summary { cursor: pointer; font-size: var(--texte-xs); color: var(--texte-discret); }
.acq .inline-form { display: flex; flex-wrap: wrap; gap: var(--esp-3); margin-top: var(--esp-3); }
.acq input[type="text"], .acq select { font-size: var(--texte-s); }
/* Une affaire dans la case « en cours » du tableau de bord : deux ou trois lignes serrées
   qui doivent rester lisibles dans une cellule de tableau. */
.affaire-case { margin-bottom: var(--esp-3); }
.affaire-case:last-child { margin-bottom: 0; }

/* ══ 4. ÉCRANS — ce qui n'appartient qu'à un écran ═══════════════════════════ */

/* GRILLE CATALOGUE */
.produit-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
                gap: var(--esp-8); }
.produit-card { background: var(--surface-carte); border-radius: var(--carte-rayon); overflow: hidden;
                box-shadow: var(--ombre); text-decoration: none; color: inherit;
                display: flex; flex-direction: column;
                transition: transform 0.12s, box-shadow 0.12s; }
.produit-card:hover { transform: translateY(-2px); box-shadow: var(--ombre-forte); }
/* `aspect-ratio` et les dimensions d'image ci-dessous ne sont PAS de l'espacement : elles
   décrivent un cadre, et ne passent donc pas par l'échelle. */
.produit-visuel { position: relative; aspect-ratio: 1 / 1; background: var(--surface-image);
                  display: flex; align-items: center; justify-content: center; overflow: hidden; }
.produit-visuel img { width: 100%; height: 100%; object-fit: contain; }
.produit-novisuel { color: var(--texte-efface); font-size: var(--texte-s); }
.badge-video { position: absolute; top: var(--esp-4); right: var(--esp-4); background: var(--voile-bleu);
               color: var(--texte-sur-bleu); border-radius: 50%; width: 1.7rem; height: 1.7rem;
               display: flex; align-items: center; justify-content: center; font-size: var(--texte-xs); }
.badge-nbimg { position: absolute; bottom: var(--esp-4); right: var(--esp-4); background: var(--voile-noir);
               color: var(--texte-sur-bleu); font-size: var(--texte-xs); padding: var(--esp-1) var(--esp-4);
               border-radius: var(--badge-rayon); }
.produit-corps { padding: var(--esp-6) var(--esp-7) var(--esp-7); display: flex; flex-direction: column;
                 flex: 1; gap: var(--esp-3); }
.produit-corps h3 { margin: 0; font-size: var(--texte-base); line-height: 1.35; }
.badge-cat { display: inline-block; align-self: flex-start; font-size: var(--texte-xs); text-transform: uppercase;
             letter-spacing: 0.05em; color: var(--bleu-clair); background: var(--accent-fond);
             padding: var(--esp-1) var(--esp-4); border-radius: var(--badge-rayon); font-weight: 600; }
.produit-resume { font-size: var(--texte-s); color: var(--texte-discret); overflow: hidden;
                  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; }
.produit-pied { margin-top: auto; padding-top: var(--esp-4); display: flex; justify-content: space-between;
                align-items: center; gap: var(--esp-4); flex-wrap: wrap; }
.produit-prix { font-weight: 700; color: var(--bleu); font-size: var(--texte-l); }
.prix-barre { color: var(--texte-efface); font-weight: 400; font-size: var(--texte-s); margin-right: var(--esp-3); }

/* FICHE PRODUIT */
.fiche-produit { display: grid; grid-template-columns: minmax(280px, 460px) 1fr; gap: var(--fiche-gouttiere);
                 align-items: start; }
/* `6rem` en dur, et c'est voulu : cette valeur suit la HAUTEUR RÉELLE de l'en-tête collé,
   filet de fuseaux compris (FL-T27). Ce n'est pas un espacement, c'est une mesure. */
.fiche-medias { position: sticky; top: 6rem; }
.visuel-principal { width: 100%; background: var(--surface-carte); border-radius: var(--carte-rayon);
                    box-shadow: var(--ombre); aspect-ratio: 1 / 1; object-fit: contain; padding: var(--esp-4); }
.visuel-absent { width: 100%; aspect-ratio: 1 / 1; background: var(--surface-image); border-radius: var(--carte-rayon);
                 display: flex; align-items: center; justify-content: center; color: var(--texte-efface); }
.vignettes { display: flex; gap: var(--esp-4); margin-top: var(--esp-5); flex-wrap: wrap; }
.vignette { width: 62px; height: 62px; object-fit: cover; border-radius: var(--champ-rayon); cursor: pointer;
            border: 2px solid transparent; background: var(--surface-carte); }
.vignette.active, .vignette:hover { border-color: var(--bleu-clair); }
/* Le seul noir du fichier, et il n'a pas à être un token : c'est la boîte aux lettres
   d'une vidéo, noire par définition et non par décision de palette. */
.produit-video { width: 100%; margin-top: var(--esp-6); border-radius: var(--carte-rayon); background: #000; }

/* LA FICHE CLIENT EN QUATRE SECTIONS (FL-T56)
   Seize <h2> se suivaient à plat, tous du même poids : rien ne disait où finissait
   ce qu'on relit avant d'entrer et où commençait ce qu'on lit une fois assis. Deux rangs
   suffisent — le titre de section, souligné, et le titre de bloc (`h3`, déjà stylé plus
   haut). Aucune boîte, aucun cadre : la fiche est déjà faite de cartes et de tableaux, un
   troisième contenant les enfermerait deux fois. */
.fiche-section { margin-top: var(--esp-11); }
.fiche-section > h2 { font-size: var(--texte-2xl); letter-spacing: -0.01em;
                      padding-bottom: var(--esp-4); margin-bottom: var(--esp-6);
                      border-bottom: 2px solid var(--filet); }
/* Le premier bloc d'une section colle à son titre : la marge haute de `h3` (2 rem) y
   ouvrirait un trou plus grand que celui qui SÉPARE les sections. */
.fiche-section > h3:first-of-type { margin-top: var(--esp-7); }
/* Les deux gestes réversibles de l'identité, côte à côte plutôt qu'empilés — deux
   formulaires d'un bouton chacun, qui tenaient chacun sa ligne. */
.fiche-gestes { display: flex; gap: var(--esp-5); flex-wrap: wrap; margin-bottom: var(--esp-7); }

.fiche-infos h1 { margin-bottom: var(--esp-4); }
.fiche-sous { display: flex; gap: var(--esp-4); align-items: center; flex-wrap: wrap; margin-bottom: var(--esp-7); }
.ref { color: var(--texte-discret); font-size: var(--texte-s); font-family: ui-monospace, monospace; }
.fiche-prix { font-size: var(--chiffre-l); font-weight: 800; color: var(--bleu); margin-bottom: var(--esp-6); }
.fiche-devise { font-size: var(--texte-s); font-weight: 500; color: var(--texte-efface);
                text-transform: uppercase; letter-spacing: 0.08em; margin-left: var(--esp-3); }
.fiche-resume { font-size: var(--texte-base); color: var(--texte-doux); margin-bottom: var(--esp-8); }
.fiche-desc p { margin-bottom: var(--esp-5); }
.fiche-meta { margin-top: var(--esp-10); color: var(--texte-efface); font-size: var(--texte-s); }

.specs { width: 100%; border-collapse: collapse; background: var(--surface-carte); border-radius: var(--table-rayon);
         overflow: hidden; box-shadow: var(--ombre); margin-bottom: var(--esp-9); }
.specs th { text-align: left; padding: var(--esp-5) var(--esp-7); width: 33%; background: var(--surface-creuse);
            color: var(--texte-discret); font-size: var(--texte-s); text-transform: uppercase;
            letter-spacing: 0.05em; font-weight: 600; vertical-align: top; }
.specs td { padding: var(--esp-5) var(--esp-7); border-bottom: 1px solid var(--filet); }
.specs tr:last-child td, .specs tr:last-child th { border-bottom: none; }

.voisins-titre { margin-top: var(--esp-11); }
.voisins { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }

/* BLOC IA */
.ia-bloc { margin-top: var(--esp-10); padding-top: var(--esp-8); border-top: 1px solid var(--filet); }
.btn-ia { background: var(--accent-fond); color: var(--accent-encre); border: 1px solid var(--accent-filet);
          padding: var(--esp-4) var(--esp-8); border-radius: var(--btn-rayon); cursor: pointer;
          font-size: var(--texte-m); font-weight: 600; }
.btn-ia:hover { background: var(--accent-fond-survol); }
.moteur-etat { font-size: var(--texte-xs); color: var(--texte-efface); margin-left: var(--esp-5); }
.ia-attente { font-size: var(--texte-s); color: var(--bleu-clair); opacity: 0; transition: opacity 0.15s; margin-left: var(--esp-4); }
.htmx-request .ia-attente, .htmx-request.ia-attente { opacity: 1; }
.ia-carte { background: var(--surface-carte); border-left: 3px solid var(--bleu-clair); border-radius: var(--rayon-l);
            padding: var(--esp-7) var(--esp-8); margin-top: var(--esp-7); box-shadow: var(--ombre); }
.ia-erreur { border-left-color: var(--rouge); color: var(--rouge); }
.ia-mock { font-size: var(--texte-xs); color: var(--orange); text-transform: uppercase;
           letter-spacing: 0.05em; margin-bottom: var(--esp-4); }
.ia-resume { font-weight: 600; margin-bottom: var(--esp-5); }
.ia-args { margin: 0 0 var(--esp-5) var(--esp-8); }
.ia-args li { margin-bottom: var(--esp-3); }
.ia-desc { color: var(--texte-doux); }
.ia-note { font-size: var(--texte-xs); color: var(--texte-efface); margin-top: var(--esp-5); }

/* FICHE CLIENT (FL-T19)
   FL-T30 — le logo d'un client. `fiche_compte.html` posait cette classe depuis toujours
   sans qu'AUCUNE feuille ne la définisse : l'image sortait à sa taille de fichier, et un
   logo de 1200 px poussait l'en-tête de la fiche hors de l'écran. */
.logo-fiche { max-height: 3rem; max-width: 10rem; object-fit: contain; vertical-align: middle; }

/* Mise en vente : une carte par boutique, atténuée tant qu'il n'y a pas de ligne */
.offre { border: 1px solid var(--filet); border-radius: var(--rayon-l); padding: var(--esp-6) var(--esp-7);
         margin-bottom: var(--esp-5); }
.offre-absente { background: var(--surface-creuse); border-style: dashed; }
.offre-titre { display: flex; align-items: center; gap: var(--esp-4); margin-bottom: var(--esp-5); }
.offre-champs { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
                gap: var(--esp-5) var(--esp-6); }
.offre-actions { margin-top: var(--esp-6); }
.offre-retrait { display: flex; align-items: center; gap: var(--esp-5);
                 /* Le retrait négatif n'est pas un cran d'échelle : il ANNULE la marge
                    du bloc au-dessus. Il se lit mieux en clair. */
                 margin: -0.2rem 0 var(--esp-8) var(--esp-2); }

/* Visuels */
.visuels { display: flex; flex-wrap: wrap; gap: var(--esp-6); margin-bottom: var(--esp-6); }
.visuel { width: 150px; border: 1px solid var(--filet); border-radius: var(--rayon-l); overflow: hidden; }
.visuel img { width: 100%; height: 110px; object-fit: cover; display: block; }
.visuel-principal-marque { border-color: var(--bleu); }
.visuel-actions { display: flex; gap: var(--esp-3); align-items: center; justify-content: center;
                  padding: var(--esp-3); flex-wrap: wrap; }

.fiche-commande { display: grid; grid-template-columns: 2fr 1fr; gap: var(--esp-9); align-items: start; }
.suivi-actions { display: flex; gap: var(--esp-10); flex-wrap: wrap; }

/* Questionnaire (FL-T12) — les deux écrans de lecture côté CRM. Le formulaire, lui, a sa
   propre feuille (questionnaire.css) : il est public et ne doit rien à celle du CRM. */
.jeton { display: inline-block; word-break: break-all; user-select: all; }
.q-lue { margin: 0 0 var(--esp-7); line-height: 1.5; white-space: pre-wrap; }

/* BANDEAU DE FAITS — FL-T14, refondu par FL-T32.
   Une tuile par fait, et l'état porte une information et non une décoration.

   LA TUILE ENTIÈRE EST LE LIEN, et c'est le point de FL-T32. FL-T31 avait mesuré les
   cibles tactiles de tout l'outil et laissé un seul manquement derrière elle : le lien
   « affaires en cours » d'une tuile, à 13 px de haut, parce que le corriger proprement
   voulait dire refaire le gabarit de l'accueil. C'est fait — la cible est la carte.

   `.bd-absent` reste défini bien qu'aucun fait absent ne paraisse plus ici : la règle
   « un fait sans chiffre ne s'affiche pas » vaut pour l'ACCUEIL, pas pour les écrans de
   détail, où dire « je ne sais pas » est la bonne réponse. La classe leur reste ouverte. */
.bandeau { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
           gap: var(--esp-6); margin: 0 0 var(--esp-10); }
.bd-tuile { display: flex; flex-direction: column; gap: var(--esp-2);
            background: var(--surface-carte); border: 1px solid var(--filet); border-left: 3px solid var(--bleu);
            border-radius: var(--rayon-m); padding: var(--esp-6) var(--esp-7); box-shadow: var(--ombre); }
/* Une tuile-lien ne se souligne pas et n'emprunte pas la couleur des liens : c'est une
   carte qu'on touche, pas un mot dans une phrase. Le survol le dit à la place. */
a.bd-tuile { color: inherit; text-decoration: none; }
a.bd-tuile:hover { border-color: var(--bleu-clair); box-shadow: var(--ombre-forte); }
a.bd-tuile:focus-visible { outline: none; box-shadow: var(--anneau-focus); }
.bd-partiel { border-left-color: var(--or); }
.bd-absent  { border-left-color: var(--gris-clair); background: var(--surface-creuse); }
.bd-alerte  { border-left-color: var(--orange); }
.bd-titre  { font-size: var(--texte-s); text-transform: uppercase; letter-spacing: 0.05em;
             color: var(--texte-discret); }
.bd-valeur { font-size: var(--chiffre-m); font-weight: 700; margin: var(--esp-2) 0 var(--esp-2);
             font-variant-numeric: tabular-nums; }
.bd-absent .bd-valeur { font-size: var(--texte-l); font-weight: 600; color: var(--texte-discret); }
.bd-tache  { font-size: var(--texte-xs); font-weight: 700; color: var(--orange);
             vertical-align: middle; margin-left: var(--esp-3); }
.bd-detail { font-size: var(--texte-s); color: var(--texte-discret); line-height: 1.35; }

/* ── L'ACCUEIL — FL-T32 ───────────────────────────────────────────────────────
   Quatre zones : ce que j'ai à faire, un client, l'état de la maison, mes clients.
   Le titre de zone les sépare sans peser : ce n'est pas un h1 de plus, c'est un
   repère de lecture sur une page qu'on parcourt de haut en bas chaque matin. */
.zone-titre { font-size: var(--texte-l); margin: var(--esp-10) 0 var(--esp-6); }
.zone-titre:first-of-type { margin-top: var(--esp-8); }

/* Le champ de saisie d'une note — le PREMIER geste de la page.
   C'est la seule fonction de l'outil dont l'audit FL-T29 ait mesuré un usage réel :
   10 notes en 4 jours, 7 signées de François. Elle était un bouton flottant. */
.note-saisie { display: flex; gap: var(--esp-6); align-items: flex-start; }
.note-saisie textarea { flex: 1; min-height: 3rem; resize: vertical; }
.note-saisie-actions { display: flex; flex-direction: column; gap: var(--esp-4); }
/* Un libellé pour qui n'a que la voix, invisible pour qui voit le champ et son
   texte d'invite. `clip-path` et non `display:none`, qui le retirerait aussi du
   lecteur d'écran — ce qui est exactement le contraire du but. */
.sr-libelle { position: absolute; width: 1px; height: 1px; overflow: hidden;
              clip-path: inset(50%); white-space: nowrap; }

/* LE MICRO N'AVAIT AUCUN STYLE DANS L'ASSISTANT — trouvé en regardant l'écran, pas en
   lisant le code. `mic.js` est un module portable et sa mise en forme vit chez ses
   hôtes : `#fb-panel .q-mic` pour le bouton d'idée, `questionnaire.css` pour les
   formulaires. Aucune des deux feuilles ne couvre les écrans du CRM, et un `<button>`
   sans règle dans une colonne flex s'étire sur toute sa largeur : un carré de 2,2 rem
   devenait une boîte vide de 8 rem avec un micro perdu au milieu.

   Les trois états sont ceux que `mic.js` pose lui-même (`q-mic-actif`, `-attente`,
   `-erreur`) : ils sont repris ici pour que l'enregistrement se voie, ce qui est
   exactement ce que les débriefs n° 1 et n° 2 de François demandaient. */
.q-mic { width: 2.2rem; height: 2.2rem; flex: none; padding: 0;
         display: flex; align-items: center; justify-content: center;
         font-size: var(--texte-l); line-height: 1; border-radius: var(--rayon-pilule);
         border: 1px solid var(--filet); background: var(--surface-carte); cursor: pointer; }
.q-mic:hover { border-color: var(--bleu-clair); }
.q-mic-actif { width: auto; padding: 0 var(--esp-5); font-size: var(--texte-xs);
               border-color: var(--rouge); color: var(--rouge); }
.q-mic-attente { opacity: 0.55; cursor: default; }
.q-mic-erreur { border-color: var(--rouge); }

/* La file des notes à trier — le fragment échangé par htmx (_notes_a_trier.html). */
.notes-titre { font-size: var(--texte-base); margin: 0 0 var(--esp-6); }
.notes-file { list-style: none; margin: 0; padding: 0;
              display: flex; flex-direction: column; gap: var(--esp-6); }
.note-carte { background: var(--surface-creuse); border: 1px solid var(--filet);
              border-radius: var(--rayon-l); padding: var(--esp-6) var(--esp-7); }
/* Un <textarea>, pas un <div> : la transcription vocale se trompe parfois, et la
   note se corrige en place (FL-T41). Transparent au repos pour se fondre dans la
   carte comme le faisait le <div> qu'il remplace ; le filet n'apparaît qu'au survol
   ou au focus, pour signaler que c'est éditable sans l'annoncer en permanence. */
.note-texte { display: block; width: 100%; box-sizing: border-box; resize: vertical;
              font: inherit; color: inherit; line-height: 1.45;
              background: transparent; border: 1px solid transparent; border-radius: var(--rayon-s);
              padding: var(--esp-3); margin: calc(var(--esp-3) * -1); }
.note-texte:hover { border-color: var(--filet); }
.note-texte:focus { border-color: var(--bleu-clair); outline: none; }
/* FL-T97 : la note n'est plus une <textarea> mais son texte même, corrigible en place
   (inline-editor) — les retours à la ligne de la dictée restent visibles. */
.note-lecture { white-space: pre-wrap; resize: none; width: auto; }
:root {
  --ie-accent: var(--bleu-clair);   --ie-accent-ink: var(--blanc);
  --ie-panel: var(--surface-haute); --ie-text: var(--texte-fort);
  --ie-muted: var(--texte-discret); --ie-border-strong: var(--filet);
  --ie-input-bg: var(--champ-fond); --ie-sunken: var(--surface-creuse);
}
.note-meta { margin: var(--esp-3) 0 var(--esp-5); }
.note-auteur { color: hsl(var(--teinte, 0), 45%, 35%); font-weight: 600; }
.note-ranger { display: flex; gap: var(--esp-5); align-items: flex-start; flex-wrap: wrap; }
.note-ranger .choix-client { flex: 1; min-width: 12rem; }
.note-sans { margin-top: var(--esp-4); }
/* FL-T54. Le tri demande deux choses de plus — QUAND, et ce que la note DEVIENT. Les
   deux restent sur la même ligne que la recherche de client tant qu'il y a la place, et
   passent dessous sur un téléphone (`flex-wrap` est déjà sur `.note-ranger`). */
.note-quand { display: flex; flex-direction: column; gap: var(--esp-2); font-size: .85em; }
.note-devient { display: flex; flex: none; align-items: center; gap: var(--esp-3);
  border: none; padding: 0; margin: 0; font-size: .85em; }
/* Un `legend` sort du flux flex de son fieldset dans tous les navigateurs : `float`
   le remet sur la ligne, à gauche des deux choix. */
.note-devient legend { float: left; padding: 0; margin-right: var(--esp-3); }
/* `flex-direction: row` EXPLICITE : la règle générale `label` (l. 231) met les
   étiquettes en colonne — c'est ce qu'on veut partout ailleurs, et ça posait ici la
   pastille AU-DESSUS de son mot. Vu à l'écran le 2026-09-06, pas en test. */
.note-devient label { display: inline-flex; flex-direction: row; align-items: center;
  gap: var(--esp-2); white-space: nowrap; }

/* ── CE QU'IL A PROMIS — FL-T34 ───────────────────────────────────────────────
   Le bloc du haut de « ce que j'ai à faire ». Il emprunte `.notes-file` et
   `.note-carte` à la file des notes, et c'est délibéré : ce sont deux files du même
   écran, et deux dessins différents pour deux listes voisines se lisent comme deux
   outils. Seuls diffèrent ici les trois choses qui ne sont pas dans une note — le
   groupe, le retard, et les deux issues. */
.eng-groupe { font-size: var(--texte-s); text-transform: uppercase;
              letter-spacing: 0.04em; color: var(--texte-discret);
              margin: var(--esp-6) 0 var(--esp-4); }
.eng-groupe-retard { color: var(--rouge); }
/* Le retard se voit sur la carte et pas seulement dans son titre de groupe : la file
   se parcourt du pouce, et un titre sort de l'écran avant les cartes qu'il coiffe. */
.eng-groupe-retard + .notes-file .eng-carte { border-left: 3px solid var(--rouge); }
/* Deux formulaires côte à côte — « Fait » et « Sans suite ». `flex-wrap` parce que
   sous 560 px ils passent l'un sous l'autre plutôt que de rétrécir sous la cible de
   44 px (FL-T31). */
.eng-gestes { display: flex; gap: var(--esp-5); flex-wrap: wrap; }
/* L'invite qui s'ouvre après avoir rangé une note, et le formulaire de la fiche
   client. Une colonne : sur un téléphone tenu d'une main, deux champs côte à côte
   font deux champs trop étroits. */
.eng-invite { display: flex; flex-direction: column; gap: var(--esp-5);
              margin-top: var(--esp-6); padding: var(--esp-6) var(--esp-7);
              background: var(--surface-creuse); border: 1px solid var(--filet);
              border-radius: var(--rayon-l); }
.eng-invite-titre { margin: 0; font-weight: 600; }
.eng-invite input[type="text"] { width: 100%; }
.eng-invite .btn-primary { align-self: flex-start; }
/* « Quand ? » — les deux sélecteurs natifs, jamais côte à côte sous la rupture. */
.eng-quand { border: 0; padding: 0; margin: 0;
             display: flex; gap: var(--esp-6); flex-wrap: wrap; }
.eng-quand legend { padding: 0; margin-bottom: var(--esp-3); }
.eng-quand-champ { display: flex; flex-direction: column; gap: var(--esp-2); }
.eng-clos { margin-top: var(--esp-7); }
.eng-clos summary { cursor: pointer; color: var(--texte-discret);
                    font-size: var(--texte-s); }

/* Choisir un client en tapant son nom (choix-client.js) — le débrief n° 6 de François.
   Le panneau de résultats reprend `.panneau-resultats` de la loupe : c'est le même
   geste, la même route, et deux présentations différentes du même résultat seraient
   deux façons d'apprendre l'outil. */
.choix-client { position: relative; }
.choix-champ { width: 100%; }
.choix-resultats { position: absolute; z-index: 60; left: 0; right: 0; top: 100%;
                   margin-top: var(--esp-2);
                   background: var(--surface-haute); border: 1px solid var(--filet);
                   border-radius: var(--rayon-l); box-shadow: var(--ombre-forte); }
.choix-resultats[hidden] { display: none; }
/* `align-items: flex-start` : sans lui, un <button> en colonne flex centre son
   contenu — les noms de clients arrivaient au milieu du panneau, alors que ceux de
   la loupe, qui sont des <a>, sont à gauche. Deux présentations du même résultat. */
.choix-resultat { display: flex; flex-direction: column; align-items: flex-start;
                  gap: var(--esp-1);
                  width: 100%; text-align: left; background: none; border: 0;
                  padding: var(--esp-5) var(--esp-7); cursor: pointer;
                  font: inherit; color: var(--texte-fort); }
.choix-resultat:hover, .choix-resultat:focus-visible { background: var(--surface-creuse); }
.choix-large .choix-champ { font-size: var(--texte-l); padding: var(--esp-6) var(--esp-7); }
/* Créer le client cherché en vain (FL-T33). Le filet du dessus le sépare du message
   « Aucun client de ce nom » : c'est une ÉCRITURE en base, pas un résultat de plus. */
.choix-creer { border-top: 1px solid var(--filet); }
.choix-creer strong { color: hsl(var(--teinte, 0), 45%, 35%); }
.choix-creer[disabled] { opacity: .6; cursor: progress; }

/* Les sept activités, repliées (FL-T32). Six sur sept n'avaient aucune ligne à
   montrer : elles ne sont plus le premier geste de la page, elles en sont le dernier.
   Le <summary> est une cible tactile à part entière — c'est le seul chemin vers le
   tableau des clients, il ne peut pas faire 17 px comme les autres `summary`. */
.replie-activites > summary { cursor: pointer; font-weight: 600;
                              padding: var(--esp-5) 0; min-height: var(--cible-tactile);
                              display: list-item; }
.replie-activites > summary:focus-visible { outline: none; box-shadow: var(--anneau-focus); }

/* FL-T18 — le texte d'un message, tel qu'il est parti. `pre` et pas `p` : les retours à
   la ligne sont dans le texte, et un message relu six mois plus tard doit se lire comme
   il a été envoyé. `white-space: pre-wrap` pour que les longues lignes reviennent quand
   même à la ligne sur un téléphone. Son papier est CHAUD et lui appartient : c'est ce qui
   le fait lire comme un document et non comme un champ de plus. */
.corps-mail {
  white-space: pre-wrap;
  font-family: inherit;
  background: var(--corps-mail-fond);
  border-left: 3px solid var(--corps-mail-filet);
  padding: var(--esp-6) var(--esp-7);
  margin: var(--esp-4) 0 var(--esp-6);
  max-width: 62ch;
}

/* FL-T19 — LE PANNEAU QUI DIT « JE NE SAIS PAS FAIRE ».
   Il ne ressemble ni à une erreur (rien n'a raté) ni à un tableau vide (« rien à
   signaler » serait le contraire de la vérité) : c'est un encadré de texte, gris et
   calme, qui explique et se lit. Même intention que `.bd-absent` du bandeau, en plus
   grand — l'absence d'une donnée est une information, pas une panne. */
.impossible {
  background: var(--surface-carte);
  border: 1px solid var(--filet);
  border-left: 3px solid var(--gris-clair);
  border-radius: var(--rayon-l);
  padding: var(--esp-7) var(--esp-8);
  max-width: 68ch;
  line-height: 1.5;
}
.impossible p { margin: 0 0 var(--esp-5); font-size: var(--texte-m); }
.impossible p:last-child { margin-bottom: 0; }
.impossible-titre { font-weight: 700; color: var(--texte-discret); }

/* LA PORTE — l'écran de connexion (2026-08-14, patron hedo-chat)
   Deux lettres, rien d'autre. Le champ n'existe qu'une fois qu'on a dit qui on est ;
   il n'a ni libellé ni texte d'aide, et il n'y a pas de bouton — on tape, on valide.
   La page ne dit jamais quels comptes existent : deux initiales, pas deux noms.

   FL-T30 — le bloc « LOGIN » qui vivait ici (.login-card, .login-header, .login-logo,
   .login-tag, .login-error) était un reliquat de l'écran d'AVANT la porte (FL-T15b) :
   aucun gabarit ne le citait depuis. Supprimé. `.login-body` sert encore, et reste.

   Ses trois dimensions sont des tokens de composant : un rond de 132 px et une lettre de
   3,6 rem ne sont pas des crans d'échelle, ce sont les proportions d'un écran unique. */
.login-body { min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.porte { display: flex; flex-direction: column; align-items: center; gap: var(--esp-11); }
.porte-lettres { display: flex; gap: var(--esp-11); }
.lettre {
  width: var(--porte-lettre); height: var(--porte-lettre); border-radius: 50%;
  background: var(--surface-carte); border: 3px solid var(--filet); color: var(--bleu);
  font-size: var(--porte-lettre-taille); font-weight: 800; line-height: 1; cursor: pointer;
  box-shadow: var(--ombre); transition: transform .15s ease, border-color .15s ease;
}
.lettre:hover  { transform: translateY(-3px); border-color: var(--bleu-clair); }
.lettre.choisie { border-color: var(--bleu); box-shadow: var(--anneau-focus); }
/* [hidden] doit gagner sur le display du formulaire — même piège que chez hedo. */
.porte-form { display: block; width: 100%; max-width: 300px; }
.porte-form[hidden] { display: none; }
.porte-form input[type="password"] {
  width: 100%; box-sizing: border-box; text-align: center;
  font-size: var(--texte-xl); letter-spacing: .18em;
  padding: var(--esp-6) var(--esp-7); border: 1px solid var(--filet); border-radius: var(--rayon-xl);
  background: var(--champ-fond); color: var(--texte-fort);
}
.porte-form input[type="password"]:focus { outline: none; border-color: var(--bleu); }
.porte-non { color: var(--rouge); font-size: var(--texte-m); margin: 0; }

/* ══ 5. PORTRAIT — le téléphone tenu droit (FL-T31) ══════════════════════════

   « L'app doit pouvoir s'utiliser sur un téléphone sans problème d'utilisation »
   (Dom, 2026-08-23). François travaille en tournée : le téléphone est le terrain,
   pas un confort.

   TROIS SEUILS, ET UN SEUL QUI COMPTE.

     900 px  les grilles à deux colonnes se replient (fiche produit, fiche de
             commande). Un seuil de mise en page, pas de téléphone.
     560 px  LE PORTRAIT. Tout ce qui suit tient ici, et rien ailleurs.
     480 px  la porte, et elle seule : les deux lettres du choix d'utilisateur
             rétrécissent pour tenir côte à côte sur 360 px.

   Ce qui a changé de FL-T30 à ici : 860 a rejoint 900 (les deux repliaient une
   grille à deux colonnes ; la fiche produit se replie donc 40 px plus tôt, seul
   écart visible de ce regroupement). 480 RESTE distinct de 560 : y fondre la
   porte réduirait les deux lettres de 132 à 104 px entre 481 et 560 px, c'est-à-
   dire rendrait plus PETITE la plus grosse cible tactile de l'outil — l'inverse
   de ce que cette tâche fait.

   L'ORDRE : ce bloc est le dernier du fichier, et il doit le rester. Une douzaine
   de paires base/modificateur du § 3 ont la même spécificité que ses sélecteurs
   (`.tbl th, .tbl td` contre `[data-cartes] td`, entre autres) : seul l'ordre
   décide du vainqueur. Un bloc glissé après celui-ci éteindrait le portrait sans
   qu'un test le voie. */

@media (max-width: 900px) {
  .fiche-commande { grid-template-columns: 1fr; }
  .fiche-produit { grid-template-columns: 1fr; }
  .fiche-medias { position: static; }
}

@media (max-width: 560px) {

  /* ── 5.1 LA PAGE ──────────────────────────────────────────────────────── */
  /* LE BANDEAU NE COLLE PLUS. Mesuré : replié en colonne, il fait 245 px de haut, et
     `.entete` est en `position: sticky` — soit 29 % d'un écran de 844 px confisqués sur
     CHAQUE page, en permanence, et 37 % sur un iPhone SE. C'était, chiffres en main, le
     plus gros problème d'usage sur téléphone de tout l'outil, devant les tables.

     Il redevient donc du contenu : on le dépasse en descendant, on y revient en
     remontant. Aucune destination n'est retirée et aucun menu n'est inventé — replier
     19 entrées derrière un bouton serait une refonte de la barre, et elle appartient à
     l'écran d'accueil (FL-T32), pas à une passe de portrait.

     Ce qu'on perd, dit franchement : le filet de fuseaux (FL-T27) n'est plus sous les
     yeux en permanence. On va y chercher une heure avant d'appeler quelqu'un — c'est un
     geste, pas une veille. Au-dessus de la rupture, rien ne change : le bandeau y fait
     une ligne, et il colle. */
  .entete { position: static; }

  main { padding: var(--esp-8); }
  .produit-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--esp-6); }
  .stat { min-width: 90px; padding: var(--esp-6) var(--esp-7); }
  nav { flex-direction: column; gap: var(--esp-4); align-items: stretch; }
  .nav-links { justify-content: space-between; }
  /* Le panneau d'un menu de la barre est en `position: absolute` : sur 360 px, un
     menu ouvert près du bord droit sortirait de l'écran et pousserait la page. */
  .nav-menu-liste { max-width: calc(100vw - var(--esp-11)); }

  /* ── LE ☰ PREND CORPS — FL-T32 ──────────────────────────────────────────────
     FL-T31 avait décollé le bandeau (245 px, 29 % d'un écran de 844) et laissé ce
     repli-ci à l'écran d'accueil, faute de vouloir refondre la navigation dans une
     passe de portrait. Le voici : les quatre menus et leurs seize destinations
     passent derrière un bouton, et TROIS LIENS RESTENT DEHORS — Ma journée, Courrier,
     Affaires. Ce sont l'ordre de sa matinée (b1, FL-T09/T15), pas trois entrées
     comme les autres : les replier aurait ajouté un clic là où il en fait le plus.

     `.entete` reste `position: static`. Une barre repliée serait peut-être assez
     courte pour recoller — c'est une décision à mesurer, pas un effet de bord. */
  .menu-tel-bouton { display: flex; align-items: center; gap: var(--esp-4);
                     width: 100%; padding: var(--esp-5) var(--esp-6);
                     border-radius: var(--rayon-s); color: var(--sur-bleu-fort);
                     cursor: pointer; }
  .menu-tel-bouton:hover, .menu-tel-case:checked ~ .menu-tel-bouton {
    background: var(--voile-survol);
  }
  /* Le focus clavier se voit sur le BOUTON, puisque la case est hors de l'écran. Sans
     cette règle, le ☰ serait atteignable à la tabulation sans qu'on sache où l'on est. */
  .menu-tel-case:focus-visible ~ .menu-tel-bouton { box-shadow: 0 0 0 2px var(--voile-focus); }
  /* Replié par défaut, déplié quand la case est cochée. Les quatre menus reprennent
     leur boîte et s'empilent ; leurs panneaux, eux, étaient déjà `position: absolute`
     et le restent. */
  .menu-tel-liste { display: none; }
  .menu-tel-case:checked ~ .menu-tel-liste {
    display: flex; flex-direction: column; gap: var(--esp-3);
    width: 100%; padding-left: var(--esp-4);
  }
  /* Sur 360 px de large, 1,5 rem de chaque côté sont 15 % de l'écran perdus pour du
     vide. Les voiles rétrécissent d'autant, sinon ils mangent une place entière. */
  .fuseaux-piste { padding-left: var(--esp-7); padding-right: var(--esp-7); gap: var(--esp-7); }
  .fuseaux::before, .fuseaux::after { width: var(--voile-fuseaux-large-mobile); }

  /* ── 5.2 LE PATRON « TABLE → CARTES » ─────────────────────────────────────
     Une ligne devient une carte, une cellule devient une paire libellé/valeur.

     Le patron ne connaît AUCUNE classe de table : il s'accroche à `[data-cartes]`,
     que cartes.js pose sur toute table à en-tête d'au moins trois colonnes portant
     des lignes de données. `.tbl`, `.pipeline-table` et ce qui viendra ensuite en
     héritent sans qu'une ligne bouge ici. Les tables à deux colonnes (`.tbl-cle`,
     `.tbl-voc`) sont DÉJÀ des paires libellé/valeur : elles ne sont pas marquées et
     restent des tables.

     SANS JAVASCRIPT, l'attribut n'est pas posé : la table reste une table dans un
     cadre qui défile au doigt. Dégradé, jamais illisible — et c'est la règle qui
     s'applique aussi aux tables que cartes.js refuse (en-tête sur deux rangs, ou
     `colspan` dans l'en-tête, où l'indice de colonne ne dit plus la vérité). */

  .tbl:not([data-cartes]), .pipeline-table:not([data-cartes]) {
    display: block; overflow-x: auto; overscroll-behavior-x: contain;
  }

  /* La table n'est plus la carte : chaque LIGNE l'est. Le fond, le rayon et l'ombre
     descendent donc d'un cran, de la table vers le `<tr>`. */
  [data-cartes] { display: block; background: none; box-shadow: none;
                  border-radius: 0; overflow: visible; }
  [data-cartes] thead { display: none; }
  [data-cartes] tbody, [data-cartes] tfoot, [data-cartes] tr { display: block; }

  [data-cartes] tr {
    background: var(--surface-carte); border-radius: var(--carte-rayon);
    box-shadow: var(--ombre); margin-bottom: var(--esp-6);
    padding: var(--esp-4) var(--esp-7); overflow: hidden;
  }

  /* La cellule : le libellé à gauche, la valeur à droite, sur une seule ligne de
     grille. Empiler le libellé AU-DESSUS aurait doublé la hauteur — vingt lignes
     par facture, quatre-vingt-dix-huit factures — pour aucune lisibilité gagnée. */
  [data-cartes] td, [data-cartes] tfoot th {
    display: grid; grid-template-columns: var(--carte-libelle) 1fr;
    gap: var(--esp-5); align-items: baseline;
    padding: var(--esp-4) 0; border-bottom: none;
    /* `.table-clients td` et `.col-actions` tiennent la ligne d'une table sur un
       seul rang. Dans une carte, il n'y a plus de colonne à préserver : le texte
       revient à la ligne au lieu de pousser la page hors de l'écran. */
    white-space: normal; vertical-align: top;
  }
  /* Le filet entre deux cellules, posé en `border-top` sur toutes sauf la première :
     ainsi la dernière n'a jamais de trait qui pende sous elle, et la règle ne se
     bat pas avec `.tbl tbody tr:last-child td`, qui est plus spécifique. */
  [data-cartes] tr > * + * { border-top: 1px solid var(--filet); }

  /* La valeur, enveloppée par cartes.js dans un élément unique : sans lui, une
     cellule à plusieurs enfants (« AQUASMITH<br><span>SAIPAN, MP</span> ») en place
     un par case de grille, et la seconde ligne repart SOUS LE LIBELLÉ.
     `min-width: 0` parce qu'une case de grille refuse par défaut de descendre
     sous la largeur de son contenu : un numéro de commande insécable ferait sinon
     grandir la carte au-delà de l'écran. */
  [data-cartes] td > [data-valeur] { min-width: 0; overflow-wrap: break-word; }

  [data-cartes] td::before {
    content: attr(data-libelle);
    font-size: var(--texte-xs); font-weight: 400; color: var(--texte-discret);
    text-transform: uppercase; letter-spacing: 0.04em; line-height: 1.6;
  }

  /* L'ancre de la carte : la première cellule porte ce qui identifie la ligne — la
     date d'une facture, le nom d'un client, l'adresse d'une boîte. Elle est lue en
     premier au balayage du pouce, donc elle pèse plus lourd. */
  [data-cartes] tbody td:first-child { font-weight: 600; }

  /* TROIS CELLULES QUI NE SONT PAS DES PAIRES.

     `data-pleine` — la cellule ne se rattache à aucune colonne : elle en couvre
     plusieurs (« Panier vide. », le pied « Total TTC ») ou sa colonne n'a pas de
     libellé (les six colonnes d'actions). Elle prend la carte entière.
     `data-large` — elle porte un champ, un formulaire ou un `<details>` : le
     libellé passe au-dessus et le contenu prend toute la largeur, parce qu'une
     liste déroulante de clients dans 38 % de 390 px ne montre aucun nom.
     `data-vide` — ni libellé ni contenu : une carte n'a pas de colonne à réserver,
     donc la cellule disparaît au lieu de laisser un trait vide. */
  [data-cartes] td[data-pleine], [data-cartes] tfoot th[data-pleine] {
    display: block; text-align: left;
  }
  [data-cartes] td[data-pleine]::before { content: none; }
  [data-cartes] td[data-large] { display: block; }
  [data-cartes] td[data-large]::before { display: block; margin-bottom: var(--esp-3); }
  [data-cartes] td[data-vide] { display: none; }

  /* Les nombres restent à droite — ils ne se lisent pas, ils se comparent, et une
     carte a maintenant un bord droit à quoi les aligner. */
  [data-cartes] .num { text-align: right; }
  /* Le libellé, lui, reste à gauche : `text-align` d'une cellule descend sur son
     `::before`, et « HT » collé au milieu de la carte creusait une gouttière au
     centre au lieu d'aligner les libellés les uns sous les autres. */
  [data-cartes] .num::before { text-align: left; }
  /* Le pied d'une commande : « Total TTC » et son montant couvrent chacun plusieurs
     colonnes, donc chacun prend la carte entière — le montant garde sa droite. */
  [data-cartes] td[data-pleine].num { text-align: right; }

  /* L'état d'une ligne teintait ses cellules ; il teinte maintenant la carte. */
  [data-cartes] tr.ligne-panier { background: var(--surface-signalee); }
  [data-cartes] .ligne-panier td { background: none; }
  [data-cartes] tr.ligne-inactive { opacity: 0.55; }
  [data-cartes] .ligne-inactive td { opacity: 1; }
  [data-cartes] tfoot tr { background: var(--surface-creuse); }
  [data-cartes] tfoot th, [data-cartes] tfoot td { background: none; }

  /* Les deux cellules qui se coupaient pour tenir dans une colonne n'ont plus de
     colonne à tenir. */
  .table-clients .enseigne { max-width: 100%; white-space: normal; }
  [data-cartes] td.ref { word-break: break-word; }

  /* ── 5.3 LES CIBLES TACTILES ──────────────────────────────────────────────
     Rien d'actionnable sous --cible-tactile. Mesuré avant : `.btn-mini` faisait
     28 px de haut, `.edit-compte summary` (« modifier », « rattacher », neuf
     emplois) 17 px, un champ de formulaire 38 px, la loupe du bandeau 28 px.
     Tout cela se vise à la souris et se rate au pouce.

     `min-height` et non `height` : un bouton dont le texte passe sur deux lignes
     doit grandir, pas se faire couper. */
  button, .btn-primary, .btn-mini, .btn-export, .btn-danger, .btn-reset,
  .filters button, .btn-loupe, .btn-ia {
    min-height: var(--cible-tactile);
    display: inline-flex; align-items: center; justify-content: center;
  }
  input:not([type="checkbox"]):not([type="radio"]), select, textarea {
    min-height: var(--cible-tactile);
  }
  /* Une case à cocher ne grandit pas : c'est son libellé qui devient la cible,
     et il l'était déjà — le `<label>` enveloppe la case dans tous les gabarits. */
  /* TOUT `<label>` prend la cible, et pas seulement `.case` : un `<label>` enveloppe
     son champ dans tous les gabarits, il EST donc la zone touchable. Pour un champ de
     saisie la règle ne change rien (le champ fait déjà 44 px) ; pour une case à cocher,
     elle est tout — mesuré à 21,6 px, une case ne se vise pas au pouce. */
  label, .form-grid .case, .ligne-form .case, label.inline { min-height: var(--cible-tactile); }
  input[type="checkbox"], input[type="radio"] { min-width: 1.35rem; min-height: 1.35rem; }

  /* `<summary>` garde `display: list-item` : le passer en flex ferait disparaître
     le triangle d'ouverture dans les moteurs WebKit, c'est-à-dire sur l'appareil
     même qu'on vise. La hauteur se prend donc en rembourrage. */
  summary { min-height: var(--cible-tactile); padding-top: var(--esp-5); padding-bottom: var(--esp-5); }
  .edit-compte summary { font-size: var(--texte-m); }
  /* `.brand` en fait partie : c'est le lien de retour à l'accueil, mesuré à 27 px
     — la plus petite cible de la barre, et celle qu'on vise en premier quand on
     s'est perdu. */
  .nav-menu > summary, .nav-links a, .nav-links .nav-menu-liste a, .brand {
    min-height: var(--cible-tactile); display: flex; align-items: center;
  }
  .onglet { min-height: var(--cible-tactile); align-items: center; }
  /* Un lien DANS une carte est une action, pas un mot dans une phrase : c'est le nom
     du client, le sujet d'un message, le numéro d'une affaire — ce qu'on vient
     toucher. Un lien dans un paragraphe, lui, reste un lien dans un paragraphe : le
     gonfler à 44 px déchirerait la ligne de texte autour. La distinction est faite
     par l'endroit, et c'est la seule qui se tienne. */
  [data-cartes] td a { min-height: var(--cible-tactile); display: inline-flex; align-items: center; }
  .stats-bar a.stat { min-height: var(--cible-tactile); }

  /* ── 5.4 LES FORMULAIRES, EN UNE COLONNE ──────────────────────────────────
     `.form-grid` tient ses colonnes par `auto-fit, minmax(210px, 1fr)` : sur
     390 px moins les marges, une seule colonne passe DÉJÀ — mais la règle est
     écrite quand même, parce qu'un champ qu'on rétrécirait un jour à 160 px
     ferait resurgir deux colonnes de 8 mm sans que personne l'ait demandé. */
  .form-grid { grid-template-columns: 1fr; }

  /* Ces trois-là, en revanche, étaient bel et bien en rangée. */
  .ligne-form { flex-direction: column; align-items: stretch; }
  .ligne-form input, .ligne-form select { width: 100%; min-width: 0; }
  .filters form { flex-direction: column; align-items: stretch; }
  .filters form > * { width: 100%; }
  .search { min-width: 0; }
  .roles-choix { flex-direction: column; gap: var(--esp-5); }
  .cat-header { gap: var(--esp-6); }
  .cat-header-actions { flex-wrap: wrap; }

  /* Les `<details>` qui contiennent un formulaire long : replié, `.edit-compte`
     se range au bout d'une ligne ; déplié, il lui faut la largeur entière — et
     dans une carte, la « ligne » est un bord à bord de 390 px. */
  .edit-compte, .edit-compte[open] { display: block; margin-left: 0; width: 100%; }
  .edit-compte form { padding: var(--esp-6); }
  .form-nouveau summary { padding: var(--esp-6) var(--esp-7); }
  .form-nouveau form { padding: 0 var(--esp-7) var(--esp-7); }

  /* ── FL-T70 lot 3 — LES DEUX VOLETS DEVIENNENT UN SEUL ────────────────────
     Même rupture que le reste de ce bloc (560 px) — pas un second seuil rien que pour
     ce module. `classement.js` bascule `.mode-panneau` sur `.classement-volets` à
     l'ouverture d'un dossier ; en dessous de la rupture, un seul des deux volets se
     montre à la fois, et « ← Dossiers » redonne l'arbre sans recharger la page. Sans
     JavaScript, `.mode-panneau` n'existe jamais : l'arbre reste seul, colonne pleine,
     et cette règle ne s'applique à rien. */
  .classement-volets { grid-template-columns: 1fr; }
  .classement-volets:not(.mode-panneau) .classement-panneau { display: none; }
  .classement-volets.mode-panneau .classement-arbre { display: none; }
  .classement-retour { display: inline-flex; }

  /* ── FL-T39 — l'écran qu'il ouvre DEBOUT ────────────────────────────────
     C'est le seul écran de l'outil conçu POUR ce format plutôt qu'adapté à lui :
     les règles ci-dessous ne réparent rien, elles donnent aux gestes la place
     qu'ils demandent à une main. Aucune table ici, donc rien à replier en cartes. */
  .visite-natures { display: grid; grid-template-columns: 1fr 1fr; }
  .btn-nature { text-align: center; }
  /* Le geste principal prend la ligne entière : viser un bouton de 40 % de large
     en marchant est ce que la règle des 44 px ne suffit pas à régler. */
  .visite-dire .btn-primary, .visite-position .btn-primary,
  .visite-photos .btn-primary, .btn-photo { width: 100%; }
  .visite-gestes { gap: var(--esp-5); }
  .visite-gestes .q-mic { flex: 0 0 auto; }
  .photo-galerie { grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr)); }
  /* En portrait, 60 vh de grande image laissaient le bandeau hors de l'écran : on
     voyait une photo sans savoir qu'il y en avait onze autres. */
  .galerie-grande img { max-height: 45vh; }
  /* La carte garde une hauteur utile en portrait sans avaler la page : on doit
     pouvoir la voir ET lire le compteur d'épinglés sans faire défiler. */
  .carte { height: 50vh; }
}

@media (max-width: 480px) {
  .lettre { width: var(--porte-lettre-mobile); height: var(--porte-lettre-mobile);
            font-size: var(--porte-lettre-taille-mobile); }
  .porte-lettres { gap: var(--esp-10); }
}


/* ═══ FL-T39 — EN VISITE, ET LA CARTE ════════════════════════════════════════
   Tout en tokens (FL-T30) : aucune valeur en dur dans le corps du fichier. Rien ici
   n'invente de composant — `.bloc`, `.notes-file`, `.note-carte`, `.hint`, `.empty`,
   `.btn-primary`, `.btn-mini` sont ceux du socle, et ces règles ne font que les
   assembler pour un écran qui se tient à une main.                                 */

/* La porte : une question, un champ. Le champ est gros parce qu'il est le seul geste
   de la page, et qu'on le vise debout. */
.visite-porte { display: grid; gap: var(--esp-7); }
.visite-porte-titre { font-size: var(--texte-xl); font-weight: 600; color: var(--texte-fort); }
.visite-porte .choix-champ { min-height: var(--cible-tactile); font-size: var(--texte-l); }

.visite-passees { margin-top: calc(var(--esp-5) * -1); }

/* Dicter : le micro et l'envoi côte à côte, et qui passent l'un sous l'autre plutôt
   que de rétrécir — même règle que les deux boutons d'issue de FL-T34. */
.visite-dire { display: grid; gap: var(--esp-6); }
.visite-dire textarea { width: 100%; font-size: var(--texte-base); }
.visite-gestes { display: flex; flex-wrap: wrap; gap: var(--esp-6); align-items: center; }

.visite-resume { background: var(--surface-creuse); }
.visite-resume-texte { white-space: pre-wrap; color: var(--texte-fort); }

/* Photos. Le bouton remplace l'input quand le script est là ; l'aperçu se déplie sous
   lui. Les vignettes sont carrées et recadrées : un rayonnage photographié de travers
   ne doit pas décaler la grille. */
.visite-photos { display: grid; gap: var(--esp-8); }
.visite-photo-titre { font-weight: 600; color: var(--texte-fort); margin-bottom: var(--esp-4); }
.photo-champ { display: grid; gap: var(--esp-5); }
/* Le même piège que `.panneau-loupe` et `.porte-form` plus haut, et il durait depuis
   FL-T45 : `photo-visite.js` pose `poster.hidden = true` — « si on prend une photo,
   c'est pour qu'elle apparaisse » — mais `.btn-primary { display: inline-block }`
   l'emporte sur le `display: none` que le navigateur attache à `[hidden]`. Le bouton
   « Envoyer » restait donc affiché sur les deux écrans de visite, alors que le script
   poste déjà tout seul. Vu à l'écran le 2026-09-06, pas en test. */
.photo-champ [hidden] { display: none; }
.btn-photo {
  min-height: var(--cible-tactile); padding: var(--esp-5) var(--esp-7);
  border: 1px dashed var(--filet); border-radius: var(--rayon-m);
  background: var(--surface-creuse); color: var(--texte-fort);
  font-size: var(--texte-base); cursor: pointer; text-align: left;
}
.btn-photo:hover { border-color: var(--bleu-clair); }
.photo-apercus { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--esp-4); }
.photo-apercus li { display: flex; align-items: center; gap: var(--esp-5);
                    font-size: var(--texte-s); color: var(--texte-doux); }
.photo-apercus img { width: 3rem; height: 3rem; object-fit: cover;
                     border-radius: var(--rayon-s); }
.photo-galerie { list-style: none; padding: 0; margin: 0;
                 display: grid; gap: var(--esp-5);
                 grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr)); }
.photo-galerie li { display: grid; gap: var(--esp-3); }
.photo-galerie img { width: 100%; aspect-ratio: 1; object-fit: cover;
                     border-radius: var(--rayon-m); border: 1px solid var(--filet); }
/* La grande vue de la galerie (2026-09-05). `contain` et NON `cover` comme les
   vignettes : une vignette est un repère, qu'on a le droit de recadrer, quand la grande
   vue doit montrer le rayonnage ENTIER — c'est ce qu'on est venu regarder. Le fond creux
   remplit ce que `contain` laisse de côté sur une photo qui n'est pas au format. */
.galerie { display: grid; gap: var(--esp-6); }
.galerie-grande { margin: 0; display: grid; gap: var(--esp-4); }
.galerie-grande img { width: 100%; max-height: 60vh; object-fit: contain;
                      background: var(--surface-creuse);
                      border-radius: var(--rayon-m); border: 1px solid var(--filet); }
.galerie-grande figcaption { font-size: var(--texte-s); color: var(--texte-doux); }
.galerie-gestes { display: flex; flex-wrap: wrap; gap: var(--esp-5); }
/* La miniature affichée en grand se distingue des autres — sans elle, cliquer ne se voit
   que dans la grande image, loin des yeux sur un bandeau de douze. */
.photo-galerie a[aria-current="true"] img { border-color: var(--bleu-clair); }

/* Ranger en lignes. Les quatre boutons sont une boucle sur NATURES_CONSTAT — ils
   n'ont donc PAS de couleur par code ici : une cinquième nature apparaîtrait sans
   style, ce qui la ferait paraître cassée. Un seul jeu de règles pour tous. */
.visite-ranger { display: grid; gap: var(--esp-6); }
.visite-ranger input[type="text"] { min-height: var(--cible-tactile);
                                    font-size: var(--texte-base); width: 100%; }
.visite-marque { font-size: var(--texte-s); color: var(--texte-libelle);
                 display: grid; gap: var(--esp-3); }
.visite-natures { display: flex; flex-wrap: wrap; gap: var(--esp-5); }
.btn-nature {
  min-height: var(--cible-tactile); padding: var(--esp-5) var(--esp-7);
  border: 1px solid var(--bleu-clair); border-radius: var(--rayon-pilule);
  background: var(--accent-fond); color: var(--bleu-sombre);
  font-size: var(--texte-s); font-weight: 600; cursor: pointer;
}
.btn-nature:hover { background: var(--bleu-clair); color: #fff; }

/* Ce qu'on sait déjà. Un paquet par nature, et la ligne du jour se distingue — il
   doit voir ce qu'il vient d'ajouter sans relire la date. */
.visite-deja > summary { cursor: pointer; }
.visite-deja > summary h2 { display: inline; }
.deja-paquet { margin-top: var(--esp-8); }
.deja-titre { font-size: var(--texte-l); color: var(--texte-fort); margin-bottom: var(--esp-5); }
.deja-carte { display: grid; gap: var(--esp-4); }
.deja-aujourdhui { border-left: 3px solid var(--bleu-clair); }
.marques-vues { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--esp-4); }
.impossible-lien { margin-top: var(--esp-9); padding-top: var(--esp-6);
                   border-top: 1px solid var(--filet); }

/* La position. `hint-doute` est le seul état visuel neuf : il porte « ±2000 m », qui
   n'est ni une erreur ni un succès — c'est un relevé dont il doit juger lui-même. */
.visite-position { display: grid; gap: var(--esp-6); }
.hint-doute { color: var(--etat-alerte-encre); }

/* La carte. Une hauteur en vh plutôt qu'en px : sur un téléphone en portrait, une
   carte de 400 px mange l'écran, et sur un grand écran elle paraît timbre-poste. */
.carte-cadre { border: 1px solid var(--filet); border-radius: var(--rayon-l);
               overflow: hidden; }
.carte { height: 60vh; min-height: 18rem; width: 100%; background: var(--surface-creuse); }
.carte-couverture { margin-bottom: var(--esp-6); }
.carte-legende { margin-top: var(--esp-5); }
.carte-puce { display: inline-block; width: .6rem; height: .6rem; border-radius: 50%;
              background: var(--bleu); vertical-align: middle; }
.carte-puce-activite { opacity: .55; }
.carte-bulle strong { display: block; color: var(--texte-fort); }
.carte-lien { display: inline-block; margin-right: var(--esp-5); margin-top: var(--esp-4); }
.carte-liste { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--esp-4); }
.carte-manquants > summary { cursor: pointer; }
.carte-manquants > summary h2 { display: inline; }
