/* ── Studio stylesheet ──────────────────────────────────────────────────────
   Extracted from static/studio/index.html on 2026-08-24, byte for byte, as
   lot 0 of ~/roadmap/studio-passe-de-design.md. Nothing was rewritten in the
   move: the design pass itself starts at lot 1, and keeping this step a pure
   extraction is what makes it reviewable by a diff rather than by reading.

   Why a file rather than the <style> block it used to be: the design pass
   edits the stylesheet and the markup separately and repeatedly, and a
   361-line inline block makes every markup diff unreadable. It is also the
   precondition for dropping 'unsafe-inline' from style-src the day the 110
   inline style= attributes go (see the plan, section 6) -- an inline block
   could never allow that.

   Served by config/urls.py's studio_serve() with Cache-Control: no-cache, so
   an edit here is picked up on the next load without a forced refresh.
   BACKLOG §2.16 is closed by that same change.
*/
/* ── Jetons de couleur, lot 1 de la passe de design (25/08/2026) ────────────
   Cible D1 de ~/roadmap/studio-passe-de-design.md. Aucune couleur inventée pour
   les trois principales : elles viennent de la palette que le site public sert
   déjà dans ce même dépôt (static/freelances/index.html), qui contient
   exactement les variantes foncées qui manquaient au Studio.

   Ce qui change est la VALEUR des jetons, pas leur nom ni leurs points d'appel :
   les huit endroits qui écrivaient var(--teal) étaient tous du texte ou un fond
   de bouton, aucun un aplat décoratif (vérifié : 6 règles ici, 2 styles en ligne
   dans index.html). Repointer le jeton suffit donc, et le diff reste lisible.

   Rapports de contraste recalculés sur les fonds réels, pas repris du plan :

     --teal   #2d8a7e → #1F6D5E   texte et fond de bouton   4,16 → 6,16 sur blanc
                                  blanc sur le bouton       4,16 → 6,16
     --ok     #2d8a7e → #1F6D5E   texte de badge-ok         3,66 → 5,42
     --warn   #c05f3c → #C44537   alerte, texte et bordure  4,25 → 4,94 sur blanc

   Deux jetons de plus, eux DÉRIVÉS et non repris du site public, mesurés ici :

     --ko-texte     #a35133   texte de badge-ko sur #fbeae3        4,75
                              (--warn n'y suffit pas : #C44537 n'y donne que 4,23)
     --line-champ   #918d7e   bordure des CONTRÔLES sur blanc      3,33

   --line #e3e1da reste, à 1,31, pour les séparateurs de cartes et de lignes :
   ils sont décoratifs, la règle des 3:1 ne les vise pas. Seules les bordures de
   champs, listes déroulantes et puces d'action passent à --line-champ.

   --teal-aplat garde l'ancien vert pour les fonds où le contraste ne s'applique
   pas. Rien ne l'utilise aujourd'hui, il est posé pour le lot 5, qui introduit
   des aplats de couleur dans les tableaux. --teal-l #7AC4B8 est inchangé : il ne
   sert que sur le bandeau sombre et les anneaux de focus, jamais en texte foncé.
*/
/* --gouttiere, lot 1 bis : l'écart entre colonnes valait 1rem écrit en dur à
   trois endroits (.row, .accueil-cartes, .docs-grid) et rien ne garantissait
   qu'ils restent égaux. Un seul jeton, qui rétrécit d'un cran sous 640 px : les
   gouttières de bureau prennent trop de place sur un téléphone, et les régler
   toutes ensemble évite d'en oublier une. */
:root{--teal:#1F6D5E;--teal-aplat:#2d8a7e;--teal-l:#7AC4B8;--ink:#1E1E1E;--bg:#F6F5F1;--card:#fff;--line:#e3e1da;--line-champ:#918d7e;--sub:#6b6b66;--ok:#1F6D5E;--warn:#C44537;--ko-texte:#a35133;--warn-texte:#6B4F22;--warn-fond:#FBF3E0;--gouttiere:1rem;--attente-texte:#3E5C76;
  /* Prestations en cours (17/09/2026, Package D of
     roadmap/prestations-vue-par-freelance.md), the screen's own validated
     mockup (roadmap/prototypes/prestations-vue-par-freelance.html): every
     colour it uses already exists above under these exact values EXCEPT the
     amber "warn" fill, which the rest of the Studio has never needed as a
     saturated icon colour (badge-warn only ever uses the muted --warn-texte
     on a pale background). --warn above already IS the mockup's bright "ko"
     red, reused as is. These two were new on purpose: arbitrage 3 of the plan
     calibrated red as "blocks now" and amber as "a deadline is coming or
     someone else must act", a distinction this screen was the first to draw
     with two full-saturation colours rather than one.

     RETIRED 23/09/2026, lot 4 of roadmap/audit-ux-studio-simplicite-iphone.md
     (plan §3.4): a Studio-wide colour must carry one meaning, and having
     Prestations' own amber a few shades brighter than every other "à
     surveiller" (--warn-texte) broke that rule quietly. The dots now read
     var(--warn-texte) directly, see .prest-d-warn/.prest-cpt-warn below. */

  /* --attente-texte, same lot: the charte's fifth badge meaning,
     "badge-attente" ("en attente normale", a step of a process running its
     course, not a problem) -- a blue-grey rather than another shade of
     green/amber/red, precisely so it is never mistaken for a severity.
     Contrast measured the same way as the block above:
       --attente-texte  #3E5C76   text of badge-attente on white   7,01
     .badge-attente pairs it with a dashed outline and no fill (§3.4: "contour
     pointillé gris-bleu"), the same dashed shape .badge-manuel already draws
     for its own not-yet-answered state, so "waiting" looks unfinished as
     well as reading as such. */
}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'DM Sans',system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--ink);line-height:1.5;font-size:15px}
/* ── Anneau de focus unique, lot 1 (cible D3) ───────────────────────────────
   Le Studio n'avait qu'UNE règle de focus, sur le champ de recherche : partout
   ailleurs on héritait de l'anneau du navigateur, différent d'un navigateur à
   l'autre et invisible sur plusieurs de nos fonds. Un seul anneau, ici, sur
   tout ce qui prend le focus.

   :focus-visible et non :focus : le navigateur ne le pose qu'au clavier, donc
   un clic à la souris sur un bouton ne laisse pas d'anneau derrière lui. C'est
   la différence qui a fait supprimer les anneaux à la main pendant des années.

   outline et non box-shadow : outline ne participe pas à la mise en page, donc
   aucun décalage d'un pixel au moment où le focus arrive, y compris dans une
   cellule de tableau ou une barre de filtres collante.

   Le bandeau .top est sombre : le vert foncé y disparaîtrait, il reçoit donc la
   variante claire, la même que celle de ses boutons actifs.

   Le champ de recherche garde son propre traitement (bordure teintée plus halo,
   déclaré plus bas) : c'est lui qui a servi de modèle, il reste l'exception
   assumée et son indicateur est au moins aussi visible que cet anneau. */
:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.top :focus-visible{outline-color:var(--teal-l)}
/* Barre collante et non fixe : « sticky » garde la barre dans le flux, donc
   aucune marge à compenser sur le contenu, ce qui casserait au moindre
   changement de hauteur — et elle passe déjà à la ligne sur petit écran.
   z-index 40, sous les 50 des modales (aperçu PDF, fiche du vivier), sans quoi
   la barre traverserait la fenêtre ouverte par-dessus. */
.top{background:var(--ink);color:#fff;padding:.8rem 1.4rem;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.5rem;position:sticky;top:0;z-index:40}
.top .brand{font-weight:700;letter-spacing:.08em;background:none;border:0;color:inherit;font-family:inherit;font-size:inherit;padding:0;cursor:pointer}
.top .brand span{color:var(--teal-l)}
.top .brand:hover span{text-decoration:underline}
.top .who{font-size:.8rem;color:#bbb}.top button{background:none;border:1px solid #555;color:#ccc;border-radius:6px;padding:.25rem .7rem;cursor:pointer;font-size:.75rem;margin-left:.4rem}
.top button:hover{border-color:var(--teal-l);color:#fff}
.top button.actif{background:var(--teal-l);border-color:var(--teal-l);color:var(--ink);font-weight:600}

/* ── Menus de rubrique, lot 3 (26/08/2026) ──────────────────────────────────
   Le menu se pose sous son bouton, dans le bandeau, qui est déjà collant : il
   suit donc la page sans calcul de position. `position:relative` sur le
   conteneur du bouton suffit à l'ancrer, et il n'y a rien à repositionner au
   défilement ni au redimensionnement.

   z-index 60 : au-dessus du bandeau lui-même (40) et des blocs collants
   (31 à 34), mais AUSSI au-dessus des fenêtres modales (50). C'est voulu : si
   une fenêtre est ouverte et qu'on ouvre un menu, le menu doit être lisible,
   pas glissé dessous. */
.nav-rubriques{display:flex;align-items:center;gap:.15rem}
.nav-rubrique{position:relative}
.nav-rubrique button::after{content:" ▾";opacity:.55;font-size:.75em}
/* Never wider than the screen minus two 8 px margins (24/09/2026, Alex's
   iPhone captures): on a phone the menu could otherwise not fit at all.
   Its horizontal position is then corrected in JS, garderMenuDansLecran(). */
.nav-menu{position:absolute;top:calc(100% + .35rem);right:0;min-width:min(15rem, calc(100vw - 16px));
  max-width:calc(100vw - 16px);z-index:60;
  background:var(--card);border:1px solid var(--line);border-radius:10px;padding:.35rem;
  box-shadow:0 12px 32px rgba(20,18,15,.28)}
.nav-menu a{display:block;padding:.42rem .7rem;border-radius:7px;color:var(--ink);
  text-decoration:none;font-size:.875rem;white-space:nowrap}
.nav-menu a:hover{background:#f2f1ec}
.nav-menu a.courant{background:#e7f3f0;color:var(--teal);font-weight:600}
.nav-menu-sep{height:1px;background:var(--line);margin:.35rem .3rem}
.nav-menu-vue{color:var(--teal);font-weight:600}
/* Le bandeau est sombre, le menu est clair : l'anneau de focus doit repasser
   au vert foncé à l'intérieur, sinon il hérite de la variante claire posée
   pour le bandeau et disparaît sur fond blanc. */
.nav-menu :focus-visible{outline-color:var(--teal)}
main{max-width:1240px;margin:0 auto;padding:1.5rem 1.2rem 4rem}
/* ── Échelle typographique, lot 1 ───────────────────────────────────────────
   Cible D2 du plan. L'échelle d'avant était plate : corps à 15 px, h1 à
   20,25 px et h2 à 15,3 px, soit exactement la taille du texte courant. Une
   section ne se lisait donc pas comme une section.

   Playfair Display sur les h1 : arbitrage 6 d'Alex, rendu le 25/08. Elle est
   DÉJÀ hébergée et déclarée dans fonts/fair4b-fonts.css (police variable, quatre
   fichiers servis, graisses 400 à 900) et n'était utilisée nulle part dans le
   Studio, contre 34 fois sur la page freelances du même projet. Zéro requête
   supplémentaire, et les titres d'écran gagnent l'identité de la maison.

   Georgia en repli plutôt que le serif générique : elle est présente partout et
   ses proportions sont proches, ce qui évite le saut de mise en page pendant le
   chargement de la police.

   Les tailles sont écrites en rem sur un corps à 15 px : 1.6rem = 24 px,
   1.133rem = 17 px, .933rem = 14 px, .767rem = 11,5 px, .867rem = 13 px. */
h1{font-family:'Playfair Display',Georgia,serif;font-size:1.6rem;font-weight:600;letter-spacing:-.01em;margin-bottom:.2rem}
h2{font-size:1.133rem;font-weight:600;margin:1.3rem 0 .5rem}
.hint{color:var(--sub);font-size:.85rem;margin-bottom:1.2rem}
/* ── Un lien du Studio est vert, jamais bleu (27/08/2026, demande d'Alex) ───
   Le Studio n'avait aucune règle pour les liens de contenu : les huit liens
   sans classe d'app.js (la fiche Boond depuis « À faire », les pièces jointes
   des panneaux dépliés, la référence d'une facture ou d'une pièce comptable)
   sortaient donc en bleu souligné du navigateur, au milieu d'un écran en vert
   et brique.

   La décision existait déjà, mais écrite au mauvais endroit : les deux liens
   de l'écran de connexion portaient `style="color:var(--teal)"` EN LIGNE, dans
   le HTML. Cette règle les remplace, et les deux styles en ligne sont retirés :
   une seule décision, à un seul endroit, plutôt que trois.

   `main a` et non `a` : le bandeau du haut et son menu de rubrique vivent hors
   de `<main>` et ont leurs propres couleurs (.nav-menu a, plus haut). Les
   puces d'action gardent les leurs aussi, `.ic` étant plus spécifique.

   Le soulignement reste : la couleur seule ne suffit pas à signaler un lien à
   qui la distingue mal, et c'est la seule chose qui distingue « fiche Boond »
   du texte rouge qui la précède dans la colonne « À faire ». Au survol le
   lien fonce vers l'encre plutôt que de s'éclaircir : un vert plus clair sur
   fond blanc tomberait sous le rapport de contraste de 4,5 exigé pour du
   texte. */
main a{color:var(--teal)}
main a:hover{color:var(--ink)}
/* "How it works" disclosure under a page's hint (see docs/pages-studio.md and
   roadmap/studio-explications-et-accueil.md, section 2) -- collapsed by
   default, four fixed points rendered from app.js's PAGES, reusing the
   existing badge-etat convention rather than a new style for the fourth
   point ("what it writes"). */
.how-it-works{margin:0 0 1.2rem}
.how-it-works summary{cursor:pointer;font-weight:600;font-size:.82rem;color:var(--teal);user-select:none}
.how-it-works summary:hover{color:var(--ink)}
.how-it-works dl{background:#fbfaf7;border:1px solid var(--line);border-radius:8px;padding:.9rem 1.1rem;margin:.6rem 0 0}
.how-it-works dt{font-weight:600;font-size:.78rem;text-transform:uppercase;letter-spacing:.04em;color:var(--sub);margin-top:.8rem}
.how-it-works dt:first-child{margin-top:0}
.how-it-works dd{margin:.3rem 0 0;font-size:.87rem;line-height:1.55}
.how-it-works dd .badge-etat{margin-bottom:.35rem}
/* Lot 5 (24/09/2026, roadmap/audit-ux-studio-simplicite-iphone.md §3.5): three
   short bullets shown as soon as "Aide" opens, the rest nested under its own
   "Tout le détail" <details> so it stays collapsed by default. */
.aide-puces{list-style:none;margin:.5rem 0 0;padding:0;font-size:.87rem;line-height:1.5}
.aide-puces li{margin-top:.4rem}
.aide-puces li:first-child{margin-top:0}
.how-it-works-detail{margin:.8rem 0 0 .2rem}
.how-it-works-detail summary{cursor:pointer;font-weight:600;font-size:.78rem;color:var(--sub);user-select:none}
.how-it-works-detail summary:hover{color:var(--ink)}
/* Screen-load error template (lot 5, roadmap/audit-ux-studio-simplicite-
   iphone.md §3.2): the technical detail stays collapsed and monospaced,
   never the first thing read. */
.detail-technique{margin-top:.5rem}
.detail-technique summary{cursor:pointer;font-size:.78rem;color:var(--sub);user-select:none}
.detail-technique pre{white-space:pre-wrap;word-break:break-word;font-size:.78rem;background:#fbfaf7;border:1px solid var(--line);border-radius:6px;padding:.6rem .8rem;margin:.4rem 0 0}
.card{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:1.2rem 1.3rem;margin-bottom:1.1rem}
/* Bug fix (Alex, 22/08-23/08) : overflow-x:auto here was wrong regardless of
   what overflow-y was set to. Per the CSS Overflow spec, ANY value other
   than visible/clip on one axis forces the computed value of visible on the
   other axis to auto too -- explicitly writing overflow-y:visible did not
   prevent that, it only documented a mistaken belief that it did. The
   result: this card itself became the nearest scroll container for every
   position:sticky descendant (the filter row, the column headers), which
   never scrolls vertically (it is always exactly as tall as its content),
   so those descendants pinned at their `top` offset INSIDE the card --
   mid-page, over the row content -- instead of tracking the window.
   `clip` is the fix: unlike hidden/scroll/auto, it does not establish a
   scroll container and is exempt from the visible-to-auto coupling, so this
   card never becomes a sticky containing block by itself. It still clips a
   table wider than the card (no horizontal scrollbar, just cut off) --
   .colle-defile-h below is the escape hatch for when that actually happens,
   switched on by app.js only once the table truly overflows horizontally,
   never by default. See the sticky header stack comment further down and
   docs/pages-studio.md for the full mechanism; every sticky level
   (.colle-titre/.colle-onglets/.colle-compteurs/.colle-filtres) now lives
   OUTSIDE any card that wraps a table.list, precisely so this rule never
   has one as a descendant again. */
.card:has(> table.list){overflow-x:clip}
/* Escape hatch (app.js, updateStickyStack(), only once a table's own
   scrollWidth truly exceeds its wrapper's clientWidth): overflow-x:auto for
   a real horizontal scrollbar. This does turn the card back into a scroll
   container -- app.js also degrades that table's own thead sticky
   (.colle-thead-degrade) in the same state, horizontal scrolling wins over
   a pinned header for that table, never both broken at once. */
.card:has(> table.list).colle-defile-h{overflow-x:auto}
/* Home page ("view-accueil"): one card per page, grouped into up to four
   business-rubric blocks (roadmap/studio-explications-et-accueil.md,
   section 3). A card is a clickable button reusing .card's look -- only
   the button-chrome reset below is new, not a style of its own. */
/* scroll-margin-top: the rubric buttons scrollIntoView() their block, and
   .top is sticky -- without this offset the bar hides the block's title
   (seen by Alex on 2026-08-16, "Sales" landed under the header). */
/* --h-top is now measured live by app.js (updateStickyStack()) instead of
   guessed here: the bandeau wraps to two lines on a narrow window, and a
   fixed rem value would then hide the block's title under it, exactly the
   regression Alex saw on 2026-08-16. Same fix reused below for the Studio
   CV editor and the Marketing result, the two other scrollIntoView targets
   outside the eleven screens this plan touches. */
/* ── « Ce matin », lot 4 (26/08/2026) ───────────────────────────────────────
   Au-dessus des cartes, pas à leur place : les cartes enseignent l'application
   et servent à qui la découvre, la bande sert à qui l'utilise tous les jours.
   Les deux publics cohabitent sur le même écran.

   Chaque chiffre est un LIEN, pas un encart : il mène à sa liste déjà filtrée.
   D'où la taille de frappe généreuse et le survol franc, qui annoncent qu'on
   peut cliquer, là où les compteurs des écrans ne l'annonçaient pas. */
.ce-matin{background:var(--card);border:1px solid var(--line);border-radius:12px;
  padding:1rem 1.1rem;margin-bottom:1.6rem}
.ce-matin-tete h2{margin:0 0 .8rem;font-size:1.05rem}
.ce-matin-chiffres{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(9rem,100%),1fr));gap:.6rem}
.ce-matin-chiffre{display:block;background:#fbfaf7;border:1px solid var(--line);border-radius:10px;
  padding:.7rem .8rem;text-decoration:none;color:inherit}
.ce-matin-chiffre:hover{border-color:var(--teal);background:var(--card)}
.ce-matin-chiffre b{display:block;font-size:1.5rem;font-weight:600;letter-spacing:-.02em;
  line-height:1.15;font-variant-numeric:tabular-nums}
.ce-matin-chiffre span{display:block;font-size:.8125rem;color:var(--sub);margin-top:.2rem;line-height:1.3}
/* Le ton vient du serveur, pas d'un seuil décidé ici : c'est l'écran qui sait
   si son chiffre appelle une action. Un zéro « calme » se lit en vert, ce qui
   est une information et pas une décoration. */
.ce-matin-chiffre.alerte b{color:var(--warn)}
.ce-matin-chiffre.calme b{color:var(--ok)}
/* Ton "neutre" (visual contract, 13/09/2026, part A): red is reserved to
   "act today" now, so a stock figure renders in plain ink. This already
   falls out of .ce-matin-chiffre's own color:inherit -> body{color:var(--ink)}
   with no rule at all, but it is spelled out here so it cannot silently
   drift if either of those changes later. */
.ce-matin-chiffre.neutre b{color:var(--ink)}

/* Row titles (part B): "À traiter aujourd'hui" then "En stock", small caps
   look -- smaller, bold, wide letter-spacing, uppercase -- same recipe as
   the approved visual contract. An empty row is simply not rendered by
   app.js, nothing to hide here. */
.ce-matin-rangee-titre{font-size:.72rem;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--sub);margin:.9rem 0 .5rem}
.ce-matin-tete + .ce-matin-rangee-titre{margin-top:0}

/* Folded night-processing line (part C): smaller and greyer than .muted --
   read once, at a glance, never the point of the page. */
.ce-matin-nuit{margin-top:.9rem;font-size:.78rem;color:var(--sub)}
.ce-matin-nuit .ok{color:var(--ok);font-weight:600}
.ce-matin-nuit-detail{display:block;margin-top:.25rem}
/* A real <button> since lot 2 of roadmap/studio-mobile-revue-ui-ux-pro-max.md
   (25/09/2026, constat 2: a plain span could not be reached by keyboard) --
   never nested inside another <button> here (its parents are plain <span>s),
   so no HTML-nesting issue like .carte-plier's own below. font/border/
   background/padding reset to erase the native button chrome; the visible
   look is otherwise unchanged. */
.ce-matin-nuit-toggle{font:inherit;color:var(--teal);font-weight:600;cursor:pointer;
  border:0;background:none;padding:0}
.ce-matin-nuit-toggle:hover{text-decoration:underline}

.accueil-bloc{margin-bottom:1.6rem;scroll-margin-top:calc(var(--h-top, 4.5rem) + .5rem)}
#studio-editor,#marketing-resultat{scroll-margin-top:calc(var(--h-top, 4.5rem) + .5rem)}
.accueil-cartes{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(230px,100%),1fr));gap:var(--gouttiere)}
.accueil-carte{display:block;width:100%;text-align:left;font:inherit;color:inherit;cursor:pointer}
.accueil-carte:hover{border-color:var(--teal-l)}
.accueil-carte h3{font-size:.95rem;margin:0 0 .35rem}
.accueil-carte p{margin:0}
/* Cards fold on the home page (part D, visual contract 13/09/2026). Until
   lot 2 of roadmap/studio-mobile-revue-ui-ux-pro-max.md (25/09/2026,
   constat 2), this was a plain span, never a <button>: .accueil-carte was
   itself a <button>, and a real nested <button> is invalid HTML the parser
   would split out of it. That lot changes the CARD instead (app.js's
   renderAccueil(): a <div role="button" tabindex="0"> now, its own
   click/keydown handling already delegated on #accueil-body), which is what
   lets "Plus ▾" become a real, keyboard-reachable <button> here -- same
   look, border/background/padding reset to erase its native chrome. */
.carte-reste{display:inline}
.carte-plier{display:inline-block;margin-left:.3rem;font:inherit;font-size:.8rem;font-weight:600;
  color:var(--teal);cursor:pointer;border:0;background:none;padding:0}
.carte-plier:hover{text-decoration:underline}
label{display:block;font-weight:600;font-size:.82rem;margin:.7rem 0 .25rem}
label small{font-weight:400;color:var(--sub)}
input[type=text],input[type=email],input[type=password],input[type=search],input[type=number],textarea,select{width:100%;border:1px solid var(--line-champ);border-radius:7px;padding:.55rem .7rem;font:inherit;background:#fff}
/* Bouton "Afficher le mot de passe" (lot 5, roadmap/audit-ux-studio-
   simplicite-iphone.md §3.6) : le champ garde sa largeur pleine, le bouton
   suit en dessous plutôt que superposé dedans, plus simple et tout aussi
   tactile que l'icône classique dans le champ. */
.mot-de-passe-champ input{margin-bottom:.35rem}
.mot-de-passe-champ .btn-sm{width:auto}
.recherche{position:relative;margin-bottom:.5rem}
.recherche svg{position:absolute;left:.95rem;top:50%;transform:translateY(-50%);width:19px;height:19px;stroke:var(--sub);fill:none;stroke-width:2;pointer-events:none}
.recherche input[type=search]{width:100%;font-size:1.02rem;padding:.85rem 1rem .85rem 2.85rem;border:1.5px solid var(--line-champ);border-radius:11px;background:#fff}
.recherche input[type=search]:focus{outline:none;border-color:var(--teal-l);box-shadow:0 0 0 4px rgba(122,196,184,.22)}

/* Filtres sur une seule ligne, 26/08/2026 (demande d'Alex, écran Documents
   obligatoires). Ce n'est pas une préférence esthétique : la hauteur de cette
   carte décide si les en-têtes de colonnes restent figés. app.js retire le
   figement quand la pile collante dépasse 45 % de la hauteur de fenêtre
   (STICKY_THEAD_TOP_CLAMP_RATIO), et la recherche sur sa propre ligne, en
   1,02 rem avec .85rem de rembourrage, pesait 154 px des 284 px de la pile.

   Posé en classe séparée plutôt que sur .colle-filtres : les autres écrans
   gardent leur mise en page, aucun n'est touché par ce lot. Les rangées de
   filtres passent toujours à la ligne sous ~600 px de large (flex-wrap), et le
   garde-fou de app.js reste le filet en dessous. */
.filtres-en-ligne{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}
/* La recherche prend la place restante, avec min-width:0 pour qu'elle accepte
   de rétrécir : sans lui un flex item refuse de passer sous la largeur de son
   contenu et pousse la carte hors de l'écran, le défaut déjà corrigé au lot 1
   bis plus bas dans ce fichier. */
.filtres-en-ligne .recherche{flex:1 1 240px;min-width:0;margin-bottom:0}
.filtres-en-ligne .recherche input[type=search]{font-size:.95rem;padding:.48rem .8rem .48rem 2.4rem}
.filtres-en-ligne .recherche svg{left:.78rem;width:17px;height:17px}
.filtres-en-ligne select{max-width:220px;margin-bottom:0}

/* Panneau de détail d'une ligne de Documents obligatoires, 26/08/2026.
   Deux colonnes sur écran large, une seule dès qu'il n'y a plus la place :
   `auto-fit` avec un plancher de 280 px suffit, sans requête média. Le fond
   légèrement distinct de --card sépare le panneau de la ligne qui l'ouvre,
   sans bordure supplémentaire qui alourdirait un tableau déjà dense. */
.suivi-detail>td{background:var(--bg);padding:.9rem 1rem}
.suivi-detail-grille{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.2rem}
textarea{min-height:90px;resize:vertical}
/* Lot 1 bis : `min-width:220px` était un PLANCHER, pas une préférence. Sous
   220 px de place disponible la colonne refusait de rétrécir et poussait la
   carte hors de l'écran, ce qui arrive dès qu'un téléphone étroit ouvre un
   formulaire à deux colonnes. `flex:1 1 220px` garde la même intention (viser
   220 px, passer à la ligne en dessous de deux colonnes) et `min-width:0`
   autorise enfin la réduction. La règle par défaut d'un élément flex est
   `min-width:auto`, c'est-à-dire « jamais plus petit que mon contenu » : c'est
   la cause la plus fréquente de débordement horizontal en flex, et elle ne se
   voit pas dans la feuille puisqu'elle n'y est pas écrite. */
.row{display:flex;gap:var(--gouttiere);flex-wrap:wrap}.row>div{flex:1 1 220px;min-width:0}
.btn{display:inline-block;background:var(--teal);color:#fff;border:none;border-radius:8px;padding:.65rem 1.3rem;font:inherit;font-weight:600;cursor:pointer}
.btn:disabled{opacity:.5;cursor:wait}
.btn-ghost{background:#fff;color:var(--teal);border:1.5px solid var(--teal)}
.btn-warn{background:#fff;color:var(--warn);border:1.5px solid var(--warn)}
.btn-del{background:#fff;border:1px solid var(--line-champ);color:var(--warn);border-radius:6px;cursor:pointer;padding:.35rem .5rem;font-weight:700}
.btn-sm{padding:.3rem .7rem;font-size:.78rem;border-radius:6px}
/* Reorder arrows for a Studio CV experience block (03/09/2026, Alex's request
   for up/down reordering -- see app.js, renderExperienceBlocks). Same family
   as .btn-del just above: white background, thin outline, small via .btn-sm
   -- but --sub rather than --warn, since moving a block is an ordinary
   action, not a destructive one, and shouldn't borrow the ✕'s warning color.
   No padding/font-size declared here: .btn-sm (combined in the markup)
   supplies both, which also sidesteps a cascade tie that source order would
   otherwise have to settle between the two classes. */
.btn-reordre{background:#fff;border:1px solid var(--line-champ);border-radius:6px;color:var(--sub);cursor:pointer;font-weight:700;margin-right:.35rem}
/* An arrow disabled at the end of the list needs its own rule: like .btn-del
   above, .btn-reordre does not carry .btn, so nothing dims it and nothing
   drops its cursor:pointer -- a disabled arrow would look and feel clickable.
   Note for anyone tempted to reach for .btn:disabled instead: that one poses
   cursor:wait, meant for a button mid request, and would be wrong here anyway
   since these arrows never load anything. */
.btn-reordre:disabled{opacity:.5;cursor:default}
.muted{color:var(--sub);font-size:.867rem}
.status{margin-left:1rem;font-size:.85rem;color:var(--sub)}
.err{color:var(--warn);font-weight:600}
.ok{color:var(--ok);font-weight:600}
.warn{color:var(--warn)}
/* Amber, not red (roadmap audit-ux-studio-simplicite-iphone.md §5.3, lot 9):
   a counter running under a minimum while typing is a nudge, not yet a
   mistake -- .err's red is reserved for an actual failed attempt. */
.compteur-manque{color:var(--warn-texte);font-weight:600}
.spinner{display:inline-block;width:14px;height:14px;border:2px solid var(--teal-l);border-top-color:transparent;border-radius:50%;animation:sp .8s linear infinite;vertical-align:middle;margin-right:.4rem}
@keyframes sp{to{transform:rotate(360deg)}}
/* Corps de tableau 12,75 → 14 px, en-tête de colonne 10,8 → 11,5 px (lot 1, D2).
   font-variant-numeric: tabular-nums aligne les chiffres sur une largeur fixe :
   sur ces quinze écrans, une colonne de montants ou de dates devient lisible en
   diagonale, ce qu'une chasse proportionnelle interdit.
   Mesuré avant de le faire : aux largeurs de travail réelles (1 440 et 1 180 px)
   ce grossissement ne change RIEN à la largeur des tableaux, qui remplissent
   déjà leur carte. À 970 px, où l'écran des documents débordait déjà (1 007 px
   pour une carte de 910), le débordement passe à 1 055 px, soit 48 px de plus.
   La barre de défilement horizontale de .colle-defile-h couvre déjà ce cas ; la
   réduction du nombre de colonnes est au lot 5.

   Deux précisions ajoutées au lot 5a.1, en reprenant cette règle :

   1. Les tailles annoncées ci-dessus sont comptées sur les 15 px du `body`,
      alors que `rem` se compte sur la racine, qui vaut 16 px et que rien ici ne
      redéfinit. Les vraies valeurs sont donc 13,6 → 14,9 px pour le corps et
      11,5 → 12,3 px pour l'en-tête. Le sens de l'écart et l'intention du lot 1
      ne changent pas, seuls les trois chiffres étaient faux.
   2. `line-height:1.35`, la seconde moitié de la densité prévue par le plan
      (section 4, B3) : l'interligne du corps de tableau était celui du `body`,
      1,5, dessiné pour des paragraphes et non pour des lignes d'une ligne. Le
      nombre de lignes visibles augmente, la hauteur de chaque ligne baisse
      d'environ 3 px, et la lisibilité ne bouge pas. Sans effet sur la largeur,
      donc sans effet sur les débordements. */
table.list{width:100%;border-collapse:collapse;font-size:.933rem;line-height:1.35;font-variant-numeric:tabular-nums}
/* ── L'en-tête de colonne a le droit de passer à la ligne (lot 5a bis) ──────
   Signalé par Alex le 27/08 en recettant le 5a : sur Contrats du Talent, la
   colonne « À faire », celle qui dit ce qu'il y a à faire, tombait à 148 px
   pour une phrase de 500, et s'affichait sur six lignes.

   Ce n'était PAS un débordement : mesuré, le tableau faisait 1 158 px dans une
   carte de 1 200, avec 42 px inutilisés. C'était une répartition. Toutes les
   autres colonnes portent `white-space:nowrap`, donc chacune réclame sa
   largeur maximale et ne cède rien ; « À faire » est la seule cellule de la
   ligne qui sache se replier, donc c'est elle qui payait, pour toutes.

   Et ce qui réclamait le plus n'était pas la donnée, c'était l'EN-TÊTE :
   « COMMANDE JUSQU'AU » en capitales espacées mesure 178 px quand la date
   qu'il coiffe en mesure 85. Le `nowrap` de cette règle interdisait à ces
   deux mots de s'empiler, et la colonne payait la différence.

   Mesuré au banc, à 1 440 px, sur les lignes réelles de l'écran :

     variante                  À faire   Pièce   1re ligne   corps
     telle quelle               148 px   208 px    136 px    444 px
     en-têtes repliables        248 px   208 px     76 px    283 px
     largeur sur « À faire »    220 px   140 px     96 px    323 px
     les deux                   321 px   175 px     76 px    263 px

   Les deux, donc. L'en-tête grandit de 16 px une seule fois, chaque ligne
   maigrit de 60, et le corps du tableau perd 181 px sur quatre lignes. La
   colonne « Pièce » garde 175 px, loin des 97 px où elle s'était écrasée le
   26/08 : c'est ce chiffre-là qu'il faut surveiller si quelqu'un reprend ces
   largeurs. */
table.list th{text-align:left;color:var(--sub);font-size:.767rem;text-transform:uppercase;letter-spacing:.06em;padding:.4rem .4rem;border-bottom:1px solid var(--line);white-space:normal}
/* La colonne « À faire » de Contrats du Talent porte la seule phrase de la
   ligne, et c'est la seule qui dise quoi faire : elle réclame sa largeur au
   lieu de la mendier. `width` sur le <th> est une préférence, pas une
   contrainte : à l'étroit elle rend le terrain, comme la colonne « Pièce »
   juste en dessous (même levier, même raison, voir le 26/08). */
table.list th.col-a-faire{width:26rem}
table.list td{padding:.45rem .4rem;border-bottom:1px solid var(--line)}
table.list tbody tr:hover{background:#faf9f6}
/* En-tête de colonne triable, lot 5a.3. Le bouton est posé par app.js dans le
   <th> (voir installerTriDeColonnes) : une cellule d'en-tête ne prend pas le
   focus et ne répond ni à Entrée ni à Espace, un <button> fait les deux sans
   qu'on ait à réinventer le clavier.
   `font:inherit` n'est pas cosmétique : sans lui le bouton reprend la police et
   l'interligne du navigateur, la hauteur du <th> change, et la pile d'en-têtes
   collants (voir updateStickyStack) se décale d'autant sur les sept écrans
   concernés. Mesuré au banc, avec cette règle : hauteur d'en-tête inchangée. */
table.list th.th-triable{padding:0}
table.list th.th-triable .tri{font:inherit;color:inherit;letter-spacing:inherit;text-transform:inherit;text-align:left;background:none;border:0;cursor:pointer;padding:.4rem .4rem;width:100%;display:block}
table.list th.th-triable .tri:hover{color:var(--ink)}
table.list th.th-triable[aria-sort="ascending"] .tri,table.list th.th-triable[aria-sort="descending"] .tri{color:var(--teal)}
.tri-fleche{margin-left:.25rem;font-size:.85em}
/* Colonne « AO / Mission » : un titre d'AO va jusqu'à 300 caractères et
   étirait toute la ligne. Plafond de largeur, avec passage à la ligne plutôt
   que troncature, pour que le titre reste lisible en entier. La valeur est le
   seul chiffre à toucher pour l'ajuster (recette du 11/08, point 3). */
/* Lot 1 bis : `min(20rem,100%)` et `min(17ch,100%)` au lieu des valeurs nues.
   Un `max-width` en unité absolue est un plafond qui ne sait pas descendre :
   à 375 px il ne mord pas (mesuré : colonne AO à 107 px, .tronque à 174 px,
   donc les 20rem et 17ch ne servent à rien), mais il devient un plancher
   déguisé dès qu'un conteneur plus étroit apparaît. `min(valeur,100%)` garde
   le plafond au large, ce pour quoi il a été écrit le 11/08, et rend la
   réduction possible à l'étroit. Aucun effet visible sur un écran de bureau,
   c'est voulu : ces deux lignes préparent, elles ne corrigent rien de visible
   aujourd'hui. */
/* ── Colonnes plafonnées ────────────────────────────────────────────────────
   Trois colonnes portent un texte dont la longueur ne dépend pas de nous, et
   toutes trois ont fait déborder leur tableau. Un tableau plus large que son
   conteneur arme le défilement horizontal (`.colle-defile-h`), et l'en-tête de
   colonnes collant abandonne alors au profit de celui-ci : le débordement ne
   coûte donc pas seulement une barre de défilement, il coûte l'en-tête figé.

   Le plafond ne sert pas à gagner quelques pixels, il sert à rendre la largeur
   du tableau **indépendante des données**. Mesuré sur chacune, à 1200 px
   utiles, le 11/08 pour la première et le 26/08 pour les deux autres :

     col-ao         Studio CV, titre d'AO           jusqu'à 300 caractères
     col-piece      Contrats, libellé de pièce      écrasée à 97 px → stable à 208 px
     col-reference  Pièces comptables, nom de PDF   étirée à 598 px → stable à ~200 px

   Les deux dernières se soignent par des moyens DIFFÉRENTS, décrits plus bas :
   l'une était écrasée, l'autre étirée, et le remède de l'une aggrave l'autre.

   `overflow-wrap:anywhere` est indispensable et non décoratif : les libellés
   qui débordent sont souvent des noms de fichiers sans espace
   (« Facture_2026_02_Prestations_Conseil_Mission_Decathlon_Digital.pdf »), que
   ni `white-space:normal` ni un plafond ne savent couper sans lui.

   Les valeurs diffèrent parce que le contenu courant diffère : une référence
   normale (« FAIR4B_FACT2602002309 ») doit tenir sur une ligne, un titre d'AO
   n'a jamais eu vocation à y tenir. */
   **Le défaut qu'Alex a vu le 26/08, et le levier qui marche.** Posé d'abord en
   `max-width` sur les cellules, le plafond a produit l'inverse : la colonne
   « Pièce » de Contrats s'est affichée sur une centaine de pixels, l'en-tête
   coupé en « PIÈC / E » et le libellé à un caractère par ligne. Cause :
   `overflow-wrap:anywhere` ramène la largeur MINIMALE du contenu à un seul
   caractère, et l'algorithme de répartition des tableaux se sert de ce minimum
   pour écraser la colonne au profit de ses voisines, ici la longue colonne
   « À faire ». Reproduit au banc : 97 px.

   Un `min-width` en réponse ne suffit pas et se retourne : sur une cellule de
   tableau, `min-width` et `max-width` ne sont que des indications pour cet
   algorithme, pas des contraintes. Mesuré : `min-width:11rem` donnait 135 px sur
   Contrats, donc en dessous du plancher demandé, et faisait déborder Pièces de
   176 px, l'inverse du but recherché.

   Ce qui marche est `width` sur la CELLULE D'EN-TÊTE, que la répartition
   respecte comme une préférence forte. Mesuré, libellé court puis long, dans les
   deux cas : Contrats 208 px et 208 px, Pièces 224 px et 224 px, tableau dans
   son conteneur avec 42 px de marge. **La largeur devient indépendante des
   données**, ce qui était le but depuis le début : c'est elle, et non quelques
   pixels gagnés, qui garantit que l'en-tête de colonnes reste figé demain.

   La colonne AO garde sa forme du 11/08, `max-width` seul : elle n'a jamais
   posé de problème et elle vit sur un autre écran. On ne la change pas pour
   faire joli. */
table.list th.col-ao,table.list td.col-ao,
table.list th.col-piece,table.list td.col-piece,
table.list th.col-reference,table.list td.col-reference{white-space:normal;overflow-wrap:anywhere}
table.list th.col-ao,table.list td.col-ao{max-width:min(20rem,100%)}
/* `min-width:0` sur la cellule est la seconde moitié du levier, et elle n'est
   pas facultative : par défaut une cellule vaut `min-width:auto`, c'est-à-dire
   « jamais plus étroite que mon contenu ». Un nom de fichier sans espace impose
   alors sa largeur entière malgré le `width` de l'en-tête. Mesuré sans elle :
   la colonne Référence repartait à 598 px et le tableau débordait de 176 px. */
/* Deux remèdes distincts, parce que les deux colonnes souffrent de maux
   opposés, et les mélanger ne soigne ni l'un ni l'autre (mesuré : le `width` de
   l'en-tête entre en conflit avec le plafond et le rend inopérant).

   « Pièce » est ÉCRASÉE : son libellé contient des espaces, il se replie donc
   tout seul, sa largeur minimale tombe à un mot et la colonne voisine « À
   faire » lui prend la place. Remède : `width` sur l'en-tête, que la
   répartition respecte comme une préférence forte, plus `min-width:0` sur la
   cellule pour qu'elle accepte cette largeur.

   « Référence » est ÉTIRÉE : son libellé est souvent un nom de fichier d'un
   seul tenant, sans espace, dont la largeur minimale vaut la longueur entière.
   Remède : un plafond sur l'en-tête ET sur la cellule, avec la coupure
   autorisée à l'intérieur du mot, seule façon de ramener ce minimum.

   Vérifié, libellé court puis long, à 1440, 1280 et 1024 px : Pièce 208 px dans
   les six cas, Référence 199 à 224 px, tableau toujours dans son cadre. */
table.list td.col-piece{min-width:0}
table.list th.col-piece{width:13rem}
/* Pour « Référence », ni `max-width` ni `width` sur la cellule ne s'imposent :
   la largeur d'une colonne de tableau ne descend jamais sous la largeur
   minimale de son contenu, et un nom de fichier d'un seul tenant a une largeur
   minimale égale à sa longueur entière. Mesuré : 598 px et 176 px de
   débordement, quelle que soit la combinaison essayée sur la cellule.
   La contrainte va donc sur le LIEN, qui est un élément ordinaire et non une
   cellule : bloc, largeur plafonnée, coupure par points de suspension. Le texte
   entier reste accessible en infobulle (`title`, posé par app.js) et le lien
   ouvre toujours le PDF. C'est le motif de `.tronque`, déjà employé sur le
   Vivier et les Créances, appliqué ici à une colonne entière. */
table.list th.col-reference{width:12rem}
/* `inline-block` et non `block` depuis le lot 5b : la référence partage
   désormais sa cellule avec le chevron de dépliage, et un lien en bloc
   l'aurait renvoyé à la ligne suivante. La troncature, elle, ne change pas.
   Le sélecteur descend d'un cran (`a` et non `>a`) pour rester vrai si le lien
   est un jour enveloppé. */
table.list td.col-reference a{display:inline-block;max-width:12rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}
/* `17ch` sec, PAS `min(17ch,100%)` (30/08/2026, signalé par Alex, capture à
   l'appui) : pendant le calcul de largeur intrinsèque d'un tableau en layout
   automatique, le `100%` est cyclique (il dépend de la cellule qu'on mesure),
   et Chrome ignore alors le plafond ENTIER. La colonne Note des Créances se
   dimensionnait sur le texte complet sans coupure : cellule de 597 px pour un
   texte affiché de 174 px, tableau de 1514 px dans une fiche de 1121 px, page
   en défilement horizontal, et les CTA épinglés par `td.actions{sticky}` au
   bord de la FENÊTRE au lieu du bord de la carte. Le `100%` n'apportait rien
   hors de ce bug : un élément en bloc ne dépasse jamais son conteneur de
   lui-même. Mesuré sur la page réelle : avec `17ch`, tableau à 1121 px pile. */
.tronque{display:block;max-width:17ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--sub)}
/* Sous-ligne d'une cellule : une seconde information sous la principale, sans
   colonne de plus. C'est le motif déjà employé sur Encaissements (payeur +
   alias) et Contrats (talent + responsable), nommé ici pour qu'il cesse d'être
   recopié en style en ligne à chaque fois qu'on en a besoin. Le gain n'est pas
   cosmétique : une colonne coûte de la largeur à un tableau qui en manque, une
   sous-ligne ne coûte que de la hauteur. */
.sous-ligne{display:block;font-size:.8125rem;color:var(--sub);margin-top:.15rem;white-space:nowrap}
/* Lot 5b : une sous-ligne dans une cellule ne doit pas ÉLARGIR sa colonne.
   Sans ce plafond, un indice de rapprochement Qonto ou un nom de société long
   imposerait sa largeur au tableau entier, et la fusion de colonnes aurait
   coûté en largeur ce qu'elle vient de faire gagner. Le texte complet reste
   dans le panneau dépliable, qui est fait pour ça. */
table.list td .sous-ligne{max-width:100%;overflow:hidden;text-overflow:ellipsis}
/* Colonne d'actions des six tableaux, et elle n'est JAMAIS posée ailleurs que
   sur un <td> (Studio CV, posts Marketing, Documents obligatoires, factures et
   encaissements des Créances, Pièces comptables). Les blocs d'actions qui vivent
   dans une carte ou une fenêtre portent d'autres classes : .actions-paiement,
   .pa-entete-actions, .enc-detail-actions.

   Défaut signalé par Alex le 25/08/2026, capture à l'appui, sur Documents
   obligatoires en production : cette règle était `display:flex`, et un <td> dont
   le display calculé vaut flex CESSE D'ÊTRE une cellule de tableau. Le navigateur
   fabrique une cellule anonyme autour de lui, la boîte ne s'étire donc plus à la
   hauteur de la ligne, elle se cale en haut et emporte avec elle la bordure basse
   que table.list td lui donne. Mesuré sur l'écran réel : boutons remontés de 11 à
   32 px selon la hauteur de ligne, filet de séparation coupé 22 à 63 px trop haut,
   et sur Studio CV les .ic de 26 px écrasées, un enfant de flex étant compressible.

   D'où table-cell explicite, l'alignement vertical rendu au tableau, et l'écart
   entre boutons repris en marge puisque `gap` ne s'applique qu'aux conteneurs
   flex/grid. Le lot 5 de ~/roadmap/studio-passe-de-design.md réécrit ces six
   cellules : si un jour elles gagnent un <div> enveloppe, le flex peut y revenir,
   sur le div, jamais sur le td. Gardé par test_studio_front.py. */
td.actions{display:table-cell;vertical-align:middle;text-align:right;white-space:nowrap}
td.actions > *{vertical-align:middle}
td.actions > * + *{margin-left:.25rem}

/* ── La colonne d'actions ne se coupe jamais (26/08/2026, signalé par Alex) ──
   Sur Pièces comptables, « Valider » et « Écarter » sortaient du cadre : le
   tableau dépasse son conteneur, `.colle-defile-h` arme un défilement
   horizontal, et ce qui est coupé en premier est la DERNIÈRE colonne, c'est-à-
   dire précisément les deux boutons qui sont la raison d'être de l'écran, sur
   1235 pièces à valider. Les actions étaient atteignables en défilant vers la
   droite, donc invisibles pour qui ne le devine pas.

   `position:sticky; right:0` épingle cette cellule au bord droit du conteneur
   qui défile. Quand le tableau tient dans son cadre, la règle ne fait rien :
   une cellule collante sans débordement reste à sa place. Il n'y a donc pas de
   cas où elle nuit.

   Le fond opaque et le liseré sont nécessaires, pas décoratifs : sans eux les
   cellules voisines défileraient VISIBLEMENT sous les boutons. Le fond reprend
   celui de la ligne survolée pour que l'effet de survol ne s'arrête pas net au
   bord de la colonne épinglée.

   Ce n'est pas la réponse de fond au débordement, qui reste le lot 5 et son
   classement des colonnes par priorité. C'est la garantie que, quoi qu'il
   arrive à la largeur, l'action reste sous la main. */
td.actions,th.col-actions{position:sticky;right:0;z-index:1;background:var(--card)}
table.list tbody tr:hover td.actions{background:#faf9f6}
.card.colle-defile-h td.actions,.card.colle-defile-h th.col-actions{box-shadow:-6px 0 6px -6px rgba(30,30,30,.25)}
.ic{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:1px solid var(--line-champ);background:#fff;border-radius:6px;cursor:pointer;font-size:.85rem;line-height:1;text-decoration:none;color:var(--sub);padding:0}
.ic:hover{border-color:var(--teal);color:var(--teal)}
/* ── La légende d'une puce, dessinée par nous (27/08/2026, demande d'Alex) ──
   L'infobulle native met environ une seconde à paraître, et ce délai n'est
   réglable ni en CSS ni en HTML : il appartient au système. Sur une rangée de
   quatre puces qu'on parcourt pour trouver la bonne, une seconde par puce est
   le prix de la recherche, à chaque ligne.
   Le texte affiché est l'`aria-label` du bouton, pas un attribut de plus : le
   lecteur d'écran et l'œil disent alors la même chose, et il n'y a qu'un
   endroit à corriger le jour où le libellé change. Le `title` est retiré des
   puces, sans quoi l'infobulle native reviendrait par-dessus une seconde plus
   tard, en double.
   Ancrée à DROITE du bouton et dépliée vers la gauche : la colonne d'actions
   touche le bord de la carte, et `.card:has(> table.list)` coupe ce qui
   dépasse horizontalement. Vers le bas en revanche rien ne coupe, `clip` ne
   valant que pour l'axe déclaré. */
/* La légende dépasse sous la ligne, donc elle empiète sur la ligne suivante :
   il faut que la cellule qui la porte passe DEVANT celle d'en dessous.
   Sans ces deux règles, la colonne d'actions de la ligne suivante, épinglée à
   droite avec son propre plan (`td.actions{position:sticky;z-index:1}`), est
   peinte par-dessus, et la légende disparaît derrière les boutons. Signalé par
   Alex le 27/08, mesuré au pixel : blanc à l'endroit exact où le fond sombre
   de la légende devait apparaître.
   Deux règles et non une : la cellule d'actions est déjà `sticky`, lui
   imposer `position:relative` la décollerait du bord droit. */
table.list td.actions:has(.ic:hover){z-index:6}
table.list td:not(.actions):has(.ic:hover){position:relative;z-index:6}
.ic{position:relative}
/* .ic-legende-ouverte, lot 4 (23/09/2026, plan §3.5/§7 arbitrage Q4): a
   finger cannot ":hover", so a pictogram-only button never showed this
   legend at all on a touch device. app.js adds/removes this class on the
   first tap (a document-level, capture-phase click handler -- see its own
   comment): first tap shows exactly the same legend a mouse gets on hover,
   second tap on the same icon acts. Mouse and keyboard are untouched. */
.ic:hover::after,.ic:focus-visible::after,.ic.ic-legende-ouverte::after{
  content:attr(aria-label);position:absolute;top:calc(100% + 6px);right:0;z-index:5;
  background:var(--ink);color:#fff;font-size:.75rem;line-height:1.35;font-weight:400;
  padding:.35rem .55rem;border-radius:6px;max-width:16rem;width:max-content;text-align:left;
  white-space:normal;pointer-events:none;box-shadow:0 2px 10px rgba(30,30,30,.22)}
/* Pictogrammes d'action, lot 5a.2 : mêmes réglages que la loupe du champ de
   recherche (.recherche svg, plus haut), à ceci près que la couleur suit celle
   du bouton par `currentColor` au lieu d'être figée. C'est ce qui fait que le
   survol de .ic teinte aussi le dessin, sans une règle de plus.
   15 px dans un bouton de 26 : le dessin garde une marge d'air de 5 px, comme
   l'émoji qu'il remplace, et `vertical-align` sert aux pictogrammes posés hors
   bouton, au fil d'un texte (les liens de fichier des panneaux de détail). */
/* `pointer-events:none` rend la légende au survol, et ce n'est pas une
   précaution théorique : mesuré sur un bouton réel de 26 px, points balayés un
   par un, **256 des 576 points de sa surface désignent le DESSIN et non le
   bouton**, soit 44 %. Or un <svg> n'expose pas le `title` HTML de son parent :
   le navigateur y cherche un <title> SVG, n'en trouve pas, et n'affiche rien.
   La légende apparaissait donc une fois sur deux environ, au hasard de
   l'endroit exact où s'arrêtait le pointeur.
   Avec cette ligne : 576 points sur 576 désignent le bouton, donc la légende,
   toujours. Le clic n'est pas affecté, il atteint le bouton de toute façon.

   Signalé deux fois par Alex le 27/08. La première fois j'ai écrit ce correctif
   puis je l'ai RETIRÉ, sur la foi d'une mesure qui comptait « un ancêtre porte
   une légende » là où il fallait compter « l'élément désigné est celui qui la
   porte ». Les deux questions ont l'air identiques et n'ont pas la même
   réponse. */
svg.pic{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;vertical-align:-.2em;pointer-events:none}
/* ── Les étapes de Studio CV (lot 7, 27/08/2026) ────────────────────────────
   Un <details> par étape, replié par la même règle d'accordéon que le
   navigateur applique déjà aux menus : le marqueur natif est remplacé par un
   chevron cohérent avec celui du dépliage de ligne, et le numéro est une
   pastille, parce que « 1, 2, 3 » se lit plus vite qu'un titre. */
details.etape{padding:0}
details.etape > summary{display:flex;align-items:center;gap:.6rem;cursor:pointer;list-style:none;
  padding:.9rem 1.15rem;font-size:1.05rem;font-weight:600;user-select:none}
details.etape > summary::-webkit-details-marker{display:none}
details.etape > summary::after{content:"▸";margin-left:auto;color:var(--sub);transition:transform .12s}
details.etape[open] > summary::after{transform:rotate(90deg)}
details.etape[open] > summary{border-bottom:1px solid var(--line)}
.etape-num{display:inline-flex;align-items:center;justify-content:center;width:1.55rem;height:1.55rem;
  border-radius:50%;background:var(--teal);color:#fff;font-size:.85rem;font-weight:700;flex:0 0 auto}
.etape-corps{padding:1rem 1.15rem 1.15rem}
.badge-etat{display:inline-block;border-radius:999px;padding:.16rem .6rem;font-size:.8rem;white-space:nowrap;cursor:default;border:1px solid transparent}
.badge-ok{background:#e7f3f0;color:var(--ok)}
.badge-warn{background:#fbf2e3;color:var(--warn-texte)}
.badge-ko{background:#fbeae3;color:var(--ko-texte)}
/* The old "neutral" badge class is renamed badge-info, 23/09/2026 (plan
   §3.4): it kept its meaning ("information", no gravity) and only its name
   changed, so that "neutral" -- which said nothing about what the colour
   meant -- never reads as a sixth, undocumented severity next to
   ok/warn/ko/attente. */
.badge-info{background:#f0efe9;color:var(--sub)}
/* New badge, same lot: "en attente normale", a step of a process running
   its course rather than a problem (Brouillon, En relecture, Planifié,
   Promesse de paiement, Pièces non lues, En attente...) -- a dashed outline
   and no fill, deliberately unlike every filled badge above, so "waiting"
   is legible even to someone who cannot see colour at all. */
.badge-attente{background:#fff;border:1px dashed var(--attente-texte);color:var(--attente-texte)}
/* .tag: an ATTRIBUTE, never a state -- interne, à démarrer, Talent Fair4B,
   société commune à N talents. A thin outline with no fill of its own marks
   it as never competing with the five badges above for the reader's eye. */
.tag{display:inline-block;border-radius:999px;padding:.16rem .6rem;font-size:.8rem;white-space:nowrap;border:1px solid var(--line-champ);color:var(--sub);background:transparent}
.badge-manuel{cursor:pointer;background:#fff;border:1.5px dashed var(--line-champ);color:var(--sub);font:inherit;font-weight:600;transition:border-color .12s,background .12s}
.badge-manuel:not(:disabled):hover{border-color:var(--teal);color:var(--teal)}
.badge-manuel.on-ok{background:#e7f3f0;border:1px solid var(--ok);color:var(--ok)}
.badge-manuel.on-ko{background:#fbeae3;border:1px solid var(--warn);color:var(--ko-texte)}
/* Enquêtes de satisfaction : mini-jauge d'évolution sur 7 mois, une barre par mois. */
.spark{display:inline-flex;gap:3px;align-items:flex-end;height:22px}
.spark i{display:block;width:9px;border-radius:2px;background:var(--teal-l)}
.spark i.bas{background:var(--warn)}
.exp{border:1px solid var(--line);border-radius:8px;padding:.9rem 1rem;margin-bottom:.9rem;background:#fbfaf7}
/* !important : .hidden est basculée par le JS sur des éléments dont la classe de
   base pose déjà un display, qui gagnerait à spécificité égale et laisserait
   l'élément visible. L'exemple qui a motivé cette règle était `.modal-overlay`,
   retirée au lot 6 ; le besoin, lui, vaut toujours pour les vues et les cartes. */
.hidden{display:none !important}
.pa-entete{display:flex;justify-content:space-between;align-items:center;gap:1rem;flex-wrap:wrap}
.pa-entete-actions{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}
/* Studio CV editor header (Alex, 20/09/2026, kanban 9ef98720): the photo
   thumbnail and its checkbox's label, replacing the style= the checkbox
   still carried after the 24/08 design pass moved every other rule out of
   index.html. Mirrors .qualite-talent .coche's own look (inline-flex, same
   gap) without dragging in that class's unrelated margin-top -- this pair
   lives in a header row, not a filter block. */
.cv-photo-vignette{width:2.3rem;height:2.3rem;border-radius:50%;object-fit:cover;flex:0 0 auto;border:1px solid var(--line)}
.cv-photo-case{display:inline-flex;align-items:center;gap:.4rem;font-size:.92rem;color:var(--ink)}
.cv-photo-case input{width:auto;margin:0}
.pa-ligne-compacte{display:flex;align-items:flex-start;gap:.6rem;margin-top:.55rem;flex-wrap:wrap}
.pa-etiquette{flex:0 0 auto;font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;color:var(--sub);width:3.6rem;padding-top:.2rem}
.pa-badges{display:flex;flex-wrap:wrap;gap:.35rem;flex:1;min-width:0}
.pa-derogation{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin:.5rem 0 0 4.2rem}
.pa-derogation input[type=number]{width:6.5rem}
.pa-derogation input[type=text]{flex:1;min-width:14rem}
.actions-paiement{display:flex;gap:.6rem;align-items:center;flex-wrap:wrap;margin-top:1.1rem;padding-top:1rem;border-top:1px solid var(--line)}
.docs-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--gouttiere)}
/* Cette requête média est la SEULE qu'ait jamais eue le Studio avant le lot
   1 bis. Elle reste ici, à sa place, plutôt que d'être fondue dans les points
   d'arrêt en fin de fichier : son seuil de 1000 px est propre aux trois PDF de
   Paiement Talents, qui ne tiennent pas côte à côte en dessous, et n'a rien à
   voir avec les seuils généraux de la mise en page. */
@media(max-width:1000px){.docs-grid{grid-template-columns:1fr}}
.feuille{border:1px solid var(--line);border-radius:10px;background:#fff;overflow:hidden;display:flex;flex-direction:column}
.feuille .tete{background:#faf9f6;border-bottom:1px solid var(--line);padding:.55rem .8rem;font-size:.78rem}
.feuille iframe{flex:1;border:0;width:100%;min-height:24rem}
.pdf-absent{height:24rem;display:flex;align-items:center;justify-content:center;color:#b6b5ae;font-size:.78rem;text-align:center;padding:1rem}
main:has(#view-payments:not(.hidden)){max-width:1800px}
/* Six columns need more room than the 1240px default too -- same fix as
   Payments just above, not a new pattern
   (roadmap/studio-kanban-demandes-evolution.md §5, §7 lot 2). Below this
   width .demandes-board's own overflow-x:auto still takes over, so nothing
   breaks on a narrower screen, it just scrolls. */
main:has(#view-demandes:not(.hidden)){max-width:1800px}
/* ── Les cinq dernières fenêtres, en natif (lot 6, 27/08/2026) ──────────────
   `.modal-overlay` était le fond sombre fait à la main, avec son `z-index` et
   son centrage en flex. Le natif fournit les trois (fond, empilement,
   centrage) et, en prime, ce que la version maison ne faisait pas : le focus
   piégé dans la fenêtre et le reste de la page rendu inerte.
   La boîte intérieure (`.modal-box`) est conservée telle quelle : c'est elle
   qui porte le fond blanc, les coins arrondis et la mise en colonne, et la
   garder évite de retoucher `.modal-head`, `.modal-body` et les trois règles
   qui les visent par identifiant. Le <dialog>, lui, n'est plus qu'un cadre
   transparent : sans `padding:0`, le navigateur en ajoute autour de la boîte. */
/* `margin:auto` recentre les onze fenêtres, et ce n'est pas une préférence :
   le navigateur centre une fenêtre modale par `margin:auto`, et la remise à
   zéro `*{margin:0}` en tête de cette feuille l'écrasait. Mesuré au lot 6 :
   les onze s'ouvraient collées en HAUT À GAUCHE de l'écran, y compris les six
   natives livrées le 21/08. Personne ne l'avait signalé, et pourtant les
   captures d'écran de la recette le montraient depuis. */
dialog.modal-fenetre,dialog.app-dialog{margin:auto}
dialog.modal-fenetre{border:0;padding:0;background:transparent;width:min(920px,92vw);max-width:none;max-height:92vh;overflow:visible}
dialog.modal-fenetre::backdrop{background:rgba(30,30,30,.6)}
/* L'aperçu d'un document passe en plein cadre (arbitrage d'Alex du 27/08) :
   un PDF se lit sans loupe, et les deux aperçus du Studio, celui du CV et
   celui d'une facture, se ressemblent enfin. */
dialog.modal-apercu{width:96vw;height:94vh}
dialog.modal-apercu .modal-box{max-height:none;height:100%}
dialog.modal-apercu .modal-body iframe{height:100%}
/* La règle `.modal-overlay` a été retirée ici par le lot 6 : plus aucun élément
   ne la porte, le fond sombre et le centrage étant désormais ceux du navigateur
   (`dialog::backdrop`, plus haut). */
.modal-box{background:#fff;border-radius:10px;max-width:920px;width:100%;max-height:92vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 10px 40px rgba(0,0,0,.3)}
.modal-head{display:flex;justify-content:space-between;align-items:center;padding:.7rem 1.1rem;border-bottom:1px solid var(--line)}
.modal-body{flex:1;overflow:auto;background:#525659}
.modal-body iframe{width:100%;height:78vh;border:0;background:#fff}
/* Matching : la fiche à corriger est un formulaire, pas un PDF -- fond clair,
   contrairement au fond sombre par défaut de .modal-body (pensé pour l'aperçu PDF). */
#matching-fiche-modal .modal-body,#contract-edit-modal .modal-body,#document-upload-modal .modal-body{background:#fff;padding:1.2rem 1.3rem}
/* Articles, lot 1 second pass (11/09/2026) : #article-editor-modal was
   missing from the list above since the first pass, left on the default
   dark #525659 (meant for a PDF preview iframe) behind every label and
   input of the editor form -- found while adding the cards below, whose
   own text would have been unreadable there too. The four dialogs this
   pass adds (a revision, the LinkedIn draft, the manual handoff, and the
   cover-image picker) join it for the same reason: none of them show a PDF. */
#article-editor-modal .modal-body,#article-revision-modal .modal-body,#article-handoff-modal .modal-body,#article-linkedin-modal .modal-body,#article-media-picker-modal .modal-body{background:#fff;padding:1.2rem 1.3rem}
.cv-block{border:1px solid var(--line);border-radius:8px;padding:.9rem 1rem;margin-bottom:.8rem;background:#fbfaf7}
.cv-block-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem}
.cv-block-head b{font-size:.85rem;color:var(--sub);text-transform:uppercase;letter-spacing:.04em}
/* Companion to .btn-reordre: with the two new arrows now leading
   .cv-block-head in the experiences editor, the header's existing
   justify-content:space-between would otherwise spread all four children
   evenly, prying the two arrows apart from each other and from the label.
   Giving the label itself the free space instead (margin-right:auto) keeps
   the arrows packed against it at the left and leaves the ✕, the only thing
   after it, alone at the right -- exactly as before. Scoped to a class the
   label wears only in the experiences editor, so the other .cv-block-head
   usage (compétences) keeps its plain two-child layout untouched. */
.cv-block-title{margin-right:auto}
table.list input[type=text],table.list input[type=number]{margin:0}
/* Évaluation interne du Studio CV (demande 7c631a74, 26/09/2026) : trois
   listes de critères possibles avant le chiffre, chacune masquée quand elle
   est vide (renderStudioEvaluationBlock() dans app.js). Compact, sans rien
   de propre au bloc lui-même -- il réutilise .cv-block, .hint, .muted et
   .btn-ghost déjà en place ailleurs sur cet écran. */
#studio-eval-bloc h4{margin:.6rem 0 .2rem;font-size:.85rem;color:var(--sub);text-transform:uppercase;letter-spacing:.04em}
#studio-eval-bloc ul{margin:0 0 .3rem;padding-left:1.2rem}
#studio-eval-bloc li{overflow-wrap:anywhere}
/* The estimated fit, first and large (Alex, 26/09/2026). Deliberately in the
   plain text colour, never red/green: a verdict colour would read an
   uncalibrated estimate as a pass/fail. */
#studio-eval-bloc .eval-score{margin:.2rem 0 .3rem;display:flex;flex-wrap:wrap;align-items:baseline;gap:.2rem .6rem}
#studio-eval-bloc .eval-score-chiffre{font-size:2.2rem;font-weight:700;line-height:1.1;color:inherit}
#studio-eval-bloc .eval-score-libelle{font-size:1rem;color:var(--sub)}
/* CRÉANCES CLIENTS : imbrication société -> fiche de facturation -> factures
   (un <details> par société, un bloc par fiche à l'intérieur), et trois
   <dialog> natifs (aperçu de relance, suivi, adresses) qui réutilisent le
   gabarit des .modal-box existantes (.modal-head/.modal-body/.actions-paiement)
   plutôt qu'un style à part, comme demandé par le plan. */
#creances-list details.creances-societe{border:1px solid var(--line);border-radius:10px;background:var(--card);margin-bottom:1rem;padding:.9rem 1.1rem}
#creances-list details.creances-societe>summary{cursor:pointer;font-weight:600;list-style:none;display:flex;gap:.6rem;flex-wrap:wrap;align-items:baseline}
#creances-list details.creances-societe>summary::-webkit-details-marker{display:none}
#creances-list details.creances-societe>summary::before{content:"▸";display:inline-block;transition:transform .12s;color:var(--sub)}
#creances-list details.creances-societe[open]>summary::before{transform:rotate(90deg)}
.creances-fiche{border-top:1px solid var(--line);margin-top:.8rem;padding-top:.8rem}
.creances-fiche:first-of-type{border-top:0;margin-top:.6rem;padding-top:0}
/* Filet de défilement horizontal (lot 8, 24/09/2026), même mécanique que
   `.card:has(> table.list)` plus haut mais scopée à ce conteneur-ci : sa
   table.creances-facture-table n'est jamais un enfant direct d'une .card
   (elle est imbriquée dans .creances-fiche, elle-même dans un <details> puis
   #creances-list), donc ce sélecteur-là ne l'atteint jamais. Toggled par
   updateCreancesFactureTableFences() dans app.js, seulement une fois la
   table réellement plus large que sa fiche. */
.creances-fiche{overflow-x:clip}
.creances-fiche.colle-defile-h{overflow-x:auto}
.creances-fiche-entete{display:flex;justify-content:space-between;align-items:flex-start;gap:.8rem;flex-wrap:wrap;margin-bottom:.5rem}
.creances-fiche-dest{font-size:.85rem}
.creances-fiche-source{font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;color:var(--sub)}
dialog.app-dialog{border:none;border-radius:10px;padding:0;max-width:640px;width:92vw;max-height:88vh;box-shadow:0 10px 40px rgba(0,0,0,.3)}
dialog.app-dialog::backdrop{background:rgba(30,30,30,.6)}
dialog.app-dialog .modal-body{background:#fff;padding:1.1rem 1.3rem;max-height:64vh}
/* Le pied d'un dialogue (Annuler / valider) vit HORS de .modal-body, donc hors
   de son rembourrage, et le dialogue lui-même est à padding:0 : sans la règle
   ci-dessous, les boutons collent aux bords. Signalé par Alex le 12/09 sur
   « Qualifier le dépôt », mais partagé par les douze dialogues de ce gabarit.
   Horizontale alignée sur le 1.3rem de .modal-body ; margin-top à 0 parce que
   le body apporte déjà son bas de 1.1rem. Trois dialogues plus anciens portent
   la même correction en style en ligne (.9rem 1.1rem), laissés tels quels. */
dialog.app-dialog>.actions-paiement{margin-top:0;padding:.9rem 1.3rem 1.1rem}
dialog#creances-apercu .modal-body p{margin-bottom:.4rem}
dialog#creances-apercu iframe{width:100%;height:38vh;border:1px solid var(--line);border-radius:8px;background:#fff;display:block;margin-top:.4rem}
/* ENCAISSEMENTS (roadmap/rapprochement-qonto-factures-clients.md, lot C) :
   deux onglets au-dessus du même écran (état en mémoire, voir app.js
   switchCreancesTab), un tableau simple pour les virements (pas d'imbrication
   comme les factures, un virement est déjà une seule ligne), et une liste de
   cases à cocher avec un montant par ligne dans le sélecteur de factures. */
.creances-tabs{display:flex;gap:.5rem;margin-bottom:1rem}
.enc-facture-ligne{font-size:.78rem;color:var(--sub);margin-top:.15rem}
.enc-facture-ligne b{color:var(--ink)}
/* Une ligne compacte par virement (Alex, 22/08 après-midi) : le montant ne
   passe jamais à la ligne, le libellé est tronqué en JS (~40 caractères,
   texte complet dans le title), Rapprochement est la colonne la plus large
   (raison déjà courte, voir matching.py::_join_references), Payeur reste
   raisonnable. Le conteneur garde son overflow-x:auto, la page ne défile
   jamais horizontalement. */
table#enc-table th.col-montant,table#enc-table td.col-montant{white-space:nowrap;text-align:right;min-width:6.5rem}
table#enc-table th.col-payeur,table#enc-table td.col-payeur{max-width:11rem}
table#enc-table th.col-libelle,table#enc-table td.col-libelle{max-width:14rem}
table#enc-table th.col-rapprochement,table#enc-table td.col-rapprochement{width:30%}
.enc-libelle-ligne{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.enc-payeur-alias{font-size:.78rem;color:var(--sub);margin-top:.1rem}
/* Rapprochement cell (Alex, 22/08 soir, "affichage plus simple") : badge en
   clair, une petite ligne "auto · R2" / "manuel" en dessous, une phrase
   courte réécrite côté client (creancesEncRaisonSimple), puis la liste
   compacte des factures affectées -- jamais plus de trois lignes visibles à
   la fois sur la ligne fermée, le reste juste compté ("+ N autres", texte
   simple) : la liste complète se lit dans la ligne dépliée (voir plus bas,
   .enc-detail). */
.enc-regle-ligne,.enc-raison-ligne{margin-top:.15rem;font-size:.78rem}
.enc-factures{margin-top:.3rem}
.enc-facture-boond{color:var(--ok)}
.enc-facture-plus{margin-top:.15rem;font-size:.78rem}
/* Colonne Boond réduite à une icône (même arbitrage) : la phrase complète va
   dans le title, la légende de l'onglet explique les deux icônes une fois. */
.enc-boond-icone{font-size:1rem;cursor:default}
.enc-boond-ok{color:var(--ok)}
.enc-boond-warn{color:var(--warn)}
/* Légende de l'onglet Encaissements : repliable, ouverte par défaut (voir
   app.js, initEncLegende, qui retient la fermeture en localStorage). Même
   habillage .card que le reste de l'écran, un sommaire au style des liens
   plutôt que le petit-capital de .how-it-works, puisque ce n'est pas ce
   bloc-là (elle reste distincte de "Comment ça marche", qu'elle référence). */
.enc-legende summary{cursor:pointer;font-weight:600;font-size:.85rem;color:var(--teal);user-select:none}
.enc-legende summary:hover{color:var(--ink)}
.enc-legende-corps{margin-top:.7rem}
.enc-legende-corps p{font-size:.85rem;line-height:1.55;margin:.5rem 0}
.enc-legende-corps p:first-child{margin-top:0}
.enc-legende .badge-etat{margin-right:.3rem}
/* Pièces comptables (lot B) : bloc repliable "Ce que le Studio a appris",
   une ligne par règle apprise ou saisie -- même habillage .card/summary que
   .enc-legende ci-dessus, jamais un nouveau patron de carte repliable. */
.pieces-regles-corps{margin-top:.7rem}
.pieces-regle{display:flex;align-items:flex-start;gap:.6rem;padding:.5rem 0;border-top:1px solid var(--line)}
.pieces-regle:first-child{border-top:0}
.pieces-regle-icone{font-weight:700;width:1.1em;text-align:center;flex:none}
.pieces-regle-corps{flex:1;font-size:.85rem;line-height:1.5}
#enc-choisir-corps{max-height:50vh;overflow:auto}
/* Per-company headers (roadmap/encaissements-choisir-factures-par-societe.md
   §2.2/§2.3, lot 2) : position:sticky pins each header at the top of
   #enc-choisir-corps -- its nearest scrolling ancestor, same structural
   invariant as .colle-titre further down this file -- while that company's
   own invoices scroll underneath it. The opaque background is required, not
   decorative : without one the scrolling rows show straight through the
   pinned header and it stops being readable. .enc-choisir-groupe-bloc wraps
   one header together with its invoices so the header releases exactly when
   its own group's last row has scrolled past, never sooner and never a
   whole card away (this dialog carries no table.list, so the sticky level
   above is not one test_no_sticky_level_sits_inside_a_card_that_carries_a_
   list_table below has to know about, but the same physical rule holds). */
.enc-choisir-groupe{position:sticky;top:0;z-index:1;background:var(--card);padding:.25rem 0;margin:.9rem 0 .3rem;font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;color:var(--sub)}
.enc-choisir-groupe-bloc:first-child .enc-choisir-groupe{margin-top:0}
/* The lead group's motif (§2.3) reads as a plain sentence, not shouted like
   the rest of the sticky title. */
.enc-choisir-motif{text-transform:none;letter-spacing:normal;font-weight:400}
.enc-choisir-ligne{display:flex;align-items:center;gap:.6rem;padding:.35rem 0;border-bottom:1px solid var(--line)}
.enc-choisir-ligne .enc-choisir-detail{flex:1;min-width:0}
.enc-choisir-ligne input[type=number]{width:7rem;margin:0}
/* Lot 1 (roadmap/encaissements-somme-masquee-et-combinaisons.md §2.1, §4,
   Alex's request 21/09/2026) : #enc-choisir-total moved into .actions-paiement
   (index.html), so margin-top no longer means anything -- it is now the
   counter that explains the "Rapprocher" button staying disabled (the amount
   checked must equal the transfer exactly, Alex's arbitrage of 30/08), and
   it was rendering below the visible, scrolled area of .modal-body's own
   64vh cap as soon as the window's usable height fell under ~810px (measured
   on the 124 real candidate rows of the AGL transfer: 14px of the counter
   already lost at 703px). margin:0 0 0 auto is the flex "push right" trick
   on this container's existing display:flex (studio.css:734) : being the
   FIRST child, its own left margin eats all the row's free space, carrying
   itself and every sibling after it (Annuler, Rapprocher, the status span)
   to the right edge of the footer -- scoped to this one id, so the eleven
   other .app-dialog windows sharing the same .actions-paiement gabarit
   do not move a pixel (plan §6 point 5). Font-size/weight unchanged. */
#enc-choisir-total{margin:0 0 0 auto;font-size:.85rem;font-weight:600}
/* Learn-the-alias checkbox (§2.5, lot 4) : shown or hidden entirely by
   updateEncChoisirAlias() in app.js, so it needs no reserved height here. */
.enc-choisir-alias-bloc{margin-top:.6rem;font-size:.85rem}
.enc-choisir-alias-bloc label{display:flex;align-items:flex-start;gap:.4rem;cursor:pointer}
/* Row expand/collapse on Encaissements (Alex, 24/08, replaces the earlier
   "..." popover): "Confirmer" stays a visible primary button on the closed
   row (mass action on proposed transfers), a chevron at the end of the row
   hints it can be opened, and a click anywhere on the row except an
   interactive element toggles a detail band underneath (app.js,
   toggleEncRow) -- same principle as the Factures échues tab's <details>
   per company, everything readable once open rather than folded away. */
table#enc-table tr.enc-row{cursor:pointer}
.enc-chevron{display:inline-block;margin-right:.4rem;color:var(--sub);transition:transform .12s}
tr.enc-row-open .enc-chevron{transform:rotate(90deg)}
/* Detail row: one cell spanning the whole table, a slightly distinct
   background so it reads as "opened" without breaking the row hover/zebra
   above, and never a horizontal scroll of its own -- text wraps normally
   while the enclosing .card:has(> table.list) still clips/scrolls the table
   as a whole (see the comment above that rule). */
tr.enc-detail{background:#fbfaf7;cursor:default}
tr.enc-detail td{padding:.8rem .6rem}
.enc-detail-body{display:flex;flex-direction:column;gap:.7rem}
.enc-detail-libelle,.enc-detail-factures{font-size:.85rem}
.enc-detail-libelle b,.enc-detail-factures b{display:block;font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;color:var(--sub);margin-bottom:.25rem}
.enc-detail-actions{display:flex;flex-wrap:wrap;gap:.4rem}

/* ── Sticky header stack (roadmap/studio-navigation-entetes-fixes.md) ──────
   "What helps navigate or filter stays in place, what is read once scrolls
   away." .top is already sticky, above (top:0, z-index:40). These three
   classes mark the next levels found on the eleven screens that carry a
   list: the one-line screen title, an optional row of tabs, the compact
   counters strip, and any row of search/filter controls -- several of the
   latter two can occur on the same screen (Créances: tabs, then counters,
   then a search field, then a filter row), so the mechanism does not assume
   a fixed count of three.
   `top` is never hardcoded here: app.js's updateStickyStack() measures the
   actual rendered height of .top and of every one of these blocks in the
   view currently on screen, in DOM order, and writes each one's `top` and
   the resulting --h-top / --h-stack-total custom properties. A fixed CSS
   calc() chain could not do this correctly: the number of stacked levels
   differs per screen (Créances adds a tabs row nothing else has; Users and
   Campagnes have neither counters nor a filter row; the bandeau itself can
   wrap to two lines on a narrow window and change --h-top at any time).
   z-index 31-34: below the bandeau (40) and dialogs (50), above the
   scrolling rows (no z-index, so 0/auto) that slide underneath them --
   hence the opaque background repeating what each block already sits on.

   Structural invariant (Alex, 22/08-23/08, fixing a bug the first version
   of this stack had): a position:sticky element sticks to its nearest
   ancestor SCROLL CONTAINER (any element whose computed overflow-x or
   overflow-y is not visible/clip) -- the page's own scroll, not that
   ancestor's, only when there is no such ancestor at all. .colle-filtres
   used to sit inside the same .card as its screen's table.list, and that
   card is a scroll container by construction (.card:has(> table.list)
   below, needed so a wide table can scroll sideways) -- so the filter row
   pinned to the CARD's top, which never scrolls vertically (it is always
   exactly as tall as its content), landing mid-page over the row content
   instead of tracking the window. Fix: every .colle-titre/.colle-onglets/
   .colle-compteurs/.colle-filtres element in the markup below now lives
   OUTSIDE any card that wraps a table.list, as a sibling above it, never a
   descendant -- see each screen's markup and docs/pages-studio.md. app.js's
   updateStickyStack() also walks ancestors defensively on every recompute
   and degrades (console.warn + .colle-niveau-degrade) any level it still
   finds inside a scroll container, so a future regression here shows up as
   a normally-scrolling block, never a header pinned mid-page again. */
.colle-titre,.colle-onglets,.colle-compteurs,.colle-filtres{position:sticky;background:var(--bg)}
.colle-titre{z-index:34;padding-top:.15rem}
.colle-onglets{z-index:33;padding-top:.4rem}
.colle-compteurs{z-index:32}
.colle-filtres{z-index:31}
/* Compact one-line counters (Alex's arbitration, 22/08): small font, the
   value and its label share one line and wrap together rather than the
   value sitting above the label on two lines, the layout used when counters
   scroll with the page on the few other screens that still show them that
   way (Candidatures, unaffected by this plan). Still a .card underneath for
   its background/border/radius, this class only tightens the spacing. */
.colle-compteurs{display:flex;flex-wrap:wrap;column-gap:1.3rem;row-gap:.3rem;align-items:baseline;padding:.5rem 1rem;font-size:.78rem}
.colle-compteurs .cpt{white-space:nowrap}
.colle-compteurs b{font-size:.92rem;margin-right:.3rem;font-variant-numeric:tabular-nums}

/* ══ Gabarit de page, lot 2 de la passe de design (26/08/2026) ══════════════
   Cible B1 du plan. Trois choses, et une seule intention : ce qui sert à
   décider reste sous les yeux, ce qui se lit une fois s'efface.

   1. Le titre et les actions de page sur la même ligne. Les deux boutons de
      rafraîchissement vivaient au milieu de la barre de filtres, entre deux
      menus, alors qu'ils ne filtrent rien. Ils remontent à droite du titre.
   2. Une phrase sous le titre, pas un paragraphe. Le texte long descend dans
      le panneau Aide, sous un cinquième point « À savoir ».
   3. Les compteurs deviennent le filtre principal.

   Le gain de hauteur n'est pas cosmétique : il tombe directement dans la pile
   collante, dont chaque pixel décide si l'en-tête de colonnes tient (voir
   STICKY_THEAD_TOP_CLAMP_RATIO dans app.js). C'est la réponse structurelle au
   défaut qu'Alex a signalé le 26/08, là où le Vivier n'a eu qu'un correctif
   ponctuel. */
.page-entete-titre{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;flex-wrap:wrap}
.page-entete-titre h1{margin-bottom:0}
.page-actions{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}
.page-actions .muted{white-space:nowrap}
/* On a phone the freshness line may wrap (Alex's iPhone capture, 24/09/2026):
   Prestations' one (« Suivi Boond du … · Factures et paiements : … ») is
   wider than the screen, and a non-wrapping line widened the whole page,
   which Safari then shrank to fit. min-width:0 lets the flex item shrink. */
@media (max-width:639px){
  .page-actions{min-width:0;max-width:100%}
  .page-actions .muted{white-space:normal;min-width:0}
}

/* Un compteur cliquable reste d'abord un compteur : même graisse, même taille,
   même place. Seul l'état actif se voit, et le survol annonce qu'il se clique.
   `aria-pressed` porte l'état pour qui n'a pas l'affichage : c'est un bouton à
   deux positions, pas un lien. */
.colle-compteurs .cpt-filtre{background:none;border:1px solid transparent;border-radius:999px;
  padding:.1rem .5rem;margin:-.1rem -.1rem;font:inherit;color:inherit;cursor:pointer;white-space:nowrap}
.colle-compteurs .cpt-filtre:hover{border-color:var(--line-champ)}
.colle-compteurs .cpt-filtre[aria-pressed="true"]{background:#e7f3f0;border-color:var(--teal);color:var(--teal)}
.colle-compteurs .cpt-filtre[aria-pressed="true"] .muted{color:var(--teal)}
/* Column headers: sticky right below whatever the stack above ends up at.
   Scoped to table.colle-thead rather than every table.list, so the two
   tables outside this plan's eleven screens (Studio CV's own lists,
   Marketing's) are deliberately left alone, per the plan's section 2. */
table.colle-thead thead th{position:sticky;z-index:29;background:var(--card)}
/* Stuck-state affordance: a subtle separator once a level has actually
   reached its pinned position, not before (so the very top of a freshly
   opened screen, where the title sits in normal flow, looks unchanged).
   body.colle-stuck is toggled by app.js from a scroll listener, cheaper
   than one IntersectionObserver per level for a purely cosmetic detail. */
body.colle-stuck .colle-titre,body.colle-stuck .colle-onglets,body.colle-stuck .colle-compteurs,body.colle-stuck .colle-filtres{box-shadow:0 2px 4px rgba(0,0,0,.08);border-bottom:1px solid var(--line)}
/* Narrow-screen fallback (plan section 2): past 40% of the viewport height,
   the counters and the filter rows give up their fixed spot and scroll away
   with the page like the rest of it -- only the title and the column
   headers stay pinned. Toggled on <body> by the same function, which also
   stops feeding these levels' height into the stack below them once this
   is on (see updateStickyStack()). */
/* Repli en deux temps depuis le 26/08/2026 (voir le long commentaire dans
   app.js, updateStickyStack). Une seule règle sacrifiait les compteurs et la
   barre de filtres ensemble ; elle se déclenchait en plein écran de bureau sur
   Commandes, Contrats et Vivier, et y décollait la recherche, ce qu'Alex a
   signalé. Les compteurs partent seuls d'abord, la barre de filtres seulement
   si cela n'a pas ramené la pile sous le seuil. */
body.colle-reduit .colle-compteurs{position:static;box-shadow:none;border-bottom:0}
body.colle-reduit-fort .colle-filtres{position:static;box-shadow:none;border-bottom:0}
/* Per-table thead degrade (app.js, updateStickyStack(), replacing the
   body-wide clamp this used to be -- a screen can carry more than one
   table.colle-thead, e.g. Campagnes, and each needs its own answer): a
   table gives up its thead's stickiness in two cases, and app.js decides
   which applies each time it recomputes --
   (1) the table itself actually overflows its wrapper horizontally right
   now (table.scrollWidth > wrapper.clientWidth) -- .colle-defile-h above
   switches that wrapper to overflow-x:auto for the horizontal scrollbar,
   which makes it a real scroll container again, so the thead cannot track
   the page inside it any more: horizontal scrolling wins, deliberately,
   over a pinned header for that one table;
   (2) the computed `top` for the stack above it would exceed 45% of the
   viewport height (STICKY_THEAD_TOP_CLAMP_RATIO in app.js) -- pinning the
   thead there would plant it mid-page, over row content, the same
   narrow-screen idea .colle-reduit already applies to counters/filtres. */
table.colle-thead.colle-thead-degrade thead th{position:static;top:auto;box-shadow:none}
/* Defensive guard (app.js, updateStickyStack(), a console.warn alongside
   it): if a future change ever puts a .colle-titre/.colle-onglets/
   .colle-compteurs/.colle-filtres level back inside a real scroll
   container -- the exact mistake .card:has(> table.list) used to make --
   app.js finds it by walking ancestors and applies this class rather than
   pin it mid-page like the thead bug above. !important because this is a
   deliberate emergency override, the same reasoning as .hidden's. */
.colle-niveau-degrade{position:static !important}

/* ══ Points d'arrêt, lot 1 bis de la passe de design (25/08/2026) ═══════════
   Cible D4 du plan, part `studio.css`. Avant ce bloc, le Studio comptait UNE
   requête média dans toute l'application, celle des trois PDF de Paiement
   Talents ; tout le reste ne tenait que par `flex-wrap`.

   Ce que ce bloc NE FAIT PAS, dit d'emblée pour qu'on ne cherche pas ici ce
   qui n'y est pas :

   — Il ne corrige pas **le débordement des tableaux**, qui est le seul vrai
     débordement horizontal du Studio. Mesuré deux fois, sur deux écrans et à
     deux largeurs : 871 px dans une carte de 337 à 375 px de fenêtre, 676 px
     dans une carte de 350 à 390 px. Il vient du NOMBRE de colonnes et de leur
     contenu, pas d'une largeur en dur : neutraliser tous les plafonds ne
     change ces deux chiffres d'aucun pixel, c'est vérifié. Il se règle au
     lot 5, par le rang de priorité des colonnes, qui demande de toucher au
     balisage. Le prétendre ici serait mentir sur ce qui est livré.

   — Il ne touche pas aux **22 listes déroulantes à largeur en dur** de
     `index.html`. Écrites en `style=` en ligne, elles l'emportent sur cette
     feuille : aucune règle d'ici ne peut les reprendre sans un `!important`
     par déclaration. Elles partent au lot 2, avec la barre de filtres qui les
     porte, pour ne pas être écrites deux fois.

   — Il ne transforme pas les lignes de tableau en cartes sur téléphone. La
     maquette du 25/08 au matin le montrait, mais elle s'appuyait sur des
     attributs `data-col` que le balisage réel n'a pas. Cela appartient au
     lot 5, avec le classement des colonnes qui le rend possible.

   DEUX seuils, pas trois, contrairement au tableau du plan : le troisième
   (« tableaux réduits aux colonnes essentielles ») décrit un travail du lot 5,
   pas une règle de feuille de style. Deux seuils qu'on applique valent mieux
   que trois dont un est décoratif.

   Ce que ce bloc fait, en un mot : rendre gouvernable ce qui, dans cette
   feuille, refusait de rétrécir, et poser les seuils que le reste du chantier
   utilisera. Voir aussi, plus haut, les trois corrections de valeur qui vont
   avec (`.row>div`, `.tronque`, `.col-ao`), qui ne sont pas dans une requête
   média parce qu'elles doivent valoir à toutes les largeurs.

   Les compteurs collants restent en `flex-wrap`, et ne passent PAS en bande
   qui défile horizontalement comme le montrait la maquette du matin : à cette
   largeur ils ne sont pas encore cliquables, donc un compteur sorti du champ
   serait purement perdu, et la garde existante `body.colle-reduit` traite déjà
   le vrai risque, celui d'une pile collante qui mange l'écran, en les rendant
   au défilement de la page au-delà de 40 % de hauteur de fenêtre. La bande qui
   défile redeviendra le bon motif au lot 2, quand ils seront des filtres. */
/* `:not(.colle-compteurs)` sur les deux seuils, et ce n'est pas une précaution
   théorique : la mesure avant/après a montré que sans elle, ces règles, situées
   en fin de fichier, l'emportaient sur le rembourrage compact de
   `.colle-compteurs` (`.5rem 1rem`) et rendaient la bande de compteurs plus
   épaisse à l'étroit qu'au large. Or cette compacité est un arbitrage d'Alex du
   22/08, et c'est précisément à l'étroit qu'elle compte le plus, la pile
   collante ayant là son coût maximal. */
/* ── Largeurs de confort des contrôles de filtre, lot 9 (28/08/2026) ─────────
   Ces dix-sept valeurs vivaient en `style=` dans index.html. Le problème n'a
   jamais été leur valeur : posées en ligne, elles l'emportent sur toute règle
   de la feuille, donc **aucun point d'arrêt ne pouvait reprendre la main sur
   ces contrôles** sans un `!important` par déclaration. C'est ce que le plan
   avait relevé (D4) et attribué au lot 2, qui ne l'a pas fait.

   Les valeurs sont reprises à l'identique : au large, rien ne bouge. Ce qui
   change est qu'en dessous de 640 px la règle plus bas peut enfin les annuler
   et rendre les contrôles pleine largeur dans la barre dépliée. */
#candidatures-etat,#candidatures-comm,#contrats-resp,#surveys-resp,#activites-resp{max-width:220px}
#surveys-filtre,#activites-filtre,#enc-statut{max-width:240px}
#contrats-filtre,#payments-month,#pieces-q{max-width:280px}
#surveys-mois-select{max-width:230px}
#payments-filter{max-width:260px}
#pieces-nature,#pieces-statut{max-width:200px}
#enc-recherche{max-width:320px;flex:1 1 220px}
#enc-depuis{max-width:160px}
/* Articles, lot 1 (11/09/2026) : mêmes raisons que le bloc ci-dessus, posées
   directement ici plutôt qu'en ligne cette fois -- pas de dette à reprendre. */
#article-archive-action{max-width:280px}
#article-archive-redirect-to{max-width:220px}
#article-archive-redirect-status{max-width:130px}

/* `insecable` remplace les `style="white-space:nowrap"` posés en ligne sur les
   cellules de Pièces reçues par mail (lot 9). Même motif que les largeurs
   ci-dessus : la valeur était juste, c'est son emplacement qui empêchait tout
   point d'arrêt de la reprendre. */
td.insecable{white-space:nowrap}

/* ── Pièces reçues par mail : couper les mots d'un seul tenant, lot 9 ────────
   Défaut ANTÉRIEUR, trouvé en mesurant le rendu réel de l'écran et non un
   tableau reconstruit : ce tableau débordait de **225 px dans une carte de
   1 200**, en plein bureau, et de 369 px à 375 px. Les bancs précédents ne le
   voyaient pas parce qu'ils remplissaient la colonne « Fichier » d'un texte
   court, quand la vraie porte des noms comme
   `URSSAF_attestation_de_vigilance_fevrier_2026.pdf`.

   `white-space:normal` ne suffit pas : un nom de fichier ou une adresse de
   courriel n'a aucune espace, donc rien à quoi le navigateur puisse casser la
   ligne, et la cellule impose sa largeur au tableau entier. `anywhere` l'y
   autorise, et seulement quand c'est nécessaire.

   Coupé plutôt que tronqué aux points de suspension : c'est sur le nom du
   fichier qu'on décide d'accepter ou d'écarter la pièce, le cacher derrière
   une infobulle déplacerait le travail dans le survol. */
#suivi-recus-table td{overflow-wrap:anywhere}

/* Le bouton « Filtres » n'existe que sur téléphone : au-dessus de 640 px la
   barre est toujours dépliée et il n'a rien à commander. Posé AVANT le bloc
   média qui le montre, sinon, à spécificité égale, c'est la dernière règle
   écrite qui gagne et le bouton disparaîtrait justement là où il sert. */
.filtres-bascule{display:none}

@media (max-width:1023px){
  main{padding:1.1rem .9rem 3rem}
  .card:not(.colle-compteurs){padding:1rem 1.05rem}
}
@media (max-width:639px){
  :root{--gouttiere:.7rem}
  main{padding:.85rem .7rem 2.5rem}
  .top{padding:.6rem .8rem}
  /* Eight rubric buttons since « Prestations » joined them (17/09/2026,
     roadmap/prestations-vue-par-freelance.md): one row no longer fits a
     phone, and the bar scrolled sideways. They wrap instead; each menu stays
     anchored under its own button (.nav-rubrique is position:relative). */
  .nav-rubriques{flex-wrap:wrap;row-gap:.35rem}
  .card:not(.colle-compteurs){padding:.85rem .8rem;border-radius:9px}
  .colle-compteurs{padding:.45rem .8rem;column-gap:1rem}
  .accueil-cartes{grid-template-columns:1fr}
  /* Le champ de recherche perd sa taille « confortable » de 1.02rem, inutile à
     l'étroit, mais reste à 16 px AU MOINS : en dessous de 16 px, iOS zoome de
     lui-même à la prise de focus et laisse l'utilisateur dans une page agrandie
     qu'il doit dézoomer à la main. C'est la seule raison de ce 1rem, et elle
     n'est pas esthétique. */
  .recherche input[type=search]{font-size:1rem;padding:.7rem .8rem .7rem 2.5rem}
  /* L'indentation de 4.2rem alignait ce bloc sous une étiquette de 3.6rem qui,
     à cette largeur, est déjà passée à la ligne : l'alignement ne rime plus à
     rien et coûtait un quart de la largeur utile. */
  .pa-derogation{margin-left:0}
  .pa-derogation input[type=text]{min-width:0}
  /* Une fenêtre de 92vw centrée laisse deux marges qui ne font que rétrécir un
     contenu déjà à l'étroit. Sur téléphone elle prend l'écran, ce qui est aussi
     ce que fait n'importe quelle application native. */
  /* Les fenêtres prennent l'écran sur téléphone. `.modal-fenetre` a remplacé
     `.modal-overlay` au lot 6 ; la boîte intérieure, elle, n'a pas changé. */
  dialog.modal-fenetre{width:100vw;max-height:100vh}
  .modal-box{max-height:100vh;border-radius:0}
  dialog.app-dialog{width:100vw;max-width:100vw;max-height:100vh;border-radius:0}
  dialog.app-dialog .modal-body{max-height:none}

  /* ── La barre de filtres se replie derrière son bouton, lot 9 ──────────────
     Repliée, la barre n'est plus qu'une ligne : la pile figée en haut de
     l'écran retombe d'environ 120 px, mesurés à 375 px de large.

     Écrit en `:not(.filtres-ouverts)` plutôt qu'en couple montrer/cacher :
     dépliée, AUCUNE règle ne s'applique et chaque contrôle retrouve
     exactement l'affichage qu'il a au large. Un `display:block` de
     réouverture aurait cassé les conteneurs en flex de certaines barres. */
  /* Et les compteurs cessent d'être figés, à cette largeur et sans condition.
     Mesuré : sans cette règle, replier la barre de filtres ne gagne RIEN.
     La garde des 40 % (updateStickyStack) sacrifiait déjà les compteurs sur
     les écrans lourds ; une fois la barre repliée, la pile brute repasse sous
     le seuil, la garde se désarme et rend les compteurs collants. Créances
     échangeait alors 139 px de filtres contre 125 px de compteurs et montait
     de 296 à 312 px. La priorité du 22/08 ne change pas pour autant : ce qui
     sert à naviguer reste en place, ce qui se lit une fois défile. */
  .colle-compteurs{position:static;box-shadow:none;border-bottom:0}
  /* `!important` assumé, et un seul dans toute la feuille : plusieurs barres
     rangent leurs contrôles dans un `<div style="display:flex">`, et une
     déclaration en ligne l'emporte sur toute règle. Mesuré : sans lui, la
     barre repliée de Contrats du Talent restait à 182 px au lieu de 43, le
     bouton s'affichant AU-DESSUS d'un bloc qui ne se repliait pas. C'est le
     cas que le plan décrit (D4) : une valeur posée en ligne ne se reprend
     qu'ainsi. La règle ne s'applique qu'en dessous de 640 px et seulement
     barre fermée : au large et une fois ouverte, elle n'existe pas. */
  .colle-filtres:not(.filtres-ouverts) > :not(.filtres-bascule){display:none!important}
  .filtres-bascule{display:inline-flex;align-items:center;gap:.35rem}
  /* Dix-sept barres sur vingt sont aussi des `.card` : repliées, leur marge
     intérieure de carte pesait plus que le bouton lui-même (60 px de barre
     pour 30 px de bouton). Repliée, la barre est une barre d'outils, pas une
     carte ; dépliée, elle retrouve sa marge. */
  .card.colle-filtres:not(.filtres-ouverts){padding:.4rem .8rem}

  /* ── Les trois tableaux qui débordaient encore, traités un par un ──────────
     Arbitrage d'Alex du 28/08 : au cas par cas, pas de refonte en cartes.

     1. La marge intérieure des cellules passe de .4 à .28rem : à sept colonnes,
        cela rend une vingtaine de pixels, ce qui suffit à Encaissements (18 px
        de trop) et au tableau d'adéquation de Studio CV (9 px). Mesuré, pas
        estimé. */
  table.list th,table.list td{padding-left:.22rem;padding-right:.22rem}
  /*  2. Pièces reçues par mail débordait de 216 px, et par une seule cause :
        trois colonnes insécables, dont une adresse de courriel et une raison
        sociale. Elles ont le droit de se replier ici. Aucune colonne n'est
        masquée : ce tableau n'a pas de ligne dépliée où ranger ce qu'on lui
        retirerait, contrairement aux quinze autres. */
  #suivi-recus-table td.insecable{white-space:normal}
  /*  3. Encaissements : 30 px de trop, portés par le bouton « Confirmer » de
        la colonne d'actions (92 px à lui seul, mesuré sur le rendu réel).
        Cette colonne ne peut pas être masquée, c'est même la raison d'être de
        son épinglage à droite : on la resserre. Les mots restent en toutes
        lettres, seule la marge du bouton cède. */
  td.actions .btn-sm{padding-left:.45rem;padding-right:.45rem}
  /*     Et le plancher de 6,5 rem de la colonne Montant, posé pour aligner les
         montants au large, dépasse ici ce que le montant lui-même réclame :
         il imposait 104 px pour un texte qui en mesure 86. */
  table#enc-table th.col-montant,table#enc-table td.col-montant{min-width:5.4rem}
  .enc-chevron{margin-right:.2rem}
  /* Un filtre posé sur une barre repliée ferait lire une liste tronquée comme
     si elle était complète : le bouton le dit, et le dit en couleur. */
  .filtres-bascule-active{border-color:var(--teal);color:var(--teal);font-weight:600}
  /* Dépliés, les contrôles prennent la largeur : c'est ce que les dix-sept
     `style="max-width:…"` sortis d'index.html au même lot rendent possible,
     eux qu'aucun point d'arrêt ne pouvait reprendre. */
  .colle-filtres.filtres-ouverts > *{max-width:100%;flex-basis:100%}
}

/* ── Dépliage de ligne, mécanisme partagé (lot 5b, 27/08/2026) ──────────────
   Le motif existait depuis le 24/08, mais recopié : Encaissements, Contrats et
   Documents obligatoires portaient chacun leurs propres classes pour faire la
   même chose. Le lot 5b l'étend à cinq écrans de plus, et recopier une
   septième fois n'était pas tenable.

   Ce bloc nomme le motif une bonne fois : `tr[data-depli]` est une ligne qui
   s'ouvre, `.ligne-detail` est le panneau qui la suit. Les trois écrans
   d'origine gardent leurs classes pour l'instant : ils fonctionnent, et les
   migrer aurait mêlé une réécriture sans effet visible à un lot qui en a
   déjà beaucoup. C'est une dette assumée, écrite ici pour être vue.

   Le panneau est une SECONDE `<tr>` dont l'unique `<td>` porte un `colspan`,
   et non un bloc posé dans la ligne : une cellule de tableau qui cesse d'être
   une cellule casse l'alignement des colonnes et le filet de séparation, comme
   l'a montré le défaut du 25/08 (lot 0 bis). Ce `colspan` doit valoir le
   nombre de colonnes du tableau, ce que garde
   test_the_expanded_detail_row_spans_every_column_of_its_table. */
tr[data-depli]{cursor:pointer}
.chevron{display:inline-block;margin-right:.4rem;color:var(--sub);transition:transform .12s}
tr.ligne-ouverte .chevron{transform:rotate(90deg)}
tr.ligne-detail{background:#fbfaf7;cursor:default}
tr.ligne-detail td{padding:.8rem .6rem}
tr.ligne-detail:hover{background:#fbfaf7}
/* Le panneau lui-même : deux colonnes au large, une seule à l'étroit. Les
   paires « intitulé / valeur » sont un <dl>, parce que c'en est un. */
.detail-panneau{display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:.7rem 2rem}
.detail-bloc dt{font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;color:var(--sub);margin-top:.5rem}
.detail-bloc dt:first-child{margin-top:0}
.detail-bloc dd{font-size:.87rem;margin:.1rem 0 0}
/* Articles, lot 1 : le rendu HTML d'un article pour un compte qui n'a que la
   lecture (rendered_html vient déjà assaini du site public, voir
   renderArticleReadOnly dans app.js) -- pas de vendor/quill-1.3.7.snow.css
   ici, cette feuille habille la chrome de l'éditeur (.ql-*), jamais du HTML
   affiché en dehors de lui. */
.quill-lecture{font-size:.9rem;line-height:1.6;margin-top:.5rem}
.quill-lecture h2,.quill-lecture h3,.quill-lecture h4{margin:1rem 0 .4rem}
.quill-lecture p{margin:0 0 .7rem}
.quill-lecture ul,.quill-lecture ol{margin:0 0 .7rem;padding-left:1.4rem}
/* Articles, lot 1 second pass (11/09/2026) : révisions, diff et rédaction
   assistée, ported from the vitrine's own static/administration/css/admin.css
   (.rev/.rev-date/.rev-meta/.diff/.revision-preview there). That stylesheet
   is a DARK theme (--bg:#1A1A18) and its .diff colours (light mint text on
   a translucent green) would be near-illegible on the Studio's light one --
   reworked here onto tokens this file already defines and measured for
   contrast (--ok/--ko-texte, the same pair .badge-ok/.badge-ko already use)
   rather than copied verbatim. .added/.removed also style the legend's own
   spans (article-revision-modal-body's "texte de cette version"/"texte
   actuel"), which the vitrine's own nesting under .diff alone does not. */
.rev{display:flex;justify-content:space-between;align-items:center;gap:.6rem;padding:.5rem 0;border-bottom:1px solid var(--line)}
.rev:last-child{border-bottom:none}
.rev-date{font-size:.8rem}
.rev-meta{font-size:.72rem;color:var(--sub);margin-top:.1rem;overflow-wrap:anywhere}
.rev.actuelle .rev-date{color:var(--teal);font-weight:600}
.rev-actions{display:flex;gap:.25rem;flex-shrink:0}
.diff{font-size:.9rem;line-height:1.7}
.diff p{margin:0 0 .8rem;white-space:pre-wrap;overflow-wrap:anywhere}
.diff .added,.diff-legend .added{background:#e7f3f0;color:var(--ok);border-radius:3px}
.diff .removed,.diff-legend .removed{background:#fbeae3;color:var(--ko-texte);text-decoration:line-through;border-radius:3px}
.diff .line-added{border-left:2px solid var(--ok);padding-left:.7rem}
.diff .line-removed{border-left:2px solid var(--ko-texte);padding-left:.7rem}
.diff-legend{font-size:.75rem;color:var(--sub);margin-bottom:1.1rem;display:flex;gap:1.25rem;flex-wrap:wrap}
.revision-preview{font-size:.9rem;line-height:1.7}
.revision-preview h1,.revision-preview h2,.revision-preview h3{line-height:1.35;margin:1.4rem 0 .5rem}
.revision-preview h1{font-size:1.35rem}
.revision-preview h2{font-size:1.15rem}
.revision-preview h3{font-size:1rem}
.revision-preview p{margin:0 0 .8rem}
.revision-preview img{max-width:100%;height:auto;border-radius:8px}
.revision-preview blockquote{border-left:2px solid var(--teal);padding-left:.8rem;margin:0 0 .8rem;color:var(--sub)}
/* Médiathèque : liste (view-medias) et sélecteur d'image de couverture
   (#article-media-picker-grid) partagent renderMediaGrid() dans app.js,
   donc cette même feuille de classes -- ported from the vitrine's own
   media.js/admin.css, --bg-soft/--text-mute/--text-sub replaced by tokens
   this file already has (#fbfaf7 is the same soft background .cv-block and
   .how-it-works dl already use). No .dropzone here: the Studio has no
   drag-and-drop convention anywhere else, a plain labelled <input
   type="file"> matches its other file inputs (e.g. #studio-gen-cv-fichier). */
.media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:.75rem;margin-top:.5rem}
.media-item{border:1px solid var(--line);border-radius:8px;overflow:hidden;position:relative;background:#fff}
.media-item:hover{border-color:var(--teal)}
.media-item[data-media-pick]{cursor:pointer}
.media-thumb{width:100%;height:120px;object-fit:cover;display:block}
.media-thumb-placeholder{width:100%;height:120px;display:flex;align-items:center;justify-content:center;background:#fbfaf7;color:var(--sub);font-size:.7rem}
.media-name{padding:.4rem .5rem;font-size:.7rem;color:var(--sub);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.media-usage{font-size:.65rem;color:var(--sub);padding:0 .5rem .25rem;text-align:center}
.media-delete{padding:0 .5rem .4rem;text-align:center}
.detail-vide{color:var(--sub)}

/* ── Rangs de priorité par colonne, lot 5a.1 (26/08/2026) ───────────────────
   Le troisième seuil que le bloc précédent annonçait sans l'écrire : les
   tableaux réduits à leurs colonnes essentielles. C'est le seul vrai
   débordement horizontal du Studio, mesuré et non déduit : 871 px de tableau
   dans une carte de 337 sur Documents obligatoires à 375 px, 1 007 px dans une
   carte de 910 à 970 px. Ni la barre de filtres ni les plafonds de largeur n'y
   sont pour quoi que ce soit, les deux relevés des 25 et 26/08 le disent.

   Chaque colonne porte un rang, déclaré UNE fois, sur son `<th>` dans
   index.html :

     rang 1  toujours visible          (pas de classe)
     rang 2  masquée sous 640 px       (class="rang-2")
     rang 3  masquée sous 1024 px      (class="rang-3")

   Les règles ci-dessous sont la traduction mécanique de ces classes en
   `nth-child`, parce qu'aucun sélecteur CSS ne sait atteindre une cellule à
   partir de la classe de son en-tête. C'est la seule duplication du
   dispositif, et `test_les_rangs_de_colonnes_disent_la_meme_chose_des_deux_cotes`
   la rend impossible à laisser diverger : ajouter une colonne au milieu d'un
   tableau sans reprendre ces index rend le test rouge.

   Deux invariants que ce test garde aussi :

   1. **La première colonne n'est jamais rangée au-delà de 1.** Les lignes
      d'état vide (« Aucun CV ne correspond ») et les lignes de dépliage sont un
      unique `<td colspan=…>`, donc `nth-child(1)` : la masquer masquerait le
      message ou le panneau entier.
   2. **La colonne d'actions reste rang 1**, sur les six tableaux qui en ont.
      Elle est déjà épinglée à droite (`td.actions`, plus haut) précisément pour
      qu'un débordement ne la coupe pas.

   Ce que ce bloc ne fait pas : la « carte du téléphone » du plan, où le tableau
   se replie en fiche sous 640 px. Masquer une colonne n'est sans perte que là
   où un dépliage de ligne la rattrape, et le dépliage généralisé est au lot 5b.
   Les rangs sont donc choisis pour que rien d'indispensable ne disparaisse, et
   `.colle-defile-h` reste le filet en dessous.

   Colonnes masquées, par écran :

     Studio CV          3: Version, Format             2: Créé le
     Post LinkedIn      3: Version                     2: Date
     Candidatures       3: Communauté, Dispo, Prov.    2: Profil, TJM, Reçue
     Vivier             3: Positionnement, Outils      2: Ville, Note
     Reprise de contact 3: Planifiés, Ouverts, Aband.  2: Envoyés, Fiches, Erreurs
     À rappeler         —                              2: Email, Corrigée le
     Pièces reçues      3: Pourquoi elle est là        2: Type, Date du document
     Documents oblig.   3: Société                     2: Kbis, RC Pro, Vigilance
     Contrats du Talent 3: Pièce, Commande jusqu'au    2: Couvert jusqu'au, À faire
     Commandes          3: Projet                      2: Fin de commande, Situation
     Satisfaction       3: Responsable                 2: Évolution, Alertes
     Demandes de contact 3: Slack, Agenda               2: Courriel
     CRA et frais       3: Responsable, M-2, Relance   2: M-1, Alertes
     Paiement Talents   3: Société, Jours, Marge, RIB  2: CRA, Fact. Talent, Fact. Client
     Utilisateurs       3: Email                       2: Statut
     Encaissements      3: Compte, Libellé, Boond      2: Rapprochement
     Pièces comptables  3: Date                        2: Client ou fourn., Montant TTC
     Articles           3: Catégorie, Auteur           2: Date
     Dépôts Fair4Share  3: Rôles demandés              2: Déposé le
     Connexion à Claude 3: Titulaire, Expire le        2: Début de la clé, Créé le, Dernier usage
     Créances, Factures échues (une fois par société)  3: État Boond, Note   2: Date, Échéance

   Les deux petits tableaux de l'éditeur de Studio CV (Adéquation,
   Disponibilité) n'ont pas de rangs : ils font quelques lignes, ils se
   remplissent à la main, et ils n'ont jamais débordé. */
@media (max-width:1023px){
  #studio-table th:nth-child(4),#studio-table td:nth-child(4),#studio-table th:nth-child(5),#studio-table td:nth-child(5){display:none}
  #marketing-table th:nth-child(2),#marketing-table td:nth-child(2){display:none}
  #candidatures-table th:nth-child(3),#candidatures-table td:nth-child(3),#candidatures-table th:nth-child(4),#candidatures-table td:nth-child(4){display:none}
  #matching-table th:nth-child(5),#matching-table td:nth-child(5),#matching-table th:nth-child(6),#matching-table td:nth-child(6){display:none}
  #campaigns-segments-table th:nth-child(2),#campaigns-segments-table td:nth-child(2),#campaigns-segments-table th:nth-child(4),#campaigns-segments-table td:nth-child(4),#campaigns-segments-table th:nth-child(7),#campaigns-segments-table td:nth-child(7){display:none}
  #suivi-recus-table th:nth-child(4),#suivi-recus-table td:nth-child(4){display:none}
  #suivi-table th:nth-child(2),#suivi-table td:nth-child(2){display:none}
  #contrats-table th:nth-child(3),#contrats-table td:nth-child(3),#contrats-table th:nth-child(6),#contrats-table td:nth-child(6){display:none}
  #commandes-table th:nth-child(3),#commandes-table td:nth-child(3){display:none}
  #surveys-table th:nth-child(2),#surveys-table td:nth-child(2){display:none}
  #activites-table th:nth-child(2),#activites-table td:nth-child(2),#activites-table th:nth-child(6),#activites-table td:nth-child(6){display:none}
  #payments-table th:nth-child(5),#payments-table td:nth-child(5),#payments-table th:nth-child(6),#payments-table td:nth-child(6){display:none}
  #users-table th:nth-child(2),#users-table td:nth-child(2){display:none}
  #enc-table th:nth-child(4),#enc-table td:nth-child(4),#enc-table th:nth-child(6),#enc-table td:nth-child(6){display:none}
  #pieces-table th:nth-child(4),#pieces-table td:nth-child(4){display:none}
  #articles-table th:nth-child(2),#articles-table td:nth-child(2),#articles-table th:nth-child(5),#articles-table td:nth-child(5){display:none}
  #relay-table th:nth-child(5),#relay-table td:nth-child(5){display:none}
  #mcp-table th:nth-child(4),#mcp-table td:nth-child(4),#mcp-table th:nth-child(7),#mcp-table td:nth-child(7){display:none}
  /* Créances, Factures échues (lot 8, 24/09/2026) : sans id, une classe à la
     place (un tableau par société, voir renderCreancesFacture dans app.js). */
  .creances-facture-table th:nth-child(7),.creances-facture-table td:nth-child(7){display:none}
  .creances-facture-table th:nth-child(9),.creances-facture-table td:nth-child(9){display:none}
  /* Demandes de contact (lot 9, 24/09/2026) : Slack, Agenda. */
  #demandesContact-table th:nth-child(4),#demandesContact-table td:nth-child(4),#demandesContact-table th:nth-child(5),#demandesContact-table td:nth-child(5){display:none}
}
@media (max-width:639px){
  #studio-table th:nth-child(6),#studio-table td:nth-child(6){display:none}
  #marketing-table th:nth-child(3),#marketing-table td:nth-child(3){display:none}
  #candidatures-table th:nth-child(2),#candidatures-table td:nth-child(2),#candidatures-table th:nth-child(5),#candidatures-table td:nth-child(5){display:none}
  #matching-table th:nth-child(3),#matching-table td:nth-child(3),#matching-table th:nth-child(4),#matching-table td:nth-child(4){display:none}
  #campaigns-segments-table th:nth-child(3),#campaigns-segments-table td:nth-child(3),#campaigns-segments-table th:nth-child(5),#campaigns-segments-table td:nth-child(5),#campaigns-segments-table th:nth-child(8),#campaigns-segments-table td:nth-child(8){display:none}
  #campaigns-callback-table th:nth-child(2),#campaigns-callback-table td:nth-child(2),#campaigns-callback-table th:nth-child(3),#campaigns-callback-table td:nth-child(3){display:none}
  #suivi-recus-table th:nth-child(5),#suivi-recus-table td:nth-child(5),#suivi-recus-table th:nth-child(6),#suivi-recus-table td:nth-child(6){display:none}
  #suivi-table th:nth-child(3),#suivi-table td:nth-child(3),#suivi-table th:nth-child(4),#suivi-table td:nth-child(4),#suivi-table th:nth-child(5),#suivi-table td:nth-child(5){display:none}
  #contrats-table th:nth-child(5),#contrats-table td:nth-child(5),#contrats-table th:nth-child(7),#contrats-table td:nth-child(7){display:none}
  #commandes-table th:nth-child(4),#commandes-table td:nth-child(4),#commandes-table th:nth-child(6),#commandes-table td:nth-child(6){display:none}
  #surveys-table th:nth-child(4),#surveys-table td:nth-child(4),#surveys-table th:nth-child(5),#surveys-table td:nth-child(5){display:none}
  #activites-table th:nth-child(3),#activites-table td:nth-child(3),#activites-table th:nth-child(5),#activites-table td:nth-child(5){display:none}
  #payments-table th:nth-child(2),#payments-table td:nth-child(2),#payments-table th:nth-child(3),#payments-table td:nth-child(3),#payments-table th:nth-child(4),#payments-table td:nth-child(4){display:none}
  #users-table th:nth-child(3),#users-table td:nth-child(3){display:none}
  #enc-table th:nth-child(5),#enc-table td:nth-child(5){display:none}
  #pieces-table th:nth-child(3),#pieces-table td:nth-child(3),#pieces-table th:nth-child(5),#pieces-table td:nth-child(5){display:none}
  #articles-table th:nth-child(4),#articles-table td:nth-child(4){display:none}
  #relay-table th:nth-child(3),#relay-table td:nth-child(3){display:none}
  #repartitions-roles-table th:nth-child(3),#repartitions-roles-table td:nth-child(3){display:none}
  #mcp-table th:nth-child(3),#mcp-table td:nth-child(3),#mcp-table th:nth-child(5),#mcp-table td:nth-child(5),#mcp-table th:nth-child(6),#mcp-table td:nth-child(6){display:none}
  .creances-facture-table th:nth-child(3),.creances-facture-table td:nth-child(3),.creances-facture-table th:nth-child(4),.creances-facture-table td:nth-child(4){display:none}
  #demandesContact-table th:nth-child(3),#demandesContact-table td:nth-child(3){display:none}
  /* ── Ce qui reste rigide à 375 px, mesuré plutôt que supposé (lot 5b) ──────
     Réduire le nombre de colonnes ne suffisait pas : sur Pièces comptables,
     les quatre colonnes survivantes réclamaient encore 618 px dans une carte
     de 337. Trois largeurs FIXES en étaient responsables, et aucune ne cède
     d'elle-même :

       Référence  205 px   le plafond de 12 rem posé sur l'en-tête le 26/08
       Statut     163 px   la pastille plus sa sous-ligne, toutes deux insécables
       Actions    164 px   « Valider » et « Écarter » côte à côte, en nowrap

     Les trois règles ci-dessous les assouplissent à l'étroit seulement. Au
     large, rien ne change : les valeurs du 26/08 tiennent toujours, et c'est
     elles qui gardent l'en-tête de colonnes figé. */
  table.list th.col-reference{width:auto}
  table.list td.col-reference a{max-width:9rem}
  /* Encaissements garde un débordement à 375 px, et il est STRUCTUREL : ses
     quatre colonnes de rang 1 (date, payeur, montant, action) mesurent chacune
     environ 100 px de contenu insécable, dans une carte qui en fait 351.
     Essayé et mesuré sans effet : abaisser le plancher du montant à 5 rem et le
     plafond du payeur à 8 rem, les deux étant déjà plus larges que ce que le
     contenu réclame. Aucun réglage de largeur ne ferme ces 59 px ; la réponse
     est la carte du téléphone, où la ligne cesse d'être une ligne. Le
     défilement horizontal (`.colle-defile-h`) couvre ce cas en attendant. */
  /* Les sous-lignes disparaissent : elles portent le complément (nature,
     indice Qonto, date d'envoi), et le complément vit désormais dans le
     panneau dépliable, à un clic. Les garder ici coûterait la largeur qu'on
     vient de récupérer, pour une information redondante. */
  table.list td .sous-ligne{display:none}
  /* Les boutons d'action s'empilent au lieu de s'aligner : la colonne passe
     d'environ 164 px de large à environ 85, contre deux lignes de haut. Sur un
     téléphone la hauteur est gratuite, la largeur ne l'est pas. */
  td.actions{white-space:normal}
  td.actions > * + *{margin-left:0}
}

/* ── Matching : bloc mission et cartes de résultat (roadmap
   matching-profils-missions.md, lot 1 §7, 06/09/2026) ────────────────────────
   Reuses .card, .btn, .badge-etat/.badge-ok/.badge-ko/.badge-info/.badge-warn,
   .muted, .status and .spinner throughout; only what none of those already
   cover is added here. */
.qualite-talent{margin-top:.9rem}
.qualite-talent .coches{display:flex;gap:.5rem 1.2rem;flex-wrap:wrap;margin:.3rem 0}
.qualite-talent .coche{display:inline-flex;align-items:center;gap:.35rem;font-size:.92rem;color:var(--ink)}
.qualite-talent .coche input{width:auto;margin:0}

/* Mission block (lot 2, roadmap §7): selector + client notes on the left,
   besoin on the right, as the validated mockup shows it. */
.matching-ligne-mission{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
@media (max-width:640px){.matching-ligne-mission{grid-template-columns:1fr}}

/* Results title, above the cards (lot 2, roadmap §7): names the mission as
   soon as it is known, so a screenshot reads on its own -- see
   matchingResultatsTitreTexte() in app.js. */
.resultats-titre{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;flex-wrap:wrap;margin:1.4rem 0 .6rem}
.resultats-titre h2{font-size:1.05rem;margin:0}

/* Mode-dégradé banner (roadmap §7, point 4: "jamais d'information fausse, le
   mode dégradé se voit") -- also usable anywhere else a warning needs to sit
   above a result list, not just Matching. */
.bandeau{background:var(--warn-fond);border:1px solid #e6cf8a;color:var(--warn-texte);padding:.6rem .8rem;border-radius:6px;font-size:.9rem;margin:.8rem 0 0}

.matching-grille{display:flex;flex-direction:column;gap:.9rem}
.matching-carte{display:grid;grid-template-columns:1fr auto;gap:.5rem 1rem}
.matching-carte h3{margin:0;font-size:1.05rem;font-weight:700}
.matching-carte .sous{color:var(--sub);font-size:.9rem;font-weight:400}
.matching-badges{display:flex;gap:.4rem;flex-wrap:wrap;margin-top:.35rem;grid-column:1/-1}
.matching-score{font-size:1.45rem;font-weight:700;color:var(--teal);white-space:nowrap;text-align:right;font-variant-numeric:tabular-nums}
.matching-score small{display:block;font-size:.7rem;color:var(--sub);font-weight:400}
.matching-attente,.matching-nonretenu,.matching-erreur-score{color:var(--sub);font-style:italic;font-size:.85rem;text-align:right;white-space:nowrap}
/* Analysis progress bar (roadmap lot 3, matching-rappel-verite-terrain.md,
   14/09/2026), under the status line: reuses .bandeau's own token colours,
   no new ones added. */
.matching-progress-piste{height:4px;background:var(--line);border-radius:2px;overflow:hidden;margin:.6rem 0 0}
.matching-progress-barre{height:100%;background:var(--teal);transition:width .3s ease}
.matching-pourquoi{grid-column:1/-1;margin:.2rem 0 0;font-size:.93rem}
.matching-retenu{grid-column:1/-1;font-size:.83rem;color:var(--sub);margin:0}
.matching-retenu b{color:var(--ink);font-weight:600}
/* Which pass(es) surfaced this profile, and at what rank (lot 2,
   roadmap/matching-rappel-verite-terrain.md, 10/09/2026): same size and
   colour as .matching-retenu above, since together the two lines are the
   card's only explanation for its own position in the list. */
.matching-verifier-titre{grid-column:1/-1;font-size:.86rem;color:var(--warn-texte);margin:.2rem 0 0;font-weight:600}
.matching-verifier{grid-column:1/-1;font-size:.88rem;color:var(--warn-texte);margin:0;padding-left:1.1rem}
.matching-verifier li{margin:.1rem 0}
/* "Détails" fold (roadmap §5.3, lot 9): wraps "Retenu sur" and "À vérifier
   avec la personne" -- open by default on a card's first 5, see
   matchingCarteHtml()'s own comment. Same pattern as .matching-plus below,
   a separate class since the two folds hold different content and can both
   be open on the same card at once. */
.matching-details{grid-column:1/-1;font-size:.85rem;color:var(--sub)}
.matching-details summary{cursor:pointer;color:var(--teal)}
.matching-contact{grid-column:1/-1;display:flex;gap:.5rem 1.4rem;flex-wrap:wrap;font-size:.9rem;margin-top:.3rem;padding-top:.6rem;border-top:1px solid var(--line);align-items:center}
.matching-contact a{color:var(--teal);text-decoration:none}
/* Boond's TJM (read) and the evoked one (this screen's only write, local,
   see PAGES.matching.ecrit), on the same contact line. */
.matching-tjm{display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap}
.matching-tjm b{font-variant-numeric:tabular-nums}
.matching-tjm input{width:5.2rem;padding:.2rem .4rem;font-size:.9rem}
/* Languages, diplomas, mobility and work arrangements: a fold, as the mockup. */
.matching-plus{grid-column:1/-1;font-size:.85rem;color:var(--sub)}
.matching-plus summary{cursor:pointer;color:var(--teal)}
.matching-plus dl{display:grid;grid-template-columns:auto 1fr;gap:.2rem 1rem;margin:.4rem 0 0}
.matching-plus dt{color:var(--sub)}
.matching-plus dd{margin:0}
.matching-pied{grid-column:1/-1;display:flex;gap:.6rem;flex-wrap:wrap;justify-content:flex-end;margin-top:.2rem}

/* Lot B (roadmap/matching-rappel-verite-terrain.md, "Proposition 2 d'Alex",
   17/09/2026, accord d'Alex) : "Sur des besoins proches", au-dessus des
   cartes de résultat -- mêmes jetons que .matching-plus ci-dessus, un fold
   plutôt qu'une carte à part. */
.matching-proches{font-size:.85rem;color:var(--sub);margin:.8rem 0 1rem}
.matching-proches summary{cursor:pointer;color:var(--teal)}
.matching-proche-ao{padding:.5rem 0;border-top:1px solid var(--line)}
.matching-proche-ao:first-child{border-top:none}
.matching-proche-ao p{margin:.15rem 0}
.matching-proche-personnes{display:flex;flex-wrap:wrap;gap:.2rem .8rem}
.matching-proche-personne{display:inline-flex;align-items:center;gap:.3rem;white-space:nowrap}

/* Lot I (roadmap/plateforme-freelance-troisieme-voie.md §5septies,
   22/09/2026, arbitrage 4 d'Alex) : le détail de la mission choisie, sous le
   sélecteur -- les positionnements réutilisent .matching-proche-personnes/
   .matching-proche-personne ci-dessus (même pastille nom + badge d'état),
   seule la ligne d'une candidature espace est propre à ce bloc. */
.matching-candidatures-espace{margin-top:1rem;padding-top:1rem;border-top:1px solid var(--line)}
.matching-candidatures-espace-note{margin-top:.5rem}
.matching-candidature-ligne{display:flex;gap:.6rem;align-items:center;flex-wrap:wrap;padding:.45rem 0;border-top:1px solid var(--line)}
.matching-candidature-ligne:first-child{border-top:none}

@media (max-width:640px){
  .matching-carte{grid-template-columns:1fr}
  .matching-score,.matching-attente,.matching-nonretenu,.matching-erreur-score{text-align:left}
}

/* ── Demandes d'évolution (10/09/2026, roadmap/studio-kanban-demandes-evolution.md,
   lot 2) ───────────────────────────────────────────────────────────────────
   Six fixed columns rather than a table: the plan's own reasoning (section 5)
   is that the columns follow Fair4B's actual decision process, not a scrum
   template, so a status column in a table would hide exactly the shape that
   matters here. Every color and spacing value below is a token already
   declared at the top of this file -- the validated mockup this screen
   reproduces already used these same token names and values, so nothing new
   was invented, only the handful of shapes (round initials, the small
   backlog-reference chip, the delivered card's tinted footer) that had no
   existing equivalent to reuse. */
.demandes-filtre{background:var(--card);border:1px solid var(--line-champ);border-radius:6px;
  padding:.32rem .7rem;font:inherit;font-size:.82rem;color:var(--ink);cursor:pointer}
.demandes-filtre:hover{border-color:var(--teal)}
.demandes-filtre[aria-pressed="true"]{background:var(--teal);border-color:var(--teal);color:#fff;font-weight:500}
.demandes-espace{flex:1}
.demandes-lien-sans-suite{color:var(--teal);font-size:.82rem;text-decoration:none;
  border-bottom:1px solid rgba(31,109,94,.35);white-space:nowrap}
.demandes-lien-sans-suite[aria-current="page"]{font-weight:600;border-bottom-width:2px}

.demandes-board{display:grid;grid-template-columns:repeat(6,minmax(230px,1fr));gap:var(--gouttiere);
  align-items:start;overflow-x:auto;padding-bottom:.4rem}
.demandes-column{background:rgba(0,0,0,.028);border-radius:8px;padding:.55rem .55rem .8rem;min-width:0}
.demandes-column h2{margin:0;padding:.25rem .3rem .6rem;display:flex;align-items:center;gap:.4rem;
  font-size:.76rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--sub)}
.demandes-count{background:var(--card);border:1px solid var(--line);border-radius:999px;
  padding:0 .42rem;font-size:.72rem;font-weight:400;color:var(--ink)}
.demandes-empty{margin:0;padding:.5rem .3rem;font-size:.78rem;color:var(--sub);font-style:italic}
.demandes-column-note{margin:0 0 .5rem;padding:0 .3rem;font-size:.7rem;color:var(--sub);font-style:italic;line-height:1.35}

.demandes-card{background:var(--card);border:1px solid var(--line);border-radius:7px;
  padding:.6rem .65rem;margin-bottom:.5rem;box-shadow:0 1px 2px rgba(0,0,0,.03)}
.demandes-card-head{display:flex;align-items:center;gap:.35rem;margin-bottom:.35rem;flex-wrap:wrap}
.demandes-card-text{margin-bottom:.45rem;font-size:.83rem;line-height:1.42}
.demandes-card-meta{display:flex;align-items:center;gap:.4rem;font-size:.72rem;color:var(--sub)}
.demandes-qui{display:flex;align-items:center;gap:.3rem}
.demandes-pastille{width:17px;height:17px;flex:none;border-radius:99px;background:var(--teal-l);
  color:var(--ink);font-size:.6rem;font-weight:700;display:flex;align-items:center;justify-content:center}
.demandes-ref{font-size:.68rem;color:var(--teal);border:1px solid rgba(31,109,94,.28);border-radius:4px;padding:0 .3rem}
.demandes-age{margin-left:auto;white-space:nowrap}

.demandes-card-footer{margin-top:.45rem;padding-top:.4rem;border-top:1px dashed var(--line)}
.demandes-card-footer .btn{margin-right:.35rem}
.demandes-card-footer.demandes-delivered{margin:.45rem -.65rem -.6rem;padding:.4rem .65rem .6rem;
  font-size:.72rem;color:var(--ok);background:rgba(31,109,94,.07);border-radius:0 0 6px 6px}

.demandes-liste-sans-suite{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr));gap:var(--gouttiere)}
.demandes-retour{margin-bottom:.8rem;font-size:.88rem}

/* Precision exchanges (10/09/2026, "demander plus de précision pour une
   demande d'évol ou anomalie") sit directly under the description, never a
   seventh column -- attendPrecision only ever adds the badge-warn marker in
   the card head and this block, the card itself never changes column for
   it. Closed rounds get the same quiet treatment as .demandes-column-note
   (small, --sub, no border); the one still open borrows the
   --warn-fond/--warn-texte pair .badge-warn already uses elsewhere on this
   screen, so "awaiting a reply" reads as the same idea everywhere it shows
   up here, and stays visibly distinct from .demandes-age's --warn red just
   above in the card: old and "awaiting a reply" are different facts, not
   variants of the same warning. No new color: every value here is a token
   already declared at the top of this file. */
.demandes-precisions{margin:.15rem 0 .45rem;display:flex;flex-direction:column;gap:.35rem}
.demandes-precision{border-radius:6px;padding:.4rem .5rem}
.demandes-precision-close{background:rgba(0,0,0,.028);color:var(--sub)}
.demandes-precision-ouverte{background:var(--warn-fond);border:1px solid #e6cf8a;color:var(--warn-texte)}
.demandes-precision-ouverte .btn{margin-top:.35rem}
.demandes-precision-ligne{margin:0 0 .2rem;font-size:.78rem;line-height:1.4}
.demandes-precision-ligne:last-child{margin-bottom:0}
.demandes-precision-tag{display:block;font-size:.66rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em}
.demandes-precision-tag-reponse{color:var(--teal)}

/* ── Prestations en cours (17/09/2026, Package D of
   roadmap/prestations-vue-par-freelance.md) ─────────────────────────────────
   The screen's own validated mockup
   (roadmap/prototypes/prestations-vue-par-freelance.html) is the visual
   contract, reproduced here on table.list/.card/.cpt-filtre/.badge-* rather
   than started over: only the shapes with no existing equivalent (round dot
   icons, a severity-coloured left edge, "Ce qui ne va pas" chips, the
   collapsible severity groups, the five-block sheet, the phone card reflow)
   get their own prest- prefixed rules below. Colours are the two tokens
   declared at the top of this file for this screen plus values already used
   elsewhere: --warn for red (already the colour of .err), --teal-aplat/--ok
   for green, --line-champ for the muted "not applicable"/"not read yet"
   grey, and the --ko-fond/--ko-texte/--warn-fond/--warn-texte pairs
   .badge-ko/.badge-warn already use, reused as is for the chips.

   Phone reflow: the "carte du téléphone" pattern the rang-2/rang-3 comment
   above names but leaves undone ("Ce que ce bloc ne fait pas") -- this
   screen is the first to build it, scoped to its own table rather than
   generalised, since a row here has no single "most important" column to
   keep the way rang-2/rang-3 assumes. */
.prest-perimetre{display:flex;align-items:center;gap:.5rem;margin:0 0 .8rem;font-size:.85rem;color:var(--warn-texte)}
.prest-perimetre b{font-weight:600}

/* .modal-fenetre's default .modal-body is a dark #525659, meant for a PDF
   preview iframe (see the comment above #matching-fiche-modal's own
   override) -- this dialog holds a plain list, so it needs the same light
   override those form dialogs already get. */
#prestations-non-suivis-modal .modal-body{background:#fff;padding:1.2rem 1.3rem}

/* "Tout afficher" (18/09/2026, §6 quinquies) never loads anything, it only
   clears filters and caps already held in the browser -- the base .btn:disabled
   rule poses cursor:wait, meant for a request in flight (see the comment
   above .btn-reordre:disabled), which would be wrong here for the same
   reason it is wrong there. */
#prestations-tout-afficher:disabled{cursor:default}

.prest-cpt-warn{color:var(--warn-texte)}

/* Round dot icons, one per sujet, in the table and the sheet's block
   titles: the shape (check/exclamation/cross/question mark, an SVG sprite
   near the top of index.html) carries the state as much as the colour, per
   the plan's §1 ("la forme dit l'état autant que la couleur"). */
.prest-d{display:inline-flex;width:22px;height:22px;flex:none;vertical-align:middle}
.prest-d svg{width:100%;height:100%}
.prest-d-ok{color:var(--teal-aplat)}
.prest-d-ko{color:var(--warn)}
.prest-d-warn{color:var(--warn-texte)}
/* nsp (pas encore lu) reads as "attente" (a step still to run its course),
   na (sans objet) reads as plain "info" -- two different pale greys before
   23/09/2026, now the same two shared tokens every other badge uses. */
.prest-d-nsp{color:var(--attente-texte)}
.prest-d-na{color:var(--sub)}
.prest-d-sm{width:16px;height:16px}

table.prest-liste{min-width:64rem}
table.prest-liste th.prest-col-presta,table.prest-liste td.prest-col-presta{width:15rem;padding-left:.9rem}
table.prest-liste th.prest-col-dot{width:5.4rem;text-align:center;vertical-align:bottom;padding:0}
table.prest-liste td.prest-col-dot{text-align:center}
.prest-col-dot-btn{display:block;width:100%;background:none;border:0;padding:.55rem .3rem;font:inherit;
  font-size:.74rem;font-weight:600;color:var(--sub);text-align:center;cursor:pointer;line-height:1.25}
.prest-col-dot-btn:hover{color:var(--ink)}
.prest-col-dot-btn[aria-pressed="true"]{color:var(--teal);background:#e7f3f0}
.prest-nb{display:block;margin-top:.2rem;font-weight:700;color:var(--ko-texte);font-variant-numeric:tabular-nums}
table.prest-liste td.prest-col-presta .prest-sous{display:block;color:var(--sub);font-size:.78rem;font-weight:400}
.prest-mini{display:none}

tr.prest-ligne{cursor:pointer}
tr.prest-ligne:hover{background:#fcfbf8}
tr.prest-ligne.ligne-ouverte{background:#f1f7f5}
tr.prest-ligne.prest-ko>td:first-child{box-shadow:inset 4px 0 0 var(--warn)}
tr.prest-ligne.prest-warn>td:first-child{box-shadow:inset 4px 0 0 var(--warn-texte)}

/* "Ce qui ne va pas" chips: same pale backgrounds and text colours as
   .badge-ko/.badge-warn, only the shape (pill row, dashed border for "not
   read yet") is new. */
.prest-chip{display:inline-block;border-radius:999px;padding:.12rem .6rem;font-size:.8rem;line-height:1.35;margin:.12rem .35rem .12rem 0}
.prest-chip-ko{background:#fbeae3;color:var(--ko-texte);font-weight:600}
.prest-chip-warn{background:#fbf2e3;color:var(--warn-texte)}
.prest-chip-nsp{background:#fff;color:var(--sub);box-shadow:inset 0 0 0 1px var(--line-champ)}

tr.prest-groupe td{background:#fbfaf7;padding:.5rem 1rem;font-size:.86rem;border-bottom:1px solid var(--line)}
tr.prest-groupe button{background:none;border:0;padding:0;font:inherit;text-align:left;cursor:pointer;color:inherit;width:100%}
tr.prest-groupe b{font-weight:700;margin-right:.5rem}
tr.prest-groupe span{color:var(--sub)}
tr.prest-groupe.prest-ko b{color:var(--ko-texte)}
tr.prest-groupe.prest-warn b{color:var(--warn-texte)}
tr.prest-groupe.prest-ok b{color:var(--ok)}
/* "À venir" (18/09/2026, §6 quinquies) gets no rule of its own: it is not a
   severity, so it keeps the same plain ink <b> the group title already
   inherits, same choice already made for "À venir" on Commandes du client.
   "Terminées" and "Sans commande connue" are muted on purpose: secondary,
   folded-by-default reading, never a colour that could be mistaken for a
   severity. */
tr.prest-groupe.prest-terminees b,tr.prest-groupe.prest-sansCommande b{color:var(--sub)}
tr.prest-groupe .chevron{margin-right:.3rem}
tr.prest-plus td{padding:.5rem 1rem .5rem 1.9rem;font-size:.84rem;border-bottom:1px solid var(--line)}
tr.prest-plus button{background:none;border:0;color:var(--teal);text-decoration:underline;font:inherit;cursor:pointer;padding:0}

/* The sheet, in the row's own expanded panel */
.prest-fiche{padding:1rem 1.2rem 1.2rem;display:grid;gap:.9rem}
.prest-f-tete{display:flex;justify-content:space-between;align-items:flex-start;gap:.6rem 1rem;flex-wrap:wrap}
.prest-f-tete h2{font-family:'Playfair Display',Georgia,serif;font-size:1.25rem;font-weight:600;margin:0}
.prest-f-tete p{margin:.15rem 0 0;color:var(--sub);font-size:.87rem}
.prest-f-tete p b{color:var(--ink);font-weight:500}
.prest-f-liens{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap}
.prest-f-sujets{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.8rem;align-items:start}
.prest-sujet{background:#fff;border:1px solid var(--line);border-radius:9px;padding:.7rem .9rem .75rem}
.prest-sujet h3{display:flex;align-items:center;gap:.45rem;margin:0 0 .5rem;font-size:.95rem;font-weight:600}
.prest-sujet ul{list-style:none;margin:0;padding:0;display:grid;gap:.5rem}
.prest-sujet li{display:grid;grid-template-columns:18px minmax(0,1fr);gap:.1rem .5rem;font-size:.87rem;line-height:1.4}
.prest-sujet li.ok,.prest-sujet li.nsp,.prest-sujet li.na{color:var(--sub)}
.prest-faire{grid-column:2;display:block;margin-top:.2rem;font-weight:600;color:var(--ink)}
.prest-faire::before{content:"→ ";color:var(--sub);font-weight:400}
.prest-faire .lien,.prest-faire .btn{margin-left:.45rem;font-weight:400;font-size:.82rem;vertical-align:baseline}
.prest-finance{font-size:.7rem;border:1px solid var(--line);border-radius:4px;padding:0 .3rem;color:var(--sub);margin-left:.3rem;white-space:nowrap}

/* The three-month grid is a CSS grid of <div>s, deliberately NOT a nested
   <table>: it lives inside table.prest-liste's own tbody (the expanded
   sheet's <td>), and table.list th/td/tr/thead/tbody rules match ANY such
   descendant regardless of how many tables are nested in between or what
   display value later overrides them -- a real nested table here fought
   that leak rule to a standstill (equal specificity resets applied per
   getComputedStyle, yet the painted order still came out wrong, a Chromium
   edge case this project has no way to chase further). Plain <div>s cannot
   match a selector that requires a th/td/tr/table tag, so the whole
   category of bug is moot rather than patched. */
.prest-mois-grille{display:grid;grid-template-columns:6.4rem repeat(3,1fr);font-size:.84rem;font-variant-numeric:tabular-nums}
.prest-mois-cell{padding:.3rem .35rem;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:.3rem;white-space:nowrap;color:var(--sub)}
.prest-mois-tete{color:var(--sub);font-weight:600;font-size:.76rem}
.prest-mois-label{font-weight:500;color:var(--ink)}
.prest-mois-cell.prest-ok,.prest-mois-cell.prest-warn,.prest-mois-cell.prest-ko{color:var(--ink)}
.prest-mois-cell .prest-d{flex:none}

.prest-legende{display:flex;gap:.4rem 1.2rem;flex-wrap:wrap;align-items:center;font-size:.8rem;color:var(--sub);margin:.7rem .2rem 0}
.prest-legende span{display:inline-flex;align-items:center;gap:.35rem}

@media (max-width:639px){
  /* The table itself becomes a block, not only its rows: a <tr> left inside
     a real table box is put back into table layout by the browser's own
     anonymous-box fixup, and the five-column grid below never applies.
     Measured on the recette's real data (18/09/2026): the five dots stacked
     one per line instead of forming a row. */
  table.prest-liste{display:block;min-width:0}
  table.prest-liste thead{display:none}
  table.prest-liste tbody,table.prest-liste tbody tr,table.prest-liste tbody td{display:block}
  /* Qualified through the table, or the generic "tbody tr{display:block}"
     above (three elements plus a class) outranks a bare "tr.prest-ligne"
     and the row stays a block: same measurement as the rule above. */
  table.prest-liste tbody tr.prest-ligne{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:.15rem .2rem;padding:.7rem .8rem;border-bottom:1px solid var(--line)}
  tr.prest-ligne>td{border:0;padding:0}
  tr.prest-ligne.prest-ko>td:first-child,tr.prest-ligne.prest-warn>td:first-child{box-shadow:none}
  tr.prest-ligne.prest-ko{box-shadow:inset 4px 0 0 var(--warn)}
  tr.prest-ligne.prest-warn{box-shadow:inset 4px 0 0 var(--warn-texte)}
  table.prest-liste td.prest-col-presta{grid-column:1/-1;margin-bottom:.35rem;width:auto;padding-left:0}
  table.prest-liste td.prest-col-dot{width:auto}
  /* The flex column lives on this inner wrapper, never on the <td> itself
     (prest-col-dot stays display:table-cell at every width): see the
     comment on prestPastilleCell() in app.js for why. */
  .prest-col-dot-inner{display:flex;flex-direction:column;align-items:center;gap:.15rem}
  .prest-mini{display:block;font-size:.64rem;color:var(--sub);line-height:1.1;text-align:center}
  table.prest-liste td.prest-col-pb{grid-column:1/-1;margin-top:.4rem}
  tr.prest-groupe td,tr.prest-plus td{padding-left:.9rem}
  .prest-fiche{padding:.9rem .8rem}
  .prest-f-sujets{grid-template-columns:minmax(0,1fr)}
  .prest-sujet{overflow-x:auto}
  /* The three-month grid drops its oldest month rather than shrinking three
     data columns plus a label column into 375px: with a fixed 4-column grid
     (label + 3 months), the oldest month is always the 2nd cell of every
     row, :nth-child(4n+2) over the flat list of cells picks exactly those,
     one per row, and the column tracks shrink from 3 to 2 to match. */
  .prest-mois-grille{grid-template-columns:5.6rem repeat(2,1fr)}
  .prest-mois-grille .prest-mois-cell:nth-child(4n+2){display:none}
}

/* ── Répartitions, refonte du 22/09/2026 (Work package E, roadmap/
   plateforme-freelance-troisieme-voie.md §5bis) ─────────────────────────────
   Alex's review of the first cut, point 4 verbatim: "l'affichage n'est pas
   beau, il faut qu'il soit plus simple et avec moins de clics". Two shapes
   with no existing equivalent: the envelope drops .colle-compteurs/.cpt for
   one quiet, non-boxed line (.repartitions-enveloppe), and the per-row
   "Modifier" button is replaced by an inline combo opened on focus
   (.repartitions-combo and its children). Everything else reuses existing
   tokens and components as is (.card, .btn/.btn-ghost/.btn-sm, .muted,
   .err, .spinner, .badge-etat). */
.repartitions-enveloppe{color:var(--sub);font-size:.85rem;margin:0 0 1.1rem}
.repartitions-enveloppe b{color:var(--ink);font-variant-numeric:tabular-nums}

/* The generic `.card:has(> table.list){overflow-x:clip}` above would crop
   the absolute suggestion list (per the CSS overflow spec, clip on one axis
   forces the other's `visible` to compute as clip too) -- the second half
   of Alex's 2026-09-22 report (« une liste déroulante mais qui ne se déplie
   pas »). Scoped visible for this one card: five columns, rang-2 hidden on
   phones, horizontal overflow is not expected here; if .colle-defile-h ever
   flags it, this rule (id-strong) deliberately keeps visible rather than
   re-clipping the dropdown. */
.card:has(> #repartitions-roles-table){overflow-x:visible}
.repartitions-combo{position:relative}
.repartitions-combo-input{width:100%;font:inherit;padding:.45rem .6rem;border:1.5px solid var(--line-champ);border-radius:7px;background:#fff}
.repartitions-combo-input:focus{outline:none;border-color:var(--teal-l);box-shadow:0 0 0 3px rgba(122,196,184,.22)}
.repartitions-combo-input:disabled{opacity:.6}
/* z-index 5 only needs to clear this row's own sticky .actions cell
   (z-index 1) and its neighbouring rows -- well under the sticky header
   stack (colle-titre and friends, 29 and up, see the long comment above
   .colle-titre) so a header still wins if the page is scrolled mid-search. */
.repartitions-combo-list{position:absolute;left:0;right:0;top:calc(100% + 4px);z-index:5;background:#fff;border:1px solid var(--line);border-radius:9px;box-shadow:0 8px 24px rgba(30,30,30,.18);max-height:260px;overflow:auto;padding:.3rem}
.repartitions-combo-option{display:block;width:100%;text-align:left;background:none;border:none;border-radius:6px;padding:.4rem .6rem;font:inherit;cursor:pointer}
.repartitions-combo-option:hover{background:#f0efe9}
.repartitions-combo-groupe{margin-top:.3rem}
.repartitions-combo-groupe:first-child{margin-top:0}
.repartitions-combo-titre{font-weight:600;font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;color:var(--sub);padding:.35rem .6rem .15rem}
.repartitions-statut-choix{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap;margin-top:.3rem}
.repartitions-ok{color:var(--ok);font-weight:600;font-size:.85rem;white-space:nowrap}

/* ── Touch targets, lot 4 (23/09/2026, plan §3.5) ────────────────────────────
   "(pointer:coarse)" targets the finger, never a narrow viewport: a mouse on
   a small window must keep the small, precise hit area it already has, and
   a tablet in landscape at a wide viewport still needs the enlarged one.
   Measured today (VÉRIFIÉ, plan §8): .btn-sm is ~24px tall, .ic is a fixed
   26x26px box, .colle-compteurs .cpt-filtre is ~22px tall, .badge-manuel
   inherits .badge-etat's own small pill -- none reaches the 44x44px a
   finger needs (WCAG 2.5.5, and simply Alex's "comme un iPhone" brief).

   The DRAWING inside each control keeps its own size on purpose (the icon
   glyph, the counter's digits, the label's own font-size: nothing here is
   scaled up) -- only the surrounding box grows, exactly the "padding"
   option the plan itself offers as an alternative to an enlarged pseudo-
   element. min-width/min-height rather than a fixed size, so a longer
   .cpt-filtre or .badge-manuel label is never clipped back down to 44px:
   the floor only ever grows a control, never shrinks it. */
@media (pointer:coarse){
  .ic{width:44px;height:44px}
  .btn-sm,.colle-compteurs .cpt-filtre,.badge-manuel{
    display:inline-flex;align-items:center;justify-content:center;
    min-width:44px;min-height:44px;
  }
}

/* Documents obligatoires on a phone (Alex's iPhone capture, 24/09/2026:
   « l'affichage est coupé à droite »). With the 44 px touch boxes above, a
   row's five actions no longer fit their narrow last column: they stacked
   one per line (a 265 px tall row, measured) and Relancer ran off the right
   edge. Same card layout as Prestations below 640 px: name and status on
   top, the actions as one wrapping row underneath, the header dropped (its
   visible columns are the name and the status, both self-explanatory). */
@media (max-width:639px){
  #suivi-table{display:block;min-width:0}
  #suivi-table thead{display:none}
  #suivi-table tbody,#suivi-table tbody tr,#suivi-table tbody td{display:block}
  /* The block rule above outranks the plain .rang-2/.rang-3 hiding rules,
     so the columns already hidden at this width are hidden again here. */
  #suivi-table tbody td.rang-2,#suivi-table tbody td.rang-3{display:none}
  #suivi-table tbody tr[data-row]{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .6rem;
    padding:.65rem .2rem;border-bottom:1px solid var(--line)}
  #suivi-table tbody tr[data-row]>td{border:0;padding:0}
  #suivi-table tbody tr[data-row]>td:first-child{flex:1 1 auto}
  /* No display on the actions cell itself (the cell-display guard in
     config/test_studio_front.py): it is already a block from the rule above,
     and its inline-flex buttons line up and wrap on their own. */
  #suivi-table tbody tr[data-row]>td.actions{flex:1 0 100%;white-space:normal}
  #suivi-table tbody tr[data-row]>td.actions>*{margin:0 .35rem .35rem 0;vertical-align:middle}
}

/* ── Lot 2, revue mobile (roadmap/studio-mobile-revue-ui-ux-pro-max.md,
   constat 1, GO d'Alex 25/09/2026) ──────────────────────────────────────────
   Safari zooms in by itself on any field under 16px on focus, and leaves the
   page zoomed in until the person zooms back out by hand. Measured (revue's
   own sweep, 25/09/2026): every text-shaped field at 15px, the file field at
   13px -- the search field alone had already been raised to 16px (see the
   max-width:639px block above, 2026-09-23). Checkboxes and radio buttons are
   excluded: their own native box does not grow text and never zooms.

   16px alone still left the plain single-line fields (input/select) under
   the 44px touch floor (measured 42px): a min-height finishes what the
   bigger font started, the same floor lot 4 gave .btn-sm and friends below.
   textarea is already taller than 44px by its own min-height:90px rule. The
   file field measured 24px even at 16px (its own browser chrome does not
   grow with the font the way a text field does, contrary to what a first
   version of this comment assumed -- caught by this lot's own new test, not
   by the sweep): min-height:44px closes that gap the same way as the others. */
@media (pointer:coarse){
  input:not([type=checkbox]):not([type=radio]),select,textarea{font-size:16px}
  input[type=text],input[type=email],input[type=password],input[type=search],
  input[type=number],input[type=date],input[type=file],select{min-height:44px}
}

/* ── Lot 2, revue mobile, constat 2 (même plan, même GO) ─────────────────────
   Lot 4 (23/09/2026) already gave .btn-sm, .ic and the compact counters a
   44px floor under (pointer:coarse); this revue's own sweep measured several
   more families of control still under it on a phone: the rubric bar's own
   buttons and "Se déconnecter" (both plain <button> children of .top, 24px),
   the brand button "FAIR4B · Studio" (also a .top button, 20px), every
   <summary> disclosure -- "Aide" and its neighbours -- (17-26px), the column
   sort buttons (button.tri, 29px), Demandes' status filters (.demandes-filtre,
   32px), the rubric menus' own entries (.nav-menu a, not caught by the sweep
   since a closed menu renders nothing, but the same family of control), and
   Demandes' own "sans suite" link (a.demandes-lien-sans-suite, 21px, found by
   this lot's own new test rather than by the sweep -- the sweep's screenshot
   pass never opened this particular link's own tab), the group-header
   buttons of Prestations' folded rows (tr.prest-groupe button, "Sans
   commande connue", 37px, same reason), and the plain .btn family itself
   (43px -- one px under the floor at its own 15px font, and lot 2's own
   16px fields nudge everything else a little taller without helping this
   one, which carries no field). Only the box grows (min-height,
   flex-centred) -- the drawing inside (icon, label, arrow glyph) keeps its
   own size, exactly lot 4's own rule. The top bar grows taller as a direct
   result (it goes from three lines to more): a phone screen already had
   three lines below 640px (.nav-rubriques wraps), this simply widens each
   of them -- lot 4 of the plan itself revisits the whole bar's shape,
   unchanged here on purpose. */
@media (pointer:coarse){
  .top button{display:inline-flex;align-items:center;min-height:44px}
  summary{display:flex;align-items:center;min-height:44px}
  .demandes-filtre,.demandes-lien-sans-suite{display:inline-flex;align-items:center;min-height:44px}
  /* Carte 710e0d85 (roadmap/demandes/2026-09-19-consultants-qui-demarrent-
     prochainement.md, §8 point 1) : même famille de défaut que
     .demandes-lien-sans-suite juste au-dessus, un lien seul en fin de
     phrase, 17px mesuré. */
  .commandes-lien-avenir{display:inline-flex;align-items:center;min-height:44px}
  .nav-menu a{display:flex;align-items:center;min-height:44px}
  .btn{min-height:44px}
  tr.prest-groupe button{display:flex;align-items:center;min-height:44px}
  /* Kept its own display:block+width:100% (it fills the whole header
     cell, that is the point of the click target) rather than the
     inline-flex above: flex instead of block, so the label can be
     centred vertically without losing that full-width hit area. */
  table.list th.th-triable .tri{display:flex;align-items:center;width:100%;min-height:44px}
}

/* ── Lot 2, revue mobile, constat 4 (même plan, même GO) ─────────────────────
   Nothing under 12px anywhere: a form's parenthetical hints (<small> inside a
   <label>, e.g. "Besoin / AO (texte ou fichier)") measured 10.9px -- label's
   own .82rem font-size (13.1px) times the browser's default "smaller" factor
   for <small>, which label small{} never overrode. Demandes' own column
   counters and notes measured 11.2-11.5px. Raised on a phone only: 13px for
   the form hints (the plan's own floor for them, one notch above the 12px
   general floor), 12px for the two Demandes classes. */
@media (pointer:coarse){
  label small{font-size:13px}
  .demandes-count,.demandes-column-note{font-size:12px}
  /* Ce matin's row titles (« À traiter aujourd'hui », « En stock »), 11.5 px
     measured by the orchestrator's re-run of the sweep (25/09/2026). */
  .ce-matin-rangee-titre{font-size:12px}
}

/* ── Lot 4, navigation sur téléphone (roadmap/studio-mobile-revue-ui-ux-pro-
   max.md, constat 5, arbitrage 1, GO d'Alex du 26/09/2026) ──────────────────
   "(max-width:639px)" here, never "(pointer:coarse)": this is a question of
   SPACE (does the bar's own content fit on one line), not of the pointer --
   unlike the touch-target lots above, a mouse on a narrow window needs the
   same collapsed bar just as much as a finger does.

   Nothing here touches 640px and up: .menu-mobile-bouton and .menu-mobile
   stay display:none outside the query below, so the bar and its rubric
   menus render exactly as before this lot on a desktop-width screen.

   Under 640px: the eight rubric buttons, the email and "Se déconnecter"
   (all still inside #nav-rubriques, untouched, see index.html's own
   comment) no longer fit before any content -- measured at 214px on three
   lines by lots 1/2. #nav-rubriques is hidden outright, replaced by the
   Menu button, which opens #menu-mobile-panneau full screen (app.js:
   ouvrirMenuMobile()/rendreMenuMobile()). */
.menu-mobile-bouton{display:none}
.menu-mobile{display:none}
@media (max-width:639px){
  .top{flex-wrap:nowrap}
  .nav-rubriques{display:none}
  .menu-mobile-bouton{display:inline-flex;align-items:center;gap:.35rem;flex:0 0 auto}
  .menu-mobile-bouton svg{width:18px;height:18px;flex:0 0 auto}
  /* Kept as a single accessible name via aria-label (index.html) so the
     button always reads "Signaler une anomalie" to a screen reader, even
     though only "Signaler" is drawn here -- there is no room left for the
     full label next to the brand and the new Menu button. */
  .anomalie-libelle-suite{display:none}

  .menu-mobile:not([hidden]){
    display:flex;flex-direction:column;position:fixed;inset:0;z-index:70;
    background:var(--card);overflow-y:auto;
  }
  .menu-mobile-entete{display:flex;align-items:center;justify-content:space-between;
    padding:.7rem .9rem;border-bottom:1px solid var(--line);position:sticky;top:0;
    background:var(--card)}
  .menu-mobile-titre{font-weight:700;letter-spacing:.02em}
  .menu-mobile-corps{padding:.4rem .9rem;flex:1 1 auto}
  .menu-mobile-rubrique{margin:1.1rem 0}
  .menu-mobile-rubrique h2{font-size:.78rem;text-transform:uppercase;letter-spacing:.04em;
    color:var(--sub);margin:0 0 .3rem}
  .menu-mobile-corps a,.menu-mobile-accueil{display:flex;align-items:center;min-height:44px;
    padding:.3rem .4rem;border-radius:8px;color:var(--ink);text-decoration:none}
  .menu-mobile-corps a:hover,.menu-mobile-accueil:hover{background:#f2f1ec}
  .menu-mobile-corps a.courant,.menu-mobile-accueil.courant{background:#e7f3f0;color:var(--teal);font-weight:600}
  .menu-mobile-accueil{font-weight:600;margin-bottom:.2rem}
  .menu-mobile-pied{border-top:1px solid var(--line);padding:.8rem .9rem 1rem;
    display:flex;flex-direction:column;gap:.55rem}
  .menu-mobile-pied .who{font-size:.8rem;color:var(--sub)}
  .menu-mobile-pied button{min-height:44px}
  /* La page derrière ne défile pas tant que le panneau est ouvert : il est
     déjà plein écran (position:fixed;inset:0), un défilement dessous ne
     ferait que déplacer ce que le retour au panneau montrerait. */
  html.menu-mobile-ouvert,html.menu-mobile-ouvert body{overflow:hidden}
}

/* ── Lot 3, "débordements" (roadmap/studio-mobile-revue-ui-ux-pro-max.md,
   constat 3, arbitrage 2, GO d'Alex du 26/09/2026) ──────────────────────────
   Three screens still scrolled sideways on a phone after lots 1/2/4:
   Demandes (six columns, up to 1447px), Rechargements (five table.list,
   up to 851px), and Documents obligatoires (a talent's name cut off, not
   scrolling, at 449px). All three are (max-width:639px) rules, not
   (pointer:coarse): this is a question of the viewport's own width, same
   distinction lot 4's own comment above makes for the phone nav bar.

   Demandes, arbitrage 2 (Alex, 26/09/2026): STACKED columns, one under the
   other, all reachable by scrolling down -- the alternative offered
   (tabs, one status at a time) was not the one he picked. .demandes-board
   is a CSS grid, 6 columns wide on a desktop (studio.css, "repeat(6,
   minmax(230px,1fr))"); switching that one property to a single column is
   the whole fix, since each column already carries its own title
   (.demandes-column h2) and count (.demandes-count) -- nothing else in the
   markup needs touching, and the columns' own DOM order (Nouveau, À
   trancher, Accepté, En cours, En recette, Livré) becomes the stacking
   order for free. overflow-x, still set for the desktop grid, is turned
   off here too: with one column there is nothing left for it to scroll. */
@media (max-width:639px){
  .demandes-board{grid-template-columns:1fr;overflow-x:visible}
}

/* Rechargements: five table.list (one per source origin, plus the read-
   only "envois" table), none carrying an id -- unlike every other screen's
   table this lot's block above targets by #id, Rechargements builds its
   tables from data (rechargementsGroupe()/renderRechargementsEnvois() in
   app.js), so the two containers they always render inside
   (#rechargements-corps, #rechargements-envois) are the stable hook.
   Same card reflow as Documents obligatoires (#suivi-table, the
   max-width:639px block higher up in this file): thead hidden, tbody/tr/td
   turned to blocks, no `display` on a class-bearing <td> (the guard test
   in config/test_studio_front.py, test_no_rule_gives_a_table_cell_a_
   display_that_stops_it_being_one -- td.actions gets no display of its
   own, only the block it inherits from "tbody td{display:block}" above).

   Column count differs by table (six for a source group: Source,
   Fraîcheur, Dernier passage, Cadence automatique, Coût, Action; four for
   an "envoi": Envoi, Fraîcheur, Dernier passage, Cadence automatique), so
   nth-child stands in for the rang-2/rang-3 classes #suivi-table's own
   markup carries -- there is no <th> here to hold a class on, everything
   is template-built. Cadence automatique and Coût drop out on a phone
   (nth-child(4)/(5) on a source row, nth-child(4) on an envoi row): what
   stays is exactly what the plan's own brief asks for -- the source, its
   freshness, its last run, and (source rows only) the action button. */
@media (max-width:639px){
  #rechargements-corps table.list,#rechargements-envois table.list{display:block;min-width:0}
  #rechargements-corps table.list thead,#rechargements-envois table.list thead{display:none}
  #rechargements-corps table.list tbody,#rechargements-corps table.list tbody tr,#rechargements-corps table.list tbody td,
  #rechargements-envois table.list tbody,#rechargements-envois table.list tbody tr,#rechargements-envois table.list tbody td{display:block}
  #rechargements-corps table.list tbody tr{padding:.65rem 0;border-bottom:1px solid var(--line)}
  #rechargements-envois table.list tbody tr{padding:.65rem 0;border-bottom:1px solid var(--line)}
  #rechargements-corps table.list tbody td,#rechargements-envois table.list tbody td{border:0;padding:.1rem 0}
  #rechargements-corps table.list td:nth-child(4),#rechargements-corps table.list td:nth-child(5),
  #rechargements-envois table.list td:nth-child(4){display:none}
  #rechargements-corps table.list td.actions{padding-top:.35rem}
}

/* Documents obligatoires: the talent's name, in #suivi-table's first
   column, carried an inline style="white-space:nowrap" (renderSuivi() in
   app.js) -- fine on a desktop-width column, but on a phone a long name
   (measured, roadmap's own sweep: "DE LA ROCHEFOUCAULD-MONTBRISON") ran
   449px into a 375px screen instead of wrapping. Moved to a class
   (.suivi-nom) so a media query can override it: an inline style attribute
   outranks any external stylesheet rule regardless of specificity or
   @media, so the override below would never have taken effect left as an
   inline style.

   Kept nowrap by default (the reason for it, unwritten until now: on
   desktop this cell sits next to five others in a single-line row, and
   letting the name wrap there would make one row taller than its
   neighbours for no reason) -- only overridden below 640px, where
   #suivi-table's own phone layout (the max-width:639px block higher up)
   has already turned the row into a wrapping flex column, so a taller
   name cell costs nothing but its own height. overflow-wrap:anywhere (not
   the plainer break-word) is what actually lets
   "ROCHEFOUCAULD-MONTBRISON" itself break mid-word: none of its dashes or
   spaces fall early enough to wrap the name before the screen edge
   otherwise. */
#suivi-table tbody tr[data-row]>td.suivi-nom{white-space:nowrap}
@media (max-width:639px){
  #suivi-table tbody tr[data-row]>td.suivi-nom{white-space:normal;overflow-wrap:anywhere}
}
