/* Composants VAIATA. Toutes les classes sont préfixées `vd-` pour ne jamais entrer en
   collision avec le CSS propre d'un espace. Ce fichier ne définit aucune couleur en dur :
   il ne consomme que les jetons de tokens.css. Voir design-system/README.md. */

/* ---------- Emphase ---------- */
/* UNE BALISE D'EMPHASE PESE UN SEUL POIDS.
   **Arbitrage de Colomban, 4 septembre 2026** : « Le socle fixe l'emphase ».
   Mesure du jour sur les vingt-huit espaces : `<b>` et `<strong>` rendaient TROIS graisses, 30 a
   600, 402 a 700, 8 a 900, et personne ne l'avait decide. Le socle n'en disait rien, donc le
   navigateur appliquait `bolder`, qui est RELATIF au parent et dont la table est en marches : un
   parent a 400 ou 500 donne 700, un parent a 600 ou 700 donne 900. Une balise qui veut dire
   « ceci compte » pesait donc trois poids selon l'endroit ou elle tombait.
   EFFET DE BORD LEVE : tant que l'emphase dependait de son parent, rabattre une graisse de 500 a
   600 faisait sauter ses descendants d'une marche entiere. Constate le 04/09 sur relation-stav,
   ou « 747 € » et « 31/08/2026 » sont passes de 700 a 900. */
b, strong { font-weight: var(--graisse-forte); }

/* ---------- Bandeau produit ---------- */
/* LE BANDEAU EST CLAIR, ET SES COULEURS SONT DES JETONS.
   **Arbitrage de Colomban, 03/09/2026** : « le bandeau clair de VAIATA Relation est meilleur ».
   Mesuré le jour même, le parc portait TROIS traitements pour la même barre : navy `#1A1A2E` sur
   les six espaces en `vd-topbar`, gris clair `#EFF0F5` chez Relation, blanc chez Syclef. Trois
   dessins pour un composant, ce que l'article AO-4 proscrit.
   Le clair de Relation n'est pas une couleur écrite à la main : c'est `var(--surface)`, un jeton
   nommé que sa charte reteinte à `#EFF0F5`. Le bandeau le prend donc, et chaque produit garde la
   main par sa charte sans qu'aucun espace n'écrive de couleur.
   Le trait de tête reste : il porte l'accent du produit, et c'est la seule signature de couleur
   du bandeau une fois son fond éclairci. */
.vd-topbar {
  display: flex;
  align-items: center;
  gap: var(--espace-m);
  background: var(--surface);
  color: var(--texte);
  padding: 12px 26px;
  border-top: 3px solid var(--accent); /* trait de tête à la couleur du produit, jamais le rouge de marque */
  border-bottom: 1px solid var(--bordure);
  font-family: var(--police-interface);
}
/* LE BANDEAU PORTE TROIS ZONES, ET IL N'Y A PLUS DE SECONDE BARRE.
   Arbitrage de Colomban, 26/08/2026 : « la barre d'application n'a pas lieu d'exister ». Deux
   barres empilées, l'une pour la plateforme et l'autre pour l'application, c'est une de trop :
   elles portent les mêmes choses, la recherche finit dans l'une, l'utilisateur dans l'autre, et
   l'écran perd deux fois sa hauteur de tête.

   La bonne idée de l'ancienne barre est conservée : TROIS COLONNES et non une ligne, parce
   qu'une ligne ne centre pas, elle place, et le centre s'y déplace au gré de ce que pèsent les
   côtés. Seule une colonne centrale de largeur propre tient la recherche au milieu quel que soit
   le nombre d'avatars ou la longueur du nom du client.

   La grille ne s'active QUE si le bandeau porte une zone centrale : un bandeau qui n'en a pas
   reste une ligne, et aucun espace existant ne bouge le jour où cette règle arrive. */
/* UNE SEULE LIGNE, ET LA RECHERCHE Y PREND LA PLACE.
   Arbitrage de Colomban, 26/08/2026 : « un bandeau qui reste sur une seule ligne, les avatars à
   droite, le changement de langue entre la recherche et les avatars, et la recherche qui prend
   une place plus généreuse ».

   CE QUI CHANGE, ET POURQUOI LA GRILLE CENTRÉE EST ABANDONNÉE. La version du matin tenait la
   recherche dans une colonne centrale de 460 px, pour qu'elle reste au milieu quel que soit le
   poids des côtés. Le centrage était l'objectif, et il coûtait deux choses : une largeur figée
   qui ne profitait jamais d'un grand écran, et deux gouttières vides de part et d'autre. Le
   centre géométrique n'est pas une valeur en soi ; la place donnée à ce qu'on utilise le plus,
   si. La recherche prend donc tout ce qui reste, jusqu'à une borne de lisibilité. */
/* LES DEUX COLONNES LATÉRALES PÈSENT PAREIL, c'est ce qui rend le centre vrai. Sans base
   égale, la colonne centrale se décale de la différence entre l'identité et les avatars. */
/* DEUX REPARTITIONS POUR UN SEUL BANDEAU, depuis le 4 septembre 2026.
   **Arbitrage de Colomban** : « Le socle apprend la grille. » Le socle portait DEUX bandeaux
   concurrents, `vd-topbar` en boite flexible et `vd-appbar` en grille a trois colonnes, six
   espaces chacun. Le second etait deprecie depuis le 26/08 sur sa parole, « la barre
   d'application n'a pas lieu d'exister », mais le renommage seul CASSAIT : mesure du 04/09 sur
   relation-stav, le bandeau passait de 73 a 106 px. Une grille a centre borne et une boite
   flexible ne sont pas deux ecritures d'un meme dessin, ce sont deux mecaniques de repartition.
   Le socle porte donc les deux, sous UN nom : `vd-topbar--grille` rend la repartition que
   `vd-appbar` servait, et les six espaces migrent par simple renommage. */
/* LA GRILLE EST UNE RÉPARTITION D’ÉCRAN LARGE, ET ELLE S’ARRÊTE OÙ LE BANDEAU CHANGE DE FORME.
   Mesuré le 05/09/2026 à 390 px, sur les six espaces en grille : le bandeau rendait des colonnes
   de 0, 346 et 0 px, la zone de fin passait en tête par l’ordre du mobile et prenait la première
   piste, la zone centrale tombait dans la troisième, réduite à zéro, et débordait de 119 px sur
   relation-stav, 27 sur Syclef, 201 sur Sextant. Les deux espaces en boîte flexible ne
   débordaient pas. Le bandeau du doigt n’a donc qu’UNE mécanique, la boîte flexible du bloc
   ci-dessous : la grille ne vaut qu’à partir de la rupture où les trois zones tiennent côte à
   côte, et c’est la même rupture que celle qui range le bandeau au doigt, 719/720. */
@media (min-width: 720px) {
  .vd-topbar--grille {
    display: grid;
    grid-template-columns: 1fr minmax(0, var(--vd-topbar-centre, 460px)) 1fr;
  }
  .vd-topbar--grille > .vd-topbar__debut,
  .vd-topbar--grille > .vd-topbar__centre,
  .vd-topbar--grille > .vd-topbar__fin { flex: none; }
}

.vd-topbar__debut { display: flex; align-items: center; gap: var(--espace-m); min-inline-size: 0; flex: 1 1 0; }
/* LA RECHERCHE SE CENTRE DANS LE BANDEAU.
   **Arbitrage de Colomban, 03/09/2026** : « je veux LE MÊME bandeau que Relation ! Je veux le même
   bandeau partout. » Mesuré le jour même : Relation et Syclef centrent leur lanceur, 827 contre un
   milieu de bandeau à 827, et 836 contre 836. Le bandeau du socle le posait en boîte flexible
   bornée à 720 px, ce qui le poussait à droite : 1 065 pour un milieu à 858, soit **207 px de
   décalage**. Trois bandeaux, deux placements.
   Le motif écrit le 26/08/2026 pour abandonner le centrage tenait à une largeur FIGÉE de 460 px
   qui ne profitait jamais d'un grand écran. Le centrage revient donc SANS la largeur figée : la
   colonne centrale prend ce qu'elle peut jusqu'à sa borne de lisibilité, et reste centrée parce
   que les deux colonnes latérales pèsent pareil. */
.vd-topbar__centre {
  display: flex; align-items: center; justify-content: center; min-inline-size: 0;
  flex: 1 1 auto;
  /* Une borne, parce qu'un champ de 1 400 px de large ne se lit pas mieux qu'un champ de 720 :
     l'œil parcourt la ligne, et au-delà il ne sait plus où revenir. */
  max-inline-size: var(--vd-topbar-recherche, 720px);
  margin-inline: auto;
}
.vd-topbar__centre > * { inline-size: 100%; }
.vd-topbar__fin { display: flex; align-items: center; justify-content: flex-end; gap: var(--espace-s); min-inline-size: 0; flex: 1 1 0; }
/* Le sélecteur de langue, ENTRE la recherche et les avatars. */
.vd-topbar__langue { display: flex; align-items: center; flex: none; }
/* LE SÉLECTEUR SOMBRE DOIT DÉCLARER QU'IL EST SOMBRE, sinon sa liste déroulante est illisible.
   Constat de Colomban, 26/08/2026, capture à l'appui : « en blanc sur blanc ça ne se voit pas
   ici, les choix ». La liste d'un `<select>` natif est peinte par le système, qui hérite la
   COULEUR du contrôle mais pas son fond : texte blanc sur fond blanc. `color-scheme: dark`
   demande au système de peindre la liste en sombre, et les options reprennent explicitement un
   fond, pour les moteurs qui ignorent la propriété. C'est le même piège que le calendrier natif :
   ce panneau n'est pas à nous, on ne peut que lui dire dans quel monde il s'ouvre. */
.vd-topbar__langue .vd-champ__saisie, .vd-topbar__langue select {
  inline-size: auto; min-inline-size: 0;
  background: rgb(255 255 255 / .1); border-color: rgb(255 255 255 / .18); color: #fff;
  color-scheme: dark;
}
.vd-topbar__langue select option {
  background: var(--navy); color: #fff;
}
.vd-topbar__compte > summary { border-radius: 999px; inline-size: auto; block-size: auto; }
.vd-topbar__compte .vd-carte__menu__liste { min-inline-size: 230px; }
/* LES PRODUITS VAIATA, EN SECTION DU MENU DU COMPTE. Demande de Colomban du 21/10/2026, « cela
   favorise l'usage de notre écosystème applicatif » ; recommandation (b) de T-057, posée le
   23/10/2026 : une variante du menu, pas un composant à neuf points, tant que VAIATA ID n'ouvre pas
   de session d'un produit à l'autre. `produits.js` la remplit depuis les manifestes ; vide, elle ne
   prend aucune place. Le produit courant se dit par « Vous y êtes » et n'est pas un lien. */
.vd-produits { display: grid; gap: var(--espace-xxxs); margin-block-start: var(--espace-xxxs); padding-block-start: 4px; border-block-start: 1px solid var(--bordure); }
.vd-produits:empty { display: none; }
.vd-produits__titre { margin: 0; padding: var(--espace-xs) var(--espace-s) 0; font-size: var(--texte-mention); letter-spacing: .08em; text-transform: uppercase; color: var(--texte-attenue); }
.vd-produits__entree { display: grid; gap: var(--espace-xxxs); align-content: center; min-block-size: var(--controle-hauteur);
  padding: var(--espace-xs) var(--espace-s); border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  color: var(--texte); text-decoration: none; }
a.vd-produits__entree:hover { background: var(--panel-2, rgb(0 0 0 / .06)); }
.vd-produits__entree[aria-current] { background: var(--panel-2, rgb(0 0 0 / .06)); }
.vd-produits__entree .vd-wordmark { font-size: var(--texte-interface); }
.vd-produits__ligne { font-size: var(--texte-legende); color: var(--texte-attenue); }
/* La recherche du bandeau est claire sur fond sombre : elle ne reprend pas les surfaces de page. */
/* LE CHAMP SUIT LE BANDEAU. Il portait un voile blanc translucide et une encre blanche, qui ne
   valaient que sur un fond sombre : sur le bandeau clair du 03/09/2026, il devenait un rectangle
   blanc sur blanc, sans bord lisible. Il prend donc la surface haute et l'encre du texte, comme
   tout champ posé sur une surface claire. */
.vd-topbar .vd-recherche__saisie {
  background: var(--surface-haute);
  border-color: var(--bordure);
  color: var(--texte);
}
.vd-topbar .vd-recherche__saisie::placeholder { color: var(--texte-attenue); }
.vd-topbar .vd-recherche__icone { color: var(--texte-attenue); }
/* Les personnes connectées, en fin de bandeau. Un espace sans compte n'en pose aucune. */
.vd-topbar .vd-avatar { border: 2px solid var(--surface); }

/* Sur un écran étroit, la colonne centrale prend ce qui reste et la recherche se range au bord
   opposé à la marque : à cette largeur, un centrage la placerait sous le pouce de personne. */
/* LA FORME MOBILE, CELLE DE PALANTÍRI. Demande de Colomban, 26/08/2026 : « les paramètres cachés
   derrière l'avatar en haut à gauche, et la barre de recherche qui prend tout le reste de la
   place ». Sur un téléphone, la seule chose qu'on vient faire dans un bandeau, c'est chercher :
   tout le reste passe derrière un seul point d'entrée, et ce point d'entrée est déjà celui qui
   dit qui est connecté.

   Le compte passe en TÊTE par l'ordre de la boîte flexible, pas par un second balisage : un même
   élément, une seule fois dans la page. La marque et la langue, elles, ne sont pas masquées :
   `topbar.js` les DÉPLACE dans le menu du compte, pour qu'elles restent atteignables. Les
   masquer les aurait fait disparaître, ce qui n'est pas la même chose que les ranger. */
@media (max-width: 719px) {
  .vd-topbar { gap: var(--espace-s); padding-inline: var(--espace-m); }
  .vd-topbar__fin { order: -1; margin-inline-start: 0; flex: none; }
  /* LE COMPTE PASSÉ EN TÊTE OUVRE SON MENU VERS LA DROITE. Il s'ouvrait encore vers la gauche,
     depuis le bord gauche de l'écran : 230 px de menu, presque tout hors champ (heures-repos,
     21/10/2026). */
  .vd-topbar__compte .vd-carte__menu__liste { inset-inline-end: auto; inset-inline-start: 0; }
  /* AU DOIGT, LES ZONES LATÉRALES PRENNENT LEUR CONTENU, ET LA RECHERCHE PREND LE RESTE.
     La part égale des deux côtés, flex 1 1 0, ne sert qu’à centrer la recherche sur un grand
     écran. À 390 px elle ne centre rien, la borne du centre étant levée juste en dessous, et
     elle dimensionne le début du bandeau sans regarder ce qu’il porte. Mesuré le 05/09/2026 :
     107 px pour 146 de contenu sur les quatre Relation, le wordmark mobile coupé de 39 px ;
     9 px pour 68 chez Sextant, une étiquette pliée sur trois lignes et un bandeau de 94 px au
     lieu de 68. Au contenu, aucun des huit bandeaux du parc ne déborde, et la recherche gagne
     ce que le début lui rend : 200 px chez Relation, 308 chez Intelligence. */
  .vd-topbar__debut { flex: 0 1 auto; }
  .vd-topbar__centre { max-inline-size: none; }
  /* LA GRILLE N'A PAS DE PETITE LARGEUR, ELLE REDEVIENT LA BOITE FLEXIBLE DU SOCLE.
     Mesure au doigt le 05/09/2026, sur les quatre Relation et sur Syclef, les cinq espaces qui
     portent `vd-topbar--grille` : a 390 px la page debordait de 119 px chez Relation et de 27 chez
     Syclef, le coupable etant l'icone du lanceur, poussee hors ecran. La cause : les regles de
     cette fenetre supposent une boite flexible, `order`, `flex: none`, `max-inline-size`, et la
     grille les ignore. Ses trois colonnes `1fr minmax(0, 460px) 1fr` gardent chacune leur contenu
     minimal, et la somme depasse l'ecran. Intelligence, en boite flexible, sortait vert sur le
     meme bandeau. Sous 720 px la grille cede donc la place a la boite flexible : les deux
     mecaniques ne sont justifiees qu'au large, ou elles centrent differemment. */
  .vd-topbar--grille { display: flex; grid-template-columns: none; }
  /* ET SES ENFANTS REPRENNENT LEUR SOUPLESSE : au large, la grille les fige avec `flex: none`,
     regle ecrite hors de toute fenetre, donc encore active ici. Sans ce retour, Sextant, dont le
     debut de bandeau porte une etiquette longue, debordait encore de 108 px a 390 px apres le
     premier repli : la boite etait flexible, ses parts ne l'etaient pas. La fin garde
     `flex: none`, comme le bandeau sans grille. */
  .vd-topbar--grille > .vd-topbar__debut,
  .vd-topbar--grille > .vd-topbar__centre { flex: 1 1 0; min-inline-size: 0; }
  .vd-topbar__debut:empty { display: none; }
  /* Dans le menu, la marque et la langue reprennent une mise en page de menu, pas de bandeau. */
  .vd-topbar__compte .vd-carte__menu__liste .vd-wordmark { display: block; padding: var(--espace-s) 10px; font-size: var(--texte-interface); color: var(--texte); }
  .vd-topbar__compte .vd-carte__menu__liste .vd-produits__entree .vd-wordmark { padding: 0; }
  .vd-topbar__compte .vd-carte__menu__liste .vd-topbar__langue { padding: var(--espace-xs) 10px; }
  /* Les autres personnes connectées rejoignent le menu, elles aussi. Sur un téléphone, savoir
     qui d'autre est là est une information de contexte, pas la raison d'ouvrir l'écran : elle
     ne prend pas la place de la recherche. */
  .vd-topbar__compte .vd-carte__menu__liste .vd-avatar-groupe { display: flex; padding: var(--espace-s) 10px; }
  .vd-topbar__compte .vd-carte__menu__liste .vd-topbar__langue select,
  .vd-topbar__compte .vd-carte__menu__liste .vd-topbar__langue .vd-champ__saisie {
    inline-size: 100%; background: var(--surface-haute); border-color: var(--bordure); color: var(--texte);
  }
}

.vd-wordmark { font-weight: var(--graisse-forte); letter-spacing: .01em; font-size: var(--texte-titre); }

/* ---------- LE POINT DE LA MARQUE, UNE SEULE DÉFINITION ----------
   « VAIATA Produit » est toujours suivi d'un point rond à la couleur du produit. C'est la
   signature de la maison, la même que le VD. du logo.

   Les trois valeurs viennent du canon de marque, `skills/linkedin-vaiata` : cercle de
   0.18em, `margin-left` 0.06em, `margin-bottom` 0.12em. C'est ce dernier qui pose le point
   à la bonne hauteur : il remonte la boîte au-dessus de la ligne de base, de sorte que le
   rond s'aligne sur le bas des lettres au lieu de tomber sous elles.

   Constat de Colomban, 22/08/2026, sur trois captures : le point n'était pas au même endroit
   d'un produit à l'autre. Le recensement a trouvé SIX implémentations, dont aucune ne portait
   le `margin-bottom` canonique. Elles avaient chacune tenté un réglage optique à la main,
   `vertical-align: .02em` ici, rien là, `vertical-align: middle` sur un point de 0.3em
   ailleurs. Un invariant recopié six fois n'est plus un invariant.

   `.vd-point` sert quand le point est un élément à part, `::after` quand il suit un texte. */
.vd-point,
.vd-wordmark::after {
  content: '';
  display: inline-block;
  width: .18em; height: .18em;
  border-radius: 50%;
  background: var(--accent);
  margin-inline-start: .06em;
  vertical-align: baseline;
}
/* La marque du client se tient en haut à GAUCHE, juste après le wordmark VAIATA : le
   co-branding se lit d'un coup d'œil avec le nom du produit, pas à l'autre bout du bandeau.
   Décision de Colomban, 30/07/2026. Le build la pose, aucun espace ne l'écrit à la main. */
.vd-topbar__client { display: flex; align-items: center; gap: var(--espace-s); font-size: var(--texte-interface); color: var(--texte-attenue); }
/* Le logo du client prend `--marque-hauteur`, defini plus bas avec le monogramme. Cette ligne
   portait un second `height: 24px`, plus specifique que la regle de classe, et c'est lui qui
   gagnait : le jeton etait pose et n'arrivait jamais. Deux definitions d'une meme chose, dont
   une invisible a un temoin qui cherche `.vd-client__logo`. Retiree le 22/08/2026. */
.vd-topbar__client img { width: auto; display: block; }

/* ---------- Surfaces ---------- */
.vd-card {
  background: var(--surface);
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-moyen));
  padding: var(--espace-l);
  color: var(--texte);
}
.vd-card__title { margin: 0 0 var(--espace-s); font-size: var(--texte-titre); font-weight: var(--graisse-forte); }
.vd-card__note { margin: 0; color: var(--texte-attenue); font-size: var(--texte-interface); }
/* LA CARTE À LISERÉ DE TÊTE, signature de la maison sur un bloc encadré, la même que celle du
   bandeau. Elle n'était pas au socle, si bien que Syclef l'a réécrite et que VAIATA ID l'a
   réécrite aussi : deux copies pour une signature de marque.
   ELLE PORTE UNE COULEUR, TOUJOURS. Constat de Colomban, 27/08/2026 : « cette forme de tuiles
   arrondies en bas et avec un liseré en haut, mais dont la couleur ne s'affiche jamais ». Chez
   Syclef le trait valait --bordure, c'est-à-dire la couleur du cadre : la forme annonçait un état
   qui ne venait jamais. Une forme qui promet une information et ne la donne pas est pire qu'une
   forme neutre, parce qu'on la cherche.
   Par défaut l'accent du produit ; --vd-lisere prend une couleur sémantique quand le bloc en dit
   une, et c'est le seul endroit où cette valeur se pose. */
.vd-card--lisere {
  border-block-start: 3px solid var(--vd-lisere, var(--accent));
  border-start-start-radius: 0; border-start-end-radius: 0;
}

/* ---------- Indicateurs ---------- */
.vd-kpi { display: grid; gap: var(--espace-xxxs); }
.vd-kpi__value { font-family: var(--police-donnee); font-variant-numeric: tabular-nums; font-size: var(--texte-affiche); font-weight: var(--graisse-forte); color: var(--texte); }
/* L'INTITULÉ RÉSERVE LA HAUTEUR DU MENU DE TUILE, qu'il en porte un ou non. Mesuré le 27/08/2026
   sur la rangée de tête de Relation : la tuile du chiffre d'affaires portait un menu, sa ligne
   d'intitulé passait de 17 à 32 px, et TOUT son contenu descendait de quinze pixels. Les trois
   tuiles étaient parfaitement alignées entre elles, et leurs chiffres ne l'étaient pas, ce qui
   est le pire des deux cas : l'oeil voit le décalage sans voir d'où il vient.
   La réserve vaut pour toutes, sinon deux tuiles voisines ne s'alignent que par hasard. */
.vd-kpi__label {
  font-size: var(--texte-legende); text-transform: uppercase; letter-spacing: .08em;
  color: var(--texte-attenue);
  display: flex; align-items: center; gap: var(--espace-s);
  min-block-size: var(--controle-hauteur-compacte);
}

/* ---------- États. Couleur jamais seule : toujours doublée d'une icône et d'un libellé. ---------- */
.vd-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* LA ZONE DE GARDE TIENT COMPTE DE LA CAPSULE. Constat de Colomban, 27/08/2026 : « pourquoi
     les pastilles n'ont pas de zone de garde ». Dix pixels suffisent sur un rectangle, pas sur
     une capsule : l'arrondi de 999 rentre dans le dégagement, si bien que le premier caractère
     se retrouve à cinq ou six pixels du trait au lieu de dix. Treize rend au regard l'écart que
     dix rendait sur un bord droit, et c'est un pas de l'échelle d'espacement.
     LE DÉGAGEMENT VERTICAL NE BOUGE PAS : la pastille vaut exactement la cible tactile de 24 px,
     et l'augmenter la ferait sortir de l'échelle des hauteurs. */
  padding: 3px var(--espace-m);
  border-radius: 999px;
  font-size: var(--texte-legende);
  font-weight: var(--graisse-appuyee);
  /* UNE SEULE FORME DE PASTILLE, LA PLEINE. Arbitrage de Colomban, 27/08/2026 : « on garde que
     les pleines ». Le parc en comptait 331 contre 50 cerclées, et les cerclées ne vivaient que
     dans deux espaces, sur des cartes et jamais dans du texte courant. Deux formes coûtaient un
     choix à chaque emploi, et c'est ce choix qui a produit les divergences relevées ce jour-là.
     LE FOND SE DÉRIVE DE L'ENCRE, il ne se déclare pas. Sept espaces posaient chacun un jeton de
     fond par état, vingt-quatre valeurs à tenir sur l'ensemble ; un mélange à 12 % rend le même
     résultat depuis la seule couleur d'état, et suit une charte client qui la change.
     LE TRAIT RESTE, TRANSPARENT : il tient la boîte à la même taille que si elle était cerclée,
     ce qui évite qu'un état de survol ou de sélection fasse sauter la ligne d'un pixel. */
  border: 1px solid transparent;
  background: color-mix(in srgb, currentColor 12%, var(--surface-haute));
  /* LE NOWRAP APPARTIENT AU CONTEXTE DENSE, PAS AU COMPOSANT. Je l'avais posé sur la pastille
     elle-même le matin du 27/08/2026, parce que « Devis envoyé » tombait sur deux lignes dans une
     colonne de tableau. Mesuré le soir à 390 px : une pastille qui porte une phrase, et Syclef en
     porte de cinq cents pixels, ne peut plus se replier et fait déborder la PAGE de 146 px.
     La pastille se replie donc comme tout texte, et ne dépasse jamais son contenant ; c'est dans
     la cellule de tableau, où trente lignes se ressemblent et où une hauteur qui saute se voit,
     que le nowrap a sa raison. La règle suit l'usage au lieu de suivre le composant. */
  max-inline-size: 100%;
}
/* L'ENCRE D'UNE PASTILLE PLEINE S'ASSOMBRIT DE 15 %, même règle que --accent-encre. Mesuré le
   27/08/2026, juste après l'arbitrage « on garde que les pleines » : le fond à 12 % de l'encre
   rapproche les deux, et le succès tombait à 4,39 pour 1, l'alerte à 4,17, sous le seuil AA de
   4,5. Descendre le fond à 6 % rendait bien 4,51 partout, mais un fond de 6 % ne se voit plus,
   ce qui revenait à défaire l'arbitrage par la bande.
   L'encre assombrie garde le fond franc ET repasse le seuil. Le fond se dérive de cette encre-là,
   donc il fonce lui aussi, et c'est mesuré, pas déduit. */
.vd-pill--succes { color: color-mix(in srgb, var(--succes) 85%, #000); }
.vd-pill--danger { color: color-mix(in srgb, var(--danger) 85%, #000); }
.vd-pill--alerte { color: color-mix(in srgb, var(--alerte) 85%, #000); }
.vd-pill--information { color: color-mix(in srgb, var(--information) 85%, #000); }
.vd-pill--accent { color: var(--accent-encre, var(--accent)); }
/* L'ICÔNE D'UNE PASTILLE OCCUPE UN CARRÉ FIXE, quel que soit le glyphe. Constat de Colomban le
   28/08/2026 sur Syclef, « des capsules sans zone de garde » : la garde était pourtant symétrique
   à 14 px des deux côtés. Le défaut était ailleurs, et il se mesure. Un point médian occupe 2,9 px
   d'avance, un triangle 10,3 : le libellé démarrait donc à 23 px du bord gauche contre 14 à
   droite, et deux pastilles voisines n'alignaient pas leur texte. La capsule penchait.
   UN CARRÉ D'UNE HAUTEUR DE TEXTE remet les deux d'aplomb : le glyphe se centre dedans, la garde
   redevient égale, et les libellés d'une rangée démarrent au même endroit. */
/* ---------- Deux règles qui valent pour TOUT ce que le socle pose ---------- */

/* LE MOUVEMENT SE COUPE UNE FOIS POUR TOUTES. Il était traité composant par composant, là où
   quelqu'un y avait pensé : neuf blocs, et le chevron du rail continuait de tourner malgré la
   préférence de réduction, mesuré le 28/08/2026 sur les quatre espaces Relation. Une règle qui
   vaut pour tout le socle s'écrit une fois (§16.7), et les blocs particuliers qui font PLUS que
   couper, comme le squelette qui baisse son opacité, restent en place.
   `0.01ms` plutôt que `none` : une transition de durée nulle n'émet pas d'événement de fin, et
   du code qui l'attend reste bloqué. Le mouvement disparaît, la mécanique tient. */
/* ELLE NE COUPE QUE CE QUI BOUGE, PAS CE QUI CHANGE DE TON. Première écriture trop large, prise
   en défaut dans l'heure : elle figeait `transition-duration` sur TOUTES les propriétés, donc
   aussi la couleur, et le banc de galerie a rendu « changer de produit ne change pas la couleur »
   alors que la couleur changeait bel et bien. La préférence de réduction vise le MOUVEMENT, pas
   la couleur : quelqu'un qui la règle demande que rien ne se déplace, pas que tout devienne
   instantané. On nomme donc les propriétés qui déplacent. */
@media (prefers-reduced-motion: reduce) {
  [class^='vd-'], [class*=' vd-'], [class^='vd-']::before, [class*=' vd-']::before,
  [class^='vd-']::after, [class*=' vd-']::after {
    transition-property: color, background-color, border-color, outline-color, opacity, fill, stroke !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* LE FOCUS SE VOIT SUR TOUT CE QUI S'ATTEINT AU CLAVIER. Mesuré le 28/08/2026 : neuf contrôles de
   Relation n'avaient aucun état de focus, tous des boutons sans classe du socle. Le socle donnait
   son contour composant par composant, donc tout ce qu'un produit ajoute en était privé. Un
   contour par défaut ne coûte rien à ce qui en a déjà un, sa règle étant plus spécifique. */
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.vd-pill__icone {
  font-style: normal;
  flex: none;
  inline-size: 1em; block-size: 1em;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1;
}
/* DANS UNE CELLULE DE TABLEAU, ELLE NE SE CASSE PAS : c'est là que la hauteur de rangée compte. */
td .vd-pill, th .vd-pill { white-space: nowrap; }

/* ---------- Les balises dont le navigateur décide la police ou la taille ---------- */
/* UN CONTRÔLE DE FORMULAIRE N'HÉRITE PAS DE LA POLICE, le moteur lui impose la sienne. Mesuré le
   27/08/2026 sur VAIATA ID : douze lignes de liste rendues en Arial parce qu'elles sont des
   boutons, et le socle ne posait la police que composant par composant, là où quelqu'un y avait
   pensé. Le même défaut avait produit l'infobulle en Arial le matin même. Une règle qui vaut pour
   toutes les surfaces s'écrit une fois. */
button, input, select, textarea, optgroup { font-family: inherit; }
/* UN TITRE SANS RÈGLE PREND LA TAILLE DU NAVIGATEUR : 32 px pour un h1, 24 pour un h2, 18,72 pour
   un h3, trois valeurs qui ne sont sur aucune échelle. Mesuré le 27/08/2026 sur Syclef, dont
   l'entête de page rendait onze titres à 32 px. Les règles locales gardent la main, leur
   spécificité est plus forte ; ce qui suit ne fait que remplacer un défaut de moteur par un
   défaut de maison. */
h1 { font-size: var(--texte-affiche); }
h2 { font-size: var(--texte-titre); }
h3, h4, h5, h6 { font-size: var(--texte-corps); }

/* ---------- Les balises dont le navigateur décide la taille ---------- */
/* `small`, `sup` et `sub` n'ont pas de taille à eux : le moteur applique un facteur à la taille
   héritée, ce qui produit 10, 10,8 ou 13,3 px selon le parent. Trois tailles que personne n'a
   choisies, mesurées le 27/08/2026 sur Relation et sur VAIATA Facturation. Une balise dont la
   taille dépend de son parent n'a pas de classe typographique : le socle lui en donne une.
   L'EXPOSANT GARDE SA POSITION, il ne perd que sa taille improvisée. */
small { font-size: var(--texte-legende); }
sup, sub { font-size: var(--texte-mention); line-height: 0; position: relative; vertical-align: baseline; }
sup { inset-block-start: -0.4em; }
sub { inset-block-start: 0.2em; }

/* ---------- Le lien dans une phrase ---------- */
/* LE SOCLE N'AVAIT AUCUN LIEN, et c'est ce trou qui explique la question de Colomban du
   27/08/2026 : « quelle est la logique dans le choix des composants ? ». Il n'y en avait pas, pour
   ce cas précis. Faute de lien, Syclef rendait un nom de société, au milieu d'une phrase, avec un
   bouton discret : quarante pixels de haut, cerclé, dix-huit de dégagement, pour trois mots dans
   une note. Relation, de son côté, écrivait son propre `.link`.
   UN LIEN N'EST PAS UNE ACTION. Le bouton engage, il se voit de loin et se compte ; le lien mène
   ailleurs et se lit dans le fil du texte. Deux natures, deux formes.
   LE SOULIGNÉ RESTE. Une couleur seule ne dit pas un lien, ni pour un daltonien, ni à
   l'impression : c'est la même règle que sur les états. Il est décalé pour ne pas couper les
   jambages. */
.vd-lien {
  color: var(--accent-encre, var(--accent));
  font-weight: var(--graisse-appuyee);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  cursor: pointer;
  background: none; border: 0; padding: 0; font-size: inherit; font-family: inherit;
}
.vd-lien:hover { text-decoration-thickness: 2px; }
.vd-lien:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--carbone-rayon-petit); }
.vd-lien:disabled, .vd-lien[aria-disabled='true'] {
  cursor: not-allowed; color: var(--texte-attenue); text-decoration: none;
}
/* LE POINT DE TÊTE EST UNE VARIANTE À PART, séparée du fond. Les deux allaient ensemble tant que
   je n'avais vu que Relation ; Intelligence chantiers navals les dissocie sur cinq mille
   pastilles, un point à la couleur de la catégorie sur un fond neutre, et un point de catégorie
   sur une pastille cerclée. Une seule variante pour deux propriétés indépendantes forçait à
   choisir entre les deux, donc à recopier le composant une troisième fois.
   Il est porté par le composant et non par le balisage : le libellé dit déjà l'information, et
   l'aide vocale n'a rien à lire dans un point. */
.vd-pill--point::before {
  content: ''; inline-size: 6px; block-size: 6px; border-radius: 999px;
  background: var(--vd-pastille-point, currentColor); flex: none;
}
/* LA PASTILLE NEUTRE, l'état sans état. Sans elle, un espace repeint la sienne en gris. */
.vd-pill--neutre { color: var(--texte-attenue); }

/* ---------- Tableaux de données ---------- */
/* UN TABLEAU DEFILE DANS SA BOITE, JAMAIS EN POUSSANT LA PAGE. Mesure du 26/08/2026 a 420 px :
   le tableau debordait de 65 px et emportait la page entiere avec lui, si bien que TOUT le
   contenu se decalait horizontalement. Une enveloppe qui defile isole le probleme la ou il est.
   Elle est obligatoire des que le tableau porte plus de trois colonnes. */
.vd-table-enveloppe { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.vd-table { width: 100%; border-collapse: collapse; font-size: var(--texte-corps); color: var(--texte); }
.vd-table th {
  text-align: start;
  font-size: var(--texte-mention);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--texte-attenue);
  padding: var(--espace-s) var(--espace-m);
  border-bottom: 1px solid var(--bordure);
  font-weight: var(--graisse-appuyee);
}
.vd-table td { padding: var(--espace-m); border-bottom: 1px solid var(--bordure); }
/* UN EN-TÊTE DE COLONNE CHIFFRÉE S'ALIGNE SUR SES CHIFFRES. Constat de Colomban, 26/08/2026 :
   « les passagers ne sont pas alignés avec leur label ». Les nombres se rangent en fin de
   cellule, pour que les unités, les dizaines et les centaines tombent l'une sous l'autre ; le
   libellé, lui, restait en début. Un titre qui ne surmonte pas sa colonne oblige l'œil à faire
   le lien deux fois. La règle vaut pour le `th` ET pour le bouton de tri qu'il contient, sans
   quoi c'est le bouton qui reprend le début de ligne. */
.vd-table th.vd-num { text-align: end; }
.vd-table th.vd-num .vd-table__tri { margin-inline-start: auto; }
.vd-table td.vd-num { font-variant-numeric: tabular-nums; text-align: end; }
/* A9, arbitré le 02/09/2026 : la cellule numérique NE prend PAS la police de la donnée. Quatre
   produits sur quatre avaient écrit la règle sans elle (72 cellules rien que chez Relation) :
   quand tout le parc s'écarte d'un détail du socle, c'est le détail qu'il faut regarder.
   `tabular-nums` suffit à l'alignement ; la police reste celle du produit. */
.vd-table tbody tr:hover { background: var(--surface-haute); }

/* ---------- La palette de recherche transverse ----------

   LOT O. Demande de Colomban, 25/08/2026 : « aller chercher dans toute l'app et ordonner les
   résultats par catégorie, dont la première est contextuelle à l'affichage de la page ». Mesure
   du parc : 35 champs de recherche, chacun le sien, chacun cherchant dans sa seule page.

   ELLE SE POSE AU TIERS SUPÉRIEUR, PAS AU CENTRE. Une liste qui s'allonge vers le bas depuis un
   centre vertical déplace son propre champ à chaque frappe. Ancrée en haut, elle ne bouge pas :
   ce qu'on lit reste où on l'a vu. */
.vd-palette {
  position: fixed; inset: 0; z-index: var(--plan-flottant);
  display: flex; justify-content: center; align-items: flex-start;
  padding: max(8vh, var(--espace-xl)) var(--espace-m) var(--espace-m);
  background: rgb(10 12 20 / .42);
}
.vd-palette[hidden] { display: none; }
.vd-palette__boite {
  inline-size: min(100%, 640px); max-block-size: 70vh;
  display: flex; flex-direction: column;
  background: var(--surface-haute);
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-moyen));
  box-shadow: var(--ombre-flottante);
  overflow: hidden;
}
.vd-palette__champ { padding: var(--espace-s); border-block-end: 1px solid var(--bordure); }
.vd-palette__resultats { overflow-y: auto; padding: var(--espace-xs); }
.vd-palette__categorie {
  margin: var(--espace-s) 0 2px; padding-inline: var(--espace-s);
  font-size: var(--texte-mention); text-transform: uppercase; letter-spacing: .08em;
  color: var(--texte-attenue);
}
.vd-palette__option {
  box-sizing: border-box;
  display: flex; align-items: baseline; gap: var(--espace-s);
  min-block-size: var(--controle-hauteur);
  padding-block: 0; padding-inline: var(--espace-s);
  border-radius: var(--courant-rayon-petit);
  color: var(--texte); text-decoration: none;
}
/* LE SURVOL ET LA SÉLECTION AU CLAVIER SE DESSINENT PAREIL. Deux dessins pour un même état
   apprennent au lecteur qu'il y a deux états, alors qu'il n'y en a qu'un. */
.vd-palette__option:hover,
.vd-palette__option[aria-selected='true'] { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.vd-palette__titre { font-size: var(--texte-interface); font-weight: var(--graisse-appuyee); }
.vd-palette__detail { font-size: var(--texte-legende); color: var(--texte-attenue); }
/* Le repli se DIT : sans cela, on croit avoir trouvé ce qu'on cherchait. */
.vd-palette__repli {
  margin: var(--espace-xs) 0; padding: var(--espace-s) 10px;
  font-size: var(--texte-legende); color: var(--alerte);
  background: color-mix(in srgb, var(--alerte) 10%, transparent);
  border-radius: var(--courant-rayon-petit);
}
.vd-palette__vide { margin: var(--espace-m) 10px; }
.vd-palette__pied {
  margin: 0; padding: var(--espace-s) var(--espace-m);
  border-block-start: 1px solid var(--bordure);
  font-size: var(--texte-legende); color: var(--texte-attenue);
}
.vd-palette__pied kbd {
  font-family: var(--police-donnee); font-size: var(--texte-mention);
  padding: 1px var(--espace-xs); margin-inline-end: var(--espace-xxs);
  border: 1px solid var(--bordure); border-radius: var(--carbone-rayon-petit); background: var(--surface);
}

/* ---------- Le bandeau de message en page ----------

   LOT M. Mesure du parc, 26/08/2026 : la bannière de Relation, la note de Facturation et de Sign,
   le bloc d'alerte de Syclef et l'erreur de VAIATA ID. Cinq dessins, cinq feuilles, un seul objet.
   Aucun ne porte de rôle : quatre `role="alert"` et six `role="status"` dans tout le parc, tandis
   que la bannière de Relation, qui annonce le décalage d'une date et la renotification de trois
   prestataires, n'en porte aucun. Elle apparaît par un changement de classe, en silence.

   UN TON N'EST PAS UNE COULEUR, C'EST UNE COULEUR PLUS UNE ICÔNE PLUS UN MOT. Règle déjà écrite
   dans les jetons sémantiques, « toujours doublée d'une icône et d'un libellé, jamais la couleur
   seule », et jamais tenue par une surface. Un daltonien lit le mot ; un écran de passerelle en
   plein soleil aussi.

   LE RÔLE SE DÉDUIT DU TON, ET C'EST `message.js` QUI L'ÉCRIT. Succès et information annoncent
   sans interrompre, `role="status"` ; alerte et danger interrompent la lecture en cours,
   `role="alert"`. Le laisser à l'appelant, c'est le voir oublié dans neuf cas sur dix, ce que la
   mesure ci-dessus établit. */
.vd-message {
  display: flex; align-items: flex-start; gap: var(--espace-s);
  padding: var(--espace-m);
  border-radius: var(--accent-rayon, var(--courant-rayon-moyen));
  /* AUCUN LISERÉ DE COULEUR. Arbitrage de Colomban, 26/08/2026 : « le liseré signature Claude
     Code est interdit ». Il portait un trait de 3 px en tête, repris de la tuile où il est la
     signature VAIATA. Sur un bandeau de message il ne signe rien, il imite : quatre bandeaux
     empilés, chacun barré d'un trait de couleur vive, sont le dessin qu'on reconnaît ailleurs.
     Le ton se lit sur le fond teinté, l'icône et le mot, ce qui suffisait déjà, et une bordure
     fine à la couleur du ton tient la boîte sans crier. */
  border: 1px solid color-mix(in srgb, var(--ton, var(--information)) 34%, transparent);
  background: color-mix(in srgb, var(--ton, var(--information)) 8%, var(--surface-haute));
  color: var(--texte);
  font-size: var(--texte-interface);
}
.vd-message__icone { flex: none; color: var(--ton, var(--information)); margin-block-start: 1px; }
.vd-message__corps { flex: 1 1 auto; min-inline-size: 0; }
/* LE MOT DU TON, et il n'est pas décoratif : c'est lui qui porte l'information quand la couleur
   ne passe pas. Il n'est jamais masqué à la synthèse vocale. */
.vd-message__ton { font-weight: var(--graisse-appuyee); }
.vd-message__ton::after { content: ' · '; font-weight: var(--graisse-courante); color: var(--texte-attenue); }
/* LA CROIX EST CENTRÉE, ET ELLE NE GRANDIT PAS LE BANDEAU.
   Deux constats de Colomban le 26/08/2026, dans cet ordre : « aligner le bouton X en le centrant
   verticalement », puis, une fois centré, « le texte du succès est non aligné maintenant ». Le
   second est la conséquence du premier mal traité : un bouton de 40 px dans une boîte flexible
   fait 58 px de haut à un bandeau dont les voisins en font 46, et son texte, aligné en haut,
   ne tombe plus à la même place que le leur.
   La fermeture sort donc du FLUX : elle se pose en absolu, centrée sur la hauteur réelle du
   bandeau, quelle que soit celle-ci. Tous les bandeaux retrouvent la même hauteur, dictée par
   leur texte seul, et la croix est centrée sur chacun. Le bandeau réserve sa place par un
   dégagement de fin, conditionné à sa présence. */
/* ---------- LE BOUTON D'ICÔNE, ET SES DEUX TAILLES ----------
   Arbitrage de Colomban, 20/09/2026, sur mesure rendue : Palantíri portait
   **sept tailles de cible** pour un bouton qui ne contient qu'une icône, de 28
   à 48 px, sur 56 boutons dans 42 classes. Sa décision : deux tailles nommées,
   40 px par défaut, 32 px en contexte dense, « et ajoute cette dualité standard
   / dense dans le design system ».

   POURQUOI DEUX ET PAS UNE. Sous 40 px, la cible passe sous ce que le pouce
   demande sur un téléphone. Au-dessus, dans une liste dense (conversations,
   tâches), la ligne respire moins et l'écran porte moins d'éléments. Les deux
   usages existent, une taille unique sacrifierait l'un des deux.

   LES HAUTEURS NE SONT PAS RÉINVENTÉES : ce sont les jetons de contrôle
   existants, --controle-hauteur (40) et --controle-hauteur-compacte (32). Un
   bouton d'icône est un contrôle comme un autre, il n'a pas son échelle propre.

   L'ICÔNE NE GRANDIT PAS AVEC LA CIBLE. C'est la zone de clic qui change, pas
   le dessin : 20 px dans les deux cas, sans quoi la version dense donnerait une
   icône illisible. */
.vd-bouton-icone {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none;
  inline-size: var(--controle-hauteur); block-size: var(--controle-hauteur);
  padding: 0; border: 0; border-radius: var(--rayon-s, 8px);
  background: transparent; color: inherit; cursor: pointer;
}
.vd-bouton-icone:hover { background: var(--fond-survol, rgba(0, 0, 0, 0.05)); }
.vd-bouton-icone:disabled { opacity: 0.45; cursor: not-allowed; }
/* Contexte dense : listes, lignes de tableau, barres d'outils d'un fil. */
.vd-bouton-icone--dense {
  inline-size: var(--controle-hauteur-compacte); block-size: var(--controle-hauteur-compacte);
}
/* Le dessin garde sa taille : seule la cible change. */
.vd-bouton-icone .vd-icone { inline-size: 20px; block-size: 20px; }

.vd-message { position: relative; }
.vd-message__fermer {
  position: absolute; inset-inline-end: var(--espace-s);
  inset-block-start: 50%; translate: 0 -50%;
  inline-size: var(--controle-hauteur-compacte); block-size: var(--controle-hauteur-compacte);
}
.vd-message:has(.vd-message__fermer) { padding-inline-end: calc(var(--controle-hauteur-compacte) + var(--espace-m)); }
.vd-message--succes { --ton: var(--succes); }
.vd-message--information { --ton: var(--information); }
.vd-message--alerte { --ton: var(--alerte); }
.vd-message--danger { --ton: var(--danger); }
.vd-messages { display: flex; flex-direction: column; gap: var(--espace-s); }

/* ---------- La barre de navigation basse, et la feuille du bas ----------

   LOT U. Demande de Colomban, 26/08/2026 : « le menu en bas de l'app » de VAIATA ID et de
   Palantíri. Forme reprise du `MobileNav` de Palantíri, qui sert depuis longtemps.

   POURQUOI EN BAS ET PAS EN HAUT. Le pouce d'une main qui tient un téléphone atteint le bas de
   l'écran, pas le haut : une navigation en tête oblige à changer de prise. Le bandeau du haut
   garde donc ce qui se LIT, l'identité et la recherche, et le bas prend ce qui se TOUCHE.

   CINQ ENTRÉES AU PLUS, ET LA CINQUIÈME EST « PLUS ». Au-delà, les cibles descendent sous ce que
   le doigt sait viser : cinq fois 48 px tiennent dans 390, six n'y tiennent pas. Ce qui déborde
   va dans la feuille du bas, qui s'ouvre depuis la même barre.

   LA MARGE DE SÉCURITÉ DU TÉLÉPHONE EST PRISE EN COMPTE. Sans elle, la barre passe sous la barre
   de gestes des téléphones sans bouton, et la dernière entrée devient intouchable. */
/* LA BOÎTE SE MESURE ENTIÈRE, ET ELLE NE DÉPASSE JAMAIS SON HÔTE. En usage réel elle est fixée
   aux deux bords, donc la question ne se pose pas ; posée dans le flux, comme dans la galerie,
   son rembourrage s'ajoutait à sa largeur et la dernière entrée sortait de onze pixels. Mesuré à
   390 px le 27/08/2026. */
.vd-barre-basse {
  box-sizing: border-box; max-inline-size: 100%;
  position: fixed; inset-block-end: 0; inset-inline: 0; z-index: var(--plan-fixe);
  display: none; align-items: center; justify-content: space-around;
  block-size: calc(56px + env(safe-area-inset-bottom, 0px));
  padding-block-end: env(safe-area-inset-bottom, 0px);
  padding-inline: var(--espace-xs);
  background: var(--surface-haute);
  border-block-start: 1px solid var(--bordure);
}
@media (max-width: 899px) { .vd-barre-basse { display: flex; } }
/* CE QUE LA BARRE RECOUVRE, LE CORPS LE REND. Elle est en position fixe : sans degagement, le bas
   de la page vit sous elle et personne ne peut l en sortir, le defilement etant deja au bout.
   L attribut est pose par le module qui DERIVE la barre, jamais par une base : un espace qui ecrit
   la sienne, comme VAIATA ID, gere son degagement lui-meme et ne doit pas en recevoir deux. */
/* CE QUI FLOTTE EN BAS S ECARTE DE LA BARRE. Mesure du 07/09/2026 a 390 px : le bouton de retours
   vit a 18 px du bas, la barre en fait 56, et les deux se chevauchaient sur les trois espaces ou
   elle venait d etre posee. VAIATA ID ne souffrait pas parce qu il avait ecrit la regle chez lui,
   en notant qu elle appartenait au socle : elle y est. */
@media (max-width: 899px) {
  [data-vd-barre-basse='presente'] .vd-retours-bouton { bottom: calc(80px + env(safe-area-inset-bottom, 0px)); }
  [data-vd-barre-basse='presente'] .vd-retours-panneau { bottom: calc(130px + env(safe-area-inset-bottom, 0px)); }
}
@media (max-width: 899px) {
  [data-vd-barre-basse-posee='oui'] body {
    padding-block-end: calc(56px + env(safe-area-inset-bottom, 0px));
  }
}
.vd-barre-basse__entree {
  display: flex; flex-direction: column; align-items: center; gap: var(--espace-xxxs);
  /* LES ENTRÉES SE PARTAGENT LA LARGEUR, ELLES NE LA RÉCLAMENT PAS.
     Mesuré à 390 px le 27/08/2026 : chaque entrée exigeait la largeur de son libellé, 58 px, et
     quatre entrées plus l'action débordaient de 12 px. Une largeur minimale par entrée fait
     dépendre la barre de la LONGUEUR DES MOTS, ce qui casse au premier libellé un peu long, dans
     une traduction ou sur un écran plus étroit.
     Elles prennent donc des parts égales de ce qui reste après l'action, et le libellé se coupe
     avant que la barre ne casse. La cible tactile est garantie par la HAUTEUR, le degré ample,
     et par le fait que toute la part se touche : c'est la hauteur qui manque au doigt. */
  flex: 1 1 0; min-inline-size: 0; overflow: hidden;
  min-block-size: var(--controle-hauteur-ample);
  justify-content: center;
  padding-inline: var(--espace-xs);
  border: 0; border-radius: var(--courant-rayon-petit);
  background: none; cursor: pointer; text-decoration: none;
  color: var(--texte-attenue); font: inherit; font-size: var(--texte-mention);
}
.vd-barre-basse__entree:hover { color: var(--texte); }
/* L'ENTRÉE COURANTE SE LIT SUR L'ATTRIBUT, comme le rail et comme le tri : le dessin se déduit,
   il ne se déclare pas. */
.vd-barre-basse__entree[aria-current='page'] {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.vd-barre-basse__libelle { white-space: nowrap; max-inline-size: 100%; overflow: hidden; text-overflow: ellipsis; }
/* L'ACTION PRINCIPALE, AU CENTRE ET EN RELIEF. Elle n'est pas une rubrique : elle ne se marque
   jamais comme courante, elle ouvre. */
.vd-barre-basse__action {
  /* PLUS GROSSE QUE L'ÉCHELLE, ET C'EST VOULU. Arbitrage de Colomban, 27/08/2026 : « le bouton
     Plus doit être plus gros ». L'échelle des contrôles règle ce qui se pose EN LIGNE ; une
     action flottante ne se pose sur aucune ligne, elle domine la barre. 56 px est la taille des
     actions flottantes partout ailleurs, et c'est la seule valeur de la maison qui sorte de
     l'échelle : elle a donc son jeton, pour qu'on la reconnaisse comme une exception nommée et
     non comme un oubli. */
  inline-size: var(--action-flottante); block-size: var(--action-flottante); flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 16px; cursor: pointer;
  background: var(--accent); color: #fff;
  box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 38%, transparent);
}

/* ---------- LE COMPTEUR ----------

   PROPOSÉ LE 29/08/2026, PAS DÉPLOYÉ. Directive de Colomban le même jour : « reprends et enrichis
   le socle VAIATA, format app mobile, à la charte Colomban pour construire Palantíri ». AO-1
   s'applique : il est en galerie, il n'entre dans aucun espace avant son arbitrage.

   POURQUOI IL MANQUAIT, ET LA MESURE QUI L'ÉTABLIT. Le socle porte déjà une `vd-pill`, mais c'est
   un ÉTAT, et un état se dit avec un MOT : « En retard », « Payé ». Un compteur ne dit pas un
   état, il dit une QUANTITÉ, et il n'a pas de mot. Les confondre est la famille 6 d'AO-2, un signe
   pour deux sens. Le seul compteur du socle vivait donc dans `.vd-rail__pastille`, prisonnier du
   rail, alors que la barre basse en réclame un et n'avait AUCUN emplacement pour le porter.
   Mesuré le 29/08/2026 dans Palantíri : QUATRE-VINGT-DOUZE classes distinctes y font ce travail,
   et un seul de ses écrans compte de quatre manières, pastille bleue, pastille grise, pastille
   bleue encore, puis un nombre entre parenthèses dans du texte.

   IL EMPORTE AVEC LUI LE CORRECTIF DU 28/08. « Le 2 n'est pas centré dans le point de
   notification », constat de Colomban. La cause est la hauteur de ligne héritée, 16,5 px dans une
   boîte de 18 : le centrage porte alors sur la LIGNE, dont les blancs ne sont pas égaux dans cette
   police, et non sur la boîte du glyphe. À hauteur de ligne unitaire, les deux boîtes se centrent
   l'une sur l'autre. Le correctif vit dans le rail depuis ce jour-là ; le promouvoir ici est ce
   qui le rend disponible partout, au lieu de le laisser à l'endroit où il a été trouvé. */
/* LE TON SOBRE EST LE DÉFAUT, L'ALERTE EST LA VARIANTE, ET C'EST L'INVERSE DE CE QUE J'AVAIS
   ÉCRIT. Premier jet du 29/08/2026 : le fond valait `var(--alerte)`, donc tout compteur criait
   par défaut. La mesure au rendu l'a montré, l'alerte du socle résolvant en ambre, `rgb(154 103
   0)`, à 4,87 pour 1 sous du blanc. Le seuil AA est tenu, mais le défaut de conception était
   ailleurs : DÉNOMBRER EST LE CAS COURANT, ALERTER EST L'EXCEPTION. Les captures de Palantíri du
   28/08 le montrent en creux, ses trois compteurs de barre basse sont du même rouge, y compris
   celui de l'onglet où l'on se trouve déjà, si bien qu'aucun ne dit plus rien. Un signe qui vaut
   partout ne signale nulle part. Le cas sûr devient donc le défaut, et l'alerte se demande. */
.vd-compteur {
  box-sizing: border-box;
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  min-inline-size: 18px; block-size: 18px; padding-inline: var(--espace-xs);
  border-radius: 999px;
  /* L'ENCRE EST PLEINE, PAS ATTÉNUÉE, ET C'EST UNE MESURE QUI LE DIT. Avec `--texte-attenue` sur
     ce lavis, le rendu donnait 4,6 pour 1 : le seuil AA de 4,5 est tenu, mais une charte cliente
     qui déplace sa surface d'un rien le fait passer dessous, sans que rien ne le signale. Un
     compteur est un CHIFFRE, il se lit ou il ne sert à rien, et la marge se prend avant qu'un
     client ne la consomme. */
  background: color-mix(in srgb, var(--texte) 12%, transparent); color: var(--texte);
  font-family: var(--police-donnee); font-size: var(--texte-mention); font-weight: var(--graisse-appuyee);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.vd-compteur[hidden] { display: none; }
/* CE QUI APPELLE UNE ACTION, ET RIEN D'AUTRE : un retard, une chose à traiter. Jamais un simple
   dénombrement. */
.vd-compteur--alerte { background: var(--alerte); color: #fff; }

/* SUR UNE ENTRÉE DE LA BARRE BASSE, il se pose au coin de l'icône, jamais dans le flux du
   libellé : la barre partage sa largeur en parts égales, et un compteur en ligne rognerait le mot
   au lieu de s'ajouter à lui. Il sort donc du flux, et l'entrée devient son repère. */
.vd-barre-basse__entree { position: relative; }
.vd-barre-basse__entree > .vd-compteur {
  position: absolute; inset-block-start: 2px;
  inset-inline-start: 50%; margin-inline-start: var(--espace-xxxs);
}

/* LA FEUILLE DU BAS. Elle sort du bas parce que c'est là qu'est le pouce, et elle s'arrête à
   60 % de la hauteur : une feuille qui prend tout l'écran est une page, et on ne sait plus d'où
   l'on vient. */
.vd-feuille { position: fixed; inset: 0; z-index: var(--plan-flottant); display: flex; align-items: flex-end; }
.vd-feuille[hidden] { display: none; }
.vd-feuille__voile { position: absolute; inset: 0; border: 0; padding: 0; background: rgb(10 12 20 / .42); }
.vd-feuille__panneau {
  position: relative; inline-size: 100%; max-block-size: 60vh; overflow-y: auto;
  background: var(--surface-haute);
  border-start-start-radius: 16px;
  border-start-end-radius: 16px;
  padding: var(--espace-l);
  padding-block-end: calc(var(--espace-l) + env(safe-area-inset-bottom, 0px));
}
.vd-feuille__tete { display: flex; align-items: center; justify-content: space-between; margin-block-end: var(--espace-m); }
.vd-feuille__titre { margin: 0; font-size: var(--texte-titre); }
.vd-feuille__grille { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: var(--espace-s); }

/* ---------- La barre de progression ----------

   DEMANDE DE COLOMBAN DU 25/08/2026, restée sans réponse jusqu'au 26 : « il faut une barre de
   progression ». Je l'avais notée et jamais livrée.

   ELLE PORTE SON CHIFFRE. Une barre sans nombre demande d'estimer une longueur du regard, ce que
   personne ne fait juste. Et elle porte les attributs de sa valeur : une progression qui ne se
   dit qu'en largeur n'existe pas pour qui ne voit pas l'écran. */
.vd-progression { display: flex; align-items: center; gap: var(--espace-s); }
.vd-progression__piste {
  flex: 1 1 auto; block-size: 8px; border-radius: 999px;
  background: var(--surface); overflow: hidden;
}
.vd-progression__part { display: block; block-size: 100%; border-radius: 999px; background: var(--accent); }
.vd-progression__valeur { flex: none; font-size: var(--texte-legende); color: var(--texte-attenue); font-variant-numeric: tabular-nums; }
/* Une progression dont on ignore la durée ne ment pas là-dessus : elle défile sans jamais
   prétendre approcher de la fin. */
/* ELLE TRAVERSE, ELLE NE FAIT PAS L'ALLER-RETOUR. Constat de Colomban, 27/08/2026 : « la barre
   mobile devrait disparaître d'un côté pour réapparaître de l'autre ». Un aller-retour se lit
   comme une hésitation, et l'œil suit le demi-tour au lieu d'oublier la barre ; une traversée se
   lit comme un flux qui continue, ce qui est justement ce qu'on veut dire. */
.vd-progression--inconnue .vd-progression__part {
  inline-size: 34%; animation: vd-progression-traverse 1.4s linear infinite;
}
@keyframes vd-progression-traverse {
  from { translate: -110% 0; } to { translate: 320% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .vd-progression--inconnue .vd-progression__part { animation: none; inline-size: 100%; opacity: .5; }
}

/* ---------- L'interrupteur et le bouton radio ----------

   DEUX CLASSIQUES QUI MANQUAIENT. L'interrupteur bascule un état MAINTENANT, sans validation :
   c'est ce qui le distingue de la case à cocher, qui attend un envoi. Employer l'un pour l'autre
   fait attendre une validation qui ne viendra pas, ou déclenche un effet qu'on croyait différé.

   LE BOUTON RADIO CHOISIT UN PARMI PLUSIEURS, et il n'existe jamais seul : un radio unique est
   une case à cocher qu'on ne peut plus décocher. */
.vd-interrupteur { display: inline-flex; align-items: center; gap: var(--espace-s); cursor: pointer; font-size: var(--texte-corps); }
.vd-interrupteur input {
  appearance: none; margin: 0; flex: none; cursor: pointer;
  inline-size: 40px; block-size: 24px; border-radius: 999px;
  background: var(--bordure); position: relative;
  transition: background .14s ease-out;
}
.vd-interrupteur input::after {
  content: ""; position: absolute; inset-block-start: 3px; inset-inline-start: 3px;
  inline-size: 18px; block-size: 18px; border-radius: 999px; background: #fff;
  transition: inset-inline-start .14s ease-out;
}
.vd-interrupteur input:checked { background: var(--accent); }
.vd-interrupteur input:checked::after { inset-inline-start: 19px; }
.vd-interrupteur input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .vd-interrupteur input, .vd-interrupteur input::after { transition: none; } }
/* UN ESPACE DE GARDE ENTRE DEUX CHOIX. Constat de Colomban, 27/08/2026 : « il devrait y avoir un
   espace de garde entre courriel et l'interrupteur suivant ». Deux interrupteurs collés se lisent
   comme un seul réglage à deux lignes, et la cible du premier touche celle du second. Le
   conteneur est le MÊME que celui des boutons radio : c'est la même intention, une liste de choix
   posés les uns sous les autres, et une règle qui vaut pour deux surfaces s'écrit une fois. */
.vd-radios, .vd-interrupteurs { display: flex; flex-direction: column; gap: var(--espace-m); }
/* DEUX GROUPES DE CHAMPS SE SÉPARENT DU GRAND PAS. Constat de Colomban, 27/08/2026 : « il devrait
   y avoir une zone de garde entre Afficher les escales annulées et Périodicité de l'export ».
   L'écart entre deux CHOIX d'un même groupe et l'écart entre deux GROUPES ne peuvent pas être le
   même : s'ils le sont, rien ne dit où finit une question et où commence la suivante. Le petit
   pas sépare les choix, le grand sépare les groupes. */
.vd-groupe-champs { display: flex; flex-direction: column; gap: var(--espace-l); }
.vd-groupe-champs fieldset { border: 0; padding: 0; margin: 0; }
.vd-radio { display: inline-flex; align-items: center; gap: var(--espace-s); cursor: pointer; font-size: var(--texte-corps); }
.vd-radio input[type="radio"] { flex: none; inline-size: 18px; block-size: 18px; margin: 0; accent-color: var(--accent); cursor: pointer; }
.vd-radio input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Le pas à pas, et les segments ----------

   DEUX CLASSIQUES DE PLUS. Le pas à pas dit où l'on en est dans une suite d'étapes obligée :
   sans lui, un formulaire long se lit comme un formulaire infini, et on abandonne au deuxième
   écran faute de savoir combien il en reste.

   L'ÉTAPE FRANCHIE PORTE UNE COCHE, l'étape courante porte son numéro et l'attribut qui le dit.
   Les étapes à venir restent lisibles : les griser jusqu'à l'illisible cache justement ce qui
   rassure, c'est-à-dire le nombre.

   LES SEGMENTS SONT LA MÊME CHOSE QUE LE PLATEAU DE PASTILLES DU MOBILE, promu à toutes les
   largeurs : un choix unique parmi deux ou trois, fondu en un seul objet. Au-delà de trois, ce
   sont des pastilles ou une liste déroulante, pas des segments. */
/* Même chose ici, et pour la même raison : le numéro vit dans la puce, pas dans un marqueur de
   liste. Constat de Colomban, 27/08/2026 : « il ne me semble pas nécessaire de remettre des
   numéros en corps de texte puisqu'ils sont dans les cercles ». */
.vd-pasapas {
  display: flex; align-items: flex-start; gap: var(--espace-s);
  list-style: none; margin: 0; padding: 0;
}
.vd-pasapas__etape { display: flex; align-items: center; gap: var(--espace-s); flex: 1 1 0; min-inline-size: 0; }
.vd-pasapas__puce {
  flex: none; inline-size: var(--controle-hauteur-compacte); block-size: var(--controle-hauteur-compacte);
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; border: 1px solid var(--bordure);
  background: var(--surface-haute); color: var(--texte-attenue);
  font-size: var(--texte-legende); font-weight: var(--graisse-forte); font-variant-numeric: tabular-nums;
}
.vd-pasapas__libelle { font-size: var(--texte-legende); color: var(--texte-attenue); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vd-pasapas__etape--franchie .vd-pasapas__puce { background: var(--succes); border-color: var(--succes); color: #fff; }
.vd-pasapas__etape[aria-current="step"] .vd-pasapas__puce { background: var(--accent); border-color: var(--accent); color: #fff; }
.vd-pasapas__etape[aria-current="step"] .vd-pasapas__libelle { color: var(--texte); font-weight: var(--graisse-appuyee); }
.vd-pasapas__filet { flex: 1 1 auto; block-size: 1px; background: var(--bordure); margin-block-start: var(--espace-m); }
@media (max-width: 620px) {
  /* Au doigt, seule l'étape courante porte son libellé : cinq libellés sur 390 px se coupent tous. */
  .vd-pasapas__etape:not([aria-current="step"]) .vd-pasapas__libelle { display: none; }
}

/* ---------- La cible étendue ---------- */
/* PROPOSÉ LE 28/08/2026, PAS ENCORE ARBITRÉ. Écrit et montré en galerie pour que Colomban le voie,
   posé dans aucun espace tant qu'il n'a pas tranché (AO-1).
   LE PROBLÈME. Certains éléments interactifs ont une taille qui EST une donnée. Les huit blocs
   d'occupation de Sextant valent 133 × 16 px parce que seize pixels valent seize minutes ; le
   ruban d'un calendrier vaut 4 px de haut, un point d'événement 5. Les porter au seuil tactile de
   24 px falsifierait ce qu'ils disent, et les exempter laisserait une cible que le doigt rate.
   LA RÉPONSE. On sépare la cible de la représentation : l'élément garde sa taille vraie, et une
   zone de touche invisible l'étend au seuil, centrée sur lui. La donnée reste juste, le doigt
   atteint. Rien ne se voit, et c'est le but.
   PREMIÈRE VERSION FAUSSE, CORRIGÉE AVANT D'ÊTRE PROPOSÉE. Elle posait la zone en `z-index: 3`,
   ce qui la range DERRIÈRE le fond de son propre parent : mesuré, un pointeur posé six pixels sous
   la barre ne touchait rien du tout. Le composant annonçait une cible qu'il ne donnait pas, ce qui
   est exactement la panne qu'il prétend corriger (§16.3).
   ELLE NE MANGE PAS SES VOISINS : quand l'élément atteint déjà le seuil, le calcul rend zéro et la
   zone se confond avec lui. */
/* LA POSITION SE POSE SANS SPÉCIFICITÉ, ET C'EST LA CONDITION POUR QUE LE COMPOSANT SOIT POSABLE.
   Constat du 29/08/2026, au moment de le déployer sur Sextant : ses barres de planning sont en
   position ABSOLUE dans une grille horaire, et une règle qui leur imposerait `relative` détruirait
   le plan entier. Un élément déjà positionné est déjà son propre bloc conteneur : il n'a besoin de
   rien. `:where()` rend une spécificité nulle, donc toute déclaration de l'élément l'emporte, et
   le composant ne sert qu'aux éléments qui n'ont pas d'avis. Compter sur l'ordre des feuilles
   aurait marché aujourd'hui et cassé au premier déplacement d'un import. */
:where(.vd-cible-etendue) { position: relative; }
.vd-cible-etendue::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  /* LE SIGNE COMPTE : un retrait NÉGATIF étend vers l'extérieur. Première écriture inversée, la
     zone rendait donc zéro et ne dépassait de rien. Quand la hauteur atteint le seuil, le calcul
     rend un positif et le min le ramène à zéro : la zone se confond alors avec l'élément. */
  inset-block: calc(min(0px, (100% - var(--vd-cible-tactile, 24px)) / 2));
}

/* ---------- La colonne de choix ---------- */
/* UNE LISTE VERTICALE DE CHOIX, arbitrée par Colomban le 28/08/2026 : « Ok pour le composant de
   liste verticale, produits en un cohérent avec le reste ».
   POURQUOI ELLE MANQUAIT. Le socle avait deux rails, celui de navigation et celui de facettes, et
   le groupe de segments pour un choix EN LIGNE. Rien pour un choix EN COLONNE, si bien que le banc
   des composites appelait « rail de navigation » le sélecteur de jours de Sextant : une colonne de
   boutons par la forme, un sélecteur par la fonction. Poser le rail dessus aurait été le bon nom
   sur la mauvaise chose.
   CE QU'ELLE EST. Le pendant vertical de `.vd-segments` : mêmes rôles, même état courant lu depuis
   `aria-current`, un défilement quand la liste est plus longue que sa place. Ses entrées peuvent
   être des `.vd-card`, c'est le cas chez Sextant, ou du texte simple.
   ELLE BASCULE EN BANDE AU DOIGT. Sous 760 px une colonne mange l'écran, mesuré à 73 % sur
   Sextant : elle passe en défilement horizontal, et la règle est au socle pour que personne n'ait
   à la redécouvrir. */
.vd-choix {
  display: grid; gap: var(--espace-s);
  max-block-size: var(--vd-choix-hauteur, 70vh);
  overflow-y: auto;
  padding-inline-end: var(--espace-xxxs);
}
/* L'ENTRÉE COURANTE SE LIT DE L'ATTRIBUT, jamais d'une classe : une classe que le lecteur d'écran
   ignore décrit un état à l'oeil seul. Même règle que le rail. */
.vd-choix > [aria-current='true'], .vd-choix > [aria-current='page'] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
/* LE SEUIL EST CELUI DE L ECHELLE, PAS UN REGLAGE. Arbitrage de Colomban du 29/08/2026 :
   « ramene a 3 seuils et fais cela bien avec rigueur et controles ». Ce 760 etait le SEUL emploi
   d une quatrieme valeur dans tout le socle, a quarante pixels du degre tablette, et le socle
   interdit deja les degres d echelle qui ne servent qu une fois, sur les rayons comme sur les
   hauteurs de controle. La bande horizontale est un traitement COMPACT : elle appartient donc au
   degre tablette, 720, ecrit ici en max-width a la valeur moins un. Entre 720 et 760 la liste
   redevient la grille verticale, qui ne deborde pas horizontalement par construction. */
@media (max-width: 719px) {
  .vd-choix {
    display: flex; overflow-x: auto; overflow-y: visible;
    max-block-size: none; scrollbar-width: none;
  }
  .vd-choix::-webkit-scrollbar { display: none; }
  .vd-choix > * { flex: none; min-inline-size: var(--vd-choix-largeur, 100px); }
}

/* LA BASCULE EST UNE COMMANDE DE BARRE D'OUTILS, ELLE S'ALIGNE SUR L'ÉCHELLE DES COMMANDES.
   Constat de Colomban, 02/09/2026 : la bascule Tableau/Carte de VAIATA Intelligence « n'est pas à
   la charte » à côté de celles des espaces Relation. Le balisage était pourtant conforme, et le
   banc d'échelle vert.

   MESURE, sur `dist/` servi en HTTP : dans la barre d'outils, les deux boutons d'icône font
   40 px, de 174 à 214, et le groupe de segments 38, de 175 à 213. Trois commandes de même rang,
   côte à côte, sur deux hauteurs et deux lignes de base. C'est la cinquième famille de l'article
   AO-2, la trame.

   POURQUOI LE BANC NE LE VOYAIT PAS. Il mesure les CONTRÔLES, donc les `part`, qui font 32 et
   sont dans l'échelle. Le 38 est celui du GROUPE, 32 plus deux fois trois pixels de dégagement,
   et un groupe n'est pas un contrôle. Un banc vert ne dit pas qu'un écran est bon.

   POURQUOI CE N'EST PAS UN RÉGLAGE MAIS UNE FAUTE D'ÉCHELLE. 38 n'est ni 32, ni 40, ni 48 : c'est
   un quatrième degré, qui ne sert qu'une fois, et le socle se l'interdit déjà explicitement pour
   les rayons comme pour les hauteurs de contrôle. Le groupe prend donc la hauteur de commande, et
   la part ce qui reste une fois le dégagement retiré.

   POURQUOI PERSONNE D'AUTRE NE BOUGE. Mesuré avant d'écrire : les six autres groupes de segments
   du parc, trois chez Relation, un chez Ticket, un chez Sextant, sont TOUS invisibles au premier
   écran, posés sur des onglets ou des panneaux non ouverts. La seule bascule rendue du parc est
   celle qui a motivé ce constat. */
.vd-segments {
  display: inline-flex; gap: 0; padding: var(--espace-xxs);
  box-sizing: border-box;
  min-block-size: var(--controle-hauteur);
  background: var(--surface); border-radius: 999px;
}
.vd-segments__part {
  box-sizing: border-box;
  min-block-size: calc(var(--controle-hauteur) - 6px);
  display: inline-flex; align-items: center; justify-content: center;
  padding-block: 0; padding-inline: var(--espace-m);
  border: 0; border-radius: 999px; background: none; cursor: pointer;
  font: inherit; font-size: var(--texte-legende); color: var(--texte-attenue);
}
.vd-segments__part[aria-pressed="true"], .vd-segments__part[aria-selected="true"] {
  background: var(--surface-haute); color: var(--texte); font-weight: var(--graisse-appuyee);
  box-shadow: var(--ombre-posee);
}
.vd-segments__part:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Les étiquettes multiples, et la cloche ----------

   L'ÉTIQUETTE MULTIPLE : une saisie qui produit des jetons plutôt qu'un texte. Chaque jeton se
   retire seul, et le champ reste au bout, jamais avant : on ajoute à la fin de ce qu'on a déjà.

   LA CLOCHE PORTE UN NOMBRE, pas un point. Un point dit qu'il se passe quelque chose sans dire
   combien, et c'est le même défaut que la pastille du rail, déjà corrigé le 26/08. Au-delà de
   neuf, elle dit « 9+ » : le nombre exact n'apprend plus rien et la pastille s'allonge. */
.vd-etiquettes {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--espace-xs);
  box-sizing: border-box; min-block-size: var(--controle-hauteur);
  padding: 4px var(--espace-s);
  border: 1px solid var(--bordure); border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  background: var(--surface-haute);
}
.vd-etiquettes:focus-within { outline: 2px solid var(--accent); outline-offset: 1px; }
.vd-etiquettes__saisie { flex: 1 1 8ch; min-inline-size: 8ch; border: 0; background: none; font: inherit; font-size: var(--texte-interface); color: var(--texte); }
.vd-etiquettes__saisie:focus { outline: none; }
.vd-etiquette {
  display: inline-flex; align-items: center; gap: 4px;
  padding-block: var(--espace-xxxs); padding-inline: var(--espace-s) 4px;
  border-radius: 999px; background: var(--surface); color: var(--texte);
  font-size: var(--texte-legende);
}
.vd-etiquette__retirer {
  /* 24 px, le seuil tactile : c'est une cible, même minuscule, et elle se touche au doigt. */
  inline-size: var(--vd-cible, 24px); block-size: var(--vd-cible, 24px); flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 999px; background: none; cursor: pointer; color: var(--texte-attenue);
}
.vd-etiquette__retirer:hover { color: var(--danger); background: var(--surface-haute); }

.vd-cloche { position: relative; }
.vd-cloche__compte {
  position: absolute; inset-block-start: -2px; inset-inline-end: -2px;
  box-sizing: border-box;
  min-inline-size: 18px; block-size: 18px; padding-inline: 4px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; background: var(--danger); color: #fff;
  font-family: var(--police-donnee); font-size: var(--texte-mention); font-weight: var(--graisse-appuyee);
  font-variant-numeric: tabular-nums;
}
.vd-cloche__compte[hidden] { display: none; }

/* ---------- Le squelette de chargement ----------

   IL VIENT DE PALANTÍRI, où il sert depuis longtemps, et il corrige un verdict que j'ai rendu le
   26/08/2026 : « zéro composant de chargement, il ne se crée pas ». Ce comptage portait sur les
   onze bases de démonstration de ce dépôt, et je l'ai énoncé comme s'il portait sur la maison.
   Palantíri en a un, `Skeleton`, avec sa pulsation. Une absence mesurée sur un périmètre ne
   s'annonce jamais comme une absence tout court.

   IL DIT « ÇA ARRIVE », PAS « C'EST VIDE ». Un écran vide pendant un chargement se lit comme un
   écran vide tout court : le lecteur repart. Le squelette occupe la place de ce qui vient, à la
   forme de ce qui vient, ce qui évite aussi le saut de mise en page à l'arrivée des données.

   IL EST ANNONCÉ. `aria-busy` sur la région qui charge, et un texte hors écran : une pulsation ne
   s'entend pas. Sans cela, la synthèse vocale lit une page vide et se tait.

   IL S'ARRÊTE DE PULSER quand la personne a demandé moins d'animation. Une pulsation permanente
   dans le champ de vision est exactement ce que ce réglage existe pour éteindre. */
.vd-squelette {
  /* IL SE DÉTACHE DE CE QU'IL Y A DERRIÈRE, QUEL QUE SOIT CE FOND.
     Constat de Colomban, 27/08/2026 : « le squelette de chargement est vide ». Il ne l'était pas,
     il était INVISIBLE : il peignait `--surface`, la teinte que porte aussi la carte qui
     l'accueille, donc du gris sur gris. Une valeur de fond nommée ne dit rien du fond réel.
     Un mélange du texte à 9 % assombrit ce qui est dessous, quel qu'il soit : blanc, gris de
     carte, ou surface haute. C'est la seule façon d'être vu partout sans connaître son hôte. */
  background: color-mix(in srgb, var(--texte) 9%, transparent);
  border-radius: var(--courant-rayon-petit);
  animation: vd-squelette-pulsation 1.5s ease-in-out infinite;
  /* Une hauteur par défaut, celle d'une ligne de texte : un squelette sans hauteur ne se voit
     pas, et l'appelant n'a pas à la déclarer pour le cas courant. */
  block-size: 14px;
}
@keyframes vd-squelette-pulsation {
  0%, 100% { opacity: .4; }
  50% { opacity: .8; }
}
@media (prefers-reduced-motion: reduce) {
  .vd-squelette { animation: none; opacity: .55; }
}
/* IL PREND LA FORME DE CE QU'IL REMPLACE, ET PAS UNE AUTRE. Constat de Colomban, 27/08/2026 :
   « le squelette n'est pas adapté », avec trois références, du texte, un graphique, des images.
   Un squelette qui ne ressemble pas au contenu attendu fait sauter la mise en page à l'arrivée
   des données, ce qui est précisément le défaut qu'il existe pour éviter.
   • Le TEXTE : des lignes de 16 px, la dernière plus courte, comme un paragraphe qui se termine.
   • Le GRAPHIQUE : des barres de hauteurs inégales, alignées sur leur base.
   • L'IMAGE : un bloc plein au rapport de l'image, jamais une ligne. */
.vd-squelette--titre { block-size: 20px; inline-size: 40%; }
.vd-squelette--rond { border-radius: 999px; inline-size: var(--controle-hauteur); block-size: var(--controle-hauteur); }
.vd-squelette--texte { block-size: 16px; }
/* La dernière ligne d'un paragraphe ne va jamais au bout : la copier rend le faux crédible. */
.vd-squelette-texte { display: flex; flex-direction: column; gap: var(--espace-s); }
.vd-squelette-texte .vd-squelette:last-child { inline-size: 62%; }
.vd-squelette--bloc { block-size: auto; aspect-ratio: 3 / 2; border-radius: var(--courant-rayon-petit); }
.vd-squelette-graphe { display: flex; align-items: flex-end; gap: 6px; block-size: 96px; }
.vd-squelette-graphe .vd-squelette { flex: 1 1 0; block-size: auto; align-self: flex-end; }
/* La ligne de squelette reprend la forme d'une ligne de liste : un rond, deux traits. */
.vd-squelette-ligne {
  display: flex; align-items: center; gap: var(--espace-m);
  padding-block: var(--espace-m);
  border-block-end: 1px solid var(--bordure);
}
.vd-squelette-ligne:last-child { border-block-end: 0; }
.vd-squelette-ligne__texte { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; }

/* ---------- Les trois utilitaires, et pourquoi il n'y en a que trois ----------

   LOT S, second volet. Mesure du 26/08/2026, après que tous les composants soient montés : ce
   qu'il reste hors du socle n'est plus un composant, ce sont des UTILITAIRES que chaque base
   réécrit. Trois reviennent partout : `mono` 377 usages dans 7 bases, `mut` 252 dans 5, et le
   hors-écran, déjà au socle.

   UN UTILITAIRE SE JUSTIFIE PAR SON ABSENCE DE MAISON. Ces trois-là ne décrivent aucun objet :
   ils disent comment un texte se lit, à l'intérieur de n'importe quel composant. Tout le reste
   de la liste, `sub`, `meta`, `top`, `cta`, `tag`, `stat`, décrit une PLACE ou un RÔLE : cela
   appartient au composant qui l'emploie, pas à une classe transverse. Une bibliothèque
   d'utilitaires qui grossit finit par remplacer les composants, et on écrit alors le design dans
   le balisage de chaque page.

   `mut` DEVIENT `attenue`. Un nom abrégé de trois lettres se relit mal et se confond : ici, avec
   « muté » ou « muet ». */
/* LES CHIFFRES S'ALIGNENT SANS CHANGER DE POLICE.
   Mesuré le 31/08/2026 sur tout le parc : `font-variant-numeric: tabular-nums` est déclaré
   VINGT-SIX fois, dans NEUF produits, sous QUINZE noms différents, `.n`, `.num`, `.mono`,
   `.hh`, `.amt`, `.d-num`, `.msref`, `.pg-info`, `.delta`, `.val`, `.vh`, `.id-immat`,
   `.id-ligne__date`, `.sy-champ__valeur`, `.sy-journal time`.
   Pourquoi tant de fois : le socle n'offrait cette propriété qu'à travers `.vd-mono`, qui CHANGE
   AUSSI LA POLICE pour celle de la donnée, et qu'à travers `.vd-table td.vd-num`, qui ne vaut que
   dans un tableau. Un produit qui veut aligner des chiffres en gardant sa police d'interface
   n'avait rien à reprendre, alors il l'écrivait.
   Une déclaration écrite vingt-six fois n'est pas une préférence de neuf équipes, c'est une
   capacité qui manque. Arbitrage de Colomban du 30/08/2026 : « un composant centralisé et ses
   variables dans un design system unifié, c'est ça pour tout ».
   ⚠ CE N'EST PAS `.vd-mono`, ET C'EST TOUT L'INTÉRÊT. `.vd-mono` dit « ceci est une donnée » et
   prend la police de la donnée. `.vd-tabulaire` dit seulement « ces chiffres tombent l'un sous
   l'autre », dans la police du texte qui les entoure. */
.vd-tabulaire { font-variant-numeric: tabular-nums; }

.vd-mono {
  font-family: var(--police-donnee);
  font-variant-numeric: tabular-nums;
}
.vd-attenue { color: var(--texte-attenue); }

/* ---------- La liste de lignes, et la liste d'attributs ----------

   LOT R bis, 26/08/2026. Mesure : le même objet vit sous trois noms, `id-liste` chez VAIATA ID,
   `sy-liste` chez Syclef, `pj-liste` ailleurs. Une liste de lignes séparées par un filet, chacune
   portant un intitulé, une métadonnée et parfois une pastille d'état.

   POURQUOI CE N'EST PAS UN TABLEAU. Un tableau compare des colonnes ; une liste énumère des
   objets. Employer un tableau pour énumérer oblige à inventer des en-têtes qui ne servent à rien,
   et il ne tient pas au doigt. La règle : dès qu'on veut TRIER ou COMPARER une colonne, c'est un
   tableau. Sinon, c'est une liste.

   LE DERNIER FILET SAUTE. Une liste qui se termine par un trait a l'air coupée : le lecteur
   cherche la suite. */
.vd-liste { list-style: none; margin: 0; padding: 0; }
/* L'ÉTAT SE RANGE EN FIN DE LIGNE, PAS SOUS LE NOM. Constat de Colomban, 27/08/2026 : « En
   service et En carénage devraient être à droite pour réduire la hauteur et utiliser l'espace de
   droite ». Une pastille posée sous le nom ajoute une ligne à chaque entrée, donc une hauteur de
   liste qui double, pendant que la moitié droite reste vide. Deux colonnes : ce qui nomme au
   début, ce qui qualifie à la fin, comme la colonne d'actions d'un tableau. */
.vd-ligne {
  display: grid; gap: 2px var(--espace-m);
  grid-template-columns: 1fr auto;
  align-items: center;
  padding-block: var(--espace-m);
  border-block-end: 1px solid var(--bordure);
}
.vd-ligne__principal, .vd-ligne__meta, .vd-ligne__date { grid-column: 1; }
.vd-ligne .vd-pill { grid-column: 2; grid-row: 1 / -1; justify-self: end; align-self: center; }
.vd-ligne:last-child { border-block-end: 0; }
.vd-ligne__principal { font-size: var(--texte-corps); font-weight: var(--graisse-appuyee); }
.vd-ligne__meta { font-size: var(--texte-legende); color: var(--texte-attenue); }
.vd-ligne__date { font-size: var(--texte-legende); color: var(--texte-attenue); font-variant-numeric: tabular-nums; }


/* LA LISTE D'ATTRIBUTS : un objet, ses champs. Deux colonnes au large, une seule au doigt, parce
   qu'une valeur coupée en deux lignes sous son intitulé ne se lit plus. */
.vd-attributs {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--espace-s) var(--espace-m); margin-block: var(--espace-s);
}
.vd-attributs--pleine { grid-template-columns: 1fr; }
.vd-attributs dt {
  font-size: var(--texte-mention); text-transform: uppercase; letter-spacing: .06em;
  color: var(--texte-attenue);
}
/* UNE VALEUR QUI NE SAIT PAS SE COUPER DEBORDE SA COLONNE. Mesure du 30/08/2026 au banc du
   doigt : dans une fiche de catalogue a 390 px, un `dd` de 209 px poussait quatre blocs
   au-dela du bord, jusqu au niveau de la section. Une adresse, un identifiant, un nom de domaine
   n ont pas d espace ou se couper, et une liste de valeurs en contient toujours. Le composant le
   porte, plutot que chaque espace qui le decouvre a son tour. */
/* ⚠ `break-word` ET NON `anywhere`, ET LA DIFFERENCE EST VISIBLE.
   Pose en `anywhere` le 30/08/2026 pour corriger un debordement ; signale illisible par Colomban
   le 31/08 sur le catalogue de Syclef, ou « France » devenait « Franc / e » et « Colomiers »
   « Colo / miers ». `anywhere` autorise la coupure a chaque caractere ET reduit la largeur
   minimale intrinseque du contenu : dans une colonne de grille etroite, la colonne se retracte
   jusqu au caractere et tout le texte s empile verticalement.
   `break-word` coupe un mot SEULEMENT s il ne tient pas seul sur sa ligne, et ne touche pas a la
   largeur minimale. Le debordement du 30/08 reste corrige par `min-inline-size: 0`, qui autorise
   la colonne a se retracter sous la taille de son contenu ; c etait deja lui le correctif. */
.vd-attributs dd { margin: 0; font-size: var(--texte-corps); font-variant-numeric: tabular-nums;
  min-inline-size: 0; overflow-wrap: break-word; }
@media (max-width: 620px) { .vd-attributs { grid-template-columns: 1fr; } }

/* ---------- Le tiroir de détail ----------

   LOT P. Forme reprise d'Intelligence Chantiers navals, où il porte la fiche d'un navire. Il
   s'ouvre sur le bord de fin, par-dessus la page, sans la remplacer : on garde la liste sous les
   yeux, et c'est toute la différence avec une page de détail.

   DEUX CORRECTIONS SUR L'ORIGINAL. Il ouvrait `right: 0` et glissait en `translateX(100%)` :
   sous écriture de droite à gauche, il serait entré par le mauvais bord, et à contresens. Et il
   déclarait `aria-hidden` sur le conteneur pour dire son état, ce qui masque le contenu aux
   lecteurs d'écran même ouvert ; c'est `hidden` qui porte l'état, et le panneau est un dialogue
   modal nommé. */
.vd-tiroir { position: fixed; inset: 0; z-index: var(--plan-flottant); }
.vd-tiroir[hidden] { display: none; }
.vd-tiroir__voile { position: absolute; inset: 0; border: 0; padding: 0; background: rgb(10 12 20 / .42); }
.vd-tiroir__panneau {
  position: absolute; inset-block: 0; inset-inline-end: 0;
  inline-size: min(720px, 96vw); block-size: 100%;
  overflow-y: auto; padding: var(--espace-xl);
  background: var(--surface-haute);
  box-shadow: -12px 0 40px rgb(0 0 0 / .18);
}
.vd-tiroir__tete { display: flex; align-items: center; gap: var(--espace-m); margin-block-end: var(--espace-l); }
.vd-tiroir__titre { flex: 1 1 auto; min-inline-size: 0; font-size: var(--texte-affiche); margin: 0; }

/* ---------- Les facettes, filtres en colonne ----------

   LOT P. Forme reprise d'Intelligence Chantiers navals. Elle sert quand les filtres sont
   nombreux et permanents : une colonne qui reste, au lieu d'un panneau qui se replie.
   L'original écrivait `border-right`, qui passe du mauvais côté en écriture inversée. */
.vd-facettes {
  overflow-y: auto; padding: var(--espace-m);
  background: var(--surface);
  border-inline-end: 1px solid var(--bordure);
  inline-size: var(--vd-facettes-largeur, 264px); flex: none;
}
.vd-facettes__groupe { margin-block-end: var(--espace-l); }
.vd-facettes__titre {
  font-size: var(--texte-mention); letter-spacing: .1em; text-transform: uppercase;
  color: var(--texte-attenue); font-weight: var(--graisse-forte); margin-block-end: var(--espace-s);
}
.vd-facettes__ligne { display: flex; gap: var(--espace-s); }
.vd-facettes .vd-champ__saisie { inline-size: 100%; min-inline-size: 0; }
@media (max-width: 899px) {
  /* Au doigt, une colonne de filtres mange la moitié de l'écran : elle passe au-dessus de la
     liste, en bande repliable, et c'est le panneau de filtres du socle qui prend le relais. */
  .vd-facettes { inline-size: auto; border-inline-end: 0; border-block-end: 1px solid var(--bordure); }
}

/* ---------- Le fil d'activité ----------

   LOT P. Forme reprise de VAIATA ID, où il porte l'historique d'un marin. La date à gauche, le
   fait au centre, un liseré de couleur par nature d'événement.

   LE LISERÉ EST ICI LÉGITIME, et c'est la seule place où il l'est : il ne décore pas, il code la
   nature de la ligne, et il est doublé par le libellé du fait. C'est un liseré de DONNÉE, pas de
   signature. Logique, évidemment : `border-inline-start`, jamais `border-left`. */
.vd-fil { list-style: none; margin: 0; padding: 0; }
.vd-fil__item {
  display: flex; gap: var(--espace-m);
  padding-block: var(--espace-s); padding-inline: var(--espace-m) 0;
  border-inline-start: 3px solid var(--vd-fil-ton, var(--bordure));
}
.vd-fil__item + .vd-fil__item { border-block-start: 1px solid var(--bordure); }
.vd-fil__date {
  flex: none; inline-size: 82px; font-size: var(--texte-legende);
  color: var(--texte-attenue); font-variant-numeric: tabular-nums;
}
.vd-fil__quoi { display: flex; flex-direction: column; gap: var(--espace-xxxs); min-inline-size: 0; }
.vd-fil__quoi strong { font-size: var(--texte-corps); font-weight: var(--graisse-appuyee); }
.vd-fil__detail { font-size: var(--texte-legende); color: var(--texte-attenue); }

/* ---------- La carte de personne ----------

   LOT P. Forme reprise de Syclef. Monogramme, nom, rôle. Elle emploie l'avatar du socle au lieu
   de redessiner un rond d'initiales, ce que faisait l'original. */
.vd-personne { display: flex; align-items: center; gap: var(--espace-s); min-inline-size: 0; }
.vd-personne__texte { display: flex; flex-direction: column; min-inline-size: 0; }
.vd-personne__nom { font-size: var(--texte-interface); font-weight: var(--graisse-appuyee); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vd-personne__role { font-size: var(--texte-legende); color: var(--texte-attenue); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- La pagination ----------

   LOT P. Forme reprise des quatre bases Relation, où elle vit sous `pager` : la plage affichée,
   le précédent, le compte de page, le suivant. Elle y est juste, et elle y est écrite quatre
   fois.

   LA PLAGE SE DIT EN CLAIR, « 1 à 20 sur 342 ». Un numéro de page seul ne dit pas combien il en
   reste, et c'est la seule question qu'on se pose devant une liste longue. */
.vd-pagination {
  display: flex; align-items: center; gap: var(--espace-s); flex-wrap: wrap;
  margin-block-start: var(--espace-m);
  font-size: var(--texte-legende); color: var(--texte-attenue);
}
.vd-pagination__plage { font-variant-numeric: tabular-nums; }
.vd-pagination__gouverne { display: flex; align-items: center; gap: var(--espace-s); margin-inline-start: auto; }
.vd-pagination__page { font-variant-numeric: tabular-nums; min-inline-size: 96px; text-align: center; }
@media (max-width: 620px) {
  /* LOT Q, MESURÉ À 390 PX : la pagination débordait de 2 px. Une largeur minimale de 96 px pour
     le compte de page, plus deux boutons, plus la plage, ne tiennent pas dans 390. La gouverne
     passe sous la plage, et le compte cesse d'exiger sa largeur. */
  .vd-pagination { gap: var(--espace-xs); }
  .vd-pagination__gouverne { margin-inline-start: 0; inline-size: 100%; justify-content: space-between; }
  .vd-pagination__page { min-inline-size: 0; }
}

/* ---------- Le graphique en barres ----------

   LOT P. Mesure du 26/08/2026 : 48 occurrences dans le parc, sous `hbar`, et zéro au socle. Trois
   colonnes, le libellé, la piste, la valeur.

   DES BARRES, JAMAIS UN CAMEMBERT. Une part de camembert ne se compare pas à l'œil : on compare
   des angles, ce que personne ne sait faire, alors qu'on compare des longueurs sans y penser.
   LA VALEUR EST ÉCRITE À CÔTÉ. Un graphique dont il faut deviner la valeur en mesurant la barre
   du regard n'est pas un graphique, c'est une devinette. */
.vd-barres { display: flex; flex-direction: column; }
.vd-barre {
  display: grid;
  grid-template-columns: minmax(0, 170px) minmax(24px, 1fr) 76px;
  gap: var(--espace-s); align-items: center;
  font-size: var(--texte-legende); padding-block: 6px;
}
.vd-barre__nom { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vd-barre__piste { block-size: 10px; border-radius: 999px; background: var(--surface); overflow: hidden; }
.vd-barre__part { display: block; block-size: 100%; border-radius: 999px; background: var(--accent); }
.vd-barre__valeur { text-align: end; font-variant-numeric: tabular-nums; color: var(--texte-attenue); }
@media (max-width: 620px) {
  /* Au doigt, le libellé prend sa ligne : trois colonnes sur 390 px laissent 40 px à la piste,
     et une barre de 40 px ne se compare à rien. */
  .vd-barre { grid-template-columns: 1fr 76px; }
  .vd-barre__nom { grid-column: 1 / -1; }
}

/* ---------- La frise de jalons ----------

   LOT P. Forme reprise de VAIATA Facturation, où elle porte les étapes d'un cycle. L'original
   pose un liseré de tête à la couleur du produit, comme la tuile : c'est la signature de la
   maison, elle est conservée. */
/* LE MARQUEUR DE LISTE NE S'AFFICHE PAS. Constat de Colomban, 27/08/2026 : « le chiffre des
   étapes n'est pas harmonieux, il est écrit comme du texte de corps de page hors des tuiles ».
   C'était le marqueur natif de la liste ordonnée, « 1. 2. 3. », posé par le navigateur en dehors
   de la boîte. Le balisage est bien une liste ordonnée, et il doit le rester : l'ordre des jalons
   est une information, pas une mise en page. C'est son marqueur qu'on retire, pas sa nature. */
.vd-frise {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--espace-m);
  list-style: none; margin: 0; padding: 0;
}
.vd-jalon {
  background: var(--surface-haute); border: 1px solid var(--bordure);
  border-block-start: 3px solid var(--accent);
  border-start-start-radius: 0; border-start-end-radius: 0;
  border-end-start-radius: var(--accent-rayon, var(--courant-rayon-petit));
  border-end-end-radius: var(--accent-rayon, var(--courant-rayon-petit));
  padding: var(--espace-m);
}
.vd-jalon__date { font-size: var(--texte-legende); color: var(--vd-jalon-ton, var(--accent)); font-weight: var(--graisse-forte); }
/* LA COCHE VIENT APRÈS LE TITRE, PAS AVANT. Constat de Colomban, 27/08/2026. Placée devant, elle
   décale le titre d'une largeur d'icône et les quatre jalons ne commencent plus au même endroit ;
   placée après, elle qualifie ce qu'on vient de lire, ce qui est son rôle. */
.vd-jalon__quoi {
  font-size: var(--texte-corps); font-weight: var(--graisse-appuyee); margin-block: 4px var(--espace-xxs);
  display: flex; align-items: center; gap: var(--espace-s);
}
.vd-jalon__quoi [data-icone] { margin-inline-start: auto; color: var(--vd-jalon-ton, var(--accent)); }
.vd-jalon__note { font-size: var(--texte-legende); color: var(--texte-attenue); }
/* LE LISERÉ SUIT L'ÉTAT DE L'ÉTAPE, il ne reste pas à la couleur du produit. Constat de Colomban,
   27/08/2026 : quatre jalons barrés du même trait ne disent pas où l'on en est. Un seul jeton, le
   ton, porte le liseré, la date et la coche : trois endroits, une valeur. */
.vd-jalon { border-block-start-color: var(--vd-jalon-ton, var(--bordure)); }
/* SIX ÉTATS, ET PAS UN DE PLUS. Arbitrage de Colomban, 27/08/2026, à partir de la facturation :
   « il y a quatre statuts, réalisé, en cours, erreur ou en attente d'élément, on bloque à
   celle-là, annulé, on passe à la suivante ».
   Deux s'ajoutent à sa liste, et chacun se justifie par ce qu'il fait faire :
   • À VENIR, l'état par défaut, celui d'une étape qui n'a pas commencé. Sans lui, une étape non
     démarrée serait « en attente », ce qui appellerait une relance qui n'a pas lieu d'être.
   • EN RETARD, qui n'est pas une erreur : rien n'a échoué, la date est passée. Les confondre fait
     chercher une panne là où il n'y a qu'un délai, et taire un délai qui n'a pas de panne.
   Ce que les six changent pour qui lit : réalisé et annulé sont CLOS, en cours et en retard sont
   OUVERTS et avancent, erreur et en attente sont BLOQUÉS et demandent une action. Trois familles,
   deux états chacune : au-delà, on nomme des nuances que personne ne saura choisir.
   ANNULÉ SE BARRE, parce que c'est le seul état où l'étape ne se fera jamais. */
.vd-jalon--franchi { --vd-jalon-ton: var(--succes); }
.vd-jalon--courant { --vd-jalon-ton: var(--accent); }
.vd-jalon--retard { --vd-jalon-ton: var(--alerte); }
.vd-jalon--erreur { --vd-jalon-ton: var(--danger); }
.vd-jalon--attente { --vd-jalon-ton: var(--information); }
.vd-jalon--annule { --vd-jalon-ton: var(--bordure); }
.vd-jalon--annule .vd-jalon__quoi { text-decoration: line-through; color: var(--texte-attenue); }
.vd-jalon--annule .vd-jalon__note { font-style: italic; }

/* ---------- Le calendrier en vue mois ----------

   LOT P. Question de Colomban, 26/08/2026 : « je ne vois pas d'agenda par exemple, c'est
   normal ? » Oui, et voici pourquoi ça ne l'est plus. Mesure du même jour : ce calendrier existe,
   chez VAIATA ID SEULEMENT, en 16 sélecteurs locaux. Une grille de mois avec ses périodes, ses
   points d'événement et sa navigation, écrite une fois, pour un produit.

   IL MONTE AVEC LA FORME QUI A FAIT SES PREUVES, pas une forme inventée : c'est la règle du lot,
   et c'est la seule manière que le premier usage reste juste après la montée. Trois choses
   changent, et chacune est un défaut de l'original, pas une préférence :

   1. LES ARRONDIS DE DÉBUT ET DE FIN DEVIENNENT LOGIQUES. L'original écrivait
      `border-top-left-radius` : sous écriture de droite à gauche, la barre de période se serait
      ouverte du mauvais côté, le début arrondi à droite et la fin à gauche.
   2. LA HAUTEUR DE LA CASE SUIT L'ÉCHELLE DES CONTRÔLES. Le pas de navigation valait 44 px
      écrits en dur, une hauteur qui n'existe nulle part ailleurs.
   3. LA COULEUR DE LA PÉRIODE PRÉVUE VIENT DU JETON SÉMANTIQUE, pas de la couleur d'un autre
      produit. L'original peignait en `--produit-compas` un état de VAIATA ID.

   UN ÉTAT N'EST JAMAIS PORTÉ PAR LA SEULE COULEUR, et l'original le disait déjà : la période
   porte un fond ET un arrondi qui ouvre et ferme la barre, et chaque case porte son libellé
   complet pour qui ne voit pas la grille. */
.vd-calendrier { display: block; }
.vd-calendrier__tete {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--espace-m); margin-block-end: var(--espace-m);
}
.vd-calendrier__mois { font-size: var(--texte-titre); text-transform: capitalize; }
.vd-calendrier__pas {
  inline-size: var(--controle-hauteur); block-size: var(--controle-hauteur);
  display: grid; place-items: center;
  border: 1px solid var(--bordure); border-radius: 999px;
  background: var(--surface-haute); color: var(--texte); cursor: pointer;
}
.vd-calendrier__pas:hover { border-color: var(--texte-attenue); }
.vd-calendrier__pas:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* LA GRILLE A UNE LARGEUR DE LECTURE, elle ne s'étale pas jusqu'au bord. Mesuré le 27/08/2026 sur
   VAIATA ID : dans une colonne de 700 px, la case carrée montait à 93 px pour un chiffre de 13, et
   le ruban, posé en haut de sa case, tombait à 45 px de son propre jour. Il se lisait alors comme
   appartenant à la ligne du dessus, ce qui est exactement l'information contraire.
   380 px met la case à 48, le degré de contrôle de la maison. Le compte qui décide n'est pas
   l'esthétique : le ruban doit être PLUS PRÈS de son propre chiffre que de celui du dessus.
   Cet écart vaut (case / 2) - 3 contre (case / 2) + 3 ; à 60 px il se joue à six pixels, ce qui
   ne se voit pas, à 48 il se lit. La grille reste fluide en dessous : c'est un plafond, pas une
   taille. */
.vd-calendrier__grille {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--espace-xxs);
  max-inline-size: 380px;
  padding: var(--espace-m);
  background: var(--surface-haute);
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-moyen));
}
.vd-jour {
  display: grid; place-items: center; aspect-ratio: 1;
  font: 400 var(--texte-interface)/1 var(--police-interface);
  font-variant-numeric: tabular-nums;
  color: var(--texte); border-radius: var(--courant-rayon-petit);
}
.vd-jour--entete {
  font-size: var(--texte-mention); font-weight: var(--graisse-forte); color: var(--texte-attenue);
  aspect-ratio: auto; padding-block: 4px;
}
/* Une case vide garde sa place dans la grille : la retirer décalerait tout le mois d'un cran. */
.vd-jour--vide { visibility: hidden; }
/* LA PÉRIODE EST UN RUBAN SOUS LE JOUR, PAS UN FOND QUI MANGE LA CASE.
   Constat de Colomban, 27/08/2026 : « je trouve ça gros et laid, la sélection des périodes, et
   elle empêche d'avoir plusieurs choses le même jour ». Les deux reproches n'en font qu'un. Un
   fond plein occupe TOUTE la case : il ne reste ni place pour un second engagement, ni contraste
   pour un point d'événement, et le mois entier se lit comme un damier de blocs.
   Le ruban prend 4 px sous le chiffre. Trois tiennent dans une case sans la déformer, le chiffre
   reste noir sur blanc donc lisible, et le jour d'aujourd'hui garde son cadre par-dessus.
   LA BARRE S'OUVRE ET SE FERME toujours, mais sur le ruban : c'est lui qui court d'un jour à
   l'autre, et c'est là que l'arrondi dit le premier et le dernier jour. */
/* LA BOÎTE SE MESURE ENTIÈRE, sinon le dégagement laissé au ruban s'ajoute au carré et les cases
   cessent d'en être. Troisième fois que ce piège se paie sur ce dépôt (27/08/2026). */
/* LE CHIFFRE SE RANGE SOUS SON RUBAN, il ne se centre pas dans le carré. Mesuré le 27/08/2026 :
   centré, il est à (case / 2) - 3 de son propre ruban et à (case / 2) + 3 de celui du dessus, soit
   vingt-trois contre vingt-neuf pixels. Six pixels d'écart ne se voient pas, et l'œil rattache
   alors le trait à la mauvaise semaine, ce qui est l'information exactement inverse.
   Rangé en haut, il est à une dizaine de pixels de son ruban et à une quarantaine de l'autre. La
   bande de douze pixels est réservée en toutes circonstances, y compris les jours sans période,
   pour que les chiffres restent sur une même ligne d'une case à l'autre. */
.vd-jour {
  position: relative; align-content: start; box-sizing: border-box; padding-block-start: 12px;
}
.vd-jour--aujourdhui { box-shadow: inset 0 0 0 2px var(--accent); font-weight: var(--graisse-forte); }
/* LES RUBANS SONT EN HAUT DE LA CASE. Constat de Colomban, 27/08/2026 : « je mettrais plutôt les
   traits en haut des jours ». En bas, ils se confondaient avec les points d'événement, qui suivent
   le chiffre ; en haut, ils forment une ligne continue d'une case à l'autre, qui se lit comme la
   durée qu'elle est.
   ILS RESTENT ALIGNÉS D'UN JOUR À L'AUTRE. Même constat : « le 10, il y a deux traits d'événements
   quotidiens non alignés ». Le retrait qui arrondissait le premier et le dernier jour décalait le
   ruban de quatre pixels, donc deux périodes voisines ne tombaient plus sur la même ligne. Le
   retrait est parti ; l'arrondi, lui, reste, et il suffit à dire où la période commence. */
.vd-jour__rubans {
  position: absolute; inset-inline: 2px; inset-block-start: 3px;
  display: flex; flex-direction: column; gap: var(--espace-xxxs);
}
/* LE RUBAN ET LE POINT SE NOMMENT AU SURVOL. Demande de Colomban, 27/08/2026 : « je dois avoir
   l'explication des points et traits au survol ». Ils portent un `title`, que l'infobulle du
   socle convertit en vraie bulle, atteignable au clavier et lisible sur fond sombre.
   ILS RESTENT MASQUÉS À LA SYNTHÈSE VOCALE, et ce n'est pas une omission : la case entière porte
   déjà son libellé complet, « 10 octobre : embarquement, et formation incendie, premier jour ».
   Faire lire deux fois la même chose, une fois par la case et une fois par chaque marque, rendrait
   le mois interminable à l'oreille. */
.vd-jour__ruban { block-size: 4px; background: var(--vd-ruban, var(--accent)); }
.vd-jour__ruban, .vd-jour__point { cursor: help; }
/* LOGIQUES, PAS PHYSIQUES : le ruban s'ouvre au DÉBUT de la lecture et se ferme à sa FIN. */
.vd-jour__ruban--debut { border-start-start-radius: 999px; border-end-start-radius: 999px; }
.vd-jour__ruban--fin { border-start-end-radius: 999px; border-end-end-radius: 999px; }
/* LES POINTS DISENT LE PONCTUEL, LES RUBANS DISENT LA DURÉE. Arbitrage de Colomban, 27/08/2026 :
   « on doit pouvoir avoir des points de couleur pour les événements ponctuels uniquement sur la
   journée, un exercice, un document qui expire ». Deux natures d'information, deux formes : un
   trait qui court d'un jour à l'autre ne peut pas dire un fait qui n'a pas de durée, et un point
   ne peut pas dire une période. Les confondre oblige à lire la légende à chaque fois. */
.vd-jour__points { display: flex; gap: var(--espace-xxs); margin-block-start: var(--espace-xxs); line-height: 1; }
.vd-jour__point {
  inline-size: 5px; block-size: 5px; border-radius: 999px;
  background: var(--vd-point, currentColor);
}
.vd-calendrier__legende {
  display: flex; flex-wrap: wrap; gap: var(--espace-s);
  margin-block: var(--espace-m) 0;
}

/* ---------- Le rail de navigation d'un espace ----------

   LOT J. Mesure du parc, 26/08/2026 : SEPT bases portent chacune leur navigation latérale, aucune
   au socle. Celle de Relation, la plus complète, sert de référence : marque en tête, entrées à
   icône et libellé, une entrée courante, une pastille de retard, un groupe repliable, un bouton
   de fermeture, et un bouton sandwich dans le bandeau pour l'ouvrir au doigt.

   L'ÉTAT COURANT SE LIT SUR `aria-current`, PAS SUR UNE CLASSE. Même règle que le tri de tableau :
   c'est l'attribut que les lecteurs d'écran annoncent, et le donner à la machine coûte le même
   geste que de le donner à l'œil. Les sept navigations du parc emploient une classe `active`, que
   personne d'autre que l'œil ne lit. Le dessin se déduit donc de l'attribut, ce qui rend
   impossible d'être visuellement courant sans l'être pour la machine.

   UNE PASTILLE PORTE UN NOMBRE, JAMAIS UN POINT NU. Relation affiche un point avec « 2 en retard »
   en infobulle : le point dit qu'il se passe quelque chose, il ne dit pas quoi, et l'infobulle
   n'existe ni au doigt ni au clavier. */
.vd-rail {
  display: flex; flex-direction: column; gap: var(--espace-xxxs);
  inline-size: var(--vd-rail-largeur, 248px); flex: none;
  padding: var(--espace-m);
  background: var(--surface-haute);
  border-inline-end: 1px solid var(--bordure);
  font-family: var(--police-interface);
  /* IL POSE SON ENCRE, PAS SEULEMENT SON FOND. Mesuré le 28/08/2026 sur Syclef : le pied de son
     rail rendait 1,06 pour 1, du presque-noir sur presque-noir, illisible. Cause : le rail posait
     `background` sans `color`, donc tout ce qu'un produit ajoute dedans héritait de l'encre de la
     PAGE. Redéfinir `--texte` sur la portée du rail ne servait à rien, puisque personne ne le
     lisait. Un composant qui impose un fond doit imposer l'encre qui va avec : sur un rail sombre,
     hériter d'un texte de page claire est une panne garantie, pas un cas limite. */
  color: var(--texte-attenue);
}
/* LA TÊTE DU RAIL EST UNE LIGNE : la marque à gauche, la fermeture au bord de fin, centrée sur
   la hauteur. Constat de Colomban, 26/08/2026 : « à quoi sert le bouton X ? à fermer le menu
   burger ? Il est mal placé. » Il était posé SOUS le bloc de marque, dans le flux, donc il
   ressemblait à une entrée de menu plutôt qu'à une fermeture, et rien ne le rattachait au
   panneau qu'il ferme. */
.vd-rail__tete {
  display: flex; align-items: center; gap: var(--espace-s);
  padding: var(--espace-s) var(--espace-s) var(--espace-m);
}
/* LA MARQUE PREND L'ENCRE DU RAIL, ET LE RAIL LA DÉCLARE. Constaté en reprenant Relation le
   27/08/2026 : le rail posait son fond sans jamais poser sa couleur de texte. Sur la galerie,
   claire, le noir hérité de la page tombait juste par accident ; sur un rail bleu de nuit, le
   titre de marque devenait illisible. Un composant qui ne marche que sur un seul fond n'est pas
   un composant du socle, c'est un dessin de page. */
.vd-rail__tete > .vd-rail__marque { flex: 1 1 auto; min-inline-size: 0; color: var(--texte); }
.vd-rail__marque h1, .vd-rail__marque > :first-child {
  margin: 0; color: inherit; font-size: var(--texte-corps); font-weight: var(--graisse-appuyee); line-height: 1.25;
}
.vd-rail__client { display: block; font-size: var(--texte-legende); color: var(--texte-attenue); margin-block-start: var(--espace-xxxs); }
.vd-rail__lien {
  box-sizing: border-box;
  display: flex; align-items: center; gap: var(--espace-s);
  min-block-size: var(--controle-hauteur);
  padding-block: 6px; padding-inline: var(--espace-s);
  border: 0; border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  background: none; cursor: pointer;
  font: inherit; font-size: var(--texte-interface); text-align: start;
  color: var(--texte-attenue); text-decoration: none;
  inline-size: 100%;
}
.vd-rail__lien:hover { background: var(--surface); color: var(--texte); }
.vd-rail__lien:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* L'ENTRÉE COURANTE SE DESSINE DEPUIS L'ATTRIBUT. Pas de classe `active` : une classe que le
   lecteur d'écran ignore décrit un état à l'œil seul. */
.vd-rail__lien[aria-current='page'] {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--texte); font-weight: var(--graisse-appuyee);
}
.vd-rail__lien[aria-current='page'] .vd-rail__icone { color: var(--accent); }
.vd-rail__icone { flex: none; display: inline-flex; color: currentColor; }

/* ---------- LE RAIL SUR FOND SOMBRE ----------

   TROIS PRODUITS LE FONT DÉJÀ, CHACUN DE SON CÔTÉ. Mesuré sur le rendu le 30/08/2026 : Syclef,
   Sextant et les quatre espaces Relation posent le rail du socle sur un fond marine, clarté
   relative 0,012. Aucun n'avait de variante à reprendre, donc chacun a réécrit les mêmes deux
   choses : les six surfaces en jetons, et l'actif en aplat plein.

   POURQUOI L'ACTIF CHANGE DE NATURE, et ce n'est pas une préférence. Le socle marque l'entrée
   courante par 14 % d'accent sur une surface claire. Sur un fond bleu de nuit, 14 % d'une teinte
   ne se distingue pas du fond : la marque disparaît, et l'utilisateur ne sait plus où il est.
   Syclef et Relation l'ont constaté séparément et l'ont écrit dans des mots presque identiques.
   La variante porte donc l'aplat PLEIN, dérivé de ce que les trois font tenir aujourd'hui.

   LE PRODUIT NE FOURNIT QU'UNE CHOSE : la couleur de son fond, par `--vd-rail-fond`. Tout le
   reste, l'encre, le voile de survol, le trait de focus, se déduit. Laisser chaque produit
   redéclarer six jetons était précisément le travers : six occasions d'en oublier un, et c'est
   arrivé, la sixième surface manquante est commentée en toutes lettres chez deux d'entre eux.

   Sans `--vd-rail-fond`, le fond vaut `--navy` : avant le 23/10/2026, l'oubli donnait un rail
   clair à encre blanche, et Intelligence comme Yield l'ont payé chacun de leur côté. */
.vd-rail--sombre {
  --surface-haute: var(--vd-rail-fond, var(--navy));
  --fond: var(--vd-rail-fond, var(--navy));
  --surface: rgb(255 255 255 / .09);
  --bordure: rgb(255 255 255 / .09);
  --texte: #fff;
  --texte-attenue: #C9CCD9;
  color: var(--texte-attenue);
}
.vd-rail--sombre .vd-rail__lien:hover { background: rgb(255 255 255 / .09); color: #fff; }
/* Le trait de focus est BLANC et RENTRÉ : posé dehors, il sort du rail et se perd sur la page. */
.vd-rail--sombre .vd-rail__lien:focus-visible { outline: 2px solid #fff; outline-offset: -2px; }
.vd-rail--sombre .vd-rail__lien[aria-current='page'] {
  background: var(--accent); color: #fff;
}
.vd-rail--sombre .vd-rail__lien[aria-current='page'] .vd-rail__icone { color: currentColor; }
/* UN LIBELLÉ DE NAVIGATION NE SE COUPE PAS. Il était sur une ligne, avec des points de suite :
   « Pilotage & opportunités » devenait « Pilotage & opportu… », c'est-à-dire une rubrique qu'il
   faut deviner. Une troncature se tolère dans une liste de données, où la valeur entière est à
   un clic ; dans un menu, elle cache la destination. Le libellé revient donc à la ligne, et
   l'entrée grandit avec lui : la hauteur de contrôle reste un PLANCHER, pas un plafond. */
.vd-rail__libelle { flex: 1 1 auto; min-inline-size: 0; overflow-wrap: break-word; }
/* LE COMPTEUR DU RAIL A MIGRE VERS `.vd-compteur`, arbitrage de Colomban du 29/08/2026 :
   « Je le prends, et migre le rail ». Le bloc qui vivait ici portait le correctif de centrage
   du 28/08 ; il est monte avec lui. Deux noms pour une capacite etait le defaut meme que le
   compteur corrige. */
.vd-rail__groupe { display: flex; flex-direction: column; gap: var(--espace-xxxs); }
.vd-rail__groupe[hidden] { display: none; }
/* Le chevron du groupe repliable tourne avec l'état annoncé, jamais avec une classe. */
.vd-rail__chevron { flex: none; inline-size: 8px; block-size: 8px; transition: rotate .12s linear; }
.vd-rail__chevron::before {
  content: ''; display: block; inline-size: 6px; block-size: 6px;
  border-inline-end: 1.5px solid currentColor; border-block-end: 1.5px solid currentColor;
  rotate: -45deg;
}
[aria-expanded='true'] > .vd-rail__chevron { rotate: 90deg; }
/* ELLE NE SE MONTRE QUE LÀ OÙ ELLE FERME QUELQUE CHOSE. Au large, le rail est une colonne du
   flux : une croix qui ne ferme rien est une interface qui ment.
   LA SPÉCIFICITÉ EST DÉLIBÉRÉE : `.vd-iconbtn` porte `display: inline-flex` et se déclare plus
   bas dans la feuille ; à spécificité égale, c'est lui qui gagnait, et la croix restait visible.
   Même piège d'ordre que la tuile discrète le matin même (26/08/2026). */
.vd-rail .vd-rail__fermer { display: none; flex: none; align-self: center; }

/* AU DOIGT, LE RAIL SORT PAR-DESSUS. Il ne se replie pas en bande d'icônes : une icône seule ne
   se comprend que si on connaît déjà le mot qu'elle remplace, et sur un espace de service la
   moitié des rubriques n'ont pas d'icône évidente. Il sort donc entier, et se referme.

   IL RESTE DANS LE FLUX TANT QUE LE SCRIPT N'A PAS PARLÉ. Sans `rail.js`, la page montre la
   navigation en tête plutôt que de la cacher derrière un bouton mort.

   LE TIROIR PASSE AU-DESSUS DE SON VOILE. Relevé par heures-repos le 21/10/2026, remesuré le jour
   même sur l'espace ACF à 390 px : le tiroir était au plan fixe (20), son voile au plan du voile
   (40), et le point au centre du premier lien rendait le voile. Toute la navigation au doigt du
   parc était inatteignable : le tiroir s'ouvrait, et le premier toucher le refermait. Il prend le
   plan flottant, celui de `.vd-tiroir`. Fermé, il ne porte plus d'ombre : hors champ, elle
   débordait sur la page en un dégradé gris le long du bord gauche. */
@media (max-width: 899px) {
  .vd-rail[data-vd-rail='pose'] {
    position: fixed; inset-block: 0; inset-inline-start: 0; z-index: var(--plan-flottant);
    inline-size: min(84vw, 320px);
    translate: -100% 0; transition: translate .18s ease-out;
    overflow-y: auto;
  }
  .vd-rail[data-vd-rail='pose'][data-vd-ouvert='oui'] { translate: 0 0; box-shadow: var(--ombre-flottante); }
  .vd-rail .vd-rail__fermer { display: inline-flex; margin-inline-start: auto; }
  .vd-rail__voile {
    position: fixed; inset: 0; z-index: var(--plan-voile); border: 0; padding: 0;
    background: rgb(0 0 0 / .38);
  }
}
@media (prefers-reduced-motion: reduce) {
  .vd-rail[data-vd-rail='pose'] { transition: none; }
}

/* ---------- L'aperçu d'un document ou d'un média ----------

   DEMANDE DE COLOMBAN, 26/08/2026 : « preview d'images ou de documents, comme les devis dans
   Relation ou les médias du kit ». Mesure du parc le même jour : 291 liens vers un PDF, 20
   images, et QUATRE éléments d'aperçu en tout, tous locaux à Relation.

   ON N'INCRUSTE PAS LE PDF. Leçon déjà payée ailleurs : un PDF posé dans un cadre est rendu par
   la visionneuse du navigateur, qui n'est pas la nôtre, ne suit ni le thème ni la langue, et
   rend un cadre vide quand elle est désactivée. L'aperçu montre donc le TYPE, le nom et le
   poids, et ouvre le fichier là où le système sait l'ouvrir. C'est moins spectaculaire et ce
   n'est jamais faux.

   UNE IMAGE, ELLE, S'AFFICHE VRAIMENT. Elle n'a pas de visionneuse tierce, et une vignette de
   média qui ne montre pas le média ne sert à rien. */
.vd-apercus {
  display: grid;
  /* La grille se remplit d'elle-même : le nombre de colonnes vient de la place disponible, pas
     d'un point de rupture écrit à la main qui serait faux sur la moitié des écrans. */
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: var(--espace-m);
}
.vd-apercu {
  display: flex; flex-direction: column;
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-moyen));
  background: var(--surface-haute);
  overflow: hidden;
  color: inherit; text-decoration: none;
}
a.vd-apercu:hover, a.vd-apercu:focus-visible { border-color: var(--accent); }
a.vd-apercu:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* La zone de vignette garde un rapport constant : sans lui, une grille d'aperçus dont les
   images ont des proportions différentes devient un mur en dents de scie. 3 sur 2, le rapport
   d'un cliché, et le plus proche du nombre d'or parmi les rapports photographiques usuels. */
.vd-apercu__vignette {
  aspect-ratio: 3 / 2;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface);
  border-block-end: 1px solid var(--bordure);
  color: var(--texte-attenue);
}
.vd-apercu__vignette img {
  inline-size: 100%; block-size: 100%;
  /* `cover` recadre au lieu de déformer : un visage étiré est pire qu'un visage rogné. */
  object-fit: cover; display: block;
}
.vd-apercu__corps { padding: var(--espace-m); display: flex; flex-direction: column; gap: var(--espace-xxxs); }
.vd-apercu__nom {
  font-size: var(--texte-interface); font-weight: var(--graisse-appuyee); color: var(--texte);
  /* Un nom de fichier long se coupe sur une ligne au lieu de pousser la carte : la grille tient
     parce que ses cellules ne se disputent pas la largeur. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vd-apercu__meta { font-size: var(--texte-legende); color: var(--texte-attenue); font-variant-numeric: tabular-nums; }
/* ---------- L'aperçu EN LIGNE, l'arrangement de la liste de documents ----------

   CONSTAT DE COLOMBAN, 26/08/2026 : « on doit permettre les aperçus comme sur armateurscotiers.fr
   ou Palantíri, mais je ne vois pas tout cela ». Vérifié sur pièce le même jour, sur
   armateurscotiers.fr/documents : la carte de document n'est PAS une vignette dans une grille,
   c'est une LIGNE. Icône de type dans un carré, catégorie et date en surtitre, titre, résumé, et
   l'action au bord de fin. Cinquante cartes de cette forme sur la seule page publique.

   DEUX ARRANGEMENTS D'UN MÊME COMPOSANT, PAS DEUX COMPOSANTS. La grille sert le média, où l'image
   EST l'information. La ligne sert le document, où le titre et la catégorie sont l'information et
   où la vignette n'apprendrait rien : cinquante PDF donnent cinquante vignettes identiques. Le
   corps, le nom, le type et le poids gardent leurs classes : seul l'arrangement change. */
.vd-apercus--liste { display: flex; flex-direction: column; gap: var(--espace-s); }
.vd-apercu--ligne {
  flex-direction: row; align-items: center; gap: var(--espace-m);
  padding: var(--espace-m); border-radius: var(--accent-rayon, var(--courant-rayon-moyen));
}
/* LA MARQUE DE FICHIER, UNE SEULE DÉFINITION POUR L'APERÇU ET POUR LE DÉPÔT.
   Constat de Colomban, 26/08/2026 : « les affichages des pièces déposées diffèrent de ce qui est
   proposé dans les icônes de l'aperçu, à harmoniser ». Le carré teinté n'appartient donc ni à
   l'un ni à l'autre : il est à eux deux, et à ce qui viendra. */
.vd-fichier-marque {
  display: inline-flex; align-items: center; justify-content: center;
  aspect-ratio: 1; inline-size: var(--controle-hauteur); flex: none;
  border-radius: var(--courant-rayon-petit);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
}
.vd-fichier-marque--petite { inline-size: var(--controle-hauteur-compacte); }
.vd-apercu--ligne .vd-apercu__vignette { border: 0; }
.vd-apercu--ligne .vd-apercu__corps { padding: 0; flex: 1 1 auto; min-inline-size: 0; }
/* Le surtitre porte la catégorie et la date : ce qui permet de RETROUVER, avant ce qui décrit. */
.vd-apercu__surtitre {
  font-size: var(--texte-mention); text-transform: uppercase; letter-spacing: .06em;
  color: var(--texte-attenue);
}
.vd-apercu__resume {
  font-size: var(--texte-legende); color: var(--texte-attenue);
  /* Deux lignes au plus : une liste de cinquante documents dont chacun déroule son résumé entier
     n'est plus une liste, c'est un document. */
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.vd-apercu--ligne .vd-apercu__nom { white-space: normal; font-size: var(--texte-corps); }
.vd-apercu__fin { flex: none; margin-inline-start: auto; display: flex; align-items: center; gap: var(--espace-s); }

.vd-apercu__type {
  align-self: flex-start; margin-block-start: var(--espace-xs);
  font-size: var(--texte-mention); text-transform: uppercase; letter-spacing: .06em;
  padding: var(--espace-xxxs) 6px; border-radius: var(--carbone-rayon-petit);
  background: var(--surface); color: var(--texte-attenue);
}

/* ---------- Le tableau se trie et se requête ----------

   DEMANDE DE COLOMBAN, 26/08/2026 : « les tableaux doivent toujours être triables et
   requêtables ». Mesure du parc le même jour : 118 tableaux, 51 tris écrits à la main, ZÉRO
   `aria-sort`. Le tri se déclenche par un BOUTON dans l'en-tête, jamais par un `th` cliquable :
   un en-tête muni d'un écouteur n'est ni atteignable au clavier, ni annoncé comme actionnable.

   L'ÉTAT SE LIT SUR `aria-sort`, PAS SUR UNE CLASSE. C'est l'attribut que les lecteurs d'écran
   annoncent, et le donner à la machine coûte exactement le même geste que de le donner à l'œil.
   La flèche se dessine donc À PARTIR de lui : impossible d'afficher un tri qui n'est pas
   annoncé, et impossible d'annoncer un tri qui ne se voit pas. */
.vd-table__tri {
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  display: inline-flex; align-items: center; gap: var(--espace-xs);
  padding: 0; border: 0; background: none; cursor: pointer;
}
.vd-table__tri:hover { color: var(--texte); }
.vd-table__tri:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--carbone-rayon-petit); }
@media (max-width: 620px) {
  /* LOT Q. Mesuré à 390 px : le déclencheur de tri faisait 17 px de haut, la hauteur de son
     intitulé. Au doigt, c'est en dessous de tout ce qui se touche. Il prend le degré compact,
     et l'en-tête grandit d'autant : une colonne qu'on ne peut pas trier au doigt n'est pas
     triable, quoi qu'en dise la flèche. */
  .vd-table__tri { min-block-size: var(--controle-hauteur-compacte); }
}
.vd-table__fleche { inline-size: 8px; block-size: 8px; opacity: .35; }
/* La flèche est dessinée en CSS et non en caractère : un chevron typographique change de
   dessin, de chasse et de hauteur d'une police système à l'autre. */
.vd-table__fleche::before {
  content: ''; display: block; inline-size: 6px; block-size: 6px;
  border-inline-end: 1.5px solid currentColor; border-block-end: 1.5px solid currentColor;
  transform: rotate(45deg);
}
th[aria-sort='ascending'] .vd-table__fleche { opacity: 1; transform: rotate(180deg); }
th[aria-sort='descending'] .vd-table__fleche { opacity: 1; }
th[aria-sort='ascending'], th[aria-sort='descending'] { color: var(--texte); }
/* UNE LISTE VIDE SE DIT. Sans phrase, elle se lit comme un chargement en cours, et le lecteur
   attend quelque chose qui ne viendra pas. */
.vd-table__vide { padding: var(--espace-l); text-align: center; }
.vd-table tbody tr[hidden] { display: none; }
/* LES ACTIONS D'UNE LIGNE SE RANGENT EN FIN DE LIGNE, dans leur propre cellule.
   Arbitrage de Colomban du 26/08/2026 : une ligne interactive porte son action principale et
   ses actions discrètes derrière trois points. La cellule ne se replie pas et ne participe pas
   à la répartition des largeurs : les colonnes de données ne doivent pas se serrer parce qu'une
   ligne porte un bouton de plus. */
/* ELLES SONT TOUJOURS VISIBLES, et ce n'est plus un survol.
   La première version les révélait au survol. Question de Colomban, 26/08/2026 : « comment ça
   s'adapte au mobile qui n'a pas de hover ? » La réponse honnête est qu'un motif qui demande un
   survol ne s'adapte pas, il se remplace. Une action cachée jusqu'au survol n'est découverte par
   personne qui ne promène pas sa souris, et la borne « hover: hover » ment sur un portable
   tactile, où le survol existe pour le navigateur et pas pour la main. Les actions restent donc
   visibles, discrètes par leur FORME et non par leur absence.

   ET ELLES TIENNENT SUR UNE LIGNE. Le menu à trois points s'appuie sur un dépliant natif, qui
   est un bloc : dans une cellule, il passait sous le bouton et la ligne doublait de hauteur.
   Constaté sur capture le même jour. */
.vd-table__actions {
  inline-size: 1%;
  white-space: nowrap;
  text-align: end;
  vertical-align: middle;
}
.vd-table__groupe {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--espace-xs);
}
/* L'ESPACE RÉSERVÉ QUAND LA LIGNE N'A PAS DE MENU. Arbitrage de Colomban, 26/08/2026 : « un même
   élément doit toujours être affiché sur toutes les lignes, même si certaines n'ont pas la
   fonctionnalité ». On réserve, on n'ajoute pas un bouton qui ne fait rien : un menu présent mais
   vide est un piège, on l'ouvre et il ne propose rien. La réserve ne se voit pas, ne se touche
   pas, ne s'annonce pas. Elle tient la colonne, et c'est tout ce qu'on lui demande. */
.vd-table__reserve { display: block; block-size: 1px; flex: none; pointer-events: none; }
/* HORS ÉCRAN MAIS LU. Un entête de colonne d'actions n'a rien à montrer et doit pourtant être
   annoncé : une colonne sans entête se lit « colonne 4 ». Le socle n'avait aucun utilitaire pour
   cela, et le sélecteur cherchable en portait une copie inline. Premier morceau de la couche
   d'états et d'utilitaires que le relevé JavaScript du 26/08/2026 a montrée manquante. */
.vd-hors-ecran {
  position: absolute; inline-size: 1px; block-size: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ---------- Boutons ---------- */
/* DEUX RÈGLES QUI DÉCIDENT DE TOUT ALIGNEMENT, ET QUI MANQUAIENT.

   1. LA BOÎTE SE MESURE BORDURE COMPRISE. Sans cela, une hauteur minimale de 40 px donne 42 dès
      qu'il y a une bordure de 1 px, et deux contrôles dont les bordures diffèrent ne s'alignent
      jamais. Mesuré le 26/08/2026 : le résumé des filtres faisait 42 là où tout le reste
      faisait 40.

   2. UN CONTRÔLE S'ALIGNE AU MILIEU DE LA LIGNE, jamais sur la ligne de base du texte. La ligne
      de base d'un bouton d'icône est celle de son dessin, celle d'un bouton celle de son
      libellé : deux voisins de MÊME hauteur se retrouvaient décalés de 5 px. Constat de Colomban
      sur capture, deux fois de suite, parce que la première correction n'avait traité que la
      hauteur et pas l'alignement. */
.vd-button, .vd-iconbtn, .vd-champ__saisie, .vd-recherche__saisie,
.vd-carte__menu > summary {
  box-sizing: border-box;
  vertical-align: middle;
}

/* UNE SEULE HAUTEUR POUR TOUT CE QUI SE CLIQUE OU SE SAISIT, --controle-hauteur.
   Constat de Colomban sur capture, 26/08/2026 : deux boutons voisins ne s'alignaient pas. Le
   bouton faisait 43 px, calculés par son rembourrage et la hauteur de ligne héritée, donc
   variables avec la taille du texte de la page ; le bouton d'icône en faisait 40, écrits en dur.
   Une ligne de formulaire ne tient que si le bouton, le champ, le sélecteur et le bouton d'icône
   partagent la même hauteur, et cette hauteur est un JETON, pas une valeur recopiée. */
.vd-button {
  /* UN LIEN HABILLÉ EN BOUTON N'EST PAS SOULIGNÉ. Constat de Colomban, 26/08/2026 : « pourquoi le
     bouton Ouvrir est souligné alors qu'aucun bouton ne propose cela ? » Le soulignement venait
     du navigateur, pour tout `<a href>`, et le socle ne le retirait pas. La règle valait pour
     tous les liens du parc habillés en bouton, pas pour ce seul exemple. */
  text-decoration: none;
  font: inherit;
  font-weight: var(--graisse-appuyee);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: var(--controle-hauteur);
  padding-block: 0;
  padding-inline: var(--espace-l);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  border: 1px solid var(--accent-plein, var(--accent));
  /* LE FOND PLEIN PREND LA VARIANTE CALCULEE, PAS L'ACCENT BRUT.
     Arbitrage de Colomban, 28/08/2026, option B. Mesure du jour : quatre accents de produit
     rendent moins de 4,5 pour 1 sous du texte blanc, Cyber a 2,87 et Yield a 3,47. Assombrir la
     couleur de marque aurait deplace le vert de Cyber de vingt-trois pour cent ; une couleur de
     marque n'a pas a payer pour un usage particulier. `--accent-plein` est donc la variante
     calculee au build, reservee aux surfaces qui portent du blanc, et l'accent garde sa valeur
     exacte partout ailleurs. Le repli sur `--accent` vaut pour les onze accents deja conformes,
     qui n'ont pas de variante et n'en ont pas besoin. */
  background: var(--accent-plein, var(--accent));
  color: #fff;
}
/* LE DISCRET PREND L'ENCRE, PAS L'ACCENT. Mesure du 27/08/2026 sur les quatre chartes de la
   galerie : avec l'accent en texte, il rendait 4,07 pour 1 sur la charte VAIATA et 2,77 sur une
   charte a surfaces sombres. Une couleur choisie pour porter du blanc n'est pas faite pour etre
   lue. L'encre se derive de l'accent par defaut, et une charte peut la poser elle-meme. */
.vd-button--discret { background: transparent; color: var(--accent-encre, var(--accent)); }
/* RETRAIT : révoquer un accès, déconnecter une application, couper une lecture. Le liseré
   ambre dit qu'on enlève quelque chose, sans crier au danger : ces gestes se défont, la
   révocation d'un accès n'efface rien de ce qui a été déposé. Le rouge est réservé à ce qui
   ne se rattrape pas, et l'employer ici le dévaluerait pour le jour où il servira vraiment.
   Demande de Colomban, 22/08/2026. */
.vd-button--retrait { background: transparent; color: var(--alerte); border-color: var(--alerte); }
.vd-button--retrait:hover { background: color-mix(in srgb, var(--alerte) 8%, transparent); }
.vd-button--retrait:focus-visible { outline-color: var(--alerte); }
.vd-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Widget de retours. Injecté dans tous les espaces, jamais copié dans un espace. ---------- */
.vd-retours-bouton {
  position: fixed;
  inset-inline-end: 18px;
  bottom: 18px;
  z-index: var(--plan-au-dessus-de-tout);
  display: inline-flex;
  align-items: center;
  gap: var(--espace-s);
  padding: var(--espace-s) var(--espace-m);
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: var(--navy);
  color: #fff;
  font: 600 var(--texte-interface)/1 var(--police-interface);
  cursor: pointer;
  box-shadow: var(--ombre-levee);
}
.vd-retours-bouton[aria-pressed='true'] { background: var(--accent); }
.vd-retours-cible { outline: 2px dashed var(--accent) !important; outline-offset: 2px; cursor: crosshair !important; }
/* ⚠ `box-sizing` EST ICI PARCE QUE SON ABSENCE SORTAIT LE PANNEAU DE L'ÉCRAN.
   Mesuré le 31/08/2026, après un signalement de Colomban sur ce panneau : il déclare
   `width: min(340px, calc(100vw - 36px))` pour tenir sur un téléphone, mais sans `border-box` le
   retrait de 21 px de chaque côté et les deux bordures s'AJOUTENT à cette largeur. Rendu réel :
   384 px pour 340 déclarés. À 390 px de fenêtre, son bord gauche tombait à **-12 px** ; à 360, à
   -26. Le panneau était coupé sur un téléphone, et sur les vingt-sept espaces, puisque c'est le
   widget du socle.
   Aucun banc ne l'avait vu : le marcheur n'ouvre pas le panneau de retours, il refuse de presser
   ce qui envoie. Le défaut vivait derrière un geste que rien ne mesure. */
.vd-retours-panneau {
  box-sizing: border-box;
  position: fixed;
  inset-inline-end: 18px;
  bottom: 68px;
  z-index: var(--plan-au-dessus-de-tout);
  width: min(340px, calc(100vw - 36px));
  background: var(--surface);
  color: var(--texte);
  border: 1px solid var(--bordure);
  /* PAS DE LISERÉ DE TÊTE SUR CE PANNEAU, et c'est une exception assumée à la charte.
     Arbitrage de Colomban, 31/08/2026 : « il reste le bandeau typique de Claude en haut de la
     modale ». Le liseré de 3 px coiffe les tuiles et les cartes d'un PRODUIT, et il y prend la
     couleur du produit. Ce panneau-ci n'appartient à aucun produit : c'est l'outil de retours de
     VAIATA, posé par-dessus l'espace d'un client. Il prenait donc l'accent du client, violet chez
     Syclef, ce qui le faisait lire comme une bannière étrangère au lieu d'un outil discret.
     Un outil qui se pose sur le travail de quelqu'un ne porte pas de bannière. */
  border-radius: var(--courant-rayon-moyen);
  padding: var(--espace-l);
  font: var(--texte-corps)/1.45 var(--police-interface);
  box-shadow: var(--ombre-flottante);
}
.vd-retours-panneau h2 { margin: 0 0 4px; font-size: var(--texte-titre); }
.vd-retours-panneau p { margin: 0 0 var(--espace-m); font-size: var(--texte-legende); color: var(--texte-attenue); word-break: break-word; }
/* M4, APPLIQUÉ le 02/09/2026 (délégation « complète l intégralité de l unification en autonomie »,
   recommandation du backlog) : la zone de saisie porte `.vd-champ__saisie` et la règle locale est
   retirée — elle recouvrait le socle par spécificité (`.vd-retours-panneau textarea` bat
   `.vd-champ__saisie`). Ce qui change et se voit : fond `--surface-haute` au lieu de `--fond`,
   boîte en border-box (114 px rendus → 96, la hauteur déclarée), rembourrage 9/11 au lieu de 8.
   Mesuré par l effet sur trois espaces le 02/09/2026. Aucun déplacement de repère : le panneau
   est hors flux (`npm run adoption`, conforme). */
.vd-retours-actions { display: flex; gap: var(--espace-s); margin-top: var(--espace-m);
  align-items: center; }
/* Le lien de suivi se range au bout, séparé des deux actions : il ne concurrence pas « Envoyer »,
   il répond à la question d'après, « et ensuite ? ».
   IL N'A PAS DE CLASSE À LUI, ET C'EST VOULU. Lui en donner une en aurait fait un composant neuf
   du socle, qu'AO-1 exige de faire arbitrer par Colomban, pour un lien qui n'est qu'un enfant du
   widget de retours. Le témoin du registre me l'a refusé, à juste titre. */
.vd-retours-actions > a { margin-inline-start: auto; font-size: var(--texte-legende); }
.vd-retours-etat { margin-top: var(--espace-s); font-size: var(--texte-legende); }

@media (prefers-reduced-motion: reduce) {
  .vd-retours-bouton, .vd-retours-panneau { transition: none; }
}

/* ---------- Provenance d'un espace copié depuis un autre dépôt ---------- */
.vd-provenance {
  margin: 0;
  padding: 8px var(--espace-l);
  background: var(--surface-haute);
  border-bottom: 1px solid var(--bordure);
  color: var(--texte-attenue);
  font: var(--texte-legende)/1.4 var(--police-interface);
}
.vd-provenance code { font-family: var(--police-donnee); }

/* ---------- Tuile. Liseré EN HAUT, à la couleur du produit. Jamais à gauche. ----------
   Règle de charte : le trait de tête de 3 px est l'invariant de marque, il coiffe la tuile.
   Un liseré latéral n'appartient pas au système VAIATA. */
/* LE LISERÉ DE TÊTE MET EN AVANT, DONC IL NE PEUT PAS ÊTRE PARTOUT. Arbitrage de Colomban du
   26/08/2026 : une variante sans liseré, pour les tuiles qui ne sont pas mises en avant. Une
   grille de douze tuiles toutes coiffées du même trait ne met plus rien en avant, elle raye
   l'écran. */
/* LES TUILES ONT UN CONTENEUR, ET C'EST LUI QUI DIT LEUR LARGEUR.
   Constat de Colomban, 26/08/2026 : « pourquoi Mise en avant et Avec options n'ont pas la même
   largeur ? » Mesuré : 253, 596 et 241 px. Le composant existait sans conteneur, les tuiles
   étaient donc posées côte à côte et chacune prenait la largeur de SON texte. Une grille de
   tuiles dont chaque cellule a la taille de son contenu n'est pas une grille, c'est une phrase.

   La même mécanique que pour les aperçus : le nombre de colonnes vient de la place disponible,
   et toutes les cellules d'une rangée ont la même largeur ET la même hauteur. */
.vd-tuiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--espace-m);
  align-items: stretch;
}
.vd-tuiles > .vd-tuile { block-size: 100%; }

.vd-tuile {
  background: var(--surface-haute);
  border: 1px solid var(--bordure);
  border-top: 3px solid var(--accent);
  border-radius: 0 0 var(--accent-rayon, var(--courant-rayon-moyen)) var(--accent-rayon, var(--courant-rayon-moyen));
  padding: var(--espace-m);
  color: var(--texte);
  text-align: start;
}
/* LA VARIANTE SANS LISERÉ, ET ELLE VIENT APRÈS SA BASE.
   Le liseré de tête met en avant, donc il ne peut pas être partout : une grille de douze tuiles
   toutes coiffées du même trait ne met plus rien en avant, elle raye l'écran.

   UN MODIFICATEUR A LA MÊME SPÉCIFICITÉ QUE SA BASE. À égalité, c'est l'ordre dans la feuille
   qui tranche, et placé au-dessus il ne s'appliquait pas : la tuile discrète portait encore le
   liseré qu'elle annonçait ne pas avoir. Mesuré le 26/08/2026, 3 px là où la règle en demandait
   1, et vu à l'œil par Colomban avant que le banc ne le voie. */
.vd-tuile--discrete {
  border-block-start: 1px solid var(--bordure);
  border-start-start-radius: var(--accent-rayon, var(--courant-rayon-moyen));
  border-start-end-radius: var(--accent-rayon, var(--courant-rayon-moyen));
}

/* ---------- Co-branding client dans le bandeau ----------
   Logo si l'espace en fournit un, sinon monogramme sobre. Jamais de nom nu.

   LA MARQUE NE DICTE PAS LA HAUTEUR DU BANDEAU, elle s'y range. Arbitrage de Colomban,
   22/08/2026 : « une hauteur cohérente avec la hauteur du bandeau ». Le défaut mesuré le
   même jour : le bandeau valait 55, 57, 61 ou 64 px selon ce que la marque du client
   pesait, un logo de 30 px l'ayant gonflé de 7 px chez VAIATA ID et Cyber Yeu Continent.
   Un client dictait la mise en page de la plateforme.

   Une seule valeur, `--marque-hauteur`, sert au logo comme au monogramme : les deux
   rendent désormais la même boîte, et le bandeau garde la sienne quoi qu'ils portent.

   RELEVÉ le 10/09/2026 sur arbitrage de Colomban (« passe le logo syclef en plus gros sur
   l'espace, doit être mis à jour au socle ») : `--marque-hauteur` passe de 26 à 40px,
   alignée sur `--controle-hauteur`, la hauteur déjà tenue par les autres contrôles du
   bandeau (langue, compte). Le lanceur de recherche, à `--controle-hauteur-ample` (48px),
   reste le plus grand élément de la barre : le bandeau ne grandit donc pas. */
.vd-client { display: inline-flex; align-items: center; gap: 9px; }
/* Plate blanche : un logo client est presque toujours dessiné pour un fond clair, il
   disparaîtrait sur le navy du bandeau. Le rembourrage est compris dans la hauteur, sans
   quoi la boîte du logo dépasserait celle du monogramme de six pixels. */
.vd-client__logo {
  height: var(--marque-hauteur);
  box-sizing: border-box;
  width: auto;
  display: block;
  background: #fff;
  border-radius: var(--carbone-rayon-petit);
  padding: var(--espace-xxs) 7px;
}
.vd-client__monogramme {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-width: var(--marque-hauteur);
  height: var(--marque-hauteur);
  padding: 0 6px;
  /* LE MONOGRAMME EST UN BADGE PLEIN, PAS UN CADRE TRANSLUCIDE.
     **Mesuré le 03/09/2026, et c'est une régression que j'ai créée.** Il portait une bordure
     blanche à 35 % et une encre blanche, dessinées pour le bandeau navy. Le bandeau étant passé
     au clair le jour même sur arbitrage de Colomban, le monogramme est devenu blanc sur clair :
     contraste mesuré à **1,06 pour 1**, quand le seuil AA est à 4,5. Il était invisible.
     La forme retenue est celle que Relation pose depuis toujours sur son badge client : un fond
     navy et une encre blanche, qui rendent 17,06. Un badge d'identité se lit sur tout fond parce
     qu'il porte le sien. */
  border: 0;
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  background: var(--navy);
  font: 700 var(--texte-mention)/1 var(--police-interface);
  letter-spacing: .06em;
  color: #fff;
}
/* Le nom du client suit l'encre du texte : il valait `#C9CCD9`, pensé pour un fond sombre, et
   rendait 1,51 pour 1 sur le bandeau clair. */
/* LE BANDEAU NE GRANDIT PAS AVEC LA LONGUEUR D'UN NOM DE CLIENT. Mesuré le 04/09/2026 pendant la
   migration des six bandeaux : un nom long enroulait sur trois lignes et poussait la barre de 68 à
   102 px, quand un nom court la laissait à 76. Une barre dont la hauteur dépend du client n'est pas
   un composant, c'est un accident. Le nom tient sur une ligne et se coupe proprement ; il reste
   entier dans son `title`. */
.vd-client__nom {
  font-size: var(--texte-interface); color: var(--texte);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vd-topbar__client { min-inline-size: 0; }
.vd-topbar__client .vd-client { min-inline-size: 0; }

/* ---------- Sélecteur de langue ----------
   Posé par le build dans la barre haute de tout espace qui active plus d'une langue. Arbitrage
   de Colomban, 22/08/2026 : le socle le place, un espace ne choisit pas où il va. Un même geste
   au même endroit dans tous les espaces vaut mieux que douze placements réfléchis.

   Il se tient à la fin de la barre, après la marque du client, et il reste visible en mobile :
   c'est le seul moyen pour un lecteur arabophone de retrouver sa langue si le défaut est faux. */
.vd-langues { display: inline-flex; align-items: center; gap: var(--espace-xxxs); margin-inline-start: auto; flex: none; }
.vd-langues__choix {
  box-sizing: border-box;
  min-block-size: var(--controle-hauteur-compacte);
  padding-block: 0;
  font: inherit;
  font-size: var(--texte-legende);
  font-weight: var(--graisse-appuyee);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #C9CCD9;
  background: transparent;
  border: 0;
  border-radius: var(--carbone-rayon-petit);
  padding-inline: 7px;
  cursor: pointer;
}
.vd-langues__choix:hover { color: #fff; background: rgb(255 255 255 / .1); }
.vd-langues__choix[aria-pressed='true'] { color: #fff; background: rgb(255 255 255 / .18); }
.vd-langues__choix:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Une traduction en retard le DIT. Elle ne fait pas passer un texte périmé pour du neuf : c'est
   la règle que `scripts/traduction.mjs` tient déjà pour les pages commerciales, portée ici. */
.vd-langues__retard {
  font-size: var(--texte-mention);
  color: var(--alerte);
  background: color-mix(in srgb, var(--alerte) 18%, transparent);
  border-radius: var(--carbone-rayon-petit);
  padding: var(--espace-xxxs) 6px;
  margin-inline-start: var(--espace-s);
}

/* Sur un téléphone, la marque du client disparaît. Arbitrage de Colomban du 22/08/2026 :
   « disparaît en mobile dans tous les cas ». La largeur y va au nom du produit et à
   l'action, et un logo client de 26 px pris entre les deux ne se lit pas. Le point de
   bascule est celui que le socle emploie déjà pour les filtres, il ne s'en invente pas
   un second. */
@media (max-width: 719px) {
  .vd-topbar__client, .vd-client { display: none; }
}

/* ---------- Icônes. Tracés dans design-system/icones.js, jamais recopiés dans un espace. ---------- */
.vd-icone {
  display: block;
  flex: none;
  color: inherit;
}

/* ---------- Rythme des boutons ----------
   Un bouton ne touche jamais son voisin ni le bloc suivant. Défaut vu le 19/08/2026 sur VAIATA ID :
   « Ajouter un document » collait à l'encart rouge des pièces manquantes. L'espacement appartient
   au système, pas à chaque espace, sinon il est oublié une fois sur deux. */
/* UN COMPOSANT NE PORTE PAS SON PROPRE ESPACEMENT. Le bouton portait une marge basse par
   défaut, annulée dans .vd-actions, dans .vd-entete, puis dans le pied de modale : une liste
   d'exceptions qui grandit est le signe que le défaut est faux. Dans la barre d'application,
   elle n'était annulée nulle part, et le bouton Créer se retrouvait 14 px plus haut que
   l'avatar posé à côté de lui. Constat de Colomban sur capture, 26/08/2026.
   L'espacement entre voisins appartient au conteneur, par gap, jamais à l'élément. */
.vd-button + .vd-button { margin-inline-start: var(--espace-s); }

/* ---------- Entête de page ----------
   Le titre à gauche, l'action principale à DROITE, sur la même ligne. Le lecteur descend la page
   par la gauche et cherche l'action par la droite : c'est le motif de VAIATA Relation, retenu
   comme règle de gamme le 19/08/2026. */
.vd-entete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-m);
  flex-wrap: wrap;
  margin-bottom: var(--espace-m);
}
.vd-entete > :first-child { margin: 0; }
.vd-actions { display: flex; align-items: center; gap: var(--espace-s); margin-inline-start: auto; flex-wrap: wrap; }

/* ---------- Barre d'outils de liste ----------
   Les puces du filtre le plus courant à gauche, le compte et les boutons d'outil à droite.
   Les filtres secondaires restent REPLIÉS derrière leur bouton : une liste ne s'ouvre pas sur
   quatre sélecteurs que personne n'a demandés. */
/* UNE SEULE LIGNE : les pastilles rapides au début, le compte et les boutons d'outils en fin.
   Arbitrage de Colomban du 26/08/2026, sur la forme éprouvée de Relation Marais Poitevin : le
   bouton de recherche et le bouton de filtres sont SUR LA MÊME LIGNE que les pastilles, en fin,
   et non sur une ligne à eux. Une seconde ligne d'outils repousse le contenu sans rien apporter.
   La ligne ne se replie PAS : les pastilles défilent au doigt, les outils restent atteignables. */
.vd-outils { display: flex; align-items: center; gap: var(--espace-m); flex-wrap: nowrap; margin-bottom: var(--espace-m); }
/* SUR UN ECRAN ETROIT, LA LIGNE SE REPLIE. La regle du 26/08 vise le poste de travail : une
   seconde ligne d outils y repousse le contenu sans rien apporter. A 420 px, la meme ligne ne
   tient pas, et forcer le non-repli ferait deborder la page. Ce n est pas une exception, c est
   la meme intention appliquee a une place differente. */
/* ---------- LA BARRE D'OUTILS AU DOIGT ----------

   ARBITRAGE DE COLOMBAN, 26/08/2026, deux captures de Palantíri à l'appui : « sur mobile on a un
   souci de place, donc la recherche doit disparaître et on garde que la recherche globale hyper
   contextualisée, plutôt que de pousser des composants sur un tout petit écran. Idem pour les
   filtres, cachés derrière les trois points, et les pastilles fusionnées, deux ou trois ça
   fonctionne. »

   TROIS DÉCISIONS, ET UNE SEULE RAISON. Sur 390 px, la barre portait cinq objets : pastilles,
   compte, recherche, filtres, carte. Empilés, ils repoussaient la liste hors de l'écran ;
   comprimés, aucun n'était touchable. On ne réduit donc pas, on RETIRE.

   1. LA RECHERCHE DE LISTE DISPARAÎT, y compris sa loupe. La recherche transverse du bandeau
      cherche déjà dans toute l'application, en commençant par la catégorie de la page : deux
      champs de recherche sur un téléphone, dont l'un ne cherche que la page, sont un champ de
      trop et une hésitation à chaque fois.
   2. LES OUTILS PASSENT DERRIÈRE LES TROIS POINTS. Filtres et carte ne sont pas le geste courant
      d'un écran de service consulté debout ; ils restent à un toucher, pas à zéro.
   3. LES PASTILLES SE LIMITENT À TROIS. Au-delà, elles défilaient sous le pouce sans qu'on sache
      qu'il y avait une suite. Trois tiennent dans la largeur et se voient toutes.
   Ce qui est retiré n'est jamais perdu : tout se retrouve dans le menu ou dans la recherche
   transverse. */
@media (max-width: 620px) {
  .vd-outils { flex-wrap: nowrap; gap: var(--espace-s); }
  .vd-outils__recherche, .vd-outils [data-vd-bascule-ligne] { display: none; }
  .vd-outils__droite > .vd-iconbtn:not(.vd-outils__plus) { display: none; }
  /* LES PASTILLES SE FONDENT EN UN SÉLECTEUR SEGMENTÉ, comme sur Palantíri.
     Constat de Colomban, 26/08/2026, deux captures à l'appui : « les pastilles ne sont pas comme
     sur Palantíri, visibles clairement et fusionnées ». En les gardant séparées, on obtenait
     trois pastilles de largeurs inégales, dont la deuxième coupée en « À co… », serrées contre
     un compte : trois formes à lire au lieu d'une.
     Fondues, elles font UN objet, un plateau à trois parts égales : la part active se détache
     par son fond clair et son ombre, les autres sont le plateau. La largeur égale n'est pas une
     coquetterie, c'est ce qui rend la cible prévisible sous le pouce. */
  .vd-chips {
    flex: 1 1 auto; gap: 0; padding: var(--espace-xxs);
    background: var(--surface); border-radius: 999px;
  }
  .vd-chips .vd-chip {
    flex: 1 1 0; min-inline-size: 0; justify-content: center;
    border: 0; background: none; padding-inline: var(--espace-s);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block;
    line-height: var(--controle-hauteur-compacte);
    font-size: var(--texte-legende);
  }
  .vd-chips .vd-chip.is-actif, .vd-chips .vd-chip[aria-pressed='true'] {
    background: var(--surface-haute); color: var(--texte);
    box-shadow: var(--ombre-posee);
  }
  /* Au-delà de trois, elles ne tiennent plus : la suite est dans le menu. */
  .vd-chips .vd-chip:nth-of-type(n + 4) { display: none; }
  /* Le compte disparaît : le tableau juste dessous porte déjà ses lignes, et sur 390 px c'est
     lui qu'on regarde. */
  .vd-outils .vd-compte { display: none; }
}
/* Au large, le menu de débordement n'a pas lieu d'être : ses entrées sont déjà dans la barre. */
.vd-outils__plus { display: none; }
@media (max-width: 620px) { .vd-outils__plus { display: inline-flex; } }
.vd-outils__droite { display: flex; align-items: center; gap: var(--espace-s); margin-inline-start: auto; flex: none; }

/* LA RECHERCHE S'OUVRE DANS LA LIGNE, ET ELLE PREND LA PLACE DES PASTILLES.
   Arbitrage de Colomban, 26/08/2026, deux captures de Relation à l'appui : « c'est beaucoup plus
   intéressant d'ouvrir la recherche en optimisant l'espace comme sur Relation ». Le champ ne
   descend pas sur une seconde ligne et ne recouvre rien : il s'insère entre les pastilles et le
   compte, et ce sont les pastilles qui cèdent la largeur, puisqu'elles défilent déjà.

   POURQUOI CE N'EST PAS UN SIMPLE MASQUAGE. Un champ posé là et masqué occuperait sa place dans
   le calcul de la ligne, ou la ferait sauter en apparaissant. Il part donc à zéro de large, et
   la largeur est ce qui s'anime : la ligne ne change jamais de hauteur, rien ne se déplace
   verticalement, et le regard reste où il était. */
/* LE ROGNAGE NE VAUT QUE PENDANT LA FERMETURE. Constat de Colomban, 26/08/2026 : « pourquoi des
   angles bleus ? » Le champ ouvert prenait le focus, son halo débordait de la région, et
   `overflow: hidden` le coupait en quatre fragments d'angle. Un halo à moitié rogné se lit comme
   un défaut d'affichage, pas comme un focus. Ouvert, la région ne rogne plus rien. */
.vd-outils__recherche {
  flex: 0 1 0; inline-size: 0; min-inline-size: 0; overflow: hidden;
  transition: flex-grow .16s ease-out, flex-basis .16s ease-out;
}
.vd-outils__recherche[data-vd-ouvert='oui'] {
  /* BASE NULLE ET PLANCHER DE 20 CARACTÈRES. Avec une base de 34ch, le champ ouvert ne tenait plus
     sur la ligne du compte et des outils dès que celle-ci se remplissait, et la barre prenait une
     ligne de plus : 85 à 138 px à 960 px sur l'Annuaire SYCLEF, 640 px sur Intelligence (#77d7d5e8).
     Partant de zéro, il prend la place libre et ne passe à la ligne que sous 20 caractères :
     mesuré de 640 à 1920 px, aucun saut, champ de 191 px au moins, compte jamais rogné. */
  flex: 1 1 0; min-inline-size: min(20ch, 100%); inline-size: auto; overflow: visible;
  /* UNE BORNE, ET UN ÉCART FRANC AVEC LE COMPTE. Constat de Colomban, 26/08/2026 : « quand on
     active la recherche, elle peut passer sur le 3 sur 3 escales, en les effaçant totalement ».
     Mesuré à six largeurs, de 630 à 1280 px, et avec un compte long, « 128 escales sur 10 342 » :
     le compte n'est jamais rogné, il garde ses 118 px. Ce qui est vrai, c'est que le champ
     arrivait à 13 px de lui, donc au ras : à l'œil, il a l'air d'être sur le point de le manger.
     L'écart passe à 21 px, et le champ cesse de grandir avant d'y toucher. */
  max-inline-size: 52ch;
  margin-inline-end: var(--espace-s);
}
/* LE COMPTE NE SE COMPRIME JAMAIS. Il tenait jusqu'ici par un effet de bord, le groupe de fin
   étant déclaré non flexible. Une propriété qui tient par accident se perd au premier
   remaniement : elle s'écrit. */
.vd-outils .vd-compte { flex: none; white-space: nowrap; }
/* Le champ garde sa hauteur de contrôle même replié : sinon la ligne se tasse à la fermeture. */
.vd-outils__recherche .vd-recherche { inline-size: 100%; }
@media (prefers-reduced-motion: reduce) { .vd-outils__recherche { transition: none; } }
.vd-compte { font-size: var(--texte-legende); color: var(--texte-attenue); font-variant-numeric: tabular-nums; }

/* AU-DESSUS DE 620 PX, LES PUCES PASSENT À LA LIGNE AU LIEU DE DÉFILER. Elles défilaient sous une
   barre masquée : recherche ouverte, la rangée tombait à 0-341 px et les puces disparaissaient
   sans signe de suite (client SYCLEF, 22/10/2026, Annuaire à 1286 px ; mesuré sur Network SYCLEF
   et Intelligence, T-059). Les puces prennent leur propre ligne, le compte, la recherche et les
   outils la suivante : la barre garde la même hauteur quand la recherche s'ouvre ou quand la vue
   change le nombre d'outils (Annuaire SYCLEF, 53 px de saut en passant au tableau). Sous 620 px,
   le plateau segmenté plus haut reste sur une ligne. */
.vd-chips { display: flex; gap: var(--espace-s); flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; flex: 1 1 0; min-width: 0; }
@media (width > 620px) {
  .vd-outils:has(.vd-chips) { flex-wrap: wrap; }
  .vd-chips { flex: 1 1 100%; max-width: 100%; flex-wrap: wrap; overflow-x: visible; }
}
.vd-chips::-webkit-scrollbar { display: none; }
.vd-chip {
  /* LA PASTILLE PREND LE DEGRÉ COMPACT DE L'ÉCHELLE. Elle valait 36 px, calculés à la main, une
     hauteur qui n'existait nulle part ailleurs. Trois degrés suffisent : compact pour ce qui se
     pose en grappe, courant pour la ligne de formulaire, ample pour ce qui ouvre une section. */
  box-sizing: border-box;
  flex: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  min-block-size: var(--controle-hauteur-compacte);
  border: 1px solid transparent;
  border-radius: 999px;
  padding-block: 0;
  padding-inline: 14px;
  background: var(--surface);
  color: var(--texte-attenue);
  font: 600 var(--texte-legende)/1 var(--police-interface);
}
.vd-chip:hover { color: var(--texte); }
.vd-chip.is-actif { background: var(--navy); color: #fff; }
.vd-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.vd-iconbtn {
  /* Même jeton que le bouton, et le RAYON DU REGISTRE au lieu de 9 px écrits en dur : sur un
     produit du registre carbone, dont le rayon vaut 3 px, un bouton d'icône à 9 px jurait avec
     tout ce qui l'entourait. */
  inline-size: var(--controle-hauteur);
  block-size: var(--controle-hauteur);
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  background: var(--surface-haute);
  color: var(--texte-attenue);
  cursor: pointer;
}
.vd-iconbtn:hover { color: var(--texte); border-color: var(--texte-attenue); }
.vd-iconbtn[aria-pressed='true'] { background: var(--navy); border-color: var(--navy); color: #fff; }
/* LE REGISTRE DISCRET EXISTAIT POUR LE BOUTON, PAS POUR LE BOUTON D'ICÔNE, et c'est ce trou qui a
   fait poser un bouton cerclé de 40 px dans une tête de tuile de Relation, à côté d'un intitulé de
   12 px. Constat de Colomban, 27/08/2026, sur capture. La forme cerclée dit « action principale »
   et une action de tête de tuile n'en est pas une : elle accompagne le titre, elle ne le concurrence
   pas. Même raisonnement que .vd-button--discret, même registre. */
.vd-iconbtn--discret {
  border-color: transparent;
  background: transparent;
}
.vd-iconbtn--discret:hover { border-color: transparent; background: var(--panel-2, rgb(0 0 0 / .05)); }
/* LE DEGRÉ COMPACT, celui du menu de tuile. Deux affordances voisines dans la même tête, l'une à 40
   et l'autre à 28, ne se lisent pas comme une paire. Le menu prenait déjà le degré compact ; le
   bouton d'icône ne pouvait pas l'atteindre. */
/* LE DEGRE COMPACT DU BOUTON D ICONE EST SORTI DU SOCLE le 29/08/2026, arbitrage de
   Colomban : « Le retirer, poser la tete de tuile ». Sa question du 28, « pourquoi besoin
   d un degre compact ? », contenait la reponse : ce n est pas le bouton qui est compact,
   c est le CONTEXTE. `.vd-carte__tete` impose desormais son degre a ce qu elle contient. */
.vd-iconbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Pied de carte : les actions à droite ----------
   Le README posait déjà la règle pour l'entête de PAGE, « l'action principale est en haut à
   droite ». Elle n'existait pas pour les CARTES, et chaque espace alignait donc ses boutons de
   tuile comme il l'entendait. Règle de Colomban du 22/08/2026 : dans une carte comme dans une
   page, le lecteur descend par la gauche et cherche l'action par la droite.

   Le pied porte un filet léger quand la carte a du contenu au-dessus : sans lui, un bouton
   posé à droite flotte sans appartenir à rien. Les actions secondaires viennent AVANT la
   principale, comme dans l'entête de page. */
/* ---------- LA TÊTE DE TUILE ----------

   ARBITRAGE DE COLOMBAN, 29/08/2026, sur sa propre question du 28 : « pourquoi besoin d'un degré
   compact ? » pour le bouton d'icône. Sa question était la bonne, et elle contenait la réponse :
   CE N'EST PAS LE BOUTON QUI EST COMPACT, C'EST LE CONTEXTE. Une tête de tuile est dense par
   nature, et c'est elle qui doit imposer son degré à ce qu'elle contient, comme le `nowrap` est
   descendu dans la cellule de tableau la veille.

   CE QUE ÇA REMPLACE. Le degré compact du bouton d'icône sort du socle. Il ne servait QUE là,
   quatre emplois dans tout le parc, tous le même bouton, et il ajoutait un degré d'échelle pour
   un seul contexte, ce que le socle interdit ailleurs.

   LA MESURE QUI L'A OUVERT : le parc compte 289 têtes de tuile écrites à la main, 72 dans chacun
   des quatre espaces Relation et une chez Bunker Check, et le socle n'en avait aucune. Il portait
   la note, le pied et le menu d'une carte, jamais sa tête.

   L'ALIGNEMENT COMMANDE LE REMBOURRAGE, et c'est le seul point qui ne se déduit pas de l'échelle.
   La tête et le corps d'une carte doivent partager leur dégagement horizontal, sinon leurs textes
   ne tombent pas sur la même verticale. Chez Relation les deux valaient 16 px, donc alignés mais
   hors échelle ; ils prennent ensemble le degré large, qui est aussi celui du rembourrage de la
   carte du socle. La tête s'aligne donc sur le corps ET sur la carte, ce qu'aucun des trois ne
   faisait avant. */
.vd-carte__tete {
  display: flex; align-items: center; gap: var(--espace-s);
  padding: var(--espace-m) var(--espace-l);
  border-block-end: 1px solid var(--bordure);
  font-size: var(--texte-legende); font-weight: var(--graisse-appuyee);
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--texte-attenue);
}
/* UNE TÊTE QUI COIFFE UN TABLEAU S'ALIGNE SUR SA PREMIÈRE CELLULE, PAS SUR LA CARTE.
   Mesuré le 29/08/2026, en basculant les 161 têtes : une carte de Relation coiffe un tableau posé
   bord à bord, dont les cellules portent leur propre dégagement. La tête ne peut pas s'aligner à
   la fois sur le corps d'une carte et sur une cellule de tableau : elle s'aligne sur CE QU'ELLE
   COIFFE. Le désalignement préexistait, à six pixels ; la bascule l'aurait porté à onze si cette
   règle n'existait pas, et c'est en le mesurant qu'il s'est vu. */
.vd-carte__tete:has(+ table),
.vd-carte__tete:has(+ * table) { padding-inline: var(--espace-m); }

/* CE QUI SE POUSSE À DROITE DANS UNE TÊTE : une action, un compte, une date. Un seul nom pour ce
   geste, là où Relation en avait deux, `.right` et `.tete-action`. */
.vd-carte__tete__fin { margin-inline-start: auto; text-transform: none; letter-spacing: 0; font-weight: var(--graisse-appuyee); }
/* LA TÊTE IMPOSE SON DEGRÉ, ET C'EST TOUT L'OBJET DU COMPOSANT. Un contrôle posé dans une tête
   prend la hauteur compacte sans avoir à la demander, donc sans variante de bouton. La liste des
   variantes close le 25/08/2026 tient entière. */
.vd-carte__tete :where(.vd-iconbtn, .vd-button, .vd-select__champ, .vd-champ__saisie) {
  min-block-size: var(--controle-hauteur-compacte);
  block-size: var(--controle-hauteur-compacte);
}

.vd-carte__pied { display: flex; align-items: center; justify-content: flex-end; gap: var(--espace-s);
  flex-wrap: wrap; margin-top: var(--espace-m); }
.vd-carte__pied--separe { padding-top: var(--espace-m); border-top: 1px solid var(--bordure); }
/* Une information qui reste à gauche pendant que l'action va à droite : un statut, une date,
   un compte. Sans elle, il faudrait un second conteneur pour une seule ligne. */
.vd-carte__pied > .vd-carte__note { margin-inline-end: auto; text-align: start; }

/* ---------- Menu discret d'une carte ----------
   Les actions rares d'une tuile ne s'étalent pas sous elle : elles se replient derrière trois
   points, en haut à droite. C'est la même intention que les filtres secondaires repliés, une
   surface ne s'ouvre pas sur ce que personne n'a demandé. */
.vd-carte__menu { position: relative; margin-inline-start: auto; flex: none; }
/* LE BOUTON DU MENU PREND LE DEGRÉ COMPACT. Il valait 28 px, l'avatar 30, la pastille 36 : trois
   affordances carrées voisines, trois tailles, aucune décidée. */
.vd-carte__menu > summary { list-style: none; cursor: pointer;
  inline-size: var(--controle-hauteur-compacte); block-size: var(--controle-hauteur-compacte);
  display: flex; align-items: center; justify-content: center; border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  color: var(--texte-attenue); font-size: var(--texte-titre); line-height: 1; }
.vd-carte__menu > summary::-webkit-details-marker { display: none; }
.vd-carte__menu > summary:hover { background: var(--panel-2, rgb(0 0 0 / .05)); color: var(--texte); }
.vd-carte__menu > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vd-carte__menu[open] > summary { background: var(--panel-2, rgb(0 0 0 / .05)); color: var(--texte); }
/* UN MENU SE DIMENSIONNE SUR SON CONTENU, PAS L'INVERSE.
   Mesuré le 30/08/2026, dans une fiche que les bancs venaient d'apprendre à ouvrir : la liste
   valait 190 px de contenu, une entrée y demandait 194 px pour son libellé, et les 4 px de trop
   sortaient de la boîte. La cause n'était pas la largeur de la liste mais le sens de la
   contrainte : l'entrée était forcée à 100 % de la liste, donc la liste ne pouvait pas apprendre
   qu'elle était trop étroite.
   Le minimum devient un PLANCHER, plus un plafond : la liste suit son contenu, l'entrée ne
   descend jamais sous la largeur de la liste. Un libellé plus long, une traduction plus large,
   un espace qui écrit un minimum plus étroit : aucun ne rogne plus rien. C'est la classe qui se
   ferme, pas le cas (AO-2).
   Le plafond garde le menu à l'écran sur un téléphone, où il s'ouvre au bord droit. */
.vd-carte__menu__liste { position: absolute; inset-inline-end: 0; top: calc(100% + 4px); z-index: var(--plan-surnage);
  min-inline-size: 190px; inline-size: max-content;
  max-inline-size: min(320px, calc(100dvw - 2 * var(--espace-m)));
  /* LES ENTRÉES SONT UNE PILE, ET UNE PILE NE DÉRIVE PAS. Mesuré : les trois entrées faisaient
     bien 190 px chacune, mais leurs bords droits tombaient à -5, +3 et +3 du cadre. Elles
     étaient posées en flux, chacune libre de se décaler. Une grille fixe la colonne : chaque
     entrée occupe exactement la largeur utile, aucune ne peut sortir. */
  display: grid; gap: var(--espace-xxxs);
  padding: 4px; border: 1px solid var(--bordure); border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  background: var(--surface); box-shadow: var(--ombre-levee); }
/* UNE ENTRÉE DE MENU EST UNE CIBLE, PAS UNE LIGNE DE TEXTE. Elle valait 36 px, hauteur qui
   n'existait nulle part ailleurs. Elle prend le degré courant : c'est sur mobile, au pouce, que
   ce menu se touche, et le degré compact y serait juste. */
.vd-carte__menu__liste button { box-sizing: border-box; display: flex; align-items: center;
  text-align: start; min-block-size: var(--controle-hauteur);
  padding-block: 0; padding-inline: var(--espace-s);
  border: 0; border-radius: var(--accent-rayon, var(--courant-rayon-petit)); background: none; cursor: pointer; font: inherit;
  font-size: var(--texte-interface); color: var(--texte); }
.vd-carte__menu__liste button:hover { background: var(--panel-2, rgb(0 0 0 / .06)); }

/* ---------- Les filtres se replient, et le bouton dit combien sont posés ----------

   ARBITRAGE DE COLOMBAN, 26/08/2026 : « les filtres doivent être masqués par défaut et
   apparaître uniquement lorsque le bouton filtre, sans libellé, est activé, comme dans VAIATA
   Relation qui le fait très bien. »

   POURQUOI UN COMPTE SUR LE BOUTON. Des filtres repliés sont des filtres qu'on oublie, et une
   liste filtrée qu'on croit complète est pire qu'une liste longue. Le bouton porte donc le
   nombre de filtres actifs, et ce nombre est la seule chose qui distingue « replié » de
   « replié et actif ».

   LE PANNEAU S'OUVRE EN SECONDE LIGNE, PAS DANS LA BARRE. Première version : un dépliant natif
   dont le contenu s'ouvrait à l'intérieur de la barre d'outils, ce qui la faisait grandir et
   déplaçait tout ce qui suivait. Constat de Colomban, 26/08/2026 : « l'activation casse la mise
   en page ». Un dépliant natif ne peut pas rendre son contenu HORS de lui : le panneau devient
   donc un frère de la barre, et le bouton le commande par `aria-expanded` et `aria-controls`.
   C'est `accordeon.js` qui pose la bascule, et sans lui le panneau reste ouvert, visible et
   utilisable. Le repli est un confort, jamais une condition d'accès. */
.vd-filtres-repli { gap: 6px; inline-size: auto; padding-inline: var(--espace-s); }
.vd-filtres-repli[aria-expanded='true'] { background: var(--navy); border-color: var(--navy); color: #fff; }
/* Le compte n'apparaît QUE s'il y a des filtres RÉELLEMENT actifs, jamais parce que des filtres
   existent. « L'indicateur ne doit être actif que si 2 filtres sont effectivement actifs, et non
   simplement disponibles », Colomban, 26/08/2026. Le nombre est calculé sur l'état des contrôles
   du panneau, jamais écrit à la main : un compte écrit à la main ment dès la première saisie. */
/* Une pastille de compte se centre sur ses DEUX axes : posée en bloc, le chiffre se collait en
   haut à gauche de sa pastille. Constat de Colomban, 26/08/2026. */
/* UN ATTRIBUT DE MASQUAGE PERD CONTRE UN `display` D'AUTEUR, et c'est la deuxième fois en deux
   jours que ce piège se paie : le script masquait bien la pastille, elle affichait « 0 » sur la
   capture de Colomban. La règle du masquage se pose donc à côté de celle qui l'écrase. */
.vd-filtres__compte[hidden] { display: none; }
.vd-filtres__compte {
  /* LA BOÎTE SE MESURE ENTIÈRE, sinon la pastille n'est ronde pour aucun chiffre. Sans cela, la
     largeur minimale de 18 px vaut pour le CONTENU et le dégagement de 5 px s'ajoute de chaque
     côté : mesuré 28 sur 18 en production pour un « 1 », une gélule au lieu d'un rond. Elle
     s'allonge à partir de deux chiffres, ce qui est la seule raison d'avoir un minimum et non une
     largeur fixe (26/08/2026). */
  box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center;
  min-inline-size: 18px; block-size: 18px; padding-inline: var(--espace-xs);
  font-family: var(--police-donnee); font-size: var(--texte-mention); font-weight: var(--graisse-appuyee);
  font-variant-numeric: tabular-nums; line-height: 1;
  border-radius: 999px;
  background: var(--accent); color: #fff;
}


/* LE PANNEAU RANGE SES CONTRÔLES SUR UNE LIGNE, ET C'EST LUI QUI EN DÉCIDE.
   Constat de Colomban, 26/08/2026, sur capture : « les filtres prennent maintenant chacun une
   ligne entière ». Cause : le champ du socle est en pleine largeur, ce qui est juste dans un
   formulaire en colonne et faux dans une barre de filtres. La largeur est une affaire
   d'ARRANGEMENT, donc du conteneur, jamais du composant : le champ garde sa définition unique et
   le panneau la contraint chez lui. C'est aussi ce qui évite une variante de champ par contexte.
   Le retour à la colonne reste en place sous 719 px, où une ligne de quatre contrôles ne tient
   dans aucun téléphone. */
.vd-filtres { display: flex; flex-wrap: wrap; align-items: center; gap: var(--espace-s); margin-bottom: var(--espace-m); }
.vd-filtres > * { flex: 0 1 auto; }
.vd-filtres .vd-champ__saisie { inline-size: auto; min-inline-size: 14ch; }
.vd-filtres[hidden] { display: none; }
/* LE MASQUAGE DU CONTENU FERMÉ SE RÉTABLIT CÔTÉ AUTEUR. Le navigateur masque les enfants d un
   <details> fermé par une règle de son propre style, et toute règle d auteur qui pose un
   display la bat en spécificité : .vd-filtres portant display: flex, le panneau restait visible
   fermé. Mesuré sur banc le 26/08/2026, après un premier correctif qui n avait pas suffi. */

/* CETTE GARDE ETAIT UNE LISTE, ET UNE LISTE N'EST JAMAIS COMPLETE.
   **Mesure du 03/09/2026.** M9 avait ferme ce piege la veille en LISTANT six familles, et son
   commentaire disait « ferme une fois pour toutes ». C'etait faux au moment ou c'etait ecrit :
   le socle compte **133 familles qui posent un display**, et **121 n'avaient aucune garde**. Le
   piege s'est repaye le jour meme, deux fois de plus : sur `.advfilters` d'un produit, puis sur
   `.vd-button` du socle, ou un « Tout retirer » masque par l'attribut restait affiche, mesure a
   109 px de large avec `hidden: true` et `display: flex`.
   Une liste demande a celui qui ajoute une famille de penser a l'inscrire, ce que personne ne
   fait. La regle, elle, couvre ce qui existe et ce qui viendra.
   POURQUOI `!important` : les feuilles de produit se chargent APRES le socle, a specificite
   egale. Sans lui, `.advfilters{display:flex}` reprend la main, ce qui est exactement l'accident
   du 03/09. Un attribut de masquage n'est pas un reglage de dessin, il ne se discute pas.
   Aucune regle du depot ne reaffiche volontairement un element masque : verifie sur les trois
   niveaux de feuilles avant de poser celle-ci. */
[hidden] { display: none !important; }

/* LE PONT VERS LE CHAMP DU SOCLE, ET IL DIT QU'IL EST UN PONT.
   Cette règle était une SECONDE DÉFINITION du champ de saisie, écrite à côté de
   `.vd-champ__saisie` : sa propre hauteur en dur, son propre rembourrage, sa propre bordure. Elle
   existe parce que 851 contrôles du parc ne portent aucune classe, dont 114 dans chacune des
   quatre bases Relation, mesurés le 26/08/2026. Tant qu'ils sont nus, elle les habille.

   ELLE NE TOUCHE PLUS NI LA CASE NI LE BOUTON RADIO. C'est le défaut vu par Colomban sur
   capture : « Seulement les confirmées n'est pas aligné ». La case y prenait 40 px de haut pour
   16 de large, parce que la règle disait « tout `input` du panneau », sans distinguer un champ
   d'une case. Une case n'est pas un champ, elle a son composant, `.vd-case`.

   ELLE PREND LE JETON, plus une valeur recopiée : c'est l'écart de 2 px qui décalait la date. */
.vd-filtres :is(select, input):not([type='checkbox']):not([type='radio']) {
  box-sizing: border-box;
  min-block-size: var(--controle-hauteur);
  padding-block: 0;
  padding-inline: var(--espace-m);
  border: 1px solid var(--bordure);
  border-radius: var(--courant-rayon-moyen);
  background: var(--surface-haute);
  color: var(--texte);
  font: 400 var(--texte-interface)/1 var(--police-interface);
  vertical-align: middle;
}
.vd-filtres select { cursor: pointer; }

/* ---------- Modale ----------
   Le socle n'en avait pas au 22/08/2026, et trois bases de code en portaient chacune une. Celle
   de Relation, reprise ici pour ce qu'elle a prouvé, avec deux règles du canon en plus.

   ELLE S'OUVRE PAR `<dialog>`, PAS PAR UNE BOÎTE POSÉE. Le navigateur donne alors gratuitement ce
   qu'une boîte maison oublie : la fermeture à Échap, le piège de tabulation, le retour du focus,
   et le voile qui bloque le reste de la page. Une modale maison redemande ce travail à chaque
   espace, et un espace sur trois l'oublie.

   FERMER N'EST PAS UNE ACTION PRINCIPALE (§6 quater). Dans l'entête, la fermeture prend
   `.vd-iconbtn` avec l'icône `fermer`. En pied, à côté de l'action qui valide, un « Fermer »
   nommé reste juste : il y est le geste secondaire d'une paire. */
.vd-modale {
  /* LE CENTRAGE SE DIT, IL NE SE SUPPOSE PAS. Mesuré le 01/09/2026 sur Relation : la remise à
     zéro du produit (`*{margin:0}`) tuait la marge automatique du <dialog> natif, et la modale
     s'ouvrait collée à gauche. Une propriété qui dépend du style navigateur se réécrit ici. */
  margin: auto;
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-moyen));
  box-shadow: var(--ombre-flottante);
  background: var(--surface-haute);
  color: var(--texte);
  padding: 0;
  width: min(36rem, calc(100vw - 2 * var(--espace-l)));
  max-height: min(80vh, 44rem);
  overflow: auto;
}
.vd-modale::backdrop { background: rgb(20 21 31 / .45); }

/* LA MODALE PLEIN ÉCRAN. Arbitrage de Colomban du 04/09/2026, « Variante au socle ».
   MESURE QUI L'A FAIT MONTER : quatre <dialog> de DEUX produits, VAIATA ID et Syclef, écrivaient
   la même chose à la main. Deuxième terrain, donc la règle cesse d'être locale (§16.10).

   ELLE PEINT SON PROPRE VOILE, et le fond natif reste transparent. C'est ce que faisaient les
   quatre : sur un dialogue qui couvre déjà tout l'écran, le `::backdrop` ne se voit jamais, et
   deux voiles superposés donnent une teinte deux fois plus sombre que celle de la charte.

   CE QU'ELLE NE POSE PAS, et c'est délibéré : le dégagement, qui dépend de ce qu'on y met, et
   `display`, qui ne se déclare qu'à l'ouverture. Déclaré sur un dialogue fermé, il battrait le
   `display:none` natif et la modale couvrirait l'écran en permanence (mesuré sur Syclef le
   01/09/2026). */
.vd-modale--pleine {
  position: fixed;
  inset: 0;
  z-index: var(--plan-flottant);
  margin: 0;
  /* UN DIALOGUE PLEIN ÉCRAN EST LA FENÊTRE : SON DÉGAGEMENT TIENT DANS SES 100 %. Mesuré le
     05/09/2026 à 390 × 844 sur les deux dialogues de Syclef : 432 × 933 et 432 × 886, parce que
     le <dialog> natif est en content-box et que le produit pose 21 px de dégagement, 67 en tête
     pour la recherche, qui s ajoutaient aux 100 %. Le défaut préexistait à la reprise au socle du
     04/09, mesuré identique avant et après elle. La boîte se compte bord à bord. */
  box-sizing: border-box;
  inline-size: 100%; max-inline-size: 100%;
  block-size: 100%; max-block-size: 100%;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  /* PAS DE `overflow` ICI, et c'est mesuré. `.vd-modale` en porte parce qu'elle est une boîte
     bornée ; un dialogue plein écran EST la fenêtre, et son contenu défile dans la boîte qu'il
     contient. Le poser dehors donnerait deux barres de défilement. */
  /* LE VOILE N'A QU'UNE VALEUR DANS LE SOCLE, celle de `.vd-modale::backdrop`. Les quatre règles
     reprises en portaient DEUX, .44 et .45 : c'est exactement la dispersion que la directive du
     03/09 vise. Elles prennent celle du socle. */
  background: rgb(20 21 31 / .45);
}
.vd-modale--pleine::backdrop { background: transparent; }
.vd-modale__entete {
  display: flex;
  align-items: center;
  gap: var(--espace-m);
  padding: var(--espace-m) var(--espace-l);
  border-block-end: 1px solid var(--bordure);
}
.vd-modale__titre { margin: 0; font-size: var(--texte-titre); font-weight: var(--graisse-forte); }
.vd-modale__entete .vd-iconbtn { margin-inline-start: auto; }
.vd-modale__corps { padding: var(--espace-l); }
.vd-modale__pied {
  display: flex;
  align-items: center;
  gap: var(--espace-s);
  padding: var(--espace-m) var(--espace-l);
  border-block-start: 1px solid var(--bordure);
}
/* L'action qui engage se tient à la fin de la ligne, la secondaire avant elle : c'est la règle
   du §6 quater, la même que pour l'entête de page et le pied de carte. */
.vd-modale__pied .vd-button:last-child { margin-inline-start: auto; }
.vd-modale__pied .vd-button { margin-block-end: 0; }

/* ---------- Message fugace ----------
   Relation en appelait un 41 fois, sur un `#toast` maison identifié par son id : un identifiant
   ne se partage pas entre deux espaces, et personne d'autre ne pouvait s'en servir.

   IL PARLE À LA SYNTHÈSE VOCALE. Un message qui apparaît sans `role="status"` n'existe pas pour
   qui n'a pas les yeux sur l'écran, et c'est justement la situation d'une passerelle. */
.vd-fugace {
  position: fixed;
  inset-block-end: var(--espace-l);
  inset-inline: 0;
  margin-inline: auto;
  width: fit-content;
  max-width: calc(100vw - 2 * var(--espace-l));
  z-index: var(--plan-flottant);
  display: flex;
  align-items: center;
  gap: var(--espace-s);
  background: var(--navy);
  color: #fff;
  font-size: var(--texte-interface);
  font-weight: var(--graisse-appuyee);
  padding: var(--espace-m) var(--espace-l);
  border-radius: var(--accent-rayon, var(--courant-rayon-moyen));
  box-shadow: var(--ombre-flottante);
  opacity: 0;
  transform: translateY(20px);
  pointer-events: none;
  transition: opacity .2s, transform .2s;
}
.vd-fugace.est-visible { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .vd-fugace { transition: none; }
}

/* ---------- État vide ----------
   UN ÉCRAN VIDE DIT POURQUOI IL EST VIDE, ET CE QU'ON PEUT Y FAIRE. Sans cela, le lecteur ne sait
   pas s'il attend un chargement, s'il a mal filtré, ou s'il n'y a rien. Les trois se ressemblent
   à l'écran et ne se traitent pas pareil. */
/* ---------- LOT S. LE VIDE, UNE SEULE DÉFINITION POUR TROIS EMPLOIS ----------

   Mesure du 26/08/2026, dans le SOCLE lui-même : `.vd-table__vide`, `.vd-palette__vide` et
   `.vd-select__vide` répétaient chacun la même chose, taille de texte d'interface et couleur
   atténuée. Trois écritures d'une même intention, dans le fichier qui est censé n'en avoir
   qu'une. Le parc, lui, en porte quatre de plus : `empty`, `sy-vide`, `d-vide`, `search-vide`.

   LA PHRASE DU VIDE EST UN COMPOSANT, pas une couleur de texte. Elle se déclare ici, et les
   trois emplois n'ajoutent que leur PLACE : un rembourrage de cellule, une marge de liste.

   DEUX VIDES, ET ILS NE SE DISENT PAS PAREIL. Le vide d'ABSENCE, « rien n'existe encore », dit
   quoi faire pour que quelque chose existe. Le vide de FILTRE, « rien ne correspond », dit
   comment revenir en arrière. Les confondre, c'est proposer de créer une escale à quelqu'un qui
   en cherchait une qui existe. */
.vd-table__vide, .vd-palette__vide, .vd-select__vide, .vd-vide__phrase {
  font-size: var(--texte-interface); color: var(--texte-attenue);
}

.vd-vide {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--espace-s);
  text-align: center;
  padding: var(--espace-xl) var(--espace-l);
  color: var(--texte-attenue);
}
.vd-vide .vd-icone { color: var(--bordure); }
.vd-vide__titre { margin: 0; font-size: var(--texte-titre); font-weight: var(--graisse-forte); color: var(--texte); }
.vd-vide__note { margin: 0; font-size: var(--texte-interface); max-width: 30rem; }

/* ---------- Infobulle ----------
   Elle complète, elle ne porte jamais l'information seule : ce qui n'est lisible qu'au survol
   n'existe ni au doigt, ni au clavier, ni à la synthèse vocale. */
.vd-infobulle { position: relative; display: inline-flex; }
.vd-infobulle__texte {
  position: absolute;
  inset-block-end: calc(100% + 6px);
  inset-inline-start: 50%;
  translate: -50% 0;
  z-index: var(--plan-flottant);
  width: max-content;
  max-width: 16rem;
  background: var(--navy);
  color: #fff;
  /* LA POLICE SE DÉCLARE, elle ne s'hérite pas. Mesuré le 27/08/2026 : l'infobulle rendait en
     Arial sur Relation et sur Syclef, parce qu'elle est portée par des éléments dont le
     navigateur ne transmet pas la police. Un composant du socle qui laisse au hasard le soin
     de sa typographie n'est pas au socle. */
  font-family: var(--police-interface);
  font-size: var(--texte-legende);
  font-weight: var(--graisse-courante);
  line-height: 1.4;
  padding: 6px 9px;
  border-radius: var(--carbone-rayon-petit);
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s;
  /* UNE BULLE MASQUÉE NE COMPTE PAS DANS LA LARGEUR DE LA PAGE. Mesuré le 27/08/2026 : posée sur
     les cases d'un calendrier, elle débordait la grille de 87 px à 390 px de large, alors même
     qu'elle est invisible. Une boîte en position absolue, translucide à zéro, occupe quand même
     la zone de défilement de son conteneur, et la page se met à défiler latéralement sans que
     rien ne se voie. `content-visibility` retire du calcul ce qui n'est pas montré, et le rend
     dès qu'il l'est : on garde le fondu, on perd le débordement fantôme. */
  content-visibility: hidden;
}
.vd-infobulle:hover .vd-infobulle__texte,
.vd-infobulle:focus-within .vd-infobulle__texte,
.vd-infobulle-cible:hover > .vd-infobulle__texte,
.vd-infobulle-cible:focus-within > .vd-infobulle__texte { opacity: 1; content-visibility: visible; }
@media (prefers-reduced-motion: reduce) {
  .vd-infobulle__texte { transition: none; }
}

/* ---------- Champ de formulaire ----------
   Le socle n'en avait aucun au 22/08/2026, et Relation en portait un sous `.field`, réduit à
   `margin-bottom: 12px` puis redéfini vingt lignes plus loin en pastille de couleur : le même nom
   pour deux objets. Une saisie a besoin de quatre choses, et le socle les tient ensemble.

   L'ÉTAT D'ERREUR N'EST JAMAIS PORTÉ PAR LA COULEUR SEULE (§6). Le liseré rouge ne suffit pas :
   le message porte son symbole et ses mots, et le champ porte `aria-invalid`. L'interface est lue
   en passerelle, en plein soleil, et par des gens qui distinguent mal certaines teintes.

   L'OBLIGATION S'ÉCRIT EN TOUTES LETTRES. Une astérisque seule est un symbole sans légende : elle
   suppose que le lecteur connaît la convention, et elle ne se lit pas à la synthèse vocale. */
.vd-champ { display: block; margin-block-end: var(--espace-m); max-width: 34rem; }
.vd-champ__etiquette {
  display: block;
  font-size: var(--texte-interface);
  font-weight: var(--graisse-appuyee);
  color: var(--texte);
  margin-block-end: var(--espace-xs);
}
.vd-champ__requis { font-size: var(--texte-mention); font-weight: var(--graisse-courante); color: var(--texte-attenue); margin-inline-start: var(--espace-xs); }
.vd-champ__saisie {
  font: inherit;
  font-size: var(--texte-corps);
  display: block;
  inline-size: 100%;
  box-sizing: border-box;
  min-block-size: var(--controle-hauteur);
  color: var(--texte);
  background: var(--surface-haute);
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  padding-block: 0;
  padding-inline: var(--espace-m);
}
/* Une zone de texte long ne peut pas se caler sur la hauteur de contrôle : elle en occupe
   plusieurs, et son rembourrage vertical lui revient. */
textarea.vd-champ__saisie { padding-block: 9px; min-block-size: calc(var(--controle-hauteur) * 2); }
.vd-champ__saisie:hover { border-color: var(--texte-attenue); }
.vd-champ__saisie:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.vd-champ__saisie:disabled { background: var(--surface); color: var(--texte-attenue); cursor: not-allowed; }
/* Une saisie de plusieurs lignes garde la hauteur qu'on lui donne, jamais une hauteur figée en
   pixels : un texte allongé par la traduction en sortirait (chantier 3, contrôle de boîte figée). */
textarea.vd-champ__saisie { min-height: 6rem; resize: vertical; }
.vd-champ__aide { margin: var(--espace-xs) 0 0; font-size: var(--texte-legende); color: var(--texte-attenue); }
.vd-champ__erreur { margin: var(--espace-xs) 0 0; font-size: var(--texte-legende); color: var(--danger); display: flex; align-items: center; gap: var(--espace-xs); }
.vd-champ__erreur .vd-icone { flex: none; }
.vd-champ.est-en-erreur .vd-champ__saisie { border-color: var(--danger); }
.vd-champ.est-en-erreur .vd-champ__saisie:focus-visible { outline-color: var(--danger); }

/* ---------- Liste déroulante cherchable ----------
   Arbitrage de Colomban, 22/08/2026 : TOUS les sélecteurs deviennent cherchables, pas seulement
   les longs. Un sélecteur qui se comporte différemment selon sa longueur est un sélecteur qu'on
   n'apprend pas. Mesure du même jour : 75 sélecteurs natifs sur six bases de code, aucun
   cherchable, le plus long à 494 options.

   Le sélecteur natif reste dans la page et porte la valeur : il est masqué, pas retiré. */
/* LA LISTE NE DÉPASSE PAS SON CONTENANT, ET SON DÉCLENCHEUR NON PLUS. Mesuré au doigt le
   27/08/2026, à 390 px : le déclencheur faisait 128 px dans une boîte de 112 et débordait de 16.
   `min-width: 0` autorise la BOÎTE à rétrécir, il n'oblige pas son contenu à suivre : c'est le
   déclencheur qui impose sa largeur intrinsèque, et il faut le lui interdire explicitement.
   Le libellé se coupe alors avec des points de suite, ce qui est le seul comportement honnête :
   la valeur choisie reste lisible en entier dans la liste ouverte et dans l'infobulle native. */
.vd-select { position: relative; display: inline-block; min-width: 0; max-inline-size: 100%; }
.vd-select__declencheur {
  inline-size: 100%; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* LE SÉLECTEUR NATIF EST MASQUÉ POUR DE BON, PAS SEULEMENT RÉTRÉCI.
   Défaut mesuré au doigt le 27/08/2026, à 390 px : la liste cherchable de Relation débordait de
   20 px. Cause : une règle du produit met 10 px de remplissage sur tout `select`, et le socle
   posant `box-sizing: border-box`, un élément de 1 px NE PEUT PAS descendre sous la somme de son
   remplissage. Le natif occupait donc 20 px dans le flux, invisible mais présent.
   ON NEUTRALISE DONC CE QU'UN PRODUIT PEUT AJOUTER : remplissage, bordure et marge à zéro. Un
   masquage qui suppose que personne ne touchera à l'élément n'est pas un masquage, c'est un pari.
   La marge négative garde la technique d'accessibilité : l'élément reste dans l'arbre, atteignable
   par la synthèse vocale et par un formulaire qui le lit, il n'occupe simplement plus de place. */
.vd-select__natif {
  /* IL EST ANCRÉ AU COIN, ET C'EST CE QUI RÈGLE VRAIMENT LE DÉBORDEMENT. Sans `inset`, un
     élément absolu reste à sa position STATIQUE, ici après le déclencheur, à 140 px du bord :
     ses 20 px de large tombaient donc à 160, hors d'une boîte de 142. Ancré au coin, il peut
     bien mesurer ce qu'un produit lui impose, il ne dépasse plus de rien.
     LA REMISE À ZÉRO DU REMPLISSAGE RESTE, pour le cas ordinaire, mais elle ne suffit pas : la
     règle du produit est plus spécifique et gagne. ON NE MONTE PAS LA SPÉCIFICITÉ POUR AUTANT.
     Une guerre de sélecteurs se perd toujours au coup suivant ; une position qui ne dépend de
     personne tient quoi qu'on écrive ailleurs. */
  position: absolute;
  inset-inline-start: 0; inset-block-start: 0;
  inline-size: 1px; block-size: 1px;
  padding: 0; border: 0; margin: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.vd-select__declencheur {
  /* LE DÉCLENCHEUR REMPLACE UN SÉLECTEUR NATIF : il en prend la hauteur, sinon la liste
     cherchable est plus courte de 4 px que le sélecteur d'à côté (mesuré 36 contre 40). */
  box-sizing: border-box;
  min-block-size: var(--controle-hauteur);
  padding-block: 0;
  font: inherit;
  font-size: var(--texte-interface);
  text-align: start;
  color: var(--texte);
  background: var(--surface-haute);
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  padding-inline: var(--espace-m);
  cursor: pointer;
  /* LA LARGEUR MINIMALE EST UN CONFORT, PAS UNE CONTRAINTE. 8 rem valent 128 px, et à 390 px de
     large la liste débordait de 16 : mesuré au doigt le 27/08/2026 sur Relation. Un minimum qui
     dépasse l'écran n'est plus un minimum, c'est une panne. Le confort tient tant que la place
     existe, et il cède quand elle manque. */
  min-width: min(8rem, 100%);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vd-select__declencheur:hover { border-color: var(--texte-attenue); }
.vd-select__declencheur:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.vd-select__panneau {
  position: absolute;
  inset-inline-start: 0;
  top: calc(100% + 4px);
  z-index: var(--plan-surnage);
  /* LE PANNEAU PREND LA LARGEUR DE SES OPTIONS, PAS CELLE DU DECLENCHEUR.
     Constat de Colomban, 28/08/2026, sur capture : « il faut élargir le bandeau du menu déroulant
     car la liste est ici illisible ». Mesuré : le déclencheur « Toutes offres » fait cent
     pixels, le panneau n'avait qu'un plancher à cent pour cent de cette largeur, et rien ne le
     poussait plus loin. « Journée groupe découverte » tombait donc sur trois lignes, et une liste
     de six entrées devenait un mur de mots.
     `max-content` lui donne la largeur de sa plus longue option ; le plancher garde l'alignement
     sur le déclencheur quand les options sont courtes, et le plafond l'empêche de traverser
     l'écran. */
  inline-size: max-content;
  min-width: 100%;
  max-width: min(22rem, 90vw);
  background: var(--surface-haute);
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  box-shadow: var(--ombre-levee);
  padding: var(--espace-s);
}
.vd-select__panneau[hidden] { display: none; }
/* LES PARTIES D'UN COMPOSANT DU SOCLE S'ADRESSENT PAR LEUR BLOC.
   Mesure du 28/08/2026, deux fois dans la journee. Une regle de produit `.fld input` porte
   0,1,1 de specificite et bat `.vd-select__champ`, qui n'en porte que 0,1,0 : le champ du
   panneau prenait 45 px de haut, hors echelle, et 185 px de large, ce qui faisait deborder la
   page a 390. Le meme mecanisme avait rendu le champ de recherche de Relation impossible a
   cliquer, `.tbl-filter` ecrasant le degagement de son icone.
   Un composant du socle pose DANS un produit doit gagner contre les regles generiques de ce
   produit, sinon il n'est pas un socle : on l'ecrit donc par son bloc, ce qui suffit a passer
   devant sans recourir a une declaration imperative. */
.vd-select .vd-select__champ {
  font: inherit;
  font-size: var(--texte-interface);
  width: 100%;
  box-sizing: border-box;
  color: var(--texte);
  background: var(--surface);
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  /* IL PREND UN DEGRE DE L'ECHELLE, comme tout controle de saisie. Il n'en portait aucun, si
     bien que sa hauteur dependait du remplissage que le produit lui laissait : 45 px sous une
     regle de Ticket, 34 sans. Le degre compact est le juste : ce champ vit DANS un panneau
     ouvert, sous le declencheur qui, lui, porte le degre courant. */
  min-block-size: var(--controle-hauteur-compacte);
  padding-block: 0;
  padding-inline: 9px;
}
.vd-select__champ:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* La liste défile plutôt que de pousser la page : 494 options tiennent dans le plus long des
   sélecteurs mesurés, et une liste de 494 lignes déroulerait l'écran entier. */
.vd-select__liste { max-height: 15rem; overflow-y: auto; margin-block-start: var(--espace-s); }
/* TROISIEME FOIS DANS LA JOURNEE QU'UNE REGLE DE PRODUIT AVALE UNE PARTIE DU SOCLE.
   Mesure du 28/08/2026 sur l'intelligence des chantiers navals : une option de liste rendait
   344 sur 12 px, soit la moitie du seuil tactile, parce que le remplissage du socle, 6 px,
   cedait devant une regle locale. Une option est une CIBLE, au doigt comme a la souris : elle
   prend le degre compact de l'echelle, et elle s'adresse par son bloc pour passer devant les
   regles generiques d'un produit, comme le champ du meme panneau. */
.vd-select .vd-select__option {
  box-sizing: border-box;
  min-block-size: var(--controle-hauteur-compacte);
  display: flex;
  align-items: center;
  font: inherit;
  font-size: var(--texte-interface);
  display: block;
  width: 100%;
  text-align: start;
  color: var(--texte);
  background: transparent;
  border: 0;
  border-radius: var(--carbone-rayon-petit);
  padding: 6px 9px;
  cursor: pointer;
}
.vd-select__option:hover, .vd-select__option[data-survole='oui'] { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.vd-select__option[aria-selected='true'] { font-weight: var(--graisse-forte); }
/* LA COCHE DE LA MULTISÉLECTION, 02/09/2026, arbitrage de Colomban sur ce composant.
   Elle garde sa gouttière même vide : sans largeur réservée, les libellés se décalent de deux
   caractères au premier clic, et la liste danse sous le doigt à chaque choix. Un état ne se dit
   jamais par la graisse seule, c'est la règle de charte du §7 : la coche est le signe, la
   graisse ne fait que l'appuyer. */
.vd-select__coche {
  display: inline-block;
  inline-size: 1.1em;
  flex: none;
  color: var(--accent);
  font-weight: var(--graisse-forte);
}
/* Une liste vide le DIT. Un panneau blanc laisse croire à un chargement qui n'arrive pas. */
.vd-select__vide { margin: var(--espace-s) 0 0; }
/* Et le repli aussi. Le moteur de recherche en fait une obligation : montrer des approchants
   sans le dire laisse le lecteur croire qu'il a trouvé exactement ce qu'il cherchait. */
.vd-select__repli { margin: 0 0 var(--espace-s); font-size: var(--texte-legende); color: var(--alerte); }
.vd-filtres :is(select, input):focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (max-width: 719px) {
  .vd-filtres { flex-direction: column; align-items: stretch; }
  .vd-filtres :is(select, input):not([type='checkbox']):not([type='radio']) { inline-size: 100%; }
}

/* ---------- Barre d'application ----------
   L'ordre est arbitré par Colomban le 19/08/2026, après la démonstration à Guillaume :
   l'identité à GAUCHE, la recherche AU CENTRE, les utilisateurs sur le CÔTÉ droit.

   Avant, la barre était une simple ligne en `flex` où la mention de démonstration portait
   `margin-left: auto`. Tout ce qui suivait était donc poussé à l'extrême droite : la
   recherche finissait collée au bord de l'écran et les visages se retrouvaient au milieu,
   à la place de la recherche.

   La barre se pose en TROIS ZONES et non en ligne, parce qu'une ligne `flex` ne centre pas :
   elle place, et le centre s'y déplace au gré de ce que pèsent la gauche et la droite. Seule
   une colonne centrale de largeur propre tient la recherche au milieu quel que soit le nombre
   d'avatars ou la longueur du nom du client. Les deux colonnes latérales sont égales (`1fr`),
   c'est ce qui rend le centre vrai.

   Un espace ne redéfinit ni l'ordre ni la disposition : il habille (fond, bordure, hauteur). */
/* DÉPRÉCIÉE LE 26/08/2026. « La barre d'application n'a pas lieu d'exister » : ses trois zones
   vivent désormais dans le bandeau, et son rôle de titre et d'actions est celui de l'entête de
   page. Elle reste ici, à l'identique, parce que CINQ bases l'emploient, Relation les quatre et
   Sextant : la retirer le jour de la décision casserait cinq espaces en production. Elle part
   au lot d'adoption, espace par espace, et pas avant. */
.vd-appbar {
  display: grid;
  grid-template-columns: 1fr minmax(0, var(--vd-appbar-centre, 460px)) 1fr;
  align-items: center;
  gap: var(--espace-m);
}
.vd-appbar__gauche { display: flex; align-items: center; gap: var(--espace-m); min-width: 0; }
.vd-appbar__centre { display: flex; align-items: center; justify-content: center; min-width: 0; }
.vd-appbar__droite { display: flex; align-items: center; justify-content: flex-end; gap: var(--espace-s); min-width: 0; }

/* Sur un écran étroit, la colonne centrale prend ce qui reste et la recherche se range au
   BORD OPPOSÉ au menu. C'est un choix, pas un effet de bord : à cette largeur la recherche
   est une cible de 44 px, un centrage la placerait sous le pouce de personne et rognerait
   l'identité du produit. */
@media (max-width: 900px) {
  .vd-appbar { grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--espace-s); }
  .vd-appbar__centre { justify-content: flex-end; }
}

/* ============================================================================================
   LOT A : le champ de recherche, la saisie de date, le dépôt de fichier.
   Trois familles mesurées sur les onze bases de code le 25/08/2026, et comparées sur leurs
   ATTRIBUTS, jamais sur leur style : la divergence ne se voit pas à l'écran, elle se voit au
   clavier, à la synthèse vocale, et sur le papier d'un client.
   ============================================================================================ */

/* ---------- Le champ de recherche ----------

   L'ÉTAT MESURÉ : 20 occurrences sur 5 bases, HUIT jeux d'attributs distincts. Le catalogue en
   annonçait quatre : il avait compté les formes, pas les jeux.
     • 5 sur 20 portent `type="search"`. Les autres perdent la sémantique et le bouton d'effacement
       que le navigateur donne gratuitement ;
     • 3 jeux sur 8 portent `autocomplete` ;
     • aucun ne porte `enterkeyhint`, donc un clavier tactile propose « Entrée » là où il pourrait
       proposer « Rechercher ».

   ET LE DÉFAUT QUI RÉSUME TOUT : 56 placeholders du parc commencent par le caractère U+2315,
   dont le nom Unicode est TELEPHONE RECORDER. Il sert de loupe parce qu'il y ressemble dans
   certaines polices. Une synthèse vocale annonce donc « enregistreur téléphonique » avant le
   texte de l'invite, le caractère ne prend pas la couleur du produit, et il change de dessin
   d'une police à l'autre. C'est le §6 ter, l'interdiction de l'émoticône, avec un autre bloc
   Unicode : le dessin d'une icône vit dans `icones.js`, jamais dans du texte.

   ET UN PLACEHOLDER N'EST PAS UNE ÉTIQUETTE. Il disparaît dès la première frappe, au moment
   précis où le lecteur voudrait vérifier ce qu'on lui demande. */
.vd-recherche { position: relative; display: flex; align-items: center; gap: var(--espace-s); }

/* LE LANCEUR : UN BOUTON QUI OUVRE LA RECHERCHE, PAS UN CHAMP QUI LA FAIT.
   Recensé le 27/08/2026 sur DEUX produits, VAIATA ID et Syclef, chacun avec sa règle, à 44 px
   et 38 px, deux rayons et deux fonds. Une règle qui vaut pour deux surfaces s'écrit une fois.
   POURQUOI IL N'EST PAS UN CHAMP. Un champ invite à taper là où il est ; celui-ci ouvre une
   palette qui prend l'écran, et la frappe se fait ailleurs. Un champ qui déplace le curseur
   ailleurs au premier caractère est une interface qui ment. C'est donc un bouton, et il porte
   le raccourci qui fait la même chose, parce qu'un raccourci que rien n'annonce n'existe pas.
   IL PREND LE DEGRÉ AMPLE : il se touche au pouce, en tête d'écran, souvent à une main. */
.vd-lanceur {
  box-sizing: border-box;
  display: flex; align-items: center; gap: var(--espace-s);
  inline-size: 100%; min-inline-size: 0;
  min-block-size: var(--controle-hauteur-ample);
  padding-block: 0; padding-inline: var(--espace-m);
  border: 1px solid var(--bordure); border-radius: 999px;
  background: var(--surface-haute); color: var(--texte-attenue);
  font: 400 var(--texte-corps)/1 var(--police-interface); text-align: start; cursor: text;
  /* IL ROGNE SON CONTENU AU LIEU DE POUSSER SON VOISIN. Mesuré au doigt le 27/08/2026 sur Sextant :
     son invite « Jour, cale, navire, opérateur… » mesure 71 px sans pouvoir se replier, et faisait
     déborder la PAGE de 51. Un lanceur occupe la place qu'on lui donne : c'est l'invite qui cède,
     pas la mise en page. Ses enfants peuvent rétrécir, ce que `min-inline-size: 0` autorise et
     qu'aucune largeur ne fait à leur place. */
  overflow: hidden;
}
.vd-lanceur > * { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* LE RAPPEL DE RACCOURCI DISPARAÎT SOUS 720 px : il n'y a pas de clavier, et il prend la place de
   l'invite qui, elle, dit ce qu'on peut chercher. Syclef le faisait déjà dans son coin, ce qui est
   le signe que la règle appartient au socle. */
@media (max-width: 719px) { .vd-lanceur__touche { display: none; } }
.vd-lanceur:hover { border-color: var(--texte-attenue); }
.vd-lanceur:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vd-lanceur__invite { flex: 1 1 auto; min-inline-size: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
/* Le raccourci est une TOUCHE, pas un mot : il se dessine comme une touche. */
.vd-lanceur__touche {
  flex: none; font: 600 var(--texte-mention)/1 var(--police-donnee); letter-spacing: .04em;
  color: var(--texte-attenue); border: 1px solid var(--bordure); border-radius: var(--carbone-rayon-petit);
  padding: 3px var(--espace-xs);
}
/* ---------- LE GROUPE DE PASTILLES, QUI SE LIT PAR FAMILLE ----------

   ARBITRAGE DE COLOMBAN, 29/08/2026, sur son constat du 28 : « des tuiles parfaitement moches et
   incohérentes ». La capsule penchée était corrigée ; restait ce que la tuile ASSEMBLE.

   LE FAIT MESURÉ. Une fiche de savoir de Syclef met TROIS familles sémantiques dans un flux
   indifférencié : l'année et le secteur, qui sont des FAITS ; les domaines techniques, qui sont
   une QUALIFICATION, en vert avec leur triangle ; et une mention sur la NATURE de la donnée, en
   ardoise. Elles s'enroulent au hasard de leur largeur, si bien qu'un domaine se retrouve sur la
   même ligne qu'une mention qui n'en est pas un. L'œil ne peut pas séparer ce que le balisage ne
   sépare pas.

   ET LE SOCLE N'AVAIT AUCUN CONTENEUR DE PASTILLES : zéro sélecteur, chaque espace écrivait le
   sien. Trois espaces en avaient un, sous trois noms.

   CE QU'IL FAIT, ET C'EST TOUT. Il pose UNE LIGNE PAR FAMILLE, dans l'ordre du balisage. Il ne
   décide pas des familles, ni de leur ordre : c'est au produit de les nommer, parce que lui seul
   sait ce qui est un fait et ce qui est un jugement. L'ordre recommandé va du FACTUEL au
   QUALIFIÉ, ce qui se lit comme une phrase : voilà ce qui est, voilà ce qu'on en dit.

   CE QU'IL NE FAIT PAS. Il n'enroule pas les familles ensemble, et c'est exactement le point : un
   groupe qui s'enroule met sur la même ligne des choses de nature différente, et le lecteur croit
   qu'elles vont ensemble parce qu'elles sont alignées. */
.vd-pilules { display: grid; gap: var(--espace-s); margin-block-start: var(--espace-s); }
/* Une famille tient sur sa ligne et s'enroule DANS elle, jamais avec la voisine. */
.vd-pilules__famille { display: flex; flex-wrap: wrap; gap: var(--espace-xs); align-items: center; }

.vd-recherche__icone {
  position: absolute; inset-inline-start: 10px;
  display: inline-flex; color: var(--texte-attenue); pointer-events: none;
}
.vd-recherche__saisie {
  flex: 1; min-width: 0;
  font: inherit; font-size: var(--texte-corps);
  /* LOGIQUE, PAS PHYSIQUE. Le raccourci a quatre valeurs portait le dégagement de l’icône à
     GAUCHE : sous écriture de droite à gauche, l’icône passait à droite, par
     `inset-inline-start`, et venait se poser sur le texte. Seul raccourci asymétrique du socle,
     mesuré le 26/08/2026, et le témoin de retournement ne voyait pas les raccourcis. */
  min-block-size: var(--controle-hauteur);
  padding-block: 0;
  padding-inline: var(--espace-xl) 12px;
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  background: var(--surface-haute); color: var(--texte);
}
/* LE SÉLECTEUR NATIF DE DATE ET LES CASES PRENNENT LA COULEUR DU PRODUIT. Sans `accent-color`,
   le navigateur peint son bleu système dans le calendrier, le sélecteur d’heure et la coche :
   une tache qui n’appartient à aucun produit. Constat de Colomban, 26/08/2026. */
.vd-champ__saisie, .vd-recherche__saisie { accent-color: var(--accent); }
.vd-recherche__saisie:hover { border-color: var(--texte-attenue); }
.vd-recherche__saisie:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* LA CROIX D'EFFACEMENT VIENT DU SOCLE, PAS DU NAVIGATEUR.
   Constat de Colomban, 26/08/2026 : « pourquoi la croix ne reprend pas celle du design system ? »
   Un champ `type="search"` reçoit du navigateur son propre bouton d'effacement, dessiné par lui,
   à sa taille et dans sa couleur. On le retire et on pose le nôtre, qui emploie la trace
   `fermer` du corpus. Le type `search` reste, lui : c'est ce qui donne le clavier de recherche
   sur un téléphone et le rôle attendu par les lecteurs d'écran. */
.vd-recherche__saisie::-webkit-search-cancel-button,
.vd-recherche__saisie::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
.vd-recherche__vider {
  position: absolute; inset-inline-end: 4px;
  inline-size: var(--controle-hauteur-compacte); block-size: var(--controle-hauteur-compacte);
}
.vd-recherche__vider[hidden] { display: none; }
.vd-recherche:has(.vd-recherche__vider:not([hidden])) .vd-recherche__saisie { padding-inline-end: 38px; }
/* Le compte de résultats se DIT, il ne se lit pas seulement. Sans `role="status"`, un lecteur
   d'écran ne saura jamais que la liste s'est réduite de 200 à 3. */
.vd-recherche__compte { font-size: var(--texte-legende); color: var(--texte-attenue); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------- La saisie de date ----------

   L'ÉTAT MESURÉ : 7 occurrences sur 4 bases, CINQ jeux d'attributs distincts.
     • un seul borne par `min` ET `max`, Sextant ;
     • deux n'ont que leur `id`, donc acceptent le 31 décembre 2099 ;
     • un seul porte `aria-describedby` et une règle de validation, VAIATA ID.

   UNE DATE SANS BORNE EST UNE DATE FAUSSE EN ATTENTE. Le cas n'est pas théorique : une date de
   rappel posée à 2099 est passée pour un report réel dans Palantíri, et personne ne l'a vue
   parce qu'aucune borne ne la refusait.

   LA BORNE SE DIT AUSSI EN TOUTES LETTRES. Un intervalle que le lecteur ne voit pas est un
   intervalle qu'il découvre en s'y cognant. `min` et `max` bornent la saisie, l'aide la nomme.
   La saisie de date n'est PAS une nouvelle famille : elle prend `.vd-champ`, qui existe depuis
   le 22/08/2026, et n'ajoute que ce qui lui est propre. */
.vd-champ__saisie[type="date"], .vd-champ__saisie[type="datetime-local"],
.vd-champ__saisie[type="month"], .vd-champ__saisie[type="week"] {
  font-variant-numeric: tabular-nums;
  /* Le sélecteur natif du navigateur reste : le remplacer coûterait le clavier, la langue et le
     calendrier du système, pour un dessin.

     LA HAUTEUR VIENT DU JETON, comme partout ailleurs. Elle valait 38 px, écrits en dur avant que
     le jeton n'existe : deux pixels de moins que tous les autres contrôles, donc une ligne de
     formulaire où la date ne s'alignait sur rien. Trouvé par le contrôle « tous les contrôles ont
     la même hauteur », qui ne juge plus au cas par cas (26/08/2026). */
  min-block-size: var(--controle-hauteur);
}

/* ---------- Le dépôt de fichier ----------

   L'ÉTAT MESURÉ : 3 bases de code, et deux techniques qui ne se parlent pas. Sextant et Bunker
   Check posent un `<input type="file">` ; Bunker Check et les quatre Relation écoutent
   `dragover` et `drop`. Aucune des deux bases à `<input>` ne le laisse atteignable.

   LE DÉFAUT LE PLUS COÛTEUX, ET IL EST INVISIBLE À L'ÉCRAN : les deux masquent la saisie, l'une
   par l'attribut `hidden`, l'autre par une classe `.hidden`. Les deux posent `display: none`,
   qui retire l'élément de l'ordre de tabulation ET de l'arbre d'accessibilité. Déposer un
   fichier n'est alors possible qu'à la souris. Le `<label for>` ne rattrape rien : une étiquette
   ne prend pas le focus.

   La saisie reste donc dans l'arbre, masquée par découpe, et la zone porte le focus visible de
   la saisie qu'elle habille.

   LE GLISSER-DÉPOSER EST UNE ADDITION, JAMAIS LE SEUL CHEMIN. Il n'existe ni au clavier, ni au
   doigt, ni pour qui ne voit pas la zone. */
.vd-depot { position: relative; }
.vd-depot__saisie {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}
.vd-depot__zone {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: var(--espace-l);
  border: 1px dashed var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  background: var(--surface);
  color: var(--texte-attenue);
  cursor: pointer;
  text-align: center;
}
.vd-depot__zone b { color: var(--texte); font-weight: var(--graisse-appuyee); font-size: var(--texte-corps); }
.vd-depot__regle { font-size: var(--texte-legende); }
.vd-depot__zone:hover { border-color: var(--accent); }
/* Le focus de la saisie masquée se voit SUR LA ZONE : sans cela, un lecteur au clavier
   atteindrait le champ sans qu'aucun pixel ne le lui dise. */
.vd-depot__saisie:focus-visible + .vd-depot__zone { outline: 2px solid var(--accent); outline-offset: 2px; }
.vd-depot.est-survole .vd-depot__zone { border-color: var(--accent); background: var(--surface-haute); }
/* Ce qui a été déposé se dit, et se lit. Une liste vide n'est pas une liste. */
.vd-depot__liste { list-style: none; margin: var(--espace-s) 0 0; padding: 0; font-size: var(--texte-interface); }
.vd-depot__liste li { display: flex; align-items: center; gap: var(--espace-s); padding: var(--espace-xs) 0; border-block-end: 1px solid var(--bordure); }
.vd-depot__liste li:last-child { border-block-end: 0; }
.vd-depot__poids { margin-inline-start: auto; color: var(--texte-attenue); font-variant-numeric: tabular-nums; }

/* ============================================================================================
   LOT B : les onglets, la case à cocher, la saisie de nombre.
   TROIS familles, pas quatre. L'interrupteur ne se crée pas : voir le §6 duodecies du canon.
   ============================================================================================ */

/* ---------- Les onglets ----------

   L'ÉTAT MESURÉ, 25/08/2026 : 13 déclencheurs portant `role="tab"`, sur trois bases, et
   ZÉRO ne porte `aria-controls`. Aucun onglet n'est donc relié à son panneau : qui ne voit pas
   l'écran active un onglet et n'a aucun moyen de savoir quelle région a changé, ni de s'y rendre.
   Les trois bases portaient bien `role` et `aria-selected`, ce que le catalogue avait relevé ;
   il n'avait pas regardé le lien vers le panneau.

   `aria-controls` est donc obligatoire, et le panneau porte `role="tabpanel"` avec
   `aria-labelledby` pointant sur son onglet. Le lien se fait dans les DEUX sens, sinon il ne se
   parcourt que dans un. */
.vd-onglets {
  display: flex; gap: var(--espace-xxxs);
  border-block-end: 1px solid var(--bordure);
  /* overflow-x seul rend overflow-y AUTO, et un pixel de dépassement suffit à faire
     apparaître une barre de défilement VERTICALE à côté des onglets. Constat de Colomban sur
     capture, 26/08/2026. La barre horizontale se cache comme celle des pastilles de filtre. */
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}
.vd-onglets::-webkit-scrollbar { display: none; }
.vd-onglet {
  box-sizing: border-box;
  min-block-size: var(--controle-hauteur);
  padding-block: 0;
  font: inherit; font-size: var(--texte-interface); font-weight: var(--graisse-appuyee);
  padding-inline: 14px;
  border: 0; background: transparent;
  color: var(--texte-attenue);
  cursor: pointer;
  white-space: nowrap;
  /* Le liseré de sélection COIFFE, comme celui d'une tuile : §6 bis. Il est posé en bas ici
     parce qu'un onglet se rattache au panneau qui le suit, et le trait dit ce rattachement. */
  border-block-end: 2px solid transparent;
  margin-block-end: -1px;
}
.vd-onglet:hover { color: var(--texte); }
.vd-onglet:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.vd-onglet[aria-selected="true"] { color: var(--texte); border-block-end-color: var(--accent); }
/* L'état ne tient JAMAIS à la seule couleur : la graisse change aussi, et `aria-selected` le
   dit à qui ne voit pas l'écran. §6. */
.vd-onglet[aria-selected="true"] { font-weight: var(--graisse-forte); }
.vd-panneau:not([hidden]) { padding-block-start: var(--espace-m); }

/* ---------- La case à cocher ----------

   L'ÉTAT MESURÉ, ET IL CORRIGE CE QUE JE M'APPRÊTAIS À ÉCRIRE : 76 cases dans le parc,
   TOUTES NOMMÉES. Un premier relevé cherchait `aria-label` ou `id` et en comptait 22 sans nom ;
   vérification faite sur pièce, ces 22 sont ENVELOPPÉES dans leur étiquette, ce qui les nomme
   parfaitement. Il n'y a pas de défaut d'accessibilité à corriger ici.

   Ce composant ne répare donc rien : il unifie une forme, et c'est tout ce qu'il prétend.
   La forme de référence est celle qui marche déjà, l'enveloppement, parce qu'elle donne le nom
   ET agrandit la cible de clic à toute l'étiquette. */
.vd-case {
  display: inline-flex; align-items: flex-start; gap: var(--espace-s);
  cursor: pointer; font-size: var(--texte-corps); line-height: 1.45;
}
.vd-case input[type="checkbox"] {
  flex: none;
  inline-size: 16px; block-size: 16px;
  margin: var(--espace-xxxs) 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.vd-case input[type="checkbox"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vd-case input[type="checkbox"]:disabled { cursor: not-allowed; }
.vd-case:has(input:disabled) { color: var(--texte-attenue); cursor: not-allowed; }
.vd-case__aide { display: block; font-size: var(--texte-legende); color: var(--texte-attenue); margin-block-start: var(--espace-xxxs); }

/* ---------- Liste de tâches ----------

   Groupe monté au socle sur demande de Colomban, 10/09/2026, mot pour mot : « le focntionnement
   des tâches doit être exactement comme sur [relation-yeu-continent] (sauf que le bouton
   d'archive doit etre en haut aligné à droite, et pas coller au titre) > crée un groupe de
   composants au socle en ce sens et integre le a lé space SYCLEF. »

   REPRIS DE RELATION (`.todo`, local à `products/relation/overrides.css`), à l'exception du
   bouton d'archive : chez Relation il suit le texte du titre dans le même flux, et deux mots un
   peu longs le poussent sur une seconde ligne. `.vd-taches__entete` le pousse à droite avec
   `.vd-carte__tete__fin`, qui fait déjà ce travail dans l'entête d'une carte (§ vd-carte__tete) :
   la même règle vaut ici, `.vd-carte__tete__fin` ne dépendant que d'un parent flexible, jamais du
   nom de ce parent.

   UNE CASE N'A QU'UNE FORME, `.vd-case` : une tâche ne s'en distingue que par une chose, le texte
   qui se barre une fois cochée. Le champ et le sélecteur d'attribution prennent `.vd-champ__saisie`,
   qui habille l'un comme l'autre sans règle propre (§ vd-champ__saisie). */
.vd-taches__entete { display: flex; align-items: center; gap: var(--espace-s); font-size: var(--texte-interface); font-weight: var(--graisse-appuyee); margin-block-end: var(--espace-xs); }
.vd-taches { list-style: none; margin: 0; padding: 0; }
.vd-taches__ligne { border-block-end: 1px solid var(--bordure); padding: var(--espace-s) 0; }
.vd-taches__ligne:last-child { border-block-end: 0; }
.vd-taches__ligne .vd-case { inline-size: 100%; }
.vd-taches__ligne .vd-case input:checked ~ span { text-decoration: line-through; color: var(--texte-attenue); }
.vd-taches__ajout { display: flex; gap: var(--espace-s); flex-wrap: wrap; align-items: center; margin-block-start: var(--espace-m); }
.vd-taches__ajout > .vd-champ__saisie:first-child { flex: 1; min-inline-size: 200px; }
.vd-taches__ajout select.vd-champ__saisie { flex: none; inline-size: auto; cursor: pointer; }

/* ---------- La saisie de nombre ----------

   L'ÉTAT MESURÉ : 61 saisies `type="number"` sur six espaces, et NEUF portent `inputmode`.
   Les cinquante-deux autres ouvrent un clavier alphabétique sur un téléphone, pour saisir un
   nombre. C'est le défaut le plus coûteux de la famille, et il ne se voit jamais sur un poste
   de travail : il ne se voit qu'au doigt.

   Elle n'est pas une famille : elle prend `.vd-champ`, comme la saisie de date du §6 undecies. */
.vd-champ__saisie[type="number"] {
  font-variant-numeric: tabular-nums;
  text-align: end;
  /* LE COMPTEUR NATIF RESTE VISIBLE, ET SA PLACE EST RÉSERVÉE. Constat de Colomban sur capture,
     26/08/2026 : « pas centré et pas harmonieux ». Le navigateur ne montre ses flèches qu'au
     survol, et elles viennent alors se poser SUR les chiffres alignés en fin de champ, ce qui
     fait sauter la valeur au passage de la souris. Les montrer en permanence coûte une largeur
     fixe et supprime le saut ; les masquer supprimerait la seule façon d'incrémenter à la
     souris. */
  padding-inline-end: 4px;
}
.vd-champ__saisie[type="number"]::-webkit-inner-spin-button { opacity: 1; margin-inline-start: 6px; }
/* Une saisie de nombre ne se borne pas en largeur par un pixel : un nombre long, une devise, ou
   un allongement de traduction en sortiraient. Elle se borne par ses caractères. */
/* 9ch pour les chiffres, plus la largeur du compteur natif, qui n'est pas du texte. */
.vd-champ__saisie[type="number"].vd-champ__saisie--court { inline-size: calc(9ch + 34px); }

/* ==========================================================================
   LOT C : L'ACCORDÉON PILOTÉ, L'AVATAR, LE FIL D'ARIANE
   Mesuré le 25/08/2026 sur les 11 bases de code, 25 fichiers, hors leaflet et hors .min.
   ========================================================================== */

/* ---------- L'accordéon, piloté ----------

   ARBITRAGE DE COLOMBAN, 25/08/2026, rendu APRÈS avoir éprouvé les deux mécaniques côte à côte
   dans la page d'arbitrage : le bouton `aria-expanded` piloté, contre la recommandation qui
   allait au `<details>` natif. Le coût est nommé et assumé : SANS SCRIPT, RIEN NE S'OUVRE.
   Le socle livre donc le script, `accordeon.js`, pour que ce coût soit payé une fois et non
   onze. Un produit qui réécrit l'état à la main réintroduit le défaut mesuré ci-dessous.

   L'ÉTAT MESURÉ : 19 déclencheurs portent `aria-expanded`, SIX portent `aria-controls`. Treize
   annoncent donc qu'ils ouvrent quelque chose sans dire quoi. Qui ne voit pas l'écran entend
   « replié », déplie, et n'a aucun moyen de savoir quelle région vient d'apparaître ni de s'y
   rendre. Quatre `<details>` natifs subsistent par ailleurs, et ils restent légitimes là où ils
   sont : le menu à trois points d'une carte n'est pas un accordéon.

   L'animation passe par `grid-template-rows`, jamais par `max-height` : une hauteur maximale
   devinée est soit trop courte, et le contenu se coupe, soit trop longue, et l'animation traîne
   dans le vide avant que le volet ne bouge. */
.vd-accordeon {
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  background: var(--surface-haute);
}
.vd-accordeon + .vd-accordeon { margin-block-start: var(--espace-s); }
.vd-accordeon__bouton {
  box-sizing: border-box;
  min-block-size: var(--controle-hauteur-ample);
  padding-block: 0;
  font: inherit;
  inline-size: 100%;
  text-align: start;
  display: flex;
  align-items: center;
  gap: var(--espace-s);
  padding-inline: var(--espace-m);
  background: none;
  color: inherit;
  border: 0;
  font-weight: var(--graisse-forte);
  font-size: var(--texte-corps);
  cursor: pointer;
}
.vd-accordeon__bouton:hover { background: var(--surface); }
.vd-accordeon__bouton:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Le chevron tourne, il ne se remplace pas : deux glyphes échangés sautent, un seul pivote. */
.vd-accordeon__bouton::before {
  content: "";
  inline-size: 0; block-size: 0; flex: none;
  border-inline-start: 6px solid currentColor;
  border-block: 4px solid transparent;
  transition: transform .2s;
}
.vd-accordeon__bouton[aria-expanded="true"]::before { transform: rotate(90deg); }
.vd-accordeon__volet {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows .2s ease;
}
.vd-accordeon__bouton[aria-expanded="true"] + .vd-accordeon__volet { grid-template-rows: 1fr; }
/* Sans `min-block-size: 0`, l'enfant d'une grille refuse de descendre sous sa hauteur de
   contenu et le volet ne se referme jamais complètement. */
.vd-accordeon__volet > * { min-block-size: 0; }
/* ET CELA NE SUFFIT PAS. Mesuré sur banc le 26/08/2026 : le volet replié rendait 14 px au lieu
   de zéro, soit un liseré visible sous chaque accordéon fermé. `min-block-size` borne la boîte
   de contenu, pas le remplissage : une piste `0fr` ne descend jamais sous le remplissage de son
   élément de grille. Le remplissage se retire donc à l'état replié, et il s'anime avec la piste
   pour que rien ne saute à l'ouverture. Le défaut n'existait pas dans la démonstration du
   25/08 parce qu'elle portait un élément intercalaire sans remplissage, ce que le canon ne
   demandait pas : la forme documentée et la forme éprouvée n'étaient pas la même. */
.vd-accordeon__volet > * { transition: padding .2s ease; }
.vd-accordeon__bouton[aria-expanded="false"] + .vd-accordeon__volet > * { padding-block: 0; }
.vd-accordeon__corps { padding: 0 var(--espace-m) var(--espace-m); font-size: var(--texte-interface); }
@media (prefers-reduced-motion: reduce) {
  .vd-accordeon__volet, .vd-accordeon__volet > *, .vd-accordeon__bouton::before { transition: none; }
}

/* ---------- L'avatar, monogramme ----------

   L'ÉTAT MESURÉ : cinq avatars sur deux bases, tous de classe `av`. TROIS portent le nom et la
   fonction de la personne dans un attribut `title`, DEUX ne portent rien du tout. Dans les deux
   cas le monogramme est muet : « CB » ne dit pas Camille Brossard, et un `title` n'existe ni au
   doigt, ni au clavier, ni à la synthèse vocale. Le nom passe donc dans le balisage.

   ARBITRAGE DE COLOMBAN, 25/08/2026 : empilement en propriété logique. Relation empile déjà par
   une marge négative de 7 px, ce qui est voulu et se garde, mais cette marge est PHYSIQUE : elle
   ne se retourne pas en écriture de droite à gauche, où le groupe s'empilerait à l'envers. */
.vd-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--controle-hauteur-compacte); block-size: var(--controle-hauteur-compacte);
  border-radius: 999px;
  background: var(--accent);
  color: #FFF;
  font-size: var(--texte-mention);
  font-weight: var(--graisse-forte);
  letter-spacing: .02em;
  text-transform: uppercase;
  flex: none;
  user-select: none;
}
.vd-avatar img { inline-size: 100%; block-size: 100%; border-radius: inherit; object-fit: cover; }
/* Le groupe empile. `margin-inline-start` se retourne, `margin-left` non. */
.vd-avatar-groupe { display: inline-flex; align-items: center; }
.vd-avatar-groupe > .vd-avatar { border: 2px solid var(--surface-haute); }
.vd-avatar-groupe > .vd-avatar + .vd-avatar { margin-inline-start: -7px; }
/* Le dernier jeton d'un groupe tronqué, « +4 », n'est pas une personne : il ne prend pas
   l'accent produit, sans quoi il se lit comme un visage de plus. */
.vd-avatar--reste { background: var(--texte-attenue); }

/* ---------- Le fil d'Ariane ----------

   L'ÉTAT MESURÉ : douze fils sur deux bases, et UNE SEULE forme. C'est la famille la moins
   divergente du parc, elle entre au socle telle quelle. Le parc porte par ailleurs neuf `<nav>`
   dont QUATRE nommés : cinq régions de navigation sont annoncées sans dire laquelle, ce que
   corrige l'`aria-label` obligatoire ci-dessous.

   ARBITRAGE DE COLOMBAN, 25/08/2026 : un `<nav>` nommé, dernier maillon non cliquable. Le
   dernier maillon est la page courante ; le rendre cliquable ferait un lien vers soi-même. */
.vd-ariane { font-size: var(--texte-legende); color: var(--texte-attenue); }
.vd-ariane ol {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--espace-xs);
  margin: 0; padding: 0; list-style: none;
}
.vd-ariane li { display: inline-flex; align-items: center; gap: var(--espace-xs); }
/* Le séparateur est décoratif : il naît du CSS et n'entre donc jamais dans la synthèse vocale,
   qui annoncerait sinon « barre oblique » entre chaque maillon. */
.vd-ariane li + li::before { content: "/"; color: var(--bordure); }
.vd-ariane a { color: inherit; text-decoration: none; border-block-end: 1px solid transparent; }
@media (max-width: 620px) {
  /* LOT Q. Mesuré à 390 px : 20 px de haut, sous le seuil de ce qui se touche. */
  .vd-ariane a { display: inline-flex; align-items: center; min-block-size: 24px; }
}
.vd-ariane a:hover { color: var(--texte); border-block-end-color: var(--bordure); }
.vd-ariane a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vd-ariane [aria-current="page"] { color: var(--texte); font-weight: var(--graisse-forte); }

/* ==========================================================================
   LOT D : L'INFOBULLE ET SON INTERDIT, LA CARTE GÉOGRAPHIQUE, LA FERMETURE DU BOUTON
   Mesuré le 25/08/2026 sur les 11 bases de code, 25 fichiers, hors leaflet et hors .min.
   ========================================================================== */

/* ---------- L'infobulle, posée sur sa cible ----------

   ARBITRAGE DE COLOMBAN, 25/08/2026 : un vrai composant, et `title` INTERDIT.

   L'ÉTAT MESURÉ, ET IL JUGE LE SOCLE AUTANT QUE LE PARC : 65 balises portent un attribut
   `title`, et `.vd-infobulle`, au socle depuis le 22/08/2026, est employé ZÉRO fois. Un
   composant que personne n'adopte n'existe pas. La cause est mécanique : l'ancienne forme
   demandait d'envelopper la cible dans un élément de plus, donc de toucher au balisage de
   538 boutons. `title` demandait un attribut.

   D'où cette seconde forme, qui se pose SUR la cible sans l'envelopper : un attribut de classe,
   un texte, et rien à déplacer. `infobulle.js` fait même la conversion depuis `title`. */
.vd-infobulle-cible { position: relative; }
.vd-infobulle-cible > .vd-infobulle__texte { inset-block-end: calc(100% + 6px); }
.vd-infobulle-cible:hover > .vd-infobulle__texte,
.vd-infobulle-cible:focus-visible > .vd-infobulle__texte,
.vd-infobulle-cible:focus-within > .vd-infobulle__texte { opacity: 1; }
/* Près du bord haut de la fenêtre, la bulle sortirait de l'écran. Le modificateur la passe
   dessous. Il se pose à la main, en connaissance de cause : le socle ne mesure pas la
   position au moment de l'affichage, et prétendre le contraire serait un témoin vert qui ne
   couvre pas le chemin réel. */
.vd-infobulle__texte--dessous {
  inset-block-end: auto;
  inset-block-start: calc(100% + 6px);
}

/* ---------- La carte géographique ----------

   ARBITRAGE DE COLOMBAN, 25/08/2026 : le socle habille, la bibliothèque reste tierce.

   L'ÉTAT MESURÉ : quatre cartes sur trois bases, Intelligence 1, Relation 2, Network 1.
   Intelligence pose SIX règles correctives sur Leaflet, Network en pose QUATRE, Relation
   AUCUNE. Les deux qui corrigent ne corrigent ni les mêmes choses ni avec les mêmes valeurs :
   Intelligence branche ses propres jetons, Network écrit `#DCE3EA` en dur. Celle qui ne
   corrige rien garde le `font-size` de 12 px de la bibliothèque et son chrome par défaut.

   Le socle ne peut pas fournir une carte, il fournit son habillage. Leaflet reste une feuille
   tierce, hissée en tête de cascade par le manifeste, règle du 22/08/2026 : ces sélecteurs
   passent donc APRÈS elle et gagnent sans `!important`. */
/* Leaflet pose ses contrôles à z-index 1000 : sans contexte isolé, ils passent au-dessus des
   surfaces fixes du socle, le bouton de retours compris, et prennent ses clics (Vigie, 22/10/2026). */
.vd-carte-geo {
  position: relative;
  isolation: isolate;
  block-size: 420px;
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-moyen));
  overflow: hidden;
  background: var(--surface);
}
.vd-carte-geo--haute { block-size: 620px; }
.vd-carte-geo--basse { block-size: 260px; }
/* LA PLEINE HAUTEUR, quatrième variante. Arbitrage de Colomban, 08/09/2026 : « Tout doit etre
   réalisé au socle (ou créer de nouveaux composants au socle si pas possible). » La carte qui
   remplit l'écran, demandée pour l'annuaire Syclef sur le modèle armateurscotiers.fr : elle
   prend toute la hauteur de son parent, qui doit donc en avoir une. */
.vd-carte-geo--pleine { block-size: 100%; }
/* La bibliothèque impose sa police et sa taille. Le socle les reprend, une fois pour toutes. */
/* Le fond d'attente de la carte se pose depuis le composant, à la couleur d'eau du fond
   choisi (fond.eau, cartepage.js) : aucune couleur ne s'écrit en dur ici, le témoin le refuse. */
.vd-carte-geo.leaflet-container,
.vd-carte-geo .leaflet-container { font: inherit; background: var(--surface); }
.vd-carte-geo .leaflet-popup-content-wrapper {
  background: var(--surface-haute);
  color: var(--texte);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  box-shadow: var(--ombre-levee);
}
.vd-carte-geo .leaflet-popup-content { margin: var(--espace-m); font-size: var(--texte-interface); line-height: 1.45; }
.vd-carte-geo .leaflet-popup-tip { background: var(--surface-haute); }
.vd-carte-geo .leaflet-bar { border: 0; box-shadow: var(--ombre-posee); }
.vd-carte-geo .leaflet-bar a {
  background: var(--surface-haute);
  color: var(--texte);
  border-block-end-color: var(--bordure);
}
.vd-carte-geo .leaflet-bar a:hover { background: var(--surface); }
.vd-carte-geo .leaflet-bar a:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Le marqueur prend la couleur du PRODUIT, pas une couleur écrite ici. C'est la seule variable
   d'ajustement de la famille, et elle vient de `--accent`. */
.vd-carte-geo__marqueur {
  /* SANS display, un span reste en ligne et ses dimensions sont IGNORÉES : le marqueur était
     invisible, y compris dans sa propre légende. Constat de Colomban, 26/08/2026. */
  display: inline-block;
  vertical-align: -2px;
  inline-size: 14px; block-size: 14px;
  border-radius: 999px;
  background: var(--accent);
  border: 2px solid var(--surface-haute);
  box-shadow: var(--ombre-posee);
}
.vd-carte-geo__legende {
  position: absolute;
  inset-block-end: var(--espace-s);
  inset-inline-start: var(--espace-s);
  z-index: var(--plan-flottant);
  background: var(--surface-haute);
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  padding: var(--espace-s) var(--espace-m);
  font-size: var(--texte-legende);
}

/* ============================================================================
   LA CARTE PLEINE PAGE, AVEC SON CARTOUCHE ET SA COLONNE

   ARBITRAGE DE COLOMBAN, 08/09/2026, mot pour mot : « Tout doit etre réalisé au socle (ou
   créer de nouveaux composants au socle si pas possible). » Mesure qui la motive : l'annuaire
   Syclef demande la carte façon armateurscotiers.fr/nos-adherents, pleine page, tuiles pays
   et métiers en bas à gauche, liste des entités à droite. Deux produits au moins portent une
   carte d'annuaire (Network, Relation), le dessin ne se redessine pas deux fois.

   CE QU'ELLE EST. Une grille : la carte à gauche, la colonne synchronisée à droite. Le
   cartouche flotte en bas à gauche de la carte. L'hôte de la bibliothèque ISOLE sa pile de
   plans chiffrés (Leaflet en fixe de 100 à 1000, figés dans sa feuille tierce) : le cartouche
   se pose en frère suivant, au degré relevé de l'échelle des plans, et aucun plan en chiffres
   ne s'écrit nulle part.

   SA HAUTEUR SE RÈGLE UNE FOIS, par `--vd-cartepage-retrait` : ce que la page retranche au-
   dessus d'elle (barre d'application, barre d'outils). Le socle donne le défaut, le produit
   ajuste, l'espace n'écrit rien. */
.vd-cartepage {
  display: grid;
  grid-template-columns: 1fr minmax(300px, 30%);
  block-size: calc(100dvh - var(--vd-cartepage-retrait, 220px));
  min-block-size: 420px;
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-moyen));
  overflow: clip;
}
.vd-cartepage__carte { position: relative; block-size: 100%; }
.vd-cartepage__hote {
  position: absolute; inset: 0;
  isolation: isolate;
  block-size: 100%;
  z-index: var(--plan-contenu);
}
.vd-cartepage__colonne {
  overflow-y: auto;
  border-inline-start: 1px solid var(--bordure);
  background: var(--fond);
}
.vd-cartepage__ligne {
  display: flex; align-items: center; gap: var(--espace-xxxs);
  border-block-end: 1px solid var(--bordure);
  padding-inline-end: var(--espace-xxxs);
}
.vd-cartepage__vol {
  flex: 1; min-inline-size: 0;
  display: flex; align-items: center; gap: var(--espace-s);
  padding: var(--espace-s) var(--espace-m);
  border: 0; background: transparent; cursor: pointer;
  font: inherit; color: inherit; text-align: start;
}
.vd-cartepage__vol:hover { background: var(--surface); }
.vd-cartepage__vol:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.vd-cartepage__texte { display: grid; min-inline-size: 0; }
.vd-cartepage__nom {
  font-weight: var(--graisse-appuyee); font-size: var(--texte-interface);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vd-cartepage__lieu { font-size: var(--texte-mention); color: var(--texte-attenue); }

/* Le cartouche flottant : tuiles de cadrage et de filtre, en bas à gauche de la carte. Il ne
   dépasse jamais la carte : au-delà de sa hauteur, il défile dans sa propre boîte. */
.vd-cartouche {
  position: absolute;
  inset-inline-start: var(--espace-m);
  inset-block-end: var(--espace-m);
  z-index: var(--plan-releve);
  display: grid; gap: var(--espace-xs);
  max-inline-size: min(520px, calc(100% - 96px));
  max-block-size: calc(100% - 2 * var(--espace-m));
  overflow-y: auto;
  /* LA BOÎTE LAISSE PASSER LE GESTE, SES ENFANTS LE REPRENNENT. Défaut mesuré le 09/09/2026
     sur Syclef : chargé de tuiles, le cartouche couvre la moitié gauche de la carte, et sa
     surface TRANSPARENTE interceptait pointeur et molette — la carte ne zoomait plus à la
     molette alors que `scrollWheelZoom` était vrai et qu'un événement synthétique, distribué
     sans test de position, passait. Un flottant ne doit occuper que ce qu'il montre. */
  pointer-events: none;
}
.vd-cartouche > * { pointer-events: auto; }
.vd-cartouche__groupe { display: flex; flex-wrap: wrap; gap: var(--espace-xxs); align-items: center; }
.vd-cartouche__tuile {
  display: inline-flex; align-items: center; gap: var(--espace-xxs);
  background: var(--surface-haute);
  border: 1px solid var(--bordure);
  border-radius: var(--accent-rayon, var(--courant-rayon-petit));
  box-shadow: var(--ombre-posee);
  padding: var(--espace-xxs) var(--espace-s);
  font: var(--graisse-appuyee) var(--texte-legende) var(--police-interface);
  color: var(--texte); cursor: pointer;
}
.vd-cartouche__tuile:hover { border-color: var(--accent); color: var(--accent); }
.vd-cartouche__tuile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vd-cartouche__tuile--active,
.vd-cartouche__tuile[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
.vd-cartouche__compte { font-variant-numeric: tabular-nums; opacity: .7; }
@media (max-width: 719px) {
  .vd-cartepage { grid-template-columns: 1fr; grid-template-rows: 1fr minmax(180px, 34%); }
  .vd-cartepage__colonne { border-inline-start: 0; border-block-start: 1px solid var(--bordure); }
  /* RELEVÉ le 10/09/2026, constat de Colomban : « sur mobile les filtres rapides sur la carte
     doivent disparaître car ils masquent la vue. » Sous 720px, la ligne de la carte tombe déjà
     à 1fr d'une hauteur de fenêtre réduite : un cartouche chargé de tuiles de cadrage et de
     filtre y couvre une part disproportionnée de ce qui reste à voir. Le cartouche se retire,
     la carte redevient lisible ; le filtre par métier reste accessible par les tuiles de la
     vue Annuaire, hors carte. */
  .vd-cartouche { display: none; }
}

/* ============================================================================
   LE CHAMP MULTI-VALEURS

   ARBITRAGE DE COLOMBAN, 08/09/2026, même parole que la carte pleine page. Mesure qui le
   motive : la fiche Syclef porte plusieurs téléphones, courriels, sites et implantations par
   entité, demande de l'atelier du même jour. Aucun composant du socle ne répétait une saisie :
   `vd-champ` en pose UNE.

   CE QU'IL EST. Une colonne de lignes de saisie, chacune avec son geste de retrait, l'ajout en
   pied. Le comportement (ajout, retrait) vit dans le produit ; le socle porte la tenue : le
   champ est `vd-champ__saisie`, le retrait est le bouton d'icône discret, l'ajout est le bouton
   discret. Il ne dessine rien que le champ ne dessine déjà, il le répète proprement. */
.vd-multi { display: grid; gap: var(--espace-xs); }
.vd-multi__ligne { display: flex; gap: var(--espace-xs); align-items: center; }
.vd-multi__ligne > .vd-champ__saisie { flex: 1; margin-block-end: 0; }

/* ============================================================================
   L'ÉTAT DÉSACTIVÉ, POUR TOUS LES CONTRÔLES À LA FOIS

   Mesuré le 27/08/2026, sur question de Colomban : sur DIX-HUIT composants interactifs, DEUX
   avaient un état désactivé, la saisie et la case. Le bouton n'en avait pas. Un formulaire qui
   part n'avait donc rien pour dire qu'il est en train de partir, et un produit qui doit interdire
   une action le faisait chacun à sa façon, ou pas du tout.

   IL SE DIT PAR TROIS SIGNES, JAMAIS PAR LA COULEUR SEULE. Un contraste abaissé ne se voit pas de
   tous ; le curseur d'interdiction et l'absence de réaction au survol se voient de tous. Les trois
   ensemble, parce qu'aucun ne suffit : le curseur ne se voit qu'à la souris, le survol ne se
   constate qu'en essayant, et la teinte ne se lit pas sur tous les écrans.

   PAS DE `pointer-events: none`, ET C'EST DÉLIBÉRÉ. C'est la réponse facile, et elle supprime
   l'infobulle qui dit POURQUOI le contrôle est fermé. Un bouton mort sans explication fait
   recharger la page ; un bouton mort qui dit « il manque la date de départ » fait remplir la date.
   Le survol reste donc atteignable, il ne fait simplement plus semblant d'être cliquable.

   DEUX ÉCRITURES, ET ELLES NE SONT PAS INTERCHANGEABLES.
   • `:disabled` retire l'élément de l'ordre de tabulation. C'est juste pour un bouton d'un
     formulaire qu'on remplit encore : rien à y faire, rien à y lire.
   • `[aria-disabled='true']` le garde atteignable au clavier. C'est ce qu'il faut pour un onglet,
     une entrée de navigation, une action de ligne : un contrôle qui disparaît de la tabulation
     sans un mot est un contrôle que l'utilisateur au clavier cherchera sans le trouver.
   Le socle dessine les deux à l'identique ; c'est au produit de choisir lequel il pose, et la
   règle est écrite ici pour qu'il n'ait pas à la redécouvrir.

   L'OPACITÉ N'EST PAS EMPLOYÉE. Elle traverse l'enfant, donc elle éteint aussi l'icône et la
   pastille de compte, et son résultat dépend du fond : sur le rail bleu de nuit, 50 % d'opacité
   rend un gris qui n'a été choisi par personne. On pose des couleurs nommées.
   ============================================================================ */

.vd-button:disabled, .vd-button[aria-disabled='true'],
.vd-iconbtn:disabled, .vd-iconbtn[aria-disabled='true'],
.vd-chip:disabled, .vd-chip[aria-disabled='true'],
.vd-onglet:disabled, .vd-onglet[aria-disabled='true'],
.vd-segments__part:disabled, .vd-segments__part[aria-disabled='true'],
.vd-rail__lien:disabled, .vd-rail__lien[aria-disabled='true'],

.vd-lanceur:disabled, .vd-lanceur[aria-disabled='true'],
.vd-select__declencheur:disabled, .vd-select__declencheur[aria-disabled='true'],
.vd-recherche__saisie:disabled,
.vd-barre-basse__entree:disabled, .vd-barre-basse__entree[aria-disabled='true'],
.vd-table__tri:disabled, .vd-table__tri[aria-disabled='true'],
.vd-pasapas__etape[aria-disabled='true'],
.vd-calendrier__pas:disabled, .vd-calendrier__pas[aria-disabled='true'],
.vd-etiquette__retirer:disabled, .vd-etiquette__retirer[aria-disabled='true'] {
  cursor: not-allowed;
  color: var(--texte-attenue);
  border-color: var(--bordure);
  background: var(--surface);
  box-shadow: none;
}
/* LE SURVOL NE RÉPOND PLUS. Sans cette annulation, la règle de survol, à spécificité égale,
   continue de rendre le contrôle plus contrasté sous le curseur : il a l'air de s'allumer au
   moment précis où il refuse. */
.vd-button:disabled:hover, .vd-button[aria-disabled='true']:hover,
.vd-iconbtn:disabled:hover, .vd-iconbtn[aria-disabled='true']:hover,
.vd-chip:disabled:hover, .vd-chip[aria-disabled='true']:hover,
.vd-onglet:disabled:hover, .vd-onglet[aria-disabled='true']:hover,
.vd-segments__part:disabled:hover, .vd-segments__part[aria-disabled='true']:hover,
.vd-rail__lien:disabled:hover, .vd-rail__lien[aria-disabled='true']:hover,
.vd-lanceur:disabled:hover, .vd-lanceur[aria-disabled='true']:hover,
.vd-select__declencheur:disabled:hover, .vd-select__declencheur[aria-disabled='true']:hover,
.vd-champ__saisie:disabled:hover,
.vd-recherche__saisie:disabled:hover,
.vd-barre-basse__entree:disabled:hover, .vd-barre-basse__entree[aria-disabled='true']:hover,
.vd-table__tri:disabled:hover, .vd-table__tri[aria-disabled='true']:hover,
.vd-calendrier__pas:disabled:hover, .vd-calendrier__pas[aria-disabled='true']:hover,
.vd-etiquette__retirer:disabled:hover, .vd-etiquette__retirer[aria-disabled='true']:hover {
  color: var(--texte-attenue);
  border-color: var(--bordure);
  background: var(--surface);
}
/* L'ÉTIQUETTE QUI ENVELOPPE SA SAISIE. L'interrupteur et le bouton radio ne sont pas le contrôle,
   ils l'entourent : c'est la saisie qui porte `disabled`, et l'étiquette doit suivre, sinon le mot
   reste noir à côté d'un contrôle éteint. */
.vd-interrupteur:has(input:disabled),
.vd-radio:has(input:disabled) { color: var(--texte-attenue); cursor: not-allowed; }
.vd-interrupteur input:disabled, .vd-radio input:disabled { cursor: not-allowed; }
/* L'ENTRÉE COURANTE DÉSACTIVÉE GARDE SON LISERÉ : on ne cache pas où l'on est parce que la
   destination est fermée. Elle perd sa teinte pleine, pas son repère. */
.vd-rail__lien[aria-disabled='true'][aria-current='page'],
.vd-onglet[aria-disabled='true'][aria-selected='true'],
.vd-barre-basse__entree[aria-disabled='true'][aria-current='page'] {
  color: var(--texte-attenue);
}

/* ============================================================================
   LE CURSEUR DE PLAGE

   Dernier manque du recensement, mesuré le 27/08/2026 : une occurrence dans le parc, chez
   Sextant, un retard simulé de 0 à 60 minutes par pas de 5. Le socle n'en avait aucun, donc
   elle portait le curseur brut du navigateur, dont le dessin change d'un poste à l'autre.

   IL PORTE SA VALEUR, TOUJOURS. Un curseur sans lecture chiffrée est un contrôle qu'on ne peut
   pas régler : on vise un endroit, on ne sait pas ce qu'on a choisi. La valeur est en chiffres
   tabulaires pour qu'elle ne saute pas de largeur quand elle change.

   LA POIGNÉE PREND LE SEUIL TACTILE, 24 px, pas la hauteur d'un contrôle. Une poignée de 40 px
   sur une piste de 4 px serait une bille sur un fil ; 24 px est le plus petit rond que le doigt
   sait attraper, et c'est la mesure que le paquet de jetons partagés porte déjà.

   DEUX DESSINS, PARCE QUE LES NAVIGATEURS N'EN LISENT PAS UN SEUL. `::-webkit-slider-thumb` et
   `::-moz-range-thumb` ne se groupent PAS dans un même sélecteur : un navigateur qui ne connaît
   pas l'une des deux pseudo-classes jette la règle entière. C'est la seule duplication assumée
   de cette feuille, et elle est écrite ici pour qu'on ne la prenne pas pour un oubli.
   ============================================================================ */
.vd-curseur { display: flex; align-items: center; gap: var(--espace-m); }
.vd-curseur__piste {
  flex: 1 1 auto; min-inline-size: 0;
  appearance: none; -webkit-appearance: none;
  block-size: var(--vd-cible-tactile, 24px);
  background: none; cursor: pointer;
}
.vd-curseur__piste::-webkit-slider-runnable-track {
  block-size: 4px; border-radius: 999px; background: var(--surface);
  border: 1px solid var(--bordure);
}
.vd-curseur__piste::-moz-range-track {
  block-size: 4px; border-radius: 999px; background: var(--surface);
  border: 1px solid var(--bordure);
}
.vd-curseur__piste::-webkit-slider-thumb {
  appearance: none; -webkit-appearance: none;
  inline-size: var(--vd-cible-tactile, 24px); block-size: var(--vd-cible-tactile, 24px);
  margin-block-start: -11px;
  border-radius: 999px; border: 2px solid var(--surface-haute);
  background: var(--accent); cursor: grab;
  box-shadow: var(--ombre-posee);
}
.vd-curseur__piste::-moz-range-thumb {
  inline-size: var(--vd-cible-tactile, 24px); block-size: var(--vd-cible-tactile, 24px);
  border-radius: 999px; border: 2px solid var(--surface-haute);
  background: var(--accent); cursor: grab;
  box-shadow: var(--ombre-posee);
}
.vd-curseur__piste:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* Le curseur fermé se dit comme les autres : curseur d'interdiction, encre atténuée, et la
   poignée cesse d'inviter à la prise. */
.vd-curseur__piste:disabled { cursor: not-allowed; }
.vd-curseur__piste:disabled::-webkit-slider-thumb { background: var(--texte-attenue); cursor: not-allowed; }
.vd-curseur__piste:disabled::-moz-range-thumb { background: var(--texte-attenue); cursor: not-allowed; }
.vd-curseur__valeur {
  flex: none; min-inline-size: 6ch; text-align: end;
  font-family: var(--police-donnee); font-size: var(--texte-interface);
  font-variant-numeric: tabular-nums; color: var(--texte);
}
