* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { scroll-padding-top: 90px; }
/* **Le fond blanc et le lissage de la maquette** — story 1.48, arbitrage d'Arthur
   du 26 septembre 2026 : « le style de la maquette partout ». Le corps rendait
   « bg », un gris que la maquette emploie au survol — des boutons et des lignes de
   table —, pour les boutons de priorité de son parcours guidé et pour quelques fonds
   — la scène du graphe, le champ de recherche, les états vides et les refus
   (« interface.css ») ; ses pastilles sont en « pilule-bg ». Elle pose le corps sur
   « surface », blanc, avec « -webkit-font-smoothing: antialiased ». Voir DESIGN,
   *Colors*. Sans ce lissage, aucune découpe n'est exacte :
   le même texte, avec et sans, diffère de plusieurs milliers de pixels, et la
   marque du menu de près de cinq cents. Les deux valeurs viennent du relevé
   étendu de la coquille (« coquille-v7.json », cible « body »).

   **Aucune largeur minimale**, comme la maquette — arbitrage d'Arthur du 13 octobre
   2026, « Retirer le minimum (Recommandé) ». Le corps imposait 1 080 px depuis la
   story 1.2 : tout écran défilait en largeur dès que la zone utile passait sous
   1 080 px, sous 1 095 px quand la barre verticale est affichée — un Mac réglé pour
   la montrer, un zoom de 125 à 150 %, une fenêtre réduite. Les écrans suivent la
   fenêtre ; « tests/test_defilement_horizontal.py » le garde, barres affichées, de
   1 728 à 1 024 px. PROVENANCE.md, 13 octobre 2026. */
body { margin: 0; background: var(--surface); color: var(--ink); font: 400 var(--texte)/var(--interligne) var(--font-body); letter-spacing: normal; -webkit-font-smoothing: antialiased; }
a { color: var(--brand); text-underline-offset: 3px; }
button, input, select, textarea { font: inherit; color: var(--ink); }
/* La syntaxe se signale au champ, **sans bloquer** : le formulaire porte
   « novalidate », donc le navigateur ne retient plus l'envoi ni n'ouvre de
   bulle, et c'est ce signal qui dit au champ ce qui ne va pas.

   « :invalid » ne crie pas sur un formulaire vierge, et c'est une conséquence de
   la même décision : aucun champ ne porte « required », donc un champ vide est
   VALIDE. Ce qui reste invalide, c'est une saisie réellement malformée — une
   date incomplète, par exemple. L'absence d'une valeur, elle, est une
   incohérence : elle se refuse à l'enregistrement, en nommant les champs.

   Deux corrections mesurées sur l'application qui tourne :

   — « :not(:focus) » : le champ virait au rouge PENDANT la frappe, à « 09 » puis
     « 0915 », c'est-à-dire avant qu'une date puisse être complète. On ne signale
     qu'une fois le champ quitté ;
   — le signal **parle**. Une bordure rouge seule est une couleur seule, ce que
     le plancher d'accessibilité du contrat refuse partout ailleurs. L'étiquette
     porte donc un mot, en plus de la teinte. */
input:invalid:not(:focus), select:invalid:not(:focus) { border-color: var(--danger); border-width: 2px; }
label:has(input:invalid:not(:focus))::after { content: ' · saisie incomplète'; color: var(--danger); background: var(--severity-bloquant-bg); font-size: var(--label); font-weight: 600; }
/* Le marqueur de durée reste lisible sur le fond de refus du panneau 1.50. */
#configuration-initiale label:has(input:invalid:not(:focus))::after { color: var(--ink); }

/* Le bouton d'ouverture du sélecteur de date est un arrêt de tabulation à part
   entière, et il ne portait aucun indicateur de focus : mesuré au navigateur,
   « outline-style: none » au second arrêt du champ.

   Il vit dans l'arbre d'ombre du champ : au moment où il a le focus, le champ
   lui-même ne satisfait ni « :focus » ni « :focus-visible » — seulement
   « :focus-within ». D'où ce couple, qui ne vaut QUE dans ce cas : un champ
   focalisé au clavier porte déjà son indicateur par « :focus-visible », et un
   champ cliqué à la souris n'en gagne aucun, puisqu'il satisfait « :focus ». */
input:focus-within:not(:focus) { outline: 2px solid var(--brand); outline-offset: 3px; }
/* « input[type='date'] » y était absent : les deux champs de date de la surface
   des sources gardaient la bordure grise du navigateur, sans rayon ni
   remplissage, au milieu de champs qui portaient les jetons.

   **Le rayon est le grand, et le relevé le confirme** : « rapport-v7.json » mesure
   9 px sur le champ comme sur le sélecteur (« champ », « selecteur »). DESIGN, à
   *Shapes* — amendé par la story 1.48 —, assigne le petit rayon aux pastilles de
   tier et aux marqueurs, le moyen aux bandeaux, **le grand aux champs** et aux bandes
   d'en-tête des tableaux denses, 14 px (« --rounded-carte ») aux cartes et aux
   tuiles, 8 (« --rounded-bande ») à la bande d'en-tête d'une table de la maquette et
   à sa barre, la pilule aux commandes. */
select, textarea, input[type='search'], input[type='text'], input[type='password'], input[type='date'], input[type='number'] { background: var(--surface); border: 1px solid var(--muted-text); border-radius: var(--rounded-lg); padding: var(--pas) var(--spacing-base); }
/* **La commande, aux valeurs de la maquette** — story 1.48 : une pilule à
   bordure, 8 × 14, en 12 px et graisse 600, dans les deux régimes, comme le
   « .button » de la maquette (« interface.css »). « coquille-v7.json » le relève sur
   « Tableau de bord » (« bouton-secondaire »), dont « button » et « .button » tiennent
   leur géométrie, et sur « Reprendre le projet » (« bouton-primaire »), dont la tient
   « .commande » ; le banc de la galerie les rejoue sur le bouton « Ouvrir » de la
   liste des projets et sur la commande principale du raccordement des sources. La
   maquette donne 8 × 12 à son élément « button », mais aucune commande visible de son
   Portefeuille n'en est un tel quel : elles sont des liens « .button », et c'est
   celles-là que le dépôt suit. La pilule, DESIGN
   l'écrivait déjà à *Shapes* ; la bordure est « border », comme dans la
   maquette : une commande se reconnaît à son libellé, le champ, lui, garde la
   bordure lisible que « rapport-v7.json » lui refuse (« BORDURE_DES_CHAMPS »).
   **« .button » est le lien qui porte le rôle d'une commande** : il se rend à
   l'identique du bouton, fond compris — au repos comme au survol, où la maquette
   ne pose aucun fond sur son lien —, pour qu'un geste passé du lien au formulaire
   ne change aucun pixel. */
/* La maquette écrit aussi « white-space: nowrap » : ses libellés sont courts. Ceux
   des gestes d'une ligne de tableau dense ne le sont pas — « Révoquer le
   raccordement à … » —, et sans retour à la ligne le tableau des sources débordait
   de 32 à 131 px de son conteneur, mesuré au banc de la galerie (story 1.48). */
button { background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 8px 14px; font-size: 12px; font-weight: 600; }
.button { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; border: 1px solid var(--border); border-radius: 999px; background: transparent; font-size: 12px; font-weight: 600; color: var(--ink); text-decoration: none; }
.button:hover { text-decoration: none; border-color: var(--muted-text); background: transparent; }
/* **La commande principale, en aplat de la marque** : « .primary » est le nom de
   la maquette, « .commande » celui du dépôt, et les deux se rendent pareil au
   repos ; leurs survols diffèrent, comme dans la maquette (ci-dessous). **Une
   seule par formulaire, son envoi** ; les gestes d'une ligne restent des boutons
   simples. */
.button.primary { background: var(--brand); color: var(--surface); border-color: var(--brand); }
/* Le motif d'une dérogation est une phrase, pas un libellé : le champ s'étend
   sur la largeur du formulaire, comme le fait la grille des autres champs. */
textarea { width: 100%; resize: vertical; }
button, summary { cursor: pointer; }
/* **Le soulignement au survol est celui d'un lien** — story 1.48, dixième tour de
   relecture, mesuré contre la maquette gelée. La règle soulignait aussi tout bouton
   survolé, à spécificité 0,1,1 : « Ouvrir » et chaque « .commande » se soulignaient.
   La maquette ne souligne aucun bouton, et ses liens gardent le soulignement du
   navigateur, au repos comme au survol. Un lien qui l'a retiré — la signature du
   menu, une commande rendue en lien — ne le reprend pas au survol, comme dans la
   maquette (« .brand:hover », « .commande:hover ») ; « .lien », le bouton rendu en
   lien de texte, le porte au repos et le garde. */
a:hover { text-decoration: underline; }
/* **Le survol d'une commande, celui de la maquette** — story 1.48 : un fond « bg »
   et une bordure lisible. Le bouton de tri d'un en-tête et le bouton qui se rend
   comme un lien ne prennent pas de fond : ils ne se donnent pas pour des pilules.
   **La commande principale, deux survols, comme dans la maquette** (sixième tour de
   relecture, mesuré sur la maquette gelée) : son bouton « button.primary » garde
   l'aplat et la bordure de la marque, et s'assombrit à peine — c'est « .commande »
   ici ; son lien « .button.primary » garde l'aplat, prend la bordure lisible de
   « .button:hover », qui l'emporte dans « interface.css » à spécificité égale, et
   ne s'assombrit pas. Le bouton qui porte « .button.primary » — un geste de l'état
   retenu — se rend comme le lien qu'il remplace, au survol aussi. */
button:hover { background: var(--bg); border-color: var(--muted-text); }
.commande:hover { background: var(--brand); border-color: var(--brand); filter: brightness(.94); text-decoration: none; }
.button.primary:hover { background: var(--brand); border-color: var(--muted-text); }
.tri:hover, .lien:hover { background: transparent; }
input { accent-color: var(--brand); }
:disabled { cursor: not-allowed; color: var(--muted-text); background: var(--bg); border-color: var(--border); }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
fieldset { border: 1px solid var(--border); border-radius: var(--rounded-md); padding: var(--spacing-base); display: flex; gap: var(--spacing-large); }
/* L'écriture d'étiquette — petite, capitale, « muted-text » — appartient à ce qui
   NOMME une donnée : une légende, un terme de liste, un en-tête de colonne. Elle
   n'appartient pas à ce qui EST la donnée.

   « th » attrapait les deux. Dans un tableau dense, un « th scope=row » porte la
   donnée qui identifie la ligne — la source, la page, la tâche — et se rendait
   donc à 11,5 px, en capitales, dans le jeton le plus discret de la palette. Sur
   une ligne teintée par la sévérité, cela contredit deux fois le contrat : « la
   sévérité teinte la ligne entière, texte toujours en ink » et « ne se combine
   jamais avec un texte coloré ». La règle est donc portée par « thead th », et
   l'en-tête de ligne redevient du texte de données.

   La règle est globale, et vaut pour les surfaces à venir autant que pour
   celle-ci. */
legend, dt, th, .surtitre { font-size: var(--label); font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase; }
legend, dt, th, .surtitre, caption, time { color: var(--muted-text); }
.tableau-dense th[scope='row'] { font: inherit; font-weight: 600; letter-spacing: inherit; text-transform: none; color: var(--ink); }
/* **L'approche des titres est celle de la maquette, −0,025 em** — story 1.48. Le
   dépôt écrivait −0,01 em, la valeur que DESIGN déclarait pour « display » ; le
   relevé du rapport mesure −0,025 em sur le titre de carte, et le titre de
   surface la portait déjà par son jeton. */
h1, h2, h3 { font-family: var(--font-display); letter-spacing: -0.025em; }
/* **Le titre de surface, aux valeurs du prototype.** Il rendait 22 px en graisse
   700 — les valeurs que la charte écrivait avant la maquette — là où il pose
   28 px en 600, interligne 1,3, approche −0,025 em. L'écart de six pixels sur le
   titre raccourcissait tout l'en-tête de dix-sept, et c'est ce qui se voyait le
   plus en posant les deux pages côte à côte. Arbitrage du 22 septembre 2026 : la
   maquette prime, le contrat l'enregistre. */
h1 { font-size: var(--display); line-height: var(--display-height); font-weight: 600; letter-spacing: var(--display-approche); }
/* **La marge du titre de surface est celle du relevé, 13 px puis 8** — story 1.48.
   Le barème des quatre pas la rendait à 12 puis 6 : un pixel au-dessus, deux en
   dessous, et l'écart déclaré ne permettait aucune découpe exacte de l'en-tête. Le
   titre vit dans l'en-tête de la coquille, et c'est sa géométrie que le relevé
   mesure (« coquille-v7.json », cible « h1 »). */
.header h1 { margin: 13px 0 8px; }
h2, h3 { font-size: var(--title); font-weight: 600; line-height: 1.3; }
h2 { margin: var(--spacing-dense) 0 var(--spacing-base); }
/* Un titre qui **ouvre** une carte n'a rien au-dessus de lui : le remplissage de
   la carte fait déjà la respiration, et le pas dense l'y ajoutait une seconde fois.
   Le relevé du prototype écrit « 0 0 12px » sur le titre de carte. */
.panneau > h2:first-child, .card > h2:first-child { margin-top: 0; }
/* **Le nom du projet actif, sous son surtitre** : 8 px au-dessus, comme la maquette
   l'écrit dans « portefeuille() » — là, en style en ligne. Relevé sur « #portefeuille »
   (« coquille-v7.json », cible « titre-du-projet-actif »), et exempté du barème à
   ce titre (story 1.48). */
.card .eyebrow.accent + h2 { margin-top: 8px; }
h3 { margin: var(--spacing-base) 0; }
p { margin: var(--spacing-base) 0; }
.evitement { position: absolute; top: -80px; padding: var(--spacing-base); background: var(--surface); z-index: 5; }
.evitement:focus { top: 8px; }

/* ─────────────────────────── La coquille — story 1.20 ───────────────────────
   Le menu latéral des domaines, le fil d'Ariane et l'en-tête de surface. Écrits
   une fois, rendus par « base.html », jamais recopiés par une surface.

   **Les valeurs de forme viennent d'un relevé, jamais d'une lecture de maquette.**
   « tests/references/ux/coquille-v7.json » porte les styles **calculés** de seize
   éléments de la coquille du prototype adopté le 13 septembre, relevés dans
   Chromium à 1440×1000 par « relever-la-coquille.mjs », qui est versé à côté. Une
   feuille de style dit ce qu'un auteur a écrit ; le style calculé dit ce que le
   navigateur en fait, cascade, héritage et unités résolues comprises — et c'est
   ce nombre-là que le banc du navigateur confronte, élément par élément.

   **Ces valeurs sortent du barème des quatre pas, et c'est une décision.** Le
   barème de DESIGN gouverne les surfaces de contenu ; la géométrie de la coquille
   suit le relevé au pixel. L'exemption est nommée et close dans
   « tests/test_charte.py » : les sélecteurs qui en bénéficient sont exactement
   ceux que le relevé couvre, et rien n'y échappe au contrôle — ce qui les garde
   est plus strict que le barème, puisque c'est une mesure. */
.shell { display: grid; grid-template-columns: 224px minmax(0, 1fr); min-height: 100vh; }
/* Personne non connectée : ni menu, ni fil d'Ariane, ni bloc projet. */
.shell-sans-menu { grid-template-columns: minmax(0, 1fr); }
/* ─── Le menu replié, et sa poignée — story 1.23 ───────────────────────
   **Les deux valeurs viennent du prototype, relues dans sa feuille** :
   « .shell.collapsed » y passe la première colonne à 28 px, et « .rail-handle »
   est un bouton de 28 px de large, haut comme l'écran, en aplat d'accent. C'est
   tout ce que le repli change — aucune surface ne se lit différemment selon que
   le menu est plié ou non.

   « display: contents » sur le formulaire : dans le prototype rien n'enveloppe la
   poignée, et c'est elle qui *est* la colonne de la grille. Sans cela le
   formulaire serait l'élément de grille, et la poignée n'occuperait plus la
   hauteur de l'écran.

   **L'indicateur de focus passe au blanc, comme dans le menu.** La poignée vit
   hors de « .rail » — elle le remplace — et n'héritait donc pas de la règle qui
   y blanchit le trait : sur l'aplat d'accent, le trait de la marque contraste
   à 1,00, c'est-à-dire qu'il est invisible. Le décalage rentre en dedans, la
   poignée faisant 28 px de large. */
.shell.collapsed { grid-template-columns: 28px minmax(0, 1fr); }
.rail-repli { display: contents; }
.rail-handle { position: sticky; top: 0; height: 100vh; width: 28px; padding: 0; border: 0 none; border-radius: 0; background: var(--brand); color: var(--surface); font-size: 18px; }
.rail-handle:hover { background: var(--brand); color: var(--surface); text-decoration: none; }
.rail-handle:focus-visible { outline-color: var(--surface); outline-offset: -3px; }
.rail { position: sticky; top: 0; height: 100vh; overflow-y: auto; background: var(--brand); color: var(--surface); padding: 26px 15px 18px; display: flex; flex-direction: column; }
/* « color: inherit » et non une valeur : dans l'aplat d'accent la signature
   hérite du blanc du menu, hors du menu — la page de connexion — elle hérite de
   « ink » du corps. Une valeur écrite ici en couvrirait une des deux, et le
   lien qu'elle est reprendrait la marque, invisible sur la marque. */
.brand { display: block; font: 700 20px var(--font-display); letter-spacing: -0.04em; color: inherit; text-decoration: none; padding: 0 8px; }
.brand:hover { text-decoration: none; }
.brand span { font-weight: 400; }
.brand-hors-menu { margin: 0; }
.rail-sub { display: block; font-size: 9px; letter-spacing: 0.15em; text-transform: uppercase; opacity: .85; padding: 8px; }
/* Le nom et le domaine du projet ouvert passent à la ligne où il faut — story 1.48 :
   un nom d'un seul mot, même de vingt-six lettres à 21 px, ou un domaine de 253
   caractères, faisait défiler le menu en largeur — de 1 908 px au banc de la galerie.
   Complément à la maquette, dont le projet s'écrit en deux mots (PROVENANCE.md). */
.rail-client { margin: 20px 8px 15px; border-top: 1px solid rgba(255, 255, 255, .2); padding-top: 20px; font: 600 21px var(--font-display); letter-spacing: -0.04em; overflow-wrap: anywhere; }
.rail-domain { display: block; font: 400 11px var(--font-body); letter-spacing: 0; margin-top: 8px; opacity: .85; }
.nav-group { font-size: 9px; letter-spacing: 0.15em; text-transform: uppercase; opacity: .8; padding: 18px 12px 5px; }
.rail a.nav { display: flex; align-items: center; gap: 10px; min-height: 35px; padding: 8px 12px; border-radius: 999px; color: rgba(255, 255, 255, .91); text-decoration: none; font-size: 12.5px; }
/* Le pictogramme du prototype : 16 px, et « flex: none » pour qu'un libellé long
   ne le comprime pas — un trait de 1,6 sur une boîte écrasée devient illisible
   avant de devenir petit. Le gap de 10 px le sépare du libellé. */
.rail a.nav svg { width: 16px; height: 16px; flex: none; }
.rail a.nav:hover { background: rgba(255, 255, 255, .1); }
.rail a.nav.active { background: rgba(255, 255, 255, .18); font-weight: 600; }
/* **Aucun compteur dans ce menu depuis la story 1.40.** Le seul que la coquille
   Django portait était celui du rapport de rattachement, et l'entrée est partie
   avec la surface : le compteur du rapport vit dans le menu de la V7, qui le lit
   dans le corpus du back. Une règle sans élément à styler est un dessin que plus
   personne ne relit. */
/* **Le bas du menu, aux valeurs de la maquette** — story 1.48 : un texte de 11 px,
   un remplissage de 9 px, une bordure à 0,3 d'opacité, un écart de 8 entre deux
   commandes, et **la pilule blanche sur la dernière**. Le relevé étendu de la
   coquille les mesure (« rail-bottom », « rail-bouton-dernier ») ; le premier
   bouton, « rapport-v7.json » le relève déjà (« commande »).

   **Chaque commande vit dans son formulaire**, là où la maquette pose ses boutons
   à même la grille : « display: contents » fait de chaque bouton un élément de la
   grille, comme dans la maquette, et l'écart de 8 tombe entre deux commandes.

   **La dernière est « Se déconnecter »**, là où la maquette la pose sur « Exporter
   mes données » : c'est la dernière commande du bas du menu ici, et l'emphase se
   déplace avec elle. « :last-of-type » viserait chaque bouton, chacun étant seul
   dans son formulaire : c'est le dernier formulaire qu'on vise. */
.rail-bottom { margin-top: auto; display: grid; gap: 8px; padding-top: var(--spacing-large); }
.rail-bottom > form { display: contents; }
.rail button { display: block; width: 100%; background: transparent; border-color: rgba(255, 255, 255, .3); color: var(--surface); padding: 9px; font-size: 11px; }
.rail-bottom > form:last-child > button { background: var(--surface); color: var(--brand); }
.rail button:hover, .rail-bottom > form:last-child > button:hover { background: rgba(255, 255, 255, .17); color: var(--surface); }
/* L'indicateur de focus est un trait de la marque : sur l'aplat d'accent il
   serait invisible, contraste 1,00. Il passe au blanc, qui contraste à 8,15
   avec l'aplat par construction. */
.rail :focus-visible { outline-color: var(--surface); }
.main { padding: 24px 30px 40px; min-width: 0; }
.header { border-bottom: 1px solid var(--border); margin: -24px -30px 20px; padding: 24px 30px 18px; }
/* **Le rang d'actions de l'en-tête, et sa hauteur réservée** — story 1.48. La
   maquette pose à droite du fil un rang d'actions — le badge du fictif, la
   commande principale —, haut comme une commande : 12 px sur un interligne de 1,4,
   8 px de remplissage et un trait de chaque côté. La hauteur est réservée sur
   **toutes** les surfaces, qu'elles portent un rang d'actions ou non : sans elle, le
   titre sautait de dix pixels en passant du Portefeuille aux sources. */
.entete-haut { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-large); flex-wrap: wrap; min-height: calc(12px * 1.4 + 18px); }
.header .actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.breadcrumbs { display: flex; align-items: center; gap: 7px; font-size: 11px; color: var(--muted-text); }
/* **Un nom de projet long ne pousse pas le rang d'actions** — story 1.48. Le fil
   prenait la largeur du nom entier : d'un seul mot de 200 caractères, « Reprendre le
   projet » passait à la ligne, l'en-tête gagnait 35 px et le titre descendait
   d'autant, à 1 440 comme à 1 080, où la page débordait en outre de 328 px (mesuré
   au banc de la galerie). Le fil prend la place que
   le rang d'actions lui laisse — base nulle, minimum nul —, ses étapes gardent leur
   largeur, et le nom du projet seul s'y coupe d'une ellipse, sur une ligne. Il se lit
   entier au survol (« title »), dans le bloc projet du menu déplié, et sur la liste
   des projets dans l'accroche. Compléments à la maquette, qui n'écrit rien de tel et
   déborde avec un tel nom : écarts déclarés dans PROVENANCE.md, et le minimum nul
   dans ECARTS_ADMIS_DE_LA_COQUILLE. */
.entete-haut > .breadcrumbs { flex: 1 1 0; min-width: 0; }
.breadcrumbs > * { flex-shrink: 0; }
.breadcrumbs > .fil-du-projet { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.breadcrumbs a { color: inherit; text-decoration: none; }
/* La dernière étape n'est pas un lien : on y est. */
.breadcrumbs strong { font-weight: 600; color: var(--ink); }
/* **L'accroche garde 13 px dans les deux régimes**, comme dans la maquette, dont la
   bascule ne touche pas l'en-tête : « body-dense », et non « texte », qui passe à
   15 en démonstration. Relevé sur « .lede », dans les deux régimes (story 1.48).
   La coupure où il faut est un complément : un nom de projet d'un seul mot de 200
   caractères, dans l'accroche, faisait déborder la page de 24 à 27 px à 1 440 et de
   384 à 387 à 1 080 (mesuré au banc de la galerie ; PROVENANCE.md). */
.lede { margin: 0; max-width: 85ch; color: var(--muted-text); font-size: var(--body-dense); overflow-wrap: anywhere; }
.surtitre { margin: 0; }
.sommaire { display: flex; gap: var(--spacing-large); margin: var(--spacing-large) 0 var(--spacing-section); padding-bottom: var(--spacing-large); border-bottom: 1px solid var(--border); }
/* **La carte, aux valeurs de la maquette** — story 1.48 : un rayon de 14, une ombre
   à 2,5 % d'opacité, un remplissage de 20 et 18 px sous elle, comme la « .card »
   que « rapport-v7.json » relève (cible « carte »). « .panneau » est le nom que le
   dépôt lui donne sur ses surfaces, « .card » celui du Portefeuille, qui porte les
   classes de la maquette : une seule règle pour les deux, et donc une seule carte.
   DESIGN, amendé le 26 septembre 2026, admet ce rayon et cette ombre. */
.panneau, .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--rounded-carte); padding: var(--spacing-large); margin: 0 0 18px; box-shadow: 0 1px 2px rgba(10, 10, 15, .025); }
/* ─── Le Portefeuille — story 1.48 ──────────────────────────────────────────
   Les tuiles, le surtitre accentué, la rangée de gestes, la barre de progression
   et la pastille de la vue Portefeuille de la maquette, sous leurs noms de la
   maquette : la page porte ses classes, et sa zone d'en-tête, de tuiles et de
   carte « Projet actif » se compare au pixel à la maquette gelée. **Les valeurs
   viennent du relevé étendu de la coquille** (« coquille-v7.json », relevé sur
   « #portefeuille »), qui les confronte au navigateur ; elles sortent du barème
   des quatre pas pour la même raison que la coquille, et l'exemption est close
   dans « tests/test_charte.py ». */
.kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--spacing-base); margin-bottom: var(--spacing-large); }
.kpi { display: grid; gap: 9px; padding: 18px; border: 1px solid var(--border); border-radius: var(--rounded-carte); background: var(--surface); }
/* « font » réinitialise l'interligne : la maquette rend le grand nombre sur
   l'interligne « normal » de Sora, et c'est ce qu'on veut. Le corps suit les deux
   régimes — 29 px en travail, 36 en démonstration —, la graisse est le jeton du
   grand nombre, 600 dans les deux. */
.kpi strong { font: var(--grand-nombre-poids) var(--grand-nombre) var(--font-display); letter-spacing: -0.035em; overflow-wrap: anywhere; }
/* **Un détail tient dans sa tuile, un domaine de 253 caractères compris** : sans
   coupure possible, il donnait sa largeur à la piste de la grille — 1 543 px dans
   une tuile de 280. La coupure où il faut est un complément à la maquette, qui ne
   la pose que sur le grand nombre (story 1.48, écart déclaré dans PROVENANCE.md). */
.kpi .small { padding-top: 8px; border-top: 1px solid var(--border); font-size: 11px; color: var(--muted-text); overflow-wrap: anywhere; }
.eyebrow { font-size: 10px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted-text); }
/* **Le surtitre accentué désigne le projet actif** : la sélection, qui est un état
   de l'interface et non une donnée — « brand désigne l'action, ce qui est
   sélectionné ». */
.eyebrow.accent { color: var(--brand); }
.muted { color: var(--muted-text); }
.small { font-size: 12px; }
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.row.between { justify-content: space-between; }
/* **Le nom et le domaine du projet actif tiennent dans leur colonne** : premier
   élément d'une rangée flexible, le bloc prenait la largeur de son mot le plus
   long, et un domaine de 253 caractères sortait de la carte. Complément à la
   maquette (story 1.48, écart déclaré dans PROVENANCE.md). */
.card .row.between > :first-child { min-width: 0; overflow-wrap: anywhere; }
/* **La barre de progression de la démonstration, et elle seule.** Elle porte la
   progression locale du plan fictif ; aucun projet réel n'a de recommandation,
   et la maquette ne la rend pas sans elles. Le fond « #E4E4EA » et la marque sur le
   remplissage sont ceux de la maquette : DESIGN les admet, pour cette barre. */
.progress { height: 6px; margin: 10px 0; border-radius: var(--rounded-bande); overflow: hidden; background: var(--barre-de-progression-bg); }
.progress span { display: block; height: 100%; border-radius: inherit; background: var(--brand); }
/* **La pastille de la maquette** : neutre sur « pilule-bg » pour dire le fictif,
   et dans le ton de la barrière pour dire son verdict. Le ton « danger » garde
   son fond de sévérité et écrit en « ink » : la palette n'a pas le rouge de texte
   de la maquette, et DESIGN écrit toute sévérité en « ink ». */
.badge { display: inline-flex; padding: 3px 9px; border-radius: 999px; font-size: 10px; font-weight: 700; letter-spacing: 0.03em; white-space: nowrap; background: var(--pilule-bg); color: var(--muted-text); }
.badge.ok { background: var(--ok-bg); color: var(--ok-text); }
.badge.warn { background: var(--severity-important-bg); color: var(--warn); }
.badge.danger { background: var(--severity-bloquant-bg); color: var(--ink); }
/* **La table de la maquette**, dans une carte : la liste des projets du
   Portefeuille. Ce n'est pas un tableau dense — ni tri, ni filtre, ni colonne
   redimensionnable —, et elle porte donc les valeurs que « rapport-v7.json »
   relève sur la table de la maquette (cibles « entete-cellule » et « cellule ») :
   9 × 12 en tête, 11 × 12 en cellule, un coin de 8. Le séparateur prend « border »,
   la palette étant close (« PALETTE_CLOSE »). */
/* **La table défile dans sa carte, et la carte ne s'élargit pas** : un domaine de
   253 caractères sans coupure pousserait sinon la table hors de la carte, et la
   page entière déborderait. La règle de la maquette (« interface.css »). */
.table-wrap { overflow-x: auto; max-width: 100%; }
.card table { width: 100%; border-collapse: collapse; font-size: 12px; }
.card thead th { padding: 9px 12px; background: var(--entete-de-table-bg); color: var(--entete-de-table-fg); font-size: 9px; font-weight: 700; letter-spacing: 0.06em; text-align: left; white-space: nowrap; }
.card thead th:first-child { border-radius: var(--rounded-bande) 0 0 var(--rounded-bande); }
.card thead th:last-child { border-radius: 0 var(--rounded-bande) var(--rounded-bande) 0; }
.card tbody th, .card tbody td { padding: 11px 12px; border-bottom: 1px solid var(--border); font-variant-numeric: tabular-nums; vertical-align: middle; }
/* L'en-tête de ligne s'aligne à gauche comme une cellule ; la cellule garde le sens
   de lecture qu'elle hérite, « start », comme dans la maquette. */
.card tbody th { text-align: left; font: inherit; font-weight: 600; letter-spacing: normal; text-transform: none; color: var(--ink); }
.card tbody th small { display: block; font-size: 10.5px; font-weight: 400; color: var(--muted-text); }
.card tbody tr:hover > * { background: var(--bg); }
/* ─── La carte « Mise en route » — story 1.49 ──────────────────────────────────
   **Le composant et l'emplacement du parcours guidé de la maquette** : une carte
   « section.card > h2 + div.cols.equal », deux colonnes égales, et chaque étape un
   bouton de priorité — un bloc de la largeur de sa colonne, en « bg », sans bordure,
   au rayon de 9, avec son détail en « small » sous le titre. Les valeurs sont celles
   que « coquille-v7.json » relève sur « #portefeuille » (cibles « grille-egale »,
   « bouton-de-priorite », « bouton-de-priorite-detail ») et que le banc de la galerie
   rejoue sur un Portefeuille qui porte la carte ; elles sortent du barème des quatre
   pas pour la même raison que la coquille, et l'exemption est close dans
   « tests/test_charte.py ».

   **Trois éléments, un dessin.** La maquette ne rend que des boutons ; la carte rend
   la prochaine étape en lien — projet ouvert — ou en bouton du formulaire
   d'« Ouvrir » — projet seulement retenu —, et les autres en paragraphes. La règle
   écrit donc ce que chacun n'hérite pas de même : la taille et la graisse que la
   maquette tient de sa règle « button », l'encre au lieu de la marque d'un lien, et
   aucun soulignement, au repos comme au survol. Le repli à 780 px de la maquette n'est
   pas porté : les écrans Django n'ont aucun point de rupture, et en poser un demande
   l'arbitrage d'Arthur (PROVENANCE.md, 13 octobre 2026). La coupure où il faut est
   un complément : un nom de personne d'un seul mot ne pousse pas la colonne. */
.cols.equal { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--spacing-large); align-items: start; }
.cols > * { min-width: 0; }
.priority-button { display: block; width: 100%; white-space: normal; text-align: left; border: 0 none; border-radius: var(--rounded-lg); background: var(--bg); padding: 11px 12px; margin: 8px 0; font-size: 12px; font-weight: 600; color: var(--ink); text-decoration: none; overflow-wrap: anywhere; }
.priority-button small { display: block; color: var(--muted-text); font-weight: 400; margin-top: 5px; }
.priority-button:hover { background: var(--bg); text-decoration: none; }
/* **La prochaine étape se met en avant** — arbitrage d'Arthur du 28 septembre 2026,
   « Mettre en avant la prochaine » : les neuf étapes avaient la même apparence, et une
   seule menait quelque part. Écart déclaré à la maquette (PROVENANCE.md), sans rien
   changer au bouton de priorité : sa boîte, son fond, son rayon et ses marges restent
   ceux du relevé.
   — la commande porte le surtitre accentué du Portefeuille, en bloc au-dessus de son
     titre, et une flèche à la fin de celui-ci ; au survol, un contour lisible, celui
     que la maquette donne à ses boutons survolés, tracé en dedans pour ne rien
     déplacer ; au clavier, l'indicateur de focus de la charte ;
   — une étape faite dit « ✓ Fait » dans la teinte de l'état conforme, 4,67 sur « bg » :
     le mot porte l'information, la teinte l'appuie ;
   — une étape à venir s'atténue en « muted-text », 4,87 sur « bg » ; une étape
     impossible ne s'atténue pas, puisqu'elle dit quoi régler et où. */
.priority-button .eyebrow { display: block; }
.priority-button.etape-prochaine:hover { box-shadow: inset 0 0 0 1px var(--muted-text); }
.etape-a-venir { color: var(--muted-text); }
.etape-faite { color: var(--ok-text); font-weight: 600; }
.variantes { display: flex; flex-wrap: wrap; gap: var(--spacing-large); align-items: center; margin: var(--spacing-large) 0; }
/* **Un message de confirmation se coupe où il faut** — story 1.48, dix-septième tour
   de relecture. « Projet ouvert : … » nomme le projet, et un nom d'un seul mot de 200
   caractères gardait au message la largeur du mot, élément d'une rangée flexible : la
   page débordait de 67 px à 1 440, de 227 à 1 280 et de 427 à 1 080. Le sélecteur se
   borne aux confirmations : « .variantes » range ailleurs des variantes de composants
   et des règles, qui n'ont pas de nom de projet. Un message ordinaire ne bouge pas.
   **La confirmation rendue dans la carte « Mise en route »** (story 1.49, treizième
   tour) nomme le même projet : la même coupure. */
#confirmation .variantes > li, #confirmation-de-la-mise-en-route .variantes > li { min-width: 0; overflow-wrap: anywhere; }
/* **Les règles de normalisation se coupent où il faut**, sur « /collecte/ » — correctif
   du défilement horizontal, troisième tour de relecture (13 octobre 2026). Chaque mention
   est un élément de la même rangée flexible, et celle de l'origine canonique en vigueur
   nomme ses adresses : sur un domaine de 253 caractères d'un seul tenant, elle gardait
   la largeur du mot et poussait la page à toute largeur. La même coupure que les
   confirmations, bornée à cette liste ; une mention ordinaire ne bouge pas. */
#regle-de-normalisation .variantes > li { min-width: 0; overflow-wrap: anywhere; }
/* **« Projet ouvert : <nom> — <domaine> » se coupe où il faut**, sur « /projet/ » —
   correctif du défilement horizontal, premier tour de relecture (13 octobre 2026). Un nom
   d'un seul mot de 200 caractères poussait la page de 15 px à 1 512 jusqu'à 503 px à
   1 024, et un domaine de 253 caractères dès 1 728 px. Un nom ordinaire ne bouge pas. */
#editeur-du-projet { overflow-wrap: anywhere; }
/* **Une phrase qui cite une valeur se coupe où il faut**, sur tout écran Django — correctif
   du défilement horizontal, la règle d'ensemble (13 octobre 2026) : toute chaîne venue des
   données se coupe, ou elle est contenue. Le message d'un refus, qui cite la saisie — « le
   domaine « … » est déjà porté par un projet existant » — ou nomme le projet ; le refus
   d'une première configuration déjà enregistrée, qui nomme le projet ; le titre de la
   surface, qui nomme le domaine d'un projet sans corpus ; la graine de la collecte, qui
   nomme l'adresse de départ et le domaine ; le motif que CrawlObserver rapporte, qui peut
   citer une adresse, et la personne qui a accepté l'exécution incomplète ; la phrase d'un
   relevé sans projet retenu chez CrawlObserver, qui nomme le projet ; l'état de chaque étape
   de la collecte, qui nomme la personne qui l'a faite. Un mot d'un seul tenant — un nom de
   200 caractères, un domaine de 253, un nom de personne — y gardait sa largeur et poussait
   la page ; une phrase ordinaire ne bouge pas. Aucune disposition neuve : une coupure. */
#message-du-refus, #configuration-refus, .header > h1, #graine-de-la-collecte, [data-rapatriement-bloque], #rattachement-sans-projet-retenu, .etat-de-l-etape { overflow-wrap: anywhere; }
[data-incompletude] > span { min-width: 0; overflow-wrap: anywhere; }
.pastille-tier { display: inline-block; padding: 2px var(--spacing-dense); border-radius: var(--rounded-sm); font: 600 var(--label)/1.35 var(--font-body); letter-spacing: .03em; white-space: nowrap; }
.tier-1 { background: var(--tier-1); color: var(--tier-1-fg); }
.tier-2 { background: var(--tier-2); color: var(--tier-2-fg); }
.tier-3 { background: var(--tier-3); color: var(--tier-3-fg); }
.tier-4 { background: var(--tier-4); color: var(--tier-4-fg); }
.ligne-severite { display: flex; align-items: center; gap: var(--spacing-large); padding: var(--pas) var(--spacing-base); min-height: var(--ligne); color: var(--ink); border-radius: var(--rounded-md); margin: var(--spacing-dense) 0; }
.ligne-severite > .marqueur-nature { margin-left: auto; }
.severite-bloquant { background: var(--severity-bloquant-bg); }
.severite-important { background: var(--severity-important-bg); }
.severite-opportunite { background: var(--severity-opportunite-bg); }
.marqueur-nature { color: var(--ink); white-space: nowrap; }
.marqueur-nature > span { font-weight: 600; display: inline-block; min-width: 16px; }
/* **Le bandeau vit dans la colonne, et ses coins ne laissent rien voir** — story
   1.48. Rendu avant la coquille, il collait sur toute la largeur de l'écran et
   couvrait la marque du menu au défilement ; il est désormais dans « main », juste
   après l'en-tête, et reste collant. Collé en haut à 0 px avec son rayon, il
   laissait voir dans ses quatre coins le contenu qui défilait dessous : un liseré
   de la couleur de la page, tracé autour de lui, les recouvre, et ne se voit pas
   au repos. Le pas qui le suit l'écarte de la première carte. */
.bandeau-donnee-incomplete { position: sticky; top: 0; z-index: 4; border-radius: var(--rounded-md); background: var(--severity-important-bg); color: var(--warn); padding: var(--spacing-base) var(--spacing-section); margin-bottom: var(--spacing-large); box-shadow: 0 0 0 var(--spacing-dense) var(--surface); }
.marqueur-provisoire { position: relative; color: var(--warn); }
.marqueur-provisoire summary { font-size: var(--label); text-transform: uppercase; font-weight: 600; letter-spacing: .03em; }
.detail-provisoire { position: absolute; top: 100%; left: 0; width: 340px; z-index: 3; background: var(--surface); border: 1px solid var(--warn); border-radius: var(--rounded-md); padding: var(--spacing-base); }
.valeur-numerique { font-family: var(--font-body); font-variant-numeric: tabular-nums; font-weight: 500; color: var(--ink); }
.nombre { text-align: right; }
/* **Aucun fond sur la table.** Le prototype la laisse transparente : elle prend
   celui de ce qui la porte — la page quand elle y est posée, la carte quand elle
   y vit. Un blanc écrit ici plaquait un rectangle blanc sur la page grise. */
/* **Un tableau dense écrit un cran sous le texte courant**, et c'est le prototype
   qui le pose : son corps de page vaut 13 px, ses tableaux 12. Ils portaient la
   même taille ici, et la ligne mesurait 61 px contre ses 57 — quatre pixels par
   ligne, quatre-vingts sur un écran plein. Le jeton suit les deux régimes, comme
   les neuf autres. */
.tableau-dense { width: 100%; border-collapse: collapse; font-size: var(--body-table); }
.tableau-dense tr { height: var(--ligne); }
/* « text-align » ne porte que sur l'en-tête : une cellule de données hérite du
   sens de lecture, et l'y écrire en dur rendrait « left » là où le relevé du
   prototype mesure « start ». Les deux se voient pareil ici et ne se voient pas
   pareil partout ; la valeur héritée est la juste. */
.tableau-dense th, .tableau-dense td { border-bottom: 1px solid var(--border); padding: 2px var(--pas); white-space: nowrap; }
.tableau-dense th { text-align: left; }
/* Une cellule qui porte une phrase, et non un nombre ni un libellé court : le
   « nowrap » du tableau dense y forcerait un défilement horizontal. */
.tableau-dense td.texte-libre { white-space: normal; }
.tableau-dense td.nombre { text-align: right; }
.tableau-dense th { position: relative; }
.tableau-dense caption { text-align: left; padding: var(--spacing-base) 0; }
/* ─── La cellule d'en-tête et la pastille d'état — story 1.21 ───────────────
   **La bande d'en-tête vient d'un relevé mesuré**, pas d'une lecture de maquette :
   « tests/references/ux/rapport-v7.json » pose sur « table thead th » un fond, pas
   de bordure basse, et un coin arrondi à chaque bout. Une ligne de libellés posée
   sur le vide se confond avec la première ligne de données dès qu'un tableau
   dépasse la hauteur de l'écran, ce que cette surface fait pour son second
   tableau. Ce que le relevé donne et que la charte ne donne pas — un fond
   lavande, un corps de 9 px — a été porté à la palette et au jeton le
   22 septembre 2026. L'écran du rapport est parti à la story 1.40 ; le relevé est
   rejoué sur la collecte, qui porte la même table dense, et ses écarts restent
   déclarés dans « tests/test_charte.py », avec leur motif.

   **UX-DR-12 : tout nombre qui s'aligne en colonne est à chasse fixe.** Le relevé
   pose « tabular-nums » sur la cellule entière, et non sur le seul composant de
   valeur numérique. C'est plus juste : dans une colonne, « 1 204 » et « 998 » ne
   se comparent à l'œil que si leurs chiffres ont la même chasse, que le gabarit
   ait pensé ou non à envelopper la valeur. Porter l'exigence au seul composant la
   laissait tomber à chaque cellule écrite à la main. */
/* **La bande d'en-tête, relevée sur le prototype et non approchée.** Elle se
   rendait sur « bg » #F7F7FA, à 1,04 de contraste avec la page : la bande ne se
   distinguait pas du fond et l'en-tête flottait au-dessus des lignes. Le
   prototype pose #ECEBF3, un gris violacé qui sépare, et un remplissage de
   9 px sur 12 — le nôtre valait 2 sur 6, quatre fois plus serré, et c'est ce qui
   faisait le plus la différence à l'œil. Le remplissage rendu est le pas dense du
   barème, 6 px, et non ses 9 : les quatre pas sont normatifs et un test les garde.
   Trois pixels séparent les deux, contre dix avant cette correction. Corps, graisse et approche sont les
   siens ; ils s'écartent du jeton « label » de la charte, qui vaut 11,5 px, et
   l'écart est déclaré dans « ECARTS_ADMIS_DU_RAPPORT ». */
.tableau-dense thead th { background: var(--entete-de-table-bg); color: var(--entete-de-table-fg); border-bottom: 0 none; padding: var(--spacing-dense) var(--spacing-base); font-size: 9px; font-weight: 700; letter-spacing: 0.06em; line-height: 1.4; }
/* Le prototype arrondit la bande à 8 px ; les trois rayons du contrat valent 4, 6
   et 9. Le grand est à un pixel du sien, et ajouter un quatrième rayon au contrat
   pour ce pixel coûterait plus que l'écart. */
.tableau-dense thead th:first-child { border-radius: var(--rounded-lg) 0 0 var(--rounded-lg); }
.tableau-dense thead th:last-child { border-radius: 0 var(--rounded-lg) var(--rounded-lg) 0; }
.tableau-dense td { font-variant-numeric: tabular-nums; }
/* Un en-tête de **groupe** de lignes porte la donnée qui identifie le groupe — la
   source —, exactement comme « th[scope=row] » porte celle qui identifie la
   ligne : ni l'un ni l'autre n'est une étiquette de colonne. */
/* **La largeur vient du relevé.** La cellule de groupe porte la provenance du
   jugement — l'état du raccordement, le rapport, la date, l'auteur —, et en
   disposition automatique cette prose lui donnait la moitié de la table : les cinq
   colonnes de valeurs se retrouvaient serrées à droite. Le prototype mesure sa
   première colonne à 293 px sur 1 114, et c'est cette proportion-là qui est
   rendue. La prose se replie ; elle ne pousse plus. */
.tableau-dense th[scope='rowgroup'] { font: inherit; font-weight: 600; letter-spacing: inherit; text-transform: none; color: var(--ink); vertical-align: top; white-space: normal; text-align: left; width: 26%; }
/* **La pastille d'état et la pagination de l'écran du rapport** ont quitté cette
   feuille avec « /rapport/ » (story 1.40), ainsi que l'en-tête et le pied de ses
   cartes, ses définitions, ses formulaires ouverts et ses seuils : aucune surface
   Django ne les rend plus, et la vue Rattachement de la V7 porte ceux de la
   maquette. Une règle sans élément à styler est un dessin que plus personne ne
   relit. */
/* **La vue de maillage et l'inventaire des pages** l'ont quittée à leur tour,
   dans la même story : leurs surfaces Django sont parties à la story 1.31, et
   leurs règles étaient restées — la scène, ses commandes et sa miniature, la
   fiche et la recherche d'une page, le tableau aéré et ses cellules à deux
   étages, la commande neutre, et le texte réservé au lecteur d'écran (« .hors-ecran »).
   Aucun gabarit, aucun script servi avec cette feuille et aucune composition du back
   ne les portait plus ; la V7 porte ceux de la maquette. */
/* **Le texte réservé au lecteur d'écran revient, sous le nom de la maquette** —
   story 1.48, sixième tour de relecture. Le Portefeuille rend une absence comme
   « absence() » de la V7 : le mot court, et la phrase de « CHAMPS_ABSENTS » dans un
   « .sr » que seul le lecteur d'écran lit. La règle est celle d'« interface.css »,
   à la lettre ; « .absence » n'a pas de style dans la maquette, et n'en prend pas
   ici : aucun pixel ne bouge. */
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
#tableau-exemples { table-layout: fixed; }
#tableau-exemples td { overflow: hidden; text-overflow: ellipsis; }
/* **Le bouton de tri porte le texte de l'en-tête : il en prend donc la couleur.**
   Il écrivait « muted-text » en dur ; quand la bande d'en-tête a pris le gris
   violacé du prototype, ce gris-là n'y contrastait plus qu'à 4,40, sous le seuil
   du texte — alors que la couleur du « th », elle, y mesure 5,34. Un enfant qui
   redit la couleur de son parent finit toujours par la redire fausse. */
.tri { border: 0; padding: 0; color: inherit; background: transparent; text-transform: inherit; font: inherit; letter-spacing: inherit; }
.poignee { position: absolute; right: 1px; top: 0; bottom: 0; display: flex; align-items: center; cursor: col-resize; color: var(--ink); touch-action: none; }
.outils-tableau { display: flex; align-items: end; gap: var(--spacing-large); flex-wrap: wrap; }
/* Une étiquette de filtre **nomme** une donnée, comme un en-tête de colonne : elle
   porte donc la même écriture, et non celle du texte courant. */
.outils-tableau label { display: grid; gap: var(--spacing-dense); font-size: var(--label); font-weight: 600; color: var(--muted-text); }
/* Le champ n'hérite pas de l'écriture de son étiquette : celle-ci **nomme** la
   donnée, le champ **porte** la saisie. Sans cette règle, « font: inherit » faisait
   rendre la valeur tapée en capitales de 11,5 px, dans le gris des libellés. */
.outils-tableau input, .outils-tableau select, .outils-tableau textarea { font-size: var(--texte); font-weight: 400; color: var(--ink); }
/* La largeur du champ de recherche vient du relevé : le prototype l'écrit à
   250 px, et c'est ce qui l'empêche de se réduire à la largeur de son contenu
   au milieu de deux sélecteurs courts. */
.outils-tableau input[type='search'] { width: 250px; }
.outils-tableau output { margin-left: auto; padding: var(--spacing-dense); }
.defilement-tableau { overflow-x: auto; }
#configuration-regles-de-type { max-width: 100%; overflow-x: auto; }
.appareil-de-preuve { background: var(--bg); border: 1px solid var(--border); border-radius: var(--rounded-md); padding: var(--spacing-base); }
.appareil-de-preuve summary { color: var(--ink); font-weight: 600; }
.appareil-de-preuve dl { margin: var(--spacing-large) 0; }
.appareil-de-preuve dl > div { display: grid; grid-template-columns: 260px 1fr; padding: var(--spacing-dense) 0; }
dd { margin: 0; }
/* **Une liste de preuve se coupe où il faut** — correctif du défilement horizontal,
   deuxième tour de relecture (13 octobre 2026). Une adresse sur le domaine le plus long
   que l'outil accepte, 253 caractères d'un seul tenant, poussait « /collecte/ » hors de
   la fenêtre de 373 px à 1 728 jusqu'à 1 228 px à 1 024 ; le domaine de la
   configuration enregistrée élargissait sa colonne. La valeur se resserre et se coupe
   n'importe où ; le terme, dans sa colonne fixe de 260 px — ou en bloc, dans les listes de
   la collecte —, se coupe sans se resserrer : sa piste ne descend pas sous sa largeur. Une
   adresse ordinaire ne bouge pas. */
.appareil-de-preuve dt { overflow-wrap: anywhere; }
.appareil-de-preuve dd { min-width: 0; overflow-wrap: anywhere; }
.score-composite { border-top: 1px solid var(--border); padding-top: var(--spacing-base); }
.bascule-lecture { background: var(--surface); border: 1px solid var(--border); border-radius: var(--rounded-md); padding: var(--spacing-base); }
.bascule-lecture fieldset { display: inline-flex; }
#scene-maillage { display: block; max-width: 760px; height: 280px; margin: var(--spacing-base) auto; }
.noeud rect { fill: var(--bg); stroke: var(--ink); rx: var(--rounded-sm); }
.noeud text { fill: var(--ink); font-family: var(--font-body); font-size: 14px; }
.aretes line { stroke: var(--muted-text); stroke-width: 2; }
.metriques-desactivees { color: var(--muted-text); }
.cartouche-annotation { background: var(--bg); color: var(--ink); border: 1px solid var(--border); border-radius: var(--rounded-md); padding: var(--spacing-base); }
.ticket-page { background: var(--surface); border: 1px solid var(--border); border-radius: var(--rounded-lg); padding: var(--spacing-base); }
.ticket-page header { display: flex; align-items: center; gap: var(--spacing-large); }
.ticket-page header > span:last-child { margin-left: auto; }
.alerte { color: var(--ink); border: 1px solid var(--border); border-radius: var(--rounded-md); }
.alerte.ligne-severite { font-size: var(--body); line-height: 1.5; }
.alerte time { margin-left: auto; }
.compteur-alerte { background: var(--surface); display: flex; align-items: center; gap: var(--spacing-base); padding: var(--spacing-base); }
.compteur-alerte .valeur-numerique { font-size: var(--grand-nombre); font-weight: var(--grand-nombre-poids); }
.marqueur-danger { display: inline-block; width: 5px; height: 24px; background: var(--danger); }
#motif-de-l-ecart, #effet-du-remplacement { color: var(--ink); max-width: 78ch; }
.mention { display: block; font-size: var(--label); color: var(--ink); }
/* **Une commande prend la largeur de son libellé, comme dans la maquette** — story
   1.49. La grille du formulaire étirait chaque bouton sur toute la carte : « Lancer
   la collecte » faisait mille pixels de large, là où la maquette ne pose jamais de
   commande pleine plus large que ce qu'elle dit. */
.formulaire { display: grid; gap: var(--spacing-base); }
.formulaire > button { justify-self: start; }
.tableau-dense td.texte-libre a { display: inline-block; }
footer { padding: var(--spacing-large) 0; color: var(--muted-text); }
.connexion { max-width: 460px; margin: var(--spacing-section) auto; }
.connexion form p { display: grid; gap: var(--spacing-dense); }
/* La commande principale. Les propriétés de boîte y sont écrites en toutes
   lettres — et non héritées de la règle des contrôles natifs — parce qu'un lien
   peut en porter le rôle : l'export du rapport est une adresse, pas un envoi de
   formulaire, et il doit se voir comme la commande qu'il est. Ce sont celles de
   « .button.primary » dans la maquette, écart entre enfants compris (« bouton-
   primaire », rejoué sur la commande du raccordement des sources). */
.commande { background: var(--brand); color: var(--surface); border: 1px solid var(--brand); border-radius: 999px; padding: 8px 14px; display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; text-decoration: none; }
/* L'indicateur de focus est un trait de la marque : sur un bouton qui porte
   déjà la marque en fond, il était invisible — contraste 1,00, mesuré par le
   navigateur sur la surface des sources. Le trait passe en dedans et prend la
   couleur du texte du bouton, qui contraste avec son fond par construction. */
.commande:focus-visible, .button.primary:focus-visible { outline-color: var(--surface); outline-offset: -3px; }
/* **Un geste qui passe par un formulaire, rendu comme le lien qu'il remplace** —
   story 1.48. Quand le projet n'est que retenu, rien n'est ouvert : les gestes de
   la carte « Projet actif » passent par le formulaire d'« Ouvrir », et un lien
   vers la V7 y mènerait à « aucun projet ». « display: contents » fait du bouton
   l'élément de la rangée, comme le lien qu'il remplace ; « .lien » rend le
   bouton du détail d'une tuile comme un lien de texte. */
form.geste { display: contents; }
.lien { display: inline; padding: 0; border: 0 none; border-radius: 0; background: transparent; font: inherit; color: var(--brand); text-decoration: underline; text-underline-offset: 3px; }

